From be0c7a063212f2b0c0efdfda79a0d89264c38865 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Wed, 13 Apr 2022 17:02:02 -0700 Subject: [PATCH 01/35] can now pip instal in editable mode --- README.md | 16 +++++++++++++--- pyproject.toml | 4 ++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4b18b2d..11c2463 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,20 @@ - - # pyharp Harp implementation of the Harp protocol. -## Edit the code +## Install with Pip +From this directory, create a *setup.py* file with +```` +poetry build +```` +and then install in editable mode with +```` +pip install -e . +``` + +Note that for the above to work, a fairly recent version of pip (>= 21.3) is required. + +## Install with Poetry Each Python user has is own very dear IDE for editing. Here, we are leaving instructions on how to edit this code using pyCharm, Anaconda and Poetry. diff --git a/pyproject.toml b/pyproject.toml index 62037dc..ca8d302 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,8 +16,8 @@ mypy = "^0.782" black = "^19.10b0" [build-system] -requires = ["poetry>=0.12"] -build-backend = "poetry.masonry.api" +requires = ["poetry-core>=1.0.8"] +build-backend = "poetry.core.masonry.api" [tool.poetry.scripts] pyharp = "pyharp.main:main" From ef9936e2fd46e8c06ae907e0614ae424442fd784 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Wed, 13 Apr 2022 17:02:48 -0700 Subject: [PATCH 02/35] updating readme --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index 11c2463..7b5a181 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,7 @@ Harp implementation of the Harp protocol. ## Install with Pip -From this directory, create a *setup.py* file with -```` -poetry build -```` -and then install in editable mode with +From this directory, install in editable mode with ```` pip install -e . ``` From 69e11f0b1f6850eb998a40b6a59f3a5f677dcdb1 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Wed, 13 Apr 2022 17:03:44 -0700 Subject: [PATCH 03/35] fixing readme bug --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b5a181..cea031f 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Harp implementation of the Harp protocol. From this directory, install in editable mode with ```` pip install -e . -``` +```` Note that for the above to work, a fairly recent version of pip (>= 21.3) is required. From 4fff7bab10d0d4284ab2b5ba656f72bf0f0cdfdd Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Wed, 13 Apr 2022 17:32:34 -0700 Subject: [PATCH 04/35] adding udev rules --- 10-ftdi.rules | 3 +++ README.md | 14 ++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 10-ftdi.rules diff --git a/10-ftdi.rules b/10-ftdi.rules new file mode 100644 index 0000000..b960fec --- /dev/null +++ b/10-ftdi.rules @@ -0,0 +1,3 @@ +# UDEV rules for an ftdi RS232 Serial (Uart) IC +SUBSYSTEMS=="usb", ENV{.LOCAL_ifNum}="$attr{bInterfaceNumber}" +SUBSYSTEMS=="usb", KERNEL=="ttyUSB*", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="0666", SYMLINK+="ftdi-uart_%E{.LOCAL_ifNum}" diff --git a/README.md b/README.md index cea031f..1a38b41 100644 --- a/README.md +++ b/README.md @@ -75,3 +75,17 @@ Device info: * Firmware version: 1.0 * Device user name: IBL_rig_0 ``` + +## For Linux Only + +### UDEV rules +either copy `10-ftdi.rules` into your /etc/udev/rules.d folder or symlink it with +```` +sudo ln -s /full/path/to/pyharp/10-ftdi.rules /etc/udev/rules.d/10-ftdi.rules + +```` + +Reload udev rules with: +```` +sudo udevadm control --reload-rules +```` From d974ee403ccf3d311318a78a6a813cfe9fce1fa2 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Wed, 13 Apr 2022 18:20:27 -0700 Subject: [PATCH 05/35] device_names is now a default dict; examples now also work on linux --- 10-ftdi.rules | 2 +- examples/check_device_id.py | 11 +++- examples/get_info.py | 10 +++- examples/write_and_read_from_registers.py | 10 +++- pyharp/device.py | 2 +- pyharp/device_names.py | 67 +++++++++-------------- 6 files changed, 53 insertions(+), 49 deletions(-) mode change 100644 => 100755 examples/check_device_id.py mode change 100644 => 100755 examples/get_info.py mode change 100644 => 100755 examples/write_and_read_from_registers.py diff --git a/10-ftdi.rules b/10-ftdi.rules index b960fec..91f9aff 100644 --- a/10-ftdi.rules +++ b/10-ftdi.rules @@ -1,3 +1,3 @@ # UDEV rules for an ftdi RS232 Serial (Uart) IC SUBSYSTEMS=="usb", ENV{.LOCAL_ifNum}="$attr{bInterfaceNumber}" -SUBSYSTEMS=="usb", KERNEL=="ttyUSB*", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="0666", SYMLINK+="ftdi-uart_%E{.LOCAL_ifNum}" +SUBSYSTEMS=="usb", KERNEL=="ttyUSB*", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="0666", SYMLINK+="harp_device_%E{.LOCAL_ifNum}" diff --git a/examples/check_device_id.py b/examples/check_device_id.py old mode 100644 new mode 100755 index b118b0e..237e20e --- a/examples/check_device_id.py +++ b/examples/check_device_id.py @@ -1,7 +1,10 @@ +#!/usr/bin/env python3 from pyharp.device import Device from pyharp.messages import HarpMessage from pyharp.messages import MessageType +from pyharp.device_names import device_names from struct import * +import os # ON THIS EXAMPLE @@ -11,7 +14,11 @@ # Open the device -device = Device("COM95") # Open serial connection +# Open serial connection +if os.name == "posix": # check for Linux. + device = Device("/dev/ttyUSB0") +else: # assume Windows. + device = Device("COM95") # Get some of the device's parameters device_id = device.WHO_AM_I # Get device's ID @@ -19,7 +26,7 @@ device_user_name = device.DEVICE_NAME # Get device's user name # Check if we are dealing with the correct device -if device_id == 2080: +if device_id in device_names: print("Correct device was found!") print(f"Device's ID: {device_id}") print(f"Device's name: {device_id_description}") diff --git a/examples/get_info.py b/examples/get_info.py old mode 100644 new mode 100755 index fca840c..56be774 --- a/examples/get_info.py +++ b/examples/get_info.py @@ -1,7 +1,9 @@ +#!/usr/bin/env python3 from pyharp.device import Device from pyharp.messages import HarpMessage from pyharp.messages import MessageType from struct import * +import os # ON THIS EXAMPLE @@ -11,7 +13,11 @@ # Open the device and print the info on screen -device = Device("COM95", "ibl.bin") # Open serial connection and save communication to a file +# Open serial connection and save communication to a file +if os.name == 'posix': # check for Linux. + device = Device("/dev/harp_device_00", "ibl.bin") +else: # assume Windows. + device = Device("COM95", "ibl.bin") device.info() # Display device's info on screen # Get some of the device's parameters @@ -29,4 +35,4 @@ device_assembly = device.ASSEMBLY_VERSION # Get device's assembly version # Close connection -device.disconnect() \ No newline at end of file +device.disconnect() diff --git a/examples/write_and_read_from_registers.py b/examples/write_and_read_from_registers.py old mode 100644 new mode 100755 index ccde127..ceb6d52 --- a/examples/write_and_read_from_registers.py +++ b/examples/write_and_read_from_registers.py @@ -1,7 +1,9 @@ +#!/usr/bin/env python3 from pyharp.device import Device from pyharp.messages import HarpMessage from pyharp.messages import MessageType from struct import * +import os # ON THIS EXAMPLE @@ -12,7 +14,11 @@ # Open the device and print the info on screen -device = Device("COM95", "ibl.bin") # Open serial connection and save communication to a file +# Open serial connection and save communication to a file +if os.name == 'posix': # check for Linux. + device = Device("/dev/harp_device_00", "ibl.bin") +else: # assume Windows. + device = Device("COM95", "ibl.bin") # Read current analog sensor's higher threshold (ANA_SENSOR_TH0_HIGH) at address 42 analog_threshold_h = device.send(HarpMessage.ReadU16(42).frame).payload_as_int() @@ -34,4 +40,4 @@ print(f"Analog sensor's values: {analog_sensor}") # Close connection -device.disconnect() \ No newline at end of file +device.disconnect() diff --git a/pyharp/device.py b/pyharp/device.py index 5e0e64d..25ecf31 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -4,7 +4,7 @@ from pyharp.messages import HarpMessage, ReplyHarpMessage from pyharp.messages import CommonRegisters -from pyharp import device_names +from pyharp.device_names import device_names class Device: diff --git a/pyharp/device_names.py b/pyharp/device_names.py index e5d8cbd..d7b7557 100644 --- a/pyharp/device_names.py +++ b/pyharp/device_names.py @@ -1,41 +1,26 @@ -def get(value: int) -> str: - if value == 1024: - return "Poke" - elif value == 1040: - return "MultiPwm" - elif value == 1056: - return "Wear" - elif value == 1072: - return "VoltsDrive" - elif value == 1088: - return "LedController" - elif value == 1104: - return "Synchronizer" - elif value == 1121: - return "SimpleAnalogGenerator" - elif value == 1136: - return "Archimedes" - elif value == 1152: - return "ClockSynchronizer" - elif value == 1168: - return "Camera" - elif value == 1184: - return "PyControl" - elif value == 1200: - return "FlyPad" - elif value == 1216: - return "Behavior" - elif value == 1232: - return "LoadCells" - elif value == 1248: - return "AudioSwitch" - elif value == 1264: - return "Rgb" - elif value == 1200: - return "FlyPad" - elif value == 2064: - return "FP3002" - elif value == 2080: - return "IblBehavior" - else: - return "NotSpecified" +from collections import defaultdict + + +current_device_names = \ + {1024: 'Poke', + 1040: 'MultiPwm', + 1056: 'Wear', + 1072: 'VoltsDrive', + 1088: 'LedController', + 1104: 'Synchronizer', + 1121: 'SimpleAnalogGenerator', + 1136: 'Archimedes', + 1152: 'ClockSynchronizer', + 1168: 'Camera', + 1184: 'PyControl', + 1200: 'FlyPad', + 1216: 'Behavior', + 1232: 'LoadCells', + 1248: 'AudioSwitch', + 1264: 'Rgb', + 1200: 'FlyPad', + 2064: 'FP3002', + 2080: 'IblBehavior'} +device_names = defaultdict(lambda: 'NotSpecified') +device_names.update(current_device_names) + From fbdacbd4d12c19feb653815b46ca318d3932e1a0 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Fri, 15 Apr 2022 14:52:15 -0700 Subject: [PATCH 06/35] fixing pdf link; tweaking serial port to use ftdi rule --- examples/check_device_id.py | 2 +- pyharp/device.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/check_device_id.py b/examples/check_device_id.py index 237e20e..9568921 100755 --- a/examples/check_device_id.py +++ b/examples/check_device_id.py @@ -16,7 +16,7 @@ # Open the device # Open serial connection if os.name == "posix": # check for Linux. - device = Device("/dev/ttyUSB0") + device = Device("/dev/harp_device_00") else: # assume Windows. device = Device("COM95") diff --git a/pyharp/device.py b/pyharp/device.py index 25ecf31..b145f9c 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -9,7 +9,7 @@ class Device: """ - https://github.com/harp-tech/protocol/blob/master/Device%201.0%201.3%2020190207.pdf + https://github.com/harp-tech/protocol/blob/master/Device%201.1%201.0%2020220402.pdf """ _ser: serial.Serial From 33d8cc896d7462658fce5cf74f422d61476912d0 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Fri, 15 Apr 2022 14:56:29 -0700 Subject: [PATCH 07/35] Update README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1a38b41..b506d9b 100644 --- a/README.md +++ b/README.md @@ -76,16 +76,16 @@ Device info: * Device user name: IBL_rig_0 ``` -## For Linux Only +## for Linux -### UDEV rules -either copy `10-ftdi.rules` into your /etc/udev/rules.d folder or symlink it with -```` -sudo ln -s /full/path/to/pyharp/10-ftdi.rules /etc/udev/rules.d/10-ftdi.rules +### Install UDEV Rules +Install by either copying `10-harp.rules` over to your `/etc/udev/rules.d` folder or by symlinking it with: +```` +sudo ln -s /absolute/path/to/vibratome-controller/10-harp.rules /etc/udev/rules.d/10-harp.rules ```` -Reload udev rules with: +Then reload udev rules with ```` sudo udevadm control --reload-rules ```` From 0191b237bafb2fe32bdff1e58777b3eb9059ab59 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Mon, 18 Apr 2022 13:41:57 -0700 Subject: [PATCH 08/35] PEP 563 return type hints --- pyharp/messages.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pyharp/messages.py b/pyharp/messages.py index 383ae2e..866e3f9 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -1,3 +1,4 @@ +from __future__ import annotations # for type hints (PEP 563) # from abc import ABC, abstractmethod from typing import Union, Tuple, Optional @@ -81,39 +82,39 @@ def message_type(self) -> int: return self._frame[0] @staticmethod - def ReadU8(address: int) -> "ReadU8HarpMessage": + def ReadU8(address: int) -> ReadU8HarpMessage: return ReadU8HarpMessage(address) @staticmethod - def ReadS8(address: int) -> "ReadS8HarpMessage": + def ReadS8(address: int) -> ReadS8HarpMessage: return ReadS8HarpMessage(address) @staticmethod - def ReadS16(address: int) -> "ReadS16HarpMessage": + def ReadS16(address: int) -> ReadS16HarpMessage: return ReadS16HarpMessage(address) @staticmethod - def ReadU16(address: int) -> "ReadU16HarpMessage": + def ReadU16(address: int) -> ReadU16HarpMessage: return ReadU16HarpMessage(address) @staticmethod - def WriteU8(address: int, value: int) -> "WriteU8HarpMessage": + def WriteU8(address: int, value: int) -> WriteU8HarpMessage: return WriteU8HarpMessage(address, value) @staticmethod - def WriteS8(address: int, value: int) -> "WriteS8HarpMessage": + def WriteS8(address: int, value: int) -> WriteS8HarpMessage: return WriteS8HarpMessage(address, value) @staticmethod - def WriteS16(address: int, value: int) -> "WriteS16HarpMessage": + def WriteS16(address: int, value: int) -> WriteS16HarpMessage: return WriteS16HarpMessage(address, value) @staticmethod - def WriteU16(address: int, value: int) -> "WriteU16HarpMessage": + def WriteU16(address: int, value: int) -> WriteU16HarpMessage: return WriteU16HarpMessage(address, value) @staticmethod - def parse(frame: bytearray) -> "ReplyHarpMessage": + def parse(frame: bytearray) -> ReplyHarpMessage: return ReplyHarpMessage(frame) From 6a73a9932c0969e2915ac993205ff2a0d312ab2f Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Mon, 18 Apr 2022 18:41:27 -0700 Subject: [PATCH 09/35] grand messages.py refactor --- 10-ftdi.rules | 3 - examples/write_and_read_from_registers.py | 13 +- pyharp/messages.py | 190 ++++++++-------------- 3 files changed, 78 insertions(+), 128 deletions(-) delete mode 100644 10-ftdi.rules diff --git a/10-ftdi.rules b/10-ftdi.rules deleted file mode 100644 index 91f9aff..0000000 --- a/10-ftdi.rules +++ /dev/null @@ -1,3 +0,0 @@ -# UDEV rules for an ftdi RS232 Serial (Uart) IC -SUBSYSTEMS=="usb", ENV{.LOCAL_ifNum}="$attr{bInterfaceNumber}" -SUBSYSTEMS=="usb", KERNEL=="ttyUSB*", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="0666", SYMLINK+="harp_device_%E{.LOCAL_ifNum}" diff --git a/examples/write_and_read_from_registers.py b/examples/write_and_read_from_registers.py index ceb6d52..6f6f029 100755 --- a/examples/write_and_read_from_registers.py +++ b/examples/write_and_read_from_registers.py @@ -21,8 +21,17 @@ device = Device("COM95", "ibl.bin") # Read current analog sensor's higher threshold (ANA_SENSOR_TH0_HIGH) at address 42 -analog_threshold_h = device.send(HarpMessage.ReadU16(42).frame).payload_as_int() -print(f"Analog sensor's higher threshold: {analog_threshold_h}") +#analog_threshold_h = device.send(HarpMessage.ReadU16(42).frame).payload_as_int() +#print(f"Analog sensor's higher threshold: {analog_threshold_h}") + + +data_stream = device.send(HarpMessage.ReadU8(33).frame) # returns a ReplyHarpMessage +#data_stream = device.send(HarpMessage.ReadS16(33).frame).payload_as_int_array() +print(f"Data Stream payload type: {data_stream.payload_type}") +print(f"Data Stream message type: {data_stream.message_type}") +print(f"Data Stream timestamp: {data_stream.timestamp}") +print(f"Data Stream num bytes: {data_stream.length}") +print(f"Data Stream: {data_stream.payload}") # Increase current analog sensor's higher threshold by one unit device.send(HarpMessage.WriteU16(42, analog_threshold_h+1).frame) diff --git a/pyharp/messages.py b/pyharp/messages.py index 866e3f9..3b09d2d 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -1,9 +1,10 @@ from __future__ import annotations # for type hints (PEP 563) +from enum import Enum # from abc import ABC, abstractmethod from typing import Union, Tuple, Optional -class MessageType: +class MessageType(Enum): READ: int = 1 WRITE: int = 2 EVENT: int = 3 @@ -11,7 +12,7 @@ class MessageType: WRITE_ERROR: int = 10 -class PayloadType: +class PayloadType(Enum): isUnsigned: int = 0x00 isSigned: int = 0x80 isFloat: int = 0x40 @@ -37,8 +38,17 @@ class PayloadType: TimestampedS64 = hasTimestamp | S64 TimestampedFloat = hasTimestamp | Float - ALL_UNSIGNED = [U8, U16, U32, TimestampedU8, TimestampedU16] - ALL_SIGNED = [S8, S16, S32, TimestampedS8, TimestampedS16] + +ALL_UNSIGNED = [PayloadType.U8, + PayloadType.U16, + PayloadType.U32, + PayloadType.TimestampedU8, + PayloadType.TimestampedU16] +ALL_SIGNED = [PayloadType.S8, + PayloadType.S16, + PayloadType.S32, + PayloadType.TimestampedS8, + PayloadType.TimestampedS16] class CommonRegisters: @@ -61,6 +71,10 @@ class CommonRegisters: class HarpMessage: + """ + https://github.com/harp-tech/protocol/blob/master/Binary%20Protocol%201.0%201.1%2020180223.pdf + """ + DEFAULT_PORT: int = 255 _frame: bytearray @@ -79,7 +93,27 @@ def frame(self) -> bytearray: @property def message_type(self) -> int: - return self._frame[0] + return MessageType(self._frame[0]) + + @property + def length(self) -> int: + return self._frame[1] + + @property + def address(self) -> int: + return self._frame[2] + + @property + def port(self) -> int: + return self._frame[3] + + @property + def payload_type(self) -> int: + return PayloadType(self._frame[4]) + + @property + def checksum(self) -> int: + return self._frame[-1] @staticmethod def ReadU8(address: int) -> ReadU8HarpMessage: @@ -118,6 +152,7 @@ def parse(frame: bytearray) -> ReplyHarpMessage: return ReplyHarpMessage(frame) +# A Response Message from a harp device. class ReplyHarpMessage(HarpMessage): PAYLOAD_START_ADDRESS: int PAYLOAD_LAST_ADDRESS: int @@ -141,16 +176,10 @@ def __init__( self._frame = frame - self._message_type = frame[0] - self._length = frame[1] - self._address = frame[2] - self._port = frame[3] - self._payload_type = frame[4] - # TOOO: add timestamp here - self._payload = frame[ - 11:-1 - ] # retrieve all content from 11 (where payload starts) until the checksum (not inclusive) - self._checksum = frame[-1] # last index is the checksum + self._timestamp = int.from_bytes(frame[5:9], byteorder="little", signed=False) + \ + int.from_bytes(frame[9:11], byteorder="little", signed=False)*32e-6 + # retrieve all content from 11 (where payload starts) until the checksum (not inclusive) + self._payload = frame[11:-1] # print(f"Type: {self.message_type}") # print(f"Length: {self.length}") @@ -161,61 +190,43 @@ def __init__( # print(f"Checksum: {self.checksum}") # print(f"Frame: {self.frame}") - @property - def frame(self) -> bytearray: - return self._frame - - @property - def message_type(self) -> int: - return self._message_type - - @property - def length(self) -> int: - return self._length - - @property - def address(self) -> int: - return self._address - - @property - def port(self) -> int: - return self._port - - @property - def payload_type(self) -> int: - return self._payload_type - @property def payload(self) -> bytes: return self._payload + @property + def timestamp(self) -> float: + return self._timestamp + def payload_as_int(self) -> int: value: int = 0 - if self.payload_type in PayloadType.ALL_UNSIGNED: + if self.payload_type in ALL_UNSIGNED: value = int.from_bytes(self.payload, byteorder="little", signed=False) - elif self.payload_type in PayloadType.ALL_SIGNED: + elif self.payload_type in ALL_SIGNED: value = int.from_bytes(self.payload, byteorder="little", signed=True) return value - def payload_as_int_array(self): - pass # TODO: implement this + def payload_as_int_array(self) -> list: + datatype_bytes = 0x0F & self.payload_type.value # number of bytes per chunk: 1, 2, 4, or 8. + # TODO: is len(self.payload) == self.length? + signed = True if self.payload_type in ALL_UNSIGNED else False + # Break the payload into chunks of datatype size in bytes + byte_chunks = [self.payload[i: i+datatype_bytes] for i in range(0, len(self.payload), datatype_bytes)] + return [int.from_bytes(chunk, byteorder="little", signed=signed) for chunk in byte_chunks] def payload_as_string(self) -> str: return self.payload.decode("utf-8") - @property - def checksum(self) -> int: - return self._checksum - +# A Read Request Message sent to a harp device. class ReadHarpMessage(HarpMessage): - MESSAGE_TYPE: int = MessageType.READ - _length: int - _address: int - _payload_type: int + MESSAGE_TYPE: int = MessageType.READ.value + _length: int # length of this message minus checksum. + _address: int # address to read from # address to read from + _payload_type: int # p _checksum: int - def __init__(self, payload_type: int, address: int): + def __init__(self, payload_type: PayloadType, address: int): self._frame = bytearray() self._frame.append(self.MESSAGE_TYPE) @@ -225,42 +236,9 @@ def __init__(self, payload_type: int, address: int): self._frame.append(address) self._frame.append(self.DEFAULT_PORT) - self._frame.append(payload_type) + self._frame.append(payload_type.value) self._frame.append(self.calculate_checksum()) - # def calculate_checksum(self) -> int: - # return ( - # self.message_type - # + self.length - # + self.address - # + self.port - # + self.payload_type - # ) & 255 - - @property - def message_type(self) -> int: - return self._frame[0] - - @property - def length(self) -> int: - return self._frame[1] - - @property - def address(self) -> int: - return self._frame[2] - - @property - def port(self) -> int: - return self._frame[3] - - @property - def payload_type(self) -> int: - return self._frame[4] - - @property - def checksum(self) -> int: - return self._frame[5] - class ReadU8HarpMessage(ReadHarpMessage): def __init__(self, address: int): @@ -284,7 +262,7 @@ def __init__(self, address: int): class WriteHarpMessage(HarpMessage): BASE_LENGTH: int = 5 - MESSAGE_TYPE: int = MessageType.WRITE + MESSAGE_TYPE: int = MessageType.WRITE.value _length: int _address: int _payload_type: int @@ -292,7 +270,7 @@ class WriteHarpMessage(HarpMessage): _checksum: int def __init__( - self, payload_type: int, payload: bytes, address: int, offset: int = 0 + self, payload_type: PayloadType, payload: bytes, address: int, offset: int = 0 ): """ @@ -309,47 +287,13 @@ def __init__( self._frame.append(address) self._frame.append(HarpMessage.DEFAULT_PORT) - self._frame.append(payload_type) + self._frame.append(payload_type.value) for i in payload: self._frame.append(i) self._frame.append(self.calculate_checksum()) - # def calculate_checksum(self) -> int: - # return ( - # self.message_type - # + self.length - # + self.address - # + self.port - # + self.payload_type - # + self.payload - # ) & 255 - - @property - def message_type(self) -> int: - return self._frame[0] - - @property - def length(self) -> int: - return self._frame[1] - - @property - def address(self) -> int: - return self._frame[2] - - @property - def port(self) -> int: - return self._frame[3] - - @property - def payload_type(self) -> int: - return self._frame[4] - - @property - def checksum(self) -> int: - return self._frame[-1] - class WriteU8HarpMessage(WriteHarpMessage): def __init__(self, address: int, value: int): From 4108f6b76e63111ba2fa97817cfefd3f07b2a95c Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Mon, 18 Apr 2022 18:58:27 -0700 Subject: [PATCH 10/35] more message.py refactor --- examples/write_and_read_from_registers.py | 4 +-- pyharp/messages.py | 44 ++++++----------------- 2 files changed, 13 insertions(+), 35 deletions(-) diff --git a/examples/write_and_read_from_registers.py b/examples/write_and_read_from_registers.py index 6f6f029..ae7ba96 100755 --- a/examples/write_and_read_from_registers.py +++ b/examples/write_and_read_from_registers.py @@ -27,8 +27,8 @@ data_stream = device.send(HarpMessage.ReadU8(33).frame) # returns a ReplyHarpMessage #data_stream = device.send(HarpMessage.ReadS16(33).frame).payload_as_int_array() -print(f"Data Stream payload type: {data_stream.payload_type}") -print(f"Data Stream message type: {data_stream.message_type}") +print(f"Data Stream payload type: {data_stream.payload_type.name}") +print(f"Data Stream message type: {data_stream.message_type.name}") print(f"Data Stream timestamp: {data_stream.timestamp}") print(f"Data Stream num bytes: {data_stream.length}") print(f"Data Stream: {data_stream.payload}") diff --git a/pyharp/messages.py b/pyharp/messages.py index 3b09d2d..434a134 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -67,9 +67,6 @@ class CommonRegisters: DEVICE_NAME = 0x0C -T = Union[int, bytearray] - - class HarpMessage: """ https://github.com/harp-tech/protocol/blob/master/Binary%20Protocol%201.0%201.1%2020180223.pdf @@ -154,38 +151,27 @@ def parse(frame: bytearray) -> ReplyHarpMessage: # A Response Message from a harp device. class ReplyHarpMessage(HarpMessage): - PAYLOAD_START_ADDRESS: int - PAYLOAD_LAST_ADDRESS: int - _message_type: int - _length: int - _address: int - _payload_type: int - _payload: bytes - _checksum: int + def __init__( self, frame: bytearray, ): """ - :param payload_type: - :param payload: - :param address: - :param offset: how many bytes more besides the length corresponding to U8 (for example, for U16 it would be offset=1) + :param frame: the serialized message frame. """ self._frame = frame - self._timestamp = int.from_bytes(frame[5:9], byteorder="little", signed=False) + \ int.from_bytes(frame[9:11], byteorder="little", signed=False)*32e-6 # retrieve all content from 11 (where payload starts) until the checksum (not inclusive) self._payload = frame[11:-1] - # print(f"Type: {self.message_type}") + # print(f"Type: {self.message_type.name}") # print(f"Length: {self.length}") # print(f"Address: {self.address}") # print(f"Port: {self.port}") - # print(f"Payload Type: {self.payload_type}") + # print(f"Payload Type: {self.payload_type.name}") # print(f"Payload: {self.payload}") # print(f"Checksum: {self.checksum}") # print(f"Frame: {self.frame}") @@ -207,7 +193,8 @@ def payload_as_int(self) -> int: return value def payload_as_int_array(self) -> list: - datatype_bytes = 0x0F & self.payload_type.value # number of bytes per chunk: 1, 2, 4, or 8. + # Number of bytes per chunk. Get this from the bit field structure. + datatype_bytes = 0x0F & self.payload_type.value # TODO: is len(self.payload) == self.length? signed = True if self.payload_type in ALL_UNSIGNED else False # Break the payload into chunks of datatype size in bytes @@ -220,20 +207,16 @@ def payload_as_string(self) -> str: # A Read Request Message sent to a harp device. class ReadHarpMessage(HarpMessage): - MESSAGE_TYPE: int = MessageType.READ.value - _length: int # length of this message minus checksum. - _address: int # address to read from # address to read from - _payload_type: int # p - _checksum: int + MESSAGE_TYPE: int = MessageType.READ + def __init__(self, payload_type: PayloadType, address: int): self._frame = bytearray() - self._frame.append(self.MESSAGE_TYPE) + self._frame.append(self.MESSAGE_TYPE.value) length: int = 4 self._frame.append(length) - self._frame.append(address) self._frame.append(self.DEFAULT_PORT) self._frame.append(payload_type.value) @@ -262,12 +245,7 @@ def __init__(self, address: int): class WriteHarpMessage(HarpMessage): BASE_LENGTH: int = 5 - MESSAGE_TYPE: int = MessageType.WRITE.value - _length: int - _address: int - _payload_type: int - _payload: int - _checksum: int + MESSAGE_TYPE: int = MessageType.WRITE def __init__( self, payload_type: PayloadType, payload: bytes, address: int, offset: int = 0 @@ -281,7 +259,7 @@ def __init__( """ self._frame = bytearray() - self._frame.append(self.MESSAGE_TYPE) + self._frame.append(self.MESSAGE_TYPE.value) self._frame.append(self.BASE_LENGTH + offset) From e3457bb148824ea869a09d68b899e67beeafa3cd Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Tue, 19 Apr 2022 16:10:16 -0700 Subject: [PATCH 11/35] adding harp rules --- 10-harp.rules | 3 ++ examples/get_info.py | 2 +- examples/write_and_read_from_registers.py | 32 ++++++------- pyharp/device.py | 56 ++++++++++++++++++++--- 4 files changed, 70 insertions(+), 23 deletions(-) create mode 100644 10-harp.rules diff --git a/10-harp.rules b/10-harp.rules new file mode 100644 index 0000000..c5114ef --- /dev/null +++ b/10-harp.rules @@ -0,0 +1,3 @@ +# UDEV rules for a Harp Device (actually an ftdi RS232 Serial [Uart] IC) +SUBSYSTEMS=="usb", ENV{.LOCAL_ifNum}="$attr{bInterfaceNumber}" +SUBSYSTEMS=="usb", KERNEL=="ttyUSB*", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="0666", SYMLINK+="harp_device_%E{.LOCAL_ifNum}" diff --git a/examples/get_info.py b/examples/get_info.py index 56be774..44e4f18 100755 --- a/examples/get_info.py +++ b/examples/get_info.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from pyharp.device import Device +from pyharp.device import Device, DeviceMode from pyharp.messages import HarpMessage from pyharp.messages import MessageType from struct import * diff --git a/examples/write_and_read_from_registers.py b/examples/write_and_read_from_registers.py index ae7ba96..3634353 100755 --- a/examples/write_and_read_from_registers.py +++ b/examples/write_and_read_from_registers.py @@ -31,22 +31,22 @@ print(f"Data Stream message type: {data_stream.message_type.name}") print(f"Data Stream timestamp: {data_stream.timestamp}") print(f"Data Stream num bytes: {data_stream.length}") -print(f"Data Stream: {data_stream.payload}") - -# Increase current analog sensor's higher threshold by one unit -device.send(HarpMessage.WriteU16(42, analog_threshold_h+1).frame) - -# Check if the register was well written -analog_threshold_h = device.send(HarpMessage.ReadU16(42).frame).payload_as_int() -print(f"Analog sensor's higher threshold: {analog_threshold_h}") - -# Read 10 samples of the analog sensor and display the values -# The value is at register STREAM[0], address 33 -analog_sensor = [] -for x in range(10): - value = device.send(HarpMessage.ReadS16(33).frame).payload_as_int() - analog_sensor.append(value & 0xffff) -print(f"Analog sensor's values: {analog_sensor}") +print(f"Data Stream payload: {data_stream.payload}") + +## Increase current analog sensor's higher threshold by one unit +#device.send(HarpMessage.WriteU16(42, analog_threshold_h+1).frame) +# +## Check if the register was well written +#analog_threshold_h = device.send(HarpMessage.ReadU16(42).frame).payload_as_int() +#print(f"Analog sensor's higher threshold: {analog_threshold_h}") +# +## Read 10 samples of the analog sensor and display the values +## The value is at register STREAM[0], address 33 +#analog_sensor = [] +#for x in range(10): +# value = device.send(HarpMessage.ReadS16(33).frame).payload_as_int() +# analog_sensor.append(value & 0xffff) +#print(f"Analog sensor's values: {analog_sensor}") # Close connection device.disconnect() diff --git a/pyharp/device.py b/pyharp/device.py index b145f9c..1c608ba 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -5,6 +5,14 @@ from pyharp.messages import HarpMessage, ReplyHarpMessage from pyharp.messages import CommonRegisters from pyharp.device_names import device_names +from enum import Enum + + +class DeviceMode(Enum): + Standby = 0 + Active = 1 + Reserved = 2 + Speed = 3 class Device: @@ -53,16 +61,13 @@ def load(self) -> None: def info(self) -> None: print("Device info:") - #print(f"* Who am I (ID): {self.WHO_AM_I}") - #print(f"* Who am I (Device): {self.WHO_AM_I_DEVICE}") print(f"* Who am I: ({self.WHO_AM_I}) {self.WHO_AM_I_DEVICE}") print(f"* HW version: {self.HW_VERSION_H}.{self.HW_VERSION_L}") print(f"* Assembly version: {self.ASSEMBLY_VERSION}") print(f"* HARP version: {self.HARP_VERSION_H}.{self.HARP_VERSION_L}") - print( - f"* Firmware version: {self.FIRMWARE_VERSION_H}.{self.FIRMWARE_VERSION_L}" - ) + print(f"* Firmware version: {self.FIRMWARE_VERSION_H}.{self.FIRMWARE_VERSION_L}") print(f"* Device user name: {self.DEVICE_NAME}") + print(f"* Mode: {self.read_device_mode().name}") def read(self): pass @@ -158,7 +163,7 @@ def read_fw_l_version(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU8(address).frame, dump=False - ) +) return reply.payload_as_int() @@ -172,6 +177,45 @@ def read_device_name(self) -> str: return reply.payload_as_string() + def read_device_mode(self) -> int: + address = CommonRegisters.OPERATION_CTRL + reply = self.send(HarpMessage.ReadU8(address).frame) + return DeviceMode(reply.payload_as_int() & 0x03) + +# TODO: Not sure if we want to implement these. Delete if no. +# def set_mode(self, mode: DeviceMode): +# address = CommonRegisters.OPERATION_CTRL +# # Read register first. +# reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() +# reg_value &= ~0x03 # mask off old mode. +# reg_value |= mode.value +# reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) +# +# def enable_alive_en(self): +# """Enable ALIVE_EN such that the device sends an event each second.""" +# address = CommonRegisters.OPERATION_CTRL +# # Read register first. +# reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() +# reg_value |= (1 << 7) +# reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) +# +# def enable_status_led(self): +# """enable the device's status led if one exists.""" +# address = CommonRegisters.OPERATION_CTRL +# # Read register first. +# reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() +# reg_value |= (1 << 6) +# reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) + + def disable_alive_en(self): + """disable ALIVE_EN such that the device does not send an event each second.""" + address = CommonRegisters.OPERATION_CTRL + # Read register first. + reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() + reg_value &= ~(1 << 7) # mask off old mode. + reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) + + def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: self._ser.write(message_bytes) From 455de819097220bce69ec44459df44ae28af5bf3 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Tue, 19 Apr 2022 16:14:54 -0700 Subject: [PATCH 12/35] adding simple behavior io stuff --- examples/behavior_device_driver_test.py | 41 +++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100755 examples/behavior_device_driver_test.py diff --git a/examples/behavior_device_driver_test.py b/examples/behavior_device_driver_test.py new file mode 100755 index 0000000..410584e --- /dev/null +++ b/examples/behavior_device_driver_test.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +from pyharp.drivers.behavior import Behavior +from pyharp.messages import HarpMessage +from pyharp.messages import MessageType +from struct import * +import os + + +# Open the device and print the info on screen +# Open serial connection and save communication to a file +device = None +if os.name == 'posix': # check for Linux. + device = Behavior("/dev/harp_device_00", "ibl.bin") +else: # assume Windows. + device = Behavior("COM95", "ibl.bin") + +print(f"digital outputs: {device.all_output_states:016b}") +print(f"setting digital outputs") +device.all_output_states = 0x0000 +device.set_outputs(0x0000) +print(f"digital outputs: {device.all_output_states:016b}") + +device.D0 = 1 +print(f"D0: {device.D0}") +device.D0 = 0 +print(f"D0: {device.D0}") + +device.D1 = 1 +print(f"D1: {device.D1}") +device.D1 = 0 +print(f"D1: {device.D1}") + +#import time +#while True: +# print(f"PORT0 IN State: {device.port0_i0}") +# print(f"PORT0 IO State: {device.port0_io0}") +# print(f"PORT0 OUT State: {device.port0_o0}") +# print(f"all port io states: {device.all_port_io_states}") +# print(f"all port output states: {device.all_port_output_states}") +# print() +# time.sleep(0.1) From 4e5e58fe33dc6c85006bfc2ca68c190e8ffa8666 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Tue, 19 Apr 2022 16:16:22 -0700 Subject: [PATCH 13/35] adding prelim behavior driver --- pyharp/drivers/__init__.py | 0 pyharp/drivers/behavior.py | 313 +++++++++++++++++++++++++++++++++++++ 2 files changed, 313 insertions(+) create mode 100644 pyharp/drivers/__init__.py create mode 100644 pyharp/drivers/behavior.py diff --git a/pyharp/drivers/__init__.py b/pyharp/drivers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyharp/drivers/behavior.py b/pyharp/drivers/behavior.py new file mode 100644 index 0000000..1a0f6e2 --- /dev/null +++ b/pyharp/drivers/behavior.py @@ -0,0 +1,313 @@ +"""Behavior Device Driver.""" + +from pyharp.messages import HarpMessage, ReplyHarpMessage +from pyharp.device_names import device_names +from pyharp.device import Device +import serial +from serial.serialutil import SerialException + + +# Type, Base Address, "Description." +REGISTERS = \ +{ # RJ45 "PORT" (0, 1, 2) Digital Inputs + "PORT_DIS" : ("U8", 32, "Reflects the state of DI digital lines of each Port."), + + # Manipulate any of the boards digital outputs. + "OUTPUTS_SET": ("U16", 34, "Set the corresponding output."), + "OUTPUTS_CLR": ("U16", 35, "Clear the corresponding output."), + "OUTPUTS_TOGGLE": ("U16", 36, "Toggle the corresponding output."), + "OUTPUTS_OUT": ("U16", 37, "Control corresponding output."), + + # RJ45 "PORT" (0, 1, 2) Digital IOs + "PORT_DIOS_SET": ("U8", 38, "Set the corresponding DIO."), + "PORT_DIOS_CLEAR": ("U8", 39, "Clear the corresponding DIO."), + "PORT_DIOS_TOGGLE": ("U8", 40, "Toggle the corresponding DIO."), + "PORT_DIOS_OUT": ("U8", 41, "Control the corresponding DIO."), + "PORT_DIOS_CONF": ("U8", 42, "Set the DIOs direction (1 is output)."), + "PORT_DIOS_IN": ("U8", 43, "State of the DIOs."), +} + + + +class Behavior: + """Driver for BehaviorDevice.""" + + # On Linux, the symlink to the first detected harp device. + # Name set in udev rules and will increment with subsequent devices. + DEVICE_NAME = "Behavior" + DEFAULT_PORT_NAME = "/dev/harp_device_00" + ID = 1216 + + # TODO: put this in a base class? + READ_MSG_LOOKUP = \ + { + "U8": HarpMessage.ReadU8, + "U16": HarpMessage.ReadU16, + "S16": HarpMessage.ReadS16, + } + + WRITE_MSG_LOOKUP = \ + { + "U8": HarpMessage.WriteU8, + "U16": HarpMessage.WriteU16, + "S16": HarpMessage.WriteS16, + } + + + def __init__(self, port_name=None, output_filename=None): + """Class constructor. Connect to a device.""" + + self.device = None + + try: + if port_name is None: + self.device = Device(self.__class__.DEFAULT_PORT_NAME, output_filename) + else: + self.device = Device(port_name, output_filename) + except (FileNotFoundError, SerialException): + print("Error: Failed to connect to Behavior Device. Is it plugged in?") + raise + + if self.device.WHO_AM_I != self.__class__.ID: + raise IOError("Error: Did not connect to Harp Behavior Device.") + + + # TODO: put this in a base class? + def get_reg_info(self, reg_name: str) -> str: + """get info for this device's particular reg.""" + try: + return REGISTERS[reg_name][2] + except KeyError: + raise KeyError(f"reg: {reg_name} is not a register in " + "{self.__class__.name} Device's register map.") + + +# Board inputs, outputs, and some settings configured as @properties. + # INPUTS + @property + def all_port_input_states(self): + """return the state of all PORT digital inputs.""" + reg_type, reg_index, _ = REGISTERS["PORT_DIS"] + read_message_type = self.__class__.READ_MSG_LOOKUP[reg_type] + return self.device.send(read_message_type(reg_index).frame).payload_as_int() + + @property + def port0_i0(self): + """return the state of port0 digital in 0.""" + return self.all_port_input_states & 0x01 + + @property + def port1_i0(self): + """return the state of port0 digital in 0.""" + return (self.all_port_input_states >> 1) & 0x01 + + @property + def port2_i0(self): + """return the state of port2 digital in 0.""" + return (self.all_port_input_states >> 2) & 0x01 + + # IOs + def set_port_io_states(self, bitmask : int): + """set the state of all PORT digital ios. (1 is output.)""" + reg_type, reg_index, _ = REGISTERS["PORT_DIOS_CONF"] + write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] + self.device.send(write_message_type(reg_index, bitmask).frame) + + @property #FIXME: this doesn't seem to work + def all_port_io_states(self): + """return the state of all PORT digital ios.""" + reg_type, reg_index, _ = REGISTERS["PORT_DIOS_IN"] + read_message_type = self.__class__.READ_MSG_LOOKUP[reg_type] + return self.device.send(read_message_type(reg_index).frame).payload_as_int() + + @property + def port0_io0(self): + """read the digital io state.""" + return self.all_port_io_states & 0x01 + + @port0_io0.setter + def port0_io0(self, value: int): + """write port0 digital io state.""" + pass + + @property + def port1_io0(self): + """read the digital io state.""" + return (self.all_port_io_states >> 1) & 0x01 + + @port0_io0.setter + def port1_io0(self, value: int): + """write port0 digital io state.""" + self.set_outputs(value&0x01) + + @property + def port2_io0(self): + """read the digital io state.""" + return (self.all_port_io_states >> 2) & 0x01 + + @port0_io0.setter + def port2_io0(self, value: int): + """write port0 digital io state.""" + pass + + + # OUTPUTS + @property + def all_output_states(self): + """return the state of all PORT digital inputs.""" + reg_type, reg_index, _ = REGISTERS["OUTPUTS_OUT"] + read_message_type = self.__class__.READ_MSG_LOOKUP[reg_type] + return self.device.send(read_message_type(reg_index).frame).payload_as_int() + + @all_output_states.setter + def all_output_states(self, bitmask : int): + """set the state of all PORT digital inputs.""" + reg_type, reg_index, _ = REGISTERS["OUTPUTS_OUT"] + write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] + return self.device.send(write_message_type(reg_index, bitmask).frame) + + def set_outputs(self, bitmask : int): + """set digital outputs to logic 1 according to bitmask.""" + reg_type, reg_index, _ = REGISTERS["OUTPUTS_SET"] + write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] + return self.device.send(write_message_type(reg_index, bitmask).frame) + + def clear_outputs(self, bitmask : int): + """clear digital outputs (specified with logic 1) according to bitmask.""" + reg_type, reg_index, _ = REGISTERS["OUTPUTS_CLR"] + write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] + return self.device.send(write_message_type(reg_index, bitmask).frame) + + @property + def D0(self): + """read the digital output D0 state.""" + return (self.all_output_states >> 10) & 0x01 + + @D0.setter + def D0(self, value): + """set the digital output D0 state.""" + if value: + self.set_outputs(1 << 10) + else: + self.clear_outputs(1 << 10) + + @property + def D1(self): + """read the digital output D1 state.""" + return (self.all_output_states >> 11) & 0x01 + + @D1.setter + def D1(self, value): + """set the digital output D1 state.""" + if value: + self.set_outputs(1 << 11) + else: + self.clear_outputs(1 << 11) + + @property + def D2(self): + """read the digital output D2 state.""" + return (self.all_output_states >> 12) & 0x01 + + @D2.setter + def D2(self, value): + """set the digital output D2 state.""" + if value: + self.set_outputs(1 << 12) + else: + self.clear_outputs(1 << 12) + + @property + def D3(self): + """read the digital output D3 state.""" + return (self.all_output_states >> 10) & 0x01 + + @D3.setter + def D3(self, value): + """set the digital output D3 state.""" + if value: + self.set_outputs(1 << 13) + else: + self.clear_outputs(1 << 13) + + @property + def port0_D0(self): + return self.all_output_states & 0x01 + + @port0_D0.setter + def port0_D0(self, value): + if value: + self.set_outputs(1) + else: + self.clear_outputs(1) + + @property + def port1_D0(self): + return (self.all_output_states >> 1) & 0x01 + + @port1_D0.setter + def port1_D0(self, value): + if value: + self.set_outputs(1 << 1) + else: + self.clear_outputs(1 << 1) + + @property + def port2_D0(self): + return (self.all_output_states >> 2) & 0x01 + + @port2_D0.setter + def port2_D0(self, value): + if value: + self.set_outputs(1 << 2) + else: + self.clear_outputs(1 << 2) + + + @property + def port0_12V(self): + return (self.all_output_states >> 3) & 0x01 + + @port0_12V.setter + def port0_12V(self, value): + if value: + self.set_outputs(1 << 3) + else: + self.clear_outputs(1 << 3) + + @property + def port1_12V(self): + return (self.all_output_states >> 4) & 0x01 + + @port1_12V.setter + def port1_12V(self, value): + if value: + self.set_outputs(1 << 4) + else: + self.clear_outputs(1 << 4) + + @property + def port2_12V(self): + return (self.all_output_states >> 5) & 0x01 + + @port2_12V.setter + def port2_12V(self, value): + if value: + self.set_outputs(1 << 5) + else: + self.clear_outputs(1 << 5) + + + def __enter__(self): + """Setup for the 'with' statement""" + return self + + + def __exit__(self, *args): + """Cleanup for the 'with' statement""" + self.device.disconnect() + + + def __del__(self): + """Cleanup when Device gets garbage collected.""" + self.device.disconnect() From 0e47767cf9ee0c23485091d946e378d35c2f5f36 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Wed, 20 Apr 2022 16:19:44 -0700 Subject: [PATCH 14/35] adding a string representation --- pyharp/messages.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyharp/messages.py b/pyharp/messages.py index 434a134..cab9351 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -167,6 +167,16 @@ def __init__( # retrieve all content from 11 (where payload starts) until the checksum (not inclusive) self._payload = frame[11:-1] + def __str__(self): + return f"Type: {self.message_type.name}\r\n" + \ + f"Length: {self.length}\r\n" + \ + f"Address: {self.address}\r\n" + \ + f"Port: {self.port}\r\n" + \ + f"Timestampe: {self.timestamp:6f}\r\n" + \ + f"Payload Type: {self.payload_type.name}\r\n" + \ + f"Payload: {self.payload}\r\n" + \ + f"Checksum: {self.checksum}\r\n" + \ + f"Raw Frame: {self.frame}\r\n" # print(f"Type: {self.message_type.name}") # print(f"Length: {self.length}") # print(f"Address: {self.address}") From 43629c0b19aaf9eb8ccbc7095533c96769137687 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Wed, 20 Apr 2022 17:55:12 -0700 Subject: [PATCH 15/35] added __str__ and active mode script --- examples/behavior_device_driver_test.py | 32 ++-- examples/wait_for_events.py | 25 +++ examples/write_and_read_from_registers.py | 11 ++ pyharp/device.py | 53 ++++--- pyharp/drivers/behavior.py | 177 +++++++++++++++------- pyharp/messages.py | 52 +++++-- 6 files changed, 249 insertions(+), 101 deletions(-) create mode 100755 examples/wait_for_events.py diff --git a/examples/behavior_device_driver_test.py b/examples/behavior_device_driver_test.py index 410584e..d9cfab7 100755 --- a/examples/behavior_device_driver_test.py +++ b/examples/behavior_device_driver_test.py @@ -14,21 +14,33 @@ else: # assume Windows. device = Behavior("COM95", "ibl.bin") +print(f"digital inputs: {device.all_input_states:03b}") print(f"digital outputs: {device.all_output_states:016b}") print(f"setting digital outputs") -device.all_output_states = 0x0000 -device.set_outputs(0x0000) +#device.all_output_states = 0x0000 # Set the whole port directly. +#device.set_outputs(0xFFFF) # Set the values set to logic 1 only. +#device.clear_outputs(0xFFFF)# Clear values set to logic 1 only. print(f"digital outputs: {device.all_output_states:016b}") +device.set_io_configuration(0b111) -device.D0 = 1 -print(f"D0: {device.D0}") -device.D0 = 0 -print(f"D0: {device.D0}") +# TODO: FIXME. IOs are not working +#device.set_io_configuration(0b111) # This is getting ignored? +#device.set_io_outputs(0b000) +#device.all_io_states = 0b000 +#print(f"digital ios: {device.all_io_states:03b}") + +#device.D0 = 0 +#print(f"D0: {device.D0}") +#device.D0 = 1 +#print(f"D0: {device.D0}") +# +#device.D1 = 0 +#print(f"D1: {device.D1}") +#device.D1 = 1 +#print(f"D1: {device.D1}") +# +#print(f"DI2: {device.DI2}") -device.D1 = 1 -print(f"D1: {device.D1}") -device.D1 = 0 -print(f"D1: {device.D1}") #import time #while True: diff --git a/examples/wait_for_events.py b/examples/wait_for_events.py new file mode 100755 index 0000000..44c2b30 --- /dev/null +++ b/examples/wait_for_events.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +from pyharp.drivers.behavior import Behavior +from pyharp.messages import HarpMessage +from pyharp.messages import MessageType +from struct import * +import os + +from pyharp.device import DeviceMode + + +# Open the device and print the info on screen +# Open serial connection and save communication to a file +device = None +if os.name == 'posix': # check for Linux. + device = Behavior("/dev/harp_device_00", "ibl.bin") +else: # assume Windows. + device = Behavior("COM95", "ibl.bin") + +print("Setting mode to active.") +device.device.set_mode(DeviceMode.Active) +import time +while True: + event_response = device.device._read() # read any incoming events. + if event_response is not None: + print(event_response) diff --git a/examples/write_and_read_from_registers.py b/examples/write_and_read_from_registers.py index 3634353..4430b00 100755 --- a/examples/write_and_read_from_registers.py +++ b/examples/write_and_read_from_registers.py @@ -25,6 +25,9 @@ #print(f"Analog sensor's higher threshold: {analog_threshold_h}") +import time + +print(f"System time: {time.perf_counter():.6f}") data_stream = device.send(HarpMessage.ReadU8(33).frame) # returns a ReplyHarpMessage #data_stream = device.send(HarpMessage.ReadS16(33).frame).payload_as_int_array() print(f"Data Stream payload type: {data_stream.payload_type.name}") @@ -33,6 +36,14 @@ print(f"Data Stream num bytes: {data_stream.length}") print(f"Data Stream payload: {data_stream.payload}") +print(f"System time: {time.perf_counter():.6f}") +event_reg_response = device.send(HarpMessage.ReadU8(77).frame) # returns a ReplyHarpMessage +print(f"EVNT_ENABLE payload type: {event_reg_response.payload_type.name}") +print(f"EVNT_ENABLE message type: {event_reg_response.message_type.name}") +print(f"EVNT_ENABLE timestamp: {event_reg_response.timestamp}") +print(f"EVNT_ENABLE num bytes: {event_reg_response.length}") +print(f"EVNT_ENABLE payload: {event_reg_response.payload[0]:08b}") + ## Increase current analog sensor's higher threshold by one unit #device.send(HarpMessage.WriteU16(42, analog_threshold_h+1).frame) # diff --git a/pyharp/device.py b/pyharp/device.py index 1c608ba..4b71f78 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -1,5 +1,5 @@ import serial -from typing import Optional +from typing import Optional, Union from pathlib import Path from pyharp.messages import HarpMessage, ReplyHarpMessage @@ -177,19 +177,22 @@ def read_device_name(self) -> str: return reply.payload_as_string() - def read_device_mode(self) -> int: + def read_device_mode(self) -> DeviceMode: address = CommonRegisters.OPERATION_CTRL reply = self.send(HarpMessage.ReadU8(address).frame) + print(reply) return DeviceMode(reply.payload_as_int() & 0x03) # TODO: Not sure if we want to implement these. Delete if no. -# def set_mode(self, mode: DeviceMode): -# address = CommonRegisters.OPERATION_CTRL -# # Read register first. -# reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() -# reg_value &= ~0x03 # mask off old mode. -# reg_value |= mode.value -# reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) + def set_mode(self, mode: DeviceMode) -> ReplyHarpMessage: + """Change the device's OPMODE. Reply can be ignored.""" + address = CommonRegisters.OPERATION_CTRL + # Read register first. + reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() + reg_value &= ~0x03 # mask off old mode. + reg_value |= mode.value + reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) + return reply # # def enable_alive_en(self): # """Enable ALIVE_EN such that the device sends an event each second.""" @@ -217,27 +220,35 @@ def disable_alive_en(self): def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: - + """Send a harp message; return the device's reply.""" self._ser.write(message_bytes) # TODO: handle case where read is None - - message_type = self._ser.read(1)[0] # byte array with only one byte - message_length = self._ser.read(1)[0] - message_content = self._ser.read(message_length) - - frame = bytearray() - frame.append(message_type) - frame.append(message_length) - frame += message_content - - reply: ReplyHarpMessage = HarpMessage.parse(frame) + reply: ReplyHarpMessage = self._read() if dump: self._dump_reply(reply.frame) return reply + + def _read(self) -> Union[ReplyHarpMessage, None]: + """Read an incoming serial message.""" + try: + message_type = self._ser.read(1)[0] # byte array with only one byte + message_length = self._ser.read(1)[0] + message_content = self._ser.read(message_length) + + frame = bytearray() + frame.append(message_type) + frame.append(message_length) + frame += message_content + + return HarpMessage.parse(frame) + except IndexError: + return None + + def _dump_reply(self, reply: bytes): assert self._dump_file_path is not None with self._dump_file_path.open(mode="ab") as f: diff --git a/pyharp/drivers/behavior.py b/pyharp/drivers/behavior.py index 1a0f6e2..77081d3 100644 --- a/pyharp/drivers/behavior.py +++ b/pyharp/drivers/behavior.py @@ -5,14 +5,16 @@ from pyharp.device import Device import serial from serial.serialutil import SerialException +from enum import Enum +# These definitions are from app_regs.h in the firmware. # Type, Base Address, "Description." REGISTERS = \ { # RJ45 "PORT" (0, 1, 2) Digital Inputs "PORT_DIS" : ("U8", 32, "Reflects the state of DI digital lines of each Port."), - # Manipulate any of the boards digital outputs. + # Manipulate any of the board's digital outputs. "OUTPUTS_SET": ("U16", 34, "Set the corresponding output."), "OUTPUTS_CLR": ("U16", 35, "Clear the corresponding output."), "OUTPUTS_TOGGLE": ("U16", 36, "Toggle the corresponding output."), @@ -25,9 +27,48 @@ "PORT_DIOS_OUT": ("U8", 41, "Control the corresponding DIO."), "PORT_DIOS_CONF": ("U8", 42, "Set the DIOs direction (1 is output)."), "PORT_DIOS_IN": ("U8", 43, "State of the DIOs."), + + "EVNT_ENABLE": ("U8", 77, "Enable events within the bitfields."), } +# Register Bitfields +class PORT_DIS(Enum): + DI0 = 0 + DI1 = 1 + DI2 = 2 + +class OUTPUTS_OUT(Enum): + PORT0_DO = 0 + PORT0_D1 = 1 + PORT0_D2 = 2 + + PORT0_12V = 3 + PORT1_12V = 4 + PORT2_12V = 5 + + B_LED0 = 6 + B_LED1 = 7 + B_RGB0 = 8 + B_RGB1 = 9 + + DO0 = 10 + DO1 = 11 + DO2 = 12 + DO3 = 13 + +class PORT_DIOS_IN(Enum): + DIO0 = 0 + DIO1 = 0 + DIO2 = 0 + +class EVNT_ENABLE(Enum): + PORT_DIS = 0 + PORT_DIOS_IN = 1 + DATA = 2 + CAM0 = 3 + CAM1 = 4 + class Behavior: """Driver for BehaviorDevice.""" @@ -72,7 +113,6 @@ def __init__(self, port_name=None, output_filename=None): raise IOError("Error: Did not connect to Harp Behavior Device.") - # TODO: put this in a base class? def get_reg_info(self, reg_name: str) -> str: """get info for this device's particular reg.""" try: @@ -85,70 +125,95 @@ def get_reg_info(self, reg_name: str) -> str: # Board inputs, outputs, and some settings configured as @properties. # INPUTS @property - def all_port_input_states(self): + def all_input_states(self): """return the state of all PORT digital inputs.""" reg_type, reg_index, _ = REGISTERS["PORT_DIS"] read_message_type = self.__class__.READ_MSG_LOOKUP[reg_type] return self.device.send(read_message_type(reg_index).frame).payload_as_int() @property - def port0_i0(self): - """return the state of port0 digital in 0.""" + def DI0(self): + """return the state of port0 digital input 0.""" return self.all_port_input_states & 0x01 @property - def port1_i0(self): - """return the state of port0 digital in 0.""" - return (self.all_port_input_states >> 1) & 0x01 - - @property - def port2_i0(self): - """return the state of port2 digital in 0.""" - return (self.all_port_input_states >> 2) & 0x01 - - # IOs - def set_port_io_states(self, bitmask : int): - """set the state of all PORT digital ios. (1 is output.)""" - reg_type, reg_index, _ = REGISTERS["PORT_DIOS_CONF"] - write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] - self.device.send(write_message_type(reg_index, bitmask).frame) - - @property #FIXME: this doesn't seem to work - def all_port_io_states(self): - """return the state of all PORT digital ios.""" - reg_type, reg_index, _ = REGISTERS["PORT_DIOS_IN"] - read_message_type = self.__class__.READ_MSG_LOOKUP[reg_type] - return self.device.send(read_message_type(reg_index).frame).payload_as_int() - - @property - def port0_io0(self): - """read the digital io state.""" - return self.all_port_io_states & 0x01 - - @port0_io0.setter - def port0_io0(self, value: int): - """write port0 digital io state.""" - pass - - @property - def port1_io0(self): - """read the digital io state.""" - return (self.all_port_io_states >> 1) & 0x01 - - @port0_io0.setter - def port1_io0(self, value: int): - """write port0 digital io state.""" - self.set_outputs(value&0x01) + def DI1(self): + """return the state of port1 digital input 0.""" + offset = PORT_DIS.DI1.value + return (self.all_port_input_states >> offset) & 0x01 @property - def port2_io0(self): - """read the digital io state.""" - return (self.all_port_io_states >> 2) & 0x01 - - @port0_io0.setter - def port2_io0(self, value: int): - """write port0 digital io state.""" - pass + def DI2(self): + """return the state of port2 digital input 0.""" + offset = PORT_DIS.DI2.value + return (self.all_input_states >> offset) & 0x01 + +# These do not work currently. Perhaps something needs to be cleared (MIMIC?) +# before they will configure properly. +# # IOs +# def set_io_configuration(self, bitmask : int): +# """set the state of all PORT digital ios. (1 is output.)""" +# reg_type, reg_index, _ = REGISTERS["PORT_DIOS_CONF"] +# write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] +# self.device.send(write_message_type(reg_index, bitmask).frame) +# +# @property +# def all_io_states(self): +# """return the state of all PORT digital ios.""" +# reg_type, reg_index, _ = REGISTERS["PORT_DIOS_IN"] +# read_message_type = self.__class__.READ_MSG_LOOKUP[reg_type] +# return self.device.send(read_message_type(reg_index).frame).payload_as_int() +# +# @all_io_states.setter +# def all_io_states(self, bitmask : int): +# """set the state of all PORT digital input/outputs.""" +# # Setting the state of the "DIO" pins, requires writing to the +# # _IN register, which is different from the OUTPUT +# reg_type, reg_index, _ = REGISTERS["PORT_DIOS_IN"] +# write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] +# return self.device.send(write_message_type(reg_index, bitmask).frame) +# +# def set_io_outputs(self, bitmask : int): +# """set digital input/outputs to logic 1 according to bitmask.""" +# reg_type, reg_index, _ = REGISTERS["PORT_DIOS_SET"] +# write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] +# return self.device.send(write_message_type(reg_index, bitmask).frame) +# +# def clear_io_outputs(self, bitmask : int): +# """clear digital input/outputs (specified with logic 1) according to bitmask.""" +# reg_type, reg_index, _ = REGISTERS["PORT_DIOS_CLEAR"] +# write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] +# return self.device.send(write_message_type(reg_index, bitmask).frame) +# +# @property +# def port0_io0(self): +# """read the digital io state.""" +# return self.all_port_io_states & 0x01 +# +# @port0_io0.setter +# def port0_io0(self, value: int): +# """write port0 digital io state.""" +# pass +# +# @property +# def port1_io0(self): +# """read the digital io state.""" +# return (self.all_port_io_states >> 1) & 0x01 +# +# @port0_io0.setter +# def port1_io0(self, value: int): +# """write port0 digital io state.""" +# self.set_outputs(value&0x01) +# +# @property +# def port2_io0(self): +# """read the digital io state.""" +# return (self.all_port_io_states >> 2) & 0x01 +# +# @port0_io0.setter +# def port2_io0(self, value: int): +# """write port0 digital io state.""" +# pass # OUTPUTS @@ -298,6 +363,8 @@ def port2_12V(self, value): self.clear_outputs(1 << 5) + + def __enter__(self): """Setup for the 'with' statement""" return self diff --git a/pyharp/messages.py b/pyharp/messages.py index cab9351..d1035b2 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -89,7 +89,7 @@ def frame(self) -> bytearray: return self._frame @property - def message_type(self) -> int: + def message_type(self) -> MessageType: return MessageType(self._frame[0]) @property @@ -162,29 +162,51 @@ def __init__( """ self._frame = frame - self._timestamp = int.from_bytes(frame[5:9], byteorder="little", signed=False) + \ - int.from_bytes(frame[9:11], byteorder="little", signed=False)*32e-6 # retrieve all content from 11 (where payload starts) until the checksum (not inclusive) self._payload = frame[11:-1] + # Assign timestamp after _payload since @properties all rely on self._payload. + self._timestamp = int.from_bytes(frame[5:9], byteorder="little", signed=False) + \ + int.from_bytes(frame[9:11], byteorder="little", signed=False)*32e-6 + # Timestamp is junk if it's not present. + if self.payload_type.value & PayloadType.hasTimestamp.value: + self._timestamp = None + + + def __repr__(self): + """Print debug representation of a reply message.""" + print(self.__str__()) + print(f"Raw Frame: {self.frame}") + + def __str__(self): + """Print friendly representation of a reply message.""" + payload_str = "" + format_str = "" + if self.payload_type.value & 0x01: + format_str = '08b' + elif self.payload_type.value & 0x02: + format_str = '016b' + elif self.payload_type.value & 0x04: + if self.message_type.value & PayloadType.isFloat.value: + format_str = '.6f' + else: + format_str = '032b' + elif self.payload_type.value & 0x08: + format_str = '064b' + + for item in self.payload: + payload_str += f"{item:{format_str}} " + return f"Type: {self.message_type.name}\r\n" + \ f"Length: {self.length}\r\n" + \ f"Address: {self.address}\r\n" + \ f"Port: {self.port}\r\n" + \ - f"Timestampe: {self.timestamp:6f}\r\n" + \ + f"Timestamp: {self.timestamp}\r\n" + \ f"Payload Type: {self.payload_type.name}\r\n" + \ - f"Payload: {self.payload}\r\n" + \ - f"Checksum: {self.checksum}\r\n" + \ - f"Raw Frame: {self.frame}\r\n" - # print(f"Type: {self.message_type.name}") - # print(f"Length: {self.length}") - # print(f"Address: {self.address}") - # print(f"Port: {self.port}") - # print(f"Payload Type: {self.payload_type.name}") - # print(f"Payload: {self.payload}") - # print(f"Checksum: {self.checksum}") - # print(f"Frame: {self.frame}") + f"Payload Length: {len(self.payload)}\r\n" + \ + f"Payload: {payload_str}\r\n" + \ + f"Checksum: {self.checksum}" @property def payload(self) -> bytes: From edfc3618647cf5bbe0a912dcfaaa7685d36146da Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Wed, 20 Apr 2022 18:09:29 -0700 Subject: [PATCH 16/35] printing DI event changes works --- examples/wait_for_events.py | 2 +- pyharp/messages.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/wait_for_events.py b/examples/wait_for_events.py index 44c2b30..291cecf 100755 --- a/examples/wait_for_events.py +++ b/examples/wait_for_events.py @@ -21,5 +21,5 @@ import time while True: event_response = device.device._read() # read any incoming events. - if event_response is not None: + if event_response is not None and event_response.address != 44: print(event_response) diff --git a/pyharp/messages.py b/pyharp/messages.py index d1035b2..da81795 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -169,7 +169,7 @@ def __init__( self._timestamp = int.from_bytes(frame[5:9], byteorder="little", signed=False) + \ int.from_bytes(frame[9:11], byteorder="little", signed=False)*32e-6 # Timestamp is junk if it's not present. - if self.payload_type.value & PayloadType.hasTimestamp.value: + if not (self.payload_type.value & PayloadType.hasTimestamp.value): self._timestamp = None From d5d835a185fe903a5b9c0426fb84a32e2b077a49 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Thu, 21 Apr 2022 12:08:11 -0700 Subject: [PATCH 17/35] updating how payload is parsed --- examples/wait_for_events.py | 2 +- pyharp/device.py | 44 ++++++++++++++----------- pyharp/messages.py | 64 +++++++++++++------------------------ 3 files changed, 49 insertions(+), 61 deletions(-) diff --git a/examples/wait_for_events.py b/examples/wait_for_events.py index 291cecf..5248f00 100755 --- a/examples/wait_for_events.py +++ b/examples/wait_for_events.py @@ -18,8 +18,8 @@ print("Setting mode to active.") device.device.set_mode(DeviceMode.Active) -import time while True: event_response = device.device._read() # read any incoming events. if event_response is not None and event_response.address != 44: + print() print(event_response) diff --git a/pyharp/device.py b/pyharp/device.py index 4b71f78..5d3c7f3 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -193,29 +193,37 @@ def set_mode(self, mode: DeviceMode) -> ReplyHarpMessage: reg_value |= mode.value reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) return reply -# -# def enable_alive_en(self): -# """Enable ALIVE_EN such that the device sends an event each second.""" -# address = CommonRegisters.OPERATION_CTRL -# # Read register first. -# reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() -# reg_value |= (1 << 7) -# reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) -# -# def enable_status_led(self): -# """enable the device's status led if one exists.""" -# address = CommonRegisters.OPERATION_CTRL -# # Read register first. -# reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() -# reg_value |= (1 << 6) -# reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) + + def enable_status_led(self): + """enable the device's status led if one exists.""" + address = CommonRegisters.OPERATION_CTRL + # Read register first. + reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() + reg_value |= (1 << 6) + reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) + + def enable_status_led(self): + """enable the device's status led if one exists.""" + address = CommonRegisters.OPERATION_CTRL + # Read register first. + reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() + reg_value &= ~(1 << 6) + reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) + + def enable_alive_en(self): + """Enable ALIVE_EN such that the device sends an event each second.""" + address = CommonRegisters.OPERATION_CTRL + # Read register first. + reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() + reg_value |= (1 << 7) + reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) def disable_alive_en(self): """disable ALIVE_EN such that the device does not send an event each second.""" address = CommonRegisters.OPERATION_CTRL # Read register first. - reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() - reg_value &= ~(1 << 7) # mask off old mode. + reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_fixed_int() + reg_value != (1<< 7 & 0xFF) reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) diff --git a/pyharp/messages.py b/pyharp/messages.py index da81795..2531fb2 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -39,18 +39,6 @@ class PayloadType(Enum): TimestampedFloat = hasTimestamp | Float -ALL_UNSIGNED = [PayloadType.U8, - PayloadType.U16, - PayloadType.U32, - PayloadType.TimestampedU8, - PayloadType.TimestampedU16] -ALL_SIGNED = [PayloadType.S8, - PayloadType.S16, - PayloadType.S32, - PayloadType.TimestampedS8, - PayloadType.TimestampedS16] - - class CommonRegisters: WHO_AM_I = 0x00 HW_VERSION_H = 0x01 @@ -163,7 +151,8 @@ def __init__( self._frame = frame # retrieve all content from 11 (where payload starts) until the checksum (not inclusive) - self._payload = frame[11:-1] + self._raw_payload = frame[11:-1] + self._payload = self._parse_payload(self._raw_payload) # payload formatted as list[payload type] # Assign timestamp after _payload since @properties all rely on self._payload. self._timestamp = int.from_bytes(frame[5:9], byteorder="little", signed=False) + \ @@ -173,6 +162,16 @@ def __init__( self._timestamp = None + def _parse_payload(self, raw_payload) -> list[int]: + """return the payload as a list of ints after parsing it from the raw payload.""" + is_signed = True if (self.payload_type.value & 0x80) else False + bytes_per_word = self.payload_type.value & 0x07 + payload_len = len(raw_payload) + + word_chunks = [raw_payload[i:i+bytes_per_word] for i in range(0, payload_len, bytes_per_word)] + return [int.from_bytes(chunk, byteorder="little", signed=is_signed) for chunk in word_chunks] + + def __repr__(self): """Print debug representation of a reply message.""" print(self.__str__()) @@ -183,17 +182,11 @@ def __str__(self): """Print friendly representation of a reply message.""" payload_str = "" format_str = "" - if self.payload_type.value & 0x01: - format_str = '08b' - elif self.payload_type.value & 0x02: - format_str = '016b' - elif self.payload_type.value & 0x04: - if self.message_type.value & PayloadType.isFloat.value: - format_str = '.6f' - else: - format_str = '032b' - elif self.payload_type.value & 0x08: - format_str = '064b' + if self.payload_type == PayloadType.Float: + format_str = '.6f' + else: + bytes_per_word = self.payload_type.value & 0x07 + format_str = f'0{bytes_per_word}b' for item in self.payload: payload_str += f"{item:{format_str}} " @@ -205,11 +198,12 @@ def __str__(self): f"Timestamp: {self.timestamp}\r\n" + \ f"Payload Type: {self.payload_type.name}\r\n" + \ f"Payload Length: {len(self.payload)}\r\n" + \ - f"Payload: {payload_str}\r\n" + \ + f"Payload: {self.payload}\r\n" + \ f"Checksum: {self.checksum}" @property - def payload(self) -> bytes: + def payload(self) -> Union[int, list[int]]: + """return the payload formatted as the appropriate type.""" return self._payload @property @@ -217,24 +211,10 @@ def timestamp(self) -> float: return self._timestamp def payload_as_int(self) -> int: - value: int = 0 - if self.payload_type in ALL_UNSIGNED: - value = int.from_bytes(self.payload, byteorder="little", signed=False) - elif self.payload_type in ALL_SIGNED: - value = int.from_bytes(self.payload, byteorder="little", signed=True) - return value - - def payload_as_int_array(self) -> list: - # Number of bytes per chunk. Get this from the bit field structure. - datatype_bytes = 0x0F & self.payload_type.value - # TODO: is len(self.payload) == self.length? - signed = True if self.payload_type in ALL_UNSIGNED else False - # Break the payload into chunks of datatype size in bytes - byte_chunks = [self.payload[i: i+datatype_bytes] for i in range(0, len(self.payload), datatype_bytes)] - return [int.from_bytes(chunk, byteorder="little", signed=signed) for chunk in byte_chunks] + return self.payload[0] def payload_as_string(self) -> str: - return self.payload.decode("utf-8") + return self._raw_payload.decode("utf-8") # A Read Request Message sent to a harp device. From 3a122a3dd11b5ec35a6ccb3b33b4f26c03e5c49f Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Thu, 21 Apr 2022 17:17:57 -0700 Subject: [PATCH 18/35] adding enabling of events --- examples/wait_for_events.py | 6 +++-- pyharp/device.py | 10 ++++---- pyharp/drivers/behavior.py | 46 +++++++++++++++++++++++++++++-------- pyharp/messages.py | 2 +- 4 files changed, 46 insertions(+), 18 deletions(-) diff --git a/examples/wait_for_events.py b/examples/wait_for_events.py index 5248f00..8f1b9f8 100755 --- a/examples/wait_for_events.py +++ b/examples/wait_for_events.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from pyharp.drivers.behavior import Behavior +from pyharp.drivers.behavior import Behavior, Events from pyharp.messages import HarpMessage from pyharp.messages import MessageType from struct import * @@ -18,8 +18,10 @@ print("Setting mode to active.") device.device.set_mode(DeviceMode.Active) +device.disable_all_events() +device.enable_events(Events.port_digital_inputs) while True: event_response = device.device._read() # read any incoming events. - if event_response is not None and event_response.address != 44: + if event_response is not None:# and event_response.address != 44: print() print(event_response) diff --git a/pyharp/device.py b/pyharp/device.py index 5d3c7f3..8eeb366 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -32,10 +32,6 @@ class Device: HARP_VERSION_L: int FIRMWARE_VERSION_H: int FIRMWARE_VERSION_L: int - # TIMESTAMP_SECOND = 0x08 - # TIMESTAMP_MICRO = 0x09 - # OPERATION_CTRL = 0x0A - # RESET_DEV = 0x0B DEVICE_NAME: str def __init__(self, serial_port: str, dump_file_path: Optional[str] = None): @@ -222,8 +218,8 @@ def disable_alive_en(self): """disable ALIVE_EN such that the device does not send an event each second.""" address = CommonRegisters.OPERATION_CTRL # Read register first. - reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_fixed_int() - reg_value != (1<< 7 & 0xFF) + reg_value = self.send(HarpMessage.ReadU8(address).frame).payload[0] + reg_value &= ((1<< 7) ^ 0xFF) # bitwise ~ operator substitute for Python ints. reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) @@ -232,6 +228,8 @@ def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: self._ser.write(message_bytes) # TODO: handle case where read is None + # FIXME: waiting for a message reply like this + # breaks if events are also being broadcasted (i.e: in ActiveMode). reply: ReplyHarpMessage = self._read() if dump: diff --git a/pyharp/drivers/behavior.py b/pyharp/drivers/behavior.py index 77081d3..989f89a 100644 --- a/pyharp/drivers/behavior.py +++ b/pyharp/drivers/behavior.py @@ -28,6 +28,8 @@ "PORT_DIOS_CONF": ("U8", 42, "Set the DIOs direction (1 is output)."), "PORT_DIOS_IN": ("U8", 43, "State of the DIOs."), + "ADD_REG_DATA": ("S16", 44, "Voltage at ADC input and decoder (poke2) value."), + "EVNT_ENABLE": ("U8", 77, "Enable events within the bitfields."), } @@ -62,16 +64,18 @@ class PORT_DIOS_IN(Enum): DIO1 = 0 DIO2 = 0 -class EVNT_ENABLE(Enum): - PORT_DIS = 0 - PORT_DIOS_IN = 1 - DATA = 2 - CAM0 = 3 - CAM1 = 4 + +# reader-friendly events for enabling/disabling. +class Events(Enum): + port_digital_inputs = 0 # PORT_DIS + port_digital_ios = 1 # PORT_DIOS_IN + analog_input = 2 # DATA + cam0 = 3 # CAM0 + cam1 = 3 # CAM1 class Behavior: - """Driver for BehaviorDevice.""" + """Driver for Behavior Device.""" # On Linux, the symlink to the first detected harp device. # Name set in udev rules and will increment with subsequent devices. @@ -122,6 +126,28 @@ def get_reg_info(self, reg_name: str) -> str: "{self.__class__.name} Device's register map.") + def disable_all_events(self) -> ReplyHarpMessage: + """Disable the publishing of all events from Behavior device.""" + event_reg_bitmask = (((1 << Events.port_digital_inputs.value) | \ + (1 << Events.port_digital_ios.value) | \ + (1 << Events.analog_input.value) | \ + (1 << Events.cam0.value) | \ + (1 << Events.cam1.value) ) ^ 0xFF) + reg_type, reg_index, _ = REGISTERS["EVNT_ENABLE"] + write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] + return self.device.send(write_message_type(reg_index, event_reg_bitmask).frame) + + + def enable_events(self, *events: Events) -> ReplyHarpMessage: + """enable any events passed in as arguments.""" + event_reg_bitmask = 0x00 + for event in events: + event_reg_bitmask |= (1 << event.value) + reg_type, reg_index, _ = REGISTERS["EVNT_ENABLE"] + write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] + return self.device.send(write_message_type(reg_index, event_reg_bitmask).frame) + + # Board inputs, outputs, and some settings configured as @properties. # INPUTS @property @@ -372,9 +398,11 @@ def __enter__(self): def __exit__(self, *args): """Cleanup for the 'with' statement""" - self.device.disconnect() + if self.device is not None: + self.device.disconnect() def __del__(self): """Cleanup when Device gets garbage collected.""" - self.device.disconnect() + if self.device is not None: + self.device.disconnect() diff --git a/pyharp/messages.py b/pyharp/messages.py index 2531fb2..e458758 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -166,7 +166,7 @@ def _parse_payload(self, raw_payload) -> list[int]: """return the payload as a list of ints after parsing it from the raw payload.""" is_signed = True if (self.payload_type.value & 0x80) else False bytes_per_word = self.payload_type.value & 0x07 - payload_len = len(raw_payload) + payload_len = len(raw_payload) # payload length in bytes. word_chunks = [raw_payload[i:i+bytes_per_word] for i in range(0, payload_len, bytes_per_word)] return [int.from_bytes(chunk, byteorder="little", signed=is_signed) for chunk in word_chunks] From 2fc5c5866a51a4d29b19bde5339ea910672b8fed Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Fri, 22 Apr 2022 12:55:00 -0700 Subject: [PATCH 19/35] adding inWaiting to reads --- examples/wait_for_events.py | 1 + pyharp/device.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/wait_for_events.py b/examples/wait_for_events.py index 8f1b9f8..18c8d29 100755 --- a/examples/wait_for_events.py +++ b/examples/wait_for_events.py @@ -17,6 +17,7 @@ device = Behavior("COM95", "ibl.bin") print("Setting mode to active.") +# Mode will remain active for up to 3 seconds after CTS pin is brought low. device.device.set_mode(DeviceMode.Active) device.disable_all_events() device.enable_events(Events.port_digital_inputs) diff --git a/pyharp/device.py b/pyharp/device.py index 8eeb366..2bae568 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -239,7 +239,11 @@ def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: def _read(self) -> Union[ReplyHarpMessage, None]: - """Read an incoming serial message.""" + """(Blocking) Read an incoming serial message.""" + # block until we get at least one byte. + while True: + if self._ser.inWaiting(): + break try: message_type = self._ser.read(1)[0] # byte array with only one byte message_length = self._ser.read(1)[0] From 3f7dfba5e12ef84842351f9408c5eaa6f1256a6b Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Thu, 28 Sep 2023 14:24:13 -0700 Subject: [PATCH 20/35] read dumped registers --- examples/get_info.py | 6 +++++- pyharp/device.py | 51 ++++++++++++++++++++++++++++++++++++++------ pyharp/messages.py | 3 +-- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/examples/get_info.py b/examples/get_info.py index 44e4f18..ebb202c 100755 --- a/examples/get_info.py +++ b/examples/get_info.py @@ -15,7 +15,9 @@ # Open the device and print the info on screen # Open serial connection and save communication to a file if os.name == 'posix': # check for Linux. - device = Device("/dev/harp_device_00", "ibl.bin") + #device = Device("/dev/harp_device_00", "ibl.bin") + #device = Device("/dev/ttyACM0") + device = Device("/dev/ttyUSB0") else: # assume Windows. device = Device("COM95", "ibl.bin") device.info() # Display device's info on screen @@ -34,5 +36,7 @@ device_harp_l = device.HARP_VERSION_L # Get device's harp core version device_assembly = device.ASSEMBLY_VERSION # Get device's assembly version +print(device.dump_registers()) + # Close connection device.disconnect() diff --git a/pyharp/device.py b/pyharp/device.py index 2bae568..9278273 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -3,9 +3,10 @@ from pathlib import Path from pyharp.messages import HarpMessage, ReplyHarpMessage -from pyharp.messages import CommonRegisters +from pyharp.messages import CommonRegisters, MessageType from pyharp.device_names import device_names from enum import Enum +from time import perf_counter class DeviceMode(Enum): @@ -34,6 +35,8 @@ class Device: FIRMWARE_VERSION_L: int DEVICE_NAME: str + TIMEOUT_S = 1.0 + def __init__(self, serial_port: str, dump_file_path: Optional[str] = None): self._serial_port = serial_port if dump_file_path is None: @@ -72,7 +75,7 @@ def connect(self) -> None: self._ser = serial.Serial( self._serial_port, # "/dev/tty.usbserial-A106C8O9" baudrate=1000000, - timeout=1, + timeout=self.__class__.TIMEOUT_S, parity=serial.PARITY_NONE, stopbits=1, bytesize=8, @@ -88,7 +91,7 @@ def read_who_am_i(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU16(address).frame, dump=False ) - + #print(str(reply)) return reply.payload_as_int() def read_who_am_i_device(self) -> str: @@ -97,6 +100,7 @@ def read_who_am_i_device(self) -> str: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU16(address).frame, dump=False ) + #print(str(reply)) return device_names.get(reply.payload_as_int()) @@ -106,6 +110,7 @@ def read_hw_version_h(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU8(address).frame, dump=False ) + #print(str(reply)) return reply.payload_as_int() @@ -115,6 +120,7 @@ def read_hw_version_l(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU8(address).frame, dump=False ) + #print(str(reply)) return reply.payload_as_int() @@ -124,6 +130,7 @@ def read_assembly_version(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU8(address).frame, dump=False ) + #print(str(reply)) return reply.payload_as_int() @@ -133,6 +140,7 @@ def read_harp_h_version(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU8(address).frame, dump=False ) + #print(str(reply)) return reply.payload_as_int() @@ -142,6 +150,7 @@ def read_harp_l_version(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU8(address).frame, dump=False ) + #print(str(reply)) return reply.payload_as_int() @@ -151,6 +160,7 @@ def read_fw_h_version(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU8(address).frame, dump=False ) + #print(str(reply)) return reply.payload_as_int() @@ -160,6 +170,7 @@ def read_fw_l_version(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU8(address).frame, dump=False ) + #print(str(reply)) return reply.payload_as_int() @@ -170,15 +181,32 @@ def read_device_name(self) -> str: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU8(address).frame, dump=False ) + #print(str(reply)) return reply.payload_as_string() def read_device_mode(self) -> DeviceMode: address = CommonRegisters.OPERATION_CTRL reply = self.send(HarpMessage.ReadU8(address).frame) - print(reply) return DeviceMode(reply.payload_as_int() & 0x03) + def dump_registers(self) -> list: + """Assert the DUMP bit to dump the values of all core and app registers + as Harp Read Reply Messages. + """ + address = CommonRegisters.OPERATION_CTRL + reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() + reg_value |= 0x08 # Assert DUMP bit + self._ser.write(HarpMessage.WriteU8(address, reg_value).frame) + replies = [] + while True: + msg = self._read() + if msg is not None: + replies.append(msg) + else: + break + return replies + # TODO: Not sure if we want to implement these. Delete if no. def set_mode(self, mode: DeviceMode) -> ReplyHarpMessage: """Change the device's OPMODE. Reply can be ignored.""" @@ -225,6 +253,7 @@ def disable_alive_en(self): def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: """Send a harp message; return the device's reply.""" + #print(f"Sending: {repr(message_bytes)}") self._ser.write(message_bytes) # TODO: handle case where read is None @@ -232,7 +261,7 @@ def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: # breaks if events are also being broadcasted (i.e: in ActiveMode). reply: ReplyHarpMessage = self._read() - if dump: + if dump and self._dump_file_path is not None: self._dump_reply(reply.frame) return reply @@ -240,21 +269,29 @@ def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: def _read(self) -> Union[ReplyHarpMessage, None]: """(Blocking) Read an incoming serial message.""" - # block until we get at least one byte. + # block for up to TIMEOUT until we get at least one byte. + read_start = perf_counter() while True: if self._ser.inWaiting(): break + if perf_counter() - read_start >= self.__class__.TIMEOUT_S: + break try: message_type = self._ser.read(1)[0] # byte array with only one byte message_length = self._ser.read(1)[0] message_content = self._ser.read(message_length) + #print(f"Read back:") + #print(f" type: {MessageType(message_type).name}") + #print(f" length : {repr(message_length)}") + #print(f" payload: {list(message_content)}") frame = bytearray() frame.append(message_type) frame.append(message_length) frame += message_content + msg = HarpMessage.parse(frame) - return HarpMessage.parse(frame) + return msg except IndexError: return None diff --git a/pyharp/messages.py b/pyharp/messages.py index e458758..71d0199 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -174,8 +174,7 @@ def _parse_payload(self, raw_payload) -> list[int]: def __repr__(self): """Print debug representation of a reply message.""" - print(self.__str__()) - print(f"Raw Frame: {self.frame}") + return self.__str__() + f"\r\nRaw Frame: {self.frame}" def __str__(self): From 401bd36a77c687698e38d4faae36a3801d6b70ce Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Thu, 21 Dec 2023 13:57:34 -0800 Subject: [PATCH 21/35] expose more core features --- examples/get_info.py | 8 +++--- examples/wait_for_events.py | 13 +++++----- pyharp/device.py | 8 +++--- pyharp/messages.py | 51 ++++++++++++++++++++++++++++++++++--- 4 files changed, 64 insertions(+), 16 deletions(-) diff --git a/examples/get_info.py b/examples/get_info.py index ebb202c..f2d8e5e 100755 --- a/examples/get_info.py +++ b/examples/get_info.py @@ -16,8 +16,8 @@ # Open serial connection and save communication to a file if os.name == 'posix': # check for Linux. #device = Device("/dev/harp_device_00", "ibl.bin") - #device = Device("/dev/ttyACM0") - device = Device("/dev/ttyUSB0") + device = Device("/dev/ttyACM0") + #device = Device("/dev/ttyUSB0") else: # assume Windows. device = Device("COM95", "ibl.bin") device.info() # Display device's info on screen @@ -36,7 +36,9 @@ device_harp_l = device.HARP_VERSION_L # Get device's harp core version device_assembly = device.ASSEMBLY_VERSION # Get device's assembly version -print(device.dump_registers()) +reg_dump = device.dump_registers() +for i in range(11): + print(reg_dump[i]) # Close connection device.disconnect() diff --git a/examples/wait_for_events.py b/examples/wait_for_events.py index 18c8d29..845e7c4 100755 --- a/examples/wait_for_events.py +++ b/examples/wait_for_events.py @@ -5,24 +5,25 @@ from struct import * import os -from pyharp.device import DeviceMode +from pyharp.device import Device, DeviceMode # Open the device and print the info on screen # Open serial connection and save communication to a file device = None if os.name == 'posix': # check for Linux. - device = Behavior("/dev/harp_device_00", "ibl.bin") + #device = Behavior("/dev/harp_device_00", "ibl.bin") + device = Device("/dev/ttyACM0",) else: # assume Windows. device = Behavior("COM95", "ibl.bin") print("Setting mode to active.") # Mode will remain active for up to 3 seconds after CTS pin is brought low. -device.device.set_mode(DeviceMode.Active) -device.disable_all_events() -device.enable_events(Events.port_digital_inputs) +device.set_mode(DeviceMode.Active) +#device.disable_all_events() +#device.enable_events(Events.port_digital_inputs) while True: - event_response = device.device._read() # read any incoming events. + event_response = device._read() # read any incoming events. if event_response is not None:# and event_response.address != 44: print() print(event_response) diff --git a/pyharp/device.py b/pyharp/device.py index 9278273..166ce2f 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -223,15 +223,15 @@ def enable_status_led(self): address = CommonRegisters.OPERATION_CTRL # Read register first. reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() - reg_value |= (1 << 6) + reg_value |= (1 << 5) reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) - def enable_status_led(self): - """enable the device's status led if one exists.""" + def disable_status_led(self): + """disable the device's status led if one exists.""" address = CommonRegisters.OPERATION_CTRL # Read register first. reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() - reg_value &= ~(1 << 6) + reg_value &= ~(1 << 5) reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) def enable_alive_en(self): diff --git a/pyharp/messages.py b/pyharp/messages.py index 71d0199..7566e79 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -2,6 +2,7 @@ from enum import Enum # from abc import ABC, abstractmethod from typing import Union, Tuple, Optional +import struct class MessageType(Enum): @@ -93,7 +94,7 @@ def port(self) -> int: return self._frame[3] @property - def payload_type(self) -> int: + def payload_type(self) -> PayloadType: return PayloadType(self._frame[4]) @property @@ -116,6 +117,16 @@ def ReadS16(address: int) -> ReadS16HarpMessage: def ReadU16(address: int) -> ReadU16HarpMessage: return ReadU16HarpMessage(address) + # TODO: ReadS16 + + @staticmethod + def ReadU32(address: int) -> ReadU32HarpMessage: + return ReadU32HarpMessage(address) + + @staticmethod + def ReadFloat(address: int) -> ReadFloatHarpMessage: + return ReadFloatHarpMessage(address) + @staticmethod def WriteU8(address: int, value: int) -> WriteU8HarpMessage: return WriteU8HarpMessage(address, value) @@ -132,6 +143,10 @@ def WriteS16(address: int, value: int) -> WriteS16HarpMessage: def WriteU16(address: int, value: int) -> WriteU16HarpMessage: return WriteU16HarpMessage(address, value) + @staticmethod + def WriteFloat(address: int, value: int) -> WriteFloatHarpMessage: + return WriteFloatHarpMessage(address, value) + @staticmethod def parse(frame: bytearray) -> ReplyHarpMessage: return ReplyHarpMessage(frame) @@ -165,11 +180,15 @@ def __init__( def _parse_payload(self, raw_payload) -> list[int]: """return the payload as a list of ints after parsing it from the raw payload.""" is_signed = True if (self.payload_type.value & 0x80) else False + is_float = True if (self.payload_type.value & 0x40) else False bytes_per_word = self.payload_type.value & 0x07 payload_len = len(raw_payload) # payload length in bytes. word_chunks = [raw_payload[i:i+bytes_per_word] for i in range(0, payload_len, bytes_per_word)] - return [int.from_bytes(chunk, byteorder="little", signed=is_signed) for chunk in word_chunks] + if not is_float: + return [int.from_bytes(chunk, byteorder="little", signed=is_signed) for chunk in word_chunks] + else: # handle float case. + return [struct.unpack(' int: def payload_as_string(self) -> str: return self._raw_payload.decode("utf-8") + def payload_as_float(self) -> float: + return self.payload[0] # already parsed. + # A Read Request Message sent to a harp device. class ReadHarpMessage(HarpMessage): @@ -253,6 +275,15 @@ class ReadS16HarpMessage(ReadHarpMessage): def __init__(self, address: int): super().__init__(PayloadType.S16, address) +class ReadU32HarpMessage(ReadHarpMessage): + def __init__(self, address: int): + super().__init__(PayloadType.U32, address) + + +class ReadFloatHarpMessage(ReadHarpMessage): + def __init__(self, address: int): + super().__init__(PayloadType.Float, address) + class WriteHarpMessage(HarpMessage): BASE_LENGTH: int = 5 @@ -327,3 +358,17 @@ def __init__(self, address: int, value: int): @property def payload(self) -> int: return int.from_bytes(self._frame[5:7], byteorder="little", signed=True) + + +class WriteFloatHarpMessage(WriteHarpMessage): + def __init__(self, address: int, value: float): + super().__init__( + PayloadType.Float, + struct.pack(' float: + return struct.unpack(' Date: Thu, 21 Dec 2023 14:53:21 -0800 Subject: [PATCH 22/35] add WriteU32 and WriteS32 --- examples/get_info.py | 4 ++-- examples/wait_for_events.py | 3 ++- pyharp/messages.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/examples/get_info.py b/examples/get_info.py index f2d8e5e..d7b7a47 100755 --- a/examples/get_info.py +++ b/examples/get_info.py @@ -16,8 +16,8 @@ # Open serial connection and save communication to a file if os.name == 'posix': # check for Linux. #device = Device("/dev/harp_device_00", "ibl.bin") - device = Device("/dev/ttyACM0") - #device = Device("/dev/ttyUSB0") + #device = Device("/dev/ttyACM0") + device = Device("/dev/ttyUSB0") else: # assume Windows. device = Device("COM95", "ibl.bin") device.info() # Display device's info on screen diff --git a/examples/wait_for_events.py b/examples/wait_for_events.py index 845e7c4..b846d37 100755 --- a/examples/wait_for_events.py +++ b/examples/wait_for_events.py @@ -13,7 +13,8 @@ device = None if os.name == 'posix': # check for Linux. #device = Behavior("/dev/harp_device_00", "ibl.bin") - device = Device("/dev/ttyACM0",) + #device = Device("/dev/ttyACM0",) + device = Device("/dev/ttyUSB0",) else: # assume Windows. device = Behavior("COM95", "ibl.bin") diff --git a/pyharp/messages.py b/pyharp/messages.py index 7566e79..b7bd57d 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -147,6 +147,14 @@ def WriteU16(address: int, value: int) -> WriteU16HarpMessage: def WriteFloat(address: int, value: int) -> WriteFloatHarpMessage: return WriteFloatHarpMessage(address, value) + @staticmethod + def WriteU32(address: int, value: int) -> WriteU32HarpMessage: + return WriteU32HarpMessage(address, value) + + @staticmethod + def WriteS32(address: int, value: int) -> WriteS32HarpMessage: + return WriteS32HarpMessage(address, value) + @staticmethod def parse(frame: bytearray) -> ReplyHarpMessage: return ReplyHarpMessage(frame) @@ -372,3 +380,25 @@ def __init__(self, address: int, value: float): @property def payload(self) -> float: return struct.unpack(' int: + return int.from_bytes(self._frame[5:9], byteorder="little", signed=False) + + +class WriteS32HarpMessage(WriteHarpMessage): + def __init__(self, address: int, value: int): + super().__init__( + PayloadType.S32, value.to_bytes(4, byteorder="little", signed=False), address, offset=3 + ) + + @property + def payload(self) -> int: + return int.from_bytes(self._frame[5:9], byteorder="little", signed=False) From fb5447628e542f2ec7ff693efe8aed6b5406005a Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Thu, 21 Dec 2023 14:55:01 -0800 Subject: [PATCH 23/35] fix signed error in WriteS32 --- pyharp/messages.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyharp/messages.py b/pyharp/messages.py index b7bd57d..3d34af4 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -396,9 +396,9 @@ def payload(self) -> int: class WriteS32HarpMessage(WriteHarpMessage): def __init__(self, address: int, value: int): super().__init__( - PayloadType.S32, value.to_bytes(4, byteorder="little", signed=False), address, offset=3 + PayloadType.S32, value.to_bytes(4, byteorder="little", signed=True), address, offset=3 ) @property def payload(self) -> int: - return int.from_bytes(self._frame[5:9], byteorder="little", signed=False) + return int.from_bytes(self._frame[5:9], byteorder="little", signed=True) From e7900d41f8bc1a9f6a246fbf9768456466308479 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Fri, 12 Jan 2024 14:57:43 -0800 Subject: [PATCH 24/35] add debug level logging. --- pyharp/device.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyharp/device.py b/pyharp/device.py index 166ce2f..547639f 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -1,4 +1,5 @@ import serial +import logging from typing import Optional, Union from pathlib import Path @@ -38,6 +39,7 @@ class Device: TIMEOUT_S = 1.0 def __init__(self, serial_port: str, dump_file_path: Optional[str] = None): + self.log = logging.getLogger(f"{__name__}.{self.__class__.__name__}") self._serial_port = serial_port if dump_file_path is None: self._dump_file_path = None @@ -280,6 +282,9 @@ def _read(self) -> Union[ReplyHarpMessage, None]: message_type = self._ser.read(1)[0] # byte array with only one byte message_length = self._ser.read(1)[0] message_content = self._ser.read(message_length) + self.log.debug(f"reply (type): {message_type}") + self.log.debug(f"reply (length): {message_length}") + self.log.debug(f"reply (payload): {message_content}") #print(f"Read back:") #print(f" type: {MessageType(message_type).name}") #print(f" length : {repr(message_length)}") From 1b1e833e2ee750471c94623f28fc499a6134c314 Mon Sep 17 00:00:00 2001 From: "jessy.liao" Date: Fri, 17 May 2024 14:54:57 -0700 Subject: [PATCH 25/35] Added ReadS32 and refactored WriteS32 to handle array inputs --- pyharp/messages.py | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/pyharp/messages.py b/pyharp/messages.py index 3d34af4..e86747a 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -1,7 +1,7 @@ from __future__ import annotations # for type hints (PEP 563) from enum import Enum # from abc import ABC, abstractmethod -from typing import Union, Tuple, Optional +from typing import Union, Tuple, Optional, List import struct @@ -123,6 +123,10 @@ def ReadU16(address: int) -> ReadU16HarpMessage: def ReadU32(address: int) -> ReadU32HarpMessage: return ReadU32HarpMessage(address) + @staticmethod + def ReadS32(address: int) -> ReadS32HarpMessage: + return ReadS32HarpMessage(address) + @staticmethod def ReadFloat(address: int) -> ReadFloatHarpMessage: return ReadFloatHarpMessage(address) @@ -288,6 +292,11 @@ def __init__(self, address: int): super().__init__(PayloadType.U32, address) +class ReadS32HarpMessage(ReadHarpMessage): + def __init__(self, address: int): + super().__init__(PayloadType.S32, address) + + class ReadFloatHarpMessage(ReadHarpMessage): def __init__(self, address: int): super().__init__(PayloadType.Float, address) @@ -317,8 +326,12 @@ def __init__( self._frame.append(HarpMessage.DEFAULT_PORT) self._frame.append(payload_type.value) - for i in payload: - self._frame.append(i) + # Handle payloads that are bytes or bytearray (bytearray = multi-motor instructions) + if isinstance(payload, bytearray): + self._frame += payload + else: + for i in payload: + self._frame.append(i) self._frame.append(self.calculate_checksum()) @@ -394,11 +407,19 @@ def payload(self) -> int: class WriteS32HarpMessage(WriteHarpMessage): - def __init__(self, address: int, value: int): + def __init__(self, address: int, value: int | List[int]): + if isinstance(value, list): + payload = bytearray() + for val in value: + payload += val.to_bytes(4, byteorder="little", signed=True) + offset = 15 + else: + payload = value.to_bytes(4, byteorder="little", signed=True) + offset = 3 super().__init__( - PayloadType.S32, value.to_bytes(4, byteorder="little", signed=True), address, offset=3 + PayloadType.S32, payload, address, offset=offset ) @property - def payload(self) -> int: + def payload(self) -> int | List[int]: return int.from_bytes(self._frame[5:9], byteorder="little", signed=True) From 368e346dd66c17516974a339a0cf3f4807c02874 Mon Sep 17 00:00:00 2001 From: Patrick Latimer Date: Wed, 30 Oct 2024 16:23:22 -0700 Subject: [PATCH 26/35] Add threaded serial port --- pyharp/device.py | 171 ++++++++++++++++++++++++------------------ pyharp/harp_serial.py | 84 +++++++++++++++++++++ pyharp/messages.py | 95 ++++++++++++++--------- pyproject.toml | 1 + tests/test_device.py | 27 ++++++- 5 files changed, 269 insertions(+), 109 deletions(-) create mode 100644 pyharp/harp_serial.py diff --git a/pyharp/device.py b/pyharp/device.py index 547639f..4496859 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -1,9 +1,18 @@ import serial import logging +import threading +import queue from typing import Optional, Union from pathlib import Path -from pyharp.messages import HarpMessage, ReplyHarpMessage +from pyharp.harp_serial import HarpSerial +from pyharp.messages import ( + HarpMessage, + ReadHarpMessage, + ReplyHarpMessage, + # ResetDevOffsets, + Register, +) from pyharp.messages import CommonRegisters, MessageType from pyharp.device_names import device_names from enum import Enum @@ -22,7 +31,8 @@ class Device: https://github.com/harp-tech/protocol/blob/master/Device%201.1%201.0%2020220402.pdf """ - _ser: serial.Serial + # _ser: serial.Serial + _ser: HarpSerial _dump_file_path: Path WHO_AM_I: int @@ -38,13 +48,19 @@ class Device: TIMEOUT_S = 1.0 - def __init__(self, serial_port: str, dump_file_path: Optional[str] = None): + def __init__( + self, + serial_port: str, + dump_file_path: Optional[str] = None, + read_timeout_s=1, + ): self.log = logging.getLogger(f"{__name__}.{self.__class__.__name__}") self._serial_port = serial_port if dump_file_path is None: self._dump_file_path = None else: self._dump_file_path = Path() / dump_file_path + self.read_timeout_s = read_timeout_s self.connect() self.load() @@ -74,7 +90,8 @@ def read(self): pass def connect(self) -> None: - self._ser = serial.Serial( + self._ser = HarpSerial( + # self._ser = serial.Serial( self._serial_port, # "/dev/tty.usbserial-A106C8O9" baudrate=1000000, timeout=self.__class__.TIMEOUT_S, @@ -93,7 +110,7 @@ def read_who_am_i(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU16(address).frame, dump=False ) - #print(str(reply)) + # print(str(reply)) return reply.payload_as_int() def read_who_am_i_device(self) -> str: @@ -102,77 +119,63 @@ def read_who_am_i_device(self) -> str: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU16(address).frame, dump=False ) - #print(str(reply)) + # print(str(reply)) return device_names.get(reply.payload_as_int()) def read_hw_version_h(self) -> int: address = CommonRegisters.HW_VERSION_H - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) + # print(str(reply)) return reply.payload_as_int() def read_hw_version_l(self) -> int: address = CommonRegisters.HW_VERSION_L - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) + # print(str(reply)) return reply.payload_as_int() def read_assembly_version(self) -> int: address = CommonRegisters.ASSEMBLY_VERSION - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) + # print(str(reply)) return reply.payload_as_int() def read_harp_h_version(self) -> int: address = CommonRegisters.HARP_VERSION_H - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) + # print(str(reply)) return reply.payload_as_int() def read_harp_l_version(self) -> int: address = CommonRegisters.HARP_VERSION_L - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) + # print(str(reply)) return reply.payload_as_int() def read_fw_h_version(self) -> int: address = CommonRegisters.FIRMWARE_VERSION_H - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) + # print(str(reply)) return reply.payload_as_int() def read_fw_l_version(self) -> int: address = CommonRegisters.FIRMWARE_VERSION_L - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False -) - #print(str(reply)) + reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) + # print(str(reply)) return reply.payload_as_int() @@ -180,10 +183,8 @@ def read_device_name(self) -> str: address = CommonRegisters.DEVICE_NAME # reply: Optional[bytes] = self.send(HarpMessage.ReadU8(address).frame, 13 + 24) - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) + # print(str(reply)) return reply.payload_as_string() @@ -209,13 +210,13 @@ def dump_registers(self) -> list: break return replies -# TODO: Not sure if we want to implement these. Delete if no. + # TODO: Not sure if we want to implement these. Delete if no. def set_mode(self, mode: DeviceMode) -> ReplyHarpMessage: """Change the device's OPMODE. Reply can be ignored.""" address = CommonRegisters.OPERATION_CTRL # Read register first. reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() - reg_value &= ~0x03 # mask off old mode. + reg_value &= ~0x03 # mask off old mode. reg_value |= mode.value reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) return reply @@ -225,7 +226,7 @@ def enable_status_led(self): address = CommonRegisters.OPERATION_CTRL # Read register first. reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() - reg_value |= (1 << 5) + reg_value |= 1 << 5 reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) def disable_status_led(self): @@ -241,7 +242,7 @@ def enable_alive_en(self): address = CommonRegisters.OPERATION_CTRL # Read register first. reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() - reg_value |= (1 << 7) + reg_value |= 1 << 7 reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) def disable_alive_en(self): @@ -249,18 +250,22 @@ def disable_alive_en(self): address = CommonRegisters.OPERATION_CTRL # Read register first. reg_value = self.send(HarpMessage.ReadU8(address).frame).payload[0] - reg_value &= ((1<< 7) ^ 0xFF) # bitwise ~ operator substitute for Python ints. + reg_value &= (1 << 7) ^ 0xFF # bitwise ~ operator substitute for Python ints. reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) + def reset_device(self): + address = CommonRegisters.RESET_DEV + # reset_value = 0xFF & (1< ReplyHarpMessage: """Send a harp message; return the device's reply.""" - #print(f"Sending: {repr(message_bytes)}") + # print(f"Sending: {repr(message_bytes)}") self._ser.write(message_bytes) # TODO: handle case where read is None - # FIXME: waiting for a message reply like this - # breaks if events are also being broadcasted (i.e: in ActiveMode). reply: ReplyHarpMessage = self._read() if dump and self._dump_file_path is not None: @@ -268,40 +273,60 @@ def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: return reply + # def _read(self) -> Union[ReplyHarpMessage, None]: + # """(Blocking) Read an incoming serial message.""" + # # block for up to TIMEOUT until we get at least one byte. + # read_start = perf_counter() + # while True: + # if self._ser.inWaiting(): + # break + # if perf_counter() - read_start >= self.__class__.TIMEOUT_S: + # break + # try: + # message_type = self._ser.read(1)[0] # byte array with only one byte + # message_length = self._ser.read(1)[0] + # message_content = self._ser.read(message_length) + # self.log.debug(f"reply (type): {message_type}") + # self.log.debug(f"reply (length): {message_length}") + # self.log.debug(f"reply (payload): {message_content}") + # # print(f"Read back:") + # # print(f" type: {MessageType(message_type).name}") + # # print(f" length : {repr(message_length)}") + # # print(f" payload: {list(message_content)}") + + # frame = bytearray() + # frame.append(message_type) + # frame.append(message_length) + # frame += message_content + # msg = HarpMessage.parse(frame) + + # return msg + # except IndexError: + # return None def _read(self) -> Union[ReplyHarpMessage, None]: """(Blocking) Read an incoming serial message.""" - # block for up to TIMEOUT until we get at least one byte. - read_start = perf_counter() - while True: - if self._ser.inWaiting(): - break - if perf_counter() - read_start >= self.__class__.TIMEOUT_S: - break try: - message_type = self._ser.read(1)[0] # byte array with only one byte - message_length = self._ser.read(1)[0] - message_content = self._ser.read(message_length) - self.log.debug(f"reply (type): {message_type}") - self.log.debug(f"reply (length): {message_length}") - self.log.debug(f"reply (payload): {message_content}") - #print(f"Read back:") - #print(f" type: {MessageType(message_type).name}") - #print(f" length : {repr(message_length)}") - #print(f" payload: {list(message_content)}") - - frame = bytearray() - frame.append(message_type) - frame.append(message_length) - frame += message_content - msg = HarpMessage.parse(frame) - - return msg - except IndexError: + return self._ser.msg_q.get(block=True, timeout=self.read_timeout_s) + except queue.Empty: return None - def _dump_reply(self, reply: bytes): assert self._dump_file_path is not None with self._dump_file_path.open(mode="ab") as f: f.write(reply) + + # def read_register(self, register_name: str): + # register: Register = CommonRegisters[register_name] + # ReadHarpMessage(register.type, register.address) + + # def write_register(self, register_name: str, value): + + def get_events(self) -> list[ReplyHarpMessage]: + msgs = [] + while True: + try: + msgs.append(self._ser.event_q.get(timeout=False)) + except queue.Empty: + break + return msgs diff --git a/pyharp/harp_serial.py b/pyharp/harp_serial.py new file mode 100644 index 0000000..85a0bb8 --- /dev/null +++ b/pyharp/harp_serial.py @@ -0,0 +1,84 @@ +from typing import Union +from functools import partial +import logging +import queue +import threading +import serial +import serial.threaded + +from pyharp.messages import HarpMessage, MessageType + + +class HarpSerialProtocol(serial.threaded.Protocol): + _read_q: queue.Queue + + def __init__(self, _read_q: queue.Queue, *args, **kwargs): + self._read_q = _read_q + super().__init__(*args, **kwargs) + + def connection_made(self, transport: serial.threaded.ReaderThread) -> None: + print(f"Connected to {transport.serial.port}") + return super().connection_made(transport) + + def data_received(self, data: bytes) -> None: + for byte in data: + self._read_q.put(byte) + return super().data_received(data) + + def connection_lost(self, exc: Union[BaseException, None]) -> None: + print(f"Lost connection!") + return super().connection_lost(exc) + + +class HarpSerial: + + msg_q: queue.Queue + event_q: queue.Queue + + def __init__(self, serial_port: str, **kwargs): + self._ser = serial.Serial(serial_port, **kwargs) + + self.log = logging.getLogger(f"{__name__}.{self.__class__.__name__}") + + self._read_q = queue.Queue() + self.msg_q = queue.Queue() + self.event_q = queue.Queue() + + self._reader = serial.threaded.ReaderThread( + self._ser, + partial(HarpSerialProtocol, self._read_q), + ) + self._reader.start() + transport, protocol = self._reader.connect() + + self._parse_thread = threading.Thread( + target=self.parse_harp_msgs_threaded, + daemon=True, + ) + self._parse_thread.start() + + def close(self): + self._reader.close() + + def write(self, data): + self._reader.write(data) + + def parse_harp_msgs_threaded(self): + while True: + message_type = self._read_q.get(1) # byte array with only one byte + message_length = self._read_q.get(1) + message_content = bytes([self._read_q.get() for _ in range(message_length)]) + self.log.debug(f"reply (type): {message_type}") + self.log.debug(f"reply (length): {message_length}") + self.log.debug(f"reply (payload): {message_content}") + + frame = bytearray() + frame.append(message_type) + frame.append(message_length) + frame += message_content + msg = HarpMessage.parse(frame) + + if msg.message_type == MessageType.EVENT: + self.event_q.put(msg) + else: + self.msg_q.put(msg) diff --git a/pyharp/messages.py b/pyharp/messages.py index e86747a..2a91a25 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -1,7 +1,9 @@ -from __future__ import annotations # for type hints (PEP 563) +from __future__ import annotations # for type hints (PEP 563) from enum import Enum + # from abc import ABC, abstractmethod -from typing import Union, Tuple, Optional, List +from typing import Union, Tuple, Optional, List, Any +from dataclasses import dataclass import struct @@ -56,6 +58,16 @@ class CommonRegisters: DEVICE_NAME = 0x0C +@dataclass +class Register: + name: str + address: int + type: PayloadType + access: Optional[str] = None + description: Optional[str] = None + reset_value: Optional[Any] = None + + class HarpMessage: """ https://github.com/harp-tech/protocol/blob/master/Binary%20Protocol%201.0%201.1%2020180223.pdf @@ -167,9 +179,9 @@ def parse(frame: bytearray) -> ReplyHarpMessage: # A Response Message from a harp device. class ReplyHarpMessage(HarpMessage): - def __init__( - self, frame: bytearray, + self, + frame: bytearray, ): """ @@ -179,57 +191,66 @@ def __init__( self._frame = frame # retrieve all content from 11 (where payload starts) until the checksum (not inclusive) self._raw_payload = frame[11:-1] - self._payload = self._parse_payload(self._raw_payload) # payload formatted as list[payload type] + self._payload = self._parse_payload( + self._raw_payload + ) # payload formatted as list[payload type] # Assign timestamp after _payload since @properties all rely on self._payload. - self._timestamp = int.from_bytes(frame[5:9], byteorder="little", signed=False) + \ - int.from_bytes(frame[9:11], byteorder="little", signed=False)*32e-6 + self._timestamp = ( + int.from_bytes(frame[5:9], byteorder="little", signed=False) + + int.from_bytes(frame[9:11], byteorder="little", signed=False) * 32e-6 + ) # Timestamp is junk if it's not present. if not (self.payload_type.value & PayloadType.hasTimestamp.value): self._timestamp = None - def _parse_payload(self, raw_payload) -> list[int]: """return the payload as a list of ints after parsing it from the raw payload.""" is_signed = True if (self.payload_type.value & 0x80) else False is_float = True if (self.payload_type.value & 0x40) else False bytes_per_word = self.payload_type.value & 0x07 - payload_len = len(raw_payload) # payload length in bytes. + payload_len = len(raw_payload) # payload length in bytes. - word_chunks = [raw_payload[i:i+bytes_per_word] for i in range(0, payload_len, bytes_per_word)] + word_chunks = [ + raw_payload[i : i + bytes_per_word] + for i in range(0, payload_len, bytes_per_word) + ] if not is_float: - return [int.from_bytes(chunk, byteorder="little", signed=is_signed) for chunk in word_chunks] - else: # handle float case. - return [struct.unpack(' Union[int, list[int]]: @@ -254,7 +275,6 @@ def payload_as_float(self) -> float: class ReadHarpMessage(HarpMessage): MESSAGE_TYPE: int = MessageType.READ - def __init__(self, payload_type: PayloadType, address: int): self._frame = bytearray() @@ -287,6 +307,7 @@ class ReadS16HarpMessage(ReadHarpMessage): def __init__(self, address: int): super().__init__(PayloadType.S16, address) + class ReadU32HarpMessage(ReadHarpMessage): def __init__(self, address: int): super().__init__(PayloadType.U32, address) @@ -359,7 +380,10 @@ def payload(self) -> int: class WriteU16HarpMessage(WriteHarpMessage): def __init__(self, address: int, value: int): super().__init__( - PayloadType.U16, value.to_bytes(2, byteorder="little", signed=False), address, offset=1 + PayloadType.U16, + value.to_bytes(2, byteorder="little", signed=False), + address, + offset=1, ) @property @@ -385,20 +409,25 @@ class WriteFloatHarpMessage(WriteHarpMessage): def __init__(self, address: int, value: float): super().__init__( PayloadType.Float, - struct.pack(' float: - return struct.unpack(' int | List[int]: diff --git a/pyproject.toml b/pyproject.toml index ca8d302..9d3b6a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ description = "Library for data acquisition and control of devices implementing authors = ["filcarv "] license = "MIT" readme = 'README.md' +python-versions = '>3.10' [tool.poetry.dependencies] python = "^3.4" diff --git a/tests/test_device.py b/tests/test_device.py index da16859..196a07c 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -1,6 +1,7 @@ import serial - +import time from typing import Optional + from pyharp.messages import HarpMessage, ReplyHarpMessage from pyharp.device import Device @@ -47,7 +48,7 @@ def test_U8() -> None: # write 65 on register 38 write_message = HarpMessage.WriteU8(register, write_value) - reply : ReplyHarpMessage = device.send(write_message.frame) + reply: ReplyHarpMessage = device.send(write_message.frame) assert reply is not None # read register 38 @@ -84,3 +85,25 @@ def test_U8() -> None: # assert not ser.is_open # # # assert data[0] == '\t' + + +def test_device_events(device: Device) -> None: + + event_q = device._ser.event_q + + while True: + print(device._ser.event_q.qsize()) + if not event_q.empty(): + try: + msg: ReplyHarpMessage = event_q.get() + print(msg) + except Exception: + pass + time.sleep(0.3) + + +if __name__ == "__main__": + # open serial connection and load info + device = Device("COM4", "dump.txt") + # assert device._dump_file_path.exists() + test_device_events(device) From 2381e8ff1d0050cb7cd8636f9b12605b661623b1 Mon Sep 17 00:00:00 2001 From: Patrick Latimer <110747402+patricklatimer@users.noreply.github.com> Date: Wed, 12 Feb 2025 14:56:44 -0800 Subject: [PATCH 27/35] clean up formatting changes --- pyharp/device.py | 103 ++++++++++++++++++--------------------------- pyharp/messages.py | 95 +++++++++++++++-------------------------- 2 files changed, 74 insertions(+), 124 deletions(-) diff --git a/pyharp/device.py b/pyharp/device.py index 4496859..0a754cb 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -31,7 +31,6 @@ class Device: https://github.com/harp-tech/protocol/blob/master/Device%201.1%201.0%2020220402.pdf """ - # _ser: serial.Serial _ser: HarpSerial _dump_file_path: Path @@ -91,7 +90,6 @@ def read(self): def connect(self) -> None: self._ser = HarpSerial( - # self._ser = serial.Serial( self._serial_port, # "/dev/tty.usbserial-A106C8O9" baudrate=1000000, timeout=self.__class__.TIMEOUT_S, @@ -110,7 +108,7 @@ def read_who_am_i(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU16(address).frame, dump=False ) - # print(str(reply)) + #print(str(reply)) return reply.payload_as_int() def read_who_am_i_device(self) -> str: @@ -119,63 +117,77 @@ def read_who_am_i_device(self) -> str: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU16(address).frame, dump=False ) - # print(str(reply)) + #print(str(reply)) return device_names.get(reply.payload_as_int()) def read_hw_version_h(self) -> int: address = CommonRegisters.HW_VERSION_H - reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) - # print(str(reply)) + reply: ReplyHarpMessage = self.send( + HarpMessage.ReadU8(address).frame, dump=False + ) + #print(str(reply)) return reply.payload_as_int() def read_hw_version_l(self) -> int: address = CommonRegisters.HW_VERSION_L - reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) - # print(str(reply)) + reply: ReplyHarpMessage = self.send( + HarpMessage.ReadU8(address).frame, dump=False + ) + #print(str(reply)) return reply.payload_as_int() def read_assembly_version(self) -> int: address = CommonRegisters.ASSEMBLY_VERSION - reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) - # print(str(reply)) + reply: ReplyHarpMessage = self.send( + HarpMessage.ReadU8(address).frame, dump=False + ) + #print(str(reply)) return reply.payload_as_int() def read_harp_h_version(self) -> int: address = CommonRegisters.HARP_VERSION_H - reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) - # print(str(reply)) + reply: ReplyHarpMessage = self.send( + HarpMessage.ReadU8(address).frame, dump=False + ) + #print(str(reply)) return reply.payload_as_int() def read_harp_l_version(self) -> int: address = CommonRegisters.HARP_VERSION_L - reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) - # print(str(reply)) + reply: ReplyHarpMessage = self.send( + HarpMessage.ReadU8(address).frame, dump=False + ) + #print(str(reply)) return reply.payload_as_int() def read_fw_h_version(self) -> int: address = CommonRegisters.FIRMWARE_VERSION_H - reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) - # print(str(reply)) + reply: ReplyHarpMessage = self.send( + HarpMessage.ReadU8(address).frame, dump=False + ) + #print(str(reply)) return reply.payload_as_int() def read_fw_l_version(self) -> int: address = CommonRegisters.FIRMWARE_VERSION_L - reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) - # print(str(reply)) + reply: ReplyHarpMessage = self.send( + HarpMessage.ReadU8(address).frame, dump=False +) + #print(str(reply)) return reply.payload_as_int() @@ -183,8 +195,10 @@ def read_device_name(self) -> str: address = CommonRegisters.DEVICE_NAME # reply: Optional[bytes] = self.send(HarpMessage.ReadU8(address).frame, 13 + 24) - reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) - # print(str(reply)) + reply: ReplyHarpMessage = self.send( + HarpMessage.ReadU8(address).frame, dump=False + ) + #print(str(reply)) return reply.payload_as_string() @@ -210,13 +224,13 @@ def dump_registers(self) -> list: break return replies - # TODO: Not sure if we want to implement these. Delete if no. +# TODO: Not sure if we want to implement these. Delete if no. def set_mode(self, mode: DeviceMode) -> ReplyHarpMessage: """Change the device's OPMODE. Reply can be ignored.""" address = CommonRegisters.OPERATION_CTRL # Read register first. reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() - reg_value &= ~0x03 # mask off old mode. + reg_value &= ~0x03 # mask off old mode. reg_value |= mode.value reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) return reply @@ -226,7 +240,7 @@ def enable_status_led(self): address = CommonRegisters.OPERATION_CTRL # Read register first. reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() - reg_value |= 1 << 5 + reg_value |= (1 << 5) reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) def disable_status_led(self): @@ -242,7 +256,7 @@ def enable_alive_en(self): address = CommonRegisters.OPERATION_CTRL # Read register first. reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() - reg_value |= 1 << 7 + reg_value |= (1 << 7) reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) def disable_alive_en(self): @@ -250,7 +264,7 @@ def disable_alive_en(self): address = CommonRegisters.OPERATION_CTRL # Read register first. reg_value = self.send(HarpMessage.ReadU8(address).frame).payload[0] - reg_value &= (1 << 7) ^ 0xFF # bitwise ~ operator substitute for Python ints. + reg_value &= ((1<< 7) ^ 0xFF) # bitwise ~ operator substitute for Python ints. reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) def reset_device(self): @@ -262,7 +276,7 @@ def reset_device(self): def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: """Send a harp message; return the device's reply.""" - # print(f"Sending: {repr(message_bytes)}") + #print(f"Sending: {repr(message_bytes)}") self._ser.write(message_bytes) # TODO: handle case where read is None @@ -273,36 +287,6 @@ def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: return reply - # def _read(self) -> Union[ReplyHarpMessage, None]: - # """(Blocking) Read an incoming serial message.""" - # # block for up to TIMEOUT until we get at least one byte. - # read_start = perf_counter() - # while True: - # if self._ser.inWaiting(): - # break - # if perf_counter() - read_start >= self.__class__.TIMEOUT_S: - # break - # try: - # message_type = self._ser.read(1)[0] # byte array with only one byte - # message_length = self._ser.read(1)[0] - # message_content = self._ser.read(message_length) - # self.log.debug(f"reply (type): {message_type}") - # self.log.debug(f"reply (length): {message_length}") - # self.log.debug(f"reply (payload): {message_content}") - # # print(f"Read back:") - # # print(f" type: {MessageType(message_type).name}") - # # print(f" length : {repr(message_length)}") - # # print(f" payload: {list(message_content)}") - - # frame = bytearray() - # frame.append(message_type) - # frame.append(message_length) - # frame += message_content - # msg = HarpMessage.parse(frame) - - # return msg - # except IndexError: - # return None def _read(self) -> Union[ReplyHarpMessage, None]: """(Blocking) Read an incoming serial message.""" @@ -310,18 +294,11 @@ def _read(self) -> Union[ReplyHarpMessage, None]: return self._ser.msg_q.get(block=True, timeout=self.read_timeout_s) except queue.Empty: return None - def _dump_reply(self, reply: bytes): assert self._dump_file_path is not None with self._dump_file_path.open(mode="ab") as f: f.write(reply) - # def read_register(self, register_name: str): - # register: Register = CommonRegisters[register_name] - # ReadHarpMessage(register.type, register.address) - - # def write_register(self, register_name: str, value): - def get_events(self) -> list[ReplyHarpMessage]: msgs = [] while True: diff --git a/pyharp/messages.py b/pyharp/messages.py index 2a91a25..e86747a 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -1,9 +1,7 @@ -from __future__ import annotations # for type hints (PEP 563) +from __future__ import annotations # for type hints (PEP 563) from enum import Enum - # from abc import ABC, abstractmethod -from typing import Union, Tuple, Optional, List, Any -from dataclasses import dataclass +from typing import Union, Tuple, Optional, List import struct @@ -58,16 +56,6 @@ class CommonRegisters: DEVICE_NAME = 0x0C -@dataclass -class Register: - name: str - address: int - type: PayloadType - access: Optional[str] = None - description: Optional[str] = None - reset_value: Optional[Any] = None - - class HarpMessage: """ https://github.com/harp-tech/protocol/blob/master/Binary%20Protocol%201.0%201.1%2020180223.pdf @@ -179,9 +167,9 @@ def parse(frame: bytearray) -> ReplyHarpMessage: # A Response Message from a harp device. class ReplyHarpMessage(HarpMessage): + def __init__( - self, - frame: bytearray, + self, frame: bytearray, ): """ @@ -191,66 +179,57 @@ def __init__( self._frame = frame # retrieve all content from 11 (where payload starts) until the checksum (not inclusive) self._raw_payload = frame[11:-1] - self._payload = self._parse_payload( - self._raw_payload - ) # payload formatted as list[payload type] + self._payload = self._parse_payload(self._raw_payload) # payload formatted as list[payload type] # Assign timestamp after _payload since @properties all rely on self._payload. - self._timestamp = ( - int.from_bytes(frame[5:9], byteorder="little", signed=False) - + int.from_bytes(frame[9:11], byteorder="little", signed=False) * 32e-6 - ) + self._timestamp = int.from_bytes(frame[5:9], byteorder="little", signed=False) + \ + int.from_bytes(frame[9:11], byteorder="little", signed=False)*32e-6 # Timestamp is junk if it's not present. if not (self.payload_type.value & PayloadType.hasTimestamp.value): self._timestamp = None + def _parse_payload(self, raw_payload) -> list[int]: """return the payload as a list of ints after parsing it from the raw payload.""" is_signed = True if (self.payload_type.value & 0x80) else False is_float = True if (self.payload_type.value & 0x40) else False bytes_per_word = self.payload_type.value & 0x07 - payload_len = len(raw_payload) # payload length in bytes. + payload_len = len(raw_payload) # payload length in bytes. - word_chunks = [ - raw_payload[i : i + bytes_per_word] - for i in range(0, payload_len, bytes_per_word) - ] + word_chunks = [raw_payload[i:i+bytes_per_word] for i in range(0, payload_len, bytes_per_word)] if not is_float: - return [ - int.from_bytes(chunk, byteorder="little", signed=is_signed) - for chunk in word_chunks - ] - else: # handle float case. - return [struct.unpack(" Union[int, list[int]]: @@ -275,6 +254,7 @@ def payload_as_float(self) -> float: class ReadHarpMessage(HarpMessage): MESSAGE_TYPE: int = MessageType.READ + def __init__(self, payload_type: PayloadType, address: int): self._frame = bytearray() @@ -307,7 +287,6 @@ class ReadS16HarpMessage(ReadHarpMessage): def __init__(self, address: int): super().__init__(PayloadType.S16, address) - class ReadU32HarpMessage(ReadHarpMessage): def __init__(self, address: int): super().__init__(PayloadType.U32, address) @@ -380,10 +359,7 @@ def payload(self) -> int: class WriteU16HarpMessage(WriteHarpMessage): def __init__(self, address: int, value: int): super().__init__( - PayloadType.U16, - value.to_bytes(2, byteorder="little", signed=False), - address, - offset=1, + PayloadType.U16, value.to_bytes(2, byteorder="little", signed=False), address, offset=1 ) @property @@ -409,25 +385,20 @@ class WriteFloatHarpMessage(WriteHarpMessage): def __init__(self, address: int, value: float): super().__init__( PayloadType.Float, - struct.pack( - " float: - return struct.unpack(" int | List[int]: From 9e305456fd539d4e317edd5af0fab023f1ccae50 Mon Sep 17 00:00:00 2001 From: Patrick Latimer Date: Wed, 12 Feb 2025 15:53:35 -0800 Subject: [PATCH 28/35] add event_count method and update test --- pyharp/device.py | 15 +++++++-------- pyharp/harp_serial.py | 4 ++-- tests/test_device.py | 20 +++----------------- 3 files changed, 12 insertions(+), 27 deletions(-) diff --git a/pyharp/device.py b/pyharp/device.py index 0a754cb..0f4368e 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -6,13 +6,7 @@ from pathlib import Path from pyharp.harp_serial import HarpSerial -from pyharp.messages import ( - HarpMessage, - ReadHarpMessage, - ReplyHarpMessage, - # ResetDevOffsets, - Register, -) +from pyharp.messages import HarpMessage, ReplyHarpMessage from pyharp.messages import CommonRegisters, MessageType from pyharp.device_names import device_names from enum import Enum @@ -287,19 +281,20 @@ def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: return reply - def _read(self) -> Union[ReplyHarpMessage, None]: """(Blocking) Read an incoming serial message.""" try: return self._ser.msg_q.get(block=True, timeout=self.read_timeout_s) except queue.Empty: return None + def _dump_reply(self, reply: bytes): assert self._dump_file_path is not None with self._dump_file_path.open(mode="ab") as f: f.write(reply) def get_events(self) -> list[ReplyHarpMessage]: + """Get all events from the event queue.""" msgs = [] while True: try: @@ -307,3 +302,7 @@ def get_events(self) -> list[ReplyHarpMessage]: except queue.Empty: break return msgs + + def event_count(self) -> int: + """Get the number of events in the event queue.""" + return self._ser.event_q.qsize() \ No newline at end of file diff --git a/pyharp/harp_serial.py b/pyharp/harp_serial.py index 85a0bb8..3b9d4a2 100644 --- a/pyharp/harp_serial.py +++ b/pyharp/harp_serial.py @@ -17,7 +17,7 @@ def __init__(self, _read_q: queue.Queue, *args, **kwargs): super().__init__(*args, **kwargs) def connection_made(self, transport: serial.threaded.ReaderThread) -> None: - print(f"Connected to {transport.serial.port}") + # print(f"Connected to {transport.serial.port}") return super().connection_made(transport) def data_received(self, data: bytes) -> None: @@ -26,7 +26,7 @@ def data_received(self, data: bytes) -> None: return super().data_received(data) def connection_lost(self, exc: Union[BaseException, None]) -> None: - print(f"Lost connection!") + # print(f"Lost connection!") return super().connection_lost(exc) diff --git a/tests/test_device.py b/tests/test_device.py index 196a07c..9501663 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -1,7 +1,6 @@ import serial import time from typing import Optional - from pyharp.messages import HarpMessage, ReplyHarpMessage from pyharp.device import Device @@ -89,21 +88,8 @@ def test_U8() -> None: def test_device_events(device: Device) -> None: - event_q = device._ser.event_q - while True: - print(device._ser.event_q.qsize()) - if not event_q.empty(): - try: - msg: ReplyHarpMessage = event_q.get() - print(msg) - except Exception: - pass + print(device.event_count()) + for msg in device.get_events(): + print(msg) time.sleep(0.3) - - -if __name__ == "__main__": - # open serial connection and load info - device = Device("COM4", "dump.txt") - # assert device._dump_file_path.exists() - test_device_events(device) From 0d993bb8d522eafd976e1aca0cd85cc9eec1b734 Mon Sep 17 00:00:00 2001 From: Patrick Latimer Date: Wed, 12 Feb 2025 15:54:56 -0800 Subject: [PATCH 29/35] remove unused import --- pyharp/device.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyharp/device.py b/pyharp/device.py index 0f4368e..b1c8a3b 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -1,6 +1,5 @@ import serial import logging -import threading import queue from typing import Optional, Union from pathlib import Path From 583a2709d5dab14d09bc86f54b3439e6e7854695 Mon Sep 17 00:00:00 2001 From: Patrick Latimer Date: Wed, 12 Feb 2025 15:57:43 -0800 Subject: [PATCH 30/35] undo formatting change --- tests/test_device.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_device.py b/tests/test_device.py index 9501663..5a5eab9 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -47,7 +47,7 @@ def test_U8() -> None: # write 65 on register 38 write_message = HarpMessage.WriteU8(register, write_value) - reply: ReplyHarpMessage = device.send(write_message.frame) + reply : ReplyHarpMessage = device.send(write_message.frame) assert reply is not None # read register 38 @@ -87,7 +87,6 @@ def test_U8() -> None: def test_device_events(device: Device) -> None: - while True: print(device.event_count()) for msg in device.get_events(): From 556e51263c35b7d6d91e5d944ba82415fd580de1 Mon Sep 17 00:00:00 2001 From: Patrick Latimer Date: Wed, 12 Feb 2025 15:59:17 -0800 Subject: [PATCH 31/35] remove python-versions pin --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9d3b6a2..ca8d302 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,6 @@ description = "Library for data acquisition and control of devices implementing authors = ["filcarv "] license = "MIT" readme = 'README.md' -python-versions = '>3.10' [tool.poetry.dependencies] python = "^3.4" From efdefc5d9387b1662a7c6c8e03fba137a2408c2a Mon Sep 17 00:00:00 2001 From: Patrick Latimer Date: Wed, 12 Feb 2025 16:53:54 -0800 Subject: [PATCH 32/35] add future import for list annotations --- pyharp/device.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyharp/device.py b/pyharp/device.py index b1c8a3b..7bfdf76 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -3,6 +3,7 @@ import queue from typing import Optional, Union from pathlib import Path +from __future__ import annotations # enable subscriptable type hints for lists. from pyharp.harp_serial import HarpSerial from pyharp.messages import HarpMessage, ReplyHarpMessage From 0a15ab28fc5fd02c871646de37aa1b3b4a4727f4 Mon Sep 17 00:00:00 2001 From: Patrick Latimer Date: Wed, 12 Feb 2025 16:57:42 -0800 Subject: [PATCH 33/35] fix future import --- pyharp/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyharp/device.py b/pyharp/device.py index 7bfdf76..b4c6f6d 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -1,9 +1,9 @@ +from __future__ import annotations # enable subscriptable type hints for lists. import serial import logging import queue from typing import Optional, Union from pathlib import Path -from __future__ import annotations # enable subscriptable type hints for lists. from pyharp.harp_serial import HarpSerial from pyharp.messages import HarpMessage, ReplyHarpMessage From c68f87b542d8109bceafa7ad7f61813ae8da5fd9 Mon Sep 17 00:00:00 2001 From: Sonya Vasquez Date: Thu, 20 Feb 2025 16:09:49 -0800 Subject: [PATCH 34/35] update wait-for-events example --- examples/get_info.py | 5 +++-- examples/wait_for_events.py | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/get_info.py b/examples/get_info.py index d7b7a47..eec8cef 100755 --- a/examples/get_info.py +++ b/examples/get_info.py @@ -37,8 +37,9 @@ device_assembly = device.ASSEMBLY_VERSION # Get device's assembly version reg_dump = device.dump_registers() -for i in range(11): - print(reg_dump[i]) +for reg_reply in reg_dump: + print(reg_reply) + print() # Close connection device.disconnect() diff --git a/examples/wait_for_events.py b/examples/wait_for_events.py index b846d37..99fbe15 100755 --- a/examples/wait_for_events.py +++ b/examples/wait_for_events.py @@ -24,7 +24,8 @@ #device.disable_all_events() #device.enable_events(Events.port_digital_inputs) while True: - event_response = device._read() # read any incoming events. - if event_response is not None:# and event_response.address != 44: + if not device.event_count(): + pass + for msg in device.get_events(): + print(msg) print() - print(event_response) From de151be77eab4a279e5b0e408e8c57fc4e6ff843 Mon Sep 17 00:00:00 2001 From: Sonya Vasquez Date: Fri, 21 Feb 2025 10:50:47 -0800 Subject: [PATCH 35/35] remove extraneous call --- examples/wait_for_events.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/wait_for_events.py b/examples/wait_for_events.py index 99fbe15..584836e 100755 --- a/examples/wait_for_events.py +++ b/examples/wait_for_events.py @@ -24,8 +24,6 @@ #device.disable_all_events() #device.enable_events(Events.port_digital_inputs) while True: - if not device.event_count(): - pass for msg in device.get_events(): print(msg) print()