From 98d0a2b69a24b9b53309be34d7c5aa6aede45c5e Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 7 Nov 2024 12:25:13 +1100 Subject: [PATCH 01/47] umqtt.simple: Restore legacy ssl/ssl_params arguments. Commit 35d41dbb0e4acf1518f520220d405ebe2db257d6 changed the API for using SSL with umqtt, but only did a minor version increase. This broke various uses of this library, eg https://github.com/aws-samples/aws-iot-core-getting-started-micropython Reinstate the original API for specifying an SSL connection. This library now supports the following: - default, ssl=None or ssl=False: no SSL - ssl=True and optional ssl_params specified: use ssl.wrap_socket - ssl=: use provided SSL context to wrap socket Signed-off-by: Damien George --- micropython/umqtt.simple/manifest.py | 4 +++- micropython/umqtt.simple/umqtt/simple.py | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/micropython/umqtt.simple/manifest.py b/micropython/umqtt.simple/manifest.py index 45f9edfbd..709a27505 100644 --- a/micropython/umqtt.simple/manifest.py +++ b/micropython/umqtt.simple/manifest.py @@ -1,5 +1,7 @@ -metadata(description="Lightweight MQTT client for MicroPython.", version="1.5.0") +metadata(description="Lightweight MQTT client for MicroPython.", version="1.6.0") # Originally written by Paul Sokolovsky. +require("ssl") + package("umqtt") diff --git a/micropython/umqtt.simple/umqtt/simple.py b/micropython/umqtt.simple/umqtt/simple.py index 2a5b91655..d9cdffc47 100644 --- a/micropython/umqtt.simple/umqtt/simple.py +++ b/micropython/umqtt.simple/umqtt/simple.py @@ -17,6 +17,7 @@ def __init__( password=None, keepalive=0, ssl=None, + ssl_params={}, ): if port == 0: port = 8883 if ssl else 1883 @@ -25,6 +26,7 @@ def __init__( self.server = server self.port = port self.ssl = ssl + self.ssl_params = ssl_params self.pid = 0 self.cb = None self.user = user @@ -65,7 +67,12 @@ def connect(self, clean_session=True, timeout=None): self.sock.settimeout(timeout) addr = socket.getaddrinfo(self.server, self.port)[0][-1] self.sock.connect(addr) - if self.ssl: + if self.ssl is True: + # Legacy support for ssl=True and ssl_params arguments. + import ssl + + self.sock = ssl.wrap_socket(self.sock, **self.ssl_params) + elif self.ssl: self.sock = self.ssl.wrap_socket(self.sock, server_hostname=self.server) premsg = bytearray(b"\x10\0\0\0\0\0") msg = bytearray(b"\x04MQTT\x04\x02\0\0") From 3e859d2118c4ef9af5f43c930b3138185e5a9fb4 Mon Sep 17 00:00:00 2001 From: marcsello Date: Mon, 30 Dec 2024 15:20:18 +0100 Subject: [PATCH 02/47] nrf24l01: Increase startup delay. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit According to the datasheet of the NRF240L1 chip, 150 μs startup time is only acceptable when the chip is clocked externally. Most modules use a crystal, which require 1.5 ms to settle. It should be okay to wait more in both cases, for a reliable startup. Signed-off-by: Marcell Pünkösd --- micropython/drivers/radio/nrf24l01/nrf24l01.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropython/drivers/radio/nrf24l01/nrf24l01.py b/micropython/drivers/radio/nrf24l01/nrf24l01.py index 76d55312f..9b034f8ba 100644 --- a/micropython/drivers/radio/nrf24l01/nrf24l01.py +++ b/micropython/drivers/radio/nrf24l01/nrf24l01.py @@ -227,7 +227,7 @@ def send(self, buf, timeout=500): def send_start(self, buf): # power up self.reg_write(CONFIG, (self.reg_read(CONFIG) | PWR_UP) & ~PRIM_RX) - utime.sleep_us(150) + utime.sleep_us(1500) # needs to be 1.5ms # send the data self.cs(0) self.spi.readinto(self.buf, W_TX_PAYLOAD) From bd1ab77324641238e684cd26e1686a890868b096 Mon Sep 17 00:00:00 2001 From: marcsello Date: Mon, 30 Dec 2024 15:47:09 +0100 Subject: [PATCH 03/47] nrf24l01: Properly handle timeout. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The timeout condition was not handled before. Upon timeout, this caused the chip to stay active until another send command changed it's state. Sometimes when it was unable to transmit the data, it got stuck in the tx fifo causing it to fill up over time, which set the TX_FULL flag in the STATUS register. Since there was no exceptions raised, the user code could not differentiate a successful send or a timeout condition. Signed-off-by: Marcell Pünkösd --- micropython/drivers/radio/nrf24l01/nrf24l01.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/micropython/drivers/radio/nrf24l01/nrf24l01.py b/micropython/drivers/radio/nrf24l01/nrf24l01.py index 9b034f8ba..04f07b9d9 100644 --- a/micropython/drivers/radio/nrf24l01/nrf24l01.py +++ b/micropython/drivers/radio/nrf24l01/nrf24l01.py @@ -220,6 +220,13 @@ def send(self, buf, timeout=500): result = None while result is None and utime.ticks_diff(utime.ticks_ms(), start) < timeout: result = self.send_done() # 1 == success, 2 == fail + + if result is None: + # timed out, cancel sending and power down the module + self.flush_tx() + self.reg_write(CONFIG, self.reg_read(CONFIG) & ~PWR_UP) + raise OSError("timed out") + if result == 2: raise OSError("send failed") From c7103bb464507137a32edafc77698e40893b773e Mon Sep 17 00:00:00 2001 From: marcsello Date: Mon, 30 Dec 2024 16:10:49 +0100 Subject: [PATCH 04/47] nrf24l01: Optimize status reading. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The value of the STATUS register is always transmitted by the chip when reading any command. So a R_REGISTER command and the turnaround time can be spared by issuing a NOP command instead. This implementation suggested by the datasheet. This operation is compatible with both nRF24L01 and nRF24L01+. Signed-off-by: Marcell Pünkösd --- micropython/drivers/radio/nrf24l01/nrf24l01.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/micropython/drivers/radio/nrf24l01/nrf24l01.py b/micropython/drivers/radio/nrf24l01/nrf24l01.py index 04f07b9d9..d015250cf 100644 --- a/micropython/drivers/radio/nrf24l01/nrf24l01.py +++ b/micropython/drivers/radio/nrf24l01/nrf24l01.py @@ -130,6 +130,13 @@ def reg_write(self, reg, value): self.cs(1) return ret + def read_status(self): + self.cs(0) + # STATUS register is always shifted during command transmit + self.spi.readinto(self.buf, NOP) + self.cs(1) + return self.buf[0] + def flush_rx(self): self.cs(0) self.spi.readinto(self.buf, FLUSH_RX) @@ -250,7 +257,8 @@ def send_start(self, buf): # returns None if send still in progress, 1 for success, 2 for fail def send_done(self): - if not (self.reg_read(STATUS) & (TX_DS | MAX_RT)): + status = self.read_status() + if not (status & (TX_DS | MAX_RT)): return None # tx not finished # either finished or failed: get and clear status flags, power down From 221a877f8a572a489e9859d0851969d57d71b8d4 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 10 Apr 2025 22:33:53 +1000 Subject: [PATCH 05/47] nrf24l10: Bump minor version. Due to the previous three commits. Signed-off-by: Damien George --- micropython/drivers/radio/nrf24l01/manifest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropython/drivers/radio/nrf24l01/manifest.py b/micropython/drivers/radio/nrf24l01/manifest.py index 474d422f9..24276ee4b 100644 --- a/micropython/drivers/radio/nrf24l01/manifest.py +++ b/micropython/drivers/radio/nrf24l01/manifest.py @@ -1,3 +1,3 @@ -metadata(description="nrf24l01 2.4GHz radio driver.", version="0.1.0") +metadata(description="nrf24l01 2.4GHz radio driver.", version="0.2.0") module("nrf24l01.py", opt=3) From f72f3f1a391f15d6fbed01d76f8c97a27427db2f Mon Sep 17 00:00:00 2001 From: Leonard Techel Date: Wed, 29 Jan 2025 10:22:17 +0100 Subject: [PATCH 06/47] lora-sx126x: Fix invert_iq_rx / invert_iq_tx behaviour. This commit fixes a typo and changes a tuple that needs to be mutable to a list (because other parts of the code change elements of this list). Signed-off-by: Damien George --- micropython/lora/lora-sx126x/lora/sx126x.py | 6 +++--- micropython/lora/lora-sx126x/manifest.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/micropython/lora/lora-sx126x/lora/sx126x.py b/micropython/lora/lora-sx126x/lora/sx126x.py index 77052c97c..641367a9f 100644 --- a/micropython/lora/lora-sx126x/lora/sx126x.py +++ b/micropython/lora/lora-sx126x/lora/sx126x.py @@ -363,11 +363,11 @@ def configure(self, lora_cfg): if "preamble_len" in lora_cfg: self._preamble_len = lora_cfg["preamble_len"] - self._invert_iq = ( + self._invert_iq = [ lora_cfg.get("invert_iq_rx", self._invert_iq[0]), lora_cfg.get("invert_iq_tx", self._invert_iq[1]), self._invert_iq[2], - ) + ] if "freq_khz" in lora_cfg: self._rf_freq_hz = int(lora_cfg["freq_khz"] * 1000) @@ -449,7 +449,7 @@ def configure(self, lora_cfg): def _invert_workaround(self, enable): # Apply workaround for DS 15.4 Optimizing the Inverted IQ Operation if self._invert_iq[2] != enable: - val = self._read_read(_REG_IQ_POLARITY_SETUP) + val = self._reg_read(_REG_IQ_POLARITY_SETUP) val = (val & ~4) | _flag(4, enable) self._reg_write(_REG_IQ_POLARITY_SETUP, val) self._invert_iq[2] = enable diff --git a/micropython/lora/lora-sx126x/manifest.py b/micropython/lora/lora-sx126x/manifest.py index 785a975aa..038710820 100644 --- a/micropython/lora/lora-sx126x/manifest.py +++ b/micropython/lora/lora-sx126x/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.1.3") +metadata(version="0.1.4") require("lora") package("lora") From d1a74360a20dadfaae717d4d8c8bd3531672362f Mon Sep 17 00:00:00 2001 From: Dominik Heidler Date: Fri, 14 Mar 2025 13:36:30 +0100 Subject: [PATCH 07/47] unix-ffi/json: Accept both str and bytes as arg for json.loads(). Same as micropython's internal json lib does. Fixes #985. Signed-off-by: Dominik Heidler --- unix-ffi/json/json/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/unix-ffi/json/json/__init__.py b/unix-ffi/json/json/__init__.py index 69a045563..954618f33 100644 --- a/unix-ffi/json/json/__init__.py +++ b/unix-ffi/json/json/__init__.py @@ -354,8 +354,8 @@ def loads( object_pairs_hook=None, **kw ): - """Deserialize ``s`` (a ``str`` instance containing a JSON - document) to a Python object. + """Deserialize ``s`` (a ``str``, ``bytes`` or ``bytearray`` instance + containing a JSON document) to a Python object. ``object_hook`` is an optional function that will be called with the result of any object literal decode (a ``dict``). The return value of @@ -413,4 +413,6 @@ def loads( kw["parse_int"] = parse_int if parse_constant is not None: kw["parse_constant"] = parse_constant + if isinstance(s, (bytes, bytearray)): + s = s.decode('utf-8') return cls(**kw).decode(s) From 42caaf14de35e39ff2e843e9957c7a3f41702fa9 Mon Sep 17 00:00:00 2001 From: Bas van Doren Date: Sun, 6 Apr 2025 14:42:09 +0200 Subject: [PATCH 08/47] unix-ffi/machine: Use libc if librt is not present. Newer implementations of libc integrate the functions from librt, for example glibc since 2.17 and uClibc-ng. So if the librt.so cannot be loaded, it can be assumed that libc contains the expected functions. Signed-off-by: Bas van Doren --- unix-ffi/machine/machine/timer.py | 5 ++++- unix-ffi/machine/manifest.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/unix-ffi/machine/machine/timer.py b/unix-ffi/machine/machine/timer.py index 3f371142c..be00cee33 100644 --- a/unix-ffi/machine/machine/timer.py +++ b/unix-ffi/machine/machine/timer.py @@ -5,7 +5,10 @@ from signal import * libc = ffilib.libc() -librt = ffilib.open("librt") +try: + librt = ffilib.open("librt") +except OSError as e: + librt = libc CLOCK_REALTIME = 0 CLOCK_MONOTONIC = 1 diff --git a/unix-ffi/machine/manifest.py b/unix-ffi/machine/manifest.py index c0e40764d..f7c11b81a 100644 --- a/unix-ffi/machine/manifest.py +++ b/unix-ffi/machine/manifest.py @@ -1,4 +1,4 @@ -metadata(version="0.2.1") +metadata(version="0.2.2") # Originally written by Paul Sokolovsky. From 43ad7c58fd5fc247c255dacb37ab815ec212ee71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=83=E6=98=95=E6=9A=90?= Date: Fri, 6 Dec 2024 13:43:24 +0800 Subject: [PATCH 09/47] requests: Use the host in the redirect url, not the one in headers. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The host in headers extracted from the original url may not be the same as the host in the redirect url. Poping out the host in headers force the code to use the host in the redirect url, otherwise the redirect may fail due to inconsistence of hosts in the original url and the redirect url. Signed-off-by: 黃昕暐 --- python-ecosys/requests/manifest.py | 2 +- python-ecosys/requests/requests/__init__.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/python-ecosys/requests/manifest.py b/python-ecosys/requests/manifest.py index f8343e2a1..85f159753 100644 --- a/python-ecosys/requests/manifest.py +++ b/python-ecosys/requests/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.10.1", pypi="requests") +metadata(version="0.10.2", pypi="requests") package("requests") diff --git a/python-ecosys/requests/requests/__init__.py b/python-ecosys/requests/requests/__init__.py index 2951035f7..4ca7489a4 100644 --- a/python-ecosys/requests/requests/__init__.py +++ b/python-ecosys/requests/requests/__init__.py @@ -182,6 +182,8 @@ def request( if redirect: s.close() + # Use the host specified in the redirect URL, as it may not be the same as the original URL. + headers.pop("Host", None) if status in [301, 302, 303]: return request("GET", redirect, None, None, headers, stream) else: From 86df7233019678616f864e4325460a2f893c5e7f Mon Sep 17 00:00:00 2001 From: FuNK3Y Date: Fri, 7 Feb 2025 19:53:53 +0100 Subject: [PATCH 10/47] aiohttp: Fix header case sensitivity. According to RFC https://datatracker.ietf.org/doc/html/rfc7230#section-3.2 header names are case-insensitive. This commit makes sure that the module behaves consistently regardless of the casing of "Content-type" and "Content-Length" (other headers are not considered by the module). Without this fix, the client seems to wait for the connection termination (~10 seconds) prior to returning any content if the casing of "Content-Length" is different. Signed-off-by: FuNK3Y --- python-ecosys/aiohttp/aiohttp/__init__.py | 12 +++++++++--- python-ecosys/aiohttp/manifest.py | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/python-ecosys/aiohttp/aiohttp/__init__.py b/python-ecosys/aiohttp/aiohttp/__init__.py index 1565163c4..3f57bac83 100644 --- a/python-ecosys/aiohttp/aiohttp/__init__.py +++ b/python-ecosys/aiohttp/aiohttp/__init__.py @@ -18,8 +18,14 @@ class ClientResponse: def __init__(self, reader): self.content = reader + def _get_header(self, keyname, default): + for k in self.headers: + if k.lower() == keyname: + return self.headers[k] + return default + def _decode(self, data): - c_encoding = self.headers.get("Content-Encoding") + c_encoding = self._get_header("content-encoding", None) if c_encoding in ("gzip", "deflate", "gzip,deflate"): try: import deflate @@ -39,10 +45,10 @@ async def read(self, sz=-1): return self._decode(await self.content.read(sz)) async def text(self, encoding="utf-8"): - return (await self.read(int(self.headers.get("Content-Length", -1)))).decode(encoding) + return (await self.read(int(self._get_header("content-length", -1)))).decode(encoding) async def json(self): - return _json.loads(await self.read(int(self.headers.get("Content-Length", -1)))) + return _json.loads(await self.read(int(self._get_header("content-length", -1)))) def __repr__(self): return "" % (self.status, self.headers) diff --git a/python-ecosys/aiohttp/manifest.py b/python-ecosys/aiohttp/manifest.py index 748970e5b..5020ec527 100644 --- a/python-ecosys/aiohttp/manifest.py +++ b/python-ecosys/aiohttp/manifest.py @@ -1,6 +1,6 @@ metadata( description="HTTP client module for MicroPython asyncio module", - version="0.0.3", + version="0.0.4", pypi="aiohttp", ) From 05a56c3cad059245c62df5d76baa5ebc3340f812 Mon Sep 17 00:00:00 2001 From: jomas Date: Mon, 2 Dec 2024 11:23:07 +0100 Subject: [PATCH 11/47] aiohttp: Allow headers to be passed to a WebSocketClient. This commit will make it possible to add headers to a Websocket. Among other things, this allows making a connection to online MQTT brokers over websocket, using the header entry "Sec-WebSocket-Protocol":"mqtt" in the handshake of the upgrade protocol. Signed-off-by: Damien George --- python-ecosys/aiohttp/aiohttp/__init__.py | 2 +- python-ecosys/aiohttp/aiohttp/aiohttp_ws.py | 2 +- python-ecosys/aiohttp/manifest.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python-ecosys/aiohttp/aiohttp/__init__.py b/python-ecosys/aiohttp/aiohttp/__init__.py index 3f57bac83..8c5493f30 100644 --- a/python-ecosys/aiohttp/aiohttp/__init__.py +++ b/python-ecosys/aiohttp/aiohttp/__init__.py @@ -269,7 +269,7 @@ def ws_connect(self, url, ssl=None): return _WSRequestContextManager(self, self._ws_connect(url, ssl=ssl)) async def _ws_connect(self, url, ssl=None): - ws_client = WebSocketClient(None) + ws_client = WebSocketClient(self._base_headers.copy()) await ws_client.connect(url, ssl=ssl, handshake_request=self.request_raw) self._reader = ws_client.reader return ClientWebSocketResponse(ws_client) diff --git a/python-ecosys/aiohttp/aiohttp/aiohttp_ws.py b/python-ecosys/aiohttp/aiohttp/aiohttp_ws.py index 07d833730..6e0818c92 100644 --- a/python-ecosys/aiohttp/aiohttp/aiohttp_ws.py +++ b/python-ecosys/aiohttp/aiohttp/aiohttp_ws.py @@ -136,7 +136,7 @@ def _encode_websocket_frame(cls, opcode, payload): return frame + payload async def handshake(self, uri, ssl, req): - headers = {} + headers = self.params _http_proto = "http" if uri.protocol != "wss" else "https" url = f"{_http_proto}://{uri.hostname}:{uri.port}{uri.path or '/'}" key = binascii.b2a_base64(bytes(random.getrandbits(8) for _ in range(16)))[:-1] diff --git a/python-ecosys/aiohttp/manifest.py b/python-ecosys/aiohttp/manifest.py index 5020ec527..d22a6ce11 100644 --- a/python-ecosys/aiohttp/manifest.py +++ b/python-ecosys/aiohttp/manifest.py @@ -1,6 +1,6 @@ metadata( description="HTTP client module for MicroPython asyncio module", - version="0.0.4", + version="0.0.5", pypi="aiohttp", ) From 9307e21dfb34152ac605cd810447716b459d7f7d Mon Sep 17 00:00:00 2001 From: Matthias Urlichs Date: Wed, 26 Mar 2025 12:00:40 +0100 Subject: [PATCH 12/47] usb-device-cdc: Optimise writing small data so it doesn't require alloc. Only allocate a memoryview when the (first) write was partial. Signed-off-by: Matthias Urlichs --- micropython/usb/usb-device-cdc/manifest.py | 2 +- micropython/usb/usb-device-cdc/usb/device/cdc.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/micropython/usb/usb-device-cdc/manifest.py b/micropython/usb/usb-device-cdc/manifest.py index 4520325e3..e844b6f01 100644 --- a/micropython/usb/usb-device-cdc/manifest.py +++ b/micropython/usb/usb-device-cdc/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.1.1") +metadata(version="0.1.2") require("usb-device") package("usb") diff --git a/micropython/usb/usb-device-cdc/usb/device/cdc.py b/micropython/usb/usb-device-cdc/usb/device/cdc.py index 28bfb0657..0acea184f 100644 --- a/micropython/usb/usb-device-cdc/usb/device/cdc.py +++ b/micropython/usb/usb-device-cdc/usb/device/cdc.py @@ -350,10 +350,9 @@ def _rd_cb(self, ep, res, num_bytes): ### def write(self, buf): - # use a memoryview to track how much of 'buf' we've written so far - # (unfortunately, this means a 1 block allocation for each write, but it's otherwise allocation free.) start = time.ticks_ms() - mv = memoryview(buf) + mv = buf + while True: # Keep pushing buf into _wb into it's all gone nbytes = self._wb.write(mv) @@ -362,6 +361,10 @@ def write(self, buf): if nbytes == len(mv): return len(buf) # Success + # if buf couldn't be fully written on the first attempt + # convert it to a memoryview to track partial writes + if mv is buf: + mv = memoryview(buf) mv = mv[nbytes:] # check for timeout From 48bf3a74a8599aca7ddf8dfbb9d08d8cd5172d25 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 11 Apr 2025 12:23:33 +1000 Subject: [PATCH 13/47] inspect: Fix isgenerator logic. Also optimise both `isgenerator()` and `isgeneratorfunction()` so they use the same lambda, and don't have to create it each time they are called. Fixes issue #997. Signed-off-by: Damien George --- python-stdlib/inspect/inspect.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/python-stdlib/inspect/inspect.py b/python-stdlib/inspect/inspect.py index 06aba8762..fb2ad885d 100644 --- a/python-stdlib/inspect/inspect.py +++ b/python-stdlib/inspect/inspect.py @@ -1,5 +1,7 @@ import sys +_g = lambda: (yield) + def getmembers(obj, pred=None): res = [] @@ -16,11 +18,11 @@ def isfunction(obj): def isgeneratorfunction(obj): - return isinstance(obj, type(lambda: (yield))) + return isinstance(obj, type(_g)) def isgenerator(obj): - return isinstance(obj, type(lambda: (yield)())) + return isinstance(obj, type((_g)())) class _Class: From 2665047fa7c88729e8d581bcf4ed047298c0dc30 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 11 Apr 2025 12:24:53 +1000 Subject: [PATCH 14/47] inspect: Add basic unit tests. Signed-off-by: Damien George --- python-stdlib/inspect/test_inspect.py | 54 +++++++++++++++++++++++++++ tools/ci.sh | 1 + 2 files changed, 55 insertions(+) create mode 100644 python-stdlib/inspect/test_inspect.py diff --git a/python-stdlib/inspect/test_inspect.py b/python-stdlib/inspect/test_inspect.py new file mode 100644 index 000000000..6f262ca64 --- /dev/null +++ b/python-stdlib/inspect/test_inspect.py @@ -0,0 +1,54 @@ +import inspect +import unittest + + +def fun(): + return 1 + + +def gen(): + yield 1 + + +class Class: + def meth(self): + pass + + +entities = ( + fun, + gen, + gen(), + Class, + Class.meth, + Class().meth, + inspect, +) + + +class TestInspect(unittest.TestCase): + def _test_is_helper(self, f, *entities_true): + for entity in entities: + result = f(entity) + if entity in entities_true: + self.assertTrue(result) + else: + self.assertFalse(result) + + def test_isfunction(self): + self._test_is_helper(inspect.isfunction, entities[0], entities[4]) + + def test_isgeneratorfunction(self): + self._test_is_helper(inspect.isgeneratorfunction, entities[1]) + + def test_isgenerator(self): + self._test_is_helper(inspect.isgenerator, entities[2]) + + def test_ismethod(self): + self._test_is_helper(inspect.ismethod, entities[5]) + + def test_isclass(self): + self._test_is_helper(inspect.isclass, entities[3]) + + def test_ismodule(self): + self._test_is_helper(inspect.ismodule, entities[6]) diff --git a/tools/ci.sh b/tools/ci.sh index a5fcdf22e..6689e8aa4 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -86,6 +86,7 @@ function ci_package_tests_run { python-stdlib/datetime \ python-stdlib/fnmatch \ python-stdlib/hashlib \ + python-stdlib/inspect \ python-stdlib/pathlib \ python-stdlib/quopri \ python-stdlib/shutil \ From 5b496e944ec045177afa1620920a168410b7f60b Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 14 Apr 2025 10:24:54 +1000 Subject: [PATCH 15/47] inspect: Implement iscoroutinefunction and iscoroutine. Signed-off-by: Damien George --- python-stdlib/inspect/inspect.py | 5 +++++ python-stdlib/inspect/manifest.py | 2 +- python-stdlib/inspect/test_inspect.py | 6 ++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/python-stdlib/inspect/inspect.py b/python-stdlib/inspect/inspect.py index fb2ad885d..c16c6b3e3 100644 --- a/python-stdlib/inspect/inspect.py +++ b/python-stdlib/inspect/inspect.py @@ -25,6 +25,11 @@ def isgenerator(obj): return isinstance(obj, type((_g)())) +# In MicroPython there's currently no way to distinguish between generators and coroutines. +iscoroutinefunction = isgeneratorfunction +iscoroutine = isgenerator + + class _Class: def meth(): pass diff --git a/python-stdlib/inspect/manifest.py b/python-stdlib/inspect/manifest.py index a9d5a2381..e99e659f2 100644 --- a/python-stdlib/inspect/manifest.py +++ b/python-stdlib/inspect/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.1.2") +metadata(version="0.1.3") module("inspect.py") diff --git a/python-stdlib/inspect/test_inspect.py b/python-stdlib/inspect/test_inspect.py index 6f262ca64..29ed80f11 100644 --- a/python-stdlib/inspect/test_inspect.py +++ b/python-stdlib/inspect/test_inspect.py @@ -44,6 +44,12 @@ def test_isgeneratorfunction(self): def test_isgenerator(self): self._test_is_helper(inspect.isgenerator, entities[2]) + def test_iscoroutinefunction(self): + self._test_is_helper(inspect.iscoroutinefunction, entities[1]) + + def test_iscoroutine(self): + self._test_is_helper(inspect.iscoroutine, entities[2]) + def test_ismethod(self): self._test_is_helper(inspect.ismethod, entities[5]) From f8c8875e250e1dfef0158f15ccbb66e8cee9aa57 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 24 Apr 2025 10:34:45 +1000 Subject: [PATCH 16/47] lora: Fix SNR value in SX126x received packets. Wasn't being treated as a signed value. Fixes issue #999. Signed-off-by: Angus Gratton --- micropython/lora/lora-sx126x/lora/sx126x.py | 5 +++-- micropython/lora/lora-sx126x/manifest.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/micropython/lora/lora-sx126x/lora/sx126x.py b/micropython/lora/lora-sx126x/lora/sx126x.py index 641367a9f..7fa4896ae 100644 --- a/micropython/lora/lora-sx126x/lora/sx126x.py +++ b/micropython/lora/lora-sx126x/lora/sx126x.py @@ -596,8 +596,9 @@ def _read_packet(self, rx_packet, flags): pkt_status = self._cmd("B", _CMD_GET_PACKET_STATUS, n_read=4) rx_packet.ticks_ms = ticks_ms - rx_packet.snr = pkt_status[2] # SNR, units: dB *4 - rx_packet.rssi = 0 - pkt_status[1] // 2 # RSSI, units: dBm + # SNR units are dB * 4 (signed) + rx_packet.rssi, rx_packet.snr = struct.unpack("xBbx", pkt_status) + rx_packet.rssi //= -2 # RSSI, units: dBm rx_packet.crc_error = (flags & _IRQ_CRC_ERR) != 0 return rx_packet diff --git a/micropython/lora/lora-sx126x/manifest.py b/micropython/lora/lora-sx126x/manifest.py index 038710820..76fa91d8d 100644 --- a/micropython/lora/lora-sx126x/manifest.py +++ b/micropython/lora/lora-sx126x/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.1.4") +metadata(version="0.1.5") require("lora") package("lora") From d887a021e831ee3b7e6f00f6a4c32b36ee6c4769 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 24 Apr 2025 10:41:10 +1000 Subject: [PATCH 17/47] top: Bump the Ruff version to 0.11.6. With small code fixes to match. Signed-off-by: Angus Gratton --- .github/workflows/ruff.yml | 3 ++- .pre-commit-config.yaml | 3 ++- .../bluetooth/aioble/examples/l2cap_file_client.py | 2 +- .../bluetooth/aioble/examples/l2cap_file_server.py | 2 +- micropython/drivers/codec/wm8960/wm8960.py | 12 ++++-------- micropython/drivers/display/lcd160cr/lcd160cr.py | 6 ++---- pyproject.toml | 3 ++- 7 files changed, 14 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 71c4131f0..b347e34ee 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -6,6 +6,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - run: pip install --user ruff==0.1.2 + # Version should be kept in sync with .pre-commit_config.yaml & also micropython + - run: pip install --user ruff==0.11.6 - run: ruff check --output-format=github . - run: ruff format --diff . diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 335c1c2fc..05f5d3df0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,8 @@ repos: verbose: true stages: [commit-msg] - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.1.2 + # Version should be kept in sync with .github/workflows/ruff.yml & also micropython + rev: v0.11.6 hooks: - id: ruff id: ruff-format diff --git a/micropython/bluetooth/aioble/examples/l2cap_file_client.py b/micropython/bluetooth/aioble/examples/l2cap_file_client.py index 9dce349a7..0817ca162 100644 --- a/micropython/bluetooth/aioble/examples/l2cap_file_client.py +++ b/micropython/bluetooth/aioble/examples/l2cap_file_client.py @@ -88,7 +88,7 @@ async def download(self, path, dest): await self._command(_COMMAND_SEND, path.encode()) - with open(dest, "wb") as f: # noqa: ASYNC101 + with open(dest, "wb") as f: # noqa: ASYNC230 total = 0 buf = bytearray(self._channel.our_mtu) mv = memoryview(buf) diff --git a/micropython/bluetooth/aioble/examples/l2cap_file_server.py b/micropython/bluetooth/aioble/examples/l2cap_file_server.py index fb806effc..3c3b3b44d 100644 --- a/micropython/bluetooth/aioble/examples/l2cap_file_server.py +++ b/micropython/bluetooth/aioble/examples/l2cap_file_server.py @@ -83,7 +83,7 @@ async def l2cap_task(connection): if send_file: print("Sending:", send_file) - with open(send_file, "rb") as f: # noqa: ASYNC101 + with open(send_file, "rb") as f: # noqa: ASYNC230 buf = bytearray(channel.peer_mtu) mv = memoryview(buf) while n := f.readinto(buf): diff --git a/micropython/drivers/codec/wm8960/wm8960.py b/micropython/drivers/codec/wm8960/wm8960.py index dc0dd655d..313649f36 100644 --- a/micropython/drivers/codec/wm8960/wm8960.py +++ b/micropython/drivers/codec/wm8960/wm8960.py @@ -331,8 +331,7 @@ def __init__( sysclk = 11289600 else: sysclk = 12288000 - if sysclk < sample_rate * 256: - sysclk = sample_rate * 256 + sysclk = max(sysclk, sample_rate * 256) if mclk_freq is None: mclk_freq = sysclk else: # sysclk_source == SYSCLK_MCLK @@ -691,10 +690,8 @@ def alc_mode(self, channel, mode=ALC_MODE): def alc_gain(self, target=-12, max_gain=30, min_gain=-17.25, noise_gate=-78): def limit(value, minval, maxval): value = int(value) - if value < minval: - value = minval - if value > maxval: - value = maxval + value = max(value, minval) + value = min(value, maxval) return value target = limit((16 + (target * 2) // 3), 0, 15) @@ -718,8 +715,7 @@ def logb(value, limit): while value > 1: value >>= 1 lb += 1 - if lb > limit: - lb = limit + lb = min(lb, limit) return lb attack = logb(attack / 6, 7) diff --git a/micropython/drivers/display/lcd160cr/lcd160cr.py b/micropython/drivers/display/lcd160cr/lcd160cr.py index 42b5e215b..177c6fea3 100644 --- a/micropython/drivers/display/lcd160cr/lcd160cr.py +++ b/micropython/drivers/display/lcd160cr/lcd160cr.py @@ -189,10 +189,8 @@ def clip_line(c, w, h): c[3] = h - 1 else: if c[0] == c[2]: - if c[1] < 0: - c[1] = 0 - if c[3] < 0: - c[3] = 0 + c[1] = max(c[1], 0) + c[3] = max(c[3], 0) else: if c[3] < c[1]: c[0], c[2] = c[2], c[0] diff --git a/pyproject.toml b/pyproject.toml index 4776ddfe9..83d29405d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ ignore = [ "ISC003", # micropython does not support implicit concatenation of f-strings "PIE810", # micropython does not support passing tuples to .startswith or .endswith "PLC1901", - "PLR1701", + "PLR1704", # sometimes desirable to redefine an argument to save code size "PLR1714", "PLR5501", "PLW0602", @@ -72,6 +72,7 @@ ignore = [ "PLW2901", "RUF012", "RUF100", + "SIM101", "W191", # tab-indent, redundant when using formatter ] line-length = 99 From 68e0dfce0a8708e7d9aef4446fad842cc5929410 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 24 Apr 2025 11:01:42 +1000 Subject: [PATCH 18/47] all: Apply Ruff 0.11.6 reformatting changes. Signed-off-by: Angus Gratton --- micropython/aiorepl/aiorepl.py | 16 ++++++++-------- micropython/drivers/imu/lsm9ds1/lsm9ds1.py | 1 + micropython/drivers/radio/nrf24l01/nrf24l01.py | 3 +-- micropython/drivers/sensor/hts221/hts221.py | 2 +- micropython/drivers/sensor/lps22h/lps22h.py | 1 + micropython/espflash/espflash.py | 16 ++++++++-------- .../lora/examples/reliable_delivery/sender.py | 2 +- .../examples/reliable_delivery/sender_async.py | 2 +- micropython/senml/examples/actuator.py | 1 - micropython/senml/examples/base.py | 1 - micropython/senml/examples/basic.py | 1 - micropython/senml/examples/basic2.py | 1 - micropython/senml/examples/basic_cbor.py | 1 - micropython/senml/examples/custom_record.py | 1 - micropython/senml/examples/gateway.py | 1 - micropython/senml/examples/gateway_actuators.py | 1 - .../senml/examples/supported_data_types.py | 1 - micropython/senml/senml/__init__.py | 1 - micropython/senml/senml/senml_pack.py | 1 - micropython/senml/senml/senml_record.py | 1 - python-ecosys/cbor2/cbor2/__init__.py | 1 - python-ecosys/cbor2/cbor2/_decoder.py | 1 - python-ecosys/cbor2/cbor2/_encoder.py | 1 - python-ecosys/cbor2/examples/cbor_test.py | 1 - 24 files changed, 22 insertions(+), 37 deletions(-) diff --git a/micropython/aiorepl/aiorepl.py b/micropython/aiorepl/aiorepl.py index 8f45dfac0..3f437459d 100644 --- a/micropython/aiorepl/aiorepl.py +++ b/micropython/aiorepl/aiorepl.py @@ -132,7 +132,7 @@ async def task(g=None, prompt="--> "): continue if curs: # move cursor to end of the line - sys.stdout.write("\x1B[{}C".format(curs)) + sys.stdout.write("\x1b[{}C".format(curs)) curs = 0 sys.stdout.write("\n") if cmd: @@ -153,10 +153,10 @@ async def task(g=None, prompt="--> "): if curs: cmd = "".join((cmd[: -curs - 1], cmd[-curs:])) sys.stdout.write( - "\x08\x1B[K" + "\x08\x1b[K" ) # move cursor back, erase to end of line sys.stdout.write(cmd[-curs:]) # redraw line - sys.stdout.write("\x1B[{}D".format(curs)) # reset cursor location + sys.stdout.write("\x1b[{}D".format(curs)) # reset cursor location else: cmd = cmd[:-1] sys.stdout.write("\x08 \x08") @@ -207,21 +207,21 @@ async def task(g=None, prompt="--> "): elif key == "[D": # left if curs < len(cmd) - 1: curs += 1 - sys.stdout.write("\x1B") + sys.stdout.write("\x1b") sys.stdout.write(key) elif key == "[C": # right if curs: curs -= 1 - sys.stdout.write("\x1B") + sys.stdout.write("\x1b") sys.stdout.write(key) elif key == "[H": # home pcurs = curs curs = len(cmd) - sys.stdout.write("\x1B[{}D".format(curs - pcurs)) # move cursor left + sys.stdout.write("\x1b[{}D".format(curs - pcurs)) # move cursor left elif key == "[F": # end pcurs = curs curs = 0 - sys.stdout.write("\x1B[{}C".format(pcurs)) # move cursor right + sys.stdout.write("\x1b[{}C".format(pcurs)) # move cursor right else: # sys.stdout.write("\\x") # sys.stdout.write(hex(c)) @@ -231,7 +231,7 @@ async def task(g=None, prompt="--> "): # inserting into middle of line cmd = "".join((cmd[:-curs], b, cmd[-curs:])) sys.stdout.write(cmd[-curs - 1 :]) # redraw line to end - sys.stdout.write("\x1B[{}D".format(curs)) # reset cursor location + sys.stdout.write("\x1b[{}D".format(curs)) # reset cursor location else: sys.stdout.write(b) cmd += b diff --git a/micropython/drivers/imu/lsm9ds1/lsm9ds1.py b/micropython/drivers/imu/lsm9ds1/lsm9ds1.py index e3d46429d..e5a96ad5c 100644 --- a/micropython/drivers/imu/lsm9ds1/lsm9ds1.py +++ b/micropython/drivers/imu/lsm9ds1/lsm9ds1.py @@ -43,6 +43,7 @@ print("") time.sleep_ms(100) """ + import array from micropython import const diff --git a/micropython/drivers/radio/nrf24l01/nrf24l01.py b/micropython/drivers/radio/nrf24l01/nrf24l01.py index d015250cf..9fbadcd60 100644 --- a/micropython/drivers/radio/nrf24l01/nrf24l01.py +++ b/micropython/drivers/radio/nrf24l01/nrf24l01.py @@ -1,5 +1,4 @@ -"""NRF24L01 driver for MicroPython -""" +"""NRF24L01 driver for MicroPython""" from micropython import const import utime diff --git a/micropython/drivers/sensor/hts221/hts221.py b/micropython/drivers/sensor/hts221/hts221.py index fec52a738..c6cd51f48 100644 --- a/micropython/drivers/sensor/hts221/hts221.py +++ b/micropython/drivers/sensor/hts221/hts221.py @@ -52,7 +52,7 @@ def __init__(self, i2c, data_rate=1, address=0x5F): # Set configuration register # Humidity and temperature average configuration - self.bus.writeto_mem(self.slv_addr, 0x10, b"\x1B") + self.bus.writeto_mem(self.slv_addr, 0x10, b"\x1b") # Set control register # PD | BDU | ODR diff --git a/micropython/drivers/sensor/lps22h/lps22h.py b/micropython/drivers/sensor/lps22h/lps22h.py index 1e7f4ec3e..7dec72528 100644 --- a/micropython/drivers/sensor/lps22h/lps22h.py +++ b/micropython/drivers/sensor/lps22h/lps22h.py @@ -37,6 +37,7 @@ print("Pressure: %.2f hPa Temperature: %.2f C"%(lps.pressure(), lps.temperature())) time.sleep_ms(10) """ + import machine from micropython import const diff --git a/micropython/espflash/espflash.py b/micropython/espflash/espflash.py index 74988777a..fbf4e1f7e 100644 --- a/micropython/espflash/espflash.py +++ b/micropython/espflash/espflash.py @@ -113,22 +113,22 @@ def _poll_reg(self, addr, flag, retry=10, delay=0.050): raise Exception(f"Register poll timeout. Addr: 0x{addr:02X} Flag: 0x{flag:02X}.") def _write_slip(self, pkt): - pkt = pkt.replace(b"\xDB", b"\xdb\xdd").replace(b"\xc0", b"\xdb\xdc") - self.uart.write(b"\xC0" + pkt + b"\xC0") + pkt = pkt.replace(b"\xdb", b"\xdb\xdd").replace(b"\xc0", b"\xdb\xdc") + self.uart.write(b"\xc0" + pkt + b"\xc0") self._log(pkt) def _read_slip(self): pkt = None # Find the packet start. - if self.uart.read(1) == b"\xC0": + if self.uart.read(1) == b"\xc0": pkt = bytearray() while True: b = self.uart.read(1) - if b is None or b == b"\xC0": + if b is None or b == b"\xc0": break pkt += b - pkt = pkt.replace(b"\xDB\xDD", b"\xDB").replace(b"\xDB\xDC", b"\xC0") - self._log(b"\xC0" + pkt + b"\xC0", False) + pkt = pkt.replace(b"\xdb\xdd", b"\xdb").replace(b"\xdb\xdc", b"\xc0") + self._log(b"\xc0" + pkt + b"\xc0", False) return pkt def _strerror(self, err): @@ -230,7 +230,7 @@ def flash_read_size(self): raise Exception(f"Unexpected flash size bits: 0x{flash_bits:02X}.") flash_size = 2**flash_bits - print(f"Flash size {flash_size/1024/1024} MBytes") + print(f"Flash size {flash_size / 1024 / 1024} MBytes") return flash_size def flash_attach(self): @@ -265,7 +265,7 @@ def flash_write_file(self, path, blksize=0x1000): self.md5sum.update(buf) # The last data block should be padded to the block size with 0xFF bytes. if len(buf) < blksize: - buf += b"\xFF" * (blksize - len(buf)) + buf += b"\xff" * (blksize - len(buf)) checksum = self._checksum(buf) if seq % erase_blocks == 0: # print(f"Erasing {seq} -> {seq+erase_blocks}...") diff --git a/micropython/lora/examples/reliable_delivery/sender.py b/micropython/lora/examples/reliable_delivery/sender.py index 2fba0d4d7..957e9d824 100644 --- a/micropython/lora/examples/reliable_delivery/sender.py +++ b/micropython/lora/examples/reliable_delivery/sender.py @@ -149,7 +149,7 @@ def send(self, sensor_data, adjust_output_power=True): delta = time.ticks_diff(maybe_ack.ticks_ms, sent_at) print( f"ACKed with RSSI {rssi}, {delta}ms after sent " - + f"(skew {delta-ACK_DELAY_MS-ack_packet_ms}ms)" + + f"(skew {delta - ACK_DELAY_MS - ack_packet_ms}ms)" ) if adjust_output_power: diff --git a/micropython/lora/examples/reliable_delivery/sender_async.py b/micropython/lora/examples/reliable_delivery/sender_async.py index a27420f6b..4c14d6f11 100644 --- a/micropython/lora/examples/reliable_delivery/sender_async.py +++ b/micropython/lora/examples/reliable_delivery/sender_async.py @@ -141,7 +141,7 @@ async def send(self, sensor_data, adjust_output_power=True): delta = time.ticks_diff(maybe_ack.ticks_ms, sent_at) print( f"ACKed with RSSI {rssi}, {delta}ms after sent " - + f"(skew {delta-ACK_DELAY_MS-ack_packet_ms}ms)" + + f"(skew {delta - ACK_DELAY_MS - ack_packet_ms}ms)" ) if adjust_output_power: diff --git a/micropython/senml/examples/actuator.py b/micropython/senml/examples/actuator.py index 8e254349d..2fac474cd 100644 --- a/micropython/senml/examples/actuator.py +++ b/micropython/senml/examples/actuator.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from senml import * diff --git a/micropython/senml/examples/base.py b/micropython/senml/examples/base.py index 426cbbd0e..6a49cfdd2 100644 --- a/micropython/senml/examples/base.py +++ b/micropython/senml/examples/base.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from senml import * import time diff --git a/micropython/senml/examples/basic.py b/micropython/senml/examples/basic.py index 18a3a9a06..3f3ed6150 100644 --- a/micropython/senml/examples/basic.py +++ b/micropython/senml/examples/basic.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from senml import * import time diff --git a/micropython/senml/examples/basic2.py b/micropython/senml/examples/basic2.py index c2ea153bd..ca53b4a6e 100644 --- a/micropython/senml/examples/basic2.py +++ b/micropython/senml/examples/basic2.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from senml import * import time diff --git a/micropython/senml/examples/basic_cbor.py b/micropython/senml/examples/basic_cbor.py index 804a886fc..b9d9d620b 100644 --- a/micropython/senml/examples/basic_cbor.py +++ b/micropython/senml/examples/basic_cbor.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from senml import * import time import cbor2 diff --git a/micropython/senml/examples/custom_record.py b/micropython/senml/examples/custom_record.py index e68d05f5b..1e83ea06b 100644 --- a/micropython/senml/examples/custom_record.py +++ b/micropython/senml/examples/custom_record.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from senml import * import time diff --git a/micropython/senml/examples/gateway.py b/micropython/senml/examples/gateway.py index d28e4cffc..e1827ff2d 100644 --- a/micropython/senml/examples/gateway.py +++ b/micropython/senml/examples/gateway.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from senml import * import time diff --git a/micropython/senml/examples/gateway_actuators.py b/micropython/senml/examples/gateway_actuators.py index add5ed24c..a7e5b378c 100644 --- a/micropython/senml/examples/gateway_actuators.py +++ b/micropython/senml/examples/gateway_actuators.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from senml import * diff --git a/micropython/senml/examples/supported_data_types.py b/micropython/senml/examples/supported_data_types.py index 3149f49d2..94976bb66 100644 --- a/micropython/senml/examples/supported_data_types.py +++ b/micropython/senml/examples/supported_data_types.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from senml import * import time diff --git a/micropython/senml/senml/__init__.py b/micropython/senml/senml/__init__.py index 93cbd7700..908375fdb 100644 --- a/micropython/senml/senml/__init__.py +++ b/micropython/senml/senml/__init__.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from .senml_base import SenmlBase from .senml_pack import SenmlPack from .senml_record import SenmlRecord diff --git a/micropython/senml/senml/senml_pack.py b/micropython/senml/senml/senml_pack.py index 4e106fd3e..5a0554467 100644 --- a/micropython/senml/senml/senml_pack.py +++ b/micropython/senml/senml/senml_pack.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from senml.senml_record import SenmlRecord from senml.senml_base import SenmlBase import json diff --git a/micropython/senml/senml/senml_record.py b/micropython/senml/senml/senml_record.py index 9dfe22873..ae40f0f70 100644 --- a/micropython/senml/senml/senml_record.py +++ b/micropython/senml/senml/senml_record.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - import binascii from senml.senml_base import SenmlBase diff --git a/python-ecosys/cbor2/cbor2/__init__.py b/python-ecosys/cbor2/cbor2/__init__.py index 7cd98734e..80790f0da 100644 --- a/python-ecosys/cbor2/cbor2/__init__.py +++ b/python-ecosys/cbor2/cbor2/__init__.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - from ._decoder import CBORDecoder from ._decoder import load from ._decoder import loads diff --git a/python-ecosys/cbor2/cbor2/_decoder.py b/python-ecosys/cbor2/cbor2/_decoder.py index 5d509a535..965dbfd46 100644 --- a/python-ecosys/cbor2/cbor2/_decoder.py +++ b/python-ecosys/cbor2/cbor2/_decoder.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - import io import struct diff --git a/python-ecosys/cbor2/cbor2/_encoder.py b/python-ecosys/cbor2/cbor2/_encoder.py index 80a4ac022..fe8715468 100644 --- a/python-ecosys/cbor2/cbor2/_encoder.py +++ b/python-ecosys/cbor2/cbor2/_encoder.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - import io import struct diff --git a/python-ecosys/cbor2/examples/cbor_test.py b/python-ecosys/cbor2/examples/cbor_test.py index b4f351786..a1cd7e93e 100644 --- a/python-ecosys/cbor2/examples/cbor_test.py +++ b/python-ecosys/cbor2/examples/cbor_test.py @@ -23,7 +23,6 @@ THE SOFTWARE. """ - import cbor2 input = [ From 0d60de65b7cf6bf784e2e4e9f84f1a70faf2ae83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Thu, 7 Mar 2024 15:01:10 +0100 Subject: [PATCH 19/47] utop: Add initial implementation for ESP32. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniël van de Giessen --- micropython/utop/README.md | 12 +++++ micropython/utop/manifest.py | 6 +++ micropython/utop/utop.py | 85 ++++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 micropython/utop/README.md create mode 100644 micropython/utop/manifest.py create mode 100644 micropython/utop/utop.py diff --git a/micropython/utop/README.md b/micropython/utop/README.md new file mode 100644 index 000000000..7b07e785d --- /dev/null +++ b/micropython/utop/README.md @@ -0,0 +1,12 @@ +# utop + +Provides a top-like live overview of the running system. + +On the `esp32` port this depends on the `esp32.idf_task_info()` function, which +can be enabled by adding the following lines to the board `sdkconfig`: + +```ini +CONFIG_FREERTOS_USE_TRACE_FACILITY=y +CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y +CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y +``` diff --git a/micropython/utop/manifest.py b/micropython/utop/manifest.py new file mode 100644 index 000000000..ebba07270 --- /dev/null +++ b/micropython/utop/manifest.py @@ -0,0 +1,6 @@ +metadata( + version="0.1.0", + description="Provides a top-like live overview of the running system.", +) + +module("utop.py") diff --git a/micropython/utop/utop.py b/micropython/utop/utop.py new file mode 100644 index 000000000..d4e94c94a --- /dev/null +++ b/micropython/utop/utop.py @@ -0,0 +1,85 @@ +import time + +try: + import esp32 + import _thread +except ImportError: + esp32 = None + + +def top(update_interval_ms=1000, timeout_ms=None, thread_names={}): + time_start = time.ticks_ms() + previous_total_runtime = None + previous_task_runtimes = {} + previous_line_count = 0 + esp32_task_state_names = dict( + enumerate(("running", "ready", "blocked", "suspended", "deleted", "invalid")) + ) + + while timeout_ms is None or abs(time.ticks_diff(time.ticks_ms(), time_start)) < timeout_ms: + if previous_line_count > 0: + print("\x1b[{}A".format(previous_line_count), end="") + line_count = 0 + + if esp32 is not None: + if not hasattr(esp32, "idf_task_info"): + print( + "INFO: esp32.idf_task_info() is not available, cannot list active tasks.\x1b[K" + ) + line_count += 1 + else: + print(" CPU% CORE PRIORITY STATE STACKWATERMARK NAME\x1b[K") + line_count += 1 + + total_runtime, tasks = esp32.idf_task_info() + current_thread_id = _thread.get_ident() + tasks.sort(key=lambda t: t[0]) + for ( + task_id, + task_name, + task_state, + task_priority, + task_runtime, + task_stackhighwatermark, + task_coreid, + ) in tasks: + task_runtime_percentage = "-" + if ( + total_runtime != previous_total_runtime + and task_id in previous_task_runtimes + ): + task_runtime_percentage = "{:.2f}%".format( + 100 + * ((task_runtime - previous_task_runtimes[task_id]) & (2**32 - 1)) + / ((total_runtime - previous_total_runtime) & (2**32 - 1)) + ) + print( + "{:>7} {:>4} {:>8d} {:<9} {:<14d} {}{}\x1b[K".format( + task_runtime_percentage, + "-" if task_coreid is None else task_coreid, + task_priority, + esp32_task_state_names.get(task_state, "unknown"), + task_stackhighwatermark, + thread_names.get(task_id, task_name), + " (*)" if task_id == current_thread_id else "", + ) + ) + line_count += 1 + + previous_task_runtimes[task_id] = task_runtime + previous_total_runtime = total_runtime + else: + print("INFO: Platform does not support listing active tasks.\x1b[K") + line_count += 1 + + if previous_line_count > line_count: + for _ in range(previous_line_count - line_count): + print("\x1b[K") + print("\x1b[{}A".format(previous_line_count - line_count), end="") + + previous_line_count = line_count + + try: + time.sleep_ms(update_interval_ms) + except KeyboardInterrupt: + break From 08e09afbe3d16ea97b78ba486153a7ea4a750232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Mon, 12 May 2025 16:24:19 +0200 Subject: [PATCH 20/47] utop: Print MicroPython memory info. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniël van de Giessen --- micropython/utop/utop.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/micropython/utop/utop.py b/micropython/utop/utop.py index d4e94c94a..76fdaade0 100644 --- a/micropython/utop/utop.py +++ b/micropython/utop/utop.py @@ -1,3 +1,4 @@ +import micropython import time try: @@ -72,6 +73,12 @@ def top(update_interval_ms=1000, timeout_ms=None, thread_names={}): print("INFO: Platform does not support listing active tasks.\x1b[K") line_count += 1 + print("\x1b[K") + line_count += 1 + print("MicroPython ", end="") + micropython.mem_info() + line_count += 3 + if previous_line_count > line_count: for _ in range(previous_line_count - line_count): print("\x1b[K") From 54d5f7cee2b95c976589bd4c815c24c358557e3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Thu, 7 Mar 2024 15:01:45 +0100 Subject: [PATCH 21/47] utop: Print IDF heap details. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniël van de Giessen --- micropython/utop/utop.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/micropython/utop/utop.py b/micropython/utop/utop.py index 76fdaade0..799ff4212 100644 --- a/micropython/utop/utop.py +++ b/micropython/utop/utop.py @@ -79,6 +79,23 @@ def top(update_interval_ms=1000, timeout_ms=None, thread_names={}): micropython.mem_info() line_count += 3 + if esp32 is not None: + print("\x1b[K") + line_count += 1 + for name, cap in (("data", esp32.HEAP_DATA), ("exec", esp32.HEAP_EXEC)): + heaps = esp32.idf_heap_info(cap) + print( + "IDF heap ({}): {} regions, {} total, {} free, {} largest contiguous, {} min free watermark\x1b[K".format( + name, + len(heaps), + sum((h[0] for h in heaps)), + sum((h[1] for h in heaps)), + max((h[2] for h in heaps)), + sum((h[3] for h in heaps)), + ) + ) + line_count += 1 + if previous_line_count > line_count: for _ in range(previous_line_count - line_count): print("\x1b[K") From 567540d4e0be3fd4500cc199416bfa602bf7bcec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Wed, 14 May 2025 18:08:22 +0200 Subject: [PATCH 22/47] tools/verifygitlog.py: Sync with changes from the main repo. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This includes the following commits: - Allow long co-author and sign-off names. - Disallow a leading slash in commit subject line. - Apply stricter rules on git subject line. - Show invalid commit subjects in quotes. Signed-off-by: Daniël van de Giessen --- tools/verifygitlog.py | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/tools/verifygitlog.py b/tools/verifygitlog.py index 20be794f8..46fec1e0c 100755 --- a/tools/verifygitlog.py +++ b/tools/verifygitlog.py @@ -49,7 +49,7 @@ def git_log(pretty_format, *args): def diagnose_subject_line(subject_line, subject_line_format, err): - err.error("Subject line: " + subject_line) + err.error('Subject line: "' + subject_line + '"') if not subject_line.endswith("."): err.error('* must end with "."') if not re.match(r"^[^!]+: ", subject_line): @@ -98,20 +98,47 @@ def verify_message_body(raw_body, err): if len(subject_line) >= 73: err.error("Subject line must be 72 or fewer characters: " + subject_line) + # Do additional checks on the prefix of the subject line. + verify_subject_line_prefix(subject_line.split(": ")[0], err) + # Second one divides subject and body. if len(raw_body) > 1 and raw_body[1]: err.error("Second message line must be empty: " + raw_body[1]) # Message body lines. for line in raw_body[2:]: - # Long lines with URLs are exempt from the line length rule. - if len(line) >= 76 and "://" not in line: + # Long lines with URLs or human names are exempt from the line length rule. + if len(line) >= 76 and not ( + "://" in line + or line.startswith("Co-authored-by: ") + or line.startswith("Signed-off-by: ") + ): err.error("Message lines should be 75 or less characters: " + line) if not raw_body[-1].startswith("Signed-off-by: ") or "@" not in raw_body[-1]: err.error('Message must be signed-off. Use "git commit -s".') +def verify_subject_line_prefix(prefix, err): + ext = (".c", ".h", ".cpp", ".js", ".rst", ".md") + + if prefix.startswith((".", "/")): + err.error('Subject prefix cannot begin with "." or "/".') + + if prefix.endswith("/"): + err.error('Subject prefix cannot end with "/".') + + if prefix.startswith("ports/"): + err.error( + 'Subject prefix cannot begin with "ports/", start with the name of the port instead.' + ) + + if prefix.endswith(ext): + err.error( + "Subject prefix cannot end with a file extension, use the main part of the filename without the extension." + ) + + def run(args): verbose("run", *args) From 96bd01ec047923c15bf936d2f77043968745542d Mon Sep 17 00:00:00 2001 From: Adam Lewis Date: Sun, 22 Sep 2024 17:55:35 +0200 Subject: [PATCH 23/47] urllib.urequest: Add support for headers to urequest.urlopen. This is an extension to CPython, similar to the `method` argument. Signed-off-by: Adam Lewis --- micropython/urllib.urequest/manifest.py | 2 +- micropython/urllib.urequest/urllib/urequest.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/micropython/urllib.urequest/manifest.py b/micropython/urllib.urequest/manifest.py index 2790208a7..033022711 100644 --- a/micropython/urllib.urequest/manifest.py +++ b/micropython/urllib.urequest/manifest.py @@ -1,4 +1,4 @@ -metadata(version="0.7.0") +metadata(version="0.8.0") # Originally written by Paul Sokolovsky. diff --git a/micropython/urllib.urequest/urllib/urequest.py b/micropython/urllib.urequest/urllib/urequest.py index f83cbaa94..a9622ea89 100644 --- a/micropython/urllib.urequest/urllib/urequest.py +++ b/micropython/urllib.urequest/urllib/urequest.py @@ -1,7 +1,7 @@ import socket -def urlopen(url, data=None, method="GET"): +def urlopen(url, data=None, method="GET", headers={}): if data is not None and method == "GET": method = "POST" try: @@ -40,6 +40,12 @@ def urlopen(url, data=None, method="GET"): s.write(host) s.write(b"\r\n") + for k in headers: + s.write(k) + s.write(b": ") + s.write(headers[k]) + s.write(b"\r\n") + if data: s.write(b"Content-Length: ") s.write(str(len(data))) From aad2e4809868354155c82de673c93abf0a38a194 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Mon, 5 May 2025 21:03:14 +1000 Subject: [PATCH 24/47] aiorepl: Use blocking reads for raw REPL and raw paste. Raw REPL mode is generally used as a command channel where all stdio traffic is related directly to the raw commands and responses sent. For this to work in aiorepl we need to ensure background tasks don't sent/ receive anything on stdio else the command channel will be corrupted. The simplest way to achieve this is to make the raw commands blocking and atomic rather than asyncio, assuming the user wont leave the device in raw mode for too long at any one time. Signed-off-by: Andrew Leech --- micropython/aiorepl/aiorepl.py | 17 +++++++++++------ micropython/aiorepl/manifest.py | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/micropython/aiorepl/aiorepl.py b/micropython/aiorepl/aiorepl.py index 3f437459d..a640efcd1 100644 --- a/micropython/aiorepl/aiorepl.py +++ b/micropython/aiorepl/aiorepl.py @@ -161,7 +161,7 @@ async def task(g=None, prompt="--> "): cmd = cmd[:-1] sys.stdout.write("\x08 \x08") elif c == CHAR_CTRL_A: - await raw_repl(s, g) + raw_repl(sys.stdin, g) break elif c == CHAR_CTRL_B: continue @@ -239,7 +239,7 @@ async def task(g=None, prompt="--> "): micropython.kbd_intr(3) -async def raw_paste(s, g, window=512): +def raw_paste(s, window=512): sys.stdout.write("R\x01") # supported sys.stdout.write(bytearray([window & 0xFF, window >> 8, 0x01]).decode()) eof = False @@ -248,7 +248,7 @@ async def raw_paste(s, g, window=512): file = b"" while not eof: for idx in range(window): - b = await s.read(1) + b = s.read(1) c = ord(b) if c == CHAR_CTRL_C or c == CHAR_CTRL_D: # end of file @@ -267,7 +267,12 @@ async def raw_paste(s, g, window=512): return file -async def raw_repl(s: asyncio.StreamReader, g: dict): +def raw_repl(s, g: dict): + """ + This function is blocking to prevent other + async tasks from writing to the stdio stream and + breaking the raw repl session. + """ heading = "raw REPL; CTRL-B to exit\n" line = "" sys.stdout.write(heading) @@ -276,7 +281,7 @@ async def raw_repl(s: asyncio.StreamReader, g: dict): line = "" sys.stdout.write(">") while True: - b = await s.read(1) + b = s.read(1) c = ord(b) if c == CHAR_CTRL_A: rline = line @@ -284,7 +289,7 @@ async def raw_repl(s: asyncio.StreamReader, g: dict): if len(rline) == 2 and ord(rline[0]) == CHAR_CTRL_E: if rline[1] == "A": - line = await raw_paste(s, g) + line = raw_paste(s) break else: # reset raw REPL diff --git a/micropython/aiorepl/manifest.py b/micropython/aiorepl/manifest.py index 0fcc21849..ca2fa1513 100644 --- a/micropython/aiorepl/manifest.py +++ b/micropython/aiorepl/manifest.py @@ -1,5 +1,5 @@ metadata( - version="0.2.0", + version="0.2.1", description="Provides an asynchronous REPL that can run concurrently with an asyncio, also allowing await expressions.", ) From 15a6233d040d18bf151c93675c9c4da793d5220e Mon Sep 17 00:00:00 2001 From: Nick Budak Date: Wed, 4 Jun 2025 09:55:47 -0700 Subject: [PATCH 25/47] errno: Add ENOTCONN constant. This is present in core MicroPython so needs to be added here. Signed-off-by: Nick Budak --- python-stdlib/errno/errno.py | 1 + python-stdlib/errno/manifest.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/python-stdlib/errno/errno.py b/python-stdlib/errno/errno.py index 05441b69c..c513a7f14 100644 --- a/python-stdlib/errno/errno.py +++ b/python-stdlib/errno/errno.py @@ -34,5 +34,6 @@ ERANGE = 34 # Math result not representable EAFNOSUPPORT = 97 # Address family not supported by protocol ECONNRESET = 104 # Connection timed out +ENOTCONN = 107 # Not connected ETIMEDOUT = 110 # Connection timed out EINPROGRESS = 115 # Operation now in progress diff --git a/python-stdlib/errno/manifest.py b/python-stdlib/errno/manifest.py index a1e1e6c7c..075d3403d 100644 --- a/python-stdlib/errno/manifest.py +++ b/python-stdlib/errno/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.1.4") +metadata(version="0.2.0") module("errno.py") From 6e24cffe958df397c38e7b3085c16ae5d051cc92 Mon Sep 17 00:00:00 2001 From: Nick Budak Date: Wed, 4 Jun 2025 14:41:33 -0700 Subject: [PATCH 26/47] logging: Allow logging.exception helper to handle tracebacks. Although `Logger.exception` supports passing exception info with `exc_info`, when you use `logging.exception` keyword arguments are not forwarded to the root logger, which makes passing `exc_info` raise `TypeError`. Signed-off-by: Nick Budak --- python-stdlib/logging/logging.py | 4 ++-- python-stdlib/logging/manifest.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python-stdlib/logging/logging.py b/python-stdlib/logging/logging.py index f4874df7d..551bf7152 100644 --- a/python-stdlib/logging/logging.py +++ b/python-stdlib/logging/logging.py @@ -202,8 +202,8 @@ def critical(msg, *args): getLogger().critical(msg, *args) -def exception(msg, *args): - getLogger().exception(msg, *args) +def exception(msg, *args, exc_info=True): + getLogger().exception(msg, *args, exc_info=exc_info) def shutdown(): diff --git a/python-stdlib/logging/manifest.py b/python-stdlib/logging/manifest.py index d9f0ee886..c2614e9ea 100644 --- a/python-stdlib/logging/manifest.py +++ b/python-stdlib/logging/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.6.1") +metadata(version="0.6.2") module("logging.py") From 913498ef05a5b9c28a246d8db64e31f7c12a35e8 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 7 May 2025 21:19:43 +1000 Subject: [PATCH 27/47] aioble-l2cap: Raise correct error if l2cap disconnects during send. If BLE disconnects in the middle of a send process, this change makes the code raise `L2CAPDisconnectedError` rather than `TypeError` due to `_cid` being `None`. Signed-off-by: Andrew Leech --- micropython/bluetooth/aioble-l2cap/manifest.py | 2 +- micropython/bluetooth/aioble/aioble/l2cap.py | 2 +- micropython/bluetooth/aioble/manifest.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/micropython/bluetooth/aioble-l2cap/manifest.py b/micropython/bluetooth/aioble-l2cap/manifest.py index 9150ad547..34a96344e 100644 --- a/micropython/bluetooth/aioble-l2cap/manifest.py +++ b/micropython/bluetooth/aioble-l2cap/manifest.py @@ -1,4 +1,4 @@ -metadata(version="0.2.0") +metadata(version="0.2.1") require("aioble-core") diff --git a/micropython/bluetooth/aioble/aioble/l2cap.py b/micropython/bluetooth/aioble/aioble/l2cap.py index e2d3bd9d4..397b7ceb6 100644 --- a/micropython/bluetooth/aioble/aioble/l2cap.py +++ b/micropython/bluetooth/aioble/aioble/l2cap.py @@ -133,7 +133,6 @@ def available(self): # Waits until the channel is free and then sends buf. # If the buffer is larger than the MTU it will be sent in chunks. async def send(self, buf, timeout_ms=None, chunk_size=None): - self._assert_connected() offset = 0 chunk_size = min(self.our_mtu * 2, self.peer_mtu, chunk_size or self.peer_mtu) mv = memoryview(buf) @@ -141,6 +140,7 @@ async def send(self, buf, timeout_ms=None, chunk_size=None): if self._stalled: await self.flush(timeout_ms) # l2cap_send returns True if you can send immediately. + self._assert_connected() self._stalled = not ble.l2cap_send( self._connection._conn_handle, self._cid, diff --git a/micropython/bluetooth/aioble/manifest.py b/micropython/bluetooth/aioble/manifest.py index 832200570..1cf06c4d8 100644 --- a/micropython/bluetooth/aioble/manifest.py +++ b/micropython/bluetooth/aioble/manifest.py @@ -3,7 +3,7 @@ # code. This allows (for development purposes) all the files to live in the # one directory. -metadata(version="0.6.0") +metadata(version="0.6.1") # Default installation gives you everything. Install the individual # components (or a combination of them) if you want a more minimal install. From f95568da431c6506354adb93343206c04a94dc11 Mon Sep 17 00:00:00 2001 From: Alon Bar-Lev Date: Wed, 30 Apr 2025 16:54:02 +0300 Subject: [PATCH 28/47] abc: Add ABC base class. This trivial addition will allow less code differences between standard Python classes and MicroPython code. Signed-off-by: Alon Bar-Lev --- python-stdlib/abc/abc.py | 4 ++++ python-stdlib/abc/manifest.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/python-stdlib/abc/abc.py b/python-stdlib/abc/abc.py index 941be4f5e..c2c707f62 100644 --- a/python-stdlib/abc/abc.py +++ b/python-stdlib/abc/abc.py @@ -1,2 +1,6 @@ +class ABC: + pass + + def abstractmethod(f): return f diff --git a/python-stdlib/abc/manifest.py b/python-stdlib/abc/manifest.py index 66495fd75..c76312960 100644 --- a/python-stdlib/abc/manifest.py +++ b/python-stdlib/abc/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.0.1") +metadata(version="0.1.0") module("abc.py") From b95ca2491aa405199a429fc24e227a32cb6564f8 Mon Sep 17 00:00:00 2001 From: FuNK3Y Date: Sat, 19 Jul 2025 12:14:17 +0000 Subject: [PATCH 29/47] aiohttp: Fix partial reads by using readexactly. Fixes issue #1012. Signed-off-by: FuNK3Y --- python-ecosys/aiohttp/aiohttp/__init__.py | 10 ++++++---- python-ecosys/aiohttp/aiohttp/aiohttp_ws.py | 10 +++++----- python-ecosys/aiohttp/manifest.py | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/python-ecosys/aiohttp/aiohttp/__init__.py b/python-ecosys/aiohttp/aiohttp/__init__.py index 8c5493f30..1e6d89d05 100644 --- a/python-ecosys/aiohttp/aiohttp/__init__.py +++ b/python-ecosys/aiohttp/aiohttp/__init__.py @@ -42,7 +42,9 @@ def _decode(self, data): return data async def read(self, sz=-1): - return self._decode(await self.content.read(sz)) + return self._decode( + await (self.content.read(sz) if sz == -1 else self.content.readexactly(sz)) + ) async def text(self, encoding="utf-8"): return (await self.read(int(self._get_header("content-length", -1)))).decode(encoding) @@ -66,13 +68,13 @@ async def read(self, sz=4 * 1024 * 1024): self.chunk_size = int(l, 16) if self.chunk_size == 0: # End of message - sep = await self.content.read(2) + sep = await self.content.readexactly(2) assert sep == b"\r\n" return b"" - data = await self.content.read(min(sz, self.chunk_size)) + data = await self.content.readexactly(min(sz, self.chunk_size)) self.chunk_size -= len(data) if self.chunk_size == 0: - sep = await self.content.read(2) + sep = await self.content.readexactly(2) assert sep == b"\r\n" return self._decode(data) diff --git a/python-ecosys/aiohttp/aiohttp/aiohttp_ws.py b/python-ecosys/aiohttp/aiohttp/aiohttp_ws.py index 6e0818c92..53a640fe5 100644 --- a/python-ecosys/aiohttp/aiohttp/aiohttp_ws.py +++ b/python-ecosys/aiohttp/aiohttp/aiohttp_ws.py @@ -189,7 +189,7 @@ async def close(self): await self.send(b"", self.CLOSE) async def _read_frame(self): - header = await self.reader.read(2) + header = await self.reader.readexactly(2) if len(header) != 2: # pragma: no cover # raise OSError(32, "Websocket connection closed") opcode = self.CLOSE @@ -197,13 +197,13 @@ async def _read_frame(self): return opcode, payload fin, opcode, has_mask, length = self._parse_frame_header(header) if length == 126: # Magic number, length header is 2 bytes - (length,) = struct.unpack("!H", await self.reader.read(2)) + (length,) = struct.unpack("!H", await self.reader.readexactly(2)) elif length == 127: # Magic number, length header is 8 bytes - (length,) = struct.unpack("!Q", await self.reader.read(8)) + (length,) = struct.unpack("!Q", await self.reader.readexactly(8)) if has_mask: # pragma: no cover - mask = await self.reader.read(4) - payload = await self.reader.read(length) + mask = await self.reader.readexactly(4) + payload = await self.reader.readexactly(length) if has_mask: # pragma: no cover payload = bytes(x ^ mask[i % 4] for i, x in enumerate(payload)) return opcode, payload diff --git a/python-ecosys/aiohttp/manifest.py b/python-ecosys/aiohttp/manifest.py index d22a6ce11..bbf22bb29 100644 --- a/python-ecosys/aiohttp/manifest.py +++ b/python-ecosys/aiohttp/manifest.py @@ -1,6 +1,6 @@ metadata( description="HTTP client module for MicroPython asyncio module", - version="0.0.5", + version="0.0.6", pypi="aiohttp", ) From 34c4ee1647ac4b177ae40adf0ec514660e433dc0 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Tue, 17 Jun 2025 15:04:39 +1000 Subject: [PATCH 30/47] aiorepl: Handle stream shutdown. Signed-off-by: Andrew Leech --- micropython/aiorepl/aiorepl.py | 2 ++ micropython/aiorepl/manifest.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/micropython/aiorepl/aiorepl.py b/micropython/aiorepl/aiorepl.py index a640efcd1..fbe513b7c 100644 --- a/micropython/aiorepl/aiorepl.py +++ b/micropython/aiorepl/aiorepl.py @@ -114,6 +114,8 @@ async def task(g=None, prompt="--> "): curs = 0 # cursor offset from end of cmd buffer while True: b = await s.read(1) + if not b: # Handle EOF/empty read + break pc = c # save previous character c = ord(b) pt = t # save previous time diff --git a/micropython/aiorepl/manifest.py b/micropython/aiorepl/manifest.py index ca2fa1513..83802e1c0 100644 --- a/micropython/aiorepl/manifest.py +++ b/micropython/aiorepl/manifest.py @@ -1,5 +1,5 @@ metadata( - version="0.2.1", + version="0.2.2", description="Provides an asynchronous REPL that can run concurrently with an asyncio, also allowing await expressions.", ) From ea763cad8d5ee095361da75bed945af5ab858449 Mon Sep 17 00:00:00 2001 From: Hyx Date: Fri, 1 Aug 2025 12:47:43 +0800 Subject: [PATCH 31/47] usb-device: Raise RuntimeError when DCD error occurs. So the behavior matches the comment. Signed-off-by: Hyx --- micropython/usb/usb-device/manifest.py | 2 +- micropython/usb/usb-device/usb/device/core.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/micropython/usb/usb-device/manifest.py b/micropython/usb/usb-device/manifest.py index 025e67547..fa0d8a3f5 100644 --- a/micropython/usb/usb-device/manifest.py +++ b/micropython/usb/usb-device/manifest.py @@ -1,2 +1,2 @@ -metadata(version="0.2.0") +metadata(version="0.2.1") package("usb") diff --git a/micropython/usb/usb-device/usb/device/core.py b/micropython/usb/usb-device/usb/device/core.py index 7be09ee46..b0d91d8ff 100644 --- a/micropython/usb/usb-device/usb/device/core.py +++ b/micropython/usb/usb-device/usb/device/core.py @@ -600,7 +600,8 @@ def submit_xfer(self, ep_addr, data, done_cb=None): # function has returned to the caller. if not self._open: raise RuntimeError("Not open") - _dev._submit_xfer(ep_addr, data, done_cb) + if not _dev._submit_xfer(ep_addr, data, done_cb): + raise RuntimeError("DCD error") def stall(self, ep_addr, *args): # Set or get the endpoint STALL state. From bdc4706cc700ae1c0a4520e252897bb0e03c327b Mon Sep 17 00:00:00 2001 From: Hyx Date: Fri, 1 Aug 2025 12:53:01 +0800 Subject: [PATCH 32/47] usb-device-hid: Return True after submit_xfer. This tells the caller that no error has occurred. The child classes can use this state to do double buffering correctly. If any fundamental error occurs in submit_xfer, the underlying code will raise an exception. Signed-off-by: Hyx --- micropython/usb/usb-device-hid/manifest.py | 2 +- micropython/usb/usb-device-hid/usb/device/hid.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/micropython/usb/usb-device-hid/manifest.py b/micropython/usb/usb-device-hid/manifest.py index af9b8cb84..4520325e3 100644 --- a/micropython/usb/usb-device-hid/manifest.py +++ b/micropython/usb/usb-device-hid/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.1.0") +metadata(version="0.1.1") require("usb-device") package("usb") diff --git a/micropython/usb/usb-device-hid/usb/device/hid.py b/micropython/usb/usb-device-hid/usb/device/hid.py index 9e4c70dde..1c1c9ac6b 100644 --- a/micropython/usb/usb-device-hid/usb/device/hid.py +++ b/micropython/usb/usb-device-hid/usb/device/hid.py @@ -123,6 +123,7 @@ def send_report(self, report_data, timeout_ms=100): if not self.is_open(): return False self.submit_xfer(self._int_ep, report_data) + return True def desc_cfg(self, desc, itf_num, ep_num, strs): # Add the standard interface descriptor From b4565b41eab684722d9fcb2deee322869e47c359 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 19 Sep 2025 13:38:26 +1000 Subject: [PATCH 33/47] inspect: Implement a very basic signature function. This implements a very basic `inspect.signature()` function. At the moment it returns only a simple `Signature` instance with a `parameters` attribute that holds an `OrderedDict` whose length matches the arity of the input function (the number of arguments it takes). So, the following code works and is compatible with CPython: def f(a, b, *, c): pass print(len(inspect.signature(f).parameters)) That should print 3. Signed-off-by: Damien George --- python-stdlib/inspect/inspect.py | 57 +++++++++++++++++++++++++++ python-stdlib/inspect/manifest.py | 2 +- python-stdlib/inspect/test_inspect.py | 13 ++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/python-stdlib/inspect/inspect.py b/python-stdlib/inspect/inspect.py index c16c6b3e3..9074549bb 100644 --- a/python-stdlib/inspect/inspect.py +++ b/python-stdlib/inspect/inspect.py @@ -80,3 +80,60 @@ def currentframe(): def getframeinfo(frame, context=1): return ("", -1, "", [""], 0) + + +class Signature: + pass + + +# This `signature()` function is very limited. It's main purpose is to work out +# the arity of the given function, ie the number of arguments it takes. +# +# The return value is an instance of `Signature` with a `parameters` member which +# is an OrderedDict whose length is the number of arguments of `f`. +def signature(f): + import collections + import uctypes + + s = Signature() + s.parameters = collections.OrderedDict() + + t = type(f) + if t is type(globals): + # A zero-parameter built-in. + num_args = 0 + elif t is type(abs): + # A one-parameter built-in. + num_args = 1 + elif t is type(hasattr): + # A two-parameter built-in. + num_args = 2 + elif t is type(setattr): + # A three-parameter built-in. + num_args = 3 + elif t is type(signature): + # A bytecode function, work out the number of arguments by inspecting the bytecode data. + fun_obj = uctypes.struct(id(f), (uctypes.ARRAY | 0, uctypes.LONG | 4)) + bytecode = uctypes.bytearray_at(fun_obj[3], 8) + # See py/bc.h:MP_BC_PRELUDE_SIG_DECODE_INTO macro. + i = 0 + z = bytecode[i] + i += 1 + A = z & 0x3 + K = 0 + n = 0 + while z & 0x80: + z = bytecode[i] + i += 1 + A |= (z & 0x4) << n + K |= ((z & 0x08) >> 3) << n + num_args = A + K + else: + raise NotImplementedError("unsupported function type") + + # Add dummy arguments to the OrderedDict. + for i in range(num_args): + a = "x{}".format(i) + s.parameters[a] = a + + return s diff --git a/python-stdlib/inspect/manifest.py b/python-stdlib/inspect/manifest.py index e99e659f2..119237c45 100644 --- a/python-stdlib/inspect/manifest.py +++ b/python-stdlib/inspect/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.1.3") +metadata(version="0.2.0") module("inspect.py") diff --git a/python-stdlib/inspect/test_inspect.py b/python-stdlib/inspect/test_inspect.py index 29ed80f11..f5110de70 100644 --- a/python-stdlib/inspect/test_inspect.py +++ b/python-stdlib/inspect/test_inspect.py @@ -1,3 +1,4 @@ +import collections import inspect import unittest @@ -58,3 +59,15 @@ def test_isclass(self): def test_ismodule(self): self._test_is_helper(inspect.ismodule, entities[6]) + + def test_signature(self): + self.assertEqual(inspect.signature(globals).parameters, collections.OrderedDict()) + self.assertEqual(len(inspect.signature(abs).parameters), 1) + self.assertEqual(len(inspect.signature(hasattr).parameters), 2) + self.assertEqual(len(inspect.signature(setattr).parameters), 3) + self.assertEqual(len(inspect.signature(lambda: 0).parameters), 0) + self.assertEqual(len(inspect.signature(lambda x: 0).parameters), 1) + self.assertEqual(len(inspect.signature(lambda *, x: 0).parameters), 1) + self.assertEqual(len(inspect.signature(lambda x, y: 0).parameters), 2) + self.assertEqual(len(inspect.signature(lambda x, y, z: 0).parameters), 3) + self.assertEqual(len(inspect.signature(lambda x, y, *, z: 0).parameters), 3) From 656f42071e8fa819e2b63e75cadba7ba639b32b5 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 25 Sep 2025 00:34:12 +1000 Subject: [PATCH 34/47] datetime: Apply localtz patch to include naive date/time support. This commit applies the existing `localtz.patch` patch to add support for naive datetime objects. That is, objects that don't have any info about the current timezone. This allows `datetime.datetime.now()` to work; prior to this patch it would raise NotImplementedError. Although we don't really have support for localtime vs gmtime on bare-metal, ports such as the unix port and webassembly port do have this distinction, and for them being able to do `datetime.datetime.now()` is quite important (at least, that's what users expect to be able to do). The associated unittest test has been updated. This patch changes the size of datetime.mpy: 8466 -> 8897, so +431 bytes. Signed-off-by: Damien George --- python-stdlib/datetime/datetime.py | 41 +++++++- python-stdlib/datetime/localtz.patch | 84 ----------------- python-stdlib/datetime/manifest.py | 2 +- python-stdlib/datetime/test_datetime.py | 118 ++++++------------------ 4 files changed, 65 insertions(+), 180 deletions(-) delete mode 100644 python-stdlib/datetime/localtz.patch diff --git a/python-stdlib/datetime/datetime.py b/python-stdlib/datetime/datetime.py index 0f2a89105..4005fcd79 100644 --- a/python-stdlib/datetime/datetime.py +++ b/python-stdlib/datetime/datetime.py @@ -633,7 +633,10 @@ def fromtimestamp(cls, ts, tz=None): else: us = 0 if tz is None: - raise NotImplementedError + dt = cls(*_tmod.localtime(ts)[:6], microsecond=us, tzinfo=tz) + s = (dt - datetime(*_tmod.localtime(ts - 86400)[:6]))._us // 1_000_000 - 86400 + if s < 0 and dt == datetime(*_tmod.localtime(ts + s)[:6]): + dt._fd = 1 else: dt = cls(*_tmod.gmtime(ts)[:6], microsecond=us, tzinfo=tz) dt = tz.fromutc(dt) @@ -810,13 +813,45 @@ def astimezone(self, tz=None): return self _tz = self._tz if _tz is None: - raise NotImplementedError + ts = int(self._mktime()) + os = datetime(*_tmod.localtime(ts)[:6]) - datetime(*_tmod.gmtime(ts)[:6]) else: os = _tz.utcoffset(self) utc = self - os utc = utc.replace(tzinfo=tz) return tz.fromutc(utc) + def _mktime(self): + def local(u): + return (datetime(*_tmod.localtime(u)[:6]) - epoch)._us // 1_000_000 + + epoch = datetime.EPOCH.replace(tzinfo=None) + t, us = divmod((self - epoch)._us, 1_000_000) + ts = None + + a = local(t) - t + u1 = t - a + t1 = local(u1) + if t1 == t: + u2 = u1 + (86400 if self.fold else -86400) + b = local(u2) - u2 + if a == b: + ts = u1 + else: + b = t1 - u1 + if ts is None: + u2 = t - b + t2 = local(u2) + if t2 == t: + ts = u2 + elif t1 == t: + ts = u1 + elif self.fold: + ts = min(u1, u2) + else: + ts = max(u1, u2) + return ts + us / 1_000_000 + def utcoffset(self): return None if self._tz is None else self._tz.utcoffset(self) @@ -840,7 +875,7 @@ def toordinal(self): def timestamp(self): if self._tz is None: - raise NotImplementedError + return self._mktime() else: return (self - datetime.EPOCH).total_seconds() diff --git a/python-stdlib/datetime/localtz.patch b/python-stdlib/datetime/localtz.patch deleted file mode 100644 index 7a2449d5d..000000000 --- a/python-stdlib/datetime/localtz.patch +++ /dev/null @@ -1,84 +0,0 @@ -localtz.patch - -The CPython's implementation of `datetime.fromtimestamp()`, -`datetime.astimezone()` and `datetime.timestamp()` for naive datetime objects -relay on proper management of DST (daylight saving time) by `time.localtime()` -for the timezone of interest. In the Unix port of MicroPython, this is -accomplished by properly setting the TZ environment variable, e.g. -`os.putenv("TZ", "Europe/Rome")`. - -Because real boards often lack a supportive `time.localtime()`, the source code -in `datetime.py` has been removed as to save precious resources. If your board -provide a proper implementation, you can restore the support to naive datetime -objects by applying this patch, e.g. `patch -p1 < localtz.patch`. - ---- a/datetime.py -+++ b/datetime.py -@@ -635,7 +635,10 @@ class datetime: - else: - us = 0 - if tz is None: -- raise NotImplementedError -+ dt = cls(*_tmod.localtime(ts)[:6], microsecond=us, tzinfo=tz) -+ s = (dt - datetime(*_tmod.localtime(ts - 86400)[:6]))._us // 1_000_000 - 86400 -+ if s < 0 and dt == datetime(*_tmod.localtime(ts + s)[:6]): -+ dt._fd = 1 - else: - dt = cls(*_tmod.gmtime(ts)[:6], microsecond=us, tzinfo=tz) - dt = tz.fromutc(dt) -@@ -812,13 +815,45 @@ class datetime: - return self - _tz = self._tz - if _tz is None: -- raise NotImplementedError -+ ts = int(self._mktime()) -+ os = datetime(*_tmod.localtime(ts)[:6]) - datetime(*_tmod.gmtime(ts)[:6]) - else: - os = _tz.utcoffset(self) - utc = self - os - utc = utc.replace(tzinfo=tz) - return tz.fromutc(utc) - -+ def _mktime(self): -+ def local(u): -+ return (datetime(*_tmod.localtime(u)[:6]) - epoch)._us // 1_000_000 -+ -+ epoch = datetime.EPOCH.replace(tzinfo=None) -+ t, us = divmod((self - epoch)._us, 1_000_000) -+ ts = None -+ -+ a = local(t) - t -+ u1 = t - a -+ t1 = local(u1) -+ if t1 == t: -+ u2 = u1 + (86400 if self.fold else -86400) -+ b = local(u2) - u2 -+ if a == b: -+ ts = u1 -+ else: -+ b = t1 - u1 -+ if ts is None: -+ u2 = t - b -+ t2 = local(u2) -+ if t2 == t: -+ ts = u2 -+ elif t1 == t: -+ ts = u1 -+ elif self.fold: -+ ts = min(u1, u2) -+ else: -+ ts = max(u1, u2) -+ return ts + us / 1_000_000 -+ - def utcoffset(self): - return None if self._tz is None else self._tz.utcoffset(self) - -@@ -842,7 +877,7 @@ class datetime: - - def timestamp(self): - if self._tz is None: -- raise NotImplementedError -+ return self._mktime() - else: - return (self - datetime.EPOCH).total_seconds() - diff --git a/python-stdlib/datetime/manifest.py b/python-stdlib/datetime/manifest.py index 017189cec..d4adce9cd 100644 --- a/python-stdlib/datetime/manifest.py +++ b/python-stdlib/datetime/manifest.py @@ -1,4 +1,4 @@ -metadata(version="4.0.0") +metadata(version="4.1.0") # Originally written by Lorenzo Cappelletti. diff --git a/python-stdlib/datetime/test_datetime.py b/python-stdlib/datetime/test_datetime.py index 98da458f9..56411b96e 100644 --- a/python-stdlib/datetime/test_datetime.py +++ b/python-stdlib/datetime/test_datetime.py @@ -68,14 +68,6 @@ import unittest -# See localtz.patch -try: - datetime.fromtimestamp(0) - LOCALTZ = True -except NotImplementedError: - LOCALTZ = False - - if hasattr(datetime, "EPOCH"): EPOCH = datetime.EPOCH else: @@ -1619,11 +1611,8 @@ def test___init__24(self): def test_fromtimestamp00(self): with LocalTz("Europe/Rome"): ts = 1012499103.001234 - if LOCALTZ: - dt = datetime.fromtimestamp(ts) - self.assertEqual(dt, d1t1) - else: - self.assertRaises(NotImplementedError, datetime.fromtimestamp, ts) + dt = datetime.fromtimestamp(ts) + self.assertEqual(dt, d1t1) def test_fromtimestamp01(self): ts = 1012506303.001234 @@ -1642,48 +1631,35 @@ def test_fromtimestamp04(self): dt = datetime(2010, 10, 31, 0, 30, tzinfo=timezone.utc) ts = (dt - EPOCH).total_seconds() dt = dt.replace(tzinfo=None) + 2 * td1h - if LOCALTZ: - ds = datetime.fromtimestamp(ts) - self.assertEqual(ds, dt) - self.assertFalse(ds.fold) - else: - self.assertRaises(NotImplementedError, datetime.fromtimestamp, ts) + ds = datetime.fromtimestamp(ts) + self.assertEqual(ds, dt) + self.assertFalse(ds.fold) def test_fromtimestamp05(self): with LocalTz("Europe/Rome"): dt = datetime(2010, 10, 31, 1, 30, tzinfo=timezone.utc) ts = (dt - EPOCH).total_seconds() dt = dt.replace(tzinfo=None) + 1 * td1h - if LOCALTZ: - ds = datetime.fromtimestamp(ts) - self.assertEqual(ds, dt) - self.assertTrue(ds.fold) - else: - self.assertRaises(NotImplementedError, datetime.fromtimestamp, ts) + ds = datetime.fromtimestamp(ts) + self.assertEqual(ds, dt) + self.assertTrue(ds.fold) def test_fromtimestamp06(self): with LocalTz("US/Eastern"): dt = datetime(2020, 11, 1, 5, 30, tzinfo=timezone.utc) ts = (dt - EPOCH).total_seconds() dt = dt.replace(tzinfo=None) - 4 * td1h - if LOCALTZ: - ds = datetime.fromtimestamp(ts) - self.assertEqual(ds, dt) - else: - self.assertRaises(NotImplementedError, datetime.fromtimestamp, ts) + ds = datetime.fromtimestamp(ts) + self.assertEqual(ds, dt) def test_fromtimestamp07(self): with LocalTz("US/Eastern"): dt = datetime(2020, 11, 1, 7, 30, tzinfo=timezone.utc) ts = (dt - EPOCH).total_seconds() dt = dt.replace(tzinfo=None) - 5 * td1h - if LOCALTZ: - ds = datetime.fromtimestamp(ts) - self.assertEqual(ds, dt) - else: - self.assertRaises(NotImplementedError, datetime.fromtimestamp, ts) + ds = datetime.fromtimestamp(ts) + self.assertEqual(ds, dt) - @unittest.skipIf(not LOCALTZ, "naive datetime not supported") def test_now00(self): tm = datetime(*mod_time.localtime()[:6]) dt = datetime.now() @@ -2004,46 +1980,31 @@ def test_astimezone04(self): with LocalTz("Europe/Rome"): dt1 = dt27tz2 dt2 = dt1.replace(tzinfo=None) - if LOCALTZ: - self.assertEqual(dt1, dt2.astimezone(tz2)) - else: - self.assertRaises(NotImplementedError, dt2.astimezone, tz2) + self.assertEqual(dt1, dt2.astimezone(tz2)) def test_astimezone05(self): with LocalTz("Europe/Rome"): dt1 = dt28tz2 dt2 = dt1.replace(tzinfo=None) - if LOCALTZ: - self.assertEqual(dt1, dt2.astimezone(tz2)) - else: - self.assertRaises(NotImplementedError, dt2.astimezone, tz2) + self.assertEqual(dt1, dt2.astimezone(tz2)) def test_astimezone06(self): with LocalTz("Europe/Rome"): dt1 = dt30tz2 dt2 = dt1.replace(tzinfo=None) - if LOCALTZ: - self.assertEqual(dt1, dt2.astimezone(tz2)) - else: - self.assertRaises(NotImplementedError, dt2.astimezone, tz2) + self.assertEqual(dt1, dt2.astimezone(tz2)) def test_astimezone07(self): with LocalTz("Europe/Rome"): dt1 = dt31tz2 dt2 = dt1.replace(tzinfo=None) - if LOCALTZ: - self.assertEqual(dt1, dt2.astimezone(tz2)) - else: - self.assertRaises(NotImplementedError, dt2.astimezone, tz2) + self.assertEqual(dt1, dt2.astimezone(tz2)) def test_astimezone08(self): with LocalTz("Europe/Rome"): dt1 = dt3 dt2 = dt1.replace(tzinfo=None) - if LOCALTZ: - self.assertEqual(dt1, dt2.astimezone(tz2)) - else: - self.assertRaises(NotImplementedError, dt2.astimezone, tz2) + self.assertEqual(dt1, dt2.astimezone(tz2)) def test_utcoffset00(self): self.assertEqual(dt1.utcoffset(), None) @@ -2123,10 +2084,7 @@ def test_weekday00(self): def test_timestamp00(self): with LocalTz("Europe/Rome"): - if LOCALTZ: - self.assertEqual(d1t1.timestamp(), 1012499103.001234) - else: - self.assertRaises(NotImplementedError, d1t1.timestamp) + self.assertEqual(d1t1.timestamp(), 1012499103.001234) def test_timestamp01(self): self.assertEqual(d1t1z.timestamp(), 1012506303.001234) @@ -2134,66 +2092,42 @@ def test_timestamp01(self): def test_timestamp02(self): with LocalTz("Europe/Rome"): dt = datetime(2010, 3, 28, 2, 30) # doens't exist - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1269739800.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1269739800.0) def test_timestamp03(self): with LocalTz("Europe/Rome"): dt = datetime(2010, 8, 10, 2, 30) - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1281400200.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1281400200.0) def test_timestamp04(self): with LocalTz("Europe/Rome"): dt = datetime(2010, 10, 31, 2, 30, fold=0) - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1288485000.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1288485000.0) def test_timestamp05(self): with LocalTz("Europe/Rome"): dt = datetime(2010, 10, 31, 2, 30, fold=1) - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1288488600.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1288488600.0) def test_timestamp06(self): with LocalTz("US/Eastern"): dt = datetime(2020, 3, 8, 2, 30) # doens't exist - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1583652600.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1583652600.0) def test_timestamp07(self): with LocalTz("US/Eastern"): dt = datetime(2020, 8, 10, 2, 30) - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1597041000.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1597041000.0) def test_timestamp08(self): with LocalTz("US/Eastern"): dt = datetime(2020, 11, 1, 2, 30, fold=0) - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1604215800.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1604215800.0) def test_timestamp09(self): with LocalTz("US/Eastern"): dt = datetime(2020, 11, 1, 2, 30, fold=1) - if LOCALTZ: - self.assertEqual(dt.timestamp(), 1604215800.0) - else: - self.assertRaises(NotImplementedError, dt.timestamp) + self.assertEqual(dt.timestamp(), 1604215800.0) def test_isoweekday00(self): self.assertEqual(dt1.isoweekday(), d1.isoweekday()) From 3eaf0279f350855f0ee3a8a65774a49afdbf4e3d Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 25 Sep 2025 00:50:31 +1000 Subject: [PATCH 35/47] datetime: Optimize for code size. Optimizations applied here are: - writing once-used helper functions inline in their place of use - writing once-used constant tuples inline in their place of use (I would have used `from micropython import const` but that renders the code not runnable under CPython for testing, and also increases code size itself for the import) - renamed _tmod to _t - renamed _format to _fmt - optimised timedelta._tuple() slightly Reduces datetime.mpy by: 8897 -> 8728, so saves 169 bytes. Signed-off-by: Damien George --- python-stdlib/datetime/datetime.py | 71 +++++++++++++----------------- 1 file changed, 31 insertions(+), 40 deletions(-) diff --git a/python-stdlib/datetime/datetime.py b/python-stdlib/datetime/datetime.py index 4005fcd79..b18edf1c2 100644 --- a/python-stdlib/datetime/datetime.py +++ b/python-stdlib/datetime/datetime.py @@ -1,37 +1,22 @@ # datetime.py -import time as _tmod - -_DBM = (0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334) -_DIM = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) -_TIME_SPEC = ("auto", "hours", "minutes", "seconds", "milliseconds", "microseconds") +import time as _t def _leap(y): return y % 4 == 0 and (y % 100 != 0 or y % 400 == 0) -def _dby(y): - # year -> number of days before January 1st of year. - Y = y - 1 - return Y * 365 + Y // 4 - Y // 100 + Y // 400 - - def _dim(y, m): # year, month -> number of days in that month in that year. if m == 2 and _leap(y): return 29 - return _DIM[m] + return (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)[m] def _dbm(y, m): # year, month -> number of days in year preceding first day of month. - return _DBM[m] + (m > 2 and _leap(y)) - - -def _ymd2o(y, m, d): - # y, month, day -> ordinal, considering 01-Jan-0001 as day 1. - return _dby(y) + _dbm(y, m) + d + return (0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334)[m] + (m > 2 and _leap(y)) def _o2ymd(n): @@ -73,7 +58,7 @@ def total_seconds(self): @property def days(self): - return self._tuple(2)[0] + return self._tuple(1) @property def seconds(self): @@ -145,7 +130,7 @@ def __bool__(self): return self._us != 0 def __str__(self): - return self._format(0x40) + return self._fmt(0x40) def __hash__(self): if not hasattr(self, "_hash"): @@ -153,9 +138,9 @@ def __hash__(self): return self._hash def isoformat(self): - return self._format(0) + return self._fmt(0) - def _format(self, spec=0): + def _fmt(self, spec=0): if self._us >= 0: td = self g = "" @@ -201,8 +186,8 @@ def tuple(self): def _tuple(self, n): d, us = divmod(self._us, 86_400_000_000) - if n == 2: - return d, us + if n == 1: + return d s, us = divmod(us, 1_000_000) if n == 3: return d, s, us @@ -241,7 +226,7 @@ def fromutc(self, dt): return dt + dtdst def isoformat(self, dt): - return self.utcoffset(dt)._format(0x12) + return self.utcoffset(dt)._fmt(0x12) class timezone(tzinfo): @@ -276,7 +261,7 @@ def dst(self, dt): def tzname(self, dt): if self._name: return self._name - return self._offset._format(0x22) + return self._offset._fmt(0x22) def fromutc(self, dt): return dt + self._offset @@ -287,7 +272,11 @@ def fromutc(self, dt): def _date(y, m, d): if MINYEAR <= y <= MAXYEAR and 1 <= m <= 12 and 1 <= d <= _dim(y, m): - return _ymd2o(y, m, d) + # year -> number of days before January 1st of year. + Y = y - 1 + _dby = Y * 365 + Y // 4 - Y // 100 + Y // 400 + # y, month, day -> ordinal, considering 01-Jan-0001 as day 1. + return _dby + _dbm(y, m) + d elif y == 0 and m == 0 and 1 <= d <= 3_652_059: return d else: @@ -310,11 +299,11 @@ def __init__(self, year, month, day): @classmethod def fromtimestamp(cls, ts): - return cls(*_tmod.localtime(ts)[:3]) + return cls(*_t.localtime(ts)[:3]) @classmethod def today(cls): - return cls(*_tmod.localtime()[:3]) + return cls(*_t.localtime()[:3]) @classmethod def fromordinal(cls, n): @@ -490,7 +479,9 @@ def _iso2t(s): def _t2iso(td, timespec, dt, tz): - s = td._format(_TIME_SPEC.index(timespec)) + s = td._fmt( + ("auto", "hours", "minutes", "seconds", "milliseconds", "microseconds").index(timespec) + ) if tz is not None: s += tz.isoformat(dt) return s @@ -633,18 +624,18 @@ def fromtimestamp(cls, ts, tz=None): else: us = 0 if tz is None: - dt = cls(*_tmod.localtime(ts)[:6], microsecond=us, tzinfo=tz) - s = (dt - datetime(*_tmod.localtime(ts - 86400)[:6]))._us // 1_000_000 - 86400 - if s < 0 and dt == datetime(*_tmod.localtime(ts + s)[:6]): + dt = cls(*_t.localtime(ts)[:6], microsecond=us, tzinfo=tz) + s = (dt - datetime(*_t.localtime(ts - 86400)[:6]))._us // 1_000_000 - 86400 + if s < 0 and dt == datetime(*_t.localtime(ts + s)[:6]): dt._fd = 1 else: - dt = cls(*_tmod.gmtime(ts)[:6], microsecond=us, tzinfo=tz) + dt = cls(*_t.gmtime(ts)[:6], microsecond=us, tzinfo=tz) dt = tz.fromutc(dt) return dt @classmethod def now(cls, tz=None): - return cls.fromtimestamp(_tmod.time(), tz) + return cls.fromtimestamp(_t.time(), tz) @classmethod def fromordinal(cls, n): @@ -814,7 +805,7 @@ def astimezone(self, tz=None): _tz = self._tz if _tz is None: ts = int(self._mktime()) - os = datetime(*_tmod.localtime(ts)[:6]) - datetime(*_tmod.gmtime(ts)[:6]) + os = datetime(*_t.localtime(ts)[:6]) - datetime(*_t.gmtime(ts)[:6]) else: os = _tz.utcoffset(self) utc = self - os @@ -823,7 +814,7 @@ def astimezone(self, tz=None): def _mktime(self): def local(u): - return (datetime(*_tmod.localtime(u)[:6]) - epoch)._us // 1_000_000 + return (datetime(*_t.localtime(u)[:6]) - epoch)._us // 1_000_000 epoch = datetime.EPOCH.replace(tzinfo=None) t, us = divmod((self - epoch)._us, 1_000_000) @@ -863,10 +854,10 @@ def tzname(self): def timetuple(self): if self._tz is None: - conv = _tmod.gmtime + conv = _t.gmtime epoch = datetime.EPOCH.replace(tzinfo=None) else: - conv = _tmod.localtime + conv = _t.localtime epoch = datetime.EPOCH return conv(round((self - epoch).total_seconds())) @@ -909,4 +900,4 @@ def tuple(self): return d + t + (self._tz, self._fd) -datetime.EPOCH = datetime(*_tmod.gmtime(0)[:6], tzinfo=timezone.utc) +datetime.EPOCH = datetime(*_t.gmtime(0)[:6], tzinfo=timezone.utc) From 200e8d13c9e186ff5d1d54cc08ec5078309f43cd Mon Sep 17 00:00:00 2001 From: Ben Wynn Date: Wed, 29 Oct 2025 17:07:03 -0400 Subject: [PATCH 36/47] sdcard: Updating sector calculation for SDXC. The current code only calculates sectors for SDHC, with a max of 32G The new code will calculate sectors for SDXC, with a max of 2T C_SIZE is 22 bits[69:48] and the high 2 bits of csd[7] are required to be 0, currently, so i'm not masking them off. The max value for C_SIZE is 0x3FFEFF, so C_SIZE+1*1024 is a 32-bit number. bumped version in manifest.py Signed-off-by: Ben Wynn --- micropython/drivers/storage/sdcard/manifest.py | 2 +- micropython/drivers/storage/sdcard/sdcard.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/micropython/drivers/storage/sdcard/manifest.py b/micropython/drivers/storage/sdcard/manifest.py index cb4647eeb..ea30a7a39 100644 --- a/micropython/drivers/storage/sdcard/manifest.py +++ b/micropython/drivers/storage/sdcard/manifest.py @@ -1,3 +1,3 @@ -metadata(description="SDCard block device driver.", version="0.1.0") +metadata(description="SDCard block device driver.", version="0.1.1") module("sdcard.py", opt=3) diff --git a/micropython/drivers/storage/sdcard/sdcard.py b/micropython/drivers/storage/sdcard/sdcard.py index c9c991f59..3df4788a2 100644 --- a/micropython/drivers/storage/sdcard/sdcard.py +++ b/micropython/drivers/storage/sdcard/sdcard.py @@ -97,7 +97,7 @@ def init_card(self, baudrate): csd = bytearray(16) self.readinto(csd) if csd[0] & 0xC0 == 0x40: # CSD version 2.0 - self.sectors = ((csd[8] << 8 | csd[9]) + 1) * 1024 + self.sectors = ((csd[7] << 16 | csd[8] << 8 | csd[9]) + 1) * 1024 elif csd[0] & 0xC0 == 0x00: # CSD version 1.0 (old, <=2GB) c_size = (csd[6] & 0b11) << 10 | csd[7] << 2 | csd[8] >> 6 c_size_mult = (csd[9] & 0b11) << 1 | csd[10] >> 7 From a7c805cc3750a2b37d35a50c36acd98b19ec98e2 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 13 Nov 2025 02:42:14 +0100 Subject: [PATCH 37/47] mip: Optimize _CHUNK_SIZE const for code size. 17 bytes reduced. Signed-off-by: Jos Verlinde --- micropython/mip/manifest.py | 2 +- micropython/mip/mip/__init__.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/micropython/mip/manifest.py b/micropython/mip/manifest.py index 9fb94ebcb..a1b340670 100644 --- a/micropython/mip/manifest.py +++ b/micropython/mip/manifest.py @@ -1,4 +1,4 @@ -metadata(version="0.4.1", description="On-device package installer for network-capable boards") +metadata(version="0.4.2", description="On-device package installer for network-capable boards") require("requests") diff --git a/micropython/mip/mip/__init__.py b/micropython/mip/mip/__init__.py index 7c0fb4d3a..ab48393d6 100644 --- a/micropython/mip/mip/__init__.py +++ b/micropython/mip/mip/__init__.py @@ -1,13 +1,14 @@ # MicroPython package installer # MIT license; Copyright (c) 2022 Jim Mussared -from micropython import const -import requests import sys +import requests + +from micropython import const _PACKAGE_INDEX = const("https://micropython.org/pi/v2") -_CHUNK_SIZE = 128 +_CHUNK_SIZE = const(128) allowed_mip_url_prefixes = ("http://", "https://", "github:", "gitlab:") From 28136d837ab936324d46f2cbe50e686475f5c38c Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 13 Nov 2025 01:20:18 +0100 Subject: [PATCH 38/47] aioble/examples: Change variable name to _ADV_INTERVAL_US. Fixes issue #1055. Signed-off-by: Jos Verlinde --- micropython/bluetooth/aioble/examples/temp_sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micropython/bluetooth/aioble/examples/temp_sensor.py b/micropython/bluetooth/aioble/examples/temp_sensor.py index 54580f595..9a4ec26de 100644 --- a/micropython/bluetooth/aioble/examples/temp_sensor.py +++ b/micropython/bluetooth/aioble/examples/temp_sensor.py @@ -20,7 +20,7 @@ _ADV_APPEARANCE_GENERIC_THERMOMETER = const(768) # How frequently to send advertising beacons. -_ADV_INTERVAL_MS = 250_000 +_ADV_INTERVAL_US = 250_000 # Register GATT server. @@ -50,7 +50,7 @@ async def sensor_task(): async def peripheral_task(): while True: async with await aioble.advertise( - _ADV_INTERVAL_MS, + _ADV_INTERVAL_US, name="mpy-temp", services=[_ENV_SENSE_UUID], appearance=_ADV_APPEARANCE_GENERIC_THERMOMETER, From 7b01b50bfbc9342b65004abf187190e144ca854f Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 13 Nov 2025 01:45:23 +0100 Subject: [PATCH 39/47] aioble: Fix typo in README in aioble.ADDR_PUBLIC. Fixes issue #699. Signed-off-by: Jos Verlinde --- micropython/bluetooth/aioble/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropython/bluetooth/aioble/README.md b/micropython/bluetooth/aioble/README.md index 83ae00209..559594850 100644 --- a/micropython/bluetooth/aioble/README.md +++ b/micropython/bluetooth/aioble/README.md @@ -93,7 +93,7 @@ async with aioble.scan(duration_ms=5000, interval_us=30000, window_us=30000, act # Either from scan result device = result.device # Or with known address -device = aioble.Device(aioble.PUBLIC, "aa:bb:cc:dd:ee:ff") +device = aioble.Device(aioble.ADDR_PUBLIC, "aa:bb:cc:dd:ee:ff") try: connection = await device.connect(timeout_ms=2000) From 0b78a15fb90d1bbd7637d6500cbbd9b2988a97fe Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 13 Nov 2025 02:04:34 +0100 Subject: [PATCH 40/47] copy: Fix typo in _deepcopy_dispatch. Fixes issue #952. Signed-off-by: Jos Verlinde --- python-stdlib/copy/copy.py | 2 +- python-stdlib/copy/manifest.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python-stdlib/copy/copy.py b/python-stdlib/copy/copy.py index 0a9283777..9d56d6cbd 100644 --- a/python-stdlib/copy/copy.py +++ b/python-stdlib/copy/copy.py @@ -191,7 +191,7 @@ def deepcopy(x, memo=None, _nil=[]): if copier: y = copier(memo) else: - reductor = dispatch_table.get(cls) + reductor = _deepcopy_dispatch.get(cls) if reductor: rv = reductor(x) else: diff --git a/python-stdlib/copy/manifest.py b/python-stdlib/copy/manifest.py index b22ebeb90..f0849295f 100644 --- a/python-stdlib/copy/manifest.py +++ b/python-stdlib/copy/manifest.py @@ -1,4 +1,4 @@ -metadata(version="3.3.4") +metadata(version="3.3.5") require("types") From 886f136662232e2532bb089ce6f85be8d71d7529 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 13 Nov 2025 02:33:58 +0100 Subject: [PATCH 41/47] requests: Update example for fetching using requests. This is a working alternative to PR #919. Signed-off-by: Jos Verlinde --- python-ecosys/requests/{example_xively.py => example_quote.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename python-ecosys/requests/{example_xively.py => example_quote.py} (85%) diff --git a/python-ecosys/requests/example_xively.py b/python-ecosys/requests/example_quote.py similarity index 85% rename from python-ecosys/requests/example_xively.py rename to python-ecosys/requests/example_quote.py index 60e139b98..cfbe8ac0e 100644 --- a/python-ecosys/requests/example_xively.py +++ b/python-ecosys/requests/example_quote.py @@ -1,6 +1,6 @@ import requests -r = requests.get("http://api.xively.com/") +r = requests.get("https://dummyjson.com/quotes/1") print(r) print(r.content) print(r.text) From 2992784136be0f4281b5941fe6ae92a46a171b5a Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Mon, 17 Nov 2025 13:25:59 +0100 Subject: [PATCH 42/47] all: Fix formatting errors in docstrings. No changes to versions as only comments and docstrings changed Signed-off-by: Jos Verlinde --- micropython/drivers/imu/bmi270/bmi270.py | 38 ++++++++++--------- micropython/drivers/imu/bmm150/bmm150.py | 24 ++++++------ micropython/drivers/imu/lsm6dsox/lsm6dsox.py | 27 ++++++------- micropython/drivers/imu/lsm9ds1/lsm9ds1.py | 24 ++++++------ micropython/drivers/sensor/ds18x20/ds18x20.py | 7 +++- micropython/drivers/sensor/hs3003/hs3003.py | 22 ++++++----- micropython/drivers/sensor/hts221/hts221.py | 22 ++++++----- python-stdlib/cmd/cmd.py | 29 +++++++------- python-stdlib/contextlib/contextlib.py | 8 ++-- python-stdlib/heapq/heapq.py | 25 ++++++------ python-stdlib/textwrap/textwrap.py | 6 ++- 11 files changed, 123 insertions(+), 109 deletions(-) diff --git a/micropython/drivers/imu/bmi270/bmi270.py b/micropython/drivers/imu/bmi270/bmi270.py index 64f819ec2..c76962f7e 100644 --- a/micropython/drivers/imu/bmi270/bmi270.py +++ b/micropython/drivers/imu/bmi270/bmi270.py @@ -21,29 +21,31 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Basic example usage: +Basic example usage:: + + import time + from bmi270 import BMI270 + from machine import Pin, SPI, I2C + + # Init in I2C mode. + imu = BMI270(I2C(1, scl=Pin(15), sda=Pin(14))) + + # Or init in SPI mode. + # TODO: Not supported yet. + # imu = BMI270(SPI(5), cs=Pin(10)) + + while (True): + print('Accelerometer: x:{:>6.3f} y:{:>6.3f} z:{:>6.3f}'.format(*imu.accel())) + print('Gyroscope: x:{:>6.3f} y:{:>6.3f} z:{:>6.3f}'.format(*imu.gyro())) + print('Magnetometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*imu.magnet())) + print("") + time.sleep_ms(100) -import time -from bmi270 import BMI270 -from machine import Pin, SPI, I2C - -# Init in I2C mode. -imu = BMI270(I2C(1, scl=Pin(15), sda=Pin(14))) - -# Or init in SPI mode. -# TODO: Not supported yet. -# imu = BMI270(SPI(5), cs=Pin(10)) - -while (True): - print('Accelerometer: x:{:>6.3f} y:{:>6.3f} z:{:>6.3f}'.format(*imu.accel())) - print('Gyroscope: x:{:>6.3f} y:{:>6.3f} z:{:>6.3f}'.format(*imu.gyro())) - print('Magnetometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*imu.magnet())) - print("") - time.sleep_ms(100) """ import array import time + from micropython import const _DEFAULT_ADDR = const(0x68) diff --git a/micropython/drivers/imu/bmm150/bmm150.py b/micropython/drivers/imu/bmm150/bmm150.py index a4845c961..aea4348b5 100644 --- a/micropython/drivers/imu/bmm150/bmm150.py +++ b/micropython/drivers/imu/bmm150/bmm150.py @@ -21,22 +21,22 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Basic example usage: +Basic example usage:: -import time -from bmm150 import BMM150 -from machine import Pin, SPI, I2C + import time + from bmm150 import BMM150 + from machine import Pin, SPI, I2C -# Init in I2C mode. -imu = BMM150(I2C(1, scl=Pin(15), sda=Pin(14))) + # Init in I2C mode. + imu = BMM150(I2C(1, scl=Pin(15), sda=Pin(14))) -# Or init in SPI mode. -# TODO: Not supported yet. -# imu = BMM150(SPI(5), cs=Pin(10)) + # Or init in SPI mode. + # TODO: Not supported yet. + # imu = BMM150(SPI(5), cs=Pin(10)) -while (True): - print('magnetometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*imu.magnet())) - time.sleep_ms(100) + while (True): + print('magnetometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*imu.magnet())) + time.sleep_ms(100) """ import array diff --git a/micropython/drivers/imu/lsm6dsox/lsm6dsox.py b/micropython/drivers/imu/lsm6dsox/lsm6dsox.py index ca1397c66..b932ff006 100644 --- a/micropython/drivers/imu/lsm6dsox/lsm6dsox.py +++ b/micropython/drivers/imu/lsm6dsox/lsm6dsox.py @@ -25,23 +25,24 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Basic example usage: +Basic example usage:: -import time -from lsm6dsox import LSM6DSOX + import time + from lsm6dsox import LSM6DSOX + + from machine import Pin, SPI, I2C + # Init in I2C mode. + lsm = LSM6DSOX(I2C(0, scl=Pin(13), sda=Pin(12))) -from machine import Pin, SPI, I2C -# Init in I2C mode. -lsm = LSM6DSOX(I2C(0, scl=Pin(13), sda=Pin(12))) + # Or init in SPI mode. + #lsm = LSM6DSOX(SPI(5), cs=Pin(10)) -# Or init in SPI mode. -#lsm = LSM6DSOX(SPI(5), cs=Pin(10)) + while (True): + print('Accelerometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*lsm.accel())) + print('Gyroscope: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*lsm.gyro())) + print("") + time.sleep_ms(100) -while (True): - print('Accelerometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*lsm.accel())) - print('Gyroscope: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*lsm.gyro())) - print("") - time.sleep_ms(100) """ import array diff --git a/micropython/drivers/imu/lsm9ds1/lsm9ds1.py b/micropython/drivers/imu/lsm9ds1/lsm9ds1.py index e5a96ad5c..a45e73039 100644 --- a/micropython/drivers/imu/lsm9ds1/lsm9ds1.py +++ b/micropython/drivers/imu/lsm9ds1/lsm9ds1.py @@ -27,21 +27,21 @@ The sensor contains an accelerometer / gyroscope / magnetometer Uses the internal FIFO to store up to 16 gyro/accel data, use the iter_accel_gyro generator to access it. -Example usage: +Example usage:: -import time -from lsm9ds1 import LSM9DS1 -from machine import Pin, I2C + import time + from lsm9ds1 import LSM9DS1 + from machine import Pin, I2C -imu = LSM9DS1(I2C(1, scl=Pin(15), sda=Pin(14))) + imu = LSM9DS1(I2C(1, scl=Pin(15), sda=Pin(14))) -while (True): - #for g,a in imu.iter_accel_gyro(): print(g,a) # using fifo - print('Accelerometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*imu.accel())) - print('Magnetometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*imu.magnet())) - print('Gyroscope: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*imu.gyro())) - print("") - time.sleep_ms(100) + while (True): + #for g,a in imu.iter_accel_gyro(): print(g,a) # using fifo + print('Accelerometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*imu.accel())) + print('Magnetometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*imu.magnet())) + print('Gyroscope: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*imu.gyro())) + print("") + time.sleep_ms(100) """ import array diff --git a/micropython/drivers/sensor/ds18x20/ds18x20.py b/micropython/drivers/sensor/ds18x20/ds18x20.py index ad2d9f52c..066a05fb9 100644 --- a/micropython/drivers/sensor/ds18x20/ds18x20.py +++ b/micropython/drivers/sensor/ds18x20/ds18x20.py @@ -1,5 +1,8 @@ -# DS18x20 temperature sensor driver for MicroPython. -# MIT license; Copyright (c) 2016 Damien P. George +""" +DS18x20 temperature sensor driver for MicroPython. + +MIT license; Copyright (c) 2016 Damien P. George +""" from micropython import const diff --git a/micropython/drivers/sensor/hs3003/hs3003.py b/micropython/drivers/sensor/hs3003/hs3003.py index 003501649..332df8de7 100644 --- a/micropython/drivers/sensor/hs3003/hs3003.py +++ b/micropython/drivers/sensor/hs3003/hs3003.py @@ -22,21 +22,23 @@ THE SOFTWARE. HS3003 driver for MicroPython. +------------------------------ Example usage: -import time -from hs3003 import HS3003 -from machine import Pin, I2C + import time + from hs3003 import HS3003 + from machine import Pin, I2C -bus = I2C(1, scl=Pin(15), sda=Pin(14)) -hts = HS3003(bus) + bus = I2C(1, scl=Pin(15), sda=Pin(14)) + hts = HS3003(bus) + + while True: + rH = hts.humidity() + temp = hts.temperature() + print ("rH: %.2f%% T: %.2fC" %(rH, temp)) + time.sleep_ms(100) -while True: - rH = hts.humidity() - temp = hts.temperature() - print ("rH: %.2f%% T: %.2fC" %(rH, temp)) - time.sleep_ms(100) """ import struct diff --git a/micropython/drivers/sensor/hts221/hts221.py b/micropython/drivers/sensor/hts221/hts221.py index c6cd51f48..5be268d40 100644 --- a/micropython/drivers/sensor/hts221/hts221.py +++ b/micropython/drivers/sensor/hts221/hts221.py @@ -23,22 +23,24 @@ THE SOFTWARE. HTS221 driver driver for MicroPython. +------------------------------------- + Original source: https://github.com/ControlEverythingCommunity/HTS221/blob/master/Python/HTS221.py Example usage: -import time -import hts221 -from machine import Pin, I2C + import time + import hts221 + from machine import Pin, I2C -bus = I2C(1, scl=Pin(15), sda=Pin(14)) -hts = hts221.HTS221(bus) + bus = I2C(1, scl=Pin(15), sda=Pin(14)) + hts = hts221.HTS221(bus) -while (True): - rH = hts.humidity() - temp = hts.temperature() - print ("rH: %.2f%% T: %.2fC" %(rH, temp)) - time.sleep_ms(100) + while (True): + rH = hts.humidity() + temp = hts.temperature() + print ("rH: %.2f%% T: %.2fC" %(rH, temp)) + time.sleep_ms(100) """ import struct diff --git a/python-stdlib/cmd/cmd.py b/python-stdlib/cmd/cmd.py index 447ea1489..b05fc245d 100644 --- a/python-stdlib/cmd/cmd.py +++ b/python-stdlib/cmd/cmd.py @@ -1,20 +1,21 @@ -"""A generic class to build line-oriented command interpreters. +""" +A generic class to build line-oriented command interpreters. Interpreters constructed with this class obey the following conventions: 1. End of file on input is processed as the command 'EOF'. 2. A command is parsed out of each line by collecting the prefix composed of characters in the identchars member. -3. A command `foo' is dispatched to a method 'do_foo()'; the do_ method +3. A command 'foo' is dispatched to a method 'do_foo()'; the do_ method is passed a single argument consisting of the remainder of the line. 4. Typing an empty line repeats the last command. (Actually, it calls the - method `emptyline', which may be overridden in a subclass.) -5. There is a predefined `help' method. Given an argument `topic', it - calls the command `help_topic'. With no arguments, it lists all topics + method 'emptyline', which may be overridden in a subclass.) +5. There is a predefined 'help' method. Given an argument 'topic', it + calls the command 'help_topic'. With no arguments, it lists all topics with defined help_ functions, broken into up to three topics; documented commands, miscellaneous help topics, and undocumented commands. -6. The command '?' is a synonym for `help'. The command '!' is a synonym - for `shell', if a do_shell method exists. +6. The command '?' is a synonym for 'help'. The command '!' is a synonym + for 'shell', if a do_shell method exists. 7. If completion is enabled, completing commands will be done automatically, and completing of commands args is done by calling complete_foo() with arguments text, line, begidx, endidx. text is string we are matching @@ -23,21 +24,21 @@ indexes of the text being matched, which could be used to provide different completion depending upon which position the argument is in. -The `default' method may be overridden to intercept commands for which there +The 'default' method may be overridden to intercept commands for which there is no do_ method. -The `completedefault' method may be overridden to intercept completions for +The 'completedefault' method may be overridden to intercept completions for commands that have no complete_ method. -The data member `self.ruler' sets the character used to draw separator lines +The data member 'self.ruler' sets the character used to draw separator lines in the help messages. If empty, no ruler line is drawn. It defaults to "=". -If the value of `self.intro' is nonempty when the cmdloop method is called, +If the value of 'self.intro' is nonempty when the cmdloop method is called, it is printed out on interpreter startup. This value may be overridden via an optional argument to the cmdloop() method. -The data members `self.doc_header', `self.misc_header', and -`self.undoc_header' set the headers used for the help function's +The data members 'self.doc_header', 'self.misc_header', and +'self.undoc_header' set the headers used for the help function's listings of documented functions, miscellaneous topics, and undocumented functions respectively. @@ -48,7 +49,7 @@ One of the notable deviations is that since MicroPython strips doc strings, this means that that help by doc string feature doesn't work. -completions have also been stripped out. +Completions have also been stripped out. """ import sys diff --git a/python-stdlib/contextlib/contextlib.py b/python-stdlib/contextlib/contextlib.py index 3e598b4b6..2f6d4a928 100644 --- a/python-stdlib/contextlib/contextlib.py +++ b/python-stdlib/contextlib/contextlib.py @@ -1,10 +1,10 @@ -"""Utilities for with-statement contexts. See PEP 343. +""" +Utilities for with-statement contexts. See PEP 343. Original source code: https://hg.python.org/cpython/file/3.4/Lib/contextlib.py Not implemented: - redirect_stdout; - """ import sys @@ -15,12 +15,12 @@ class closing(object): """Context to automatically close something at the end of a block. - Code like this: + Code like this:: with closing(.open()) as f: - is equivalent to this: + is equivalent to this:: f = .open() try: diff --git a/python-stdlib/heapq/heapq.py b/python-stdlib/heapq/heapq.py index b11853b8d..792497e68 100644 --- a/python-stdlib/heapq/heapq.py +++ b/python-stdlib/heapq/heapq.py @@ -1,4 +1,5 @@ -"""Heap queue algorithm (a.k.a. priority queue). +""" +Heap queue algorithm (a.k.a. priority queue). Heaps are arrays for which a[k] <= a[2*k+1] and a[k] <= a[2*k+2] for all k, counting elements from 0. For the sake of comparison, @@ -7,13 +8,13 @@ Usage: -heap = [] # creates an empty heap -heappush(heap, item) # pushes a new item on the heap -item = heappop(heap) # pops the smallest item from the heap -item = heap[0] # smallest item on the heap without popping it -heapify(x) # transforms list into a heap, in-place, in linear time -item = heapreplace(heap, item) # pops and returns smallest item, and adds - # new item; the heap size is unchanged + heap = [] # creates an empty heap + heappush(heap, item) # pushes a new item on the heap + item = heappop(heap) # pops the smallest item from the heap + item = heap[0] # smallest item on the heap without popping it + heapify(x) # transforms list into a heap, in-place, in linear time + item = heapreplace(heap, item) # pops and returns smallest item, and adds + # new item; the heap size is unchanged Our API differs from textbook heap algorithms as follows: @@ -40,7 +41,7 @@ property of a heap is that a[0] is always its smallest element. The strange invariant above is meant to be an efficient memory -representation for a tournament. The numbers below are `k', not a[k]: +representation for a tournament. The numbers below are 'k', not a[k]:: 0 @@ -53,7 +54,7 @@ 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 -In the tree above, each cell `k' is topping `2*k+1' and `2*k+2'. In +In the tree above, each cell 'k' is topping '2*k+1' and '2*k+2'. In an usual binary tournament we see in sports, each cell is the winner over the two cells it tops, and we can trace the winner down the tree to see all opponents s/he had. However, in many computer applications @@ -108,7 +109,7 @@ effective! In a word, heaps are useful memory structures to know. I use them in -a few applications, and I think it is good to keep a `heap' module +a few applications, and I think it is good to keep a `heap` module around. :-) -------------------- @@ -377,7 +378,7 @@ def _siftup_max(heap, pos): def merge(*iterables): """Merge multiple sorted inputs into a single sorted output. - Similar to sorted(itertools.chain(*iterables)) but returns a generator, + Similar to `sorted(itertools.chain(*iterables))` but returns a generator, does not pull the data into memory all at once, and assumes that each of the input streams is already sorted (smallest to largest). diff --git a/python-stdlib/textwrap/textwrap.py b/python-stdlib/textwrap/textwrap.py index 4e9f35069..c5771bec6 100644 --- a/python-stdlib/textwrap/textwrap.py +++ b/python-stdlib/textwrap/textwrap.py @@ -1,4 +1,5 @@ -"""Text wrapping and filling. +""" +Text wrapping and filling. """ # Copyright (C) 1999-2001 Gregory P. Ward. @@ -169,7 +170,8 @@ def _split(self, text): return chunks def _fix_sentence_endings(self, chunks): - """_fix_sentence_endings(chunks : [string]) + """ + _fix_sentence_endings(chunks : [string]) Correct for sentence endings buried in 'chunks'. Eg. when the original text contains "... foo.\nBar ...", munge_whitespace() From bfd33d581ae0a02fe51d0c43ad8e2a538e02d830 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Mon, 17 Nov 2025 13:30:39 +0100 Subject: [PATCH 43/47] CONTRIBUTING: Add guidelines for module documentation and versioning. Signed-off-by: Jos Verlinde --- CONTRIBUTING.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 61a49101e..815d7373c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -87,6 +87,40 @@ specific conventions and guidelines for micropython-lib: packages](README.md#installing-packages-from-forks) in your Pull Request description. +### Module documentation + +Each package in micropython-lib is encouraged to include documentation in +the form of docstrings in the code itself. The top-level docstring in the main +module or package should provide an overview of the package's functionality, +usage examples, and any important notes or caveats. + +Note that the docstrings should be concise and relevant, even though they will +not be included in the cross-compiled bytecode and will not impact the size of +the module when installed via `mip`. + +When writing docstrings, please follow these guidelines: +* Use triple double quotes (`"""`) for docstrings. +* Multi-line docstrings should place the starting and ending triple quotes on + their own lines. +* Start with a brief summary of the module's purpose. +* Include usage examples where appropriate (indent examplecode blocks with 4 + spaces). +* Use proper indentation for multi-line docstrings to align with the code block. + +### Module versioning + +Each package in micropython-lib should include a `manifest.py` file that +specifies metadata about the package, including its version. The version +management is intentionally manual to ensure package stability and prevent +accidental version bumps. When making changes to a package that affect its +functionality or API, please update the version in `manifest.py`. The +`tools/build.py` script will detect new versions and add them to the index, but +won't increment versions automatically + +> [!NOTE] +> Changes to docstrings or comments that do not change the generated bytecode +> should not change the version. + ### Publishing packages from forks You can easily publish the packages from your micropython-lib From 9ff562640dabca05f07e27835ebad97a0428578c Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Tue, 18 Nov 2025 09:04:40 +0000 Subject: [PATCH 44/47] cbor2: Silence missing `__eq__` warning. Signed-off-by: Jos Verlinde --- python-ecosys/cbor2/cbor2/_decoder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python-ecosys/cbor2/cbor2/_decoder.py b/python-ecosys/cbor2/cbor2/_decoder.py index 965dbfd46..0ddfc31d3 100644 --- a/python-ecosys/cbor2/cbor2/_decoder.py +++ b/python-ecosys/cbor2/cbor2/_decoder.py @@ -34,7 +34,7 @@ class CBORDecodeError(Exception): break_marker = object() -class CBORSimpleValue(object): +class CBORSimpleValue(object): # noqa: PLW1641 """ Represents a CBOR "simple value". :param int value: the value (0-255) From 95fd713b8a0331dfa6730a6fa89f461f5f6c1192 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Thu, 13 Nov 2025 03:00:18 +0100 Subject: [PATCH 45/47] pyproject.toml: Reorganize ruff lint settings for newer ruff. Changes are: - Reorganize lint settings to new sections. - Align ignore rules with micropython/micropython repo. - Update to python38 syntax to enable `:=` operator. Signed-off-by: Jos Verlinde --- pyproject.toml | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 83d29405d..a309df1f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,14 @@ [tool.ruff] -exclude = [ +extend-exclude = [ "python-stdlib", "unix-ffi", ] +line-length = 99 +target-version = "py38" # enable use of walrus operator + +[tool.ruff.lint] select = [ - "ASYNC", # flake8-comprehensions + "ASYNC", # flake8-async "C4", # flake8-comprehensions "C90", # McCabe cyclomatic complexity "DTZ", # flake8-datetimez @@ -53,42 +57,40 @@ select = [ # "TRY", # tryceratops # "UP", # pyupgrade ] -ignore = [ +extend-ignore = [ "E722", - "E741", # 'l' is currently widely used + "E741", # 'l' is currently widely used "F401", "F403", "F405", - "E501", # line length, recommended to disable + "E501", # line length, recommended to disable "ISC001", - "ISC003", # micropython does not support implicit concatenation of f-strings - "PIE810", # micropython does not support passing tuples to .startswith or .endswith + "ISC003", # MicroPython does not support implicit concatenation of f-strings + "PIE810", # MicroPython does not support passing tuples to .startswith or .endswith + "PLC0415", # conditional imports are common in MicroPython "PLC1901", - "PLR1704", # sometimes desirable to redefine an argument to save code size + "PLR1704", # sometimes desirable to redefine an argument to save code size "PLR1714", "PLR5501", "PLW0602", "PLW0603", "PLW2901", - "RUF012", - "RUF100", + "RUF012", # mutable default values in class attributes. + "RUF059", # Unpacked variable `foo` is never used + "RUF100", # duplicate noqa directives. "SIM101", - "W191", # tab-indent, redundant when using formatter + "W191", # tab-indent, redundant when using formatter ] -line-length = 99 -target-version = "py37" +mccabe.max-complexity = 61 -[tool.ruff.mccabe] -max-complexity = 61 - -[tool.ruff.pylint] +[tool.ruff.lint.pylint] allow-magic-value-types = ["bytes", "int", "str"] max-args = 14 max-branches = 58 max-returns = 13 max-statements = 166 -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "micropython/aiorepl/aiorepl.py" = ["PGH001"] # manifest.py files are evaluated with some global names pre-defined @@ -97,5 +99,3 @@ max-statements = 166 # ble multitests are evaluated with some names pre-defined "micropython/bluetooth/aioble/multitests/*" = ["F821"] - -[tool.ruff.format] From b3125247bfd0fa4d03662c49eb416aa3397a36de Mon Sep 17 00:00:00 2001 From: Marcel Petrick Date: Mon, 17 Nov 2025 13:25:15 +0100 Subject: [PATCH 46/47] all: Correct various typos in comments and docs. Non-functional changes only: - Fixed minor spelling mistakes in comments. - Corrected typos in user-facing strings. - No variables, logic, or functional code was modified. Signed-off-by: Marcel Petrick --- micropython/aiorepl/aiorepl.py | 2 +- .../bluetooth/aioble/examples/l2cap_file_server.py | 2 +- micropython/drivers/display/lcd160cr/lcd160cr.py | 2 +- micropython/drivers/imu/bmi270/bmi270.py | 2 +- micropython/drivers/imu/bmm150/bmm150.py | 2 +- micropython/drivers/imu/lsm6dsox/lsm6dsox.py | 2 +- micropython/drivers/imu/lsm9ds1/lsm9ds1.py | 6 +++--- micropython/lora/lora-sx126x/lora/sx126x.py | 2 +- micropython/lora/lora/lora/modem.py | 2 +- micropython/senml/docs/index.md | 2 +- micropython/senml/docs/senml_record.md | 4 ++-- micropython/senml/examples/custom_record.py | 2 +- micropython/senml/senml/senml_pack.py | 2 +- micropython/senml/senml/senml_record.py | 6 +++--- micropython/ucontextlib/ucontextlib.py | 2 +- micropython/umqtt.simple/README.rst | 2 +- micropython/usb/usb-device-cdc/usb/device/cdc.py | 2 +- micropython/usb/usb-device-midi/usb/device/midi.py | 2 +- .../usb/usb-device-mouse/usb/device/mouse.py | 2 +- micropython/usb/usb-device/usb/device/core.py | 2 +- python-stdlib/datetime/test_datetime.py | 4 ++-- python-stdlib/pathlib/tests/test_pathlib.py | 2 +- python-stdlib/time/README.md | 2 +- python-stdlib/unittest/examples/example_subtest.py | 4 ++-- tools/uncrustify.cfg | 14 +++++++------- unix-ffi/cgi/cgi.py | 2 +- unix-ffi/ucurses/ucurses/__init__.py | 2 +- 27 files changed, 40 insertions(+), 40 deletions(-) diff --git a/micropython/aiorepl/aiorepl.py b/micropython/aiorepl/aiorepl.py index fbe513b7c..15026e435 100644 --- a/micropython/aiorepl/aiorepl.py +++ b/micropython/aiorepl/aiorepl.py @@ -74,7 +74,7 @@ async def kbd_intr_task(exec_task, s): except asyncio.CancelledError: pass else: - # Excute code snippet directly. + # Execute code snippet directly. try: try: micropython.kbd_intr(3) diff --git a/micropython/bluetooth/aioble/examples/l2cap_file_server.py b/micropython/bluetooth/aioble/examples/l2cap_file_server.py index 3c3b3b44d..95aad0b38 100644 --- a/micropython/bluetooth/aioble/examples/l2cap_file_server.py +++ b/micropython/bluetooth/aioble/examples/l2cap_file_server.py @@ -1,7 +1,7 @@ # MIT license; Copyright (c) 2021 Jim Mussared # This is a BLE file server, based very loosely on the Object Transfer Service -# specification. It demonstrated transfering data over an L2CAP channel, as +# specification. It demonstrated transferring data over an L2CAP channel, as # well as using notifications and GATT writes on a characteristic. # The server supports downloading and uploading files, as well as querying diff --git a/micropython/drivers/display/lcd160cr/lcd160cr.py b/micropython/drivers/display/lcd160cr/lcd160cr.py index 177c6fea3..1d5da2293 100644 --- a/micropython/drivers/display/lcd160cr/lcd160cr.py +++ b/micropython/drivers/display/lcd160cr/lcd160cr.py @@ -56,7 +56,7 @@ def __init__(self, connect=None, *, pwr=None, i2c=None, spi=None, i2c_addr=98): pwr(1) sleep_ms(10) # else: - # alread have power + # already have power # lets be optimistic... # set connections diff --git a/micropython/drivers/imu/bmi270/bmi270.py b/micropython/drivers/imu/bmi270/bmi270.py index c76962f7e..1e4a940c3 100644 --- a/micropython/drivers/imu/bmi270/bmi270.py +++ b/micropython/drivers/imu/bmi270/bmi270.py @@ -504,7 +504,7 @@ def __init__( accel_scale=4, bmm_magnet=None, ): - """Initalizes Gyro and Accelerometer. + """Initializes Gyro and Accelerometer. bus: IMU bus address: I2C address (in I2C mode). cs: SPI CS pin (in SPI mode). diff --git a/micropython/drivers/imu/bmm150/bmm150.py b/micropython/drivers/imu/bmm150/bmm150.py index aea4348b5..a56c6fe78 100644 --- a/micropython/drivers/imu/bmm150/bmm150.py +++ b/micropython/drivers/imu/bmm150/bmm150.py @@ -66,7 +66,7 @@ def __init__( address=_DEFAULT_ADDR, magnet_odr=30, ): - """Initalizes the Magnetometer. + """Initializes the Magnetometer. bus: IMU bus address: I2C address (in I2C mode). cs: SPI CS pin (in SPI mode). diff --git a/micropython/drivers/imu/lsm6dsox/lsm6dsox.py b/micropython/drivers/imu/lsm6dsox/lsm6dsox.py index b932ff006..ca7b77673 100644 --- a/micropython/drivers/imu/lsm6dsox/lsm6dsox.py +++ b/micropython/drivers/imu/lsm6dsox/lsm6dsox.py @@ -89,7 +89,7 @@ def __init__( accel_scale=4, ucf=None, ): - """Initalizes Gyro and Accelerator. + """Initializes Gyro and Accelerator. accel_odr: (0, 1.6Hz, 3.33Hz, 6.66Hz, 12.5Hz, 26Hz, 52Hz, 104Hz, 208Hz, 416Hz, 888Hz) gyro_odr: (0, 1.6Hz, 3.33Hz, 6.66Hz, 12.5Hz, 26Hz, 52Hz, 104Hz, 208Hz, 416Hz, 888Hz) gyro_scale: (245dps, 500dps, 1000dps, 2000dps) diff --git a/micropython/drivers/imu/lsm9ds1/lsm9ds1.py b/micropython/drivers/imu/lsm9ds1/lsm9ds1.py index a45e73039..5f1bfb6cc 100644 --- a/micropython/drivers/imu/lsm9ds1/lsm9ds1.py +++ b/micropython/drivers/imu/lsm9ds1/lsm9ds1.py @@ -81,7 +81,7 @@ def __init__( magnet_odr=80, magnet_scale=4, ): - """Initalizes Gyro, Accelerometer and Magnetometer. + """Initializes Gyro, Accelerometer and Magnetometer. bus: IMU bus address_imu: IMU I2C address. address_magnet: Magnetometer I2C address. @@ -134,14 +134,14 @@ def __init__( mv[5] = 0x2 # ctrl9 - FIFO enabled self.bus.writeto_mem(self.address_imu, _CTRL_REG4_G, mv) - # fifo: use continous mode (overwrite old data if overflow) + # fifo: use continuous mode (overwrite old data if overflow) self.bus.writeto_mem(self.address_imu, _FIFO_CTRL_REG, b"\x00") self.bus.writeto_mem(self.address_imu, _FIFO_CTRL_REG, b"\xc0") # Configure Magnetometer mv[0] = 0x40 | (magnet_odr << 2) # ctrl1: high performance mode mv[1] = _MAGNET_SCALE.index(magnet_scale) << 5 # ctrl2: scale, normal mode, no reset - mv[2] = 0x00 # ctrl3: continous conversion, no low power, I2C + mv[2] = 0x00 # ctrl3: continuous conversion, no low power, I2C mv[3] = 0x08 # ctrl4: high performance z-axis mv[4] = 0x00 # ctr5: no fast read, no block update self.bus.writeto_mem(self.address_magnet, _CTRL_REG1_M, mv[:5]) diff --git a/micropython/lora/lora-sx126x/lora/sx126x.py b/micropython/lora/lora-sx126x/lora/sx126x.py index 7fa4896ae..446489431 100644 --- a/micropython/lora/lora-sx126x/lora/sx126x.py +++ b/micropython/lora/lora-sx126x/lora/sx126x.py @@ -501,7 +501,7 @@ def calibrate_image(self): self._cmd(">BH", _CMD_CALIBRATE_IMAGE, args) - # Can't find anythign in Datasheet about how long image calibration + # Can't find anything in Datasheet about how long image calibration # takes or exactly how it signals completion. Assuming it will be # similar to _CMD_CALIBRATE. self._wait_not_busy(_CALIBRATE_TIMEOUT_US) diff --git a/micropython/lora/lora/lora/modem.py b/micropython/lora/lora/lora/modem.py index 499712acf..73fd4db20 100644 --- a/micropython/lora/lora/lora/modem.py +++ b/micropython/lora/lora/lora/modem.py @@ -47,7 +47,7 @@ def __init__(self, ant_sw): self._bw_hz = 125000 # Reset value self._coding_rate = 5 self._crc_en = True # use packet CRCs - self._implicit_header = False # implict vs explicit header mode + self._implicit_header = False # implicit vs explicit header mode self._preamble_len = 12 self._coding_rate = 5 diff --git a/micropython/senml/docs/index.md b/micropython/senml/docs/index.md index 91ed7fe99..e849f2ca7 100644 --- a/micropython/senml/docs/index.md +++ b/micropython/senml/docs/index.md @@ -1,4 +1,4 @@ -Welcome to the API documet site for the micro-python SenML library. +Welcome to the API document site for the micro-python SenML library. The following api sections are available: diff --git a/micropython/senml/docs/senml_record.md b/micropython/senml/docs/senml_record.md index 6bac549a5..e7c8d0cd0 100644 --- a/micropython/senml/docs/senml_record.md +++ b/micropython/senml/docs/senml_record.md @@ -42,11 +42,11 @@ _parameters:_ - `kwargs:` optional parameters: - value: the value to store in the record - time: the timestamp to use (when was the value measured) - - name: the name of hte record + - name: the name of the record - unit: unit value - sum: sum value - update_time: max time before sensor will provide an updated reading - - callback: a callback function taht will be called when actuator data has been found. Expects no params + - callback: a callback function that will be called when actuator data has been found. Expects no params ### do_actuate diff --git a/micropython/senml/examples/custom_record.py b/micropython/senml/examples/custom_record.py index 1e83ea06b..ece866791 100644 --- a/micropython/senml/examples/custom_record.py +++ b/micropython/senml/examples/custom_record.py @@ -33,7 +33,7 @@ def __init__(self, name, **kwargs): """overriding the init function so we can initiate the 3 senml records that will represent lat,lon, alt""" self._lat = SenmlRecord( "lattitude", unit=SenmlUnits.SENML_UNIT_DEGREES_LATITUDE - ) # create these befor calling base constructor so that all can be init correctly from constructor + ) # create these before calling base constructor so that all can be init correctly from constructor self._lon = SenmlRecord("longitude", unit=SenmlUnits.SENML_UNIT_DEGREES_LONGITUDE) self._alt = SenmlRecord("altitude", unit=SenmlUnits.SENML_UNIT_METER) super(Coordinates, self).__init__( diff --git a/micropython/senml/senml/senml_pack.py b/micropython/senml/senml/senml_pack.py index 5a0554467..cb5f89fa5 100644 --- a/micropython/senml/senml/senml_pack.py +++ b/micropython/senml/senml/senml_pack.py @@ -169,7 +169,7 @@ def from_json(self, data): def _process_incomming_data(self, records, naming_map): """ - generic processor for incomming data (actuators. + generic processor for incoming data (actuators. :param records: the list of raw senml data, parsed from a json or cbor structure :param naming_map: translates cbor to json field names (when needed). :return: None diff --git a/micropython/senml/senml/senml_record.py b/micropython/senml/senml/senml_record.py index ae40f0f70..9cc260f5b 100644 --- a/micropython/senml/senml/senml_record.py +++ b/micropython/senml/senml/senml_record.py @@ -36,11 +36,11 @@ def __init__(self, name, **kwargs): :param kwargs: optional parameters: - value: the value to store in the record - time: the timestamp to use (when was the value measured) - - name: the name of hte record + - name: the name of the record - unit: unit value - sum: sum value - update_time: max time before sensor will provide an updated reading - - callback: a callback function taht will be called when actuator data has been found. Expects no params + - callback: a callback function that will be called when actuator data has been found. Expects no params """ self.__parent = None # using double __ cause it's a field for an internal property self._unit = None # declare and init internal fields @@ -106,7 +106,7 @@ def value(self): @value.setter def value(self, value): - """set the current value. Will not automatically update the time stamp. This has to be done seperatly for more + """set the current value. Will not automatically update the time stamp. This has to be done separately for more finegrained control Note: when the value is a float, you can control rounding in the rendered output by using the function round() while assigning the value. ex: record.value = round(12.2 / 1.5423, 2) diff --git a/micropython/ucontextlib/ucontextlib.py b/micropython/ucontextlib/ucontextlib.py index d259f9b8f..d7d984a95 100644 --- a/micropython/ucontextlib/ucontextlib.py +++ b/micropython/ucontextlib/ucontextlib.py @@ -6,7 +6,7 @@ - redirect_stdout; - ExitStack. - closing - - supress + - suppress """ diff --git a/micropython/umqtt.simple/README.rst b/micropython/umqtt.simple/README.rst index d9d09b970..ee360ad61 100644 --- a/micropython/umqtt.simple/README.rst +++ b/micropython/umqtt.simple/README.rst @@ -47,7 +47,7 @@ Taking into account API traits described above, umqtt pretty closely follows MQTT control operations, and maps them to class methods: * ``connect(...)`` - Connect to a server. Returns True if this connection - uses persisten session stored on a server (this will be always False if + uses persistent session stored on a server (this will be always False if clean_session=True argument is used (default)). * ``disconnect()`` - Disconnect from a server, release resources. * ``ping()`` - Ping server (response is processed automatically by wait_msg()). diff --git a/micropython/usb/usb-device-cdc/usb/device/cdc.py b/micropython/usb/usb-device-cdc/usb/device/cdc.py index 0acea184f..4ec012bc5 100644 --- a/micropython/usb/usb-device-cdc/usb/device/cdc.py +++ b/micropython/usb/usb-device-cdc/usb/device/cdc.py @@ -228,7 +228,7 @@ def desc_cfg(self, desc, itf_num, ep_num, strs): 5, # bFunctionLength _CS_DESC_TYPE, # bDescriptorType _CDC_FUNC_DESC_CALL_MANAGEMENT, # bDescriptorSubtype - 0, # bmCapabilities - XXX no call managment so far + 0, # bmCapabilities - XXX no call management so far itf_num + 1, # bDataInterface - interface 1 ) diff --git a/micropython/usb/usb-device-midi/usb/device/midi.py b/micropython/usb/usb-device-midi/usb/device/midi.py index ecb178ea4..55bfbd08b 100644 --- a/micropython/usb/usb-device-midi/usb/device/midi.py +++ b/micropython/usb/usb-device-midi/usb/device/midi.py @@ -61,7 +61,7 @@ class MIDIInterface(Interface): # Base class to implement a USB MIDI device in Python. # - # To be compliant this also regisers a dummy USB Audio interface, but that + # To be compliant this also registers a dummy USB Audio interface, but that # interface isn't otherwise used. def __init__(self, rxlen=16, txlen=16): diff --git a/micropython/usb/usb-device-mouse/usb/device/mouse.py b/micropython/usb/usb-device-mouse/usb/device/mouse.py index d3dea9c61..61345276b 100644 --- a/micropython/usb/usb-device-mouse/usb/device/mouse.py +++ b/micropython/usb/usb-device-mouse/usb/device/mouse.py @@ -77,7 +77,7 @@ def move_by(self, dx, dy): b'\xA1\x00' # Collection (Physical) b'\x05\x09' # Usage Page (Buttons) b'\x19\x01' # Usage Minimum (01), - b'\x29\x03' # Usage Maximun (03), + b'\x29\x03' # Usage Maximum (03), b'\x15\x00' # Logical Minimum (0), b'\x25\x01' # Logical Maximum (1), b'\x95\x03' # Report Count (3), diff --git a/micropython/usb/usb-device/usb/device/core.py b/micropython/usb/usb-device/usb/device/core.py index b0d91d8ff..926c662bd 100644 --- a/micropython/usb/usb-device/usb/device/core.py +++ b/micropython/usb/usb-device/usb/device/core.py @@ -446,7 +446,7 @@ def num_itfs(self): # Return the number of actual USB Interfaces represented by this object # (as set in desc_cfg().) # - # Only needs to be overriden if implementing a Interface class that + # Only needs to be overridden if implementing a Interface class that # represents more than one USB Interface descriptor (i.e. MIDI), or an # Interface Association Descriptor (i.e. USB-CDC). return 1 diff --git a/python-stdlib/datetime/test_datetime.py b/python-stdlib/datetime/test_datetime.py index 56411b96e..999bfaca9 100644 --- a/python-stdlib/datetime/test_datetime.py +++ b/python-stdlib/datetime/test_datetime.py @@ -2091,7 +2091,7 @@ def test_timestamp01(self): def test_timestamp02(self): with LocalTz("Europe/Rome"): - dt = datetime(2010, 3, 28, 2, 30) # doens't exist + dt = datetime(2010, 3, 28, 2, 30) # doesn't exist self.assertEqual(dt.timestamp(), 1269739800.0) def test_timestamp03(self): @@ -2111,7 +2111,7 @@ def test_timestamp05(self): def test_timestamp06(self): with LocalTz("US/Eastern"): - dt = datetime(2020, 3, 8, 2, 30) # doens't exist + dt = datetime(2020, 3, 8, 2, 30) # doesn't exist self.assertEqual(dt.timestamp(), 1583652600.0) def test_timestamp07(self): diff --git a/python-stdlib/pathlib/tests/test_pathlib.py b/python-stdlib/pathlib/tests/test_pathlib.py index e632e1242..31e762eea 100644 --- a/python-stdlib/pathlib/tests/test_pathlib.py +++ b/python-stdlib/pathlib/tests/test_pathlib.py @@ -234,7 +234,7 @@ def test_touch(self): self.assertExists(target) # Technically should be FileExistsError, - # but thats not builtin to micropython + # but that's not builtin to micropython with self.assertRaises(OSError): path.touch(exist_ok=False) diff --git a/python-stdlib/time/README.md b/python-stdlib/time/README.md index f07517305..f3d880ad7 100644 --- a/python-stdlib/time/README.md +++ b/python-stdlib/time/README.md @@ -19,7 +19,7 @@ See [Package management](https://docs.micropython.org/en/latest/reference/packag ## Common uses -`strftime()` is used when using a loggging [Formatter +`strftime()` is used when using a logging [Formatter Object](https://docs.python.org/3/library/logging.html#formatter-objects) that employs [`asctime`](https://docs.python.org/3/library/logging.html#formatter-objects). diff --git a/python-stdlib/unittest/examples/example_subtest.py b/python-stdlib/unittest/examples/example_subtest.py index 558af0b26..40b87d45b 100644 --- a/python-stdlib/unittest/examples/example_subtest.py +++ b/python-stdlib/unittest/examples/example_subtest.py @@ -99,7 +99,7 @@ def test_sorted(self) -> None: self.assertEqual(sorted(test["unsorted"]), test["sorted"]) def test_factorial(self) -> None: - """Test that the factorial fuction correctly calculates factorials + """Test that the factorial function correctly calculates factorials Makes use of `msg` argument in subtest method to clarify which subtests had an error in the results @@ -190,7 +190,7 @@ def test_person(self) -> None: self.assertFalse(bob.has_friend(alice)) # Friendship is not always commutative, so Bob is not implicitly friends with Alice - with self.subTest("Alice and Bob should not both be friends with eachother"): + with self.subTest("Alice and Bob should not both be friends with each other"): with self.assertRaises(AssertionError): self.assertTrue(bob.has_friend(alice) and alice.has_friend(bob)) diff --git a/tools/uncrustify.cfg b/tools/uncrustify.cfg index 80542b903..7a0ff0d46 100644 --- a/tools/uncrustify.cfg +++ b/tools/uncrustify.cfg @@ -1323,7 +1323,7 @@ indent_using_block = true # true/false # 2: When the `:` is a continuation, indent it under `?` indent_ternary_operator = 0 # unsigned number -# Whether to indent the statments inside ternary operator. +# Whether to indent the statements inside ternary operator. indent_inside_ternary_operator = false # true/false # If true, the indentation of the chunks after a `return` sequence will be set at return indentation column. @@ -1779,7 +1779,7 @@ nl_func_call_args_multi_line = false # true/false # different lines. nl_func_call_end_multi_line = false # true/false -# Whether to respect nl_func_call_XXX option incase of closure args. +# Whether to respect nl_func_call_XXX option in case of closure args. nl_func_call_args_multi_line_ignore_closures = false # true/false # Whether to add a newline after '<' of a template parameter list. @@ -2570,7 +2570,7 @@ align_oc_decl_colon = false # true/false # (OC) Whether to not align parameters in an Objectve-C message call if first # colon is not on next line of the message call (the same way Xcode does -# aligment) +# alignment) align_oc_msg_colon_xcode_like = false # true/false # @@ -2916,28 +2916,28 @@ pp_define_at_level = false # true/false pp_ignore_define_body = false # true/false # Whether to indent case statements between #if, #else, and #endif. -# Only applies to the indent of the preprocesser that the case statements +# Only applies to the indent of the preprocessor that the case statements # directly inside of. # # Default: true pp_indent_case = true # true/false # Whether to indent whole function definitions between #if, #else, and #endif. -# Only applies to the indent of the preprocesser that the function definition +# Only applies to the indent of the preprocessor that the function definition # is directly inside of. # # Default: true pp_indent_func_def = true # true/false # Whether to indent extern C blocks between #if, #else, and #endif. -# Only applies to the indent of the preprocesser that the extern block is +# Only applies to the indent of the preprocessor that the extern block is # directly inside of. # # Default: true pp_indent_extern = true # true/false # Whether to indent braces directly inside #if, #else, and #endif. -# Only applies to the indent of the preprocesser that the braces are directly +# Only applies to the indent of the preprocessor that the braces are directly # inside of. # # Default: true diff --git a/unix-ffi/cgi/cgi.py b/unix-ffi/cgi/cgi.py index 550f70713..e8e91bfe4 100644 --- a/unix-ffi/cgi/cgi.py +++ b/unix-ffi/cgi/cgi.py @@ -197,7 +197,7 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0): # parse query string function called from urlparse, -# this is done in order to maintain backward compatiblity. +# this is done in order to maintain backward compatibility. def parse_qs(qs, keep_blank_values=0, strict_parsing=0): diff --git a/unix-ffi/ucurses/ucurses/__init__.py b/unix-ffi/ucurses/ucurses/__init__.py index 688df745c..2e3af32d6 100644 --- a/unix-ffi/ucurses/ucurses/__init__.py +++ b/unix-ffi/ucurses/ucurses/__init__.py @@ -311,7 +311,7 @@ def meta(yes): def mousemask(mask): - # Mouse reporting - X10 compatbility mode + # Mouse reporting - X10 compatibility mode _wr(b"\x1b[?9h") From 7d0a1f86beb7ab035f9cbf60f4b4204603eb150d Mon Sep 17 00:00:00 2001 From: Breno RdV Date: Sat, 15 Nov 2025 02:18:40 -0500 Subject: [PATCH 47/47] lora: Fix import error detection for missing drivers. Signed-off-by: Breno RdV --- micropython/lora/lora/lora/__init__.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/micropython/lora/lora/lora/__init__.py b/micropython/lora/lora/lora/__init__.py index 7f8930b8c..9030c91bd 100644 --- a/micropython/lora/lora/lora/__init__.py +++ b/micropython/lora/lora/lora/__init__.py @@ -5,6 +5,12 @@ ok = False # Flag if at least one modem driver package is installed + +def _can_ignore_error(e): + """Check if ImportError can be ignored due to missing module.""" + return all(x in str(e) for x in ["no module named", "lora"]) + + # Various lora "sub-packages" try: @@ -12,7 +18,7 @@ ok = True except ImportError as e: - if "no module named 'lora." not in str(e): + if not _can_ignore_error(e): raise try: @@ -20,7 +26,7 @@ ok = True except ImportError as e: - if "no module named 'lora." not in str(e): + if not _can_ignore_error(e): raise try: @@ -28,7 +34,7 @@ ok = True except ImportError as e: - if "no module named 'lora." not in str(e): + if not _can_ignore_error(e): raise