diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index b8c482aa..16861330 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -16,10 +16,10 @@ end python_command = find_python3() print('Using python command "'..python_command..'"') -tup.frule{inputs={'fibre/cpp/interfaces_template.j2'}, command=python_command..' interface_generator_stub.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/interfaces.hpp'} -tup.frule{inputs={'fibre/cpp/function_stubs_template.j2'}, command=python_command..' interface_generator_stub.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/function_stubs.hpp'} -tup.frule{inputs={'fibre/cpp/endpoints_template.j2'}, command=python_command..' interface_generator_stub.py --definitions odrive-interface.yaml --generate-endpoints ODrive --template %f --output %o', outputs='autogen/endpoints.hpp'} -tup.frule{inputs={'fibre/cpp/type_info_template.j2'}, command=python_command..' interface_generator_stub.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/type_info.hpp'} +tup.frule{inputs={'fibre-cpp/interfaces_template.j2'}, command=python_command..' interface_generator_stub.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/interfaces.hpp'} +tup.frule{inputs={'fibre-cpp/function_stubs_template.j2'}, command=python_command..' interface_generator_stub.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/function_stubs.hpp'} +tup.frule{inputs={'fibre-cpp/endpoints_template.j2'}, command=python_command..' interface_generator_stub.py --definitions odrive-interface.yaml --generate-endpoints ODrive --template %f --output %o', outputs='autogen/endpoints.hpp'} +tup.frule{inputs={'fibre-cpp/type_info_template.j2'}, command=python_command..' interface_generator_stub.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/type_info.hpp'} -- Note: we currently check this file into source control for two reasons: -- - Don't require tup to run in order to use odrivetool from the repo @@ -192,7 +192,7 @@ sources = { 'communication/interface_usb.cpp', 'communication/interface_can.cpp', 'communication/interface_i2c.cpp', - 'fibre/cpp/legacy_protocol.cpp', + 'fibre-cpp/legacy_protocol.cpp', 'FreeRTOS-openocd.c', 'autogen/version.c' } @@ -207,14 +207,14 @@ build{ includes={ 'Drivers/DRV8301', 'MotorControl', - 'fibre/cpp/include', + 'fibre-cpp/include', '.', "doctest" } } if tup.getconfig('DOCTEST') == 'true' then - TEST_INCLUDES = '-I. -I./MotorControl -I./fibre/cpp/include -I./Drivers/DRV8301 -I./doctest' + TEST_INCLUDES = '-I. -I./MotorControl -I./fibre-cpp/include -I./Drivers/DRV8301 -I./doctest' tup.foreach_rule('Tests/*.cpp', 'g++ -O3 -std=c++17 '..TEST_INCLUDES..' -c %f -o %o', 'Tests/bin/%B.o') tup.frule{inputs='Tests/bin/*.o', command='g++ %f -o %o', outputs='Tests/test_runner.exe'} tup.frule{inputs='Tests/test_runner.exe', command='%f'} diff --git a/Firmware/fibre-cpp/.gitignore b/Firmware/fibre-cpp/.gitignore new file mode 100644 index 00000000..7bbdcb78 --- /dev/null +++ b/Firmware/fibre-cpp/.gitignore @@ -0,0 +1 @@ +/third_party \ No newline at end of file diff --git a/Firmware/fibre-cpp/Makefile b/Firmware/fibre-cpp/Makefile index 8ae93b2c..8c4c41f1 100644 --- a/Firmware/fibre-cpp/Makefile +++ b/Firmware/fibre-cpp/Makefile @@ -1,16 +1,12 @@ -# pkgconf --cflags libusb -# pkgconf --libs libusb - -# Prerequisites: -# Ubuntu: libusb-dev - ifeq ($(OS),Windows_NT) target = windows ifeq ($(PROCESSOR_ARCHITEW6432),AMD64) outname = libfibre-windows-amd64.dll + else ifeq ($(PROCESSOR_ARCHITECTURE),AMD64) + outname = libfibre-windows-amd64.dll else - $(error unsupported platform) + $(error unsupported platform ${PROCESSOR_ARCHITECTURE}) endif else ifeq ($(shell uname -s),Linux) target = linux @@ -41,15 +37,10 @@ linux: g++ -shared -o $(outname) -fPIC -std=c++11 -I/usr/include/libusb-1.0 -I./include -DFIBRE_COMPILE -DFIBRE_ENABLE_CLIENT \ $(FILES) \ -lusb-1.0 - -linux-cross: - arm-linux-gnueabihf-g++ -march=armv7 -shared -o libfibre.so -fPIC -std=c++11 -I/usr/include/libusb-1.0 -I./include -DFIBRE_COMPILE -DFIBRE_ENABLE_CLIENT \ - $(FILES) \ - -lusb - windows: g++ -shared -o $(outname) -fPIC -std=c++11 -I./third_party/libusb-windows/libusb-1.0.23/include/libusb-1.0 -I./include -DFIBRE_COMPILE -DFIBRE_ENABLE_CLIENT \ $(FILES) \ -static-libgcc -Wl,-Bstatic -lstdc++ ./third_party/libusb-windows/libusb-1.0.23/MinGW64/static/libusb-1.0.a -Wl,-Bdynamic -# nm -D libfibre.so | grep T +# To check exported symbols run: +# nm -D libfibre.so | grep T diff --git a/Firmware/fibre-cpp/compile_for_all_platforms.sh b/Firmware/fibre-cpp/compile_for_all_platforms.sh index ca01651d..01bdbbe3 100755 --- a/Firmware/fibre-cpp/compile_for_all_platforms.sh +++ b/Firmware/fibre-cpp/compile_for_all_platforms.sh @@ -13,6 +13,7 @@ set -euo pipefail mkdir -p third_party +# Usage: download_deb_pkg destination-dir url function download_deb_pkg() { dir="$1" url="$2" @@ -30,6 +31,7 @@ function download_deb_pkg() { popd > /dev/null } +# Usage: compile_libusb arch-name arch function compile_libusb() { arch_name="$1" arch="$2" @@ -58,6 +60,25 @@ function compile_libusb() { popd > /dev/null } +FILES=('libfibre.cpp' + 'platform_support/libusb_transport.cpp' + 'legacy_protocol.cpp' + 'legacy_object_client.cpp' + 'logging.cpp') + +function build_for_linux() { + arch_name="$1" + "${CXX}" -shared -o libfibre-"$arch_name".so -fPIC -std=c++11 -I./include -DFIBRE_COMPILE -DFIBRE_ENABLE_CLIENT \ + ${CFLAGS} \ + "${FILES[@]}" \ + ${LIBS} \ + -lpthread \ + -Wl,--unresolved-symbols=ignore-in-shared-libs -static-libstdc++ +} + + +### Download/compile prerequisites + download_deb_pkg libusb-dev-amd64 "http://mirrors.kernel.org/ubuntu/pool/main/libu/libusb-1.0/libusb-1.0-0-dev_1.0.23-2build1_amd64.deb" download_deb_pkg libusb-amd64 "http://mirrors.kernel.org/ubuntu/pool/main/libu/libusb-1.0/libusb-1.0-0_1.0.23-2build1_amd64.deb" download_deb_pkg libusb-i386 "http://mirrors.kernel.org/ubuntu/pool/main/libu/libusb-1.0/libusb-1.0-0_1.0.23-2build1_i386.deb" @@ -65,35 +86,6 @@ download_deb_pkg libusb-dev-i386 "http://mirrors.kernel.org/ubuntu/pool/main/lib download_deb_pkg libusb-armhf "http://mirrordirector.raspbian.org/raspbian/pool/main/libu/libusb-1.0/libusb-1.0-0_1.0.23-2_armhf.deb" download_deb_pkg libusb-dev-armhf "http://mirrordirector.raspbian.org/raspbian/pool/main/libu/libusb-1.0/libusb-1.0-0-dev_1.0.23-2_armhf.deb" download_deb_pkg libstdc++-linux-armhf "http://mirrors.kernel.org/ubuntu/pool/universe/g/gcc-10-cross/libstdc++-10-dev-armhf-cross_10-20200411-0ubuntu1cross1_all.deb" - #compile_libusb 'x86_64-apple-darwin' # fails with "sys/sysctl.h: No such file or directory" - -_architectures=( - #'arm-linux-gnueabihf' - #'x86_64-apple-darwin' - #'x86_64-pc-linux-gnu' - #'i686-w64-mingw32' - #'x86_64-w64-mingw32' - ) - - -FILES=('libfibre.cpp' - 'platform_support/libusb_transport.cpp' - 'legacy_protocol.cpp' - 'legacy_object_client.cpp' - 'logging.cpp') - -### Raspberry Pi - -#arm-linux-gnueabihf-g++ -shared -o libfibre-linux-armhf.so -fPIC -std=c++11 -I./include -DFIBRE_COMPILE -DFIBRE_ENABLE_CLIENT \ -# -I./third_party/libusb-dev-armhf/usr/include/libusb-1.0 \ -# "${FILES[@]}" \ -# ./third_party/libusb-armhf/lib/arm-linux-gnueabihf/libusb-1.0.so.0.2.0 \ -# -lpthread \ -# -L./third_party/libstdc++-linux-armhf/usr/lib/gcc-cross/arm-linux-gnueabihf/10 \ -# -Wl,--unresolved-symbols=ignore-in-shared-libs -static-libstdc++ - - -### Windows mkdir -p "third_party/libusb-windows" pushd "third_party/libusb-windows" > /dev/null @@ -105,14 +97,43 @@ if [ ! -f "libusb-1.0.23/libusb-1.0.def" ]; then fi popd > /dev/null -#x86_64-w64-mingw32-g++ -shared -o libfibre-windows-amd64.dll -fPIC -std=c++11 -I./include -DFIBRE_COMPILE -DFIBRE_ENABLE_CLIENT \ -# -I./third_party/libusb-windows/libusb-1.0.23/include/libusb-1.0 \ -# "${FILES[@]}" \ -# -static-libgcc \ -# -Wl,-Bstatic \ -# -lstdc++ \ -# ./third_party/libusb-windows/libusb-1.0.23/MinGW64/static/libusb-1.0.a \ -# -Wl,-Bdynamic + + +### Compile libfibre + +echo "building libfibre for Linux (AMD64)..." +CFLAGS="-I./third_party/libusb-dev-amd64/usr/include/libusb-1.0" \ +LIBS="third_party/libusb-amd64/lib/x86_64-linux-gnu/libusb-1.0.so.0.2.0" \ +CXX="x86_64-pc-linux-gnu-g++" \ + build_for_linux 'linux-amd64' + +echo "building libfibre for Linux (ARM)..." +CFLAGS="-I./third_party/libusb-dev-armhf/usr/include/libusb-1.0 -L./third_party/libstdc++-linux-armhf/usr/lib/gcc-cross/arm-linux-gnueabihf/10" \ +LIBS="third_party/libusb-armhf/lib/arm-linux-gnueabihf/libusb-1.0.so.0.2.0" \ +CXX="arm-linux-gnueabihf-g++" \ + build_for_linux 'linux-armhf' + + +### Windows + +echo "building libfibre for Windows (AMD64)..." +x86_64-w64-mingw32-g++ -shared -o libfibre-windows-amd64.dll -fPIC -std=c++11 -I./include -DFIBRE_COMPILE -DFIBRE_ENABLE_CLIENT \ + -I./third_party/libusb-windows/libusb-1.0.23/include/libusb-1.0 \ + "${FILES[@]}" \ + -static-libgcc \ + -Wl,-Bstatic \ + -lstdc++ \ + ./third_party/libusb-windows/libusb-1.0.23/MinGW64/static/libusb-1.0.a \ + -Wl,-Bdynamic +cp /usr/x86_64-w64-mingw32/bin/libwinpthread-1.dll . + +### macOS + +# Link are broken: +# …ions/Current/Headers $ ls -l IOReturn.h +# lrwxrwxrwx 1 root root 189 Dec 26 2019 IOReturn.h -> Users/phracker/Documents/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/IOKit/IOReturn.h +# Fix with: +# sudo ln -sf /opt/osxcross/SDK/MacOSX10.13.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/IOKit/IOReturn.h IOReturn.h oldprefix="Users/phracker/Documents/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk" newprefix="/opt/osxcross/SDK/MacOSX10.13.sdk" @@ -124,12 +145,6 @@ while IFS= read -r link; do fi done <<< "$(find /opt/osxcross/SDK/MacOSX10.13.sdk/System/Library/Frameworks/IOKit.framework -xtype l)" -# Link are broken: -# …ions/Current/Headers $ ls -l IOReturn.h -# lrwxrwxrwx 1 root root 189 Dec 26 2019 IOReturn.h -> Users/phracker/Documents/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/IOKit/IOReturn.h -# Fix with: -# sudo ln -sf /opt/osxcross/SDK/MacOSX10.13.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/IOKit/IOReturn.h IOReturn.h - export PATH="/opt/osxcross/bin:$PATH" export LD_LIBRARY_PATH="/opt/osxcross/lib" export CFLAGS='-I/opt/osxcross/SDK/MacOSX10.13.sdk/usr/include -arch i386 -arch x86_64' @@ -137,21 +152,7 @@ export MACOSX_DEPLOYMENT_TARGET='10.9' CC='o64-clang' \ compile_libusb 'macos-amd64' 'x86_64-apple-darwin17' -#mkdir -p "third_party/libusb-src" -#pushd "third_party/libusb-src" > /dev/null -#if [ ! -f v1.0.23.tar.gz ]; then -# wget "https://github.com/libusb/libusb/archive/v1.0.23.tar.gz" -#fi -#if [ ! -f "libusb-1.0.23/README.md" ]; then -# tar -xvf "v1.0.23.tar.gz" -#fi -#export CC=o64-clang++ -#mkdir -p "third_party/libusb-src/build-macos-amd64" -#../configure -#pushd "third_party/libusb-src" > /dev/null -#./configure -#popd > /dev/null - +echo "building libfibre for macOS" o64-clang++ -shared -o libfibre-macos-x86.dylib -fPIC -std=c++11 -I./include -DFIBRE_COMPILE -DFIBRE_ENABLE_CLIENT \ -I./third_party/libusb-windows/libusb-1.0.23/include/libusb-1.0 \ @@ -160,19 +161,3 @@ o64-clang++ -shared -o libfibre-macos-x86.dylib -fPIC -std=c++11 -I./include -DF -static-libstdc++ \ ./third_party/libusb-1.0.23/build-macos-amd64/libusb/.libs/libusb-1.0.a \ -framework CoreFoundation -framework IOKit - - #"${FILES[@]}" \ - - #-arch i386 \ - #-Wl,-Bstatic \ - #./third_party/libusb-1.0.23/build-macos-amd64/libusb/libusb-1.0.la \ - #-Wl,-Bdynamic - - - # \ - #-Wl,-Bstatic \ - #-lgcc \ - #-lstdc++ \ - #./third_party/libusb-windows/libusb-1.0.23/MinGW64/static/libusb-1.0.a \ - #-Wl,-Bdynamic - diff --git a/Firmware/fibre-cpp/include/fibre/introspection.hpp b/Firmware/fibre-cpp/include/fibre/introspection.hpp index f7e43f68..8fbe3545 100644 --- a/Firmware/fibre-cpp/include/fibre/introspection.hpp +++ b/Firmware/fibre-cpp/include/fibre/introspection.hpp @@ -191,7 +191,7 @@ struct FibrePropertyTypeInfo> : FloatSettableTypeInfo, StringConvert } bool set_string(const Introspectable& obj, char* buffer, size_t length) const override { - maybe_underlying_type_t value; + maybe_underlying_type_t value{}; if (!from_string(buffer, length, &value, 0)) { return false; } @@ -200,7 +200,7 @@ struct FibrePropertyTypeInfo> : FloatSettableTypeInfo, StringConvert } bool set_float(const Introspectable& obj, float val) const override { - maybe_underlying_type_t value; + maybe_underlying_type_t value{}; if (!conversion::set_from_float(val, &value)) { return false; } diff --git a/Firmware/fibre-cpp/libfibre-macos-x86.dylib b/Firmware/fibre-cpp/libfibre-macos-x86.dylib deleted file mode 100644 index b881a087..00000000 --- a/Firmware/fibre-cpp/libfibre-macos-x86.dylib +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ad86cbdadd274245b9220f9728c1fc05ae05a1f196510a35e78e37309b3d6adf -size 2195160 diff --git a/Firmware/fibre-cpp/libfibre.cpp b/Firmware/fibre-cpp/libfibre.cpp index edf09689..7ffeb61f 100644 --- a/Firmware/fibre-cpp/libfibre.cpp +++ b/Firmware/fibre-cpp/libfibre.cpp @@ -425,9 +425,8 @@ struct FIBRE_PRIVATE LibFibreCallContext : fibre::Completerclient, rx_vec, func->outputs, false); size_t len = std::min(rx_vec.size(), rx_buf.size()); - FIBRE_LOG(D) << "copying " << rx_vec.size() << " or " << rx_buf.size() << " bytes to output"; std::copy(rx_vec.begin(), rx_vec.begin() + len, rx_buf.begin()); - FIBRE_LOG(D) << "result is " << as_hex(rx_buf); + FIBRE_LOG(D) << "result is [" << as_hex(rx_buf) << "]"; if (on_completed) { (*on_completed)(ctx, output.status, rx_buf.begin() + len); diff --git a/Firmware/fibre-cpp/platform_support/libusb_transport.cpp b/Firmware/fibre-cpp/platform_support/libusb_transport.cpp index 4ed00dbc..7b2a15f4 100644 --- a/Firmware/fibre-cpp/platform_support/libusb_transport.cpp +++ b/Firmware/fibre-cpp/platform_support/libusb_transport.cpp @@ -15,8 +15,13 @@ using namespace fibre; DEFINE_LOG_TOPIC(USB); USE_LOG_TOPIC(USB); +// This probably has no noteworthy effect since we automatically restart +// timed out operations anyway. constexpr unsigned int kBulkTimeoutMs = 2000; +// Only relevant for platforms don't support hotplug detection and thus +// need polling. +constexpr unsigned int kPollingIntervalMs = 1000; /* LibusbDiscoverer ----------------------------------------------------------*/ @@ -352,16 +357,16 @@ int LibusbDiscoverer::on_hotplug(struct libusb_device *dev, if (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED == event) { FIBRE_LOG(D) << "device arrived: bus " << (int)bus_number << ", " << (int)dev_number; - for (auto& subscription: subscriptions_) { - consider_device(dev, subscription); - } - // add empty placeholder to the list of known devices known_devices_[bus_number << 8 | dev_number] = { .dev = libusb_ref_device(dev), .handle = nullptr }; + for (auto& subscription: subscriptions_) { + consider_device(dev, subscription); + } + } else if (LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT == event) { FIBRE_LOG(D) << "device left: bus " << (int)bus_number << ", " << (int)dev_number; @@ -412,6 +417,17 @@ void LibusbDiscoverer::poll_devices_now() { for (auto& dev: current_devices) { if (known_devices_.find(dev.first) == known_devices_.end()) { on_hotplug(dev.second, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED); + + // Immediately forget about the devices that weren't opened on plugin. + // The reason is this: On Windows the device address and even the + // device pointer can remain equal across device reset. Since we don't + // poll at infinite frequency This means we could miss a device reset. + // To avoid this, we reinspect the all unopened devices on + // every polling iteration. + auto it = known_devices_.find(dev.first); + if (it->second.handle == nullptr) { + known_devices_.erase(it); + } } } @@ -434,7 +450,7 @@ void LibusbDiscoverer::poll_devices_now() { // It's possible that the discoverer was deinited during this function. if (event_loop_) { - device_polling_timer_ = event_loop_->call_later(1.0, [](void* ctx) { + device_polling_timer_ = event_loop_->call_later(kPollingIntervalMs * 0.001f, [](void* ctx) { ((LibusbDiscoverer*)ctx)->poll_devices_now(); }, this); } @@ -516,10 +532,8 @@ void LibusbDiscoverer::consider_device(struct libusb_device *device, ChannelDisc continue; } - EventLoop* event_loop = using_sparate_libusb_thread_ ? event_loop_ : nullptr; - LibusbBulkInEndpoint* ep_in = new LibusbBulkInEndpoint(); - if (libusb_ep_in && ep_in->init(event_loop, my_dev.handle, libusb_ep_in->bEndpointAddress)) { + if (libusb_ep_in && ep_in->init(this, my_dev.handle, libusb_ep_in->bEndpointAddress)) { my_dev.ep_in.push_back(ep_in); } else { delete ep_in; @@ -527,7 +541,7 @@ void LibusbDiscoverer::consider_device(struct libusb_device *device, ChannelDisc } LibusbBulkOutEndpoint* ep_out = new LibusbBulkOutEndpoint(); - if (libusb_ep_out && ep_out->init(event_loop, my_dev.handle, libusb_ep_out->bEndpointAddress)) { + if (libusb_ep_out && ep_out->init(this, my_dev.handle, libusb_ep_out->bEndpointAddress)) { my_dev.ep_out.push_back(ep_out); } else { delete ep_out; @@ -549,8 +563,8 @@ void LibusbDiscoverer::consider_device(struct libusb_device *device, ChannelDisc /* LibusbBulkEndpoint --------------------------------------------------------*/ template -bool LibusbBulkEndpoint::init(EventLoop* event_loop, libusb_device_handle* handle, uint8_t endpoint_id) { - event_loop_ = event_loop; +bool LibusbBulkEndpoint::init(LibusbDiscoverer* parent, libusb_device_handle* handle, uint8_t endpoint_id) { + parent_ = parent; handle_ = handle; transfer_ = libusb_alloc_transfer(0); endpoint_id_ = endpoint_id; @@ -560,7 +574,7 @@ bool LibusbBulkEndpoint::init(EventLoop* event_loop, libusb_device_handle* template bool LibusbBulkEndpoint::deinit() { if (completer_) { - FIBRE_LOG(E) << "Transfer still in progress. This is gonna be messy."; + FIBRE_LOG(E) << "Transfer on EP " << as_hex(endpoint_id_) << " still in progress. This is gonna be messy."; } libusb_free_transfer(transfer_); @@ -593,7 +607,7 @@ void LibusbBulkEndpoint::start_transfer(bufptr_t buffer, TransferHandle* h // This callback is used if we start our own libusb thread // separate from the application's event loop thread auto indirect_callback = [](struct libusb_transfer* transfer){ - ((LibusbBulkEndpoint*)transfer->user_data)->event_loop_->post( + ((LibusbBulkEndpoint*)transfer->user_data)->parent_->event_loop_->post( [](void* ctx) { ((LibusbBulkEndpoint*)ctx)->on_transfer_finished(); }, transfer->user_data @@ -603,7 +617,7 @@ void LibusbBulkEndpoint::start_transfer(bufptr_t buffer, TransferHandle* h //FIBRE_LOG(D) << "transfer of size " << buffer.size(); libusb_fill_bulk_transfer(transfer_, handle_, endpoint_id_, buffer.begin(), buffer.size(), - event_loop_ ? indirect_callback : direct_callback, + parent_->using_sparate_libusb_thread_ ? indirect_callback : direct_callback, this, kBulkTimeoutMs); completer_ = &completer; @@ -643,18 +657,59 @@ void LibusbBulkEndpoint::on_transfer_finished() { submit_transfer(); return; } + + libusb_device* dev = libusb_get_device(handle_); - // On linux we get LIBUSB_TRANSFER_STALL on the RX pipe when the cable is plugged out - StreamStatus status = LIBUSB_TRANSFER_COMPLETED == transfer_->status ? kStreamOk : - LIBUSB_TRANSFER_CANCELLED == transfer_->status ? kStreamCancelled : - LIBUSB_TRANSFER_STALL == transfer_->status ? kStreamClosed : - LIBUSB_TRANSFER_NO_DEVICE == transfer_->status ? kStreamClosed : - kStreamError; + StreamStatus status; + + if (transfer_->status == LIBUSB_TRANSFER_COMPLETED) { + status = kStreamOk; + } else if (transfer_->status == LIBUSB_TRANSFER_CANCELLED) { + status = kStreamCancelled; + } else { + // The error that we get on device removal tends to be inaccurate. + // Sometimes it's LIBUSB_TRANSFER_STALL, sometimes + // LIBUSB_TRANSFER_ERROR. Therefore we just check if the device + // is still present to determine which error code to return. + + bool found = false; + + libusb_device** list; + ssize_t n_devices = libusb_get_device_list(parent_->libusb_ctx_, &list); + + if (n_devices >= 0) { + for (size_t i = 0; i < n_devices; ++i) { + if (list[i] == dev) { + break; + } + } + libusb_free_device_list(list, 1); + } + + if (found) { + status = kStreamError; + } else { + FIBRE_LOG(D) << "device removed during transfer"; + status = kStreamClosed; + } + } (status == kStreamError ? FIBRE_LOG(W) : FIBRE_LOG(T)) << "USB transfer on EP " << as_hex(endpoint_id_) << " finished with " << libusb_error_name(transfer_->status); - uint8_t* end = std::max(transfer_->buffer + transfer_->actual_length, transfer_->buffer); + if (status == kStreamClosed) { + handle_ = nullptr; // Ensure that no new transfer is started + } + uint8_t* end = std::max(transfer_->buffer + transfer_->actual_length, transfer_->buffer); safe_complete(completer_, {status, end}); + + // If libusb does hotplug detection itself then we don't need to handle + // device removal here. Libusb will call the corresponding hotplug callback. + if (status == kStreamClosed && !parent_->hotplug_callback_handle_) { + if (!parent_->using_sparate_libusb_thread_) { + FIBRE_LOG(E) << "It's not a good idea to unref the device from within this callback. This will probably hang."; + } + parent_->on_hotplug(dev, LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT); + } } diff --git a/Firmware/fibre-cpp/platform_support/libusb_transport.hpp b/Firmware/fibre-cpp/platform_support/libusb_transport.hpp index 5d383691..2d8ea2b0 100644 --- a/Firmware/fibre-cpp/platform_support/libusb_transport.hpp +++ b/Firmware/fibre-cpp/platform_support/libusb_transport.hpp @@ -21,6 +21,8 @@ struct ChannelDiscoveryResult { AsyncStreamSink* tx_channel; }; +template class FIBRE_PRIVATE LibusbBulkEndpoint; + class FIBRE_PRIVATE LibusbDiscoverer { public: @@ -45,6 +47,9 @@ public: int stop_channel_discovery(ChannelDiscoveryContext* handle); private: + friend class LibusbBulkEndpoint; + friend class LibusbBulkEndpoint; + struct Device { struct libusb_device* dev; struct libusb_device_handle* handle; @@ -76,7 +81,7 @@ private: template class FIBRE_PRIVATE LibusbBulkEndpoint { public: - bool init(EventLoop* event_loop, struct libusb_device_handle* handle, uint8_t endpoint_id); + bool init(LibusbDiscoverer* parent, struct libusb_device_handle* handle, uint8_t endpoint_id); bool deinit(); protected: @@ -87,7 +92,7 @@ private: void submit_transfer(); void on_transfer_finished(); - EventLoop* event_loop_ = nullptr; // only non-null on Windows where we use a separate libusb thread + LibusbDiscoverer* parent_ = nullptr; struct libusb_device_handle* handle_ = nullptr; uint8_t endpoint_id_ = 0; struct libusb_transfer* transfer_ = nullptr; diff --git a/Firmware/interface_generator_stub.py b/Firmware/interface_generator_stub.py index d5b22093..ee163023 100644 --- a/Firmware/interface_generator_stub.py +++ b/Firmware/interface_generator_stub.py @@ -4,7 +4,7 @@ import sys import os try: - exec(open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'fibre', 'tools', 'interface_generator.py')).read()) + exec(open(os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'tools', 'fibre-tools', 'interface_generator.py')).read()) except ImportError as ex: print(str(ex), file=sys.stderr) print("Note that there are new compile-time dependencies since around v0.5.1.", file=sys.stderr) diff --git a/docs/ascii-protocol.md b/docs/ascii-protocol.md index 9625ec47..7f6857ee 100644 --- a/docs/ascii-protocol.md +++ b/docs/ascii-protocol.md @@ -4,7 +4,7 @@ ## How to send commands * **Via USB:** - * **Windows:** Use the Zadig utility to set the ODrive's driver to "usbser". Windows will then make the device available as COM port. You can use [PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/) to manually send commands or open the COM port using your favorite programming language + * **Windows:** Use [PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/) to manually send commands or open the COM port using your favorite programming language * **Linux/macOS:** Run `/dev/tty*` to list all serial ports. The ODrive will show up as `/dev/ttyACM0` (or similar) on Linux and `/dev/tty.usbmodem[...]` on macOS. Once you know the name, you can use `screen /dev/ttyACM0` (with the correct name) to send commands manually or open the device using your favorite programming language. Serial ports on Unix can be opened, written to and read from like a normal file. * **Via UART:** Connect the ODrive's TX (GPIO1) to your host's RX. Connect your ODrive's RX (GPIO2) to your host's TX. The logic level of the ODrive is 3.3V. * **Arduino:** You can use the [ODrive Arduino library](https://github.com/madcowswe/ODrive/tree/master/Arduino/ODriveArduino) to talk to the ODrive. diff --git a/docs/developer-guide.md b/docs/developer-guide.md index e49fa5b9..6d7de336 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -263,8 +263,9 @@ We use GitHub Releases to provide firmware releases. 1. Cut off the changelog to reflect the new release 2. Merge the release candidate into master. 3. Push a (lightweight) tag to the master branch. Follow the existing naming convention. -4. Push the python tools to PyPI. -5. Edit the release on GitHub to add a title and description (copy&paste from changelog). +4. If you changed something in libfibre, regenerate the binaries using `Firmware/fibre-cpp/compile_for_all_platforms.sh` and then copy all resulting `*.so`, `*.dll` and `*.dylib` files to `tools/odrive/pyfibre/fibre/`. +5. Push the python tools to PyPI (see setup.py for details). +6. Edit the release on GitHub to add a title and description (copy&paste from changelog). ## Other code maintenance notes The cortex M4F processor has hardware single precision float unit. However double precision operations are not accelerated, and hence should be avoided. The following regex is helpful for cleaning out double constants: diff --git a/docs/getting-started.md b/docs/getting-started.md index 94bcf15e..afb14129 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -82,9 +82,6 @@ Most instructions in this guide refer to a utility called `odrivetool`, so you s * __Anaconda__: In the start menu, type `Anaconda Prompt` Enter * __Standalone Python__: In the start menu, type `cmd` Enter 3. Install the ODrive tools by typing `pip install odrive` Enter -4. Plug in a USB cable into the microUSB connector on ODrive, and connect it to your PC. -5. Use the [Zadig](http://zadig.akeo.ie/) utility to set ODrive driver to libusb-win32. - * Check 'List All Devices' from the options menu, and select 'ODrive 3.x Native Interface (Interface 2)'. With that selected in the device list choose 'libusb-win32' from the target driver list and then press the large 'install driver' button. ### OSX @@ -112,16 +109,22 @@ pip3 install odrive __Troubleshooting__ 1. Permission Errors: Just run the previous command in sudo -```bash -sudo pip3 install odrive -``` + ```bash + sudo pip3 install odrive + ``` 2. Dependency Errors: If the installer doesn't complete and you get a dependency error (Ex. "No module..." or "module_name not found") -```bash -sudo pip3 install module_name -``` -Try step 5 again + ```bash + sudo pip3 install module_name + ``` + Try step 5 again + +3. Other Install Errors: If the installer fails at installing dependencies, try + ```bash + sudo pip3 install odrive --no-deps + ``` + If you do this, brace yourself for runtime errors when you run `odrivetool` (the basic functionality should work though). ### Linux @@ -143,7 +146,7 @@ Your board should come preflashed with firmware. If you run into problems, follo Your board does **not** come preflashed with any firmware. Follow the instructions [here](odrivetool.md#device-firmware-update) on the ST Link procedure before you continue. ## Start `odrivetool` -To launch the main interactive ODrive tool, type `odrivetool` Enter. Connect your ODrive and wait for the tool to find it. Now you can, for instance type `odrv0.vbus_voltage` Enter to inpect the boards main supply voltage. +To launch the main interactive ODrive tool, type `odrivetool` Enter. Connect your ODrive and wait for the tool to find it. If it doesn't connect after a few seconds refer to the [troubleshooting page](troubleshooting.md#usb-connectivity-issues). Now you can, for instance type `odrv0.vbus_voltage` Enter to inpect the boards main supply voltage. It should look something like this: ```text diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index a06366eb..3d47157f 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -27,7 +27,7 @@ With this information you can look up the API documentation for your error(s): * **Linux**: Type `lsusb` to list all USB devices. Verify that your ODrive is listed. * **Linux**: Make sure you [set up your udev rules](getting-started#downloading-and-installing-tools) correctly. * **Windows**: Right-click on the start menu and open "Device Manager". Verify that your ODrive is listed. - * **Windows**: Use the [Zadig utility](http://zadig.akeo.ie/) to verify the driver is set to `libusb-win32`. Note that there are two options listed in Zadig for Odrive: `ODrive 3.x Native Interface (Interface 2)` and `ODrive 3.x CDC Interface (Interface 0)`. Only the native interface should have `libusb-win32` while the CDC interface should use `WinUSB`. + * **Windows**: Use the [Zadig utility](http://zadig.akeo.ie/) to verify the driver is set to `WinUSB` or `libusb-win32`. Note that there are two options listed in Zadig for ODrive: `ODrive 3.x Native Interface (Interface 2)` and `ODrive 3.x CDC Interface (Interface 0)`. Only the driver setting of the native interface is important to `odrivetool`. * Ensure that no other ODrive program is running * Run `odrivetools` with the `--verbose` option. * Run `PYUSB_DEBUG=debug odrivetools` to get even more log output. diff --git a/tools/odrive/__init__.py b/tools/odrive/__init__.py index f962cc9d..3c661423 100644 --- a/tools/odrive/__init__.py +++ b/tools/odrive/__init__.py @@ -1,18 +1,15 @@ import os import sys -sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname( - os.path.dirname(os.path.realpath(__file__)))), - "Firmware", "fibre", "python")) -# Syntactic sugar to make usage more intuative. -# Try/pass used to break install-time dep issues -try: - import fibre - find_any = fibre.find_any - find_all = fibre.find_all -except: - pass +# We want to use the fibre package that is included with the odrive package +# in order to avoid any version mismatch issues, +sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "pyfibre")) + +# Syntactic sugar to make usage more intuitive. +import fibre +find_any = fibre.find_any +find_all = fibre.find_all # Standard convention is to add a __version__ attribute to the package from .version import get_version_str diff --git a/tools/odrive/pyfibre/fibre/libfibre-linux-amd64.so b/tools/odrive/pyfibre/fibre/libfibre-linux-amd64.so new file mode 100755 index 00000000..20362c66 Binary files /dev/null and b/tools/odrive/pyfibre/fibre/libfibre-linux-amd64.so differ diff --git a/tools/odrive/pyfibre/fibre/libfibre-linux-armhf.so b/tools/odrive/pyfibre/fibre/libfibre-linux-armhf.so new file mode 100755 index 00000000..2ab77800 Binary files /dev/null and b/tools/odrive/pyfibre/fibre/libfibre-linux-armhf.so differ diff --git a/tools/odrive/pyfibre/fibre/libfibre-macos-x86.dylib b/tools/odrive/pyfibre/fibre/libfibre-macos-x86.dylib new file mode 100644 index 00000000..54fc935f Binary files /dev/null and b/tools/odrive/pyfibre/fibre/libfibre-macos-x86.dylib differ diff --git a/tools/odrive/pyfibre/fibre/libfibre-windows-amd64.dll b/tools/odrive/pyfibre/fibre/libfibre-windows-amd64.dll new file mode 100755 index 00000000..8bed3ec6 Binary files /dev/null and b/tools/odrive/pyfibre/fibre/libfibre-windows-amd64.dll differ diff --git a/tools/odrive/pyfibre/fibre/libfibre.py b/tools/odrive/pyfibre/fibre/libfibre.py index 038f51d7..522947e9 100644 --- a/tools/odrive/pyfibre/fibre/libfibre.py +++ b/tools/odrive/pyfibre/fibre/libfibre.py @@ -9,8 +9,8 @@ from types import MethodType import concurrent import threading import time -from fibre.utils import Logger, Event import platform +from .utils import Logger, Event lib_names = { ('Linux', 'x86_64'): 'libfibre-linux-amd64.so', @@ -21,20 +21,40 @@ lib_names = { system_desc = (platform.system(), platform.machine()) -lib_dir = os.path.join( - os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))), - 'cpp') +script_dir = os.path.dirname(os.path.realpath(__file__)) +fibre_cpp_paths = [ + os.path.join(os.path.dirname(os.path.dirname(script_dir)), "cpp"), + os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(script_dir)))), "Firmware", "fibre-cpp") +] -def test_path(path): - return path if os.path.isfile(path) else None +def get_first(lst, predicate, default): + for item in lst: + if predicate(item): + return item + return default -lib_path = (test_path(os.path.join(lib_dir, 'libfibre.so')) or - test_path(os.path.join(lib_dir, 'libfibre.dll')) or - (test_path(os.path.join(lib_dir, lib_names[system_desc])) if (system_desc in lib_names) else None)) +if not system_desc in lib_names: + fibre_cpp_path = get_first(fibre_cpp_paths, os.path.isdir, None) + + if fibre_cpp_path is None: + instructions = ("Go to https://github.com/samuelsadok/fibre-cpp for " + "instructions on how to compile libfibre. Once you have compiled it, " + "add it to this folder.") + else: + instructions = ("Go to {} and run `make`. Then edit this file (libfibre.py) " + "to include the name of the binary that was generated by `make`.".format(fibre_cpp_path)) + + raise ModuleNotFoundError("libfibre is not supported on your platform ({} {}). {}".format(*system_desc, instructions)) + +lib_name = lib_names[system_desc] +search_paths = fibre_cpp_paths + [script_dir] + +lib_path = get_first( + (os.path.join(p, lib_name) for p in search_paths), + os.path.isfile, None) if lib_path is None: - raise ModuleNotFoundError("This package has no precompiled libfibre for your platform ({} {}). " - "Go to fibre/cpp/ and run `make` to compile libfibre for your platform.".format(*system_desc)) + raise ModuleNotFoundError("{} was not found in {}".format(lib_name, search_paths)) lib = windll.LoadLibrary(lib_path) if os.name == 'nt' else cdll.LoadLibrary(lib_path) diff --git a/tools/odrive/pyfibre/fibre/libwinpthread-1.dll b/tools/odrive/pyfibre/fibre/libwinpthread-1.dll new file mode 100755 index 00000000..cc8ca20e Binary files /dev/null and b/tools/odrive/pyfibre/fibre/libwinpthread-1.dll differ diff --git a/tools/odrivetool b/tools/odrivetool index 571eabc6..d6a78148 100755 --- a/tools/odrivetool +++ b/tools/odrivetool @@ -19,13 +19,10 @@ import argparse import time import math -sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname( - os.path.realpath(__file__))), - "Firmware", "fibre", "python")) -from fibre import Logger, Event import odrive from odrive.utils import OperationAbortedException from odrive.configuration import * +from fibre import Logger, Event # Flush stdout by default # Source: diff --git a/tools/setup.py b/tools/setup.py index 0a29e5b5..625e9b32 100644 --- a/tools/setup.py +++ b/tools/setup.py @@ -73,19 +73,6 @@ if creating_package: version_file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'odrive', 'version.txt') with open(version_file_path, mode='w') as version_file: version_file.write(version) - - # Temporarily link fibre into the python tools directory - # TODO: distribute a fibre package separately - fibre_src = os.path.join(os.path.dirname(os.path.dirname( - os.path.realpath(__file__))), - "Firmware", "fibre", "python", "fibre") - fibre_link = os.path.join(os.path.dirname( - os.path.realpath(__file__)), "fibre") - if not os.path.exists(fibre_link): - if sys.version_info > (3, 3): - os.symlink(fibre_src, fibre_link, target_is_directory=True) - else: - os.symlink(fibre_src, fibre_link) # TODO: find a better place for this if not creating_package: @@ -99,7 +86,7 @@ if not creating_package: try: setup( name = 'odrive', - packages = ['odrive', 'odrive.dfuse', 'fibre'], + packages = ['odrive', 'odrive.dfuse', 'odrive.pyfibre.fibre'], scripts = ['odrivetool', 'odrivetool.bat', 'odrive_demo.py'], version = version, description = 'Control utilities for the ODrive high performance motor controller', @@ -117,7 +104,12 @@ try: 'monotonic', # For compatibility with older python versions 'pywin32 >= 222; platform_system == "Windows"' # Required for fancy terminal features on Windows ], - package_data={'': ['version.txt']}, + package_data={'': [ + 'version.txt', + 'pyfibre/fibre/*.so', + 'pyfibre/fibre/*.dll', + 'pyfibre/fibre/*.dylib' + ]}, classifiers = [], ) @@ -127,4 +119,3 @@ finally: # clean up if creating_package: os.remove(version_file_path) - os.remove(fibre_link)