From f296c21f053dc53c4d956722fefcbacea4e5fdab Mon Sep 17 00:00:00 2001 From: chrisruk Date: Tue, 24 May 2022 11:42:02 +0100 Subject: [PATCH 01/50] Remove use of sudo for pip --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a20d9d5..0b5968a 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Projects and inspiration: https://projects.raspberrypi.org/en/pathways/lego-intr To install the Build HAT library, enter the following commands in a terminal: - sudo pip3 install buildhat + pip3 install buildhat ## Usage From 94434341330a178a9c2b031b9bf341e4602b4b34 Mon Sep 17 00:00:00 2001 From: chrisruk Date: Thu, 23 Jun 2022 11:36:59 +0100 Subject: [PATCH 02/50] Modify github flake8 action (#154) --- .github/workflows/python-app.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 8e72fc3..833f952 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -34,5 +34,3 @@ jobs: flake8 . --version # stop the build if there are Python syntax errors or undefined names flake8 . --count --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics From efe5557f4b72125721e4c69a849cea0569896e98 Mon Sep 17 00:00:00 2001 From: Ben Rogers Date: Mon, 18 Jul 2022 10:04:33 +0100 Subject: [PATCH 03/50] Added get_distance() to ColourDistanceSensor (#157) * Add get_distance() to example code * Only return first element of array in get_distance() * Use mode 1 for distance --- buildhat/colordistance.py | 10 ++++++++++ docs/buildhat/colordistance.py | 1 + 2 files changed, 11 insertions(+) diff --git a/buildhat/colordistance.py b/buildhat/colordistance.py index 90f0f94..da85c3a 100644 --- a/buildhat/colordistance.py +++ b/buildhat/colordistance.py @@ -115,6 +115,16 @@ def get_reflected_light(self): readings.append(self.get()[0]) return int(sum(readings) / len(readings)) + def get_distance(self): + """Return the distance + + :return: Distance + :rtype: int + """ + self.mode(1) + distance = self.get()[0] + return distance + def _clamp(self, val, small, large): return max(small, min(val, large)) diff --git a/docs/buildhat/colordistance.py b/docs/buildhat/colordistance.py index 8a73964..d8a3466 100755 --- a/docs/buildhat/colordistance.py +++ b/docs/buildhat/colordistance.py @@ -4,6 +4,7 @@ color = ColorDistanceSensor('C') +print("Distance", color.get_distance()) print("RGBI", color.get_color_rgb()) print("Ambient", color.get_ambient_light()) print("Reflected", color.get_reflected_light()) From 2d84f521ab7d5fce3a577639dfd1a2c3ee5dfa76 Mon Sep 17 00:00:00 2001 From: skrueger-ftc <71362472+skrueger-ftc@users.noreply.github.com> Date: Tue, 9 Aug 2022 07:47:36 -0500 Subject: [PATCH 04/50] Use debug logging level (#159) --- buildhat/serinterface.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/buildhat/serinterface.py b/buildhat/serinterface.py index 14e6803..05f864b 100644 --- a/buildhat/serinterface.py +++ b/buildhat/serinterface.py @@ -94,7 +94,7 @@ def __init__(self, firmware, signature, version, device="/dev/serial0", debug=Fa if debug: tmp = tempfile.NamedTemporaryFile(suffix=".log", prefix="buildhat-", delete=False) logging.basicConfig(filename=tmp.name, format='%(asctime)s %(message)s', - level=logging.INFO) + level=logging.DEBUG) for _ in range(4): self.connections.append(Connection()) @@ -239,9 +239,9 @@ def write(self, data, log=True, replace=""): self.ser.write(data) if not self.fin and log: if replace != "": - logging.info(f"> {replace}") + logging.debug(f"> {replace}") else: - logging.info(f"> {data.decode('utf-8', 'ignore').strip()}") + logging.debug(f"> {data.decode('utf-8', 'ignore').strip()}") def read(self): """Read data from the serial port of Build HAT @@ -254,7 +254,7 @@ def read(self): except serial.SerialException: pass if line != "": - logging.info(f"< {line}") + logging.debug(f"< {line}") return line def shutdown(self): From 6dba5bb47e58e60966c2677ff514996301c909ef Mon Sep 17 00:00:00 2001 From: chrisruk Date: Wed, 24 Aug 2022 13:12:52 +0100 Subject: [PATCH 05/50] Remove flake8-bandit which is now broken, due to changes in flake8 (#161) --- requirements-test.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements-test.txt b/requirements-test.txt index b6d3acc..e8ccc38 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,5 +1,4 @@ flake8 -flake8-bandit flake8-broken-line flake8-bugbear flake8-commas From cc5abc93d5d9a2126a819959815a2527622ce5a5 Mon Sep 17 00:00:00 2001 From: chrisruk Date: Tue, 6 Sep 2022 14:04:00 +0100 Subject: [PATCH 06/50] Add support for 88008 motor to allow setting speed and position (#165) --- buildhat/motors.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/buildhat/motors.py b/buildhat/motors.py index 62f80ea..b28e0bc 100644 --- a/buildhat/motors.py +++ b/buildhat/motors.py @@ -108,7 +108,14 @@ def __init__(self, port): super().__init__(port) self.default_speed = 20 self._currentspeed = 0 - self.mode([(1, 0), (2, 0), (3, 0)]) + if self._typeid in {38}: + self.mode([(1, 0), (2, 0)]) + self._combi = "1 0 2 0" + self._noapos = True + else: + self.mode([(1, 0), (2, 0), (3, 0)]) + self._combi = "1 0 2 0 3 0" + self._noapos = False self.plimit(0.7) self.bias(0.3) self._release = True @@ -160,7 +167,10 @@ def _run_to_position(self, degrees, speed, direction): self._runmode = MotorRunmode.DEGREES data = self.get() pos = data[1] - apos = data[2] + if self._noapos: + apos = pos + else: + apos = data[2] diff = (degrees - apos + 180) % 360 - 180 newpos = (pos + diff) / 360 v1 = (degrees - apos) % 360 @@ -192,7 +202,7 @@ def _run_positional_ramp(self, pos, newpos, speed): # Collapse speed range to -5 to 5 speed *= 0.05 dur = abs((newpos - pos) / speed) - cmd = (f"port {self.port}; combi 0 1 0 2 0 3 0 ; select 0 ; pid {self.port} 0 1 s4 0.0027777778 0 5 0 .1 3 ; " + cmd = (f"port {self.port}; combi 0 {self._combi} ; select 0 ; pid {self.port} 0 1 s4 0.0027777778 0 5 0 .1 3 ; " f"set ramp {pos} {newpos} {dur} 0\r") self._write(cmd) with self._hat.rampcond[self.port]: @@ -248,7 +258,7 @@ def run_to_position(self, degrees, speed=None, blocking=True, direction="shortes def _run_for_seconds(self, seconds, speed): self._runmode = MotorRunmode.SECONDS - cmd = (f"port {self.port} ; combi 0 1 0 2 0 3 0 ; select 0 ; pid {self.port} 0 0 s1 1 0 0.003 0.01 0 100; " + cmd = (f"port {self.port} ; combi 0 {self._combi} ; select 0 ; pid {self.port} 0 0 s1 1 0 0.003 0.01 0 100; " f"set pulse {speed} 0.0 {seconds} 0\r") self._write(cmd) with self._hat.pulsecond[self.port]: @@ -298,7 +308,7 @@ def start(self, speed=None): raise MotorError("Invalid Speed") cmd = f"port {self.port} ; set {speed}\r" if self._runmode == MotorRunmode.NONE: - cmd = (f"port {self.port} ; combi 0 1 0 2 0 3 0 ; select 0 ; pid {self.port} 0 0 s1 1 0 0.003 0.01 0 100; " + cmd = (f"port {self.port} ; combi 0 {self._combi} ; select 0 ; pid {self.port} 0 0 s1 1 0 0.003 0.01 0 100; " f"set {speed}\r") self._runmode = MotorRunmode.FREE self._currentspeed = speed @@ -324,7 +334,10 @@ def get_aposition(self): :return: Absolute position of motor from -180 to 180 :rtype: int """ - return self.get()[2] + if self._noapos: + raise MotorError("No absolute position with this motor") + else: + return self.get()[2] def get_speed(self): """Get speed of motor @@ -346,7 +359,11 @@ def when_rotated(self): return self._when_rotated def _intermediate(self, data): - speed, pos, apos = data + if self._noapos: + speed, pos = data + apos = None + else: + speed, pos, apos = data if self._oldpos is None: self._oldpos = pos return From ac2137f454f79acb79da22b89fcbda8e786c9d0b Mon Sep 17 00:00:00 2001 From: chrisruk Date: Thu, 29 Sep 2022 13:50:03 +0100 Subject: [PATCH 07/50] Update changelog for new version (#167) * Update changelog for new version * Bump version to 0.5.10 --- CHANGELOG.md | 15 +++++++++++++++ VERSION | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 053a830..e200fb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Change Log +## 0.5.10 + +### Added + +* Support for 88008 motor +* get_distance support for ColourDistanceSensor + +### Changed + +* Use debug log level for logging + +### Removed + +n/a + ## 0.5.9 ### Added diff --git a/VERSION b/VERSION index 416bfb0..50c76ef 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.9 +0.5.10 From fa860885c6a44678bbe294f26543a0c87386e5c5 Mon Sep 17 00:00:00 2001 From: chrisruk Date: Fri, 28 Oct 2022 11:51:24 +0100 Subject: [PATCH 08/50] Update documentation for WeDo sensors (#171) --- docs/buildhat/index.rst | 4 +++- docs/buildhat/motion.py | 11 +++++++++++ docs/buildhat/motionsensor.rst | 19 +++++++++++++++++++ docs/buildhat/tilt.py | 11 +++++++++++ docs/buildhat/tiltsensor.rst | 19 +++++++++++++++++++ 5 files changed, 63 insertions(+), 1 deletion(-) create mode 100755 docs/buildhat/motion.py create mode 100644 docs/buildhat/motionsensor.rst create mode 100755 docs/buildhat/tilt.py create mode 100644 docs/buildhat/tiltsensor.rst diff --git a/docs/buildhat/index.rst b/docs/buildhat/index.rst index 9ac86b4..91b48b0 100644 --- a/docs/buildhat/index.rst +++ b/docs/buildhat/index.rst @@ -40,9 +40,11 @@ power supply. For best results, use the `official Raspberry Pi Build HAT power s colordistancesensor.rst distancesensor.rst forcesensor.rst + light.rst matrix.rst + motionsensor.rst motor.rst motorpair.rst passivemotor.rst + tiltsensor.rst hat.rst - light.rst diff --git a/docs/buildhat/motion.py b/docs/buildhat/motion.py new file mode 100755 index 0000000..cf48829 --- /dev/null +++ b/docs/buildhat/motion.py @@ -0,0 +1,11 @@ +"""Example using motion sensor""" + +from time import sleep + +from buildhat import MotionSensor + +motion = MotionSensor('A') + +for _ in range(50): + print(motion.get_distance()) + sleep(0.1) diff --git a/docs/buildhat/motionsensor.rst b/docs/buildhat/motionsensor.rst new file mode 100644 index 0000000..e9d0f5f --- /dev/null +++ b/docs/buildhat/motionsensor.rst @@ -0,0 +1,19 @@ +Motion Sensor +============= + +|location_link| + +.. |location_link| raw:: html + + BrickLink item + +.. autoclass:: buildhat.MotionSensor + :members: + :inherited-members: + +Example +------- + +.. literalinclude:: motion.py + + diff --git a/docs/buildhat/tilt.py b/docs/buildhat/tilt.py new file mode 100755 index 0000000..36ec8d1 --- /dev/null +++ b/docs/buildhat/tilt.py @@ -0,0 +1,11 @@ +"""Example using tilt sensor""" + +from time import sleep + +from buildhat import TiltSensor + +tilt = TiltSensor('A') + +for _ in range(50): + print(tilt.get_tilt()) + sleep(0.1) diff --git a/docs/buildhat/tiltsensor.rst b/docs/buildhat/tiltsensor.rst new file mode 100644 index 0000000..e428882 --- /dev/null +++ b/docs/buildhat/tiltsensor.rst @@ -0,0 +1,19 @@ +Tilt Sensor +=========== + +|location_link| + +.. |location_link| raw:: html + + BrickLink item + +.. autoclass:: buildhat.TiltSensor + :members: + :inherited-members: + +Example +------- + +.. literalinclude:: tilt.py + + From 33ca5e022d6779a31e6b1d0ebbf4e55e4466808e Mon Sep 17 00:00:00 2001 From: chrisruk Date: Tue, 1 Nov 2022 09:34:20 +0000 Subject: [PATCH 09/50] Expose release property to allow motor to hold position (#172) --- buildhat/motors.py | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/buildhat/motors.py b/buildhat/motors.py index b28e0bc..265245a 100644 --- a/buildhat/motors.py +++ b/buildhat/motors.py @@ -419,6 +419,28 @@ def float(self): """Float motor""" self.pwm(0) + @property + def release(self): + """Determine if motor is released after running, so can be turned by hand + + :getter: Returns whether motor is released, so can be turned by hand + :setter: Sets whether motor is released, so can be turned by hand + :return: Whether motor is released, so can be turned by hand + :rtype: bool + """ + return self._release + + @release.setter + def release(self, value): + """Determine if the motor is released after running, so can be turned by hand + + :param value: Whether motor should be released, so can be turned by hand + :type value: bool + """ + if not isinstance(value, bool): + raise MotorError("Must pass boolean") + self._release = value + class MotorPair: """Pair of motors @@ -438,6 +460,7 @@ def __init__(self, leftport, rightport): self._leftmotor = Motor(leftport) self._rightmotor = Motor(rightport) self.default_speed = 20 + self._release = True def set_default_speed(self, default_speed): """Set the default speed of the motor @@ -537,3 +560,27 @@ def run_to_position(self, degreesl, degreesr, speed=None, direction="shortest"): th2.start() th1.join() th2.join() + + @property + def release(self): + """Determine if motors are released after running, so can be turned by hand + + :getter: Returns whether motors are released, so can be turned by hand + :setter: Sets whether motors are released, so can be turned by hand + :return: Whether motors are released, so can be turned by hand + :rtype: bool + """ + return self._release + + @release.setter + def release(self, value): + """Determine if motors are released after running, so can be turned by hand + + :param value: Whether motors should be released, so can be turned by hand + :type value: bool + """ + if not isinstance(value, bool): + raise MotorError("Must pass boolean") + self._release = value + self._leftmotor.release = value + self._rightmotor.release = value From 61f39301e040fc16b1b31bb54dab15bc60ae9fff Mon Sep 17 00:00:00 2001 From: chrisruk Date: Wed, 2 Nov 2022 13:47:24 +0000 Subject: [PATCH 10/50] Bump version to 0.5.11 (#173) --- CHANGELOG.md | 15 +++++++++++++++ VERSION | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e200fb1..1f9eabf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Change Log +## 0.5.11 + +### Added + +* Expose release property to allow motor to hold position +* Updated documentation + +### Changed + +n/a + +### Removed + +n/a + ## 0.5.10 ### Added diff --git a/VERSION b/VERSION index 50c76ef..69626fb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.10 +0.5.11 From 16f50efcdaf0ba8593f2d09f369bf6539d63ada0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20=C3=98stergaard?= Date: Thu, 17 Nov 2022 16:55:02 +0100 Subject: [PATCH 11/50] Link used hhttps and not https (#177) --- docs/buildhat/passivemotor.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/buildhat/passivemotor.rst b/docs/buildhat/passivemotor.rst index 2b57b20..357055e 100644 --- a/docs/buildhat/passivemotor.rst +++ b/docs/buildhat/passivemotor.rst @@ -6,7 +6,7 @@ LEGO® TECHNIC™ motors which do not have an integrated rotation sensor (encode .. |location_link1| raw:: html - LEGO® Train motor 88011 + LEGO® Train motor 88011 .. |location_link1b| raw:: html From 90d9f9774ca62676da1f335d7c8bae6e5b79cce8 Mon Sep 17 00:00:00 2001 From: chrisruk Date: Mon, 28 Nov 2022 14:25:15 +0000 Subject: [PATCH 12/50] Add a delay to account for the disconnection/reconnection with matrix device (#178) --- buildhat/serinterface.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/buildhat/serinterface.py b/buildhat/serinterface.py index 05f864b..31f062b 100644 --- a/buildhat/serinterface.py +++ b/buildhat/serinterface.py @@ -151,14 +151,17 @@ def __init__(self, firmware, signature, version, device="/dev/serial0", debug=Fa self.cb.start() # Drop timeout value to 1s + listevt = threading.Event() self.ser.timeout = 1 - self.th = threading.Thread(target=self.loop, args=(self.cond, self.state == HatState.FIRMWARE, self.cbqueue)) + self.th = threading.Thread(target=self.loop, args=(self.cond, self.state == HatState.FIRMWARE, self.cbqueue, listevt)) self.th.daemon = True self.th.start() if self.state == HatState.FIRMWARE: self.write(b"port 0 ; select ; port 1 ; select ; port 2 ; select ; port 3 ; select ; echo 0\r") + time.sleep(3.5) self.write(b"list\r") + listevt.set() elif self.state == HatState.NEEDNEWFIRMWARE or self.state == HatState.BOOTLOADER: self.write(b"reboot\r") @@ -292,7 +295,7 @@ def callbackloop(self, q): cb[0]()(cb[1]) q.task_done() - def loop(self, cond, uselist, q): + def loop(self, cond, uselist, q, listevt): """Event handling for Build HAT :param cond: Condition used to block user's script till we're ready @@ -312,12 +315,12 @@ def loop(self, cond, uselist, q): self.connections[portid].update(typeid, True) if typeid == 64: self.write(f"port {portid} ; on\r".encode()) - if uselist: + if uselist and listevt.is_set(): count += 1 elif cmp(msg, BuildHAT.CONNECTEDPASSIVE): typeid = int(line[2 + len(BuildHAT.CONNECTEDPASSIVE):], 16) self.connections[portid].update(typeid, True) - if uselist: + if uselist and listevt.is_set(): count += 1 elif cmp(msg, BuildHAT.DISCONNECTED): self.connections[portid].update(-1, False) @@ -325,7 +328,7 @@ def loop(self, cond, uselist, q): self.connections[portid].update(-1, False) elif cmp(msg, BuildHAT.NOTCONNECTED): self.connections[portid].update(-1, False) - if uselist: + if uselist and listevt.is_set(): count += 1 elif cmp(msg, BuildHAT.RAMPDONE): with self.rampcond[portid]: From d762bcef4aef3c09e813f69c4ef3e3f1b6e0521c Mon Sep 17 00:00:00 2001 From: chrisruk Date: Tue, 6 Dec 2022 11:16:13 +0000 Subject: [PATCH 13/50] Use latest flake8 and add pre-commit config (#180) * Use latest flake8 and add pre-commit config * Update version of Python used for github action to 3.9 --- .github/workflows/python-app.yml | 4 ++-- .pre-commit-config.yaml | 15 +++++++++++++++ requirements-test.txt | 15 +++++---------- 3 files changed, 22 insertions(+), 12 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 833f952..57a67fb 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -16,10 +16,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Python 3.7 + - name: Set up Python 3.9 uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: 3.9 - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..3fc02e5 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,15 @@ +repos: +- repo: https://github.com/PyCQA/flake8 + rev: 6.0.0 + hooks: + - id: flake8 + additional_dependencies: [ + 'flake8-bugbear>=22.10.27', + 'flake8-comprehensions>=3.10', + 'flake8-debugger', + 'flake8-docstrings>=1.6.0', + 'flake8-isort>=5.0', + 'flake8-pylint', + 'flake8-rst-docstrings', + 'flake8-string-format' + ] diff --git a/requirements-test.txt b/requirements-test.txt index e8ccc38..8d7b912 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,15 +1,10 @@ -flake8 -flake8-broken-line -flake8-bugbear -flake8-commas -flake8-comprehensions +flake8>=6.0.0 +flake8-bugbear>=22.10.27 +flake8-comprehensions>=3.10 flake8-debugger -flake8-docstrings -flake8-eradicate -flake8-isort -flake8-isort +flake8-docstrings>=1.6.0 +flake8-isort>=5.0 flake8-pylint -flake8-quotes flake8-rst-docstrings flake8-string-format gpiozero From 5e50fbee89f838f10ce50e1afa1fb6891fde51f0 Mon Sep 17 00:00:00 2001 From: chrisruk Date: Tue, 6 Dec 2022 14:36:38 +0000 Subject: [PATCH 14/50] New firmware to prevent matrix disconnecting --- .pre-commit-config.yaml | 4 +++- buildhat/data/firmware.bin | Bin 53112 -> 53768 bytes buildhat/data/signature.bin | 3 +-- buildhat/data/version | 2 +- buildhat/serinterface.py | 1 - 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3fc02e5..bb16389 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,5 +11,7 @@ repos: 'flake8-isort>=5.0', 'flake8-pylint', 'flake8-rst-docstrings', - 'flake8-string-format' + 'flake8-string-format', + 'gpiozero', + 'pyserial' ] diff --git a/buildhat/data/firmware.bin b/buildhat/data/firmware.bin index d29a920969a829917bd1a05aee405ca9e8c83998..9e9f922ecce254d54b2224fec15654a150f87dd8 100644 GIT binary patch literal 53768 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�U1KN1es^vG+Wu<{E1F@8cTslIs(k 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 W#(wxL<8Q}zGT(cg@$deS@&5}~dWSLq diff --git a/buildhat/data/signature.bin b/buildhat/data/signature.bin index 72ba9a3..869a062 100644 --- a/buildhat/data/signature.bin +++ b/buildhat/data/signature.bin @@ -1,2 +1 @@ -cF̠m㰶v t?#YFxQ -H|#9J49MG $dIӛ \ No newline at end of file +2߰l~aiÌ! &]X8 Date: Fri, 16 Sep 2022 14:49:16 +0100 Subject: [PATCH 15/50] Test continuous motor feedback --- test/motors.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/motors.py b/test/motors.py index 4d3275f..2ae6d74 100644 --- a/test/motors.py +++ b/test/motors.py @@ -3,7 +3,7 @@ import time import unittest -from buildhat import Motor +from buildhat import Hat, Motor from buildhat.exc import DeviceError, MotorError @@ -121,6 +121,15 @@ def test_continuous_position(self): while time.time() < t: m.run_to_position(0) + def test_continuous_feedback(self): + """Test feedback of motor for 30mins""" + Hat(debug=True) + t = time.time() + (60 * 30) + m = Motor('A') + m.start(40) + while time.time() < t: + _ = (m.get_speed(), m.get_position(), m.get_aposition()) + if __name__ == '__main__': unittest.main() From 4594835b5a592cae2a370f99bdabcf0f70919698 Mon Sep 17 00:00:00 2001 From: chrisruk Date: Fri, 9 Dec 2022 13:54:25 +0000 Subject: [PATCH 16/50] Allow custom device interval for callbacks --- buildhat/devices.py | 28 +++++++++++++++++++++++++++- buildhat/motors.py | 9 ++++++--- test/motors.py | 17 +++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/buildhat/devices.py b/buildhat/devices.py index 3b433b2..2f14453 100644 --- a/buildhat/devices.py +++ b/buildhat/devices.py @@ -58,6 +58,7 @@ def __init__(self, port): self._simplemode = -1 self._combimode = -1 self._typeid = self._conn.typeid + self._interval = 100 if ( self._typeid in Device._device_names and Device._device_names[self._typeid][0] != type(self).__name__ # noqa: W503 @@ -239,7 +240,7 @@ def select(self): idx = self._combimode else: raise DeviceError("Not in simple or combimode") - self._write(f"port {self.port} ; select {idx}\r") + self._write(f"port {self.port} ; select {idx} ; selrate {self._interval}\r") def on(self): """Turn on sensor""" @@ -274,3 +275,28 @@ def callback(self, func): self._conn.callit = None else: self._conn.callit = weakref.WeakMethod(func) + + @property + def interval(self): + """Interval between data points in milliseconds + + :getter: Gets interval + :setter: Sets interval + :return: Device interval + :rtype: int + """ + return self._interval + + @interval.setter + def interval(self, value): + """Interval between data points in milliseconds + + :param value: Interval + :type value: int + :raises DeviceError: Occurs if invalid interval passed + """ + if isinstance(value, int) and value >= 0 and value <= 1000000000: + self._interval = value + self._write(f"port {self.port} ; selrate {self._interval}\r") + else: + raise DeviceError("Invalid interval") diff --git a/buildhat/motors.py b/buildhat/motors.py index 265245a..2a457e4 100644 --- a/buildhat/motors.py +++ b/buildhat/motors.py @@ -202,7 +202,8 @@ def _run_positional_ramp(self, pos, newpos, speed): # Collapse speed range to -5 to 5 speed *= 0.05 dur = abs((newpos - pos) / speed) - cmd = (f"port {self.port}; combi 0 {self._combi} ; select 0 ; pid {self.port} 0 1 s4 0.0027777778 0 5 0 .1 3 ; " + cmd = (f"port {self.port}; combi 0 {self._combi} ; select 0 ; selrate {self._interval}; " + f"pid {self.port} 0 1 s4 0.0027777778 0 5 0 .1 3; " f"set ramp {pos} {newpos} {dur} 0\r") self._write(cmd) with self._hat.rampcond[self.port]: @@ -258,7 +259,8 @@ def run_to_position(self, degrees, speed=None, blocking=True, direction="shortes def _run_for_seconds(self, seconds, speed): self._runmode = MotorRunmode.SECONDS - cmd = (f"port {self.port} ; combi 0 {self._combi} ; select 0 ; pid {self.port} 0 0 s1 1 0 0.003 0.01 0 100; " + cmd = (f"port {self.port} ; combi 0 {self._combi} ; select 0 ; selrate {self._interval}; " + f"pid {self.port} 0 0 s1 1 0 0.003 0.01 0 100; " f"set pulse {speed} 0.0 {seconds} 0\r") self._write(cmd) with self._hat.pulsecond[self.port]: @@ -308,7 +310,8 @@ def start(self, speed=None): raise MotorError("Invalid Speed") cmd = f"port {self.port} ; set {speed}\r" if self._runmode == MotorRunmode.NONE: - cmd = (f"port {self.port} ; combi 0 {self._combi} ; select 0 ; pid {self.port} 0 0 s1 1 0 0.003 0.01 0 100; " + cmd = (f"port {self.port} ; combi 0 {self._combi} ; select 0 ; selrate {self._interval}; " + f"pid {self.port} 0 0 s1 1 0 0.003 0.01 0 100; " f"set {speed}\r") self._runmode = MotorRunmode.FREE self._currentspeed = speed diff --git a/test/motors.py b/test/motors.py index 2ae6d74..70f388a 100644 --- a/test/motors.py +++ b/test/motors.py @@ -81,6 +81,23 @@ def handle_motor(speed, pos, apos): m.run_for_seconds(1) self.assertGreater(handle_motor.evt, 0) + def test_callback_interval(self): + """Test setting callback and interval""" + m = Motor('A') + m.interval = 10 + + def handle_motor(speed, pos, apos): + handle_motor.evt += 1 + handle_motor.evt = 0 + m.when_rotated = handle_motor + m.run_for_seconds(5) + self.assertGreater(handle_motor.evt, 0.8 * ((1 / ((m.interval) * 1e-3)) * 5)) + + handle_motor.evt = 0 + m.interval = 5 + m.run_for_seconds(5) + self.assertGreater(handle_motor.evt, 0.8 * ((1 / ((m.interval) * 1e-3)) * 5)) + def test_none_callback(self): """Test setting empty callback""" m = Motor('A') From dd6264ca2e38e49ef5f20708e7d766d967b4d959 Mon Sep 17 00:00:00 2001 From: chrisruk Date: Thu, 15 Dec 2022 12:04:27 +0000 Subject: [PATCH 17/50] New firmware with selrate and mitigation for motor disconnecting --- buildhat/data/firmware.bin | Bin 53768 -> 54168 bytes buildhat/data/signature.bin | 2 +- buildhat/data/version | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/buildhat/data/firmware.bin b/buildhat/data/firmware.bin index 9e9f922ecce254d54b2224fec15654a150f87dd8..3d1cdb279e6e4d1e208fecabdda50aa65ce2e45d 100644 GIT binary patch delta 20768 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 GwEqv*=3RsU delta 20416 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ۼ*c=Y옢3#lw!HwFZ2 y49d鱾o \ No newline at end of file diff --git a/buildhat/data/version b/buildhat/data/version index f8cd87b..a2e93cb 100644 --- a/buildhat/data/version +++ b/buildhat/data/version @@ -1 +1 @@ -1670332907 +1670596313 From 74d764428a6a774a4b7dcbadf4d0042c6e6c21c4 Mon Sep 17 00:00:00 2001 From: chrisruk Date: Thu, 15 Dec 2022 12:10:35 +0000 Subject: [PATCH 18/50] Bump version and add to changelog --- CHANGELOG.md | 14 ++++++++++++++ VERSION | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f9eabf..e070036 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Change Log +## 0.5.12 + +### Added + +* Ability to set custom device interval for callbacks + +### Changed + +* New firmware with mitigation for motor disconnecting and selrate command + +### Removed + +n/a + ## 0.5.11 ### Added diff --git a/VERSION b/VERSION index 69626fb..9d6c175 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.11 +0.5.12 From c06bea7dee3867d7a35b37744fcb7f8c755591e9 Mon Sep 17 00:00:00 2001 From: chrisruk Date: Thu, 2 Feb 2023 16:26:36 +0000 Subject: [PATCH 19/50] Test ordering of motor run_to_position commands (#176) * Test ordering of motor run_to_position commands * Restructure to use Future * Use Future to obtain voltage of build HAT * Support multiple non-blocking commands simultaneously * Enable 'del' to function correctly * Bump threshold temporarily * Refactor variable names --- buildhat/devices.py | 8 +++--- buildhat/hat.py | 9 ++++--- buildhat/motors.py | 26 +++++++++--------- buildhat/serinterface.py | 58 ++++++++++++++++++++++++++++------------ test/motors.py | 54 +++++++++++++++++++++++++++++++++++-- 5 files changed, 115 insertions(+), 40 deletions(-) diff --git a/buildhat/devices.py b/buildhat/devices.py index 2f14453..df94983 100644 --- a/buildhat/devices.py +++ b/buildhat/devices.py @@ -3,6 +3,7 @@ import os import sys import weakref +from concurrent.futures import Future from .exc import DeviceError from .serinterface import BuildHAT @@ -202,11 +203,10 @@ def get(self): idx = self._combimode else: raise DeviceError("Not in simple or combimode") + ftr = Future() + self._hat.portftr[self.port].append(ftr) self._write(f"port {self.port} ; selonce {idx}\r") - # wait for data - with Device._instance.portcond[self.port]: - Device._instance.portcond[self.port].wait() - return self._conn.data + return ftr.result() def mode(self, modev): """Set combimode or simple mode diff --git a/buildhat/hat.py b/buildhat/hat.py index 782a87f..395a3d6 100644 --- a/buildhat/hat.py +++ b/buildhat/hat.py @@ -1,5 +1,7 @@ """HAT handling functionality""" +from concurrent.futures import Future + from .devices import Device @@ -45,11 +47,10 @@ def get_vin(self): :return: Voltage on the input power jack :rtype: float """ + ftr = Future() + Device._instance.vinftr.append(ftr) Device._instance.write(b"vin\r") - with Device._instance.vincond: - Device._instance.vincond.wait() - - return Device._instance.vin + return ftr.result() def _set_led(self, intmode): if isinstance(intmode, int) and intmode >= -1 and intmode <= 3: diff --git a/buildhat/motors.py b/buildhat/motors.py index 2a457e4..fb18355 100644 --- a/buildhat/motors.py +++ b/buildhat/motors.py @@ -3,6 +3,7 @@ import threading import time from collections import deque +from concurrent.futures import Future from enum import Enum from threading import Condition @@ -205,9 +206,10 @@ def _run_positional_ramp(self, pos, newpos, speed): cmd = (f"port {self.port}; combi 0 {self._combi} ; select 0 ; selrate {self._interval}; " f"pid {self.port} 0 1 s4 0.0027777778 0 5 0 .1 3; " f"set ramp {pos} {newpos} {dur} 0\r") + ftr = Future() + self._hat.rampftr[self.port].append(ftr) self._write(cmd) - with self._hat.rampcond[self.port]: - self._hat.rampcond[self.port].wait() + ftr.result() if self._release: time.sleep(0.2) self.coast() @@ -228,9 +230,7 @@ def run_for_degrees(self, degrees, speed=None, blocking=True): if not (speed >= -100 and speed <= 100): raise MotorError("Invalid Speed") if not blocking: - th = threading.Thread(target=self._run_for_degrees, args=(degrees, speed)) - th.daemon = True - th.start() + self._queue((self._run_for_degrees, (degrees, speed))) else: self._run_for_degrees(degrees, speed) @@ -251,9 +251,7 @@ def run_to_position(self, degrees, speed=None, blocking=True, direction="shortes if degrees < -180 or degrees > 180: raise MotorError("Invalid angle") if not blocking: - th = threading.Thread(target=self._run_to_position, args=(degrees, speed, direction)) - th.daemon = True - th.start() + self._queue((self._run_to_position, (degrees, speed, direction))) else: self._run_to_position(degrees, speed, direction) @@ -262,9 +260,10 @@ def _run_for_seconds(self, seconds, speed): cmd = (f"port {self.port} ; combi 0 {self._combi} ; select 0 ; selrate {self._interval}; " f"pid {self.port} 0 0 s1 1 0 0.003 0.01 0 100; " f"set pulse {speed} 0.0 {seconds} 0\r") + ftr = Future() + self._hat.pulseftr[self.port].append(ftr) self._write(cmd) - with self._hat.pulsecond[self.port]: - self._hat.pulsecond[self.port].wait() + ftr.result() if self._release: self.coast() self._runmode = MotorRunmode.NONE @@ -283,9 +282,7 @@ def run_for_seconds(self, seconds, speed=None, blocking=True): if not (speed >= -100 and speed <= 100): raise MotorError("Invalid Speed") if not blocking: - th = threading.Thread(target=self._run_for_seconds, args=(seconds, speed)) - th.daemon = True - th.start() + self._queue((self._run_for_seconds, (seconds, speed))) else: self._run_for_seconds(seconds, speed) @@ -444,6 +441,9 @@ def release(self, value): raise MotorError("Must pass boolean") self._release = value + def _queue(self, cmd): + Device._instance.motorqueue[self.port].put(cmd) + class MotorPair: """Pair of motors diff --git a/buildhat/serinterface.py b/buildhat/serinterface.py index b46b3ce..d134b2b 100644 --- a/buildhat/serinterface.py +++ b/buildhat/serinterface.py @@ -84,13 +84,13 @@ def __init__(self, firmware, signature, version, device="/dev/serial0", debug=Fa self.cond = Condition() self.state = HatState.OTHER self.connections = [] - self.portcond = [] - self.pulsecond = [] - self.rampcond = [] + self.portftr = [] + self.pulseftr = [] + self.rampftr = [] + self.vinftr = [] + self.motorqueue = [] self.fin = False self.running = True - self.vincond = Condition() - self.vin = None if debug: tmp = tempfile.NamedTemporaryFile(suffix=".log", prefix="buildhat-", delete=False) logging.basicConfig(filename=tmp.name, format='%(asctime)s %(message)s', @@ -98,9 +98,10 @@ def __init__(self, firmware, signature, version, device="/dev/serial0", debug=Fa for _ in range(4): self.connections.append(Connection()) - self.portcond.append(Condition()) - self.pulsecond.append(Condition()) - self.rampcond.append(Condition()) + self.portftr.append([]) + self.pulseftr.append([]) + self.rampftr.append([]) + self.motorqueue.append(queue.Queue()) self.ser = serial.Serial(device, 115200, timeout=5) # Check if we're in the bootloader or the firmware @@ -150,6 +151,11 @@ def __init__(self, firmware, signature, version, device="/dev/serial0", debug=Fa self.cb.daemon = True self.cb.start() + for q in self.motorqueue: + ml = threading.Thread(target=self.motorloop, args=(q,)) + ml.daemon = True + ml.start() + # Drop timeout value to 1s listevt = threading.Event() self.ser.timeout = 1 @@ -266,6 +272,8 @@ def shutdown(self): self.running = False self.th.join() self.cbqueue.put(()) + for q in self.motorqueue: + q.put((None, None)) self.cb.join() turnoff = "" for p in range(4): @@ -278,6 +286,20 @@ def shutdown(self): self.write(f"{turnoff}\r".encode()) self.write(b"port 0 ; select ; port 1 ; select ; port 2 ; select ; port 3 ; select ; echo 0\r") + def motorloop(self, q): + """Event handling for non-blocking motor commands + + :param q: Queue of motor functions + """ + while self.running: + func, data = q.get() + if func is None: + break + else: + func(*data) + func = None # Necessary for 'del' to function correctly on motor object + data = None + def callbackloop(self, q): """Event handling for callbacks @@ -330,11 +352,11 @@ def loop(self, cond, uselist, q, listevt): if uselist and listevt.is_set(): count += 1 elif cmp(msg, BuildHAT.RAMPDONE): - with self.rampcond[portid]: - self.rampcond[portid].notify() + ftr = self.rampftr[portid].pop() + ftr.set_result(True) elif cmp(msg, BuildHAT.PULSEDONE): - with self.pulsecond[portid]: - self.pulsecond[portid].notify() + ftr = self.pulseftr[portid].pop() + ftr.set_result(True) if uselist and count == 4: with cond: @@ -362,11 +384,13 @@ def runit(): if callit is not None: q.put((callit, newdata)) self.connections[portid].data = newdata - with self.portcond[portid]: - self.portcond[portid].notify() + try: + ftr = self.portftr[portid].pop() + ftr.set_result(newdata) + except IndexError: + pass if len(line) >= 5 and line[1] == "." and line.endswith(" V"): vin = float(line.split(" ")[0]) - self.vin = vin - with self.vincond: - self.vincond.notify() + ftr = self.vinftr.pop() + ftr.set_result(vin) diff --git a/test/motors.py b/test/motors.py index 70f388a..b6fa87e 100644 --- a/test/motors.py +++ b/test/motors.py @@ -10,6 +10,8 @@ class TestMotor(unittest.TestCase): """Test motors""" + THRESHOLD_DISTANCE = 15 + def test_rotations(self): """Test motor rotating""" m = Motor('A') @@ -19,18 +21,66 @@ def test_rotations(self): rotated = (pos2 - pos1) / 360 self.assertLess(abs(rotated - 2), 0.5) + def test_nonblocking(self): + """Test motor nonblocking mode""" + m = Motor('A') + last = 0 + for delay in [1, 0]: + for _ in range(3): + m.run_to_position(90, blocking=False) + time.sleep(delay) + m.run_to_position(90, blocking=False) + time.sleep(delay) + m.run_to_position(90, blocking=False) + time.sleep(delay) + m.run_to_position(last, blocking=False) + time.sleep(delay) + # Wait for a bit, before reading last position + time.sleep(7) + pos1 = m.get_aposition() + diff = abs((last - pos1 + 180) % 360 - 180) + self.assertLess(diff, self.THRESHOLD_DISTANCE) + + def test_nonblocking_multiple(self): + """Test motor nonblocking mode""" + m1 = Motor('A') + m2 = Motor('B') + last = 0 + for delay in [1, 0]: + for _ in range(3): + m1.run_to_position(90, blocking=False) + m2.run_to_position(90, blocking=False) + time.sleep(delay) + m1.run_to_position(90, blocking=False) + m2.run_to_position(90, blocking=False) + time.sleep(delay) + m1.run_to_position(90, blocking=False) + m2.run_to_position(90, blocking=False) + time.sleep(delay) + m1.run_to_position(last, blocking=False) + m2.run_to_position(last, blocking=False) + time.sleep(delay) + # Wait for a bit, before reading last position + time.sleep(7) + pos1 = m1.get_aposition() + diff = abs((last - pos1 + 180) % 360 - 180) + self.assertLess(diff, self.THRESHOLD_DISTANCE) + pos2 = m2.get_aposition() + diff = abs((last - pos2 + 180) % 360 - 180) + self.assertLess(diff, self.THRESHOLD_DISTANCE) + def test_position(self): """Test motor goes to desired position""" m = Motor('A') m.run_to_position(0) pos1 = m.get_aposition() diff = abs((0 - pos1 + 180) % 360 - 180) - self.assertLess(diff, 10) + self.assertLess(diff, self.THRESHOLD_DISTANCE) m.run_to_position(180) pos1 = m.get_aposition() diff = abs((180 - pos1 + 180) % 360 - 180) - self.assertLess(diff, 10) + self.assertLess(diff, self.THRESHOLD_DISTANCE) def test_time(self): """Test motor runs for correct duration""" From 68e52938c8db54a127149f32bb360afbbe4a0c09 Mon Sep 17 00:00:00 2001 From: chrisruk Date: Thu, 2 Feb 2023 16:31:05 +0000 Subject: [PATCH 20/50] Use set rather than plimit for train light control (#188) * Use set rather than plimit for train light control * Use coast to switch off LEDs --- buildhat/light.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/buildhat/light.py b/buildhat/light.py index 7472f5c..1748e55 100644 --- a/buildhat/light.py +++ b/buildhat/light.py @@ -30,4 +30,12 @@ def brightness(self, brightness): """ if not (brightness >= 0 and brightness <= 100): raise LightError("Need brightness arg, of 0 to 100") - self._write(f"port {self.port} ; on ; plimit {brightness / 100.0}\r") + if brightness > 0: + self._write(f"port {self.port} ; on ; set {brightness / 100.0}\r") + else: + self.off() + + def off(self): + """Turn off lights""" + # Using coast to turn off DIY lights completely + self._write(f"port {self.port} ; coast\r") From a6ac1fbffcc94ec938c7e0383a4d5f5e8f7925d2 Mon Sep 17 00:00:00 2001 From: chrisruk Date: Fri, 3 Feb 2023 12:27:45 +0000 Subject: [PATCH 21/50] Ensure mixed blocking/nonblocking functions work together (#189) --- buildhat/motors.py | 9 +++++++++ buildhat/serinterface.py | 1 + test/motors.py | 14 ++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/buildhat/motors.py b/buildhat/motors.py index fb18355..9af22e7 100644 --- a/buildhat/motors.py +++ b/buildhat/motors.py @@ -232,6 +232,7 @@ def run_for_degrees(self, degrees, speed=None, blocking=True): if not blocking: self._queue((self._run_for_degrees, (degrees, speed))) else: + self._wait_for_nonblocking() self._run_for_degrees(degrees, speed) def run_to_position(self, degrees, speed=None, blocking=True, direction="shortest"): @@ -253,6 +254,7 @@ def run_to_position(self, degrees, speed=None, blocking=True, direction="shortes if not blocking: self._queue((self._run_to_position, (degrees, speed, direction))) else: + self._wait_for_nonblocking() self._run_to_position(degrees, speed, direction) def _run_for_seconds(self, seconds, speed): @@ -284,6 +286,7 @@ def run_for_seconds(self, seconds, speed=None, blocking=True): if not blocking: self._queue((self._run_for_seconds, (seconds, speed))) else: + self._wait_for_nonblocking() self._run_for_seconds(seconds, speed) def start(self, speed=None): @@ -292,6 +295,7 @@ def start(self, speed=None): :param speed: Speed ranging from -100 to 100 :raises MotorError: Occurs when invalid speed specified """ + self._wait_for_nonblocking() if self._runmode == MotorRunmode.FREE: if self._currentspeed == speed: # Already running at this speed, do nothing @@ -316,6 +320,7 @@ def start(self, speed=None): def stop(self): """Stop motor""" + self._wait_for_nonblocking() self._runmode = MotorRunmode.NONE self._currentspeed = 0 self.coast() @@ -444,6 +449,10 @@ def release(self, value): def _queue(self, cmd): Device._instance.motorqueue[self.port].put(cmd) + def _wait_for_nonblocking(self): + """Wait for nonblocking commands to finish""" + Device._instance.motorqueue[self.port].join() + class MotorPair: """Pair of motors diff --git a/buildhat/serinterface.py b/buildhat/serinterface.py index d134b2b..77d1a71 100644 --- a/buildhat/serinterface.py +++ b/buildhat/serinterface.py @@ -299,6 +299,7 @@ def motorloop(self, q): func(*data) func = None # Necessary for 'del' to function correctly on motor object data = None + q.task_done() def callbackloop(self, q): """Event handling for callbacks diff --git a/test/motors.py b/test/motors.py index b6fa87e..865b9ce 100644 --- a/test/motors.py +++ b/test/motors.py @@ -69,6 +69,20 @@ def test_nonblocking_multiple(self): diff = abs((last - pos2 + 180) % 360 - 180) self.assertLess(diff, self.THRESHOLD_DISTANCE) + def test_nonblocking_mixed(self): + """Test motor nonblocking mode mixed with blocking mode""" + m = Motor('A') + m.run_for_seconds(5, blocking=False) + m.run_for_degrees(360) + m.run_for_seconds(5, blocking=False) + m.run_to_position(180) + m.run_for_seconds(5, blocking=False) + m.run_for_seconds(5) + m.run_for_seconds(5, blocking=False) + m.start() + m.run_for_seconds(5, blocking=False) + m.stop() + def test_position(self): """Test motor goes to desired position""" m = Motor('A') From 9c1dbb97e34ff45f6e7e718ee37fee16f382378a Mon Sep 17 00:00:00 2001 From: chrisruk Date: Fri, 24 Feb 2023 12:34:24 +0000 Subject: [PATCH 22/50] Improve sensor interval (#192) * Initial rate improvements * Add test for color sensor * Add test for motors at low intervals * Reduce default interval to 10ms to give 100Hz of data readings * Add toggle to position/speed This is so that we don't end up with a busy loop, which interfers with processing serial data from another thread. I think if there wasn't a GIL this problem might not exist. * Dont need to resend combi setting --- buildhat/devices.py | 29 ++++++++++++++++--------- buildhat/motors.py | 6 ++--- buildhat/serinterface.py | 7 ++++++ test/color.py | 47 ++++++++++++++++++++++++++++++++++++++++ test/motors.py | 42 ++++++++++++++++++++++++++++++++--- 5 files changed, 115 insertions(+), 16 deletions(-) create mode 100644 test/color.py diff --git a/buildhat/devices.py b/buildhat/devices.py index df94983..e8bf086 100644 --- a/buildhat/devices.py +++ b/buildhat/devices.py @@ -58,8 +58,9 @@ def __init__(self, port): Device._setup() self._simplemode = -1 self._combimode = -1 + self._modestr = "" self._typeid = self._conn.typeid - self._interval = 100 + self._interval = 10 if ( self._typeid in Device._device_names and Device._device_names[self._typeid][0] != type(self).__name__ # noqa: W503 @@ -196,16 +197,10 @@ def get(self): :raises DeviceError: Occurs if device not in valid mode """ self.isconnected() - idx = -1 - if self._simplemode != -1: - idx = self._simplemode - elif self._combimode != -1: - idx = self._combimode - else: + if self._simplemode == -1 and self._combimode == -1: raise DeviceError("Not in simple or combimode") ftr = Future() self._hat.portftr[self.port].append(ftr) - self._write(f"port {self.port} ; selonce {idx}\r") return ftr.result() def mode(self, modev): @@ -215,18 +210,32 @@ def mode(self, modev): """ self.isconnected() if isinstance(modev, list): - self._combimode = 0 modestr = "" for t in modev: modestr += f"{t[0]} {t[1]} " - self._write(f"port {self.port} ; combi {self._combimode} {modestr}\r") + if self._simplemode == -1 and self._combimode == 0 and self._modestr == modestr: + return + self._write(f"port {self.port}; select\r") + self._combimode = 0 + self._write((f"port {self.port} ; combi {self._combimode} {modestr} ; " + f"select {self._combimode} ; " + f"selrate {self._interval}\r")) self._simplemode = -1 + self._modestr = modestr + self._conn.combimode = 0 + self._conn.simplemode = -1 else: + if self._combimode == -1 and self._simplemode == int(modev): + return # Remove combi mode if self._combimode != -1: self._write(f"port {self.port} ; combi {self._combimode}\r") + self._write(f"port {self.port}; select\r") self._combimode = -1 self._simplemode = int(modev) + self._write(f"port {self.port} ; select {int(modev)} ; selrate {self._interval}\r") + self._conn.combimode = -1 + self._conn.simplemode = int(modev) def select(self): """Request data from mode diff --git a/buildhat/motors.py b/buildhat/motors.py index 9af22e7..0027fff 100644 --- a/buildhat/motors.py +++ b/buildhat/motors.py @@ -203,7 +203,7 @@ def _run_positional_ramp(self, pos, newpos, speed): # Collapse speed range to -5 to 5 speed *= 0.05 dur = abs((newpos - pos) / speed) - cmd = (f"port {self.port}; combi 0 {self._combi} ; select 0 ; selrate {self._interval}; " + cmd = (f"port {self.port}; select 0 ; selrate {self._interval}; " f"pid {self.port} 0 1 s4 0.0027777778 0 5 0 .1 3; " f"set ramp {pos} {newpos} {dur} 0\r") ftr = Future() @@ -259,7 +259,7 @@ def run_to_position(self, degrees, speed=None, blocking=True, direction="shortes def _run_for_seconds(self, seconds, speed): self._runmode = MotorRunmode.SECONDS - cmd = (f"port {self.port} ; combi 0 {self._combi} ; select 0 ; selrate {self._interval}; " + cmd = (f"port {self.port} ; select 0 ; selrate {self._interval}; " f"pid {self.port} 0 0 s1 1 0 0.003 0.01 0 100; " f"set pulse {speed} 0.0 {seconds} 0\r") ftr = Future() @@ -311,7 +311,7 @@ def start(self, speed=None): raise MotorError("Invalid Speed") cmd = f"port {self.port} ; set {speed}\r" if self._runmode == MotorRunmode.NONE: - cmd = (f"port {self.port} ; combi 0 {self._combi} ; select 0 ; selrate {self._interval}; " + cmd = (f"port {self.port} ; select 0 ; selrate {self._interval}; " f"pid {self.port} 0 0 s1 1 0 0.003 0.01 0 100; " f"set {speed}\r") self._runmode = MotorRunmode.FREE diff --git a/buildhat/serinterface.py b/buildhat/serinterface.py index 77d1a71..af1ab1b 100644 --- a/buildhat/serinterface.py +++ b/buildhat/serinterface.py @@ -31,6 +31,8 @@ def __init__(self): self.typeid = -1 self.connected = False self.callit = None + self.simplemode = -1 + self.combimode = -1 def update(self, typeid, connected, callit=None): """Update connection information for port @@ -381,6 +383,11 @@ def runit(): else: if d != "": newdata.append(int(d)) + # Check data was for our current mode + if line[2] == "M" and self.connections[portid].simplemode != int(line[3]): + continue + elif line[2] == "C" and self.connections[portid].combimode != int(line[3]): + continue callit = self.connections[portid].callit if callit is not None: q.put((callit, newdata)) diff --git a/test/color.py b/test/color.py new file mode 100644 index 0000000..688e23c --- /dev/null +++ b/test/color.py @@ -0,0 +1,47 @@ +"""Test Color Sensor functionality""" +import time +import unittest + +from buildhat import ColorSensor + + +class TestColor(unittest.TestCase): + """Test color sensor functions""" + + def test_color_interval(self): + """Test color sensor interval""" + color = ColorSensor('A') + color.avg_reads = 1 + color.interval = 10 + count = 1000 + expected_dur = count * color.interval * 1e-3 + + start = time.time() + for _ in range(count): + color.get_ambient_light() + end = time.time() + diff = abs((end - start) - expected_dur) + self.assertLess(diff, 0.25) + + start = time.time() + for _ in range(count): + color.get_color_rgbi() + end = time.time() + diff = abs((end - start) - expected_dur) + self.assertLess(diff, 0.25) + + def test_caching(self): + """Test to make sure we're not reading cached data""" + color = ColorSensor('A') + color.avg_reads = 1 + color.interval = 1 + + for _ in range(100): + color.mode(2) + self.assertEqual(len(color.get()), 1) + color.mode(5) + self.assertEqual(len(color.get()), 4) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/motors.py b/test/motors.py index 865b9ce..fad9c65 100644 --- a/test/motors.py +++ b/test/motors.py @@ -185,22 +185,28 @@ def test_continuous_start(self): """Test starting motor for 5mins""" t = time.time() + (60 * 5) m = Motor('A') + toggle = 0 while time.time() < t: - m.start(0) + m.start(toggle) + toggle ^= 1 def test_continuous_degrees(self): """Test setting degrees for 5mins""" t = time.time() + (60 * 5) m = Motor('A') + toggle = 0 while time.time() < t: - m.run_for_degrees(0) + m.run_for_degrees(toggle) + toggle ^= 1 def test_continuous_position(self): """Test setting position of motor for 5mins""" t = time.time() + (60 * 5) m = Motor('A') + toggle = 0 while time.time() < t: - m.run_to_position(0) + m.run_to_position(toggle) + toggle ^= 1 def test_continuous_feedback(self): """Test feedback of motor for 30mins""" @@ -211,6 +217,36 @@ def test_continuous_feedback(self): while time.time() < t: _ = (m.get_speed(), m.get_position(), m.get_aposition()) + def test_interval(self): + """Test motor interval""" + m = Motor('A') + m.interval = 10 + count = 1000 + expected_dur = count * m.interval * 1e-3 + start = time.time() + for _ in range(count): + m.get_position() + end = time.time() + diff = abs((end - start) - expected_dur) + self.assertLess(diff, expected_dur * 0.1) + + def test_dual_interval(self): + """Test dual motor interval""" + m1 = Motor('A') + m2 = Motor('B') + for interval in [10, 5]: + m1.interval = interval + m2.interval = interval + count = 1000 + expected_dur = count * m1.interval * 1e-3 + start = time.time() + for _ in range(count): + m1.get_position() + m2.get_position() + end = time.time() + diff = abs((end - start) - expected_dur) + self.assertLess(diff, expected_dur * 0.1) + if __name__ == '__main__': unittest.main() From 1ac33cf50fb5849187eadff41e2e1c3f28404a16 Mon Sep 17 00:00:00 2001 From: chrisruk Date: Fri, 31 Mar 2023 12:23:56 +0100 Subject: [PATCH 23/50] New firmware support (#190) * New firmware * Changes for new PID * Remove bias test, as no longer exists * Update pwm params * Test down to 10ms interval * Lower speed to improve positioning accuracy --- buildhat/data/firmware.bin | Bin 54168 -> 54360 bytes buildhat/data/signature.bin | 2 +- buildhat/data/version | 2 +- buildhat/motors.py | 77 +++++++++++++++++++++++++++++------- test/motors.py | 17 ++------ 5 files changed, 69 insertions(+), 29 deletions(-) diff --git a/buildhat/data/firmware.bin b/buildhat/data/firmware.bin index 3d1cdb279e6e4d1e208fecabdda50aa65ce2e45d..64358ee52512bf324a5201140895c78b0a7f6556 100644 GIT binary patch delta 26705 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 H7mWV{TfuoU delta 26359 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 diff --git a/buildhat/data/signature.bin b/buildhat/data/signature.bin index 364b0d4..27ef00d 100644 --- a/buildhat/data/signature.bin +++ b/buildhat/data/signature.bin @@ -1 +1 @@ -ktCr>_iۼ*c=Y옢3#lw!HwFZ2 y49d鱾o \ No newline at end of file +Q#E]]-.T~駶e[yEV}A5L}A$I!u9Nzw`l@eK;{E \ No newline at end of file diff --git a/buildhat/data/version b/buildhat/data/version index a2e93cb..e9bf96c 100644 --- a/buildhat/data/version +++ b/buildhat/data/version @@ -1 +1 @@ -1670596313 +1674818421 diff --git a/buildhat/motors.py b/buildhat/motors.py index 0027fff..102a159 100644 --- a/buildhat/motors.py +++ b/buildhat/motors.py @@ -27,7 +27,6 @@ def __init__(self, port): self._default_speed = 20 self._currentspeed = 0 self.plimit(0.7) - self.bias(0.3) def set_default_speed(self, default_speed): """Set the default speed of the motor @@ -79,10 +78,10 @@ def bias(self, bias): :param bias: Value 0 to 1 :raises MotorError: Occurs if invalid bias value passed - """ - if not (bias >= 0 and bias <= 1): - raise MotorError("bias should be 0 to 1") - self._write(f"port {self.port} ; bias {bias}\r") + + .. deprecated:: 0.6.0 + """ # noqa: RST303 + raise MotorError("Bias no longer available") class MotorRunmode(Enum): @@ -118,7 +117,8 @@ def __init__(self, port): self._combi = "1 0 2 0 3 0" self._noapos = False self.plimit(0.7) - self.bias(0.3) + self.pwmparams(0.65, 0.01) + self._rpm = False self._release = True self._bqueue = deque(maxlen=5) self._cvqueue = Condition() @@ -126,6 +126,13 @@ def __init__(self, port): self._oldpos = None self._runmode = MotorRunmode.NONE + def set_speed_unit_rpm(self, rpm=False): + """Set whether to use RPM for speed units or not + + :param rpm: Boolean to determine whether to use RPM for units + """ + self._rpm = rpm + def set_default_speed(self, default_speed): """Set the default speed of the motor @@ -200,11 +207,13 @@ def _run_positional_ramp(self, pos, newpos, speed): :param newpos: New motor postion in decimal rotations (from preset position) :param speed: -100 to 100 """ - # Collapse speed range to -5 to 5 - speed *= 0.05 + if self._rpm: + speed = self._speed_process(speed) + else: + speed *= 0.05 # Collapse speed range to -5 to 5 dur = abs((newpos - pos) / speed) cmd = (f"port {self.port}; select 0 ; selrate {self._interval}; " - f"pid {self.port} 0 1 s4 0.0027777778 0 5 0 .1 3; " + f"pid {self.port} 0 1 s4 0.0027777778 0 5 0 .1 3 0.01; " f"set ramp {pos} {newpos} {dur} 0\r") ftr = Future() self._hat.rampftr[self.port].append(ftr) @@ -258,9 +267,14 @@ def run_to_position(self, degrees, speed=None, blocking=True, direction="shortes self._run_to_position(degrees, speed, direction) def _run_for_seconds(self, seconds, speed): + speed = self._speed_process(speed) self._runmode = MotorRunmode.SECONDS + if self._rpm: + pid = f"pid_diff {self.port} 0 5 s2 0.0027777778 1 0 2.5 0 .4 0.01; " + else: + pid = f"pid {self.port} 0 0 s1 1 0 0.003 0.01 0 100 0.01;" cmd = (f"port {self.port} ; select 0 ; selrate {self._interval}; " - f"pid {self.port} 0 0 s1 1 0 0.003 0.01 0 100; " + f"{pid}" f"set pulse {speed} 0.0 {seconds} 0\r") ftr = Future() self._hat.pulseftr[self.port].append(ftr) @@ -309,10 +323,15 @@ def start(self, speed=None): else: if not (speed >= -100 and speed <= 100): raise MotorError("Invalid Speed") + speed = self._speed_process(speed) cmd = f"port {self.port} ; set {speed}\r" if self._runmode == MotorRunmode.NONE: + if self._rpm: + pid = f"pid_diff {self.port} 0 5 s2 0.0027777778 1 0 2.5 0 .4 0.01; " + else: + pid = f"pid {self.port} 0 0 s1 1 0 0.003 0.01 0 100 0.01; " cmd = (f"port {self.port} ; select 0 ; selrate {self._interval}; " - f"pid {self.port} 0 0 s1 1 0 0.003 0.01 0 100; " + f"{pid}" f"set {speed}\r") self._runmode = MotorRunmode.FREE self._currentspeed = speed @@ -401,10 +420,23 @@ def bias(self, bias): :param bias: Value 0 to 1 :raises MotorError: Occurs if invalid bias value passed + + .. deprecated:: 0.6.0 + """ # noqa: RST303 + raise MotorError("Bias no longer available") + + def pwmparams(self, pwmthresh, minpwm): + """PWM thresholds + + :param pwmthresh: Value 0 to 1, threshold below, will switch from fast to slow, PWM + :param minpwm: Value 0 to 1, threshold below which it switches off the drive altogether + :raises MotorError: Occurs if invalid values are passed """ - if not (bias >= 0 and bias <= 1): - raise MotorError("bias should be 0 to 1") - self._write(f"port {self.port} ; bias {bias}\r") + if not (pwmthresh >= 0 and pwmthresh <= 1): + raise MotorError("pwmthresh should be 0 to 1") + if not (minpwm >= 0 and minpwm <= 1): + raise MotorError("minpwm should be 0 to 1") + self._write(f"port {self.port} ; pwmparams {pwmthresh} {minpwm}\r") def pwm(self, pwmv): """PWM motor @@ -453,6 +485,13 @@ def _wait_for_nonblocking(self): """Wait for nonblocking commands to finish""" Device._instance.motorqueue[self.port].join() + def _speed_process(self, speed): + """Lower speed value""" + if self._rpm: + return speed / 60 + else: + return speed + class MotorPair: """Pair of motors @@ -473,6 +512,7 @@ def __init__(self, leftport, rightport): self._rightmotor = Motor(rightport) self.default_speed = 20 self._release = True + self._rpm = False def set_default_speed(self, default_speed): """Set the default speed of the motor @@ -481,6 +521,15 @@ def set_default_speed(self, default_speed): """ self.default_speed = default_speed + def set_speed_unit_rpm(self, rpm=False): + """Set whether to use RPM for speed units or not + + :param rpm: Boolean to determine whether to use RPM for units + """ + self._rpm = rpm + self._leftmotor.set_speed_unit_rpm(rpm) + self._rightmotor.set_speed_unit_rpm(rpm) + def run_for_rotations(self, rotations, speedl=None, speedr=None): """Run pair of motors for N rotations diff --git a/test/motors.py b/test/motors.py index fad9c65..46d01ec 100644 --- a/test/motors.py +++ b/test/motors.py @@ -24,6 +24,7 @@ def test_rotations(self): def test_nonblocking(self): """Test motor nonblocking mode""" m = Motor('A') + m.set_default_speed(10) last = 0 for delay in [1, 0]: for _ in range(3): @@ -44,7 +45,9 @@ def test_nonblocking(self): def test_nonblocking_multiple(self): """Test motor nonblocking mode""" m1 = Motor('A') + m1.set_default_speed(10) m2 = Motor('B') + m2.set_default_speed(10) last = 0 for delay in [1, 0]: for _ in range(3): @@ -118,13 +121,6 @@ def test_plimit(self): self.assertRaises(MotorError, m.plimit, -1) self.assertRaises(MotorError, m.plimit, 2) - def test_bias(self): - """Test setting motor bias""" - m = Motor('A') - m.bias(0.5) - self.assertRaises(MotorError, m.bias, -1) - self.assertRaises(MotorError, m.bias, 2) - def test_pwm(self): """Test PWMing motor""" m = Motor('A') @@ -157,11 +153,6 @@ def handle_motor(speed, pos, apos): m.run_for_seconds(5) self.assertGreater(handle_motor.evt, 0.8 * ((1 / ((m.interval) * 1e-3)) * 5)) - handle_motor.evt = 0 - m.interval = 5 - m.run_for_seconds(5) - self.assertGreater(handle_motor.evt, 0.8 * ((1 / ((m.interval) * 1e-3)) * 5)) - def test_none_callback(self): """Test setting empty callback""" m = Motor('A') @@ -234,7 +225,7 @@ def test_dual_interval(self): """Test dual motor interval""" m1 = Motor('A') m2 = Motor('B') - for interval in [10, 5]: + for interval in [20, 10]: m1.interval = interval m2.interval = interval count = 1000 From eeffd9d7b75cc2a4826095016b8eb2ad1557a46b Mon Sep 17 00:00:00 2001 From: chrisruk Date: Fri, 31 Mar 2023 14:56:41 +0100 Subject: [PATCH 24/50] Per port power limiting (#193) --- buildhat/color.py | 2 +- buildhat/colordistance.py | 2 +- buildhat/devices.py | 4 ++-- buildhat/motors.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/buildhat/color.py b/buildhat/color.py index edf6030..5959263 100644 --- a/buildhat/color.py +++ b/buildhat/color.py @@ -210,4 +210,4 @@ def wait_for_new_color(self): def on(self): """Turn on the sensor and LED""" - self._write(f"port {self.port} ; plimit 1 ; set -1\r") + self.reverse() diff --git a/buildhat/colordistance.py b/buildhat/colordistance.py index da85c3a..3447e17 100644 --- a/buildhat/colordistance.py +++ b/buildhat/colordistance.py @@ -199,4 +199,4 @@ def wait_for_new_color(self): def on(self): """Turn on the sensor and LED""" - self._write(f"port {self.port} ; plimit 1 ; set -1\r") + self.reverse() diff --git a/buildhat/devices.py b/buildhat/devices.py index e8bf086..0db16b1 100644 --- a/buildhat/devices.py +++ b/buildhat/devices.py @@ -188,7 +188,7 @@ def isconnected(self): def reverse(self): """Reverse polarity""" - self._write(f"port {self.port} ; plimit 1 ; set -1\r") + self._write(f"port {self.port} ; port_plimit 1 ; set -1\r") def get(self): """Extract information from device @@ -253,7 +253,7 @@ def select(self): def on(self): """Turn on sensor""" - self._write(f"port {self.port} ; plimit 1 ; on\r") + self._write(f"port {self.port} ; port_plimit 1 ; on\r") def off(self): """Turn off sensor""" diff --git a/buildhat/motors.py b/buildhat/motors.py index 102a159..38786c0 100644 --- a/buildhat/motors.py +++ b/buildhat/motors.py @@ -71,7 +71,7 @@ def plimit(self, plimit): """ if not (plimit >= 0 and plimit <= 1): raise MotorError("plimit should be 0 to 1") - self._write(f"port {self.port} ; plimit {plimit}\r") + self._write(f"port {self.port} ; port_plimit {plimit}\r") def bias(self, bias): """Bias motor @@ -413,7 +413,7 @@ def plimit(self, plimit): """ if not (plimit >= 0 and plimit <= 1): raise MotorError("plimit should be 0 to 1") - self._write(f"port {self.port} ; plimit {plimit}\r") + self._write(f"port {self.port} ; port_plimit {plimit}\r") def bias(self, bias): """Bias motor From a0981fca71b1baa6569247614a4118fdaf9f0445 Mon Sep 17 00:00:00 2001 From: mutesplash <49622611+mutesplash@users.noreply.github.com> Date: Tue, 12 Sep 2023 00:20:40 -0400 Subject: [PATCH 25/50] Add movement counter to WeDo 2.0 Motion Sensor --- buildhat/wedo.py | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/buildhat/wedo.py b/buildhat/wedo.py index df99dd6..fc874c2 100644 --- a/buildhat/wedo.py +++ b/buildhat/wedo.py @@ -35,6 +35,7 @@ class MotionSensor(Device): :param port: Port of device :raises DeviceError: Occurs if there is no motion sensor attached to port """ + default_mode = 0 def __init__(self, port): """ @@ -43,7 +44,18 @@ def __init__(self, port): :param port: Port of device """ super().__init__(port) - self.mode(0) + self.mode(self.default_mode) + + def set_default_data_mode(self, mode): + """ + Set the mode most often queried from this device to significantly + improve performance when repeatedly accessing data + + :param mode: 0 for distance (default), 1 for movement count + """ + if mode == 1 or mode == 0: + self.default_mode = mode + self.mode(mode) def get_distance(self): """ @@ -52,4 +64,24 @@ def get_distance(self): :return: Distance from motion sensor :rtype: int """ - return self.get()[0] + return self._get_data_from_mode(0) + + def get_movement_count(self): + """ + Return the movement counter: The count of how many times the sensor has + detected an object that moved within 4 blocks of the sensor since the + sensor has been plugged in or the BuildHAT reset + + :return: Count of objects detected + :rtype: int + """ + return self._get_data_from_mode(1) + + def _get_data_from_mode(self, mode): + if self.default_mode == mode: + return self.get()[0] + else: + self.mode(mode) + retval = self.get()[0] + self.mode(self.default_mode) + return retval From e2b9a7910cadcfabff47df7d022bd39afceb925d Mon Sep 17 00:00:00 2001 From: mutesplash <49622611+mutesplash@users.noreply.github.com> Date: Tue, 12 Sep 2023 00:36:32 -0400 Subject: [PATCH 26/50] Didn't have all the flake8 checkers installed --- buildhat/wedo.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/buildhat/wedo.py b/buildhat/wedo.py index fc874c2..372524e 100644 --- a/buildhat/wedo.py +++ b/buildhat/wedo.py @@ -35,6 +35,7 @@ class MotionSensor(Device): :param port: Port of device :raises DeviceError: Occurs if there is no motion sensor attached to port """ + default_mode = 0 def __init__(self, port): @@ -48,8 +49,9 @@ def __init__(self, port): def set_default_data_mode(self, mode): """ - Set the mode most often queried from this device to significantly - improve performance when repeatedly accessing data + Set the mode most often queried from this device. + + This significantly improves performance when repeatedly accessing data :param mode: 0 for distance (default), 1 for movement count """ @@ -68,9 +70,11 @@ def get_distance(self): def get_movement_count(self): """ - Return the movement counter: The count of how many times the sensor has - detected an object that moved within 4 blocks of the sensor since the - sensor has been plugged in or the BuildHAT reset + Return the movement counter + + This is the count of how many times the sensor has detected an object + that moved within 4 blocks of the sensor since the sensor has been + plugged in or the BuildHAT reset :return: Count of objects detected :rtype: int From 4f0ac084488bbf7eab04fb58633a4a52800580c9 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Thu, 23 Nov 2023 15:22:32 +0000 Subject: [PATCH 27/50] Fix Build HAT library to work on Raspberry Pi 5 --- buildhat/serinterface.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/buildhat/serinterface.py b/buildhat/serinterface.py index af1ab1b..fbed704 100644 --- a/buildhat/serinterface.py +++ b/buildhat/serinterface.py @@ -5,6 +5,7 @@ import tempfile import threading import time +import os from enum import Enum from threading import Condition, Timer @@ -105,6 +106,11 @@ def __init__(self, firmware, signature, version, device="/dev/serial0", debug=Fa self.rampftr.append([]) self.motorqueue.append(queue.Queue()) + # On a Pi 5 /dev/serial0 will point to /dev/ttyAMA10 (which *only* + # exists on a Pi 5, and is the 3-pin debug UART connector) + # The UART on the Pi 5 GPIO header is /dev/ttyAMA0 + if device == "/dev/serial0" and os.readlink(device) == "ttyAMA10": + device = "/dev/ttyAMA0" self.ser = serial.Serial(device, 115200, timeout=5) # Check if we're in the bootloader or the firmware self.write(b"version\r") From e6e642d21aaab7d327a41dbd21f6bd38d446d3c9 Mon Sep 17 00:00:00 2001 From: Greg Annandale Date: Thu, 23 Nov 2023 17:03:01 +0000 Subject: [PATCH 28/50] Release version 0.6.0 --- CHANGELOG.md | 6 ++++++ VERSION | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e070036..2d3d92c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## 0.6.0 + +### Added + +* Support for Raspberry Pi 5 (https://github.com/RaspberryPiFoundation/python-build-hat/pull/203) + ## 0.5.12 ### Added diff --git a/VERSION b/VERSION index 9d6c175..a918a2a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.12 +0.6.0 From 84e31014568fa4c35dab5bbf74b39cb9d091559e Mon Sep 17 00:00:00 2001 From: Greg Annandale Date: Thu, 23 Nov 2023 17:15:34 +0000 Subject: [PATCH 29/50] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f6d7f91..7112796 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST +.pypirc # Installer logs pip-log.txt From d60686f6c353918eac29f10ea54c9ce4dc751188 Mon Sep 17 00:00:00 2001 From: Greg Annandale Date: Thu, 23 Nov 2023 17:23:16 +0000 Subject: [PATCH 30/50] Fix linting error --- buildhat/serinterface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildhat/serinterface.py b/buildhat/serinterface.py index fbed704..c8e5063 100644 --- a/buildhat/serinterface.py +++ b/buildhat/serinterface.py @@ -1,11 +1,11 @@ """Build HAT handling functionality""" import logging +import os import queue import tempfile import threading import time -import os from enum import Enum from threading import Condition, Timer From ea87bafc957380c8d2a6092e5769eac155e2e049 Mon Sep 17 00:00:00 2001 From: mutesplash <49622611+mutesplash@users.noreply.github.com> Date: Wed, 29 Nov 2023 09:14:46 -0500 Subject: [PATCH 31/50] Public access to the filename of the debug log --- buildhat/hat.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/buildhat/hat.py b/buildhat/hat.py index 395a3d6..227b054 100644 --- a/buildhat/hat.py +++ b/buildhat/hat.py @@ -41,6 +41,9 @@ def get(self): "description": desc} return devices + def get_logfile(self): + return Device._instance.debug_filename + def get_vin(self): """Get the voltage present on the input power jack From 338296334f65ffad2e9b04d20de991bebb447cad Mon Sep 17 00:00:00 2001 From: mutesplash <49622611+mutesplash@users.noreply.github.com> Date: Wed, 29 Nov 2023 09:15:39 -0500 Subject: [PATCH 32/50] Save debug log filename vs throwing it away in the logging framework --- buildhat/serinterface.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/buildhat/serinterface.py b/buildhat/serinterface.py index c8e5063..77ee53e 100644 --- a/buildhat/serinterface.py +++ b/buildhat/serinterface.py @@ -94,8 +94,10 @@ def __init__(self, firmware, signature, version, device="/dev/serial0", debug=Fa self.motorqueue = [] self.fin = False self.running = True + self.debug_filename = None if debug: tmp = tempfile.NamedTemporaryFile(suffix=".log", prefix="buildhat-", delete=False) + self.debug_filename = tmp.name logging.basicConfig(filename=tmp.name, format='%(asctime)s %(message)s', level=logging.DEBUG) From 057e98662990c54ed0d8593d2f048f1e947da2fe Mon Sep 17 00:00:00 2001 From: mutesplash <49622611+mutesplash@users.noreply.github.com> Date: Wed, 29 Nov 2023 09:31:07 -0500 Subject: [PATCH 33/50] Flake8 compliance --- buildhat/hat.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/buildhat/hat.py b/buildhat/hat.py index 227b054..0a277eb 100644 --- a/buildhat/hat.py +++ b/buildhat/hat.py @@ -42,6 +42,11 @@ def get(self): return devices def get_logfile(self): + """Get the filename of the debug log (If enabled, None otherwise) + + :return: Path of the debug logfile + :rtype: str or None + """ return Device._instance.debug_filename def get_vin(self): From 3670a2f19fde8df221093e612d8082b0e9204ea0 Mon Sep 17 00:00:00 2001 From: mutesplash <49622611+mutesplash@users.noreply.github.com> Date: Mon, 4 Dec 2023 15:09:03 -0500 Subject: [PATCH 34/50] Support Mode 7 IR Transmission As described in "LEGO Power Functions RC" PDF https://www.philohome.com/pf/pf.htm --- buildhat/colordistance.py | 334 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 334 insertions(+) diff --git a/buildhat/colordistance.py b/buildhat/colordistance.py index 3447e17..52cae03 100644 --- a/buildhat/colordistance.py +++ b/buildhat/colordistance.py @@ -25,6 +25,9 @@ def __init__(self, port): self.mode(6) self.avg_reads = 4 self._old_color = None + self._ir_channel = 0x0 + self._ir_address = 0x0 + self._ir_toggle = 0x0 def segment_color(self, r, g, b): """Return the color name from HSV @@ -197,6 +200,337 @@ def wait_for_new_color(self): self.callback(None) return self._old_color + @property + def ir_channel(self): + return self._ir_channel + + @ir_channel.setter + def ir_channel(self, channel=1): + """ + Set the IR channel for RC Tx + + :param channel: 1-4 indicating the selected IR channel on the reciever + """ + check_chan = channel + if check_chan > 4: + check_chan = 4 + elif check_chan < 1: + check_chan = 1 + # Internally: 0-3 + self._ir_channel = int(check_chan)-1 + + @property + def ir_address(self): + """ + IR Address space of 0x0 for default PoweredUp or 0x1 for extra space + """ + return self._ir_address + + def toggle_ir_toggle(self): + """ + Toggle the IR toggle bit + + """ + # IYKYK, because the RC documents are not clear + if self._ir_toggle: + self._ir_toggle = 0x0 + else: + self._ir_toggle = 0x1 + return self._ir_toggle + + def send_ir_sop(self, port, mode): + """ + Send an IR message via Power Functions RC Protocol in Single Output PWM mode + https://www.philohome.com/pf/pf.htm + + Port B is blue + + Valid values for mode are: + 0x0: Float output + 0x1: Forward/Clockwise at speed 1 + 0x2: Forward/Clockwise at speed 2 + 0x3: Forward/Clockwise at speed 3 + 0x4: Forward/Clockwise at speed 4 + 0x5: Forward/Clockwise at speed 5 + 0x6: Forward/Clockwise at speed 6 + 0x7: Forward/Clockwise at speed 7 + 0x8: Brake (then float v1.20) + 0x9: Backwards/Counterclockwise at speed 7 + 0xA: Backwards/Counterclockwise at speed 6 + 0xB: Backwards/Counterclockwise at speed 5 + 0xC: Backwards/Counterclockwise at speed 4 + 0xD: Backwards/Counterclockwise at speed 3 + 0xE: Backwards/Counterclockwise at speed 2 + 0xF: Backwards/Counterclockwise at speed 1 + + :param port: 'A' or 'B' + :param mode: 0-15 indicating the port's mode to set + """ + escape_modeselect = 0x0 + escape = escape_modeselect + + ir_mode_single_output = 0x4 + ir_mode = ir_mode_single_output + + so_mode_pwm = 0x0 + so_mode = so_mode_pwm + + output_port_a = 0x0 + output_port_b = 0x1 + + output_port = None + if port == 'A' or port == 'a': + output_port = output_port_a + elif port == 'B' or port == 'b': + output_port = output_port_b + else: + return False + + ir_mode = ir_mode | (so_mode << 1) | output_port + + nibble1 = (self._ir_toggle << 3) | (escape << 2) | self._ir_channel + nibble2 = (self._ir_address << 3) | ir_mode + + # Mode range checked here + return self._send_ir_nibbles(nibble1, nibble2, mode) + + def send_ir_socstid(self, port, mode): + """ + Send an IR message via Power Functions RC Protocol in Single Output Clear/Set/Toggle/Increment/Decrement mode + https://www.philohome.com/pf/pf.htm + + Valid values for mode are: + 0x0: Toggle full Clockwise/Forward (Stop to Clockwise, Clockwise to Stop, Counterclockwise to Clockwise) + 0x1: Toggle direction + 0x2: Increment numerical PWM + 0x3: Decrement numerical PWM + 0x4: Increment PWM + 0x5: Decrement PWM + 0x6: Full Clockwise/Forward + 0x7: Full Counterclockwise/Backward + 0x8: Toggle full (defaults to Forward, first) + 0x9: Clear C1 (C1 to High) + 0xA: Set C1 (C1 to Low) + 0xB: Toggle C1 + 0xC: Clear C2 (C2 to High) + 0xD: Set C2 (C2 to Low) + 0xE: Toggle C2 + 0xF: Toggle full Counterclockwise/Backward (Stop to Clockwise, Counterclockwise to Stop, Clockwise to Counterclockwise) + + :param port: 'A' or 'B' + :param mode: 0-15 indicating the port's mode to set + """ + + escape_modeselect = 0x0 + escape = escape_modeselect + + ir_mode_single_output = 0x4 + ir_mode = ir_mode_single_output + + so_mode_cstid = 0x1 + so_mode = so_mode_cstid + + output_port_a = 0x0 + output_port_b = 0x1 + + output_port = None + if port == 'A' or port == 'a': + output_port = output_port_a + elif port == 'B' or port == 'b': + output_port = output_port_b + else: + return False + + ir_mode = ir_mode | (so_mode << 1) | output_port + + nibble1 = (self._ir_toggle << 3) | (escape << 2) | self._ir_channel + nibble2 = (self._ir_address << 3) | ir_mode + + # Mode range checked here + return self._send_ir_nibbles(nibble1, nibble2, mode) + + def send_ir_combo_pwm(self, port_b_mode, port_a_mode): + """ + Send an IR message via Power Functions RC Protocol in Combo PWM mode + https://www.philohome.com/pf/pf.htm + + Valid values for the modes are: + 0x0 Float + 0x1 PWM Forward step 1 + 0x2 PWM Forward step 2 + 0x3 PWM Forward step 3 + 0x4 PWM Forward step 4 + 0x5 PWM Forward step 5 + 0x6 PWM Forward step 6 + 0x7 PWM Forward step 7 + 0x8 Brake (then float v1.20) + 0x9 PWM Backward step 7 + 0xA PWM Backward step 6 + 0xB PWM Backward step 5 + 0xC PWM Backward step 4 + 0xD PWM Backward step 3 + 0xE PWM Backward step 2 + 0xF PWM Backward step 1 + + :param port_b_mode: 0-15 indicating the command to send to port B + :param port_a_mode: 0-15 indicating the command to send to port A + """ + + escape_combo_pwm = 0x1 + escape = escape_combo_pwm + + nibble1 = (self._ir_toggle << 3) | (escape << 2) | self._ir_channel + + # Port modes are range checked here + return self._send_ir_nibbles(nibble1, port_b_mode, port_a_mode) + + def send_ir_combo_direct(self, port_b_output, port_a_output): + """ + Send an IR message via Power Functions RC Protocol in Combo Direct mode + https://www.philohome.com/pf/pf.htm + + Valid values for the output variables are: + 0x0: Float output + 0x1: Clockwise/Forward + 0x2: Counterclockwise/Backwards + 0x3: Brake then float + + :param port_b_output: 0-3 indicating the output to send to port B + :param port_a_output: 0-3 indicating the output to send to port A + """ + escape_modeselect = 0x0 + escape = escape_modeselect + + ir_mode_combo_direct = 0x1 + ir_mode = ir_mode_combo_direct + + nibble1 = (self._ir_toggle << 3) | (escape << 2) | self._ir_channel + nibble2 = (self._ir_address << 3) | ir_mode + + if port_b_output > 0x3 or port_a_output > 0x3: + return False + if port_b_output < 0x0 or port_a_output < 0x0: + return False + + nibble3 = (port_b_output << 2) | port_a_output + + return self._send_ir_nibbles(nibble1, nibble2, nibble3) + + def send_ir_extended(self, mode): + """ + Send an IR message via Power Functions RC Protocol in Extended mode + https://www.philohome.com/pf/pf.htm + + Valid values for the mode are: + 0x0: Brake Port A (timeout) + 0x1: Increment Speed on Port A + 0x2: Decrement Speed on Port A + + 0x4: Toggle Forward/Clockwise/Float on Port B + + 0x6: Toggle Address bit + 0x7: Align toggle bit + + :param mode: 0-2,4,6-7 + """ + escape_modeselect = 0x0 + escape = escape_modeselect + + ir_mode_extended = 0x0 + ir_mode = ir_mode_extended + + nibble1 = (self._ir_toggle << 3) | (escape << 2) | self._ir_channel + nibble2 = (self._ir_address << 3) | ir_mode + + if mode < 0x0 or mode == 0x3 or mode == 0x5 or mode > 0x7: + return False + + return self._send_ir_nibbles(nibble1, nibble2, mode) + + def send_ir_single_pin(self, port, pin, mode, timeout): + """ + Send an IR message via Power Functions RC Protocol in Single Pin mode + https://www.philohome.com/pf/pf.htm + + Valid values for the mode are: + 0x0: No-op + 0x1: Clear + 0x2: Set + 0x3: Toggle + + Note: The unlabeled IR receiver (vs the one labeled V2) has a "firmware bug in Single Pin mode" + https://www.philohome.com/pfrec/pfrec.htm + + :param port: 'A' or 'B' + :param pin: 1 or 2 + :param mode: 0-3 indicating the pin's mode to set + :param timeout: True or False + """ + escape_mode = 0x0 + escape = escape_mode + + ir_mode_single_continuous = 0x2 + ir_mode_single_timeout = 0x3 + ir_mode = None + if timeout: + ir_mode = ir_mode_single_timeout + else: + ir_mode = ir_mode_single_continuous + + output_port_a = 0x0 + output_port_b = 0x1 + + output_port = None + if port == 'A' or port == 'a': + output_port = output_port_a + elif port == 'B' or port == 'b': + output_port = output_port_b + else: + return False + + if pin != 1 and pin != 2: + return False + pin_value = pin-1 + + if mode > 0x3 or mode < 0x0: + return False + + nibble1 = (self._ir_toggle << 3) | (escape << 2) | self._ir_channel + nibble2 = (self._ir_address << 3) | ir_mode + nibble3 = (output_port << 3) | (pin_value << 3) | mode + + return self._send_ir_nibbles(nibble1, nibble2, mode) + + def _send_ir_nibbles(self, nibble1, nibble2, nibble3): + + # M7 IR Tx SI = N/A + # format count=1 type=1 chars=5 dp=0 + # RAW: 00000000 0000FFFF PCT: 00000000 00000064 SI: 00000000 0000FFFF + + mode = 7 + self.mode(mode) + + # The upper bits of data[2] are ignored + if nibble1 > 0xF or nibble2 > 0xF or nibble3 > 0xF: + return False + if nibble1 < 0x0 or nibble2 < 0x0 or nibble3 < 0x0: + return False + + byte_two = (nibble2 << 4) | nibble3 + + data = bytearray(3) + data[0] = (0xc << 4) | mode + data[1] = byte_two + data[2] = nibble1 + + # print(" ".join('{:04b}'.format(nibble1))) + # print(" ".join('{:04b}'.format(nibble2))) + # print(" ".join('{:04b}'.format(nibble3))) + # print(" ".join('{:08b}'.format(n) for n in data)) + + self._write1(data) + return True + def on(self): """Turn on the sensor and LED""" self.reverse() From a306d01951de028d9323d4295905bbcb31a3ceae Mon Sep 17 00:00:00 2001 From: mutesplash <49622611+mutesplash@users.noreply.github.com> Date: Mon, 4 Dec 2023 15:26:40 -0500 Subject: [PATCH 35/50] Flake8 compliance ... and a bugfix, thanks to flake8... so mad that it actually helped --- buildhat/colordistance.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/buildhat/colordistance.py b/buildhat/colordistance.py index 52cae03..3c722a3 100644 --- a/buildhat/colordistance.py +++ b/buildhat/colordistance.py @@ -202,6 +202,7 @@ def wait_for_new_color(self): @property def ir_channel(self): + """Get the IR channel for message transmission""" return self._ir_channel @ir_channel.setter @@ -217,20 +218,15 @@ def ir_channel(self, channel=1): elif check_chan < 1: check_chan = 1 # Internally: 0-3 - self._ir_channel = int(check_chan)-1 + self._ir_channel = int(check_chan) - 1 @property def ir_address(self): - """ - IR Address space of 0x0 for default PoweredUp or 0x1 for extra space - """ + """IR Address space of 0x0 for default PoweredUp or 0x1 for extra space""" return self._ir_address def toggle_ir_toggle(self): - """ - Toggle the IR toggle bit - - """ + """Toggle the IR toggle bit""" # IYKYK, because the RC documents are not clear if self._ir_toggle: self._ir_toggle = 0x0 @@ -241,7 +237,8 @@ def toggle_ir_toggle(self): def send_ir_sop(self, port, mode): """ Send an IR message via Power Functions RC Protocol in Single Output PWM mode - https://www.philohome.com/pf/pf.htm + + PF IR RC Protocol documented at https://www.philohome.com/pf/pf.htm Port B is blue @@ -297,7 +294,8 @@ def send_ir_sop(self, port, mode): def send_ir_socstid(self, port, mode): """ Send an IR message via Power Functions RC Protocol in Single Output Clear/Set/Toggle/Increment/Decrement mode - https://www.philohome.com/pf/pf.htm + + PF IR RC Protocol documented at https://www.philohome.com/pf/pf.htm Valid values for mode are: 0x0: Toggle full Clockwise/Forward (Stop to Clockwise, Clockwise to Stop, Counterclockwise to Clockwise) @@ -320,7 +318,6 @@ def send_ir_socstid(self, port, mode): :param port: 'A' or 'B' :param mode: 0-15 indicating the port's mode to set """ - escape_modeselect = 0x0 escape = escape_modeselect @@ -352,7 +349,8 @@ def send_ir_socstid(self, port, mode): def send_ir_combo_pwm(self, port_b_mode, port_a_mode): """ Send an IR message via Power Functions RC Protocol in Combo PWM mode - https://www.philohome.com/pf/pf.htm + + PF IR RC Protocol documented at https://www.philohome.com/pf/pf.htm Valid values for the modes are: 0x0 Float @@ -375,7 +373,6 @@ def send_ir_combo_pwm(self, port_b_mode, port_a_mode): :param port_b_mode: 0-15 indicating the command to send to port B :param port_a_mode: 0-15 indicating the command to send to port A """ - escape_combo_pwm = 0x1 escape = escape_combo_pwm @@ -387,7 +384,8 @@ def send_ir_combo_pwm(self, port_b_mode, port_a_mode): def send_ir_combo_direct(self, port_b_output, port_a_output): """ Send an IR message via Power Functions RC Protocol in Combo Direct mode - https://www.philohome.com/pf/pf.htm + + PF IR RC Protocol documented at https://www.philohome.com/pf/pf.htm Valid values for the output variables are: 0x0: Float output @@ -419,7 +417,8 @@ def send_ir_combo_direct(self, port_b_output, port_a_output): def send_ir_extended(self, mode): """ Send an IR message via Power Functions RC Protocol in Extended mode - https://www.philohome.com/pf/pf.htm + + PF IR RC Protocol documented at https://www.philohome.com/pf/pf.htm Valid values for the mode are: 0x0: Brake Port A (timeout) @@ -450,7 +449,8 @@ def send_ir_extended(self, mode): def send_ir_single_pin(self, port, pin, mode, timeout): """ Send an IR message via Power Functions RC Protocol in Single Pin mode - https://www.philohome.com/pf/pf.htm + + PF IR RC Protocol documented at https://www.philohome.com/pf/pf.htm Valid values for the mode are: 0x0: No-op @@ -490,7 +490,7 @@ def send_ir_single_pin(self, port, pin, mode, timeout): if pin != 1 and pin != 2: return False - pin_value = pin-1 + pin_value = pin - 1 if mode > 0x3 or mode < 0x0: return False @@ -499,7 +499,7 @@ def send_ir_single_pin(self, port, pin, mode, timeout): nibble2 = (self._ir_address << 3) | ir_mode nibble3 = (output_port << 3) | (pin_value << 3) | mode - return self._send_ir_nibbles(nibble1, nibble2, mode) + return self._send_ir_nibbles(nibble1, nibble2, nibble3) def _send_ir_nibbles(self, nibble1, nibble2, nibble3): @@ -516,7 +516,7 @@ def _send_ir_nibbles(self, nibble1, nibble2, nibble3): if nibble1 < 0x0 or nibble2 < 0x0 or nibble3 < 0x0: return False - byte_two = (nibble2 << 4) | nibble3 + byte_two = (nibble2 << 4) | nibble3 data = bytearray(3) data[0] = (0xc << 4) | mode From e73e4153be9e73fde0269f20a37e9efa1b95884a Mon Sep 17 00:00:00 2001 From: Greg Annandale Date: Tue, 19 Dec 2023 07:42:21 -0600 Subject: [PATCH 36/50] Release version 0.7.0 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index a918a2a..faef31a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.0 +0.7.0 From 78c0c6988f9c786113b12c4c6309aecccb36b1da Mon Sep 17 00:00:00 2001 From: Greg Annandale Date: Tue, 19 Dec 2023 07:45:38 -0600 Subject: [PATCH 37/50] Adds 0.7.0 changes to Change Log --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d3d92c..7cbdc59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log +## 0.7.0 + +Adds: + +* Mode 7 IR transmission to ColorDistanceSensor https://github.com/RaspberryPiFoundation/python-build-hat/pull/205 +* Debug log filename access https://github.com/RaspberryPiFoundation/python-build-hat/pull/204 +* Movement counter to WeDo 2.0 Motion Sensor https://github.com/RaspberryPiFoundation/python-build-hat/pull/201 + ## 0.6.0 ### Added From aa6c67de0488defd6ca593c49531a79814ef0956 Mon Sep 17 00:00:00 2001 From: chrisruk Date: Sun, 23 Feb 2025 13:06:00 +0000 Subject: [PATCH 38/50] Increase interval for sensor while testing Avoids error that occurs when handling large amount of sensor data. In future, might be interesting to see if we can support faster UART baud rate. --- test/color.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/color.py b/test/color.py index 688e23c..ba23ff2 100644 --- a/test/color.py +++ b/test/color.py @@ -34,7 +34,7 @@ def test_caching(self): """Test to make sure we're not reading cached data""" color = ColorSensor('A') color.avg_reads = 1 - color.interval = 1 + color.interval = 10 for _ in range(100): color.mode(2) From dd6ee6dd32e0f99d1dc0b5812b05ef2b121c1477 Mon Sep 17 00:00:00 2001 From: chrisruk Date: Sun, 23 Feb 2025 13:09:29 +0000 Subject: [PATCH 39/50] Add W503 to ignore This is so that we can handle multiple line if statements. --- .flake8 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.flake8 b/.flake8 index 732c12e..8f58749 100644 --- a/.flake8 +++ b/.flake8 @@ -1,7 +1,7 @@ [flake8] docstring_style=sphinx max-line-length = 127 -ignore = D400, Q000, S311, PLW, PLC, PLR +ignore = D400, Q000, S311, W503, PLW, PLC, PLR per-file-ignores = buildhat/__init__.py:F401 exclude = docs/conf.py, docs/sphinxcontrib/cmtinc-buildhat.py, docs/sphinx_selective_exclude/*.py From b2ca18d617bfd0b58c18a6515164e9e81abf99fb Mon Sep 17 00:00:00 2001 From: chrisruk Date: Sun, 23 Feb 2025 13:13:35 +0000 Subject: [PATCH 40/50] Allow use of own custom firmware If user creates directory called 'data/' where they run their Python script from and places following files in: * firmware.bin * signature.bin * version This custom firmware will then be used instead of bundled firmware. --- buildhat/devices.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/buildhat/devices.py b/buildhat/devices.py index 0db16b1..80dec80 100644 --- a/buildhat/devices.py +++ b/buildhat/devices.py @@ -72,7 +72,15 @@ def __init__(self, port): def _setup(**kwargs): if Device._instance: return - data = os.path.join(os.path.dirname(sys.modules["buildhat"].__file__), "data/") + if ( + os.path.isdir(os.path.join(os.getcwd(), "data/")) + and os.path.isfile(os.path.join(os.getcwd(), "data", "firmware.bin")) + and os.path.isfile(os.path.join(os.getcwd(), "data", "signature.bin")) + and os.path.isfile(os.path.join(os.getcwd(), "data", "version")) + ): + data = os.path.join(os.getcwd(), "data/") + else: + data = os.path.join(os.path.dirname(sys.modules["buildhat"].__file__), "data/") firm = os.path.join(data, "firmware.bin") sig = os.path.join(data, "signature.bin") ver = os.path.join(data, "version") From 1f5e1205dadc56e3a7f7422196fa6b8f1fb4c0f6 Mon Sep 17 00:00:00 2001 From: chrisruk Date: Sun, 23 Feb 2025 14:29:34 +0000 Subject: [PATCH 41/50] Increase timer Bump timer from 8s to 11s, to wait after receiving "Done initialising ports", to avoid geting disconnected message, even though sensor connected to port. --- buildhat/serinterface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildhat/serinterface.py b/buildhat/serinterface.py index 77ee53e..89c9c92 100644 --- a/buildhat/serinterface.py +++ b/buildhat/serinterface.py @@ -378,7 +378,7 @@ def loop(self, cond, uselist, q, listevt): def runit(): with cond: cond.notify() - t = Timer(8.0, runit) + t = Timer(11.0, runit) t.start() if line[0] == "P" and (line[2] == "C" or line[2] == "M"): From 3f33814857c86e8c463910e39857d649d5706377 Mon Sep 17 00:00:00 2001 From: chrisruk Date: Sun, 23 Feb 2025 14:31:55 +0000 Subject: [PATCH 42/50] Add new firmware --- buildhat/data/firmware.bin | Bin 54360 -> 51456 bytes buildhat/data/signature.bin | 2 +- buildhat/data/version | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/buildhat/data/firmware.bin b/buildhat/data/firmware.bin index 64358ee52512bf324a5201140895c78b0a7f6556..f6dcde379adcfa02e6b9ef87f8f0548548174e94 100644 GIT binary patch literal 51456 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 Y3#t@u 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 literal 54360 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 SKWmOK{=&12|JzR(|33j#kSRj| diff --git a/buildhat/data/signature.bin b/buildhat/data/signature.bin index 27ef00d..8f495fa 100644 --- a/buildhat/data/signature.bin +++ b/buildhat/data/signature.bin @@ -1 +1 @@ -Q#E]]-.T~駶e[yEV}A5L}A$I!u9Nzw`l@eK;{E \ No newline at end of file +N.2wSmQ/IӍm )io\~IAz4ĘɅPw>\w LV \ No newline at end of file diff --git a/buildhat/data/version b/buildhat/data/version index e9bf96c..1f3109b 100644 --- a/buildhat/data/version +++ b/buildhat/data/version @@ -1 +1 @@ -1674818421 +1737564117 From 31b746ac1895681a443d9b61b93515c8c0fdb2c4 Mon Sep 17 00:00:00 2001 From: chrisruk Date: Thu, 6 Mar 2025 20:17:25 +0000 Subject: [PATCH 43/50] Update documentation Add information on how to make use of custom firmware. --- docs/buildhat/index.rst | 43 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/docs/buildhat/index.rst b/docs/buildhat/index.rst index 91b48b0..0b9c94e 100644 --- a/docs/buildhat/index.rst +++ b/docs/buildhat/index.rst @@ -28,10 +28,51 @@ power supply. For best results, use the `official Raspberry Pi Build HAT power s .. _official Raspberry Pi Build HAT power supply: http://raspberrypi.com/products/build-hat-power-supply +It is now possible to use custom firmware with the library. To do this you can follow the steps below. + +.. code-block:: + :caption: Using custom firmware + + sudo apt install cmake python3 build-essential gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib + + git clone https://github.com/raspberrypi/pico-sdk.git --recursive + git clone https://github.com/raspberrypi/buildhat.git --recursive + + cd buildhat + export PICO_SDK_PATH="$(pwd)/../pico-sdk/" + make + + cd .. + mkdir test + cd test + mkdir data + + cp ../buildhat/firmware-pico/build/main.bin data/firmware.bin + cp ../buildhat/bhbl-pico/signature.bin data/signature.bin + cat ../buildhat/firmware-pico/version.h | sed 's/#define FWVERSION "//g; s/ .*//g' > data/version + +Then place your script, such as the following, within the test/ directory. + +.. code-block:: + :caption: Create test.py in test directory + + import time + from buildhat import Motor + + m = Motor('A') + m.start() + + time.sleep(5) + +Then use: ``python test.py`` in the test directory, to run your script with your custom firmware. + +Note if you want python to always reload the firmware from your **data/** directory each time +you run your script, simply write the value: -1 to **data/version**. + .. warning:: The API for the Build HAT is undergoing active development and is subject - to change. + to change. .. toctree:: :maxdepth: 2 From 6a763de85852f29100ad692aaa732720eb68b191 Mon Sep 17 00:00:00 2001 From: Greg Annandale Date: Wed, 9 Jul 2025 09:06:37 +0100 Subject: [PATCH 44/50] Whitespace fix --- docs/buildhat/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/buildhat/index.rst b/docs/buildhat/index.rst index 0b9c94e..f4f7fc4 100644 --- a/docs/buildhat/index.rst +++ b/docs/buildhat/index.rst @@ -28,7 +28,7 @@ power supply. For best results, use the `official Raspberry Pi Build HAT power s .. _official Raspberry Pi Build HAT power supply: http://raspberrypi.com/products/build-hat-power-supply -It is now possible to use custom firmware with the library. To do this you can follow the steps below. +It is now possible to use custom firmware with the library. To do this you can follow the steps below. .. code-block:: :caption: Using custom firmware From 04ce41ceaa639785846c7d0623c3a40f48ab6390 Mon Sep 17 00:00:00 2001 From: Greg Annandale Date: Wed, 9 Jul 2025 14:44:15 +0100 Subject: [PATCH 45/50] Fix Buildthedocs config --- .readthedocs.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 3f5c924..121204f 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -6,10 +6,11 @@ version: 2 build: - image: latest + os: ubuntu-22.04 + tools: + python: "3.8" python: - version: 3.8 install: - method: setuptools path: . From 7cb54ca9d57e23f0879a5b47245e7fdc9fcae87a Mon Sep 17 00:00:00 2001 From: Greg Annandale Date: Wed, 9 Jul 2025 14:45:45 +0100 Subject: [PATCH 46/50] Update copyright dates --- LICENSE | 2 +- docs/conf.py | 2 +- docs/license.rst | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LICENSE b/LICENSE index 78d99a6..a43a2e8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2020-2021 - Raspberry Pi Foundation +Copyright (c) 2020-2025 - Raspberry Pi Foundation Copyright (c) 2017-2021 - LEGO System A/S - Aastvej 1, 7190 Billund, DK Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/docs/conf.py b/docs/conf.py index e17f1fd..aae1e0d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,7 +59,7 @@ # General information about the project. project = 'Raspberry Pi Build HAT' -copyright = '''2020-2021 - Raspberry Pi Foundation; +copyright = '''2020-2025 - Raspberry Pi Foundation; 2017-2020 - LEGO System A/S - Aastvej 1, 7190 Billund, DK.''' # The version info for the project you're documenting, acts as replacement for diff --git a/docs/license.rst b/docs/license.rst index 2ad53fd..af5c040 100644 --- a/docs/license.rst +++ b/docs/license.rst @@ -3,7 +3,7 @@ License information The MIT License (MIT) -Copyright (c) 2020-2021 - Raspberry Pi Foundation +Copyright (c) 2020-2025 - Raspberry Pi Foundation Copyright (c) 2017-2021 - LEGO System A/S - Aastvej 1, 7190 Billund, DK diff --git a/setup.py b/setup.py index fe788ea..38ae4f1 100755 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ """Setup file""" -# Copyright (c) 2020-2021 Raspberry Pi Foundation +# Copyright (c) 2020-2025 Raspberry Pi Foundation # # SPDX-License-Identifier: MIT From ea80469800ed4164e6d5798cd2accf118b1997ce Mon Sep 17 00:00:00 2001 From: Greg Annandale Date: Wed, 9 Jul 2025 14:46:25 +0100 Subject: [PATCH 47/50] Release version 0.8.0 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index faef31a..a3df0a6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.7.0 +0.8.0 From 7e27ff2a1752503d2796e4073a99d462d0af57a0 Mon Sep 17 00:00:00 2001 From: Greg Annandale Date: Wed, 9 Jul 2025 14:49:18 +0100 Subject: [PATCH 48/50] Update CHANGELOG.md --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cbdc59..53d91df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log +## 0.8.0 + +Adds: + +* Support for custom firmware (see: https://www.raspberrypi.com/news/build-hat-firmware-now-fully-open-source/) +* Add W503 to Flake8 ignore +* Increase interval for sensor while testing - avoids error that occurs when handling large amount of sensor data. In future, might be interesting to see if we can support faster UART baud rate. + ## 0.7.0 Adds: From ab7ff577b6df8f0a57735250f97d35fde1102bbc Mon Sep 17 00:00:00 2001 From: chrisruk Date: Thu, 12 Jun 2025 22:11:10 +0100 Subject: [PATCH 49/50] Resend 'version' if we get empty data back --- buildhat/serinterface.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/buildhat/serinterface.py b/buildhat/serinterface.py index 89c9c92..901dc2e 100644 --- a/buildhat/serinterface.py +++ b/buildhat/serinterface.py @@ -117,17 +117,9 @@ def __init__(self, firmware, signature, version, device="/dev/serial0", debug=Fa # Check if we're in the bootloader or the firmware self.write(b"version\r") - emptydata = 0 incdata = 0 while True: line = self.read() - if len(line) == 0: - # Didn't receive any data - emptydata += 1 - if emptydata > 3: - break - else: - continue if cmp(line, BuildHAT.FIRMWARE): self.state = HatState.FIRMWARE ver = line[len(BuildHAT.FIRMWARE):].split(' ') From 535de8782326eabc7c5f620ac3baf8c9dd40f339 Mon Sep 17 00:00:00 2001 From: Greg Annandale Date: Thu, 10 Jul 2025 08:58:30 +0100 Subject: [PATCH 50/50] Release version 0.9.0 --- CHANGELOG.md | 6 ++++++ VERSION | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53d91df..8bf1d81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## 0.9.0 + +Fixes: + +* Resend 'version' if empty data received from the Build HAT + ## 0.8.0 Adds: diff --git a/VERSION b/VERSION index a3df0a6..ac39a10 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.8.0 +0.9.0