From 0d4a78650734fb1b97c8a896947a2c84927791a4 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Thu, 6 Feb 2025 22:34:46 -0800 Subject: [PATCH] tools/rpc: Remove the desktop RPC library. This code doesn't work well on non-real time systems. Additionally, given USB UART is the connection of choice it makes scripts hard to use. It's better to use the debug protocol scripts or the pyboard controller script. --- README.md | 11 +- scripts/libraries/rpc.py | 42 - tools/rpc/README.md | 212 ----- tools/rpc/rpc.py | 810 ------------------ ...e_transfer_jpg_as_the_controller_device.py | 130 --- ..._jpg_streaming_as_the_controller_device.py | 86 -- ...atures_as_the_controller_device_example.py | 159 ---- 7 files changed, 1 insertion(+), 1449 deletions(-) delete mode 100644 tools/rpc/README.md delete mode 100644 tools/rpc/rpc.py delete mode 100644 tools/rpc/rpc_image_transfer_jpg_as_the_controller_device.py delete mode 100644 tools/rpc/rpc_image_transfer_jpg_streaming_as_the_controller_device.py delete mode 100644 tools/rpc/rpc_popular_features_as_the_controller_device_example.py diff --git a/README.md b/README.md index 45615f1f6..46193ca62 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ The OpenMV firmware supports loading quantized TensorFlow Lite models. The firmw ## Interface library -The OpenMV Cam comes built-in with an RPC (Remote Python/Procedure Call) library which makes it easy to connect the OpenMV Cam to your computer, a SBC (single board computer) like the RaspberryPi or Beaglebone, or a microcontroller like the Arduino or ESP8266/32. The RPC Interface Library works over: +The OpenMV Cam comes built-in with an RPC (Remote Python/Procedure Call) library which makes it easy to connect the OpenMV Cam to another microcontroller like the Arduino or ESP8266/32. The RPC Interface Library works over: * Async Serial (UART) - at up **7.5 Mb/s** on the OpenMV Cam H7. * I2C Bus - at up to **1 Mb/s** on the OpenMV Cam H7. @@ -39,20 +39,11 @@ The OpenMV Cam comes built-in with an RPC (Remote Python/Procedure Call) library * SPI Bus - at up to **20 Mb/s** on the OpenMV Cam H7. * Up to **80 Mb/s** or **40 Mb/s** is achievable with short enough wires. * CAN Bus - at up to **1 Mb/s** on the OpenMV Cam H7. -* USB Virtual COM Port (VCP) - at up to **12 Mb/s** on the OpenMV Cam M4/M7/H7. -* WiFi using the [WiFi Shield](https://openmv.io/collections/shields/products/wifi-shield-1) - at up to **12 Mb/s** on the OpenMV Cam M4/M7/H7. With the RPC Library you can easily get image processing results, stream RAW or JPG image data, or have the OpenMV Cam control another Microcontroller for lower-level hardware control like driving motors. You can find examples that run on the OpenMV Cam under `File->Examples->Remote Control` in OpenMV IDE and online [here](scripts/examples/34-Remote-Control). Finally, OpenMV provides the following libraries for interfacing your OpenMV Cam to other systems below: -* [Generic Python Interface Library for USB VCP, Ethernet/WiFi, UART, Kvarser CAN, and I2C/SPI Comms](tools/rpc/README.md) - * Provides Python code for connecting your OpenMV Cam to a Windows, Mac, or Linux computer (or RaspberryPi/Beaglebone, etc.). - * Supports USB VCP on all systems. E.g. direct USB connection to the OpenMV Cam. - * Supports Ethernet/WiFi on all systems. - * Supports RS232/RS422/RS485/TTL UARTs on all systems. E.g. the old school DB9 port on the back of a PC, USB to serial RS232/RS422/RS485/TTL adapters, and TTL serial on I/O pins on SBCs like the RaspberryPi/Beaglebone. - * Supports Kvarser CAN on Windows and Linux (Kvarser does not support Mac). - * Supports I2C/SPI on Linux SBCs like the RaspberryPi/Beaglebone, etc. (coming soon) * [Arduino Interface Library for CAN, I2C, SPI, UART Comms](https://github.com/openmv/openmv-arduino-rpc) * Works on all Arduino variants. * CAN support via the MCP2515 over SPI or via the CAN peripheral on the ESP32. diff --git a/scripts/libraries/rpc.py b/scripts/libraries/rpc.py index fef26ffb5..e0c09c397 100644 --- a/scripts/libraries/rpc.py +++ b/scripts/libraries/rpc.py @@ -660,45 +660,3 @@ class rpc_uart_slave(rpc_slave): p.register(self.__uart, select.POLLIN) p.poll(1000) return self.get_bytes(buff, timeout_ms) - - -class rpc_usb_vcp_master(rpc_master): - def __init__(self): # private - import pyb - self.__usb_vcp = pyb.USB_VCP() - if omv.debug_mode(): - raise OSError("You cannot use the USB VCP while the IDE is connected!") - self.__usb_vcp.setinterrupt(-1) - rpc_master.__init__(self) - - def _flush(self): # protected - self.__usb_vcp.read() - - def get_bytes(self, buff, timeout_ms): # protected - if self.__usb_vcp.recv(buff, timeout=timeout_ms) != len(buff): - return None - return buff - - def put_bytes(self, data, timeout_ms): # protected - self.__usb_vcp.send(data, timeout=timeout_ms) - - -class rpc_usb_vcp_slave(rpc_slave): - def __init__(self): # private - import pyb - self.__usb_vcp = pyb.USB_VCP() - if omv.debug_mode(): - raise OSError("You cannot use the USB VCP while the IDE is connected!") - self.__usb_vcp.setinterrupt(-1) - rpc_slave.__init__(self) - - def _flush(self): # protected - self.__usb_vcp.read() - - def get_bytes(self, buff, timeout_ms): # protected - if self.__usb_vcp.recv(buff, timeout=timeout_ms) != len(buff): - return None - return buff - - def put_bytes(self, data, timeout_ms): # protected - self.__usb_vcp.send(data, timeout=timeout_ms) diff --git a/tools/rpc/README.md b/tools/rpc/README.md deleted file mode 100644 index 1867f8aa1..000000000 --- a/tools/rpc/README.md +++ /dev/null @@ -1,212 +0,0 @@ -# OpenMV Remote Python Call Library - -The `rpc` module on the OpenMV Cam allows you to connect your OpenMV Cam to another microcontroller or computer and execute remote python (or procedure) calls on your OpenMV Cam. The `rpc` module also allows for the reverse too if you want your OpenMV Cam to be able to execute remote procedure (or python) calls on another microcontroller or computer. - -For computer control the [rpc](rpc.py) python module in this directory implements the OpenMV `rpc` protocol for control of an OpenMV Cam over USB VCP (i.e. a USB serial port) or over Ethernet/WiFi (i.e. over sockets). - -# Library Dependencies - -The OpenMV Cam `rpc` library on the computer only depends on [pyserial](https://pythonhosted.org/pyserial/). All other modules used by it come installed with python. To get `pyserial` just do: - - pip install pyserial - -However, the examples depend on [pygame](https://www.pygame.org/news) so you need to install pygame too: - - pip install pygame - -Because the interface library is implemented in pure python with no external dependencies it works on Windows, Mac, and Linux. - -## UART Support - -pySerial provides support for pure USB virtual COM ports, USB to RS232/RS422/RS485/TTL COM ports, and standard RS232/RS422/RS485/TTL COM ports. Please use the `rpc_usb_vcp_master` and `rpc_usb_vcp_slave` for pure USB virtual COM port communication and `rpc_uart_master` and `rpc_uart_slave` for USB to RS232/RS422/RS485/TTL COM ports and standard RS232/RS422/RS485/TTL COM ports. - -### USB to Serial Converter Issues - -Pure hardware RS232/RS422/RS485/TTL COM ports should work using the `rpc_uart_master` and `rpc_uart_slave` interfaces without issues. However, FTDI like USB to serial converter chips may add unexpected latency to communication. In particular, FTDI chips have a latency timer which is used to buffer bytes for transmission over USB to improve bandwidth... but, that also increases the worse case latency of a single byte being sent over USB to **16 ms** by default. You need to reduce the latency timer to **1 ms** as detailed below or the interface library will fail to work: - -* https://stackoverflow.com/questions/54230836/inconsistent-delay-in-reading-serial-data-in-python-3-7-2-using-pyserial -* https://projectgus.com/2011/10/notes-on-ftdi-latency-with-arduino/ - -## CAN Support - -You may use the RPC Interface Library over CAN using [Kvaser](https://www.kvaser.com/) hardware on Windows and Linux (Kvaser does not support Mac). You need to install the following: - -* For Windows - * [Kvaser Drivers for Windows](https://www.kvaser.com/download/?utm_source=software&utm_ean=7330130980013&utm_status=latest) - * [Kvaser CANlib SDK](https://www.kvaser.com/download/?utm_source=software&utm_ean=7330130980150&utm_status=latest) - * [Python module](https://www.kvaser.com/download/?utm_source=software&utm_ean=7330130981911&utm_status=latest) -* For Linux - * [Kvaser Linux Drivers and SDK](https://www.kvaser.com/download/?utm_source=software&utm_ean=7330130980754&utm_status=latest) - * [Python module](https://www.kvaser.com/download/?utm_source=software&utm_ean=7330130981911&utm_status=latest) - -# How to use the Library - -Please checkout the following scripts for how to control your OpenMV Cam from the computer: - -* [Slow but Synchronous JPG Image Transfer](rpc_image_transfer_jpg_as_the_controller_device.py) -* [Fast JPG Image Streaming](rpc_image_transfer_jpg_streaming_as_the_controller_device.py) -* [Face Detection, April Tag Detection, Color Tracking, and more](rpc_popular_features_as_the_controller_device_example.py) - -You will need to edit the example code above to choose which interface you want to use (USB versus Ethernet/WiFi) and to play with the settings the scripts use. When you run the scripts make sure to save them first after editing them and then run them with `python -u ` to make sure that script output to stdio is not buffered. - -In general, for the controller device to use the `rpc` library you will create an interface object using the `rpc` library. For example: - - interface = rpc.rpc_usb_vcp_master("COM3") - -This create a USB VCP interface to talk to your OpenMV Cam over COM3. For Mac and Linux you would pass some type of `/dev/...` device instead. - -Once the interface is created you just need to do: - - memory_view_object_result = interface.call("remote_function_or_method_name", bytes_object_argument) - -And the `rpc` library will try to execute that `"remote_function_or_method_name"` on your OpenMV Cam. The remote function or method will receive the `bytes_object_argument` which can be up to 2^32-1 bytes in size. Once the remote method finishes executing it will return a `memory_view_object_result` which can also be up to 2^32-1 bytes in size. Because the argument and response are both generic byte containers you can pass anything through the `rpc` library and receive any type of response. A simple way to pass arguments is to use `struct.pack()` to create the argument and `struct.unpack()` to receive the argument on the OpenMV Cam side. For the response, the OpenMV Cam may send a string object or json string as the result which the computer can then interpret. Most objects or lists returned from method calls on the OpenMV Cam generate valid json strings when you call `str()` on them. - -As for errors, if you try to execute a non-existent function or method name on the OpenMV Cam the `call` method will return an empty `bytes()` object. If the `rpc` library failed to communicate with the OpenMV Cam the `rpc` library will return `None`. - -To keep things simple the `rpc` library doesn't maintain a connection between the master and slave devices. The `call` method encapsulates trying to connect to the OpenMV Cam, starting execution of the remote function or method, and getting the result. - -Now, on the OpenMV Cam side of things you have to create an `rpc` interface to communicate with the computer. This looks like: - - interface = rpc.rpc_usb_vcp_slave() - -This will create the interface layer on the OpenMV Cam (this needs to be done on a script running on the OpenMV Cam - see the [Remote Control](../../scripts/examples/34-Remote-Control) example scripts). - -Once you create the slave interface you then need to register call backs that the master can call with the interface object. - - def remote_function_or_method_name(memoryview_object_argument): - - return bytes_object_result - - interface.register_callback(remote_function_or_method_name) - -You may register as many callbacks as you like on the OpenMV Cam that the computer can call. Finally, once you are done registering callbacks you just need to execute: - - interface.loop() - -On the OpenMV Cam to start the `rpc` library up and begin listening for the computer. Note that the `loop()` method does not return. Also, to make your OpenMV Cam more robust against errors you may want to wrap the `loop()` with `try:` and `except:` for whatever exceptions might be thrown by your callback methods. The `rpc` library will not generate any exceptions itself. Note: passing large data structures around (like jpeg images) can potentially exhaust the heap on the OpenMV Cam and generate `MemoryError` exceptions. - -And that is it! The `rpc` library is designed to be simple to use. It was designed to allow remote control of the OpenMV Cam by a computer or microcontroller so there are also interfaces for control of your OpenMV Cam over CAN, I2C, SPI, and UART. - -# API - -Please see the example scripts above for starting code on how to use the `rpc` library. The below API documents the public interface of the library. - -## class rpc(): - -The `rpc` base class is reimplemented by the `rpc_master` and `rpc_slave` classes to create the master and slave interfaces. It is a pure virtual class and not meant to be used directly. - -#### get_bytes(buff, timeout_ms): - -This method is meant to be reimplemented by specific interface classes of `rpc_master` and `rpc_slave`. It should fill the `buff` argument which is either a `bytearray` or `memoryview` object of bytes from the interface equal to the length of the `buff` object in `timeout_ms` milliseconds. On timeout this method should return `None`. Note that for master and slave synchronization this method should try to always complete in at least `timeout_ms` milliseconds and not faster as the `rpc_master` and `rpc_slave` objects will automatically increase the `timeout_ms` to synchronize. - -#### put_bytes(data, timeout_ms): - -This method is meant to be reimplemented by specific interface classes of `rpc_master` and `rpc_slave`. It should send `data` bytes on the interface within `timeout_ms` milliseconds. If it completes faster than the timeout that is okay. No return value is expected. - -#### stream_reader(call_back, queue_depth=1, read_timeout_ms=5000): - -This method is meant to be called directly. After synchronization of the master and slave on return of a callback `stream_reader` may be called to receive data as fast as possible from the master or slave device. `call_back` will be called repeatedly with a `bytes_or_memory_view` argument that was sent by the `stream_writer`. `call_back` is not expected to return anything. `queue_depth` defines how many frames of data the `stream_writer` may generate before slowing down and waiting on the `stream_reader`. Higher `queue_depth` values lead to higher performance (up to a point) but require the `stream_reader` to be able to handle outstanding packets in its interface layer. Note that computers typically do not buffer much more than 4KB of data in device driver buffers. If you make the `queue_depth` larger than 1 then `call_back` should return very quickly and not block. Otherwise, you should implement a multi-thread architecture to process the received data so that `stream_reader` is always executing and moving data out of device driver buffers into larger user memory buffers. Finally, `read_timeout_ms` defines how many milliseconds to wait to receive the `bytes_or_memory_view` payload per `call_back`. - -On any errors `stream_reader` will return. The master and slave devices can try to setup the stream again afterwards to continue as demonstrated in [Fast JPG Image Streaming](rpc_image_transfer_jpg_streaming_as_the_controller_device.py). - -If you need to cancel the `stream_reader` just raise an exception in the `call_back` and catch it. The remote side will automatically timeout. - -#### stream_writer(call_back, write_timeout_ms=5000): - -This method is meant to be called directly. After synchronization of the master and slave on return of a callback `stream_writer` may be called to send data as fast as possible from the master or slave device. `call_back` will be called repeatedly and should return a `bytes_or_memory_view` object that will be sent to the `stream_reader`. `call_back` should not take any arguments. Finally, `write_timeout_ms` defines how many milliseconds to wait to send the `bytes_or_memory_view` object returned by `call_back`. - -On any errors `stream_writer` will return. The master and slave devices can try to setup the stream again afterwards to continue as demonstrated in [Fast JPG Image Streaming](rpc_image_transfer_jpg_streaming_as_the_controller_device.py). - -If you need to cancel the `stream_writer` just raise an exception in the `call_back` and catch it. The remote side will automatically timeout. - -## class rpc_master(): - -The `rpc_master` is a pure virtual class and not meant to be used directly. Specific interface classes should reimplement `rpc_master`. - -#### call(name, data=bytes(), send_timeout=1000, recv_timeout=1000): - -Executes a remote call on the slave device. `name` is a string name of the remote function or method to execute. `data` is the `bytes` like object that will be sent as the argument of the remote function or method to execute. `send_timeout` defines how many milliseconds to wait while trying to connect to the slave and get it to execute the remote function or method. Once the master starts sending the argument to the slave device `send_timeout` does not apply. The library will allow the argument to take up to 5 seconds to be sent. `recv_timeout` defines how many milliseconds to wait after the slave started executing the remote method to receive the response. Note that once the master starts receiving the response `recv_timeout` does not apply. The library will allow the response to take up to 5 seconds to be received. - -Note that a new packet that includes a copy of `data` will be created internally inside the `rpc` library. You may encounter memory issues on the OpenMV Cam if you try to pass very large data arguments. - -## class rpc_slave(): - -The `rpc_slave` is a pure virtual class and not meant to be used directly. Specific interface classes should reimplement `rpc_slave`. - -#### register_callback(cb): - -Registers a call back that can be executed by the master device. The call back should take one argument which will be a `memoryview` object and it should return a `bytes()` like object as the result. The call back should return in less than 1 second if possible. - -#### schedule_callback(cb): - -After you execute `loop()` it is not possible to execute long running operations outside of the `rpc` library. `schedule_callback` allows you to break out of the `rpc` library temporarily after completion of an call back. You should execute `schedule_callback` during the execution of an `rpc` call back method to register a new non-rpc call back that will be executed immediately after the successful completion of that call back you executed `schedule_callback` in. The function or method should not take any arguments. After the the call back that was registered returns it must be registered again in the next parent call back. On any error of the parent call back the registered call back will not be called and must be registered again. Here's how to use this: - - def some_function_or_method_that_takes_a_long_time_to_execute(): - - - def normal_rpc_call_back(data): - - interface.schedule_callback(some_function_or_method_that_takes_a_long_time_to_execute) - return bytes(response) - - interface.register_callback(normal_rpc_call_back) - - interface.loop() - -`schedule_callback` in particular allows you to use the `get_bytes` and `put_bytes` methods for cut-through data transfer between one device and another without the cost of packetization which limits the size of the data moved inside the `rpc` library without running out of memory on the OpenMV Cam. - -#### setup_loop_callback(cb): - -The loop call back is called every loop iteration of `loop()`. Unlike the `schedule_callback()` call back this call back stays registered after being registered once. You can use the loop call back to blink an activity LED or something like that. You should not use the loop call back to execute any blocking code as this will get in the way of polling for communication from the master. Additionally, the loop call back will be called at a variable rate depending on when and what call backs the master is trying to execute. Given this, the loop call back is not suitable for any method that needs to be executed at a fixed frequency. - -On the OpenMV Cam, if you need to execute something at a fixed frequency, you should setup a timer before executing `loop()` and use a timer interrupt based callback to execute some function or method at a fixed frequency. Please see how to [Write Interrupt Handlers](http://docs.openmv.io/reference/isr_rules.html) for more information. Note: The `mutex` library is installed on your OpenMV Cam along with the `rpc` library. - -#### loop(recv_timeout=1000, send_timeout=1000): - -Starts execution of the `rpc` library on the slave to receive data. This method does not return (except via an exception from a call back). You should register all call backs first before executing this method. However, it is possible to register new call backs inside of a call back previously being registered that is executing. - -`recv_timeout` defines how long to wait to receive a command from the master device before trying again. `send_timeout` defines how long the slave will wait for the master to receive the call back response before going back to trying to receive. The loop call back will be executed before trying to receive again. - -## rpc_uart_master(port, baudrate=9600) - -Creates a master implementation of the `rpc` library to communicate over a hardware RS232/RS422/RS485 or TTL COM port. `port` is the string name of the serial port. `baudrate` is the bits per second to run at. - -Please use the `rpc_usb_vcp_master` to talk to the OpenMV Cam over its USB interface. This class is for talking to the OpenMV Cam over its hardware UART. - -If you are using this class with USB to serial converters you will likely experience intermittent synchronization issues. The RPC library uart interface on the OpenMV Cam is designed to work with real-time UARTs like hardware RS232/RS422/RS485 COM ports on PCs and TTL UARTS on single board computers like the RaspberryPi and Beaglebone. That said, the interface library can handle this and will continue to work. - -## rpc_uart_slave(port, baudrate=9600) - -Creates a slave implementation of the `rpc` library to communicate over a hardware RS232/RS422/RS485 or TTL COM port. `port` is the string name of the serial port. `baudrate` is the bits per second to run at. - -Please use the `rpc_usb_vcp_slave` to talk to the OpenMV Cam over its USB interface. This class is for talking to the OpenMV Cam over its hardware UART. - -If you are using this class with USB to serial converters you will likely experience intermittent synchronization issues. The RPC library uart interface on the OpenMV Cam is designed to work with real-time UARTs like hardware RS232/RS422/RS485 COM ports on PCs and TTL UARTS on single board computers like the RaspberryPi and Beaglebone. That said, the interface library can handle this and will continue to work. - -## rpc_usb_vcp_master(port): - -Creates a master implementation of the `rpc` library to communicate over a USB VCP (virtual COM port). `port` is the string name of the serial port. - -Communication over USB is the most reliable and high speed way to connect an OpenMV Cam to a computer. However, as the OpenMV Cam has only one VCP port we recommend that you fully debug your script using the `rpc_usb_vcp_slave` on your OpenMV using another `rpc` interface if possible as you will not be able to get error messages off the OpenMV Cam easily. For example, connecting the `rpc_usb_vcp_master` to the `rpc_uart_slave` on your OpenMV Cam at 115200 BPS over a USB-to-Serial adapter when debugging will make your life far easier. Alternatively, use the Ethernet or WiFi interface when debugging. - -## rpc_usb_vcp_slave(port): - -Creates a slave implementation of the `rpc` library to communicate over a USB VCP (virtual COM port). `port` is the string name of the serial port. - -Communication over USB is the most reliable and high speed way to connect an OpenMV Cam to a computer. However, as the OpenMV Cam has only one VCP port we recommend that you fully debug your script using the `rpc_usb_vcp_master` on your OpenMV using another `rpc` interface if possible as you will not be able to get error messages off the OpenMV Cam easily. For example, connecting the `rpc_usb_vcp_slave` to the `rpc_uart_master` on your OpenMV Cam at 115200 BPS over a USB-to-Serial adapter when debugging will make your life far easier. Alternatively, use the Ethernet or WiFi interface when debugging. - -## rpc_network_master(slave_ip, my_ip="", port=0x1DBA): - -Creates a master implementation of the `rpc` library to communicate over WiFi or Ethernet. `slave_ip` is the IPV4 address of the `rpc` slave device. `my_ip` can be `""` which binds the master to any interface adapter to communicate to the slave. If `my_ip` is not `""` then it should be an IP address on the same subnet as `slave_ip`. `port` is a free port to use for UDP and TCP traffic. - -## rpc_network_slave(my_ip="", port=0x1DBA): - -Creates a slave implementation of the `rpc` library to communicate over WiFi or Ethernet. `my_ip` can be `""` which binds the slave to any interface adapter to communicate to the master. If `my_ip` is not `""` then it should be an IP address on the same subnet as the master. `port` is a free port to use for UDP and TCP traffic. - -## rpc_kvarser_can_master(channel, message_id=0x7FF, bit_rate=250000, sampling_point=75) - -Creates a master implementation of the `rpc` library to communicate over CAN using Kvarser hardware. `channel` is the Kvarser channel to use. `message_id` is the 11-bit CAN ID to use for data transport. `bit_rate` is the CAN bus rate. `sampling_point` is the percentage sample point on the CAN bus. - -## rpc_kvarser_can_slave(channel, message_id=0x7FF, bit_rate=250000, sampling_point=75) - -Creates a slave implementation of the `rpc` library to communicate over CAN using Kvarser hardware. `channel` is the Kvarser channel to use. `message_id` is the 11-bit CAN ID to use for data transport. `bit_rate` is the CAN bus rate. `sampling_point` is the percentage sample point on the CAN bus. diff --git a/tools/rpc/rpc.py b/tools/rpc/rpc.py deleted file mode 100644 index d9675a5f3..000000000 --- a/tools/rpc/rpc.py +++ /dev/null @@ -1,810 +0,0 @@ -# This file is part of the OpenMV project. -# -# Copyright (c) 2013-2020 Ibrahim Abdelkader -# Copyright (c) 2013-2020 Kwabena W. Agyeman -# -# This work is licensed under the MIT license, see the file LICENSE for details. - -import gc, serial, socket, struct, time - -class rpc: - - _COMMAND_HEADER_PACKET_MAGIC = 0x1209 - _COMMAND_DATA_PACKET_MAGIC = 0xABD1 - _RESULT_HEADER_PACKET_MAGIC = 0x9021 - _RESULT_DATA_PACKET_MAGIC = 0x1DBA - - def __def_crc_16(self, data, size): # private - crc = 0xFFFF - for i in range(size): - crc ^= data[i] << 8 - for j in range(8): crc = (crc << 1) ^ (0x1021 if crc & 0x8000 else 0) - return crc & 0xFFFF - - __crc_16_table = [0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, - 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, - 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, - 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, - 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, - 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, - 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, - 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, - 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, - 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, - 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, - 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, - 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, - 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, - 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, - 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, - 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, - 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, - 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, - 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, - 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, - 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, - 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, - 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, - 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, - 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, - 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, - 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, - 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, - 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, - 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, - 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0] - - def __tab_crc_16(self, data, size): # private - crc = 0xFFFF - for i in range(size): crc = self.__crc_16_table[((crc >> 8) ^ data[i]) & 0xff] ^ (crc << 8) - return crc & 0xFFFF - - def _zero(self, buff, size): # private - for i in range(size): buff[i] = 0 - - def _same(self, data, size): # private - if not size: return False - old = data[0] - for i in range(1, size): - new = data[i] - if new != old: return False - old = new - return True - - # djb2 algorithm; see http://www.cse.yorku.ca/~oz/hash.html - def _hash(self, data, size): # private - h = 5381 - for i in range(size): - h = ((h << 5) + h) ^ ord(data[i]) - return h & 0xFFFFFFFF - - def __init__(self): # private - self.__crc_16 = self.__tab_crc_16 - self._stream_writer_queue_depth_max = 255 - - def _get_packet_pre_alloc(self, payload_len=0): - buff = bytearray(payload_len + 4) - return (buff, memoryview(buff)[2:-2]) - - def _get_packet(self, magic_value, payload_buf_tuple, timeout): # private - packet = self.get_bytes(payload_buf_tuple[0], timeout) - if packet is not None: - magic = packet[0] | (packet[1] << 8) - crc = packet[-2] | (packet[-1] << 8) - if magic == magic_value and crc == self.__crc_16(packet, len(packet) - 2): - return payload_buf_tuple[1] - return None - - def _set_packet(self, magic_value, payload=bytes()): # private - new_payload = bytearray(len(payload) + 4) - new_payload[:2] = struct.pack("> 1) ^ (0xB8 if tx_lfsr & 1 else 0x00) - - def stream_writer(self, call_back, write_timeout_ms=5000): # public - packet = self._stream_get_bytes(bytearray(8), 1000) - if packet is None: return - magic = packet[0] | (packet[1] << 8) - crc = packet[-2] | (packet[-1] << 8) - if magic != 0xEDF6 and crc != self.__crc_16(packet, len(packet) - 2): return - queue_depth = max(min(struct.unpack("> 1) ^ (0xB8 if rx_lfsr & 1 else 0x00) - credits += 1 - if credits > 0: - data = call_back() - try: self._stream_put_bytes(self._set_packet(0x542E, struct.pack("