diff --git a/.gitignore b/.gitignore index 37b4f387e8..bee2e6579b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ tools/make/config.mk cflie.* version.c tags +*cffirmware*.so +cffirmware.py /cf2.* /tag.* @@ -21,6 +23,7 @@ current_platform.mk /generated/** **/__pycache__/** +**/*.pyc docs/.jekyll-metadata docs/.jekyll-cache diff --git a/Makefile b/Makefile index 8e4e5af466..1acc6d2b3c 100644 --- a/Makefile +++ b/Makefile @@ -493,4 +493,18 @@ unit: # The flag "-DUNITY_INCLUDE_DOUBLE" allows comparison of double values in Unity. See: https://stackoverflow.com/a/37790196 rake unit "DEFINES=$(CFLAGS) -DUNITY_INCLUDE_DOUBLE" "FILES=$(FILES)" "UNIT_TEST_STYLE=$(UNIT_TEST_STYLE)" -.PHONY: all clean build compile unit prep erase flash check_submodules trace openocd gdb halt reset flash_dfu flash_verify cload size print_version clean_version +# Python bindings +MOD_INC = $(CRAZYFLIE_BASE)/src/modules/interface +MOD_SRC = $(CRAZYFLIE_BASE)/src/modules/src + +bindings_python: bindings/setup.py bin/cffirmware_wrap.c $(MOD_SRC)/*.c + $(PYTHON) bindings/setup.py build_ext --inplace + +bin/cffirmware_wrap.c cffirmware.py: bindings/cffirmware.i $(MOD_INC)/*.h + swig -python -I$(MOD_INC) -o bin/cffirmware_wrap.c bindings/cffirmware.i + mv bin/cffirmware.py cffirmware.py + +test_python: bindings_python + $(PYTHON) -m pytest test_python + +.PHONY: all clean build compile unit prep erase flash check_submodules trace openocd gdb halt reset flash_dfu flash_verify cload size print_version clean_version bindings_python diff --git a/bindings/cffirmware.i b/bindings/cffirmware.i new file mode 100755 index 0000000000..43928ba0af --- /dev/null +++ b/bindings/cffirmware.i @@ -0,0 +1,70 @@ +%module cffirmware + +// ignore GNU specific compiler attributes +#define __attribute__(x) + +%{ +#define SWIG_FILE_WITH_INIT +#include "math3d.h" +%} + +%include "math3d.h" + +%inline %{ +%} + +%pythoncode %{ +import numpy as np +%} + +#define COPY_CTOR(structname) \ +structname(struct structname const *x) { \ + struct structname *y = malloc(sizeof(struct structname)); \ + *y = *x; \ + return y; \ +} \ +~structname() { \ + free($self); \ +} \ + +%extend vec { + COPY_CTOR(vec) + + %pythoncode %{ + def __repr__(self): + return "({}, {}, {})".format(self.x, self.y, self.z) + + def __array__(self): + return np.array([self.x, self.y, self.z]) + + def __len__(self): + return 3 + + def __getitem__(self, i): + if 0 <= i and i < 3: + return _cffirmware.vindex(self, i) + else: + raise IndexError("vec index must be in {0, 1, 2}.") + + # Unary operator overloads. + def __neg__(self): + return _cffirmware.vneg(self) + + # Vector-scalar binary operator overloads. + def __rmul__(self, s): + return _cffirmware.vscl(s, self) + + def __div__(self, s): + return self.__truediv__(s) + + def __truediv__(self, s): + return _cffirmware.vdiv(self, s) + + # Vector-vector binary operator overloads. + def __add__(self, other): + return _cffirmware.vadd(self, other) + + def __sub__(self, other): + return _cffirmware.vsub(self, other) + %} +}; diff --git a/bindings/setup.py b/bindings/setup.py new file mode 100644 index 0000000000..d0f2f3c072 --- /dev/null +++ b/bindings/setup.py @@ -0,0 +1,37 @@ +"""Compiles the cffirmware C extension.""" + +import distutils.command.build +from distutils.core import setup, Extension +import os + +fw_dir = "." +include = [ + os.path.join(fw_dir, "src/modules/interface"), +] + +modules = [ + # list firmware c-files here +] +fw_sources = [os.path.join(fw_dir, "src/modules/src", mod) for mod in modules] + +cffirmware = Extension( + "_cffirmware", + include_dirs=include, + sources=fw_sources + ["bin/cffirmware_wrap.c"], + extra_compile_args=[ + "-O3", + ], +) + +# Override build command to specify custom "build" directory +class BuildCommand(distutils.command.build.build): + def initialize_options(self): + distutils.command.build.build.initialize_options(self) + self.build_base = "bin" + +setup( + name="cffirmware", + version="1.0", + cmdclass={"build": BuildCommand}, + ext_modules=[cffirmware] +) diff --git a/test_python/test_math3d.py b/test_python/test_math3d.py new file mode 100644 index 0000000000..e4aee7044a --- /dev/null +++ b/test_python/test_math3d.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +import numpy as np +import cffirmware + +def test_conversion_to_numpy(): + v_cf = cffirmware.mkvec(1, 2, 3) + v_np = np.array(v_cf) + assert np.allclose(v_np, np.array([1,2,3])) diff --git a/tools/build/build b/tools/build/build index 452bd88d8c..925740adcb 100755 --- a/tools/build/build +++ b/tools/build/build @@ -4,5 +4,6 @@ set -e scriptDir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) ${scriptDir}/test "${@}" +${scriptDir}/test_python "${@}" ${scriptDir}/make "${@}" ${scriptDir}/check_elf diff --git a/tools/build/test_python b/tools/build/test_python new file mode 100755 index 0000000000..7fd93b33c6 --- /dev/null +++ b/tools/build/test_python @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -e + +scriptDir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) + +make test_python "${@}" diff --git a/tools/make/targets.mk b/tools/make/targets.mk index 52bc7a7bc2..cf65eac840 100644 --- a/tools/make/targets.mk +++ b/tools/make/targets.mk @@ -68,7 +68,7 @@ clean_o: clean_version @$(if $(QUIET), ,echo $(CLEAN_O_COMMAND$(VERBOSE)) ) @$(CLEAN_O_COMMAND) -CLEAN_COMMAND=rm -f cf*.elf cf*.hex cf*.bin cf*.dfu cf*.map $(BIN)/dep/*.d $(BIN)/*.o +CLEAN_COMMAND=rm -f cf*.elf cf*.hex cf*.bin cf*.dfu cf*.map cf*.py _cf*.so $(BIN)/dep/*.d $(BIN)/*.o $(BIN)/*.c CLEAN_COMMAND_SILENT=" CLEAN" clean: @$(if $(QUIET), ,echo $(CLEAN_COMMAND$(VERBOSE)) )