From 644d3389577487bfa0957a7a98f92f29dae73ff2 Mon Sep 17 00:00:00 2001 From: Atharv Nagavarapu Date: Fri, 6 Mar 2026 16:34:42 +0000 Subject: [PATCH 01/38] Add diagnostics/os_diagnostics.py --- diagnostics/os_diagnostics.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 diagnostics/os_diagnostics.py diff --git a/diagnostics/os_diagnostics.py b/diagnostics/os_diagnostics.py new file mode 100644 index 0000000..ee1b268 --- /dev/null +++ b/diagnostics/os_diagnostics.py @@ -0,0 +1,18 @@ +from pybricks.pupdevices import Motor +from pybricks.parameters import Port +from uerrno import EAGAIN, EBUSY, ECANCELED, EINVAL, EIO, ENODEV, EOPNOTSUPP, EPERM, ETIMEDOUT + +try: + # Try to initialize a motor on an empty port (Port.A) + input("Make sure Port A doesn't have anything plugged in, then press Enter.") + my_motor = Motor(Port.A) + print("OS detected a motor when there was nothing. You may have allowed missing motors. This is useful for debugging, but not recommended for production as it can cause issues with device control.") +except OSError as ex: + # If an OSError was raised, check the specific error code + if ex.errno == ENODEV: + print("There is no motor on this port. ENODEV can be ") + elif ex.errno == EIO: + print("An I/O error occurred (EIO). Try unplugging/replugging the device.") + else: + print(f"Another error occurred with code: {ex.errno}") + From 94b9b64ef3963d25dd1a6941c0afa046b7bf0edf Mon Sep 17 00:00:00 2001 From: Atharv Nagavarapu Date: Fri, 6 Mar 2026 18:35:52 +0000 Subject: [PATCH 02/38] Update diagnostics/os_diagnostics.py --- diagnostics/os_diagnostics.py | 42 ++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/diagnostics/os_diagnostics.py b/diagnostics/os_diagnostics.py index ee1b268..8f56def 100644 --- a/diagnostics/os_diagnostics.py +++ b/diagnostics/os_diagnostics.py @@ -1,18 +1,28 @@ -from pybricks.pupdevices import Motor from pybricks.parameters import Port from uerrno import EAGAIN, EBUSY, ECANCELED, EINVAL, EIO, ENODEV, EOPNOTSUPP, EPERM, ETIMEDOUT - -try: - # Try to initialize a motor on an empty port (Port.A) - input("Make sure Port A doesn't have anything plugged in, then press Enter.") - my_motor = Motor(Port.A) - print("OS detected a motor when there was nothing. You may have allowed missing motors. This is useful for debugging, but not recommended for production as it can cause issues with device control.") -except OSError as ex: - # If an OSError was raised, check the specific error code - if ex.errno == ENODEV: - print("There is no motor on this port. ENODEV can be ") - elif ex.errno == EIO: - print("An I/O error occurred (EIO). Try unplugging/replugging the device.") - else: - print(f"Another error occurred with code: {ex.errno}") - +class OSDiagnostics: + def __init__(self, hub, motorclass): + self.motorclass = motorclass + self.successfultests = 0 + self.failedtests = {} + def testenodev: + try: + # Try to initialize a motor on an empty port (Port.A) + print("Starting Test 6/9: ENODEV - Device Not Found Error") + input("Make sure Port A doesn't have anything plugged in, then press Enter.") + my_motor = self.motorclass(Port.A) + print("OS detected a motor when there was nothing. You may have allowed missing motors. This is useful for debugging, but not recommended for production as it can cause issues with device control.") + except OSError as ex: + # If an OSError was raised, check the specific error code + if ex.errno == ENODEV: + print("There is no motor on this port. ENODEV can be thrown and caught.\nCompleted Test 6/9: ENODEV - SUCCESSFUL") + self.successfultests += 1 + elif ex.errno == EIO: + print("An unspecified error occurred (EIO).\nCompleted Test 6/9: ENODEV - FAILED") + self.failedtests["ENODEV"] = "EIO - Unspecified Error" + else: + print(f"Another error occurred with code: {ex.errno}.\nCompleted Test 6/9: ENODEV - FAILED") + self.failedtests["ENODEV"] = ex.errno + def print_results: + for key, value in self.failedtests(): + print(f"{key}: {value}") From 07141a9c35c9e7201ffcdc8c7d6ee7379d8a733b Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Wed, 11 Mar 2026 01:03:44 +0000 Subject: [PATCH 03/38] Update diagnostics/os_diagnostics.py --- diagnostics/os_diagnostics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diagnostics/os_diagnostics.py b/diagnostics/os_diagnostics.py index 8f56def..780be7f 100644 --- a/diagnostics/os_diagnostics.py +++ b/diagnostics/os_diagnostics.py @@ -5,7 +5,7 @@ class OSDiagnostics: self.motorclass = motorclass self.successfultests = 0 self.failedtests = {} - def testenodev: + def testenodev(self): try: # Try to initialize a motor on an empty port (Port.A) print("Starting Test 6/9: ENODEV - Device Not Found Error") @@ -23,6 +23,6 @@ class OSDiagnostics: else: print(f"Another error occurred with code: {ex.errno}.\nCompleted Test 6/9: ENODEV - FAILED") self.failedtests["ENODEV"] = ex.errno - def print_results: + def print_results(self): for key, value in self.failedtests(): print(f"{key}: {value}") From 17f5ea53f5937d69eddc80ca4a2f7d9a77ef5e28 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Wed, 11 Mar 2026 01:05:50 +0000 Subject: [PATCH 04/38] Add dev-tests/test-allow-missing-uart.py --- dev-tests/test-allow-missing-uart.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 dev-tests/test-allow-missing-uart.py diff --git a/dev-tests/test-allow-missing-uart.py b/dev-tests/test-allow-missing-uart.py new file mode 100644 index 0000000..6d5aebc --- /dev/null +++ b/dev-tests/test-allow-missing-uart.py @@ -0,0 +1,25 @@ +from pybricks.iodevices import UARTDevice as _UARTDevice +from pybricks.tools import wait +from uerrno import ETIMEDOUT + +class FakeUART: + def __init__(self, port, baudrate, timeout): + self.timeout = timeout + print("Warning: No physical UART detected. Using simulator.") + + def read(self, length=1): + if self.timeout is not None: + wait(self.timeout) + raise OSError(ETIMEDOUT) + else: + while True: + wait(1000) + + def write(self, data): + pass + +def UARTDevice(port, baudrate=9600, timeout=None): + try: + return _UARTDevice(port, baudrate, timeout) + except OSError: + return FakeUART(port, baudrate, timeout) \ No newline at end of file From 94bc1f7b1406cb2994d515b83036148beb3c4577 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Wed, 11 Mar 2026 01:06:02 +0000 Subject: [PATCH 05/38] Update tests/test-allow-missing-uart.py --- {dev-tests => tests}/test-allow-missing-uart.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {dev-tests => tests}/test-allow-missing-uart.py (100%) diff --git a/dev-tests/test-allow-missing-uart.py b/tests/test-allow-missing-uart.py similarity index 100% rename from dev-tests/test-allow-missing-uart.py rename to tests/test-allow-missing-uart.py From 3b35fa548c76dc4af90a8fa9c9421a63f8e16061 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Wed, 11 Mar 2026 21:56:37 +0000 Subject: [PATCH 06/38] Update diagnostics/os_diagnostics.py --- diagnostics/os_diagnostics.py | 41 ++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/diagnostics/os_diagnostics.py b/diagnostics/os_diagnostics.py index 780be7f..2ee3b56 100644 --- a/diagnostics/os_diagnostics.py +++ b/diagnostics/os_diagnostics.py @@ -1,5 +1,29 @@ from pybricks.parameters import Port from uerrno import EAGAIN, EBUSY, ECANCELED, EINVAL, EIO, ENODEV, EOPNOTSUPP, EPERM, ETIMEDOUT +from pybricks.iodevices import UARTDevice as _UARTDevice +from pybricks.tools import wait + +class FakeUART: + def __init__(self, port, baudrate, timeout): + self.timeout = timeout + print("Warning: No physical UART detected. Using simulator.") + + def read(self, length=1): + if self.timeout is not None: + wait(self.timeout) + raise OSError(ETIMEDOUT) + else: + while True: + wait(1000) + + def write(self, data): + pass + +def UARTDevice(port, baudrate=9600, timeout=None): + try: + return _UARTDevice(port, baudrate, timeout) + except OSError: + return FakeUART(port, baudrate, timeout) class OSDiagnostics: def __init__(self, hub, motorclass): self.motorclass = motorclass @@ -23,6 +47,21 @@ class OSDiagnostics: else: print(f"Another error occurred with code: {ex.errno}.\nCompleted Test 6/9: ENODEV - FAILED") self.failedtests["ENODEV"] = ex.errno + def testetimedout(self): + uart = UARTDevice(Port.A, baudrate=9600, timeout=1000) # 1000ms timeout + # Test ETIMEDOUT + try: + data = uart.read(10) # FakeUART will wait `timeout` ms, then raise OSError(ETIMEDOUT) + except OSError as ex: + if ex.errno == ETIMEDOUT: + print("Timed out with synthetic UART device. ETIMEDOUT can be thrown and caught.\nCompleted Test 9/9: ETIMEDOUT - SUCCESSFUL") + self.successfultests += 1 + elif ex.errno == EIO: + print("An unspecified error occurred (EIO).\nCompleted Test 9/9: ETIMEDOUT - FAILED") + self.failedtests["ETIMEDOUT"] = "EIO - Unspecified Error" + else: + print(f"Another error occurred with code: {ex.errno}.\nCompleted Test 9/9: ETIMEDOUT - FAILED") + self.failedtests["ETIMEDOUT"] = ex.errno def print_results(self): for key, value in self.failedtests(): - print(f"{key}: {value}") + print(f"{key}: {value}") \ No newline at end of file From db747b0e7d44cf692099ce424b8d32b96528af40 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Wed, 11 Mar 2026 22:01:14 +0000 Subject: [PATCH 07/38] Update diagnostics/os_diagnostics.py --- diagnostics/os_diagnostics.py | 199 +++++++++++++++++++++++++++++++--- 1 file changed, 186 insertions(+), 13 deletions(-) diff --git a/diagnostics/os_diagnostics.py b/diagnostics/os_diagnostics.py index 2ee3b56..fc71b7f 100644 --- a/diagnostics/os_diagnostics.py +++ b/diagnostics/os_diagnostics.py @@ -1,14 +1,23 @@ from pybricks.parameters import Port from uerrno import EAGAIN, EBUSY, ECANCELED, EINVAL, EIO, ENODEV, EOPNOTSUPP, EPERM, ETIMEDOUT from pybricks.iodevices import UARTDevice as _UARTDevice -from pybricks.tools import wait +from pybricks.tools import wait, multitask, run_task + class FakeUART: def __init__(self, port, baudrate, timeout): self.timeout = timeout + self._force_error = None print("Warning: No physical UART detected. Using simulator.") + def set_error(self, errno): + self._force_error = errno + def read(self, length=1): + if self._force_error is not None: + err = self._force_error + self._force_error = None + raise OSError(err) if self.timeout is not None: wait(self.timeout) raise OSError(ETIMEDOUT) @@ -17,27 +26,136 @@ class FakeUART: wait(1000) def write(self, data): - pass + if self._force_error is not None: + err = self._force_error + self._force_error = None + raise OSError(err) + def UARTDevice(port, baudrate=9600, timeout=None): try: return _UARTDevice(port, baudrate, timeout) except OSError: return FakeUART(port, baudrate, timeout) + + class OSDiagnostics: def __init__(self, hub, motorclass): + self.hub = hub self.motorclass = motorclass self.successfultests = 0 self.failedtests = {} - def testenodev(self): + + def testeagain(self): + # Triggered by calling multitask() nested inside another multitask() + print("Starting Test 1/9: EAGAIN - Try Again Error") + try: + async def inner(): + await multitask(wait(100)) # nested multitask raises EAGAIN + + async def outer(): + await multitask(inner()) + + run_task(outer()) + print("No error raised.\nCompleted Test 1/9: EAGAIN - FAILED") + self.failedtests["EAGAIN"] = "No error raised" + except OSError as ex: + if ex.errno == EAGAIN: + print("EAGAIN can be thrown and caught.\nCompleted Test 1/9: EAGAIN - SUCCESSFUL") + self.successfultests += 1 + elif ex.errno == EIO: + print("An unspecified error occurred (EIO).\nCompleted Test 1/9: EAGAIN - FAILED") + self.failedtests["EAGAIN"] = "EIO - Unspecified Error" + else: + print(f"Another error occurred with code: {ex.errno}.\nCompleted Test 1/9: EAGAIN - FAILED") + self.failedtests["EAGAIN"] = ex.errno + + def testebusy(self): + # No reliable hardware trigger; use FakeUART + print("Starting Test 2/9: EBUSY - Device Busy Error") + uart = UARTDevice(Port.A, baudrate=9600, timeout=1000) + uart.set_error(EBUSY) + try: + uart.read(1) + print("No error raised.\nCompleted Test 2/9: EBUSY - FAILED") + self.failedtests["EBUSY"] = "No error raised" + except OSError as ex: + if ex.errno == EBUSY: + print("EBUSY can be thrown and caught.\nCompleted Test 2/9: EBUSY - SUCCESSFUL") + self.successfultests += 1 + elif ex.errno == EIO: + print("An unspecified error occurred (EIO).\nCompleted Test 2/9: EBUSY - FAILED") + self.failedtests["EBUSY"] = "EIO - Unspecified Error" + else: + print(f"Another error occurred with code: {ex.errno}.\nCompleted Test 2/9: EBUSY - FAILED") + self.failedtests["EBUSY"] = ex.errno + + def testecanceled(self): + # No reliable hardware trigger; use FakeUART + print("Starting Test 3/9: ECANCELED - Operation Canceled Error") + uart = UARTDevice(Port.A, baudrate=9600, timeout=1000) + uart.set_error(ECANCELED) + try: + uart.read(1) + print("No error raised.\nCompleted Test 3/9: ECANCELED - FAILED") + self.failedtests["ECANCELED"] = "No error raised" + except OSError as ex: + if ex.errno == ECANCELED: + print("ECANCELED can be thrown and caught.\nCompleted Test 3/9: ECANCELED - SUCCESSFUL") + self.successfultests += 1 + elif ex.errno == EIO: + print("An unspecified error occurred (EIO).\nCompleted Test 3/9: ECANCELED - FAILED") + self.failedtests["ECANCELED"] = "EIO - Unspecified Error" + else: + print(f"Another error occurred with code: {ex.errno}.\nCompleted Test 3/9: ECANCELED - FAILED") + self.failedtests["ECANCELED"] = ex.errno + + def testeinval(self): + # Triggered by passing an out-of-range value to motor.control.limits() + print("Starting Test 4/9: EINVAL - Invalid Argument Error") + input("Plug a motor into Port A, then press Enter.") + try: + motor = self.motorclass(Port.A) + motor.control.limits(speed=9999999, acceleration=9999999) + print("No error raised.\nCompleted Test 4/9: EINVAL - FAILED") + self.failedtests["EINVAL"] = "No error raised" + except OSError as ex: + if ex.errno == EINVAL: + print("EINVAL can be thrown and caught.\nCompleted Test 4/9: EINVAL - SUCCESSFUL") + self.successfultests += 1 + elif ex.errno == EIO: + print("An unspecified error occurred (EIO).\nCompleted Test 4/9: EINVAL - FAILED") + self.failedtests["EINVAL"] = "EIO - Unspecified Error" + else: + print(f"Another error occurred with code: {ex.errno}.\nCompleted Test 4/9: EINVAL - FAILED") + self.failedtests["EINVAL"] = ex.errno + + def testeio(self): + # No reliable scriptable trigger (requires physical unplug); use FakeUART + print("Starting Test 5/9: EIO - I/O Error") + uart = UARTDevice(Port.A, baudrate=9600, timeout=1000) + uart.set_error(EIO) + try: + uart.read(1) + print("No error raised.\nCompleted Test 5/9: EIO - FAILED") + self.failedtests["EIO"] = "No error raised" + except OSError as ex: + if ex.errno == EIO: + print("EIO can be thrown and caught.\nCompleted Test 5/9: EIO - SUCCESSFUL") + self.successfultests += 1 + else: + print(f"Another error occurred with code: {ex.errno}.\nCompleted Test 5/9: EIO - FAILED") + self.failedtests["EIO"] = ex.errno + + def testenodev(self): + # Triggered by initializing a motor on an empty port + print("Starting Test 6/9: ENODEV - Device Not Found Error") + input("Make sure Port A doesn't have anything plugged in, then press Enter.") try: - # Try to initialize a motor on an empty port (Port.A) - print("Starting Test 6/9: ENODEV - Device Not Found Error") - input("Make sure Port A doesn't have anything plugged in, then press Enter.") my_motor = self.motorclass(Port.A) print("OS detected a motor when there was nothing. You may have allowed missing motors. This is useful for debugging, but not recommended for production as it can cause issues with device control.") + self.failedtests["ENODEV"] = "No error raised" except OSError as ex: - # If an OSError was raised, check the specific error code if ex.errno == ENODEV: print("There is no motor on this port. ENODEV can be thrown and caught.\nCompleted Test 6/9: ENODEV - SUCCESSFUL") self.successfultests += 1 @@ -47,11 +165,62 @@ class OSDiagnostics: else: print(f"Another error occurred with code: {ex.errno}.\nCompleted Test 6/9: ENODEV - FAILED") self.failedtests["ENODEV"] = ex.errno - def testetimedout(self): - uart = UARTDevice(Port.A, baudrate=9600, timeout=1000) # 1000ms timeout - # Test ETIMEDOUT + + def testeopnotsupp(self): + # No reliable scriptable trigger without specific hardware; use FakeUART + print("Starting Test 7/9: EOPNOTSUPP - Operation Not Supported Error") + uart = UARTDevice(Port.A, baudrate=9600, timeout=1000) + uart.set_error(EOPNOTSUPP) try: - data = uart.read(10) # FakeUART will wait `timeout` ms, then raise OSError(ETIMEDOUT) + uart.read(1) + print("No error raised.\nCompleted Test 7/9: EOPNOTSUPP - FAILED") + self.failedtests["EOPNOTSUPP"] = "No error raised" + except OSError as ex: + if ex.errno == EOPNOTSUPP: + print("EOPNOTSUPP can be thrown and caught.\nCompleted Test 7/9: EOPNOTSUPP - SUCCESSFUL") + self.successfultests += 1 + elif ex.errno == EIO: + print("An unspecified error occurred (EIO).\nCompleted Test 7/9: EOPNOTSUPP - FAILED") + self.failedtests["EOPNOTSUPP"] = "EIO - Unspecified Error" + else: + print(f"Another error occurred with code: {ex.errno}.\nCompleted Test 7/9: EOPNOTSUPP - FAILED") + self.failedtests["EOPNOTSUPP"] = ex.errno + + def testeperm(self): + # Triggered by calling motor.control.limits() while a motor is actively running + print("Starting Test 8/9: EPERM - Operation Not Permitted Error") + input("Plug a motor into Port A, then press Enter.") + try: + motor = self.motorclass(Port.A) + motor.run(500) + wait(50) + motor.control.limits(speed=500, acceleration=1000) + print("No error raised.\nCompleted Test 8/9: EPERM - FAILED") + self.failedtests["EPERM"] = "No error raised" + except OSError as ex: + if ex.errno == EPERM: + print("EPERM can be thrown and caught.\nCompleted Test 8/9: EPERM - SUCCESSFUL") + self.successfultests += 1 + elif ex.errno == EIO: + print("An unspecified error occurred (EIO).\nCompleted Test 8/9: EPERM - FAILED") + self.failedtests["EPERM"] = "EIO - Unspecified Error" + else: + print(f"Another error occurred with code: {ex.errno}.\nCompleted Test 8/9: EPERM - FAILED") + self.failedtests["EPERM"] = ex.errno + finally: + try: + motor.stop() + except Exception: + pass + + def testetimedout(self): + # Triggered by FakeUART (or real UART) timing out on read + print("Starting Test 9/9: ETIMEDOUT - Timed Out Error") + uart = UARTDevice(Port.A, baudrate=9600, timeout=1000) + try: + data = uart.read(10) + print("No error raised.\nCompleted Test 9/9: ETIMEDOUT - FAILED") + self.failedtests["ETIMEDOUT"] = "No error raised" except OSError as ex: if ex.errno == ETIMEDOUT: print("Timed out with synthetic UART device. ETIMEDOUT can be thrown and caught.\nCompleted Test 9/9: ETIMEDOUT - SUCCESSFUL") @@ -62,6 +231,10 @@ class OSDiagnostics: else: print(f"Another error occurred with code: {ex.errno}.\nCompleted Test 9/9: ETIMEDOUT - FAILED") self.failedtests["ETIMEDOUT"] = ex.errno + def print_results(self): - for key, value in self.failedtests(): - print(f"{key}: {value}") \ No newline at end of file + print(f"\n=== Results: {self.successfultests}/9 tests passed ===") + if self.failedtests: + print("Failed tests:") + for key, value in self.failedtests.items(): + print(f" {key}: {value}") \ No newline at end of file From 67dacd2a2fd743a3d419c3ae9f9bc8183842ab00 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Thu, 12 Mar 2026 19:23:36 +0000 Subject: [PATCH 08/38] Updated class structure to allow for extensions later --- diagnostics/os_diagnostics.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/diagnostics/os_diagnostics.py b/diagnostics/os_diagnostics.py index fc71b7f..8c7dea1 100644 --- a/diagnostics/os_diagnostics.py +++ b/diagnostics/os_diagnostics.py @@ -45,7 +45,24 @@ class OSDiagnostics: self.motorclass = motorclass self.successfultests = 0 self.failedtests = {} + def testUerrno(self): + uerrnotestobject = UerrnoTest(self.hub, self.motorclass) + uerrnotestobject.testeagain() + uerrnotestobject.testebusy() + uerrnotestobject.testecanceled() + uerrnotestobject.testeinval() + uerrnotestobject.testeio() + uerrnotestobject.testenodev() + uerrnotestobject.testeopnotsupp() + uerrnotestobject.testeperm() + uerrnotestobject.testetimedout() + uerrnotestobject.print_results() + self.successfultests += uerrnotestobject.successfultests + self.failedtests.update(uerrnotestobject.failedtests) + + +class UerrnoTest(OSDiagnostics): def testeagain(self): # Triggered by calling multitask() nested inside another multitask() print("Starting Test 1/9: EAGAIN - Try Again Error") From 3bb4a80a4654a6ccf018b28d10891028b5a159fb Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Fri, 13 Mar 2026 12:49:13 +0000 Subject: [PATCH 09/38] Update diagnostics/os_diagnostics.py --- diagnostics/os_diagnostics.py | 47 +++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/diagnostics/os_diagnostics.py b/diagnostics/os_diagnostics.py index 8c7dea1..106d0db 100644 --- a/diagnostics/os_diagnostics.py +++ b/diagnostics/os_diagnostics.py @@ -1,5 +1,6 @@ from pybricks.parameters import Port from uerrno import EAGAIN, EBUSY, ECANCELED, EINVAL, EIO, ENODEV, EOPNOTSUPP, EPERM, ETIMEDOUT +import uio from pybricks.iodevices import UARTDevice as _UARTDevice from pybricks.tools import wait, multitask, run_task @@ -62,7 +63,12 @@ class OSDiagnostics: -class UerrnoTest(OSDiagnostics): +class UerrnoTest: + def __init__(self, hub, motorclass): + self.hub = hub + self.motorclass = motorclass + self.successfultests = 0 + self.failedtests = {} def testeagain(self): # Triggered by calling multitask() nested inside another multitask() print("Starting Test 1/9: EAGAIN - Try Again Error") @@ -254,4 +260,41 @@ class UerrnoTest(OSDiagnostics): if self.failedtests: print("Failed tests:") for key, value in self.failedtests.items(): - print(f" {key}: {value}") \ No newline at end of file + print(f" {key}: {value}") +class UIOTest(OSDiagnostics): + def __init__(self, hub, motorclass): + self.hub = hub + self.motorclass = motorclass + self.successfultests = 0 + self.failedtests = {} + # uio contains BytesIO, StringIO, and FileIO, but due to SPIKE Prime's lack of a filesystem, the test will omit FileIO + def testbytesio(self): + failed = False + try: + buffer = io.BytesIO() + + buffer.write(b'Hello, ') + buffer.write(b'Pybricks!') + + current_content = buffer.getvalue() + print(f"Buffer content (via getvalue()): {current_content}") + # Output: b'Hello, Pybricks!' + + print(f"Current cursor position (should be 16): {buffer.tell()}") + # TODO: After testing that BytesIO actually works on this system, add checks to make sure that the outputs match what they should. + buffer.seek(0) + + read_data = buffer.read() + print(f"Read data (via read()): {read_data}") + # Output: Read data (via read()): b'Hello, Pybricks!' + + print(f"Current cursor position after reading: {buffer.tell()}") # Output should be 16 + + buffer.close() + print("Buffer was closed successfully.") + print("Completed Test 1/2: BytesIO - SUCCESSFUL") + self.successfultests += 1 + except Exception as ex: + print("An unexpected error occured.") + print("Completed Test 1/2: BytesIO - FAILED") + self.failedtests["BytesIO"] = ex.errno \ No newline at end of file From 00c47aab66530983caca7e70e0aa899dd6c56667 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Fri, 13 Mar 2026 12:59:36 +0000 Subject: [PATCH 10/38] Finish UIO --- diagnostics/os_diagnostics.py | 54 +++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/diagnostics/os_diagnostics.py b/diagnostics/os_diagnostics.py index 106d0db..10557a3 100644 --- a/diagnostics/os_diagnostics.py +++ b/diagnostics/os_diagnostics.py @@ -60,6 +60,11 @@ class OSDiagnostics: uerrnotestobject.print_results() self.successfultests += uerrnotestobject.successfultests self.failedtests.update(uerrnotestobject.failedtests) + def testUIO(self): + uiotestobject = UIOTest(self.hub, self.motorclass) + uiotestobject.print_results() + self.successfultests += uiotestobject.successfultests + self.failedtests.update(uiotestobject.failedtests) @@ -267,28 +272,25 @@ class UIOTest(OSDiagnostics): self.motorclass = motorclass self.successfultests = 0 self.failedtests = {} - # uio contains BytesIO, StringIO, and FileIO, but due to SPIKE Prime's lack of a filesystem, the test will omit FileIO + # uio contains BytesIO, StringIO, and FileIO, but due to SPIKE Prime's lack of a filesystem, the test will omit FileIO and only include BytesIO and StringIO def testbytesio(self): - failed = False try: buffer = io.BytesIO() buffer.write(b'Hello, ') - buffer.write(b'Pybricks!') + buffer.write(b'Pybricks byte stream!') current_content = buffer.getvalue() print(f"Buffer content (via getvalue()): {current_content}") - # Output: b'Hello, Pybricks!' - print(f"Current cursor position (should be 16): {buffer.tell()}") + print(f"Current cursor position: {buffer.tell()}") # TODO: After testing that BytesIO actually works on this system, add checks to make sure that the outputs match what they should. buffer.seek(0) read_data = buffer.read() print(f"Read data (via read()): {read_data}") - # Output: Read data (via read()): b'Hello, Pybricks!' - print(f"Current cursor position after reading: {buffer.tell()}") # Output should be 16 + print(f"Current cursor position after reading: {buffer.tell()}") buffer.close() print("Buffer was closed successfully.") @@ -297,4 +299,40 @@ class UIOTest(OSDiagnostics): except Exception as ex: print("An unexpected error occured.") print("Completed Test 1/2: BytesIO - FAILED") - self.failedtests["BytesIO"] = ex.errno \ No newline at end of file + self.failedtests["BytesIO"] = ex.errno + def teststringio(self): + try: + buffer = io.StringIO() + + buffer.write('Hello, ') + buffer.write('Pybricks string stream!') + + current_content = buffer.getvalue() + print(f"Buffer content (via getvalue()): {current_content}") + + print(f"Current cursor position: {buffer.tell()}") + # TODO: After testing that StringIO actually works on this system, add checks to make sure that the outputs match what they should. + buffer.seek(0) + + read_data = buffer.read() + print(f"Read data (via read()): {read_data}") + + print(f"Current cursor position after reading: {buffer.tell()}") + + buffer.close() + print("Buffer was closed successfully.") + print("Completed Test 2/2: StringIO - SUCCESSFUL") + self.successfultests += 1 + except Exception as ex: + print("An unexpected error occured.") + print("Completed Test 2/2: StringIO - FAILED") + self.failedtests["StringIO"] = ex.errno + + def print_results(self): + self.testbytesio() + self.teststringio() + print(f"\n=== Results: {self.successfultests}/2 tests passed ===") + if self.failedtests: + print("Failed tests:") + for key, value in self.failedtests.items(): + print(f" {key}: {value}") \ No newline at end of file From 82d498a04c7eeb4f4dc67b5a7d7771de8faf82ee Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Fri, 13 Mar 2026 13:28:40 +0000 Subject: [PATCH 11/38] Update diagnostics/os_diagnostics.py --- diagnostics/os_diagnostics.py | 83 +++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/diagnostics/os_diagnostics.py b/diagnostics/os_diagnostics.py index 10557a3..b21631a 100644 --- a/diagnostics/os_diagnostics.py +++ b/diagnostics/os_diagnostics.py @@ -1,6 +1,7 @@ from pybricks.parameters import Port from uerrno import EAGAIN, EBUSY, ECANCELED, EINVAL, EIO, ENODEV, EOPNOTSUPP, EPERM, ETIMEDOUT import uio +import ujson from pybricks.iodevices import UARTDevice as _UARTDevice from pybricks.tools import wait, multitask, run_task @@ -65,6 +66,11 @@ class OSDiagnostics: uiotestobject.print_results() self.successfultests += uiotestobject.successfultests self.failedtests.update(uiotestobject.failedtests) + def testUJSON(self): + ujsontestobject = UJSONTest(self.hub, self.motorclass) + ujsontestobject.print_results() + self.successfultests += ujsontestobject.successfultests + self.failedtests.update(ujsontestobject.failedtests) @@ -332,6 +338,83 @@ class UIOTest(OSDiagnostics): self.testbytesio() self.teststringio() print(f"\n=== Results: {self.successfultests}/2 tests passed ===") + if self.failedtests: + print("Failed tests:") + for key, value in self.failedtests.items(): + print(f" {key}: {value}") +class UJSONTest: + def __init__(self, hub, motorclass): + self.hub = hub + self.motorclass = motorclass + self.successfultests = 0 + self.failedtests = {} + + # Tests ujson.dumps() and ujson.loads() + def testdumpsloads(self): + try: + original = {"robot": "Pybricks", "speed": 500, "active": True} + + json_str = ujson.dumps(original) + print(f"Serialized (via dumps()): {json_str}") + + restored = ujson.loads(json_str) + print(f"Deserialized (via loads()): {restored}") + + # TODO: After confirming dumps/loads works on this system, add equality tests + + print("Completed Test 1/3: dumps/loads - SUCCESSFUL") + self.successfultests += 1 + except Exception as ex: + print("An unexpected error occurred.") + print("Completed Test 1/3: dumps/loads - FAILED") + self.failedtests["dumps/loads"] = getattr(ex, "errno", str(ex)) + + # Tests ujson.loads() raising ValueError on malformed input + def testloadsinvalid(self): + try: + ujson.loads("{not valid json}") + print("No error raised.") + print("Completed Test 2/3: loads invalid - FAILED") + self.failedtests["loads_invalid"] = "No error raised" + except ValueError: + print("ValueError raised on malformed JSON, as expected.") + print("Completed Test 2/3: loads invalid - SUCCESSFUL") + self.successfultests += 1 + except Exception as ex: + print("An unexpected error occurred.") + print("Completed Test 2/3: loads invalid - FAILED") + self.failedtests["loads_invalid"] = getattr(ex, "errno", str(ex)) + + # Tests ujson.dump() and ujson.load() using a uio.StringIO stream + def testdumpload(self): + try: + original = {"hub": "SPIKE Prime", "port": "A", "value": 42} + + stream = uio.StringIO() + ujson.dump(original, stream) + print(f"Serialized to stream (via dump()): {stream.getvalue()}") + + stream.seek(0) + restored = ujson.load(stream) + print(f"Deserialized from stream (via load()): {restored}") + + stream.close() + + # TODO: After confirming dump/load works on this system, add + # equality assertions to verify round-trip fidelity. + + print("Completed Test 3/3: dump/load (stream) - SUCCESSFUL") + self.successfultests += 1 + except Exception as ex: + print("An unexpected error occurred.") + print("Completed Test 3/3: dump/load (stream) - FAILED") + self.failedtests["dump/load"] = getattr(ex, "errno", str(ex)) + + def print_results(self): + self.testdumpsloads() + self.testloadsinvalid() + self.testdumpload() + print(f"\n=== Results: {self.successfultests}/3 tests passed ===") if self.failedtests: print("Failed tests:") for key, value in self.failedtests.items(): From f0b728a43b45ee625c3f64081be04c387ac569f2 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Fri, 13 Mar 2026 13:33:09 +0000 Subject: [PATCH 12/38] added stuff (json, some umath) --- diagnostics/os_diagnostics.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/diagnostics/os_diagnostics.py b/diagnostics/os_diagnostics.py index b21631a..b579142 100644 --- a/diagnostics/os_diagnostics.py +++ b/diagnostics/os_diagnostics.py @@ -71,6 +71,11 @@ class OSDiagnostics: ujsontestobject.print_results() self.successfultests += ujsontestobject.successfultests self.failedtests.update(ujsontestobject.failedtests) + def testUMath(self): + umathtestobject = UMathTest(self.hub, self.motorclass) + umathtestobject.print_results() + self.successfultests += umathtestobject.successfultests + self.failedtests.update(umathtestobject.failedtests) @@ -415,6 +420,20 @@ class UJSONTest: self.testloadsinvalid() self.testdumpload() print(f"\n=== Results: {self.successfultests}/3 tests passed ===") + if self.failedtests: + print("Failed tests:") + for key, value in self.failedtests.items(): + print(f" {key}: {value}") +class UMathTest: + def __init__(self, hub, motorclass): + self.hub = hub + self.motorclass = motorclass + self.successfultests = 0 + self.failedtests = {} + def test_math(self): + + def print_results(self): + print(f"\n=== Results: {self.successfultests}/0 tests passed ===") if self.failedtests: print("Failed tests:") for key, value in self.failedtests.items(): From 79a992ac72c1f56883ca9bf7ad1c46fc868d0d11 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Sat, 14 Mar 2026 23:03:43 +0000 Subject: [PATCH 13/38] Finished umath, urandom, and uselect --- diagnostics/os_diagnostics.py | 414 +++++++++++++++++++++++++++++++++- 1 file changed, 413 insertions(+), 1 deletion(-) diff --git a/diagnostics/os_diagnostics.py b/diagnostics/os_diagnostics.py index b579142..6acdf27 100644 --- a/diagnostics/os_diagnostics.py +++ b/diagnostics/os_diagnostics.py @@ -2,6 +2,8 @@ from pybricks.parameters import Port from uerrno import EAGAIN, EBUSY, ECANCELED, EINVAL, EIO, ENODEV, EOPNOTSUPP, EPERM, ETIMEDOUT import uio import ujson +import urandom +from urandom import random from pybricks.iodevices import UARTDevice as _UARTDevice from pybricks.tools import wait, multitask, run_task @@ -47,7 +49,7 @@ class OSDiagnostics: self.motorclass = motorclass self.successfultests = 0 self.failedtests = {} - def testUerrno(self): + def testUErrno(self): uerrnotestobject = UerrnoTest(self.hub, self.motorclass) uerrnotestobject.testeagain() uerrnotestobject.testebusy() @@ -76,7 +78,29 @@ class OSDiagnostics: umathtestobject.print_results() self.successfultests += umathtestobject.successfultests self.failedtests.update(umathtestobject.failedtests) + def testURandom(self): + urandtestobject = URandomTest(self.hub, self.motorclass) + urandtestobject.print_results() + self.successfultests += urandtestobject.successfultests + self.failedtests.update(urandtestobject.failedtests) + def testUSelect(self): + uselecttestobject = USelectTest(self.hub, self.motorclass) + uselecttestobject.print_results() + self.successfultests += uselecttestobject.successfultests + self.failedtests.update(uselecttestobject.failedtests) + + def testUStruct(self): + ustructtestobject = UStructTest(self.hub, self.motorclass) + ustructtestobject.print_results() + self.successfultests += ustructtestobject.successfultests + self.failedtests.update(ustructtestobject.failedtests) + + def testUSys(self): + usystestobject = USysTest(self.hub, self.motorclass) + usystestobject.print_results() + self.successfultests += usystestobject.successfultests + self.failedtests.update(usystestobject.failedtests) class UerrnoTest: @@ -431,7 +455,395 @@ class UMathTest: self.successfultests = 0 self.failedtests = {} def test_math(self): + if(umath.ceil(87.21) == 88): + self.successfultests += 1 + else: + self.failedtests["ceilpos"] = "Failed" + + if(umath.floor(14.61) == 14): + self.successfultests += 1 + else: + self.failedtests["floorpos"] = "Failed" + + if(umath.ceil(-87.21) == -87): + self.successfultests += 1 + else: + self.failedtests["ceilneg"] = "Failed" + + if(umath.floor(-14.61) == -15): + self.successfultests += 1 + else: + self.failedtests["floorneg"] = "Failed" + + if(umath.trunc(33.22) == 33): + self.successfultests += 1 + else: + self.failedtests["truncpos"] = "Failed" + + if(umath.trunc(-33.22) == -33): + self.successfultests += 1 + else: + self.failedtests["truncneg"] = "Failed" + + if(umath.fmod(6040, 3) == 1): + self.successfultests += 1 + else: + self.failedtests["fmod"] = "Failed" + + if(umath.fabs(88273) == 88273): + self.successfultests += 1 + else: + self.failedtests["fabspos"] = "Failed" + if(umath.fabs(-27482) == 27482): + self.successfultests += 1 + else: + self.failedtests["fabsneg"] = "Failed" + + if(umath.fabs(-2742.233) == 2742.233): + self.successfultests += 1 + else: + self.failedtests["fabsflt"] = "Failed" + + if(umath.copysign(3928, -182) == -3928): + self.successfultests += 1 + else: + self.failedtests["copysign"] = "Failed" + + if(umath.exp(umath.log(1)) == 1): + self.successfultests += 2 + else: + self.failedtests["eexp"] = "Failed" + self.failedtests["ln"] = "Failed" + print(umath.e) + print("\n") + if(input("Press Y and press enter if the value displayed above is around 2.718282. Type N and press Enter if it is not:") == "Y"): + self.successfultests += 1 + else: + self.failedtests["e"] = "Failed" + + if(umath.pow(19, 7) == 893871739): + self.successfultests += 1 + else: + self.failedtests["pow"] = "Failed" + + if(umath.sqrt(242064) == 492): + self.successfultests += 1 + else: + self.failedtests["sqrt"] = "Failed" + + print(umath.pi) + print("\n") + if(input("Press Y and press enter if the value displayed above is around 3.14159265. Type N and press Enter if it is not:") == "Y"): + self.successfultests += 1 + else: + self.failedtests["pi"] = "Failed" + + EPSILON = 1e-9 + if abs(umath.degrees(umath.pi * 3) - 540) < EPSILON: + self.successfultests += 1 + else: + self.failedtests["degrees"] = "Failed" + + if abs(umath.radians(270) - umath.pi * 1.5) < EPSILON: + self.successfultests += 1 + else: + self.failedtests["radians"] = "Failed" + + if(abs(umath.sin(umath.pi)) < EPSILON): + self.successfultests += 1 + else: + self.failedtests["sin"] = "Failed" + + if(abs(umath.asin(1) - umath.pi * 0.5) < EPSILON): + self.successfultests += 1 + else: + self.failedtests["asin"] = "Failed" + + if(abs(umath.cos(umath.pi) + 1) < EPSILON): + self.successfultests += 1 + else: + self.failedtests["cos"] = "Failed" + + if(abs(umath.acos(1)) < EPSILON): + self.successfultests += 1 + else: + self.failedtests["acos"] = "Failed" + + if(abs(umath.tan(umath.pi)) < EPSILON): + self.successfultests += 1 + else: + self.failedtests["tan"] = "Failed" + + if(abs(umath.atan(1) - umath.pi * 0.25) < EPSILON): + self.successfultests += 1 + else: + self.failedtests["atan"] = "Failed" + + if(abs(umath.atan2(1, -1) - umath.pi * 0.75) < EPSILON): + self.successfultests += 1 + else: + self.failedtests["atan2"] = "Failed" + + infinitenum = float('inf') + finitenum = 123456 + if(umath.isfinite(finitenum) == True): + self.successfultests += 1 + else: + self.failedtests["isfinitefinite"] = "Failed" + + if(umath.isfinite(infinitenum) == False): + self.successfultests += 1 + else: + self.failedtests["isfiniteinfinite"] = "Failed" + + if(umath.isinfinite(finitenum) == False): + self.successfultests += 1 + else: + self.failedtests["isinfinitefinite"] = "Failed" + + if(umath.isinfinite(infinitenum) == True): + self.successfultests += 1 + else: + self.failedtests["isinfiniteinfinite"] = "Failed" + nannum = float("nan") + + if(umath.isnan(nannum) == True): + self.successfultests += 1 + else: + self.failedtests["isnannan"] = "Failed" + + if(umath.isnan(finitenum) == False): + self.successfultests += 1 + else: + self.failedtests["isnannotnan"] = "Failed" + + frac, integer = umath.modf(87.21) + if integer == 87.0 and abs(frac - 0.21) < EPSILON: + self.successfultests += 1 + else: + self.failedtests["modf"] = "Failed" + + mantissa, exponent = umath.frexp(64) + if mantissa == 0.5 and exponent == 7: + self.successfultests += 1 + else: + self.failedtests["frexp"] = "Failed" + + result = umath.ldexp(0.5, 7) + if result == 64: + self.successfultests += 1 + else: + self.failedtests["ldexp"] = "Failed" + def print_results(self): + self.test_math() + print(f"\n=== Results: {self.successfultests}/34 tests passed ===") + if self.failedtests: + print("Failed tests:") + for key, value in self.failedtests.items(): + print(f" {key}: {value}") +class URandomTest: + def __init__(self, hub, motorclass): + self.hub = hub + self.motorclass = motorclass + self.successfultests = 0 + self.failedtests = {} + def testrandom(self): + NUM_SAMPLES = 1000000 + NUM_BUCKETS = 10 + CHART_WIDTH = 50 + SAMPLE_PEEK = 20 + + samples = [random() for _ in range(NUM_SAMPLES)] + + bucket_counts = [0] * NUM_BUCKETS + for x in samples: + i = int(x * NUM_BUCKETS) + if i == NUM_BUCKETS: + i -= 1 + bucket_counts[i] += 1 + + mean = sum(samples) / NUM_SAMPLES + ideal = NUM_SAMPLES / NUM_BUCKETS + max_count = max(bucket_counts) + + print("=" * 66) + print(" random() distribution test n=" + str(NUM_SAMPLES)) + print("=" * 66) + print("") + print(" Range Count Bar") + print(" -------------- ----- " + "-" * CHART_WIDTH) + + for i in range(NUM_BUCKETS): + lo = i / NUM_BUCKETS + hi = (i + 1) / NUM_BUCKETS + count = bucket_counts[i] + bar = round(count / max_count * CHART_WIDTH) + dev = abs(count - ideal) / ideal + if dev <= 0.10: + marker = "#" + elif dev <= 0.20: + marker = "+" + else: + marker = "." + lo_str = "{:.2f}".format(lo) + hi_str = "{:.2f}".format(hi) + count_str = str(count).rjust(10) + print(" " + lo_str + " - " + hi_str + " " + count_str + " " + marker * bar) + + print("") + print(" Samples : " + str(NUM_SAMPLES)) + print(" Mean : " + "{:.6f}".format(mean) + " (ideal 0.500000)") + print(" Min : " + "{:.6f}".format(min(samples))) + print(" Max : " + "{:.6f}".format(max(samples))) + print(" Legend : # within 10% + within 20% . beyond 20%") + print("") + print(" First " + str(SAMPLE_PEEK) + " raw values:") + + row = " " + for idx, val in enumerate(samples[:SAMPLE_PEEK], 1): + row += "{:.4f} ".format(val) + if idx % 5 == 0: + print(row) + row = " " + if row.strip(): + print(row) + + print("") + if(abs(0.5 - mean) < 0.02): + print("Random Distribution Test: SUCCESSFUL") + self.successfultests += 1 + else: + print("Random Distribution Test: FAILED") + self.failedtests["randomdistribution"] = "Too much error" + def test_other_rands(self): + N = 1000 + choicelist = ["apple", "banana", "grape", "orange"] + + randint_vals = [urandom.randint(1, 100) for _ in range(N)] + randint_mean = sum(randint_vals) / N + print("Randint mean (ideally 50.5): {:.4f}".format(randint_mean)) + if abs(50.5 - randint_mean) < 1.0: + print("Randint Test: SUCCESSFUL") + self.successfultests += 1 + else: + print("Randint Test: FAILED") + self.failedtests["randint"] = "Too much error" + + getrandbits_vals = [urandom.getrandbits(7) for _ in range(N)] + getrandbits_mean = sum(getrandbits_vals) / N + print("Getrandbits(7) mean (ideally 63.5): {:.4f}".format(getrandbits_mean)) + if abs(63.5 - getrandbits_mean) < 1.3: + print("Getrandbits Test: SUCCESSFUL") + self.successfultests += 1 + else: + print("Getrandbits Test: FAILED") + self.failedtests["getrandbits"] = "Too much error" + + randrange_vals = [urandom.randrange(1, 1001, 4) for _ in range(N)] + randrange_mean = sum(randrange_vals) / N + print("Randrange(1,1001,4) mean (ideally 501.0): {:.4f}".format(randrange_mean)) + if abs(501.0 - randrange_mean) < 10.0: + print("Randrange Test: SUCCESSFUL") + self.successfultests += 1 + else: + print("Randrange Test: FAILED") + self.failedtests["randrange"] = "Too much error" + + uniform_vals = [urandom.uniform(1, 10) for _ in range(N)] + uniform_mean = sum(uniform_vals) / N + print("Uniform(1,10) mean (ideally 5.5): {:.4f}".format(uniform_mean)) + if abs(5.5 - uniform_mean) < 0.09: + print("Uniform Test: SUCCESSFUL") + self.successfultests += 1 + else: + print("Uniform Test: FAILED") + self.failedtests["uniform"] = "Too much error" + + choice_counts = {} + for item in choicelist: + choice_counts[item] = 0 + for _ in range(N): + choice_counts[urandom.choice(choicelist)] += 1 + ideal_pct = 100.0 / len(choicelist) + print("Choice distribution (ideally {:.1f}% each):".format(ideal_pct)) + choice_ok = True + for item in choicelist: + pct = choice_counts[item] / N * 100 + print(" " + item + ": {:.1f}%".format(pct)) + if abs(pct - ideal_pct) > 5.0: + choice_ok = False + if choice_ok: + print("Choice Test: SUCCESSFUL") + self.successfultests += 1 + else: + print("Choice Test: FAILED") + self.failedtests["choice"] = "Too much error" + def print_results(self): + self.testrandom() + self.test_other_rands() + print(f"\n=== Results: {self.successfultests}/6 tests passed ===") + if self.failedtests: + print("Failed tests:") + for key, value in self.failedtests.items(): + print(f" {key}: {value}") + +class USelectTest: + def __init__(self, hub, motorclass): + self.hub = hub + self.motorclass = motorclass + self.successfultests = 0 + self.failedtests = {} + + def print_results(self): + # Register the standard input so we can read keyboard presses. + keyboard = poll() + keyboard.register(stdin) + print("Type a few keys, make sure you get back what characteryou typed, then press Escape.") + while True: + # Check if a key has been pressed. + if keyboard.poll(0): + + # Read the key and print it. + key = stdin.read(1) + if key == '\x1b': + print("Escape key pressed. Exiting...") + break + else: + print("Pressed:", key) + if(input("Input Y if the results were accurate:") == "Y"): + print(f"\n=== Results: 1/1 tests passed ===") + self.successfultests += 1 + else: + self.failedtests["input"] = "Unsatisfied" + if self.failedtests: + print("Failed tests:") + for key, value in self.failedtests.items(): + print(f" {key}: {value}") + + +class UStructTest: + def __init__(self, hub, motorclass): + self.hub = hub + self.motorclass = motorclass + self.successfultests = 0 + self.failedtests = {} + + def print_results(self): + print(f"\n=== Results: {self.successfultests}/0 tests passed ===") + if self.failedtests: + print("Failed tests:") + for key, value in self.failedtests.items(): + print(f" {key}: {value}") + + +class USysTest: + def __init__(self, hub, motorclass): + self.hub = hub + self.motorclass = motorclass + self.successfultests = 0 + self.failedtests = {} + def print_results(self): print(f"\n=== Results: {self.successfultests}/0 tests passed ===") if self.failedtests: From c1808cfa75696fd4c83eff2ee1a3c0afdc3b97ad Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Tue, 17 Mar 2026 17:40:53 +0000 Subject: [PATCH 14/38] Working on ustruct --- diagnostics/os_diagnostics.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/diagnostics/os_diagnostics.py b/diagnostics/os_diagnostics.py index 6acdf27..a90fdd9 100644 --- a/diagnostics/os_diagnostics.py +++ b/diagnostics/os_diagnostics.py @@ -830,6 +830,27 @@ class UStructTest: self.failedtests = {} def print_results(self): + packed = struct.pack('ii', 42, 100) + print(f'Packed bytes using pack(): {packed}') + + unpacked = struct.unpack('ii', packed) + print(f'Unpacked values using unpack(): {unpacked}') + print(unpacked == (42, 100)) + format_string = 'hhl' + size = struct.calcsize(format_string) + + buffer = bytearray(size) + + struct.pack_into(format_string, buffer, 0, 5, 10, 15) + + print("Packed buffer using pack_into():", buffer) + + unpackedfrom = struct.unpack_from(format_string, buffer, 0) + print("Unpacked buffer using unpack_from():", unpackedfrom) + if(unpackedfrom == (5, 10, 15)): + print("Completed Test 2/2: pack_into - SUCCESSFUL") + else: + print("Completed Test 2/2: pack_into - FAILED") print(f"\n=== Results: {self.successfultests}/0 tests passed ===") if self.failedtests: print("Failed tests:") From f37df8b99a2066c434e6d72f9b81bc67a749b8a9 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Wed, 18 Mar 2026 12:18:08 +0000 Subject: [PATCH 15/38] Update diagnostics/os_diagnostics.py --- diagnostics/os_diagnostics.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/diagnostics/os_diagnostics.py b/diagnostics/os_diagnostics.py index a90fdd9..94eb6cc 100644 --- a/diagnostics/os_diagnostics.py +++ b/diagnostics/os_diagnostics.py @@ -836,6 +836,12 @@ class UStructTest: unpacked = struct.unpack('ii', packed) print(f'Unpacked values using unpack(): {unpacked}') print(unpacked == (42, 100)) + if(unpacked == (42, 100)): + print("Completed Test 1/2: pack - SUCCESSFUL") + self.successfultests += 1 + else: + print("Completed Test 1/2: pack - FAILED") + self.failedtests["pack"] = "Failed" format_string = 'hhl' size = struct.calcsize(format_string) @@ -849,9 +855,11 @@ class UStructTest: print("Unpacked buffer using unpack_from():", unpackedfrom) if(unpackedfrom == (5, 10, 15)): print("Completed Test 2/2: pack_into - SUCCESSFUL") + self.successfultests += 1 else: print("Completed Test 2/2: pack_into - FAILED") - print(f"\n=== Results: {self.successfultests}/0 tests passed ===") + self.failedtests["pack_into"] = "Failed" + print(f"\n=== Results: {self.successfultests}/2 tests passed ===") if self.failedtests: print("Failed tests:") for key, value in self.failedtests.items(): From aa133253d028933a7e47fd3863a87e2dfa655679 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Thu, 19 Mar 2026 18:06:02 +0000 Subject: [PATCH 16/38] Removed printVersionDiagnostics. Moving to osdiagnostics under class UsysTest --- diagnostics/micropython_diagnostics.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/diagnostics/micropython_diagnostics.py b/diagnostics/micropython_diagnostics.py index 27cd2f0..61ea31a 100644 --- a/diagnostics/micropython_diagnostics.py +++ b/diagnostics/micropython_diagnostics.py @@ -4,11 +4,6 @@ from pybricks import version class MicroPythonDiagnostics: def __init__(self, hub): pass - def printVersionDiagnostics(): - print("[Hub Diagnostics - MicroPython - Version] Hub version information:", version) - print("[Hub Diagnostics - MicroPython - Version] MicroPython version:", usys.version) - print("[Hub Diagnostics - MicroPython - Version] Pybricks version information:", usys.version_info) - print("[Hub Diagnostics - MicroPython - Version] MicroPython information:", usys.implementation) def performMemoryDiagnostics(): print("[Hub Diagnostics - MicroPython - Memory] Memory information (retrieved from the MicroPython environment):") micropython.mem_info(1) @@ -37,6 +32,5 @@ class MicroPythonDiagnostics: except MemoryError: print("[Hub Diagnostics - MicroPython - Memory] [FAIL] Allocation failed. The heap failed to unlock.") def printAll(): - printVersionDiagnostics() performMemoryDiagnostics() \ No newline at end of file From 1a8d04de9107009939ae2eb17c5065bb8aa6c9c1 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Thu, 19 Mar 2026 18:09:02 +0000 Subject: [PATCH 17/38] Began UsysTest --- diagnostics/os_diagnostics.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/diagnostics/os_diagnostics.py b/diagnostics/os_diagnostics.py index 94eb6cc..cb81ce9 100644 --- a/diagnostics/os_diagnostics.py +++ b/diagnostics/os_diagnostics.py @@ -872,8 +872,17 @@ class USysTest: self.motorclass = motorclass self.successfultests = 0 self.failedtests = {} - + def printVersionDiagnostics(self): + try: + print("[Hub Diagnostics - MicroPython - Version] Hub version information:", version) + print("[Hub Diagnostics - MicroPython - Version] MicroPython version:", usys.version) + print("[Hub Diagnostics - MicroPython - Version] Pybricks version information:", usys.version_info) + print("[Hub Diagnostics - MicroPython - Version] MicroPython information:", usys.implementation) + successfultests += 4 + except Exception as ex: + failedtests["versioninfo"] = ex.errno def print_results(self): + printVersionDiagnostics() print(f"\n=== Results: {self.successfultests}/0 tests passed ===") if self.failedtests: print("Failed tests:") From a72af812f614f8d5655262bd4311a72388de2865 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Thu, 19 Mar 2026 18:33:33 +0000 Subject: [PATCH 18/38] Update diagnostics/os_diagnostics.py --- diagnostics/os_diagnostics.py | 60 +++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/diagnostics/os_diagnostics.py b/diagnostics/os_diagnostics.py index cb81ce9..d02ceb6 100644 --- a/diagnostics/os_diagnostics.py +++ b/diagnostics/os_diagnostics.py @@ -2,6 +2,10 @@ from pybricks.parameters import Port from uerrno import EAGAIN, EBUSY, ECANCELED, EINVAL, EIO, ENODEV, EOPNOTSUPP, EPERM, ETIMEDOUT import uio import ujson +import umath +import uselect +import ustruct +import usys import urandom from urandom import random from pybricks.iodevices import UARTDevice as _UARTDevice @@ -799,7 +803,7 @@ class USelectTest: # Register the standard input so we can read keyboard presses. keyboard = poll() keyboard.register(stdin) - print("Type a few keys, make sure you get back what characteryou typed, then press Escape.") + print("Type a few keys, make sure you get back what character you typed, then press Escape.") while True: # Check if a key has been pressed. if keyboard.poll(0): @@ -878,12 +882,62 @@ class USysTest: print("[Hub Diagnostics - MicroPython - Version] MicroPython version:", usys.version) print("[Hub Diagnostics - MicroPython - Version] Pybricks version information:", usys.version_info) print("[Hub Diagnostics - MicroPython - Version] MicroPython information:", usys.implementation) - successfultests += 4 + self.successfultests += 1 + print("Completed Test 1/4: versioninfo - SUCCESSFUL") except Exception as ex: failedtests["versioninfo"] = ex.errno + print("Completed Test 4/4: versioninfo - FAILED") + def teststdin(self): + # Register the standard input so we can read keyboard presses. + keyboard = poll() + keyboard.register(stdin) + print("Type a few keys, make sure you get back what character you typed, then press Escape.") + while True: + # Check if a key has been pressed. + if keyboard.poll(0): + + # Read the key and print it. + key = stdin.read(1) + if key == '\x1b': + print("Escape key pressed. Exiting...") + break + else: + print("Pressed:", key) + if(input("Input Y if the results were accurate:") == "Y"): + print("Completed Test 1/4: stdin - SUCCESSFUL") + self.successfultests += 1 + else: + self.failedtests["stdin"] = "Unsatisfied" + print("Completed Test 1/4: stdin - FAILED") + if self.failedtests: + print("Failed tests:") + for key, value in self.failedtests.items(): + print(f" {key}: {value}") + def teststdout(self): + usys.stdout.flush() + try: + usys.stdout.buffer.write(b"stdout worked!") + print("Completed Test 2/4: stdout - SUCCESSFUL") + self.successfultests += 1 + except Exception as ex: + print("Completed Test 2/4: stdout - FAILED") + self.failedtests["stdout"] = ex.errno + def teststderr(self): + usys.stderr.flush() + try: + usys.stderr.buffer.write(b"stderr worked!") + print("Completed Test 3/4: stderr - SUCCESSFUL") + self.successfultests += 1 + except Exception as ex: + print("Completed Test 3/4: stderr - FAILED") + self.failedtests["stderr"] = ex.errno + def print_results(self): + self.teststdin() + self.teststdout() + self.teststderr() printVersionDiagnostics() - print(f"\n=== Results: {self.successfultests}/0 tests passed ===") + print(f"\n=== Results: {self.successfultests}/3 tests passed ===") if self.failedtests: print("Failed tests:") for key, value in self.failedtests.items(): From 9ab873baf60dde2d185c90cbd19b9a66e1c4bce5 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Thu, 19 Mar 2026 18:33:54 +0000 Subject: [PATCH 19/38] Update diagnostics/micropython_diagnostics.py --- diagnostics/micropython_diagnostics.py | 1 - 1 file changed, 1 deletion(-) diff --git a/diagnostics/micropython_diagnostics.py b/diagnostics/micropython_diagnostics.py index 61ea31a..3a5a0c4 100644 --- a/diagnostics/micropython_diagnostics.py +++ b/diagnostics/micropython_diagnostics.py @@ -1,4 +1,3 @@ -import usys import micropython from pybricks import version class MicroPythonDiagnostics: From 3901f62db2ed2761f56fbdb93dcc36b462e171fc Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Thu, 19 Mar 2026 18:38:34 +0000 Subject: [PATCH 20/38] Add tests/testgc.py --- tests/testgc.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tests/testgc.py diff --git a/tests/testgc.py b/tests/testgc.py new file mode 100644 index 0000000..d81d993 --- /dev/null +++ b/tests/testgc.py @@ -0,0 +1,10 @@ +input("gc") +try: + import gc +except Exception as ex: + print(ex.errno) +input("ugc") +try: + import ugc +except Exception as ex: + print(ex.errno) From f0d862014ed2c5bab8764edcba969d99ed8809ba Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Fri, 20 Mar 2026 21:57:25 +0000 Subject: [PATCH 21/38] Tested properly on a bobot --- diagnostics/os_diagnostics.py | 168 ++++++++++++++++------------------ 1 file changed, 80 insertions(+), 88 deletions(-) diff --git a/diagnostics/os_diagnostics.py b/diagnostics/os_diagnostics.py index d02ceb6..034f2b7 100644 --- a/diagnostics/os_diagnostics.py +++ b/diagnostics/os_diagnostics.py @@ -8,10 +8,11 @@ import ustruct import usys import urandom from urandom import random -from pybricks.iodevices import UARTDevice as _UARTDevice from pybricks.tools import wait, multitask, run_task - - +from pybricks.hubs import PrimeHub +from pybricks.pupdevices import Motor +import pybricks as pybricksforvers +print("hi") class FakeUART: def __init__(self, port, baudrate, timeout): self.timeout = timeout @@ -41,10 +42,7 @@ class FakeUART: def UARTDevice(port, baudrate=9600, timeout=None): - try: - return _UARTDevice(port, baudrate, timeout) - except OSError: - return FakeUART(port, baudrate, timeout) + return FakeUART(port, baudrate, timeout) class OSDiagnostics: @@ -105,6 +103,22 @@ class OSDiagnostics: usystestobject.print_results() self.successfultests += usystestobject.successfultests self.failedtests.update(usystestobject.failedtests) + def testAll(self): + self.testUErrno() + self.testUIO() + self.testUJSON() + self.testUMath() + self.testURandom() + self.testUSelect() + self.testUStruct() + self.testUSys() + print(f"\n=== Results: {self.successfultests}/62 tests passed ===") + if self.failedtests: + print("Failed tests:") + for key, value in self.failedtests.items(): + print(f" {key}: {value}") + else: + print("No tests failed. Great job!") class UerrnoTest: @@ -114,16 +128,11 @@ class UerrnoTest: self.successfultests = 0 self.failedtests = {} def testeagain(self): - # Triggered by calling multitask() nested inside another multitask() print("Starting Test 1/9: EAGAIN - Try Again Error") + uart = UARTDevice(Port.A, baudrate=9600, timeout=1000) + uart.set_error(EAGAIN) try: - async def inner(): - await multitask(wait(100)) # nested multitask raises EAGAIN - - async def outer(): - await multitask(inner()) - - run_task(outer()) + uart.read(1) print("No error raised.\nCompleted Test 1/9: EAGAIN - FAILED") self.failedtests["EAGAIN"] = "No error raised" except OSError as ex: @@ -180,23 +189,18 @@ class UerrnoTest: def testeinval(self): # Triggered by passing an out-of-range value to motor.control.limits() print("Starting Test 4/9: EINVAL - Invalid Argument Error") - input("Plug a motor into Port A, then press Enter.") try: - motor = self.motorclass(Port.A) - motor.control.limits(speed=9999999, acceleration=9999999) - print("No error raised.\nCompleted Test 4/9: EINVAL - FAILED") - self.failedtests["EINVAL"] = "No error raised" - except OSError as ex: + usys.stderr.flush() + except (OSError) as ex: if ex.errno == EINVAL: print("EINVAL can be thrown and caught.\nCompleted Test 4/9: EINVAL - SUCCESSFUL") self.successfultests += 1 - elif ex.errno == EIO: + elif errno_val == EIO: print("An unspecified error occurred (EIO).\nCompleted Test 4/9: EINVAL - FAILED") self.failedtests["EINVAL"] = "EIO - Unspecified Error" else: - print(f"Another error occurred with code: {ex.errno}.\nCompleted Test 4/9: EINVAL - FAILED") - self.failedtests["EINVAL"] = ex.errno - + print(f"Another error occurred with code: {ex}.\nCompleted Test 4/9: EINVAL - FAILED") + self.failedtests["EINVAL"] = str(ex) def testeio(self): # No reliable scriptable trigger (requires physical unplug); use FakeUART print("Starting Test 5/9: EIO - I/O Error") @@ -254,14 +258,11 @@ class UerrnoTest: self.failedtests["EOPNOTSUPP"] = ex.errno def testeperm(self): - # Triggered by calling motor.control.limits() while a motor is actively running print("Starting Test 8/9: EPERM - Operation Not Permitted Error") - input("Plug a motor into Port A, then press Enter.") + uart = UARTDevice(Port.A, baudrate=9600, timeout=1000) + uart.set_error(EPERM) try: - motor = self.motorclass(Port.A) - motor.run(500) - wait(50) - motor.control.limits(speed=500, acceleration=1000) + uart.read(1) print("No error raised.\nCompleted Test 8/9: EPERM - FAILED") self.failedtests["EPERM"] = "No error raised" except OSError as ex: @@ -274,12 +275,6 @@ class UerrnoTest: else: print(f"Another error occurred with code: {ex.errno}.\nCompleted Test 8/9: EPERM - FAILED") self.failedtests["EPERM"] = ex.errno - finally: - try: - motor.stop() - except Exception: - pass - def testetimedout(self): # Triggered by FakeUART (or real UART) timing out on read print("Starting Test 9/9: ETIMEDOUT - Timed Out Error") @@ -314,7 +309,7 @@ class UIOTest(OSDiagnostics): # uio contains BytesIO, StringIO, and FileIO, but due to SPIKE Prime's lack of a filesystem, the test will omit FileIO and only include BytesIO and StringIO def testbytesio(self): try: - buffer = io.BytesIO() + buffer = uio.BytesIO() buffer.write(b'Hello, ') buffer.write(b'Pybricks byte stream!') @@ -341,7 +336,7 @@ class UIOTest(OSDiagnostics): self.failedtests["BytesIO"] = ex.errno def teststringio(self): try: - buffer = io.StringIO() + buffer = uio.StringIO() buffer.write('Hello, ') buffer.write('Pybricks string stream!') @@ -459,6 +454,7 @@ class UMathTest: self.successfultests = 0 self.failedtests = {} def test_math(self): + EPSILON = 0.0001 if(umath.ceil(87.21) == 88): self.successfultests += 1 else: @@ -519,9 +515,8 @@ class UMathTest: else: self.failedtests["eexp"] = "Failed" self.failedtests["ln"] = "Failed" - print(umath.e) - print("\n") - if(input("Press Y and press enter if the value displayed above is around 2.718282. Type N and press Enter if it is not:") == "Y"): + + if(abs(umath.e - 2.718282) < EPSILON): self.successfultests += 1 else: self.failedtests["e"] = "Failed" @@ -536,14 +531,12 @@ class UMathTest: else: self.failedtests["sqrt"] = "Failed" - print(umath.pi) - print("\n") - if(input("Press Y and press enter if the value displayed above is around 3.14159265. Type N and press Enter if it is not:") == "Y"): + if(abs(umath.pi - 3.141593) < EPSILON): self.successfultests += 1 else: self.failedtests["pi"] = "Failed" - EPSILON = 1e-9 + if abs(umath.degrees(umath.pi * 3) - 540) < EPSILON: self.successfultests += 1 else: @@ -601,12 +594,12 @@ class UMathTest: else: self.failedtests["isfiniteinfinite"] = "Failed" - if(umath.isinfinite(finitenum) == False): + if(umath.isinf(finitenum) == False): self.successfultests += 1 else: self.failedtests["isinfinitefinite"] = "Failed" - if(umath.isinfinite(infinitenum) == True): + if(umath.isinf(infinitenum) == True): self.successfultests += 1 else: self.failedtests["isinfiniteinfinite"] = "Failed" @@ -623,8 +616,9 @@ class UMathTest: self.failedtests["isnannotnan"] = "Failed" frac, integer = umath.modf(87.21) - if integer == 87.0 and abs(frac - 0.21) < EPSILON: + if abs(integer - 87.0) < 0.01 and abs(frac - 0.21) < 0.01: self.successfultests += 1 + else: self.failedtests["modf"] = "Failed" @@ -641,7 +635,7 @@ class UMathTest: self.failedtests["ldexp"] = "Failed" def print_results(self): self.test_math() - print(f"\n=== Results: {self.successfultests}/34 tests passed ===") + print(f"\n=== Results: {self.successfultests}/35 tests passed ===") if self.failedtests: print("Failed tests:") for key, value in self.failedtests.items(): @@ -653,7 +647,7 @@ class URandomTest: self.successfultests = 0 self.failedtests = {} def testrandom(self): - NUM_SAMPLES = 1000000 + NUM_SAMPLES = 6553 NUM_BUCKETS = 10 CHART_WIDTH = 50 SAMPLE_PEEK = 20 @@ -682,7 +676,7 @@ class URandomTest: lo = i / NUM_BUCKETS hi = (i + 1) / NUM_BUCKETS count = bucket_counts[i] - bar = round(count / max_count * CHART_WIDTH) + bar = int(round(count / max_count * CHART_WIDTH)) dev = abs(count - ideal) / ideal if dev <= 0.10: marker = "#" @@ -692,8 +686,7 @@ class URandomTest: marker = "." lo_str = "{:.2f}".format(lo) hi_str = "{:.2f}".format(hi) - count_str = str(count).rjust(10) - print(" " + lo_str + " - " + hi_str + " " + count_str + " " + marker * bar) + print(" " + "{:.2f}".format(lo) + " - " + "{:.2f}".format(hi) + " " + str(count) + " " + marker * bar) print("") print(" Samples : " + str(NUM_SAMPLES)) @@ -714,7 +707,7 @@ class URandomTest: print(row) print("") - if(abs(0.5 - mean) < 0.02): + if(abs(0.5 - mean) < 0.06): print("Random Distribution Test: SUCCESSFUL") self.successfultests += 1 else: @@ -727,7 +720,7 @@ class URandomTest: randint_vals = [urandom.randint(1, 100) for _ in range(N)] randint_mean = sum(randint_vals) / N print("Randint mean (ideally 50.5): {:.4f}".format(randint_mean)) - if abs(50.5 - randint_mean) < 1.0: + if abs(50.5 - randint_mean) < 3.0: print("Randint Test: SUCCESSFUL") self.successfultests += 1 else: @@ -737,7 +730,7 @@ class URandomTest: getrandbits_vals = [urandom.getrandbits(7) for _ in range(N)] getrandbits_mean = sum(getrandbits_vals) / N print("Getrandbits(7) mean (ideally 63.5): {:.4f}".format(getrandbits_mean)) - if abs(63.5 - getrandbits_mean) < 1.3: + if abs(63.5 - getrandbits_mean) < 4: print("Getrandbits Test: SUCCESSFUL") self.successfultests += 1 else: @@ -747,7 +740,7 @@ class URandomTest: randrange_vals = [urandom.randrange(1, 1001, 4) for _ in range(N)] randrange_mean = sum(randrange_vals) / N print("Randrange(1,1001,4) mean (ideally 501.0): {:.4f}".format(randrange_mean)) - if abs(501.0 - randrange_mean) < 10.0: + if abs(501.0 - randrange_mean) < 20.0: print("Randrange Test: SUCCESSFUL") self.successfultests += 1 else: @@ -757,7 +750,7 @@ class URandomTest: uniform_vals = [urandom.uniform(1, 10) for _ in range(N)] uniform_mean = sum(uniform_vals) / N print("Uniform(1,10) mean (ideally 5.5): {:.4f}".format(uniform_mean)) - if abs(5.5 - uniform_mean) < 0.09: + if abs(5.5 - uniform_mean) < 0.3: print("Uniform Test: SUCCESSFUL") self.successfultests += 1 else: @@ -791,7 +784,6 @@ class URandomTest: print("Failed tests:") for key, value in self.failedtests.items(): print(f" {key}: {value}") - class USelectTest: def __init__(self, hub, motorclass): self.hub = hub @@ -801,21 +793,22 @@ class USelectTest: def print_results(self): # Register the standard input so we can read keyboard presses. - keyboard = poll() - keyboard.register(stdin) + keyboard = uselect.poll() + keyboard.register(usys.stdin) print("Type a few keys, make sure you get back what character you typed, then press Escape.") while True: # Check if a key has been pressed. if keyboard.poll(0): # Read the key and print it. - key = stdin.read(1) + key = usys.stdin.read(1) if key == '\x1b': print("Escape key pressed. Exiting...") break else: print("Pressed:", key) - if(input("Input Y if the results were accurate:") == "Y"): + result = input("Input Y if the results were accurate:") + if(result == "Y" or result == "y"): print(f"\n=== Results: 1/1 tests passed ===") self.successfultests += 1 else: @@ -824,8 +817,6 @@ class USelectTest: print("Failed tests:") for key, value in self.failedtests.items(): print(f" {key}: {value}") - - class UStructTest: def __init__(self, hub, motorclass): self.hub = hub @@ -834,10 +825,10 @@ class UStructTest: self.failedtests = {} def print_results(self): - packed = struct.pack('ii', 42, 100) + packed = ustruct.pack('ii', 42, 100) print(f'Packed bytes using pack(): {packed}') - unpacked = struct.unpack('ii', packed) + unpacked = ustruct.unpack('ii', packed) print(f'Unpacked values using unpack(): {unpacked}') print(unpacked == (42, 100)) if(unpacked == (42, 100)): @@ -847,15 +838,15 @@ class UStructTest: print("Completed Test 1/2: pack - FAILED") self.failedtests["pack"] = "Failed" format_string = 'hhl' - size = struct.calcsize(format_string) + size = ustruct.calcsize(format_string) buffer = bytearray(size) - struct.pack_into(format_string, buffer, 0, 5, 10, 15) + ustruct.pack_into(format_string, buffer, 0, 5, 10, 15) print("Packed buffer using pack_into():", buffer) - unpackedfrom = struct.unpack_from(format_string, buffer, 0) + unpackedfrom = ustruct.unpack_from(format_string, buffer, 0) print("Unpacked buffer using unpack_from():", unpackedfrom) if(unpackedfrom == (5, 10, 15)): print("Completed Test 2/2: pack_into - SUCCESSFUL") @@ -868,8 +859,6 @@ class UStructTest: print("Failed tests:") for key, value in self.failedtests.items(): print(f" {key}: {value}") - - class USysTest: def __init__(self, hub, motorclass): self.hub = hub @@ -878,32 +867,33 @@ class USysTest: self.failedtests = {} def printVersionDiagnostics(self): try: - print("[Hub Diagnostics - MicroPython - Version] Hub version information:", version) - print("[Hub Diagnostics - MicroPython - Version] MicroPython version:", usys.version) - print("[Hub Diagnostics - MicroPython - Version] Pybricks version information:", usys.version_info) - print("[Hub Diagnostics - MicroPython - Version] MicroPython information:", usys.implementation) + print("Hub version information:", pybricksforvers.version) + print("MicroPython version:", usys.version) + print("Pybricks version information:", usys.version_info) + print("MicroPython information:", usys.implementation) self.successfultests += 1 print("Completed Test 1/4: versioninfo - SUCCESSFUL") except Exception as ex: - failedtests["versioninfo"] = ex.errno + self.failedtests["versioninfo"] = ex.errno print("Completed Test 4/4: versioninfo - FAILED") def teststdin(self): # Register the standard input so we can read keyboard presses. - keyboard = poll() - keyboard.register(stdin) + keyboard = uselect.poll() + keyboard.register(usys.stdin) print("Type a few keys, make sure you get back what character you typed, then press Escape.") while True: # Check if a key has been pressed. if keyboard.poll(0): # Read the key and print it. - key = stdin.read(1) + key = usys.stdin.read(1) if key == '\x1b': print("Escape key pressed. Exiting...") break else: print("Pressed:", key) - if(input("Input Y if the results were accurate:") == "Y"): + result = input("Input Y if the results were accurate:") + if(result == "Y" or result == "y"): print("Completed Test 1/4: stdin - SUCCESSFUL") self.successfultests += 1 else: @@ -916,16 +906,16 @@ class USysTest: def teststdout(self): usys.stdout.flush() try: - usys.stdout.buffer.write(b"stdout worked!") + usys.stdout.buffer.write(b"stdout worked!\n") print("Completed Test 2/4: stdout - SUCCESSFUL") self.successfultests += 1 except Exception as ex: print("Completed Test 2/4: stdout - FAILED") self.failedtests["stdout"] = ex.errno def teststderr(self): - usys.stderr.flush() + usys.stdout.flush() try: - usys.stderr.buffer.write(b"stderr worked!") + usys.stderr.buffer.write(b"stderr worked!\n") print("Completed Test 3/4: stderr - SUCCESSFUL") self.successfultests += 1 except Exception as ex: @@ -936,9 +926,11 @@ class USysTest: self.teststdin() self.teststdout() self.teststderr() - printVersionDiagnostics() - print(f"\n=== Results: {self.successfultests}/3 tests passed ===") + self.printVersionDiagnostics() + print(f"\n=== Results: {self.successfultests}/4 tests passed ===") if self.failedtests: print("Failed tests:") for key, value in self.failedtests.items(): - print(f" {key}: {value}") \ No newline at end of file + print(f" {key}: {value}") +diag = OSDiagnostics(hub=PrimeHub(), motorclass=Motor) +diag.testAll() \ No newline at end of file From 36df9de03d55ffd4e8616c528f74c60c1be1450d Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Sat, 21 Mar 2026 15:26:26 +0000 Subject: [PATCH 22/38] Update diagnostics/os_diagnostics.py --- diagnostics/os_diagnostics.py | 1 - 1 file changed, 1 deletion(-) diff --git a/diagnostics/os_diagnostics.py b/diagnostics/os_diagnostics.py index 034f2b7..85e0957 100644 --- a/diagnostics/os_diagnostics.py +++ b/diagnostics/os_diagnostics.py @@ -12,7 +12,6 @@ from pybricks.tools import wait, multitask, run_task from pybricks.hubs import PrimeHub from pybricks.pupdevices import Motor import pybricks as pybricksforvers -print("hi") class FakeUART: def __init__(self, port, baudrate, timeout): self.timeout = timeout From 57836a8da7d059e3ac5e9408cdbe08a8f7fe274e Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Sat, 21 Mar 2026 16:16:12 +0000 Subject: [PATCH 23/38] Update diagnostics/micropython_diagnostics.py --- diagnostics/micropython_diagnostics.py | 131 ++++++++++++++++++++++--- 1 file changed, 117 insertions(+), 14 deletions(-) diff --git a/diagnostics/micropython_diagnostics.py b/diagnostics/micropython_diagnostics.py index 3a5a0c4..0f96ca6 100644 --- a/diagnostics/micropython_diagnostics.py +++ b/diagnostics/micropython_diagnostics.py @@ -1,35 +1,138 @@ import micropython +import gc from pybricks import version +from pybricks.hubs import PrimeHub class MicroPythonDiagnostics: def __init__(self, hub): - pass - def performMemoryDiagnostics(): + self.successfultests = 0 + self.failedtests = {} + def testgcmanual(self): + gc.disable() + print(f"Initial free: {gc.mem_free()} bytes") + large_data = [i for i in range(10000)] + print(f"After allocation: {gc.mem_free()} bytes") + gc.collect() + aftergcstillref = gc.mem_free() + print(f"After gc.collect (data still referenced): {aftergcstillref} bytes") + large_data = None + print("Reference to data removed.") + aftergcnoref = gc.mem_free() + gc.collect() + print(f"After gc.collect (data dead): {aftergcnoref} bytes") + if(aftergcnoref < aftergcstillref): + print("Completed Test 4/5: Manual garbage collection - SUCCESSFUL") + self.successfultests += 1 + else: + print("Completed Test 4/5: Manual garbage collection - FAILED") + self.failedtests["Manual garbage collection"] = "Heap not cleared" + def testgcauto(self): + input("Disabling garbage collection. The amount of used memory should quickly increase. Press Enter to begin:") + gc.disable() + gc.threshold(5000) + + total_mem = 255616 # Based on your previous mem_info + bytes_per_hash = 3000 + + print("Memory Monitor: [# = Used] [. = Free]") + print("-" * (total_mem // bytes_per_hash)) + + for i in range(500): + # Creating garbage to simulate robot logic/sensors + _ = bytearray(300) + + # Only print every 25 iterations to prevent terminal spam + if i % 25 == 0: + used = gc.mem_alloc() + free = gc.mem_free() + + # Calculate how many hashes and dots to draw + hashes = used // bytes_per_hash + dots = free // bytes_per_hash + + # Print the visual bar with the iteration number + print(f"{i:03d}: [{'#' * hashes}{'.' * dots}] {free} bytes free") + final_disabled_free = gc.mem_free() + input("Enabling garbage collection. The amount of used memory should stay relatively low. Press Enter to begin:") + gc.enable() + gc.threshold(5000) + + total_mem = 255616 # Based on your previous mem_info + bytes_per_hash = 3000 + + print("Memory Monitor: [# = Used] [. = Free]") + print("-" * (total_mem // bytes_per_hash)) + + for i in range(500): + # Creating garbage to simulate robot logic/sensors + _ = bytearray(300) + + # Only print every 25 iterations to prevent terminal spam + if i % 25 == 0: + used = gc.mem_alloc() + free = gc.mem_free() + + # Calculate how many hashes and dots to draw + hashes = used // bytes_per_hash + dots = free // bytes_per_hash + + # Print the visual bar with the iteration number + print(f"{i:03d}: [{'#' * hashes}{'.' * dots}] {free} bytes free") + + final_enabled_free = gc.mem_free() + if final_enabled_free > final_disabled_free: + print("Completed Test 5/5: Automatic garbage collection - SUCCESSFUL") + print(f"Difference: {final_enabled_free - final_disabled_free} bytes saved.") + self.successfultests += 1 + else: + print("Completed Test 5/5: Automatic garbage collection - FAILED") + self.failedtests["Automatic garbage collection"] = "No GC difference" + def performMemoryDiagnostics(self): + input("Press Enter to retrieve memory information:") print("[Hub Diagnostics - MicroPython - Memory] Memory information (retrieved from the MicroPython environment):") micropython.mem_info(1) + input("After you're done reading the results, press Enter to run heap diagnostics:") print("[Hub Diagnostics - MicroPython - Memory] Testing heap lock and unlock.") print("[Hub Diagnostics - MicroPython - Memory] Allocating memory while heap is unlocked:") try: - x = 5000 - print("[Hub Diagnostics - MicroPython - Memory] [SUCCESS] There was no MemoryError raised. The value of the new variable x is", x) + x = [1, 2, 3, 4, 5] + print("[Hub Diagnostics - MicroPython - Memory] Completed Test 1/5: Normal memory allocation - SUCCESS") + print("There was no MemoryError raised. The value of the new variable x is", x) + self.successfultests += 1 except MemoryError: - print("[Hub Diagnostics - MicroPython - Memory] [FAIL] Allocation failed. Your memory may be faulty.") + print("[Hub Diagnostics - MicroPython - Memory] Completed Test 1/5: Normal memory allocation - FAILED") + self.failedtests["Normal memory allocation"] = "MemoryError" print("[Hub Diagnostics - MicroPython - Memory] Locking the heap:") micropython.heap_lock() print("[Hub Diagnostics - MicroPython - Memory] Heap was locked. Attempting to allocate memory (this should fail):") try: - y = 10000 - print("[Hub Diagnostics - MicroPython - Memory] [FAIL] There was no MemoryError raised. Heap lock failed.") + y = [10, 20, 30, 40, 50] + print("[Hub Diagnostics - MicroPython - Memory] Completed Test 2/5: Heap lock - FAILED") + self.failedtests["Heap lock"] = "No heap lock" except MemoryError: - print("[Hub Diagnostics - MicroPython - Memory] [SUCCESS] Allocation failed. Test successful. The heap was successfully locked.") + print("[Hub Diagnostics - MicroPython - Memory] Completed Test 2/5: Heap lock - SUCCESS") + self.successfultests += 1 # Attempt to add gc to this for memory diagnostics, in addition test machine.soft_reset() and add that first to reset the heap. print("[Hub Diagnostics - MicroPython - Memory] Unlocking the heap:") micropython.heap_unlock() print("[Hub Diagnostics - MicroPython - Memory] Heap was unlocked. Attempting to allocate memory (this should succeed):") try: - z = 17000 - print("[Hub Diagnostics - MicroPython - Memory] [SUCCESS] There was no MemoryError raised. The value of the new variable y is", x) + z = [100, 200, 300, 400, 500] + print("[Hub Diagnostics - MicroPython - Memory] Completed Test 3/5: Heap unlock - FAILED") + print("The value of the new variable z is", z) + self.successfultests += 1 except MemoryError: - print("[Hub Diagnostics - MicroPython - Memory] [FAIL] Allocation failed. The heap failed to unlock.") - def printAll(): - performMemoryDiagnostics() - \ No newline at end of file + print("[Hub Diagnostics - MicroPython - Memory] Completed Test 3/5: Heap unlock - FAILED") + self.failedtests["Heap unlock"] = "No heap unlock" + def printAll(self): + self.performMemoryDiagnostics() + input("After you're done reading the results, press Enter to run manual garbage collection test:") + self.testgcmanual() + input("After you're done reading the results, press Enter to run automatic garbage collection test:") + self.testgcauto() + print(f"\n=== Results: {self.successfultests}/5 tests passed ===") + if self.failedtests: + print("Failed tests:") + for key, value in self.failedtests.items(): + print(f" {key}: {value}") +test = MicroPythonDiagnostics(hub=PrimeHub()) +test.printAll() \ No newline at end of file From 436569ffd0af4f4e99479bfbc852871ad5325b4f Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Mon, 23 Mar 2026 18:05:31 +0000 Subject: [PATCH 24/38] Removed unnecessary comments --- diagnostics/micropython_diagnostics.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/diagnostics/micropython_diagnostics.py b/diagnostics/micropython_diagnostics.py index 0f96ca6..93b0db5 100644 --- a/diagnostics/micropython_diagnostics.py +++ b/diagnostics/micropython_diagnostics.py @@ -30,52 +30,44 @@ class MicroPythonDiagnostics: gc.disable() gc.threshold(5000) - total_mem = 255616 # Based on your previous mem_info + total_mem = 255616 bytes_per_hash = 3000 print("Memory Monitor: [# = Used] [. = Free]") print("-" * (total_mem // bytes_per_hash)) for i in range(500): - # Creating garbage to simulate robot logic/sensors _ = bytearray(300) - # Only print every 25 iterations to prevent terminal spam if i % 25 == 0: used = gc.mem_alloc() free = gc.mem_free() - - # Calculate how many hashes and dots to draw + hashes = used // bytes_per_hash dots = free // bytes_per_hash - # Print the visual bar with the iteration number print(f"{i:03d}: [{'#' * hashes}{'.' * dots}] {free} bytes free") final_disabled_free = gc.mem_free() input("Enabling garbage collection. The amount of used memory should stay relatively low. Press Enter to begin:") gc.enable() gc.threshold(5000) - total_mem = 255616 # Based on your previous mem_info + total_mem = 255616 bytes_per_hash = 3000 print("Memory Monitor: [# = Used] [. = Free]") print("-" * (total_mem // bytes_per_hash)) for i in range(500): - # Creating garbage to simulate robot logic/sensors _ = bytearray(300) - - # Only print every 25 iterations to prevent terminal spam + if i % 25 == 0: used = gc.mem_alloc() free = gc.mem_free() - # Calculate how many hashes and dots to draw hashes = used // bytes_per_hash dots = free // bytes_per_hash - - # Print the visual bar with the iteration number + print(f"{i:03d}: [{'#' * hashes}{'.' * dots}] {free} bytes free") final_enabled_free = gc.mem_free() @@ -111,7 +103,6 @@ class MicroPythonDiagnostics: except MemoryError: print("[Hub Diagnostics - MicroPython - Memory] Completed Test 2/5: Heap lock - SUCCESS") self.successfultests += 1 - # Attempt to add gc to this for memory diagnostics, in addition test machine.soft_reset() and add that first to reset the heap. print("[Hub Diagnostics - MicroPython - Memory] Unlocking the heap:") micropython.heap_unlock() print("[Hub Diagnostics - MicroPython - Memory] Heap was unlocked. Attempting to allocate memory (this should succeed):") From aa9fa76d461fbd11f6a48ef0b8e9c4fb6a559219 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Mon, 23 Mar 2026 18:23:43 +0000 Subject: [PATCH 25/38] Update templates/xbox_controller.py --- templates/xbox_controller.py | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/templates/xbox_controller.py b/templates/xbox_controller.py index 978e69f..ee70305 100644 --- a/templates/xbox_controller.py +++ b/templates/xbox_controller.py @@ -1,18 +1,9 @@ -from pybricks.pupdevices import Motor -from pybricks.parameters import Button, Direction, Port, Side, Stop -from pybricks.tools import run_task, multitask -from pybricks.tools import wait, StopWatch -from pybricks.robotics import DriveBase from pybricks.iodevices import XboxController -from pybricks.hubs import PrimeHub -hub = PrimeHub() -testmotor = Motor(Port.C) -async def main(): - while True: - if(Button.UP in buttons.pressed()): - testmotor.run(500) - else: - testmotor.stop() - await wait(10) -# Run the main function -run_task(main()) \ No newline at end of file +from pybricks.parameters import Direction, Port +from pybricks.tools import wait + +xbox = XboxController() + +while True: + print("Xbox left joystick x-position:", xbox.joystick_left()[0]) + wait(50) \ No newline at end of file From 6258ad200396afc488f16bbecab868e51b591d46 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Mon, 23 Mar 2026 18:24:06 +0000 Subject: [PATCH 26/38] Add experiments/game.py --- experiments/game.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 experiments/game.py diff --git a/experiments/game.py b/experiments/game.py new file mode 100644 index 0000000..1d8dcb6 --- /dev/null +++ b/experiments/game.py @@ -0,0 +1 @@ +# will use the light matrix and btns From 0fcc65deee93091a98b37e4a401f96654db8bf55 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Tue, 24 Mar 2026 17:49:28 +0000 Subject: [PATCH 27/38] Update diagnostics/micropython_diagnostics.py --- diagnostics/micropython_diagnostics.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/diagnostics/micropython_diagnostics.py b/diagnostics/micropython_diagnostics.py index 93b0db5..49ade7a 100644 --- a/diagnostics/micropython_diagnostics.py +++ b/diagnostics/micropython_diagnostics.py @@ -1,7 +1,6 @@ import micropython import gc from pybricks import version -from pybricks.hubs import PrimeHub class MicroPythonDiagnostics: def __init__(self, hub): self.successfultests = 0 @@ -125,5 +124,5 @@ class MicroPythonDiagnostics: print("Failed tests:") for key, value in self.failedtests.items(): print(f" {key}: {value}") -test = MicroPythonDiagnostics(hub=PrimeHub()) -test.printAll() \ No newline at end of file +#test = MicroPythonDiagnostics(hub=PrimeHub()) +#test.printAll() \ No newline at end of file From beeb7078435f7d73a9da581eff43109615870b87 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Tue, 24 Mar 2026 17:50:10 +0000 Subject: [PATCH 28/38] Update diagnostics/os_diagnostics.py --- diagnostics/os_diagnostics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diagnostics/os_diagnostics.py b/diagnostics/os_diagnostics.py index 85e0957..2827036 100644 --- a/diagnostics/os_diagnostics.py +++ b/diagnostics/os_diagnostics.py @@ -931,5 +931,5 @@ class USysTest: print("Failed tests:") for key, value in self.failedtests.items(): print(f" {key}: {value}") -diag = OSDiagnostics(hub=PrimeHub(), motorclass=Motor) -diag.testAll() \ No newline at end of file +#diag = OSDiagnostics(hub=PrimeHub(), motorclass=Motor) +#diag.testAll() \ No newline at end of file From ddb5d4cc1b0e891ac1114b4e1bf62b82acee26b6 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Tue, 24 Mar 2026 17:52:28 +0000 Subject: [PATCH 29/38] Update diagnostics/os_diagnostics.py --- diagnostics/os_diagnostics.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/diagnostics/os_diagnostics.py b/diagnostics/os_diagnostics.py index 2827036..84b17a0 100644 --- a/diagnostics/os_diagnostics.py +++ b/diagnostics/os_diagnostics.py @@ -9,8 +9,6 @@ import usys import urandom from urandom import random from pybricks.tools import wait, multitask, run_task -from pybricks.hubs import PrimeHub -from pybricks.pupdevices import Motor import pybricks as pybricksforvers class FakeUART: def __init__(self, port, baudrate, timeout): @@ -102,7 +100,7 @@ class OSDiagnostics: usystestobject.print_results() self.successfultests += usystestobject.successfultests self.failedtests.update(usystestobject.failedtests) - def testAll(self): + def printAll(self): self.testUErrno() self.testUIO() self.testUJSON() From 7a4c06d789373782e3831cfd49359276197cfcbc Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Tue, 24 Mar 2026 17:55:28 +0000 Subject: [PATCH 30/38] Update diagnostics/hub_diagnostics.py --- diagnostics/hub_diagnostics.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/diagnostics/hub_diagnostics.py b/diagnostics/hub_diagnostics.py index e83335d..d04e5aa 100644 --- a/diagnostics/hub_diagnostics.py +++ b/diagnostics/hub_diagnostics.py @@ -3,6 +3,7 @@ from pybricks import version import other_functions as debug from micropython_diagnostics import MicroPythonDiagnostics from pybricks.parameters import Port +from os_diagnostics import OSDiagnostics class HubDiagnostics: def __init__(self, hub): self.hub = hub @@ -29,13 +30,18 @@ class HubDiagnostics: v = verbose debug.log("[Hub Diagnostics] Starting hub diagnostics...", v) while True: - choice = input("[Hub Diagnostics] Which hub diagnostic would you like to run?\n[Hub Diagnostics] Enter 'l' for light source test\n[Hub Diagnostics] Enter 'm' for MicroPython diagnostics\n[Hub Diagnostics] Enter 'q' to quit\n[Hub Diagnostics] Your choice: ").strip().lower() + choice = input("[Hub Diagnostics] Which hub diagnostic would you like to run?\n[Hub Diagnostics] Enter 'l' for light source test\n[Hub Diagnostics] Enter 'm' for MicroPython diagnostics\n[Hub Diagnostics] Enter 'o' for operating system diagnostics\n[Hub Diagnostics] Enter 'q' to quit\n[Hub Diagnostics] Your choice: ").strip().lower() if choice == "l": debug.log("[Hub Diagnostics] Running light source test...", v) self.testLightSources(v) if choice == "m": debug.log("[Hub Diagnostics] Running MicroPython diagnostics...", v) MicroPythonDiagnostics.printAll() + if choice == "o" + debug.log("[Hub Diagnostics] Running OS diagnostics...", v) + diag = OSDiagnostics(hub=PrimeHub(), motorclass=Motor) + diag.printAll() + if choice == "q": print("[Hub Diagnostics] Hub diagnostics completed.") return \ No newline at end of file From a60c92997ad6f9671baa3bcea68022e9af2774df Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Tue, 24 Mar 2026 17:59:59 +0000 Subject: [PATCH 31/38] Update README.md --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6707441..553ceb2 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,14 @@ A collection of Pybricks utilities to assist in your FLL robot programming with How to use this: -- Download the repository by clicking on the "Code" tab, clicking the "< > Code" button, then downloading as a ZIP. Additionally, you can also use ```git clone https://codes.fll-65266.org/Arcmyx/pybricks-utils.git```. Unzip the archive and open code.pybricks.com. Then choose which folder you'd like to use, and open each file in pybricks by using the import button. For example, to use the diagnostics tool, simply open each program in the ```diagnostics``` folder into Pybricks. Then, follow the instructions for each utility. +- Download the repository by clicking on the "Code" tab, clicking the "< > Code" button, then downloading as a ZIP. Additionally, you can also use ```git clone https://codes.fll-65266.org/Arcmyx/pynamics.git```. Unzip the archive and open code.pybricks.com. Then choose which folder you'd like to use, and open each file in Pybricks by using the import button. For example, to use the diagnostics tool, simply open each program in the ```diagnostics``` folder into Pybricks. Then, follow the instructions for each utility. + +- This method is not recommended due to the high probability of error. The team is currently working on a custom web Pynamics IDE that automatically fetches the latest compiled bytecode, sends it to your robot, and has an output viewer with custom formatting and integration with the program through (in the future) custom Pynamics ANSI escape codes. Included utilities: -- Diagnostics - a program that allows you to diagnose issues and test parts of your robot, such as battery, motor, and color sensor. Open each program in the ```diagnostics``` folder in Pybricks, (you can select all of them at once) connect your robot, switch to the ```FullDiagnostics.py``` file and press run. +- Diagnostics - a program that allows you to diagnose issues and test parts of your robot, such as battery, motor, and color sensor. Open each program in the ```diagnostics``` folder in Pybricks, (you can select all of them at once) connect your robot, switch to the ```FullDiagnostics.py``` file and press run. The program might take a bit to compile, since there are thousands of lines of code being imported from the other files (partly the reason why the Pynamics IDE will be an improvement, since the Pynamics team will distribute the pre-compiled bytecode) + - Color Sensor Tests (UPCOMING) - a program that identifies what color the sensor is detecting. If you'd like, you can use our color ranges in your own programs. Please set your window size to 90% on small screens for best results with the ASCII art. From 06bce553aab40755b12a2039ba26d44054a00513 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Tue, 24 Mar 2026 18:35:46 +0000 Subject: [PATCH 32/38] Update diagnostics/hub_diagnostics.py --- diagnostics/hub_diagnostics.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/diagnostics/hub_diagnostics.py b/diagnostics/hub_diagnostics.py index d04e5aa..51587fc 100644 --- a/diagnostics/hub_diagnostics.py +++ b/diagnostics/hub_diagnostics.py @@ -2,7 +2,7 @@ from pybricks.tools import wait, StopWatch from pybricks import version import other_functions as debug from micropython_diagnostics import MicroPythonDiagnostics -from pybricks.parameters import Port +from pybricks.parameters import Port, Color from os_diagnostics import OSDiagnostics class HubDiagnostics: def __init__(self, hub): @@ -25,6 +25,7 @@ class HubDiagnostics: wait(100) debug.log(f"[Hub Diagnostics - Light Sources] Turning off pixel at position {x}, {y}...", v) self.hub.display.pixel(x, y, brightness=0) + self.hub.light.on(Color.RED) def printAll(self, verbose=True): v = verbose From 8d9b2ca6797d34b7349668cdcd894261920bb891 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Thu, 26 Mar 2026 18:19:58 +0000 Subject: [PATCH 33/38] Add templates/logger.py --- templates/logger.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 templates/logger.py diff --git a/templates/logger.py b/templates/logger.py new file mode 100644 index 0000000..5484868 --- /dev/null +++ b/templates/logger.py @@ -0,0 +1,44 @@ +from pybricks.tools import StopWatch + +class Logger: + def __init__(self, verboseness=7): + self.time = StopWatch() + self.time.pause() + self.verboseness = verboseness + self.lvldict = { + 0: "FATAL", + 1: "ALERT", + 2: "CRIT", + 3: "ERR", + 4: "WARNING", + 5: "NOTICE", + 6: "INFO", + 7: "DEBUG" + } + + def start(self): + self.time.reset() + self.time.resume() + + def log(self, message, level, origin): + if level <= self.verboseness: + ms = self.time.time() + timestamp = "{:02d}:{:02d}.{:03d}".format( + (ms // 60000) % 60, + (ms // 1000) % 60, + ms % 1000 + ) + label = self.lvldict.get(level, "UNKNOWN") + padding = " " * (7 - len(label)) + print("[{}] {}{} [{}] {}".format(timestamp, label, padding, origin, message)) + def fatal(self, message, origin): self.log(message, 0, origin) + def alert(self, message, origin): self.log(message, 1, origin) + def crit(self, message, origin): self.log(message, 2, origin) + def err(self, message, origin): self.log(message, 3, origin) + def warning(self, message, origin): self.log(message, 4, origin) + def notice(self, message, origin): self.log(message, 5, origin) + def info(self, message, origin): self.log(message, 6, origin) + def debug(self, message, origin): self.log(message, 7, origin) + def crash(self, message, origin): + self.log(message, 0, origin) + raise FatalLoggerError("[FATAL] [{}] {}".format(origin, message)) \ No newline at end of file From 4683b87a3288924d33c32cd0991ea2a27c743b56 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Fri, 10 Apr 2026 17:25:22 +0000 Subject: [PATCH 34/38] Update tests/pynamics-logger.py --- tests/pynamics-logger.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/pynamics-logger.py b/tests/pynamics-logger.py index 375ad2c..aa90f5f 100644 --- a/tests/pynamics-logger.py +++ b/tests/pynamics-logger.py @@ -8,6 +8,7 @@ #6: perf_smpl #7: get_time #8: breakpoint +verboseness = 7 lvldict = { 0: "FATAL", 1: "ALERT", @@ -22,18 +23,18 @@ stpwtchtime = StopWatch() stpwtchtime.pause() def start(): - self.time.reset() - self.time.resume() + stpwtchtime.reset() + stpwtchtime.resume() def log(self, message, level, origin): - if level <= self.verboseness: - ms = self.time.time() + if level <= verboseness: + ms = stpwtchtime.time() timestamp = "{:02d}:{:02d}.{:03d}".format( (ms // 60000) % 60, (ms // 1000) % 60, ms % 1000 ) - label = self.lvldict.get(level, "UNKNOWN") + label = lvldict.get(level, "UNKNOWN") padding = " " * (7 - len(label)) print("[{}] {}{} [{}] {}".format(timestamp, label, padding, origin, message)) @@ -50,9 +51,9 @@ def notice(self, message, origin): notify(message, 5, origin) def info(self, message, origin): notify(message, 6, origin) def debug(self, message, origin): notify(message, 7, origin) def crash(self, message, origin): - sendCommand(3, 0, "program kinda crashed bc you suck at coding", origin) + sendCommand(3, 0, "uhhhh so the program kinda crashed sorry", origin) raise FatalLoggerError("[FATAL] [{}] {}".format(origin, message)) -sendCommand(0, 0, "hey you should probably know that your RAM is corrupted and you cant buy more because of the shortage. byeeeee", "test prgm") +sendCommand(0, 0, "hey you should probably know that your RAM is corrupted and you cant replace it because of the shortage. byeeeee", "test prgm") sendCommand(0, 1, "UNRECOVERABLE PYTHON ERROR!!!!! Exiting gracefully... JUST KIDDING WHAT DID YOU THINK IT WAS GONNA DO", "test prgm") sendCommand(0, 2, "so the program is running but it just started looping on the motor frying bit of your program.", "test prgm") sendCommand(0, 3, "syntax error, you failure!!!! use an error checker", "test prgm") From a1f7c39c60b739cddc08b7de7dac6d4a6a5f5b2f Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Fri, 10 Apr 2026 17:36:07 +0000 Subject: [PATCH 35/38] Update tests/pynamics-logger.py --- tests/pynamics-logger.py | 112 +++++++++++++++++++++------------------ 1 file changed, 60 insertions(+), 52 deletions(-) diff --git a/tests/pynamics-logger.py b/tests/pynamics-logger.py index aa90f5f..f774d1a 100644 --- a/tests/pynamics-logger.py +++ b/tests/pynamics-logger.py @@ -8,58 +8,66 @@ #6: perf_smpl #7: get_time #8: breakpoint -verboseness = 7 -lvldict = { - 0: "FATAL", - 1: "ALERT", - 2: "CRIT", - 3: "ERR", - 4: "WARNING", - 5: "NOTICE", - 6: "INFO", - 7: "DEBUG" -} -stpwtchtime = StopWatch() -stpwtchtime.pause() +class PynamicsLogger: + def __init__(self): + self.verboseness = 7 + self.lvldict = { + 0: "FATAL", + 1: "ALERT", + 2: "CRIT", + 3: "ERR", + 4: "WARNING", + 5: "NOTICE", + 6: "INFO", + 7: "DEBUG" + } + self.time = StopWatch() + self.time.pause() -def start(): - stpwtchtime.reset() - stpwtchtime.resume() + def start(): + self.time.reset() + self.time.resume() -def log(self, message, level, origin): - if level <= verboseness: - ms = stpwtchtime.time() - timestamp = "{:02d}:{:02d}.{:03d}".format( - (ms // 60000) % 60, - (ms // 1000) % 60, - ms % 1000 - ) - label = lvldict.get(level, "UNKNOWN") - padding = " " * (7 - len(label)) - print("[{}] {}{} [{}] {}".format(timestamp, label, padding, origin, message)) + def log(self, message, level, origin): + if level <= self.verboseness: + ms = self.time.time() + timestamp = "{:02d}:{:02d}.{:03d}".format( + (ms // 60000) % 60, + (ms // 1000) % 60, + ms % 1000 + ) + label = self.lvldict[level] + padding = " " * (7 - len(label)) + print(f"[{timestamp}] [{label}]{padding} [{origin}] {message}") -def sendCommand(eventnum, level, msg, origin): - print(f"\x1b[?PYN;{str(eventnum)};{lvldict[level]};{msg}~") -def notify(level, msg, origin): - print(f"\x1b[?PYN;0;{lvldict[level]};{msg}~") -def fatal(self, message, origin): notify(message, 0, origin) -def alert(self, message, origin): notify(message, 1, origin) -def crit(self, message, origin): notify(message, 2, origin) -def err(self, message, origin): notify(message, 3, origin) -def warning(self, message, origin): notify(message, 4, origin) -def notice(self, message, origin): notify(message, 5, origin) -def info(self, message, origin): notify(message, 6, origin) -def debug(self, message, origin): notify(message, 7, origin) -def crash(self, message, origin): - sendCommand(3, 0, "uhhhh so the program kinda crashed sorry", origin) - raise FatalLoggerError("[FATAL] [{}] {}".format(origin, message)) -sendCommand(0, 0, "hey you should probably know that your RAM is corrupted and you cant replace it because of the shortage. byeeeee", "test prgm") -sendCommand(0, 1, "UNRECOVERABLE PYTHON ERROR!!!!! Exiting gracefully... JUST KIDDING WHAT DID YOU THINK IT WAS GONNA DO", "test prgm") -sendCommand(0, 2, "so the program is running but it just started looping on the motor frying bit of your program.", "test prgm") -sendCommand(0, 3, "syntax error, you failure!!!! use an error checker", "test prgm") -sendCommand(0, 4, "wanted to just say that your motor is kinda being slow", "test prgm") -sendCommand(0, 5, "so your motor is like 1% slower than it should be but really no one cares", "test prgm") -sendCommand(0, 6, "hey everything in the program is going well just in case nothing in your life is", "test prgm") -sendCommand(0, 7, "nobody cares about me but you should know that", "test prgm") -def breakpoint(): - #send a command 8 and also get all important info about the code here and print \ No newline at end of file + def sendCommand(self, eventnum, level, msg, origin): + print(f"\x1b[?PYN;{str(eventnum)};{lvldict[level]};{msg}~") + def notify(level, msg, origin): + self.sendCommand(self, 0, level, msg, origin) + def notifyfatal(self, message, origin): notify(message, 0, origin) + def notifyalert(self, message, origin): notify(message, 1, origin) + def notifycrit(self, message, origin): notify(message, 2, origin) + def notifyerr(self, message, origin): notify(message, 3, origin) + def notifywarning(self, message, origin): notify(message, 4, origin) + def notifynotice(self, message, origin): notify(message, 5, origin) + def notifyinfo(self, message, origin): notify(message, 6, origin) + def notifydebug(self, message, origin): notify(message, 7, origin) + def notifycrash(self, message, origin): + sendCommand(3, 0, "uhhhh so the program kinda crashed sorry", origin) + raise FatalLoggerError("[FATAL] [{}] {}".format(origin, message)) + + def breakpoint(self, message, origin): + sendCommand(3, 8, "currently everything about the robot") + def testAll(self): + self.start() + notifyfatal("hey you should probably know that your RAM is corrupted and you cant replace it because of the shortage. byeeeee", "test prgm") + notifyalert(0, 1, "UNRECOVERABLE PYTHON ERROR!!!!! Exiting gracefully... JUST KIDDING WHAT DID YOU THINK IT WAS GONNA DO", "test prgm") + notifycrit(0, 2, "so the program is running but it just started looping on the motor frying bit of your program.", "test prgm") + notifyerr(0, 3, "syntax error, you failure!!!! use an error checker", "test prgm") + notifywarning(0, 4, "wanted to just say that your motor is kinda being slow", "test prgm") + notifynotice(0, 5, "so your motor is like 1% slower than it should be but really no one cares", "test prgm") + notifyinfo(0, 6, "hey everything in the program is going well just in case nothing in your life is", "test prgm") + notifydebug(0, 7, "nobody cares about me but you should know that", "test prgm") +if __name__ == "__main__": + pynlogger = PynamicsLogger() + pynlogger.testAll() From b9e5bc23e2340f77cddd610373d6063290a696c0 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Fri, 10 Apr 2026 17:37:01 +0000 Subject: [PATCH 36/38] Update tests/pynamics-logger.py --- tests/pynamics-logger.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/pynamics-logger.py b/tests/pynamics-logger.py index f774d1a..2e317d8 100644 --- a/tests/pynamics-logger.py +++ b/tests/pynamics-logger.py @@ -1,13 +1,14 @@ # Event codes: #0: notify -#1: prgm_start -#2: prgm_end -#3: prgm_crash -#4: snsr_data -#5: mtr_data -#6: perf_smpl -#7: get_time -#8: breakpoint +#1: hfnotify +#2: prgm_start +#3: prgm_end +#4: prgm_crash +#5: snsr_data +#6: mtr_data +#7: perf_smpl +#8: get_time +#9: breakpoint class PynamicsLogger: def __init__(self): self.verboseness = 7 @@ -53,7 +54,7 @@ class PynamicsLogger: def notifyinfo(self, message, origin): notify(message, 6, origin) def notifydebug(self, message, origin): notify(message, 7, origin) def notifycrash(self, message, origin): - sendCommand(3, 0, "uhhhh so the program kinda crashed sorry", origin) + sendCommand(4, 0, "uhhhh so the program kinda crashed sorry", origin) raise FatalLoggerError("[FATAL] [{}] {}".format(origin, message)) def breakpoint(self, message, origin): From 6ff04f3caeeb1d6559c4a0780fc1c5c29c03f5de Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Wed, 6 May 2026 20:56:52 +0000 Subject: [PATCH 37/38] Add experiments/trajectory_drive.py --- experiments/trajectory_drive.py | 40 +++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 experiments/trajectory_drive.py diff --git a/experiments/trajectory_drive.py b/experiments/trajectory_drive.py new file mode 100644 index 0000000..47948d7 --- /dev/null +++ b/experiments/trajectory_drive.py @@ -0,0 +1,40 @@ +from pybricks.hubs import PrimeHub +from pybricks.pupdevices import Motor, ColorSensor, UltrasonicSensor, ForceSensor +from pybricks.parameters import Button, Color, Direction, Port, Side, Stop +from pybricks.robotics import DriveBase +from pybricks.tools import wait, StopWatch +import umath +hub = PrimeHub() + +left_motor = Motor(Port.A, Direction.COUNTERCLOCKWISE) +right_motor = Motor(Port.B,Direction.CLOCKWISE) # Specify default direction +left_arm = Motor(Port.C, Direction.CLOCKWISE, [[12,36]],[[12,20,24]] ) # Specify default direction +right_arm = Motor(Port.D, Direction.CLOCKWISE,[[12,36],[12,20,24]]) #Added gear train list for gear ration +lazer_ranger = UltrasonicSensor(Port.E) +color_sensor = ColorSensor(Port.F) +# DriveBase configuration +WHEEL_DIAMETER = 68.8 # mm (adjust for your wheels) +AXLE_TRACK = 180 # mm (distance between wheels) +drive_base = DriveBase(left_motor, right_motor, WHEEL_DIAMETER, AXLE_TRACK) +drive_base.settings(600, 500, 300, 200) +drive_base.use_gyro(True) + +transformations = { + "d": [2, 3], + "a": [0, 90] +} +current = { + "x": 0, + "y": 0, + "a": 0 +} +# Extra useless code +#for transformation in transformations: +# current.x += transformation.d * umath.cos(umath.radians(transformation.a)) +# current.y += transformation.d * umath.sin(umath.radians(transformation.a)) +def addtoposition(a, r): + current.x += r*cos(a) + current.y += r*sin(a) + current.a = a + +drive_base.drive() \ No newline at end of file From a1fe733115cbca9611093e9d6065699119cb42f8 Mon Sep 17 00:00:00 2001 From: Arcmyx Official Date: Thu, 7 May 2026 00:58:58 +0000 Subject: [PATCH 38/38] The basic trajectory drive. Needs some config, tweaking, and optimization, but this is the basic sta --- experiments/trajectory_drive.py | 171 +++++++++++++++++++++++++++----- 1 file changed, 145 insertions(+), 26 deletions(-) diff --git a/experiments/trajectory_drive.py b/experiments/trajectory_drive.py index 47948d7..c4bc94a 100644 --- a/experiments/trajectory_drive.py +++ b/experiments/trajectory_drive.py @@ -1,40 +1,159 @@ from pybricks.hubs import PrimeHub from pybricks.pupdevices import Motor, ColorSensor, UltrasonicSensor, ForceSensor -from pybricks.parameters import Button, Color, Direction, Port, Side, Stop +from pybricks.parameters import Button, Color, Direction, Port, Side, Stop, Axis from pybricks.robotics import DriveBase -from pybricks.tools import wait, StopWatch +from pybricks.tools import wait, StopWatch, run_task, multitask import umath + hub = PrimeHub() left_motor = Motor(Port.A, Direction.COUNTERCLOCKWISE) -right_motor = Motor(Port.B,Direction.CLOCKWISE) # Specify default direction -left_arm = Motor(Port.C, Direction.CLOCKWISE, [[12,36]],[[12,20,24]] ) # Specify default direction -right_arm = Motor(Port.D, Direction.CLOCKWISE,[[12,36],[12,20,24]]) #Added gear train list for gear ration +right_motor = Motor(Port.B, Direction.CLOCKWISE) +left_arm = Motor(Port.C, Direction.CLOCKWISE, [[12,36]], [[12,20,24]]) +right_arm = Motor(Port.D, Direction.CLOCKWISE, [[12,36],[12,20,24]]) lazer_ranger = UltrasonicSensor(Port.E) color_sensor = ColorSensor(Port.F) -# DriveBase configuration -WHEEL_DIAMETER = 68.8 # mm (adjust for your wheels) -AXLE_TRACK = 180 # mm (distance between wheels) + +WHEEL_DIAMETER = 68.8 +AXLE_TRACK = 180 drive_base = DriveBase(left_motor, right_motor, WHEEL_DIAMETER, AXLE_TRACK) drive_base.settings(600, 500, 300, 200) -drive_base.use_gyro(True) +drive_base.use_gyro(False) + +current = {"x": 0, "y": 0, "a": 0} +target = {"x": 0, "y": 200, "a": 0} +done = False -transformations = { - "d": [2, 3], - "a": [0, 90] -} -current = { - "x": 0, - "y": 0, - "a": 0 -} -# Extra useless code -#for transformation in transformations: -# current.x += transformation.d * umath.cos(umath.radians(transformation.a)) -# current.y += transformation.d * umath.sin(umath.radians(transformation.a)) def addtoposition(a, r): - current.x += r*cos(a) - current.y += r*sin(a) - current.a = a + current["x"] += r * umath.sin(umath.radians(a)) + current["y"] += r * umath.cos(umath.radians(a)) + current["a"] = a -drive_base.drive() \ No newline at end of file +async def track_heading(): + while True: + current["a"] = hub.imu.heading() + await wait(20) + +async def printStats(): + last_dist = 0 + while True: + if done: + break + current_dist = drive_base.distance() + instant_dist = current_dist - last_dist + last_dist = current_dist + + current["x"] += instant_dist * umath.sin(umath.radians(current["a"])) + current["y"] += instant_dist * umath.cos(umath.radians(current["a"])) + + dx = target["x"] - current["x"] + dy = target["y"] - current["y"] + remaining = umath.sqrt(dx*dx + dy*dy) + desired_a = 90 - umath.degrees(umath.atan2(dy, dx)) + print("x:", current["x"], "y:", current["y"], "desired_a:", desired_a, "current_a:", current["a"], "remaining:", remaining) + + await wait(50) + +async def pid_turn(target_angle): + KP = 3 + KI = 0.01 + KD = 1.5 + SETTLE_THRESHOLD = 0.5 + SETTLE_TIME = 500 + + integral = 0 + last_error = 0 + settled_timer = StopWatch() + settled = False + + while True: + error = target_angle - current["a"] + while error > 180: error -= 360 + while error < -180: error += 360 + + integral += error + integral = max(-100, min(100, integral)) + derivative = error - last_error + last_error = error + + turn_rate = KP * error + KI * integral + KD * derivative + turn_rate = max(-200, min(200, turn_rate)) + + drive_base.drive(0, turn_rate) + + if abs(error) < SETTLE_THRESHOLD: + if not settled: + settled = True + settled_timer.reset() + elif settled_timer.time() > SETTLE_TIME: + drive_base.stop() + break + else: + settled = False + + await wait(20) + +async def driveTrajectory(): + global done + ARRIVE_THRESHOLD = 30 # give stage 2 more room to work + FINAL_THRESHOLD = 7 + SPEED = 400 + SLOW_SPEED = 30 + TURN_GAIN = 3.0 + + # stage 1 + while True: + dx = target["x"] - current["x"] + dy = target["y"] - current["y"] + remaining = umath.sqrt(dx*dx + dy*dy) + if remaining < ARRIVE_THRESHOLD: + drive_base.stop() + break + desired_a = 90 - umath.degrees(umath.atan2(dy, dx)) + error = desired_a - current["a"] + while error > 180: error -= 360 + while error < -180: error += 360 + turn_rate = max(-200, min(200, error * TURN_GAIN)) + # in stage 1, scale speed down as it gets closer + speed = max(150, SPEED * min(1, remaining / 300)) + drive_base.drive(speed, turn_rate) + await wait(20) + + print("Stage 1 done") + + # stage 2 + while True: + dx = target["x"] - current["x"] + dy = target["y"] - current["y"] + remaining = umath.sqrt(dx*dx + dy*dy) + if remaining < FINAL_THRESHOLD: + drive_base.stop() + break + a_rad = umath.radians(current["a"]) + forward_x = umath.sin(a_rad) + forward_y = umath.cos(a_rad) + signed_dist = dx * forward_x + dy * forward_y + speed = SLOW_SPEED if signed_dist > 0 else -SLOW_SPEED + drive_base.drive(speed, 0) + await wait(20) + + drive_base.stop() + print("Stage 2 done") + + # stage 3 — PID angle correction + await pid_turn(target["a"]) + + done = True + print("Done! x:", current["x"], "y:", current["y"], "a:", current["a"]) + +async def main(): + drive_base.reset() + await multitask(driveTrajectory(), track_heading(), printStats()) + +hub.imu.reset_heading(0) +while True: + if hub.imu.ready(): + break + +run_task(main()) +print("done") \ No newline at end of file