diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..48dffdd --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +.DEFAULT_GOAL := send + +.PHONY: send +send: + cp code/code.py /media/chee/prettychips/ + +.PHONY: init +init: + cp -r code/* /media/chee/prettychips/ diff --git a/code/code.py b/code/code.py new file mode 100755 index 0000000..14ecb5c --- /dev/null +++ b/code/code.py @@ -0,0 +1,110 @@ +import board +import time +import displayio +import gamepadshift +import adafruit_imageload + +MODE_LIVE = 0 +MODE_MENU1 = 1 +MODE_MENU2 = 2 + +SOUND_TYPE_SQUARE = 0 +SOUND_TYPE_PULSE = 1 +SOUND_TYPE_SINE = 2 +SOUND_TYPE_NOISE = 3 + +class PitchEnvelope(): + pass + +class AmpEnvelope(): + pass + +class SoundBlock(): + index = -1 + note = None + intervals = [0, 0, 0] + interval_mode = 0 + octave = 3 + pitch_env = PitchEnvelope() + amp_env = AmpEnvelope() + delay = 0 + pan = 0 + def __init__(self, index): + self.index = index + +class Sound(): + name = None + type = None + blocks = [] + rate = 8 # 8 for normal, 4 for half, 2 for quarter, 1 for eigth + + def __init__(self, machine, name, type): + self.machine = machine + self.name = name + self.type = type + for i in range(16): + self.blocks.append(SoundBlock(i)) + @property + def playing_block(self): + return self.machine.current_tick // self.rate + +class BleepBloopMachine(): + mode = MODE_LIVE + bpm = 120 + current_tick = 0 + max_tick = 127 + selected_live_block = 0 + selected_menu1_control = 0 + selected_menu2_control = 0 + current_sound_index = 0 + sounds = [] + @property + def tick_length(self): + return self.bpm / self.max_tick / 16 + @property + def current_sound(self): + return self.sounds[self.current_sound_index] + def tick(self): + time.sleep(self.tick_length) + self.current_tick += 1 + if self.current_tick > 127: + self.current_tick = 0 + def __init__(self): + self.sounds.append(Sound(self, "q", SOUND_TYPE_SQUARE)) + self.sounds.append(Sound(self, "p", SOUND_TYPE_PULSE)) + self.sounds.append(Sound(self, "s", SOUND_TYPE_SINE)) + self.sounds.append(Sound(self, "n", SOUND_TYPE_NOISE)) + +machine = BleepBloopMachine() + +display = board.DISPLAY + +colorsheet, palette = adafruit_imageload.load( + "colors.bmp", + bitmap=displayio.Bitmap, + palette=displayio.Palette, +) +grid = displayio.TileGrid( + colorsheet, + pixel_shader=palette, + width=10, + height=8, + tile_width=16, + tile_height=16, +) +grid_group = displayio.Group() +grid_group.append(grid) +display.show(grid_group) + +sound = machine.current_sound +while True: + sound = machine.current_sound + for y in range(0, 4): + for x in range(0, 4): + color = 1 + if x == sound.playing_block % 4 and y == sound.playing_block // 4: + color = 2 + grid[x + 3, y + 2] = color + print(sound.playing_block) + print(x, y, sound.playing_block / 4) + machine.tick() diff --git a/code/colors.bmp b/code/colors.bmp new file mode 100644 index 0000000..fefe5de Binary files /dev/null and b/code/colors.bmp differ diff --git a/code/lib/adafruit_imageload/__init__.mpy b/code/lib/adafruit_imageload/__init__.mpy new file mode 100644 index 0000000..0e4341b Binary files /dev/null and b/code/lib/adafruit_imageload/__init__.mpy differ diff --git a/code/lib/adafruit_imageload/bmp/__init__.mpy b/code/lib/adafruit_imageload/bmp/__init__.mpy new file mode 100644 index 0000000..f610ba1 Binary files /dev/null and b/code/lib/adafruit_imageload/bmp/__init__.mpy differ diff --git a/code/lib/adafruit_imageload/bmp/indexed.mpy b/code/lib/adafruit_imageload/bmp/indexed.mpy new file mode 100644 index 0000000..b375035 Binary files /dev/null and b/code/lib/adafruit_imageload/bmp/indexed.mpy differ diff --git a/code/lib/adafruit_imageload/bmp/negative_height_check.mpy b/code/lib/adafruit_imageload/bmp/negative_height_check.mpy new file mode 100644 index 0000000..a0c86e4 Binary files /dev/null and b/code/lib/adafruit_imageload/bmp/negative_height_check.mpy differ diff --git a/code/lib/adafruit_imageload/gif.mpy b/code/lib/adafruit_imageload/gif.mpy new file mode 100644 index 0000000..6c3c982 Binary files /dev/null and b/code/lib/adafruit_imageload/gif.mpy differ diff --git a/code/lib/adafruit_imageload/pnm/__init__.mpy b/code/lib/adafruit_imageload/pnm/__init__.mpy new file mode 100644 index 0000000..73d3de1 Binary files /dev/null and b/code/lib/adafruit_imageload/pnm/__init__.mpy differ diff --git a/code/lib/adafruit_imageload/pnm/pbm_ascii.mpy b/code/lib/adafruit_imageload/pnm/pbm_ascii.mpy new file mode 100644 index 0000000..53970a8 Binary files /dev/null and b/code/lib/adafruit_imageload/pnm/pbm_ascii.mpy differ diff --git a/code/lib/adafruit_imageload/pnm/pbm_binary.mpy b/code/lib/adafruit_imageload/pnm/pbm_binary.mpy new file mode 100644 index 0000000..dcdde64 Binary files /dev/null and b/code/lib/adafruit_imageload/pnm/pbm_binary.mpy differ diff --git a/code/lib/adafruit_imageload/pnm/pgm/__init__.mpy b/code/lib/adafruit_imageload/pnm/pgm/__init__.mpy new file mode 100644 index 0000000..efcb83d Binary files /dev/null and b/code/lib/adafruit_imageload/pnm/pgm/__init__.mpy differ diff --git a/code/lib/adafruit_imageload/pnm/pgm/ascii.mpy b/code/lib/adafruit_imageload/pnm/pgm/ascii.mpy new file mode 100644 index 0000000..26bac4f Binary files /dev/null and b/code/lib/adafruit_imageload/pnm/pgm/ascii.mpy differ diff --git a/code/lib/adafruit_imageload/pnm/pgm/binary.mpy b/code/lib/adafruit_imageload/pnm/pgm/binary.mpy new file mode 100644 index 0000000..7da0e3d Binary files /dev/null and b/code/lib/adafruit_imageload/pnm/pgm/binary.mpy differ diff --git a/code/lib/adafruit_imageload/pnm/ppm_ascii.mpy b/code/lib/adafruit_imageload/pnm/ppm_ascii.mpy new file mode 100644 index 0000000..7fef95a Binary files /dev/null and b/code/lib/adafruit_imageload/pnm/ppm_ascii.mpy differ diff --git a/code/lib/adafruit_imageload/pnm/ppm_binary.mpy b/code/lib/adafruit_imageload/pnm/ppm_binary.mpy new file mode 100644 index 0000000..ae97974 Binary files /dev/null and b/code/lib/adafruit_imageload/pnm/ppm_binary.mpy differ diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 0000000..161d791 --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,4 @@ +{ + "stubPath": "./stubs", + "include": ["./code/*", "./code/lib/*"] +} diff --git a/stubs/_bleio/__init__.pyi b/stubs/_bleio/__init__.pyi new file mode 100644 index 0000000..d9397c0 --- /dev/null +++ b/stubs/_bleio/__init__.pyi @@ -0,0 +1,686 @@ +"""Bluetooth Low Energy (BLE) communication + +The `_bleio` module provides necessary low-level functionality for communicating +using Bluetooth Low Energy (BLE). The '_' prefix indicates this module is meant +for internal use by libraries but not by the end user. Its API may change incompatibly +between minor versions of CircuitPython. +Please use the +`adafruit_ble `_ +CircuitPython library instead, which builds on `_bleio`, and +provides higher-level convenience functionality, including predefined beacons, clients, +servers.""" + +from __future__ import annotations + +from typing import Iterable, Iterator, Optional, Tuple, Union + +import _bleio +import busio +import digitalio +from _typing import ReadableBuffer, WriteableBuffer + +adapter: Adapter +"""BLE Adapter used to manage device discovery and connections. +This object is the sole instance of `_bleio.Adapter`.""" + +class BluetoothError(Exception): + """Catchall exception for Bluetooth related errors.""" + + ... + +class RoleError(BluetoothError): + """Raised when a resource is used as the mismatched role. For example, if a local CCCD is + attempted to be set but they can only be set when remote.""" + + ... + +class SecurityError(BluetoothError): + """Raised when a security related error occurs.""" + + ... + +def set_adapter(adapter: Optional[_bleio.Adapter]) -> None: + """Set the adapter to use for BLE, such as when using an HCI adapter. + Raises `NotImplementedError` when the adapter is a singleton and cannot be set.""" + ... + +class Adapter: + """ + The BLE Adapter object manages the discovery and connection to other nearby Bluetooth Low Energy devices. + This part of the Bluetooth Low Energy Specification is known as Generic Access Profile (GAP). + + Discovery of other devices happens during a scanning process that listens for small packets of + information, known as advertisements, that are broadcast unencrypted. The advertising packets + have two different uses. The first is to broadcast a small piece of data to anyone who cares and + and nothing more. These are known as beacons. The second class of advertisement is to promote + additional functionality available after the devices establish a connection. For example, a + BLE heart rate monitor would advertise that it provides the standard BLE Heart Rate Service. + + The Adapter can do both parts of this process: it can scan for other device + advertisements and it can advertise its own data. Furthermore, Adapters can accept incoming + connections and also initiate connections.""" + + def __init__( + self, + *, + uart: busio.UART, + rts: digitalio.DigitalInOut, + cts: digitalio.DigitalInOut + ) -> None: + """On boards that do not have native BLE, you can use an HCI co-processor. + Pass the uart and pins used to communicate with the co-processor, such as an Adafruit AirLift. + The co-processor must have been reset and put into BLE mode beforehand + by the appropriate pin manipulation. + The ``uart``, ``rts``, and ``cts`` objects are used to + communicate with the HCI co-processor in HCI mode. + The `Adapter` object is enabled during this call. + + After instantiating an Adapter, call `_bleio.set_adapter()` to set `_bleio.adapter` + + On boards with native BLE, you cannot create an instance of `_bleio.Adapter`; + this constructor will raise `NotImplementedError`. + Use `_bleio.adapter` to access the sole instance already available. + """ + ... + enabled: bool + """State of the BLE adapter.""" + + address: Address + """MAC address of the BLE adapter.""" + + name: str + """name of the BLE adapter used once connected. + The name is "CIRCUITPY" + the last four hex digits of ``adapter.address``, + to make it easy to distinguish multiple CircuitPython boards.""" + def start_advertising( + self, + data: ReadableBuffer, + *, + scan_response: Optional[ReadableBuffer] = None, + connectable: bool = True, + anonymous: bool = False, + timeout: int = 0, + interval: float = 0.1 + ) -> None: + """Starts advertising until `stop_advertising` is called or if connectable, another device + connects to us. + + .. warning: If data is longer than 31 bytes, then this will automatically advertise as an + extended advertisement that older BLE 4.x clients won't be able to scan for. + + .. note: If you set ``anonymous=True``, then a timeout must be specified. If no timeout is + specified, then the maximum allowed timeout will be selected automatically. + + :param ~_typing.ReadableBuffer data: advertising data packet bytes + :param ~_typing.ReadableBuffer scan_response: scan response data packet bytes. ``None`` if no scan response is needed. + :param bool connectable: If `True` then other devices are allowed to connect to this peripheral. + :param bool anonymous: If `True` then this device's MAC address is randomized before advertising. + :param int timeout: If set, we will only advertise for this many seconds. + :param float interval: advertising interval, in seconds""" + ... + def stop_advertising(self) -> None: + """Stop sending advertising packets.""" + ... + def start_scan( + self, + prefixes: ReadableBuffer = b"", + *, + buffer_size: int = 512, + extended: bool = False, + timeout: Optional[float] = None, + interval: float = 0.1, + window: float = 0.1, + minimum_rssi: int = -80, + active: bool = True + ) -> Iterable[ScanEntry]: + """Starts a BLE scan and returns an iterator of results. Advertisements and scan responses are + filtered and returned separately. + + :param ~_typing.ReadableBuffer prefixes: Sequence of byte string prefixes to filter advertising packets + with. A packet without an advertising structure that matches one of the prefixes is + ignored. Format is one byte for length (n) and n bytes of prefix and can be repeated. + :param int buffer_size: the maximum number of advertising bytes to buffer. + :param bool extended: When True, support extended advertising packets. Increasing buffer_size is recommended when this is set. + :param float timeout: the scan timeout in seconds. If None, will scan until `stop_scan` is called. + :param float interval: the interval (in seconds) between the start of two consecutive scan windows + Must be in the range 0.0025 - 40.959375 seconds. + :param float window: the duration (in seconds) to scan a single BLE channel. + window must be <= interval. + :param int minimum_rssi: the minimum rssi of entries to return. + :param bool active: retrieve scan responses for scannable advertisements. + :returns: an iterable of `_bleio.ScanEntry` objects + :rtype: iterable""" + ... + def stop_scan(self) -> None: + """Stop the current scan.""" + ... + advertising: bool + """True when the adapter is currently advertising. (read-only)""" + + connected: bool + """True when the adapter is connected to another device regardless of who initiated the + connection. (read-only)""" + + connections: Tuple[Connection] + """Tuple of active connections including those initiated through + :py:meth:`_bleio.Adapter.connect`. (read-only)""" + def connect(self, address: Address, *, timeout: float) -> Connection: + """Attempts a connection to the device with the given address. + + :param Address address: The address of the peripheral to connect to + :param float/int timeout: Try to connect for timeout seconds.""" + ... + def erase_bonding(self) -> None: + """Erase all bonding information stored in flash memory.""" + ... + +class Address: + """Encapsulates the address of a BLE device.""" + + def __init__(self, address: ReadableBuffer, address_type: int) -> None: + """Create a new Address object encapsulating the address value. + The value itself can be one of: + + :param ~_typing.ReadableBuffer address: The address value to encapsulate. A buffer object (bytearray, bytes) of 6 bytes. + :param int address_type: one of the integer values: `PUBLIC`, `RANDOM_STATIC`, + `RANDOM_PRIVATE_RESOLVABLE`, or `RANDOM_PRIVATE_NON_RESOLVABLE`.""" + ... + address_bytes: bytes + """The bytes that make up the device address (read-only). + + Note that the ``bytes`` object returned is in little-endian order: + The least significant byte is ``address_bytes[0]``. So the address will + appear to be reversed if you print the raw ``bytes`` object. If you print + or use `str()` on the :py:class:`~_bleio.Attribute` object itself, the address will be printed + in the expected order. For example: + + .. code-block:: python + + >>> import _bleio + >>> _bleio.adapter.address +
+ >>> _bleio.adapter.address.address_bytes + b'5\\xa8\\xed\\xf5\\x1d\\xc8'""" + + type: int + """The address type (read-only). + + One of the integer values: `PUBLIC`, `RANDOM_STATIC`, `RANDOM_PRIVATE_RESOLVABLE`, + or `RANDOM_PRIVATE_NON_RESOLVABLE`.""" + def __eq__(self, other: object) -> bool: + """Two Address objects are equal if their addresses and address types are equal.""" + ... + def __hash__(self) -> int: + """Returns a hash for the Address data.""" + ... + PUBLIC: int + """A publicly known address, with a company ID (high 24 bits)and company-assigned part (low 24 bits).""" + + RANDOM_STATIC: int + """A randomly generated address that does not change often. It may never change or may change after + a power cycle.""" + + RANDOM_PRIVATE_RESOLVABLE: int + """An address that is usable when the peer knows the other device's secret Identity Resolving Key (IRK).""" + + RANDOM_PRIVATE_NON_RESOLVABLE: int + """A randomly generated address that changes on every connection.""" + +class Attribute: + """Definitions associated with all BLE attributes: characteristics, descriptors, etc. + + :py:class:`~_bleio.Attribute` is, notionally, a superclass of + :py:class:`~Characteristic` and :py:class:`~Descriptor`, + but is not defined as a Python superclass of those classes.""" + + def __init__(self) -> None: + """You cannot create an instance of :py:class:`~_bleio.Attribute`.""" + ... + NO_ACCESS: int + """security mode: access not allowed""" + + OPEN: int + """security_mode: no security (link is not encrypted)""" + + ENCRYPT_NO_MITM: int + """security_mode: unauthenticated encryption, without man-in-the-middle protection""" + + ENCRYPT_WITH_MITM: int + """security_mode: authenticated encryption, with man-in-the-middle protection""" + + LESC_ENCRYPT_WITH_MITM: int + """security_mode: LESC encryption, with man-in-the-middle protection""" + + SIGNED_NO_MITM: int + """security_mode: unauthenticated data signing, without man-in-the-middle protection""" + + SIGNED_WITH_MITM: int + """security_mode: authenticated data signing, without man-in-the-middle protection""" + +class Characteristic: + """Stores information about a BLE service characteristic and allows reading + and writing of the characteristic's value.""" + + def __init__(self) -> None: + """There is no regular constructor for a Characteristic. A new local Characteristic can be created + and attached to a Service by calling `add_to_service()`. + Remote Characteristic objects are created by `Connection.discover_remote_services()` + as part of remote Services.""" + ... + def add_to_service( + self, + service: Service, + uuid: UUID, + *, + properties: int = 0, + read_perm: int = Attribute.OPEN, + write_perm: int = Attribute.OPEN, + max_length: int = 20, + fixed_length: bool = False, + initial_value: Optional[ReadableBuffer] = None + ) -> Characteristic: + """Create a new Characteristic object, and add it to this Service. + + :param Service service: The service that will provide this characteristic + :param UUID uuid: The uuid of the characteristic + :param int properties: The properties of the characteristic, + specified as a bitmask of these values bitwise-or'd together: + `BROADCAST`, `INDICATE`, `NOTIFY`, `READ`, `WRITE`, `WRITE_NO_RESPONSE`. + :param int read_perm: Specifies whether the characteristic can be read by a client, and if so, which + security mode is required. Must be one of the integer values `Attribute.NO_ACCESS`, `Attribute.OPEN`, + `Attribute.ENCRYPT_NO_MITM`, `Attribute.ENCRYPT_WITH_MITM`, `Attribute.LESC_ENCRYPT_WITH_MITM`, + `Attribute.SIGNED_NO_MITM`, or `Attribute.SIGNED_WITH_MITM`. + :param int write_perm: Specifies whether the characteristic can be written by a client, and if so, which + security mode is required. Values allowed are the same as ``read_perm``. + :param int max_length: Maximum length in bytes of the characteristic value. The maximum allowed is + is 512, or possibly 510 if ``fixed_length`` is False. The default, 20, is the maximum + number of data bytes that fit in a single BLE 4.x ATT packet. + :param bool fixed_length: True if the characteristic value is of fixed length. + :param ~_typing.ReadableBuffer initial_value: The initial value for this characteristic. If not given, will be + filled with zeros. + + :return: the new Characteristic.""" + ... + properties: int + """An int bitmask representing which properties are set, specified as bitwise or'ing of + of these possible values. + `BROADCAST`, `INDICATE`, `NOTIFY`, `READ`, `WRITE`, `WRITE_NO_RESPONSE`.""" + + uuid: Optional[UUID] + """The UUID of this characteristic. (read-only) + + Will be ``None`` if the 128-bit UUID for this characteristic is not known.""" + + value: bytearray + """The value of this characteristic.""" + + descriptors: Descriptor + """A tuple of :py:class:`Descriptor` objects related to this characteristic. (read-only)""" + + service: Service + """The Service this Characteristic is a part of.""" + def set_cccd(self, *, notify: bool = False, indicate: bool = False) -> None: + """Set the remote characteristic's CCCD to enable or disable notification and indication. + + :param bool notify: True if Characteristic should receive notifications of remote writes + :param float indicate: True if Characteristic should receive indications of remote writes""" + ... + BROADCAST: int + """property: allowed in advertising packets""" + + INDICATE: int + """property: server will indicate to the client when the value is set and wait for a response""" + + NOTIFY: int + """property: server will notify the client when the value is set""" + + READ: int + """property: clients may read this characteristic""" + + WRITE: int + """property: clients may write this characteristic; a response will be sent back""" + + WRITE_NO_RESPONSE: int + """property: clients may write this characteristic; no response will be sent back""" + +class CharacteristicBuffer: + """Accumulates a Characteristic's incoming values in a FIFO buffer.""" + + def __init__( + self, characteristic: Characteristic, *, timeout: int = 1, buffer_size: int = 64 + ) -> None: + + """Monitor the given Characteristic. Each time a new value is written to the Characteristic + add the newly-written bytes to a FIFO buffer. + + :param Characteristic characteristic: The Characteristic to monitor. + It may be a local Characteristic provided by a Peripheral Service, or a remote Characteristic + in a remote Service that a Central has connected to. + :param int timeout: the timeout in seconds to wait for the first character and between subsequent characters. + :param int buffer_size: Size of ring buffer that stores incoming data coming from client. + Must be >= 1.""" + ... + def read(self, nbytes: Optional[int] = None) -> Optional[bytes]: + """Read characters. If ``nbytes`` is specified then read at most that many + bytes. Otherwise, read everything that arrives until the connection + times out. Providing the number of bytes expected is highly recommended + because it will be faster. + + :return: Data read + :rtype: bytes or None""" + ... + def readinto(self, buf: WriteableBuffer) -> Optional[int]: + """Read bytes into the ``buf``. Read at most ``len(buf)`` bytes. + + :return: number of bytes read and stored into ``buf`` + :rtype: int or None (on a non-blocking error)""" + ... + def readline(self) -> bytes: + """Read a line, ending in a newline character. + + :return: the line read + :rtype: int or None""" + ... + in_waiting: int + """The number of bytes in the input buffer, available to be read""" + def reset_input_buffer(self) -> None: + """Discard any unread characters in the input buffer.""" + ... + def deinit(self) -> None: + """Disable permanently.""" + ... + +class Connection: + """A BLE connection to another device. Used to discover and interact with services on the other + device. + + Usage:: + + import _bleio + + my_entry = None + for entry in _bleio.adapter.scan(2.5): + if entry.name is not None and entry.name == 'InterestingPeripheral': + my_entry = entry + break + + if not my_entry: + raise Exception("'InterestingPeripheral' not found") + + connection = _bleio.adapter.connect(my_entry.address, timeout=10)""" + + def __init__(self) -> None: + """Connections cannot be made directly. Instead, to initiate a connection use `Adapter.connect`. + Connections may also be made when another device initiates a connection. To use a Connection + created by a peer, read the `Adapter.connections` property.""" + ... + def disconnect(self) -> None: + """Disconnects from the remote peripheral. Does nothing if already disconnected.""" + ... + def pair(self, *, bond: bool = True) -> None: + """Pair to the peer to improve security.""" + ... + def discover_remote_services( + self, service_uuids_whitelist: Optional[Iterable[UUID]] = None + ) -> Tuple[Service, ...]: + """Do BLE discovery for all services or for the given service UUIDS, + to find their handles and characteristics, and return the discovered services. + `Connection.connected` must be True. + + :param iterable service_uuids_whitelist: + + an iterable of :py:class:`UUID` objects for the services provided by the peripheral + that you want to use. + + The peripheral may provide more services, but services not listed are ignored + and will not be returned. + + If service_uuids_whitelist is None, then all services will undergo discovery, which can be + slow. + + If the service UUID is 128-bit, or its characteristic UUID's are 128-bit, you + you must have already created a :py:class:`UUID` object for that UUID in order for the + service or characteristic to be discovered. Creating the UUID causes the UUID to be + registered for use. (This restriction may be lifted in the future.) + + :return: A tuple of `_bleio.Service` objects provided by the remote peripheral.""" + ... + connected: bool + """True if connected to the remote peer.""" + + paired: bool + """True if paired to the remote peer.""" + + connection_interval: float + """Time between transmissions in milliseconds. Will be multiple of 1.25ms. Lower numbers + increase speed and decrease latency but increase power consumption. + + When setting connection_interval, the peer may reject the new interval and + `connection_interval` will then remain the same. + + Apple has additional guidelines that dictate should be a multiple of 15ms except if HID is + available. When HID is available Apple devices may accept 11.25ms intervals.""" + + attribute: int + """The maximum number of data bytes that can be sent in a single transmission, + not including overhead bytes. + + This is the maximum number of bytes that can be sent in a notification, + which must be sent in a single packet. + But for a regular characteristic read or write, may be sent in multiple packets, + so this limit does not apply.""" + +class Descriptor: + """Stores information about a BLE descriptor. + + Descriptors are attached to BLE characteristics and provide contextual + information about the characteristic.""" + + def __init__(self) -> None: + """There is no regular constructor for a Descriptor. A new local Descriptor can be created + and attached to a Characteristic by calling `add_to_characteristic()`. + Remote Descriptor objects are created by `Connection.discover_remote_services()` + as part of remote Characteristics in the remote Services that are discovered.""" + @classmethod + def add_to_characteristic( + cls, + characteristic: Characteristic, + uuid: UUID, + *, + read_perm: int = Attribute.OPEN, + write_perm: int = Attribute.OPEN, + max_length: int = 20, + fixed_length: bool = False, + initial_value: ReadableBuffer = b"" + ) -> Descriptor: + """Create a new Descriptor object, and add it to this Service. + + :param Characteristic characteristic: The characteristic that will hold this descriptor + :param UUID uuid: The uuid of the descriptor + :param int read_perm: Specifies whether the descriptor can be read by a client, and if so, which + security mode is required. Must be one of the integer values `Attribute.NO_ACCESS`, `Attribute.OPEN`, + `Attribute.ENCRYPT_NO_MITM`, `Attribute.ENCRYPT_WITH_MITM`, `Attribute.LESC_ENCRYPT_WITH_MITM`, + `Attribute.SIGNED_NO_MITM`, or `Attribute.SIGNED_WITH_MITM`. + :param int write_perm: Specifies whether the descriptor can be written by a client, and if so, which + security mode is required. Values allowed are the same as ``read_perm``. + :param int max_length: Maximum length in bytes of the descriptor value. The maximum allowed is + is 512, or possibly 510 if ``fixed_length`` is False. The default, 20, is the maximum + number of data bytes that fit in a single BLE 4.x ATT packet. + :param bool fixed_length: True if the descriptor value is of fixed length. + :param ~_typing.ReadableBuffer initial_value: The initial value for this descriptor. + + :return: the new Descriptor.""" + ... + uuid: UUID + """The descriptor uuid. (read-only)""" + + characteristic: Characteristic + """The Characteristic this Descriptor is a part of.""" + + value: bytearray + """The value of this descriptor.""" + +class PacketBuffer: + """Accumulates a Characteristic's incoming packets in a FIFO buffer and facilitates packet aware + outgoing writes. A packet's size is either the characteristic length or the maximum transmission + unit (MTU) minus overhead, whichever is smaller. The MTU can change so check `incoming_packet_length` + and `outgoing_packet_length` before creating a buffer to store data. + + When we're the server, we ignore all connections besides the first to subscribe to + notifications.""" + + def __init__(self, characteristic: Characteristic, *, buffer_size: int) -> None: + """Monitor the given Characteristic. Each time a new value is written to the Characteristic + add the newly-written bytes to a FIFO buffer. + + Monitor the given Characteristic. Each time a new value is written to the Characteristic + add the newly-written packet of bytes to a FIFO buffer. + + :param Characteristic characteristic: The Characteristic to monitor. + It may be a local Characteristic provided by a Peripheral Service, or a remote Characteristic + in a remote Service that a Central has connected to. + :param int buffer_size: Size of ring buffer (in packets of the Characteristic's maximum + length) that stores incoming packets coming from the peer.""" + ... + def readinto(self, buf: WriteableBuffer) -> int: + """Reads a single BLE packet into the ``buf``. Raises an exception if the next packet is longer + than the given buffer. Use `packet_size` to read the maximum length of a single packet. + + :return: number of bytes read and stored into ``buf`` + :rtype: int""" + ... + def write(self, data: ReadableBuffer, *, header: Optional[bytes] = None) -> int: + """Writes all bytes from data into the same outgoing packet. The bytes from header are included + before data when the pending packet is currently empty. + + This does not block until the data is sent. It only blocks until the data is pending. + + :return: number of bytes written. May include header bytes when packet is empty. + :rtype: int""" + ... + def deinit(self) -> None: + """Disable permanently.""" + ... + packet_size: int + """`packet_size` is the same as `incoming_packet_length`. + The name `packet_size` is deprecated and + will be removed in CircuitPython 6.0.0.""" + + incoming_packet_length: int + """Maximum length in bytes of a packet we are reading.""" + + outgoing_packet_length: int + """Maximum length in bytes of a packet we are writing.""" + +class ScanEntry: + """Encapsulates information about a device that was received during scanning. It can be + advertisement or scan response data. This object may only be created by a `_bleio.ScanResults`: + it has no user-visible constructor.""" + + def __init__(self) -> None: + """Cannot be instantiated directly. Use `_bleio.Adapter.start_scan`.""" + ... + def matches(self, prefixes: ScanEntry, *, all: bool = True) -> bool: + """Returns True if the ScanEntry matches all prefixes when ``all`` is True. This is stricter + than the scan filtering which accepts any advertisements that match any of the prefixes + where all is False.""" + ... + address: Address + """The address of the device (read-only), of type `_bleio.Address`.""" + + advertisement_bytes: bytes + """All the advertisement data present in the packet, returned as a ``bytes`` object. (read-only)""" + + rssi: int + """The signal strength of the device at the time of the scan, in integer dBm. (read-only)""" + + connectable: bool + """True if the device can be connected to. (read-only)""" + + scan_response: bool + """True if the entry was a scan response. (read-only)""" + +class ScanResults: + """Iterates over advertising data received while scanning. This object is always created + by a `_bleio.Adapter`: it has no user-visible constructor.""" + + def __init__(self) -> None: + """Cannot be instantiated directly. Use `_bleio.Adapter.start_scan`.""" + ... + def __iter__(self) -> Iterator[ScanEntry]: + """Returns itself since it is the iterator.""" + ... + def __next__(self) -> ScanEntry: + """Returns the next `_bleio.ScanEntry`. Blocks if none have been received and scanning is still + active. Raises `StopIteration` if scanning is finished and no other results are available.""" + ... + +class Service: + """Stores information about a BLE service and its characteristics.""" + + def __init__(self, uuid: UUID, *, secondary: bool = False) -> None: + """Create a new Service identified by the specified UUID. It can be accessed by all + connections. This is known as a Service server. Client Service objects are created via + `Connection.discover_remote_services`. + + To mark the Service as secondary, pass `True` as :py:data:`secondary`. + + :param UUID uuid: The uuid of the service + :param bool secondary: If the service is a secondary one + + :return: the new Service""" + ... + characteristics: Tuple[Characteristic, ...] + """A tuple of :py:class:`Characteristic` designating the characteristics that are offered by + this service. (read-only)""" + + remote: bool + """True if this is a service provided by a remote device. (read-only)""" + + secondary: bool + """True if this is a secondary service. (read-only)""" + + uuid: Optional[UUID] + """The UUID of this service. (read-only) + + Will be ``None`` if the 128-bit UUID for this service is not known.""" + +class UUID: + """A 16-bit or 128-bit UUID. Can be used for services, characteristics, descriptors and more.""" + + def __init__(self, value: Union[int, ReadableBuffer, str]) -> None: + """Create a new UUID or UUID object encapsulating the uuid value. + The value can be one of: + + - an `int` value in range 0 to 0xFFFF (Bluetooth SIG 16-bit UUID) + - a buffer object (bytearray, bytes) of 16 bytes in little-endian order (128-bit UUID) + - a string of hex digits of the form 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + + Creating a 128-bit UUID registers the UUID with the onboard BLE software, and provides a + temporary 16-bit UUID that can be used in place of the full 128-bit UUID. + + :param value: The uuid value to encapsulate + :type value: int, ~_typing.ReadableBuffer or str""" + ... + uuid16: int + """The 16-bit part of the UUID. (read-only) + + :type: int""" + + uuid128: bytes + """The 128-bit value of the UUID + Raises AttributeError if this is a 16-bit UUID. (read-only) + + :type: bytes""" + + size: int + """128 if this UUID represents a 128-bit vendor-specific UUID. 16 if this UUID represents a + 16-bit Bluetooth SIG assigned UUID. (read-only) 32-bit UUIDs are not currently supported. + + :type: int""" + def pack_into(self, buffer: WriteableBuffer, offset: int = 0) -> None: + """Packs the UUID into the given buffer at the given offset.""" + ... + def __eq__(self, other: object) -> bool: + """Two UUID objects are equal if their values match and they are both 128-bit or both 16-bit.""" + ... diff --git a/stubs/_eve/__init__.pyi b/stubs/_eve/__init__.pyi new file mode 100644 index 0000000..a083ea1 --- /dev/null +++ b/stubs/_eve/__init__.pyi @@ -0,0 +1,391 @@ +"""Low-level BridgeTek EVE bindings + +The `_eve` module provides a class _EVE which +contains methods for constructing EVE command +buffers and appending basic graphics commands.""" + +from __future__ import annotations + +from typing import Tuple + +from _typing import ReadableBuffer + +class _EVE: + def register(self, o: object) -> None: ... + def flush(self) -> None: + """Send any queued drawing commands directly to the hardware. + + :param int width: The width of the grid in tiles, or 1 for sprites.""" + ... + def cc(self, b: ReadableBuffer) -> None: + """Append bytes to the command FIFO. + + :param ~_typing.ReadableBuffer b: The bytes to add""" + ... + def AlphaFunc(self, func: int, ref: int) -> None: + """Set the alpha test function + + :param int func: specifies the test function, one of ``NEVER``, ``LESS``, ``LEQUAL``, ``GREATER``, ``GEQUAL``, ``EQUAL``, ``NOTEQUAL``, or ``ALWAYS``. Range 0-7. The initial value is ALWAYS(7) + :param int ref: specifies the reference value for the alpha test. Range 0-255. The initial value is 0 + + These values are part of the graphics context and are saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def Begin(self, prim: int) -> None: + """Begin drawing a graphics primitive + + :param int prim: graphics primitive. + + Valid primitives are ``BITMAPS``, ``POINTS``, ``LINES``, ``LINE_STRIP``, ``EDGE_STRIP_R``, ``EDGE_STRIP_L``, ``EDGE_STRIP_A``, ``EDGE_STRIP_B`` and ``RECTS``.""" + ... + def BitmapExtFormat(self, format: int) -> None: + """Set the bitmap format + + :param int format: bitmap pixel format.""" + ... + def BitmapHandle(self, handle: int) -> None: + """Set the bitmap handle + + :param int handle: bitmap handle. Range 0-31. The initial value is 0 + + This value is part of the graphics context and is saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def BitmapLayoutH(self, linestride: int, height: int) -> None: + """Set the source bitmap memory format and layout for the current handle. high bits for large bitmaps + + :param int linestride: high part of bitmap line stride, in bytes. Range 0-7 + :param int height: high part of bitmap height, in lines. Range 0-3""" + ... + def BitmapLayout(self, format: int, linestride: int, height: int) -> None: + """Set the source bitmap memory format and layout for the current handle + + :param int format: bitmap pixel format, or GLFORMAT to use BITMAP_EXT_FORMAT instead. Range 0-31 + :param int linestride: bitmap line stride, in bytes. Range 0-1023 + :param int height: bitmap height, in lines. Range 0-511""" + ... + def BitmapSizeH(self, width: int, height: int) -> None: + """Set the screen drawing of bitmaps for the current handle. high bits for large bitmaps + + :param int width: high part of drawn bitmap width, in pixels. Range 0-3 + :param int height: high part of drawn bitmap height, in pixels. Range 0-3""" + ... + def BitmapSize( + self, filter: int, wrapx: int, wrapy: int, width: int, height: int + ) -> None: + """Set the screen drawing of bitmaps for the current handle + + :param int filter: bitmap filtering mode, one of ``NEAREST`` or ``BILINEAR``. Range 0-1 + :param int wrapx: bitmap :math:`x` wrap mode, one of ``REPEAT`` or ``BORDER``. Range 0-1 + :param int wrapy: bitmap :math:`y` wrap mode, one of ``REPEAT`` or ``BORDER``. Range 0-1 + :param int width: drawn bitmap width, in pixels. Range 0-511 + :param int height: drawn bitmap height, in pixels. Range 0-511""" + ... + def BitmapSource(self, addr: int) -> None: + """Set the source address for bitmap graphics + + :param int addr: Bitmap start address, pixel-aligned. May be in SRAM or flash. Range 0-16777215""" + ... + def BitmapSwizzle(self, r: int, g: int, b: int, a: int) -> None: + """Set the source for the r,g,b and a channels of a bitmap + + :param int r: red component source channel. Range 0-7 + :param int g: green component source channel. Range 0-7 + :param int b: blue component source channel. Range 0-7 + :param int a: alpha component source channel. Range 0-7""" + ... + def BitmapTransformA(self, p: int, v: int) -> None: + """Set the :math:`a` component of the bitmap transform matrix + + :param int p: precision control: 0 is 8.8, 1 is 1.15. Range 0-1. The initial value is 0 + :param int v: The :math:`a` component of the bitmap transform matrix, in signed 8.8 or 1.15 bit fixed-point form. Range 0-131071. The initial value is 256 + + The initial value is **p** = 0, **v** = 256. This represents the value 1.0. + + These values are part of the graphics context and are saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def BitmapTransformB(self, p: int, v: int) -> None: + """Set the :math:`b` component of the bitmap transform matrix + + :param int p: precision control: 0 is 8.8, 1 is 1.15. Range 0-1. The initial value is 0 + :param int v: The :math:`b` component of the bitmap transform matrix, in signed 8.8 or 1.15 bit fixed-point form. Range 0-131071. The initial value is 0 + + The initial value is **p** = 0, **v** = 0. This represents the value 0.0. + + These values are part of the graphics context and are saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def BitmapTransformC(self, v: int) -> None: + """Set the :math:`c` component of the bitmap transform matrix + + :param int v: The :math:`c` component of the bitmap transform matrix, in signed 15.8 bit fixed-point form. Range 0-16777215. The initial value is 0 + + This value is part of the graphics context and is saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def BitmapTransformD(self, p: int, v: int) -> None: + """Set the :math:`d` component of the bitmap transform matrix + + :param int p: precision control: 0 is 8.8, 1 is 1.15. Range 0-1. The initial value is 0 + :param int v: The :math:`d` component of the bitmap transform matrix, in signed 8.8 or 1.15 bit fixed-point form. Range 0-131071. The initial value is 0 + + The initial value is **p** = 0, **v** = 0. This represents the value 0.0. + + These values are part of the graphics context and are saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def BitmapTransformE(self, p: int, v: int) -> None: + """Set the :math:`e` component of the bitmap transform matrix + + :param int p: precision control: 0 is 8.8, 1 is 1.15. Range 0-1. The initial value is 0 + :param int v: The :math:`e` component of the bitmap transform matrix, in signed 8.8 or 1.15 bit fixed-point form. Range 0-131071. The initial value is 256 + + The initial value is **p** = 0, **v** = 256. This represents the value 1.0. + + These values are part of the graphics context and are saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def BitmapTransformF(self, v: int) -> None: + """Set the :math:`f` component of the bitmap transform matrix + + :param int v: The :math:`f` component of the bitmap transform matrix, in signed 15.8 bit fixed-point form. Range 0-16777215. The initial value is 0 + + This value is part of the graphics context and is saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def BlendFunc(self, src: int, dst: int) -> None: + """Set pixel arithmetic + + :param int src: specifies how the source blending factor is computed. One of ``ZERO``, ``ONE``, ``SRC_ALPHA``, ``DST_ALPHA``, ``ONE_MINUS_SRC_ALPHA`` or ``ONE_MINUS_DST_ALPHA``. Range 0-7. The initial value is SRC_ALPHA(2) + :param int dst: specifies how the destination blending factor is computed, one of the same constants as **src**. Range 0-7. The initial value is ONE_MINUS_SRC_ALPHA(4) + + These values are part of the graphics context and are saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def Call(self, dest: int) -> None: + """Execute a sequence of commands at another location in the display list + + :param int dest: display list address. Range 0-65535""" + ... + def Cell(self, cell: int) -> None: + """Set the bitmap cell number for the vertex2f command + + :param int cell: bitmap cell number. Range 0-127. The initial value is 0 + + This value is part of the graphics context and is saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def ClearColorA(self, alpha: int) -> None: + """Set clear value for the alpha channel + + :param int alpha: alpha value used when the color buffer is cleared. Range 0-255. The initial value is 0 + + This value is part of the graphics context and is saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def ClearColorRGB(self, red: int, green: int, blue: int) -> None: + """Set clear values for red, green and blue channels + + :param int red: red value used when the color buffer is cleared. Range 0-255. The initial value is 0 + :param int green: green value used when the color buffer is cleared. Range 0-255. The initial value is 0 + :param int blue: blue value used when the color buffer is cleared. Range 0-255. The initial value is 0 + + These values are part of the graphics context and are saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def Clear(self, c: int, s: int, t: int) -> None: + """Clear buffers to preset values + + :param int c: clear color buffer. Range 0-1 + :param int s: clear stencil buffer. Range 0-1 + :param int t: clear tag buffer. Range 0-1""" + ... + def ClearStencil(self, s: int) -> None: + """Set clear value for the stencil buffer + + :param int s: value used when the stencil buffer is cleared. Range 0-255. The initial value is 0 + + This value is part of the graphics context and is saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def ClearTag(self, s: int) -> None: + """Set clear value for the tag buffer + + :param int s: value used when the tag buffer is cleared. Range 0-255. The initial value is 0 + + This value is part of the graphics context and is saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + def ColorA(self, alpha: int) -> None: + """Set the current color alpha + + :param int alpha: alpha for the current color. Range 0-255. The initial value is 255 + + This value is part of the graphics context and is saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def ColorMask(self, r: int, g: int, b: int, a: int) -> None: + """Enable and disable writing of frame buffer color components + + :param int r: allow updates to the frame buffer red component. Range 0-1. The initial value is 1 + :param int g: allow updates to the frame buffer green component. Range 0-1. The initial value is 1 + :param int b: allow updates to the frame buffer blue component. Range 0-1. The initial value is 1 + :param int a: allow updates to the frame buffer alpha component. Range 0-1. The initial value is 1 + + These values are part of the graphics context and are saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def ColorRGB(self, red: int, green: int, blue: int) -> None: + """Set the drawing color + + :param int red: red value for the current color. Range 0-255. The initial value is 255 + :param int green: green for the current color. Range 0-255. The initial value is 255 + :param int blue: blue for the current color. Range 0-255. The initial value is 255 + + These values are part of the graphics context and are saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def Display(self) -> None: + """End the display list""" + ... + def End(self) -> None: + """End drawing a graphics primitive + + :meth:`Vertex2ii` and :meth:`Vertex2f` calls are ignored until the next :meth:`Begin`.""" + ... + def Jump(self, dest: int) -> None: + """Execute commands at another location in the display list + + :param int dest: display list address. Range 0-65535""" + ... + def LineWidth(self, width: int) -> None: + """Set the width of rasterized lines + + :param int width: line width in :math:`1/16` pixel. Range 0-4095. The initial value is 16 + + This value is part of the graphics context and is saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def Macro(self, m: int) -> None: + """Execute a single command from a macro register + + :param int m: macro register to read. Range 0-1""" + ... + def Nop(self) -> None: + """No operation""" + ... + def PaletteSource(self, addr: int) -> None: + """Set the base address of the palette + + :param int addr: Address in graphics SRAM, 2-byte aligned. Range 0-4194303. The initial value is 0 + + This value is part of the graphics context and is saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def PointSize(self, size: int) -> None: + """Set the radius of rasterized points + + :param int size: point radius in :math:`1/16` pixel. Range 0-8191. The initial value is 16 + + This value is part of the graphics context and is saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def RestoreContext(self) -> None: + """Restore the current graphics context from the context stack""" + ... + def Return(self) -> None: + """Return from a previous call command""" + ... + def SaveContext(self) -> None: + """Push the current graphics context on the context stack""" + ... + def ScissorSize(self, width: int, height: int) -> None: + """Set the size of the scissor clip rectangle + + :param int width: The width of the scissor clip rectangle, in pixels. Range 0-4095. The initial value is hsize + :param int height: The height of the scissor clip rectangle, in pixels. Range 0-4095. The initial value is 2048 + + These values are part of the graphics context and are saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def ScissorXY(self, x: int, y: int) -> None: + """Set the top left corner of the scissor clip rectangle + + :param int x: The :math:`x` coordinate of the scissor clip rectangle, in pixels. Range 0-2047. The initial value is 0 + :param int y: The :math:`y` coordinate of the scissor clip rectangle, in pixels. Range 0-2047. The initial value is 0 + + These values are part of the graphics context and are saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def StencilFunc(self, func: int, ref: int, mask: int) -> None: + """Set function and reference value for stencil testing + + :param int func: specifies the test function, one of ``NEVER``, ``LESS``, ``LEQUAL``, ``GREATER``, ``GEQUAL``, ``EQUAL``, ``NOTEQUAL``, or ``ALWAYS``. Range 0-7. The initial value is ALWAYS(7) + :param int ref: specifies the reference value for the stencil test. Range 0-255. The initial value is 0 + :param int mask: specifies a mask that is ANDed with the reference value and the stored stencil value. Range 0-255. The initial value is 255 + + These values are part of the graphics context and are saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def StencilMask(self, mask: int) -> None: + """Control the writing of individual bits in the stencil planes + + :param int mask: the mask used to enable writing stencil bits. Range 0-255. The initial value is 255 + + This value is part of the graphics context and is saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def StencilOp(self, sfail: int, spass: int) -> None: + """Set stencil test actions + + :param int sfail: specifies the action to take when the stencil test fails, one of ``KEEP``, ``ZERO``, ``REPLACE``, ``INCR``, ``INCR_WRAP``, ``DECR``, ``DECR_WRAP``, and ``INVERT``. Range 0-7. The initial value is KEEP(1) + :param int spass: specifies the action to take when the stencil test passes, one of the same constants as **sfail**. Range 0-7. The initial value is KEEP(1) + + These values are part of the graphics context and are saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def TagMask(self, mask: int) -> None: + """Control the writing of the tag buffer + + :param int mask: allow updates to the tag buffer. Range 0-1. The initial value is 1 + + This value is part of the graphics context and is saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def Tag(self, s: int) -> None: + """Set the current tag value + + :param int s: tag value. Range 0-255. The initial value is 255 + + This value is part of the graphics context and is saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def VertexTranslateX(self, x: int) -> None: + """Set the vertex transformation's x translation component + + :param int x: signed x-coordinate in :math:`1/16` pixel. Range 0-131071. The initial value is 0 + + This value is part of the graphics context and is saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def VertexTranslateY(self, y: int) -> None: + """Set the vertex transformation's y translation component + + :param int y: signed y-coordinate in :math:`1/16` pixel. Range 0-131071. The initial value is 0 + + This value is part of the graphics context and is saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def VertexFormat(self, frac: int) -> None: + """Set the precision of vertex2f coordinates + + :param int frac: Number of fractional bits in X,Y coordinates, 0-4. Range 0-7. The initial value is 4 + + This value is part of the graphics context and is saved and restored by :meth:`SaveContext` and :meth:`RestoreContext`.""" + ... + def Vertex2ii(self, x: int, y: int, handle: int, cell: int) -> None: + """:param int x: x-coordinate in pixels. Range 0-511 + :param int y: y-coordinate in pixels. Range 0-511 + :param int handle: bitmap handle. Range 0-31 + :param int cell: cell number. Range 0-127 + + This method is an alternative to :meth:`Vertex2f`.""" + ... + def Vertex2f(self, b: float) -> None: + """Draw a point. + + :param float x: pixel x-coordinate + :param float y: pixel y-coordinate""" + ... + def cmd0(self, n: int) -> None: + """Append the command word n to the FIFO + + :param int n: The command code + + This method is used by the ``eve`` module to efficiently add + commands to the FIFO.""" + ... + def cmd(self, n: int, fmt: str, args: Tuple[str, ...]) -> None: + """Append a command packet to the FIFO. + + :param int n: The command code + :param str fmt: The command format `struct` layout + :param args: The command's arguments + :type args: tuple(str, ...) + + Supported format codes: h, H, i, I. + + This method is used by the ``eve`` module to efficiently add + commands to the FIFO.""" + ... diff --git a/stubs/_pew/__init__.pyi b/stubs/_pew/__init__.pyi new file mode 100644 index 0000000..03f3e0b --- /dev/null +++ b/stubs/_pew/__init__.pyi @@ -0,0 +1,36 @@ +"""LED matrix driver""" + +from __future__ import annotations + +from typing import List + +import digitalio +from _typing import ReadableBuffer + +class PewPew: + """This is an internal module to be used by the ``pew.py`` library from + https://github.com/pewpew-game/pew-pewpew-standalone-10.x to handle the + LED matrix display and buttons on the ``pewpew10`` board. + + Usage:: + + This singleton class is instantiated by the ``pew`` library, and + used internally by it. All user-visible interactions are done through + that library.""" + + def __init__( + self, + buffer: ReadableBuffer, + rows: List[digitalio.DigitalInOut], + cols: List[digitalio.DigitalInOut], + buttons: digitalio.DigitalInOut, + ) -> None: + """Initializes matrix scanning routines. + + The ``buffer`` is a 64 byte long ``bytearray`` that stores what should + be displayed on the matrix. ``rows`` and ``cols`` are both lists of + eight ``DigitalInputOutput`` objects that are connected to the matrix + rows and columns. ``buttons`` is a ``DigitalInputOutput`` object that + is connected to the common side of all buttons (the other sides of the + buttons are connected to rows of the matrix).""" + ... diff --git a/stubs/_pixelbuf/__init__.pyi b/stubs/_pixelbuf/__init__.pyi new file mode 100644 index 0000000..83a1089 --- /dev/null +++ b/stubs/_pixelbuf/__init__.pyi @@ -0,0 +1,108 @@ +"""A fast RGB(W) pixel buffer library for like NeoPixel and DotStar + +The `_pixelbuf` module provides the :py:class:`PixelBuf` class to accelerate +RGB(W) strip/matrix manipulation, such as DotStar and Neopixel. + +Byteorders are configured with strings, such as "RGB" or "RGBD".""" + +from __future__ import annotations + +from typing import List, Tuple, Union, overload + +from _typing import ReadableBuffer + +def colorwheel(n: float) -> int: + """C implementation of the common wheel() function found in many examples. + Returns the colorwheel RGB value as an integer value for n (usable in :py:class:`PixelBuf`, neopixel, and dotstar).""" + ... + +def wheel(n: float) -> int: + """Use of wheel() is deprecated. Please use colorwheel().""" + +class PixelBuf: + """A fast RGB[W] pixel buffer for LED and similar devices.""" + + def __init__( + self, + size: int, + *, + byteorder: str = "BGR", + brightness: float = 0, + auto_write: bool = False, + header: ReadableBuffer = b"", + trailer: ReadableBuffer = b"" + ) -> None: + """Create a PixelBuf object of the specified size, byteorder, and bits per pixel. + + When brightness is less than 1.0, a second buffer will be used to store the color values + before they are adjusted for brightness. + + When ``P`` (PWM duration) is present as the 4th character of the byteorder + string, the 4th value in the tuple/list for a pixel is the individual pixel + brightness (0.0-1.0) and will enable a Dotstar compatible 1st byte for each + pixel. + + :param int size: Number of pixels + :param str byteorder: Byte order string (such as "RGB", "RGBW" or "PBGR") + :param float brightness: Brightness (0 to 1.0, default 1.0) + :param bool auto_write: Whether to automatically write pixels (Default False) + :param ~_typing.ReadableBuffer header: Sequence of bytes to always send before pixel values. + :param ~_typing.ReadableBuffer trailer: Sequence of bytes to always send after pixel values.""" + ... + bpp: int + """The number of bytes per pixel in the buffer (read-only)""" + + brightness: float + """Float value between 0 and 1. Output brightness. + + When brightness is less than 1.0, a second buffer will be used to store the color values + before they are adjusted for brightness.""" + + auto_write: bool + """Whether to automatically write the pixels after each update.""" + + byteorder: str + """byteorder string for the buffer (read-only)""" + def show(self) -> None: + """Transmits the color data to the pixels so that they are shown. This is done automatically + when `auto_write` is True.""" + ... + def fill( + self, color: Union[int, Tuple[int, int, int], Tuple[int, int, int, float]] + ) -> None: + """Fills the given pixelbuf with the given color.""" + ... + @overload + def __getitem__( + self, index: slice + ) -> Union[ + Tuple[Tuple[int, int, int], ...], Tuple[Tuple[int, int, int, float], ...] + ]: ... + @overload + def __getitem__( + self, index: int + ) -> Union[Tuple[int, int, int], Tuple[int, int, int, float]]: + """Returns the pixel value at the given index as a tuple of (Red, Green, Blue[, White]) values + between 0 and 255. When in PWM (DotStar) mode, the 4th tuple value is a float of the pixel + intensity from 0-1.0.""" + ... + @overload + def __setitem__( + self, + index: slice, + value: Tuple[Union[int, Tuple[float, ...], List[float]], ...], + ) -> None: ... + @overload + def __setitem__( + self, index: slice, value: List[Union[int, Tuple[float, ...], List[float]]] + ) -> None: ... + @overload + def __setitem__( + self, index: int, value: Union[int, Tuple[float, ...], List[float]] + ) -> None: + """Sets the pixel value at the given index. Value can either be a tuple or integer. Tuples are + The individual (Red, Green, Blue[, White]) values between 0 and 255. If given an integer, the + red, green and blue values are packed into the lower three bytes (0xRRGGBB). + For RGBW byteorders, if given only RGB values either as an int or as a tuple, the white value + is used instead when the red, green, and blue values are the same.""" + ... diff --git a/stubs/_stage/__init__.pyi b/stubs/_stage/__init__.pyi new file mode 100644 index 0000000..31fb514 --- /dev/null +++ b/stubs/_stage/__init__.pyi @@ -0,0 +1,102 @@ +"""C-level helpers for animation of sprites on a stage + +The `_stage` module contains native code to speed-up the ```stage`` Library +`_.""" + +from __future__ import annotations + +from typing import List + +import displayio +from _typing import ReadableBuffer, WriteableBuffer + +def render( + x0: int, + y0: int, + x1: int, + y1: int, + layers: List[Layer], + buffer: WriteableBuffer, + display: displayio.Display, + scale: int, + background: int, +) -> None: + """Render and send to the display a fragment of the screen. + + :param int x0: Left edge of the fragment. + :param int y0: Top edge of the fragment. + :param int x1: Right edge of the fragment. + :param int y1: Bottom edge of the fragment. + :param layers: A list of the :py:class:`~_stage.Layer` objects. + :type layers: list[Layer] + :param ~_typing.WriteableBuffer buffer: A buffer to use for rendering. + :param ~displayio.Display display: The display to use. + :param int scale: How many times should the image be scaled up. + :param int background: What color to display when nothing is there. + + There are also no sanity checks, outside of the basic overflow + checking. The caller is responsible for making the passed parameters + valid. + + This function is intended for internal use in the ``stage`` library + and all the necessary checks are performed there.""" + +class Layer: + """Keep information about a single layer of graphics""" + + def __init__( + self, + width: int, + height: int, + graphic: ReadableBuffer, + palette: ReadableBuffer, + grid: ReadableBuffer, + ) -> None: + """Keep internal information about a layer of graphics (either a + ``Grid`` or a ``Sprite``) in a format suitable for fast rendering + with the ``render()`` function. + + :param int width: The width of the grid in tiles, or 1 for sprites. + :param int height: The height of the grid in tiles, or 1 for sprites. + :param ~_typing.ReadableBuffer graphic: The graphic data of the tiles. + :param ~_typing.ReadableBuffer palette: The color palette to be used. + :param ~_typing.ReadableBuffer grid: The contents of the grid map. + + This class is intended for internal use in the ``stage`` library and + it shouldn't be used on its own.""" + ... + def move(self, x: int, y: int) -> None: + """Set the offset of the layer to the specified values.""" + ... + def frame(self, frame: int, rotation: int) -> None: + """Set the animation frame of the sprite, and optionally rotation its + graphic.""" + ... + +class Text: + """Keep information about a single grid of text""" + + def __init__( + self, + width: int, + height: int, + font: ReadableBuffer, + palette: ReadableBuffer, + chars: ReadableBuffer, + ) -> None: + """Keep internal information about a grid of text + in a format suitable for fast rendering + with the ``render()`` function. + + :param int width: The width of the grid in tiles, or 1 for sprites. + :param int height: The height of the grid in tiles, or 1 for sprites. + :param ~_typing.ReadableBuffer font: The font data of the characters. + :param ~_typing.ReadableBuffer palette: The color palette to be used. + :param ~_typing.ReadableBuffer chars: The contents of the character grid. + + This class is intended for internal use in the ``stage`` library and + it shouldn't be used on its own.""" + ... + def move(self, x: int, y: int) -> None: + """Set the offset of the text to the specified values.""" + ... diff --git a/stubs/_typing/__init__.pyi b/stubs/_typing/__init__.pyi new file mode 100644 index 0000000..120c4f4 --- /dev/null +++ b/stubs/_typing/__init__.pyi @@ -0,0 +1,68 @@ +"""Types for the C-level protocols""" + +from __future__ import annotations + +import array +from typing import Union + +import alarm +import alarm.pin +import alarm.time +import audiocore +import audiomixer +import audiomp3 +import rgbmatrix +import ulab + +ReadableBuffer = Union[ + bytes, bytearray, memoryview, array.array, ulab.array, rgbmatrix.RGBMatrix +] +"""Classes that implement the readable buffer protocol + + - `bytes` + - `bytearray` + - `memoryview` + - `array.array` + - `ulab.array` + - `rgbmatrix.RGBMatrix` +""" + +WriteableBuffer = Union[ + bytearray, memoryview, array.array, ulab.array, rgbmatrix.RGBMatrix +] +"""Classes that implement the writeable buffer protocol + + - `bytearray` + - `memoryview` + - `array.array` + - `ulab.array` + - `rgbmatrix.RGBMatrix` +""" + +AudioSample = Union[ + audiocore.WaveFile, audiocore.RawSample, audiomixer.Mixer, audiomp3.MP3Decoder +] +"""Classes that implement the audiosample protocol + + - `audiocore.WaveFile` + - `audiocore.RawSample` + - `audiomixer.Mixer` + - `audiomp3.MP3Decoder` + + You can play these back with `audioio.AudioOut`, `audiobusio.I2SOut` or `audiopwmio.PWMAudioOut`. +""" + +FrameBuffer = Union[rgbmatrix.RGBMatrix] +"""Classes that implement the framebuffer protocol + + - `rgbmatrix.RGBMatrix` +""" + +Alarm = Union[alarm.pin.PinAlarm, alarm.time.TimeAlarm] +"""Classes that implement alarms for sleeping and asynchronous notification. + + - `alarm.pin.PinAlarm` + - `alarm.time.TimeAlarm` + + You can use these alarms to wake up from light or deep sleep. +""" diff --git a/stubs/adafruit_bus_device/__init__.pyi b/stubs/adafruit_bus_device/__init__.pyi new file mode 100644 index 0000000..21eb871 --- /dev/null +++ b/stubs/adafruit_bus_device/__init__.pyi @@ -0,0 +1,133 @@ +"""Hardware accelerated external bus access + +The I2CDevice and SPIDevice helper classes make managing transaction state on a bus easy. +For example, they manage locking the bus to prevent other concurrent access. For SPI +devices, it manages the chip select and protocol changes such as mode. For I2C, it +manages the device address.""" + +from __future__ import annotations + +import busio +import microcontroller +from _typing import ReadableBuffer, WriteableBuffer + +class I2CDevice: + """I2C Device Manager""" + + def __init__(self, i2c: busio.I2C, device_address: int, probe: bool = True) -> None: + + """Represents a single I2C device and manages locking the bus and the device + address. + :param ~busio.I2C i2c: The I2C bus the device is on + :param int device_address: The 7 bit device address + :param bool probe: Probe for the device upon object creation, default is true + + Example:: + + import busio + from board import * + from adafruit_bus_device.i2c_device import I2CDevice + with busio.I2C(SCL, SDA) as i2c: + device = I2CDevice(i2c, 0x70) + bytes_read = bytearray(4) + with device: + device.readinto(bytes_read) + # A second transaction + with device: + device.write(bytes_read)""" + ... + def __enter__(self) -> I2CDevice: + """Context manager entry to lock bus.""" + ... + def __exit__(self) -> None: + """Automatically unlocks the bus on exit.""" + ... + def readinto(self, buf: WriteableBuffer, *, start: int = 0, end: int = 0) -> None: + """Read into ``buf`` from the device. The number of bytes read will be the + length of ``buf``. + If ``start`` or ``end`` is provided, then the buffer will be sliced + as if ``buf[start:end]``. This will not cause an allocation like + ``buf[start:end]`` will so it saves memory. + :param bytearray buf: buffer to write into + :param int start: Index to start writing at + :param int end: Index to write up to but not include; if None, use ``len(buf)``""" + ... + def write(self, buf: ReadableBuffer, *, start: int = 0, end: int = 0) -> None: + """Write the bytes from ``buffer`` to the device, then transmit a stop bit. + If ``start`` or ``end`` is provided, then the buffer will be sliced + as if ``buffer[start:end]``. This will not cause an allocation like + ``buffer[start:end]`` will so it saves memory. + :param bytearray buf: buffer containing the bytes to write + :param int start: Index to start writing from + :param int end: Index to read up to but not include; if None, use ``len(buf)`` + """ + ... + def write_then_readinto( + self, + out_buffer: WriteableBuffer, + in_buffer: ReadableBuffer, + *, + out_start: int = 0, + out_end: int = 0, + in_start: int = 0, + in_end: int = 0 + ) -> None: + """Write the bytes from ``out_buffer`` to the device, then immediately + reads into ``in_buffer`` from the device. The number of bytes read + will be the length of ``in_buffer``. + If ``out_start`` or ``out_end`` is provided, then the output buffer + will be sliced as if ``out_buffer[out_start:out_end]``. This will + not cause an allocation like ``buffer[out_start:out_end]`` will so + it saves memory. + If ``in_start`` or ``in_end`` is provided, then the input buffer + will be sliced as if ``in_buffer[in_start:in_end]``. This will not + cause an allocation like ``in_buffer[in_start:in_end]`` will so + it saves memory. + :param bytearray out_buffer: buffer containing the bytes to write + :param bytearray in_buffer: buffer containing the bytes to read into + :param int out_start: Index to start writing from + :param int out_end: Index to read up to but not include; if None, use ``len(out_buffer)`` + :param int in_start: Index to start writing at + :param int in_end: Index to write up to but not include; if None, use ``len(in_buffer)`` + """ + ... + +class SPIDevice: + """SPI Device Manager""" + + def __init__( + self, + spi: busio.SPI, + chip_select: microcontroller.Pin, + *, + baudrate: int = 100000, + polarity: int = 0, + phase: int = 0, + extra_clocks: int = 0 + ) -> None: + + """ + Represents a single SPI device and manages locking the bus and the device address. + :param ~busio.SPI spi: The SPI bus the device is on + :param ~digitalio.DigitalInOut chip_select: The chip select pin object that implements the DigitalInOut API. + :param int extra_clocks: The minimum number of clock cycles to cycle the bus after CS is high. (Used for SD cards.) + + Example:: + + import busio + import digitalio + from board import * + from adafruit_bus_device.spi_device import SPIDevice + with busio.SPI(SCK, MOSI, MISO) as spi_bus: + cs = digitalio.DigitalInOut(D10) + device = SPIDevice(spi_bus, cs) + bytes_read = bytearray(4) + # The object assigned to spi in the with statements below + # is the original spi_bus object. We are using the busio.SPI + # operations busio.SPI.readinto() and busio.SPI.write(). + with device as spi: + spi.readinto(bytes_read) + # A second transaction + with device as spi: + spi.write(bytes_read)""" + ... diff --git a/stubs/aesio/__init__.pyi b/stubs/aesio/__init__.pyi new file mode 100644 index 0000000..e917065 --- /dev/null +++ b/stubs/aesio/__init__.pyi @@ -0,0 +1,55 @@ +"""AES encryption routines + +The `AES` module contains classes used to implement encryption +and decryption. It aims to be low overhead in terms of memory.""" + +from __future__ import annotations + +from typing import Optional + +from _typing import ReadableBuffer, WriteableBuffer + +class AES: + """Encrypt and decrypt AES streams""" + + def __init__( + self, + key: ReadableBuffer, + mode: int = 0, + iv: Optional[ReadableBuffer] = None, + segment_size: int = 8, + ) -> None: + """Create a new AES state with the given key. + + :param ~_typing.ReadableBuffer key: A 16-, 24-, or 32-byte key + :param int mode: AES mode to use. One of: AES.MODE_ECB, AES.MODE_CBC, or + AES.MODE_CTR + :param ~_typing.ReadableBuffer iv: Initialization vector to use for CBC or CTR mode + + Additional arguments are supported for legacy reasons. + + Encrypting a string:: + + import aesio + from binascii import hexlify + + key = b'Sixteen byte key' + inp = b'Circuit Python!!' # Note: 16-bytes long + outp = bytearray(len(inp)) + cipher = aesio.AES(key, aesio.mode.MODE_ECB) + cipher.encrypt_into(inp, outp) + hexlify(outp)""" + ... + def encrypt_into(self, src: ReadableBuffer, dest: WriteableBuffer) -> None: + """Encrypt the buffer from ``src`` into ``dest``. + + For ECB mode, the buffers must be 16 bytes long. For CBC mode, the + buffers must be a multiple of 16 bytes, and must be equal length. For + CTX mode, there are no restrictions.""" + ... + def decrypt_into(self, src: ReadableBuffer, dest: WriteableBuffer) -> None: + """Decrypt the buffer from ``src`` into ``dest``. + For ECB mode, the buffers must be 16 bytes long. For CBC mode, the + buffers must be a multiple of 16 bytes, and must be equal length. For + CTX mode, there are no restrictions.""" + ... diff --git a/stubs/alarm/__init__.pyi b/stubs/alarm/__init__.pyi new file mode 100644 index 0000000..d5b0c34 --- /dev/null +++ b/stubs/alarm/__init__.pyi @@ -0,0 +1,116 @@ +"""Alarms and sleep + +Provides alarms that trigger based on time intervals or on external events, such as pin +changes. +The program can simply wait for these alarms, or go to sleep and be awoken when they trigger. + +There are two supported levels of sleep: light sleep and deep sleep. + +Light sleep keeps sufficient state so the program can resume after sleeping. +It does not shut down WiFi, BLE, or other communications, or ongoing activities such +as audio playback. It reduces power consumption to the extent possible that leaves +these continuing activities running. In some cases there may be no decrease in power consumption. + +Deep sleep shuts down power to nearly all of the microcontroller including the CPU and RAM. This can save +a more significant amount of power, but CircuitPython must restart ``code.py`` from the beginning when +awakened. + +For both light sleep and deep sleep, if CircuitPython is connected to a host computer, +maintaining the connection takes priority and power consumption may not be reduced. +""" + +from __future__ import annotations + +from typing import overload + +from _typing import Alarm, ReadableBuffer + +sleep_memory: SleepMemory +"""Memory that persists during deep sleep. +This object is the sole instance of `alarm.SleepMemory`.""" + +wake_alarm: Alarm +"""The most recently triggered alarm. If CircuitPython was sleeping, the alarm the woke it from sleep.""" + +def light_sleep_until_alarms(*alarms: Alarm) -> Alarm: + """Go into a light sleep until awakened one of the alarms. The alarm causing the wake-up + is returned, and is also available as `alarm.wake_alarm`. + + If no alarms are specified, return immediately. + + **If CircuitPython is connected to a host computer, the connection will be maintained, + and the microcontroller may not actually go into a light sleep.** + This allows the user to interrupt an existing program with ctrl-C, + and to edit the files in CIRCUITPY, which would not be possible in true light sleep. + Thus, to use light sleep and save significant power, + """ + ... + +def exit_and_deep_sleep_until_alarms(*alarms: Alarm) -> None: + """Exit the program and go into a deep sleep, until awakened by one of the alarms. + This function does not return. + + When awakened, the microcontroller will restart and will run ``boot.py`` and ``code.py`` + from the beginning. + + After restart, an alarm *equivalent* to the one that caused the wake-up + will be available as `alarm.wake_alarm`. + Its type and/or attributes may not correspond exactly to the original alarm. + For time-base alarms, currently, an `alarm.time.TimeAlarm()` is created. + + If no alarms are specified, the microcontroller will deep sleep until reset. + + **If CircuitPython is connected to a host computer, the connection will be maintained, + and the system will not go into deep sleep.** + This allows the user to interrupt an existing program with ctrl-C, + and to edit the files in CIRCUITPY, which would not be possible in true deep sleep. + Thus, to use deep sleep and save significant power, you will need to disconnect from the host. + + Here is skeletal example that deep-sleeps and restarts every 60 seconds: + + .. code-block:: python + + import alarm + import time + + print("Waking up") + + # Set an alarm for 60 seconds from now. + time_alarm = alarm.time.TimeAlarm(monotonic_time=time.monotonic() + 60) + + # Deep sleep until the alarm goes off. Then restart the program. + alarm.exit_and_deep_sleep_until_alarms(time_alarm) + """ + ... + +class SleepMemory: + """Store raw bytes in RAM that persists during deep sleep. + The class acts as a ``bytearray``. + If power is lost, the memory contents are lost. + + Note that this class can't be imported and used directly. The sole + instance of :class:`SleepMemory` is available at + :attr:`alarm.sleep_memory`. + + Usage:: + + import alarm + alarm.sleep_memory[0] = True + alarm.sleep_memory[1] = 12 + """ + + def __init__(self) -> None: + """Not used. Access the sole instance through `alarm.sleep_memory`.""" + ... + @overload + def __getitem__(self, index: slice) -> bytearray: ... + @overload + def __getitem__(self, index: int) -> int: + """Returns the value at the given index.""" + ... + @overload + def __setitem__(self, index: slice, value: ReadableBuffer) -> None: ... + @overload + def __setitem__(self, index: int, value: int) -> None: + """Set the value at the given index.""" + ... diff --git a/stubs/alarm/pin/__init__.pyi b/stubs/alarm/pin/__init__.pyi new file mode 100644 index 0000000..7d89280 --- /dev/null +++ b/stubs/alarm/pin/__init__.pyi @@ -0,0 +1,39 @@ +from __future__ import annotations + +import microcontroller + +class PinAlarm: + """Trigger an alarm when a pin changes state.""" + + def __init__( + self, + pin: microcontroller.Pin, + value: bool, + edge: bool = False, + pull: bool = False, + ) -> None: + """Create an alarm triggered by a `microcontroller.Pin` level. The alarm is not active + until it is passed to an `alarm`-enabling function, such as `alarm.light_sleep_until_alarms()` or + `alarm.exit_and_deep_sleep_until_alarms()`. + + :param microcontroller.Pin pin: The pin to monitor. On some ports, the choice of pin + may be limited due to hardware restrictions, particularly for deep-sleep alarms. + :param bool value: When active, trigger when the pin value is high (``True``) or low (``False``). + On some ports, multiple `PinAlarm` objects may need to have coordinated values + for deep-sleep alarms. + :param bool edge: If ``True``, trigger only when there is a transition to the specified + value of ``value``. If ``True``, if the alarm becomes active when the pin value already + matches ``value``, the alarm is not triggered: the pin must transition from ``not value`` + to ``value`` to trigger the alarm. On some ports, edge-triggering may not be available, + particularly for deep-sleep alarms. + :param bool pull: Enable a pull-up or pull-down which pulls the pin to the level opposite + that of ``value``. For instance, if ``value`` is set to ``True``, setting ``pull`` + to ``True`` will enable a pull-down, to hold the pin low normally until an outside signal + pulls it high. + """ + ... + pin: microcontroller.Pin + """The trigger pin.""" + + value: bool + """The value on which to trigger.""" diff --git a/stubs/alarm/time/__init__.pyi b/stubs/alarm/time/__init__.pyi new file mode 100644 index 0000000..7f417e5 --- /dev/null +++ b/stubs/alarm/time/__init__.pyi @@ -0,0 +1,26 @@ +from __future__ import annotations + +from typing import Optional + +class TimeAlarm: + """Trigger an alarm when the specified time is reached.""" + + def __init__( + self, monotonic_time: Optional[float] = None, epoch_time: Optional[int] = None + ) -> None: + """Create an alarm that will be triggered when `time.monotonic()` would equal + ``monotonic_time``, or when `time.time()` would equal ``epoch_time``. + Only one of the two arguments can be given. + The alarm is not active until it is passed to an + `alarm`-enabling function, such as `alarm.light_sleep_until_alarms()` or + `alarm.exit_and_deep_sleep_until_alarms()`. + + If the given time is in the past when sleep occurs, the alarm will be triggered + immediately. + """ + ... + monotonic_time: float + """When this time is reached, the alarm will trigger, based on the `time.monotonic()` clock. + The time may be given as ``epoch_time`` in the constructor, but it is returned + by this property only as a `time.monotonic()` time. + """ diff --git a/stubs/analogio/__init__.pyi b/stubs/analogio/__init__.pyi new file mode 100644 index 0000000..93fb14d --- /dev/null +++ b/stubs/analogio/__init__.pyi @@ -0,0 +1,99 @@ +"""Analog hardware support + +The `analogio` module contains classes to provide access to analog IO +typically implemented with digital-to-analog (DAC) and analog-to-digital +(ADC) converters. + +All classes change hardware state and should be deinitialized when they +are no longer needed if the program continues after use. To do so, either +call :py:meth:`!deinit` or use a context manager. See +:ref:`lifetime-and-contextmanagers` for more info. + +For example:: + + import analogio + from board import * + + pin = analogio.AnalogIn(A0) + print(pin.value) + pin.deinit() + +This example will initialize the the device, read +:py:data:`~analogio.AnalogIn.value` and then +:py:meth:`~analogio.AnalogIn.deinit` the hardware. The last step is optional +because CircuitPython will do it automatically after the program finishes.""" + +from __future__ import annotations + +from typing import Optional + +import microcontroller + +class AnalogIn: + """Read analog voltage levels + + Usage:: + + import analogio + from board import * + + adc = analogio.AnalogIn(A1) + val = adc.value""" + + def __init__(self, pin: microcontroller.Pin) -> None: + """Use the AnalogIn on the given pin. The reference voltage varies by + platform so use ``reference_voltage`` to read the configured setting. + + :param ~microcontroller.Pin pin: the pin to read from""" + ... + def deinit(self) -> None: + """Turn off the AnalogIn and release the pin for other use.""" + ... + def __enter__(self) -> AnalogIn: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + value: int + """The value on the analog pin between 0 and 65535 inclusive (16-bit). (read-only) + + Even if the underlying analog to digital converter (ADC) is lower + resolution, the value is 16-bit.""" + + reference_voltage: Optional[float] + """The maximum voltage measurable (also known as the reference voltage) as a + `float` in Volts.""" + +class AnalogOut: + """Output analog values (a specific voltage). + + Example usage:: + + import analogio + from microcontroller import pin + + dac = analogio.AnalogOut(pin.PA02) # output on pin PA02 + dac.value = 32768 # makes PA02 1.65V""" + + def __init__(self, pin: microcontroller.Pin) -> None: + """Use the AnalogOut on the given pin. + + :param ~microcontroller.Pin pin: the pin to output to""" + ... + def deinit(self) -> None: + """Turn off the AnalogOut and release the pin for other use.""" + ... + def __enter__(self) -> AnalogOut: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + value: int + """The value on the analog pin between 0 and 65535 inclusive (16-bit). (write-only) + + Even if the underlying digital to analog converter (DAC) is lower + resolution, the value is 16-bit.""" diff --git a/stubs/audiobusio/__init__.pyi b/stubs/audiobusio/__init__.pyi new file mode 100644 index 0000000..972a929 --- /dev/null +++ b/stubs/audiobusio/__init__.pyi @@ -0,0 +1,188 @@ +"""Support for audio input and output over digital buses + +The `audiobusio` module contains classes to provide access to audio IO +over digital buses. These protocols are used to communicate audio to other +chips in the same circuit. It doesn't include audio interconnect protocols +such as S/PDIF. + +All libraries change hardware state and should be deinitialized when they +are no longer needed. To do so, either call :py:meth:`!deinit` or use a +context manager.""" + +from __future__ import annotations + +import _typing +import microcontroller +from _typing import WriteableBuffer + +class I2SOut: + """Output an I2S audio signal""" + + def __init__( + self, + bit_clock: microcontroller.Pin, + word_select: microcontroller.Pin, + data: microcontroller.Pin, + *, + left_justified: bool + ) -> None: + """Create a I2SOut object associated with the given pins. + + :param ~microcontroller.Pin bit_clock: The bit clock (or serial clock) pin + :param ~microcontroller.Pin word_select: The word select (or left/right clock) pin + :param ~microcontroller.Pin data: The data pin + :param bool left_justified: True when data bits are aligned with the word select clock. False + when they are shifted by one to match classic I2S protocol. + + Simple 8ksps 440 Hz sine wave on `Metro M0 Express `_ + using `UDA1334 Breakout `_:: + + import audiobusio + import audiocore + import board + import array + import time + import math + + # Generate one period of sine wave. + length = 8000 // 440 + sine_wave = array.array("H", [0] * length) + for i in range(length): + sine_wave[i] = int(math.sin(math.pi * 2 * i / length) * (2 ** 15) + 2 ** 15) + + sine_wave = audiocore.RawSample(sine_wave, sample_rate=8000) + i2s = audiobusio.I2SOut(board.D1, board.D0, board.D9) + i2s.play(sine_wave, loop=True) + time.sleep(1) + i2s.stop() + + Playing a wave file from flash:: + + import board + import audioio + import audiocore + import audiobusio + import digitalio + + + f = open("cplay-5.1-16bit-16khz.wav", "rb") + wav = audiocore.WaveFile(f) + + a = audiobusio.I2SOut(board.D1, board.D0, board.D9) + + print("playing") + a.play(wav) + while a.playing: + pass + print("stopped")""" + ... + def deinit(self) -> None: + """Deinitialises the I2SOut and releases any hardware resources for reuse.""" + ... + def __enter__(self) -> I2SOut: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + def play(self, sample: _typing.AudioSample, *, loop: bool = False) -> None: + """Plays the sample once when loop=False and continuously when loop=True. + Does not block. Use `playing` to block. + + Sample must be an `audiocore.WaveFile`, `audiocore.RawSample`, `audiomixer.Mixer` or `audiomp3.MP3Decoder`. + + The sample itself should consist of 8 bit or 16 bit samples.""" + ... + def stop(self) -> None: + """Stops playback.""" + ... + playing: bool + """True when the audio sample is being output. (read-only)""" + def pause(self) -> None: + """Stops playback temporarily while remembering the position. Use `resume` to resume playback.""" + ... + def resume(self) -> None: + """Resumes sample playback after :py:func:`pause`.""" + ... + paused: bool + """True when playback is paused. (read-only)""" + +class PDMIn: + """Record an input PDM audio stream""" + + def __init__( + self, + clock_pin: microcontroller.Pin, + data_pin: microcontroller.Pin, + *, + sample_rate: int = 16000, + bit_depth: int = 8, + mono: bool = True, + oversample: int = 64, + startup_delay: float = 0.11 + ) -> None: + """Create a PDMIn object associated with the given pins. This allows you to + record audio signals from the given pins. Individual ports may put further + restrictions on the recording parameters. The overall sample rate is + determined by `sample_rate` x ``oversample``, and the total must be 1MHz or + higher, so `sample_rate` must be a minimum of 16000. + + :param ~microcontroller.Pin clock_pin: The pin to output the clock to + :param ~microcontroller.Pin data_pin: The pin to read the data from + :param int sample_rate: Target sample_rate of the resulting samples. Check `sample_rate` for actual value. + Minimum sample_rate is about 16000 Hz. + :param int bit_depth: Final number of bits per sample. Must be divisible by 8 + :param bool mono: True when capturing a single channel of audio, captures two channels otherwise + :param int oversample: Number of single bit samples to decimate into a final sample. Must be divisible by 8 + :param float startup_delay: seconds to wait after starting microphone clock + to allow microphone to turn on. Most require only 0.01s; some require 0.1s. Longer is safer. + Must be in range 0.0-1.0 seconds.""" + + """Record 8-bit unsigned samples to buffer:: + + import audiobusio + import board + + # Prep a buffer to record into + b = bytearray(200) + with audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA, sample_rate=16000) as mic: + mic.record(b, len(b)) + + Record 16-bit unsigned samples to buffer:: + + import audiobusio + import board + + # Prep a buffer to record into. The array interface doesn't allow for + # constructing with a set size so we append to it until we have the size + # we want. + b = array.array("H") + for i in range(200): + b.append(0) + with audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA, sample_rate=16000, bit_depth=16) as mic: + mic.record(b, len(b))""" + ... + def deinit(self) -> None: + """Deinitialises the PDMIn and releases any hardware resources for reuse.""" + ... + def __enter__(self) -> PDMIn: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context.""" + ... + def record(self, destination: WriteableBuffer, destination_length: int) -> None: + """Records destination_length bytes of samples to destination. This is + blocking. + + An IOError may be raised when the destination is too slow to record the + audio at the given rate. For internal flash, writing all 1s to the file + before recording is recommended to speed up writes. + + :return: The number of samples recorded. If this is less than ``destination_length``, + some samples were missed due to processing time.""" + ... + sample_rate: int + """The actual sample_rate of the recording. This may not match the constructed + sample rate due to internal clock limitations.""" diff --git a/stubs/audiocore/__init__.pyi b/stubs/audiocore/__init__.pyi new file mode 100644 index 0000000..e924fa2 --- /dev/null +++ b/stubs/audiocore/__init__.pyi @@ -0,0 +1,116 @@ +"""Support for audio samples""" + +from __future__ import annotations + +import typing +from typing import Optional + +from _typing import ReadableBuffer, WriteableBuffer + +class RawSample: + """A raw audio sample buffer in memory""" + + def __init__( + self, buffer: ReadableBuffer, *, channel_count: int = 1, sample_rate: int = 8000 + ) -> None: + """Create a RawSample based on the given buffer of signed values. If channel_count is more than + 1 then each channel's samples should alternate. In other words, for a two channel buffer, the + first sample will be for channel 1, the second sample will be for channel two, the third for + channel 1 and so on. + + :param ~_typing.ReadableBuffer buffer: A buffer with samples + :param int channel_count: The number of channels in the buffer + :param int sample_rate: The desired playback sample rate + + Simple 8ksps 440 Hz sin wave:: + + import audiocore + import audioio + import board + import array + import time + import math + + # Generate one period of sine wav. + length = 8000 // 440 + sine_wave = array.array("h", [0] * length) + for i in range(length): + sine_wave[i] = int(math.sin(math.pi * 2 * i / length) * (2 ** 15)) + + dac = audioio.AudioOut(board.SPEAKER) + sine_wave = audiocore.RawSample(sine_wave) + dac.play(sine_wave, loop=True) + time.sleep(1) + dac.stop()""" + ... + def deinit(self) -> None: + """Deinitialises the AudioOut and releases any hardware resources for reuse.""" + ... + def __enter__(self) -> RawSample: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + sample_rate: Optional[int] + """32 bit value that dictates how quickly samples are played in Hertz (cycles per second). + When the sample is looped, this can change the pitch output without changing the underlying + sample. This will not change the sample rate of any active playback. Call ``play`` again to + change it.""" + +class WaveFile: + """Load a wave file for audio playback + + A .wav file prepped for audio playback. Only mono and stereo files are supported. Samples must + be 8 bit unsigned or 16 bit signed. If a buffer is provided, it will be used instead of allocating + an internal buffer.""" + + def __init__(self, file: typing.BinaryIO, buffer: WriteableBuffer) -> None: + """Load a .wav file for playback with `audioio.AudioOut` or `audiobusio.I2SOut`. + + :param typing.BinaryIO file: Already opened wave file + :param ~_typing.WriteableBuffer buffer: Optional pre-allocated buffer, that will be split in half and used for double-buffering of the data. If not provided, two 512 byte buffers are allocated internally. + + + Playing a wave file from flash:: + + import board + import audiocore + import audioio + import digitalio + + # Required for CircuitPlayground Express + speaker_enable = digitalio.DigitalInOut(board.SPEAKER_ENABLE) + speaker_enable.switch_to_output(value=True) + + data = open("cplay-5.1-16bit-16khz.wav", "rb") + wav = audiocore.WaveFile(data) + a = audioio.AudioOut(board.A0) + + print("playing") + a.play(wav) + while a.playing: + pass + print("stopped")""" + ... + def deinit(self) -> None: + """Deinitialises the WaveFile and releases all memory resources for reuse.""" + ... + def __enter__(self) -> WaveFile: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + sample_rate: int + """32 bit value that dictates how quickly samples are loaded into the DAC + in Hertz (cycles per second). When the sample is looped, this can change + the pitch output without changing the underlying sample.""" + + bits_per_sample: int + """Bits per sample. (read only)""" + + channel_count: int + """Number of audio channels. (read only)""" diff --git a/stubs/audioio/__init__.pyi b/stubs/audioio/__init__.pyi new file mode 100644 index 0000000..4d9dd53 --- /dev/null +++ b/stubs/audioio/__init__.pyi @@ -0,0 +1,115 @@ +"""Support for audio output + +The `audioio` module contains classes to provide access to audio IO. + +All classes change hardware state and should be deinitialized when they +are no longer needed if the program continues after use. To do so, either +call :py:meth:`!deinit` or use a context manager. See +:ref:`lifetime-and-contextmanagers` for more info. + +Since CircuitPython 5, `RawSample` and `WaveFile` are moved +to :mod:`audiocore`, and `Mixer` is moved to :mod:`audiomixer`. + +For compatibility with CircuitPython 4.x, some builds allow the items in +`audiocore` to be imported from `audioio`. This will be removed for all +boards in a future build of CircuitPython.""" + +from __future__ import annotations + +from typing import Optional + +import _typing +import microcontroller + +class AudioOut: + """Output an analog audio signal""" + + def __init__( + self, + left_channel: microcontroller.Pin, + *, + right_channel: Optional[microcontroller.Pin] = None, + quiescent_value: int = 0x8000 + ) -> None: + """Create a AudioOut object associated with the given pin(s). This allows you to + play audio signals out on the given pin(s). + + :param ~microcontroller.Pin left_channel: The pin to output the left channel to + :param ~microcontroller.Pin right_channel: The pin to output the right channel to + :param int quiescent_value: The output value when no signal is present. Samples should start + and end with this value to prevent audible popping. + + Simple 8ksps 440 Hz sin wave:: + + import audiocore + import audioio + import board + import array + import time + import math + + # Generate one period of sine wav. + length = 8000 // 440 + sine_wave = array.array("H", [0] * length) + for i in range(length): + sine_wave[i] = int(math.sin(math.pi * 2 * i / length) * (2 ** 15) + 2 ** 15) + + dac = audioio.AudioOut(board.SPEAKER) + sine_wave = audiocore.RawSample(sine_wave, sample_rate=8000) + dac.play(sine_wave, loop=True) + time.sleep(1) + dac.stop() + + Playing a wave file from flash:: + + import board + import audioio + import digitalio + + # Required for CircuitPlayground Express + speaker_enable = digitalio.DigitalInOut(board.SPEAKER_ENABLE) + speaker_enable.switch_to_output(value=True) + + data = open("cplay-5.1-16bit-16khz.wav", "rb") + wav = audiocore.WaveFile(data) + a = audioio.AudioOut(board.A0) + + print("playing") + a.play(wav) + while a.playing: + pass + print("stopped")""" + ... + def deinit(self) -> None: + """Deinitialises the AudioOut and releases any hardware resources for reuse.""" + ... + def __enter__(self) -> AudioOut: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + def play(self, sample: _typing.AudioSample, *, loop: bool = False) -> None: + """Plays the sample once when loop=False and continuously when loop=True. + Does not block. Use `playing` to block. + + Sample must be an `audiocore.WaveFile`, `audiocore.RawSample`, `audiomixer.Mixer` or `audiomp3.MP3Decoder`. + + The sample itself should consist of 16 bit samples. Microcontrollers with a lower output + resolution will use the highest order bits to output. For example, the SAMD21 has a 10 bit + DAC that ignores the lowest 6 bits when playing 16 bit samples.""" + ... + def stop(self) -> None: + """Stops playback and resets to the start of the sample.""" + ... + playing: bool + """True when an audio sample is being output even if `paused`. (read-only)""" + def pause(self) -> None: + """Stops playback temporarily while remembering the position. Use `resume` to resume playback.""" + ... + def resume(self) -> None: + """Resumes sample playback after :py:func:`pause`.""" + ... + paused: bool + """True when playback is paused. (read-only)""" diff --git a/stubs/audiomixer/__init__.pyi b/stubs/audiomixer/__init__.pyi new file mode 100644 index 0000000..b05b713 --- /dev/null +++ b/stubs/audiomixer/__init__.pyi @@ -0,0 +1,116 @@ +"""Support for audio mixing""" + +from __future__ import annotations + +from typing import Tuple + +import _typing + +class Mixer: + """Mixes one or more audio samples together into one sample.""" + + def __init__( + self, + voice_count: int = 2, + buffer_size: int = 1024, + channel_count: int = 2, + bits_per_sample: int = 16, + samples_signed: bool = True, + sample_rate: int = 8000, + ) -> None: + """Create a Mixer object that can mix multiple channels with the same sample rate. + Samples are accessed and controlled with the mixer's `audiomixer.MixerVoice` objects. + + :param int voice_count: The maximum number of voices to mix + :param int buffer_size: The total size in bytes of the buffers to mix into + :param int channel_count: The number of channels the source samples contain. 1 = mono; 2 = stereo. + :param int bits_per_sample: The bits per sample of the samples being played + :param bool samples_signed: Samples are signed (True) or unsigned (False) + :param int sample_rate: The sample rate to be used for all samples + + Playing a wave file from flash:: + + import board + import audioio + import audiocore + import audiomixer + import digitalio + + a = audioio.AudioOut(board.A0) + music = audiocore.WaveFile(open("cplay-5.1-16bit-16khz.wav", "rb")) + drum = audiocore.WaveFile(open("drum.wav", "rb")) + mixer = audiomixer.Mixer(voice_count=2, sample_rate=16000, channel_count=1, + bits_per_sample=16, samples_signed=True) + + print("playing") + # Have AudioOut play our Mixer source + a.play(mixer) + # Play the first sample voice + mixer.voice[0].play(music) + while mixer.playing: + # Play the second sample voice + mixer.voice[1].play(drum) + time.sleep(1) + print("stopped")""" + ... + def deinit(self) -> None: + """Deinitialises the Mixer and releases any hardware resources for reuse.""" + ... + def __enter__(self) -> Mixer: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + playing: bool + """True when any voice is being output. (read-only)""" + + sample_rate: int + """32 bit value that dictates how quickly samples are played in Hertz (cycles per second).""" + + voice: Tuple[MixerVoice, ...] + """A tuple of the mixer's `audiomixer.MixerVoice` object(s). + + .. code-block:: python + + >>> mixer.voice + (,)""" + def play( + self, sample: _typing.AudioSample, *, voice: int = 0, loop: bool = False + ) -> None: + """Plays the sample once when loop=False and continuously when loop=True. + Does not block. Use `playing` to block. + + Sample must be an `audiocore.WaveFile`, `audiocore.RawSample`, `audiomixer.Mixer` or `audiomp3.MP3Decoder`. + + The sample must match the Mixer's encoding settings given in the constructor.""" + ... + def stop_voice(self, voice: int = 0) -> None: + """Stops playback of the sample on the given voice.""" + ... + +class MixerVoice: + """Voice objects used with Mixer + + Used to access and control samples with `audiomixer.Mixer`.""" + + def __init__(self) -> None: + """MixerVoice instance object(s) created by `audiomixer.Mixer`.""" + ... + def play(self, sample: _typing.AudioSample, *, loop: bool = False) -> None: + """Plays the sample once when ``loop=False``, and continuously when ``loop=True``. + Does not block. Use `playing` to block. + + Sample must be an `audiocore.WaveFile`, `audiocore.RawSample`, `audiomixer.Mixer` or `audiomp3.MP3Decoder`. + + The sample must match the `audiomixer.Mixer`'s encoding settings given in the constructor.""" + ... + def stop(self) -> None: + """Stops playback of the sample on this voice.""" + ... + level: float + """The volume level of a voice, as a floating point number between 0 and 1.""" + + playing: bool + """True when this voice is being output. (read-only)""" diff --git a/stubs/audiomp3/__init__.pyi b/stubs/audiomp3/__init__.pyi new file mode 100644 index 0000000..e7f7754 --- /dev/null +++ b/stubs/audiomp3/__init__.pyi @@ -0,0 +1,66 @@ +"""Support for MP3-compressed audio files""" + +from __future__ import annotations + +import typing + +from _typing import WriteableBuffer + +class MP3Decoder: + """Load a mp3 file for audio playback""" + + def __init__(self, file: typing.BinaryIO, buffer: WriteableBuffer) -> None: + + """Load a .mp3 file for playback with `audioio.AudioOut` or `audiobusio.I2SOut`. + + :param typing.BinaryIO file: Already opened mp3 file + :param ~_typing.WriteableBuffer buffer: Optional pre-allocated buffer, that will be split in half and used for double-buffering of the data. If not provided, two buffers are allocated internally. The specific buffer size required depends on the mp3 file. + + + Playing a mp3 file from flash:: + + import board + import audiomp3 + import audioio + import digitalio + + # Required for CircuitPlayground Express + speaker_enable = digitalio.DigitalInOut(board.SPEAKER_ENABLE) + speaker_enable.switch_to_output(value=True) + + data = open("cplay-16bit-16khz-64kbps.mp3", "rb") + mp3 = audiomp3.MP3Decoder(data) + a = audioio.AudioOut(board.A0) + + print("playing") + a.play(mp3) + while a.playing: + pass + print("stopped")""" + ... + def deinit(self) -> None: + """Deinitialises the MP3 and releases all memory resources for reuse.""" + ... + def __enter__(self) -> MP3Decoder: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + file: typing.BinaryIO + """File to play back.""" + + sample_rate: int + """32 bit value that dictates how quickly samples are loaded into the DAC + in Hertz (cycles per second). When the sample is looped, this can change + the pitch output without changing the underlying sample.""" + + bits_per_sample: int + """Bits per sample. (read only)""" + + channel_count: int + """Number of audio channels. (read only)""" + + rms_level: float + """The RMS audio level of a recently played moment of audio. (read only)""" diff --git a/stubs/audiopwmio/__init__.pyi b/stubs/audiopwmio/__init__.pyi new file mode 100644 index 0000000..a88e764 --- /dev/null +++ b/stubs/audiopwmio/__init__.pyi @@ -0,0 +1,114 @@ +"""Audio output via digital PWM + +The `audiopwmio` module contains classes to provide access to audio IO. + +All classes change hardware state and should be deinitialized when they +are no longer needed if the program continues after use. To do so, either +call :py:meth:`!deinit` or use a context manager. See +:ref:`lifetime-and-contextmanagers` for more info. + +Since CircuitPython 5, `Mixer`, `RawSample` and `WaveFile` are moved +to :mod:`audiocore`.""" + +from __future__ import annotations + +from typing import Optional + +import _typing +import microcontroller + +class PWMAudioOut: + """Output an analog audio signal by varying the PWM duty cycle.""" + + def __init__( + self, + left_channel: microcontroller.Pin, + *, + right_channel: Optional[microcontroller.Pin] = None, + quiescent_value: int = 0x8000 + ) -> None: + """Create a PWMAudioOut object associated with the given pin(s). This allows you to + play audio signals out on the given pin(s). In contrast to mod:`audioio`, + the pin(s) specified are digital pins, and are driven with a device-dependent PWM + signal. + + :param ~microcontroller.Pin left_channel: The pin to output the left channel to + :param ~microcontroller.Pin right_channel: The pin to output the right channel to + :param int quiescent_value: The output value when no signal is present. Samples should start + and end with this value to prevent audible popping. + + Simple 8ksps 440 Hz sin wave:: + + import audiocore + import audiopwmio + import board + import array + import time + import math + + # Generate one period of sine wav. + length = 8000 // 440 + sine_wave = array.array("H", [0] * length) + for i in range(length): + sine_wave[i] = int(math.sin(math.pi * 2 * i / length) * (2 ** 15) + 2 ** 15) + + dac = audiopwmio.PWMAudioOut(board.SPEAKER) + sine_wave = audiocore.RawSample(sine_wave, sample_rate=8000) + dac.play(sine_wave, loop=True) + time.sleep(1) + dac.stop() + + Playing a wave file from flash:: + + import board + import audiocore + import audiopwmio + import digitalio + + # Required for CircuitPlayground Express + speaker_enable = digitalio.DigitalInOut(board.SPEAKER_ENABLE) + speaker_enable.switch_to_output(value=True) + + data = open("cplay-5.1-16bit-16khz.wav", "rb") + wav = audiocore.WaveFile(data) + a = audiopwmio.PWMAudioOut(board.SPEAKER) + + print("playing") + a.play(wav) + while a.playing: + pass + print("stopped")""" + ... + def deinit(self) -> None: + """Deinitialises the PWMAudioOut and releases any hardware resources for reuse.""" + ... + def __enter__(self) -> PWMAudioOut: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + def play(self, sample: _typing.AudioSample, *, loop: bool = False) -> None: + """Plays the sample once when loop=False and continuously when loop=True. + Does not block. Use `playing` to block. + + Sample must be an `audiocore.WaveFile`, `audiocore.RawSample`, `audiomixer.Mixer` or `audiomp3.MP3Decoder`. + + The sample itself should consist of 16 bit samples. Microcontrollers with a lower output + resolution will use the highest order bits to output. For example, the SAMD21 has a 10 bit + DAC that ignores the lowest 6 bits when playing 16 bit samples.""" + ... + def stop(self) -> None: + """Stops playback and resets to the start of the sample.""" + ... + playing: bool + """True when an audio sample is being output even if `paused`. (read-only)""" + def pause(self) -> None: + """Stops playback temporarily while remembering the position. Use `resume` to resume playback.""" + ... + def resume(self) -> None: + """Resumes sample playback after :py:func:`pause`.""" + ... + paused: bool + """True when playback is paused. (read-only)""" diff --git a/stubs/bitbangio/__init__.pyi b/stubs/bitbangio/__init__.pyi new file mode 100644 index 0000000..915ebbe --- /dev/null +++ b/stubs/bitbangio/__init__.pyi @@ -0,0 +1,306 @@ +"""Digital protocols implemented by the CPU + +The `bitbangio` module contains classes to provide digital bus protocol +support regardless of whether the underlying hardware exists to use the +protocol. + +First try to use `busio` module instead which may utilize peripheral +hardware to implement the protocols. Native implementations will be faster +than bitbanged versions and have more capabilities. + +All classes change hardware state and should be deinitialized when they +are no longer needed if the program continues after use. To do so, either +call :py:meth:`!deinit` or use a context manager. See +:ref:`lifetime-and-contextmanagers` for more info. + +For example:: + + import bitbangio + from board import * + + i2c = bitbangio.I2C(SCL, SDA) + print(i2c.scan()) + i2c.deinit() + +This example will initialize the the device, run +:py:meth:`~bitbangio.I2C.scan` and then :py:meth:`~bitbangio.I2C.deinit` the +hardware. The last step is optional because CircuitPython automatically +resets hardware after a program finishes.""" + +from __future__ import annotations + +from typing import List, Optional + +import microcontroller +from _typing import ReadableBuffer, WriteableBuffer + +class I2C: + """Two wire serial protocol""" + + def __init__( + self, + scl: microcontroller.Pin, + sda: microcontroller.Pin, + *, + frequency: int = 400000, + timeout: int = 255 + ) -> None: + """I2C is a two-wire protocol for communicating between devices. At the + physical level it consists of 2 wires: SCL and SDA, the clock and data + lines respectively. + + .. seealso:: Using this class directly requires careful lock management. + Instead, use :class:`~adafruit_bus_device.i2c_device.I2CDevice` to + manage locks. + + .. seealso:: Using this class to directly read registers requires manual + bit unpacking. Instead, use an existing driver or make one with + :ref:`Register ` data descriptors. + + :param ~microcontroller.Pin scl: The clock pin + :param ~microcontroller.Pin sda: The data pin + :param int frequency: The clock frequency of the bus + :param int timeout: The maximum clock stretching timeout in microseconds""" + ... + def deinit(self) -> None: + """Releases control of the underlying hardware so other classes can use it.""" + ... + def __enter__(self) -> I2C: + """No-op used in Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware on context exit. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + def scan(self) -> List[int]: + """Scan all I2C addresses between 0x08 and 0x77 inclusive and return a list of + those that respond. A device responds if it pulls the SDA line low after + its address (including a read bit) is sent on the bus.""" + ... + def try_lock(self) -> bool: + """Attempts to grab the I2C lock. Returns True on success.""" + ... + def unlock(self) -> None: + """Releases the I2C lock.""" + ... + def readfrom_into( + self, + address: int, + buffer: WriteableBuffer, + *, + start: int = 0, + end: Optional[int] = None + ) -> None: + """Read into ``buffer`` from the device selected by ``address``. + The number of bytes read will be the length of ``buffer``. + At least one byte must be read. + + If ``start`` or ``end`` is provided, then the buffer will be sliced + as if ``buffer[start:end]``. This will not cause an allocation like + ``buf[start:end]`` will so it saves memory. + + :param int address: 7-bit device address + :param ~_typing.WriteableBuffer buffer: buffer to write into + :param int start: Index to start writing at + :param int end: Index to write up to but not include""" + ... + def writeto( + self, + address: int, + buffer: ReadableBuffer, + *, + start: int = 0, + end: Optional[int] = None, + stop: bool = True + ) -> None: + """Write the bytes from ``buffer`` to the device selected by ``address`` and then transmits a + stop bit. Use `writeto_then_readfrom` when needing a write, no stop and repeated start + before a read. + + If ``start`` or ``end`` is provided, then the buffer will be sliced + as if ``buffer[start:end]``. This will not cause an allocation like + ``buffer[start:end]`` will so it saves memory. + + Writing a buffer or slice of length zero is permitted, as it can be used + to poll for the existence of a device. + + :param int address: 7-bit device address + :param ~_typing.ReadableBuffer buffer: buffer containing the bytes to write + :param int start: Index to start writing from + :param int end: Index to read up to but not include""" + ... + def writeto_then_readfrom( + self, + address: int, + out_buffer: ReadableBuffer, + in_buffer: ReadableBuffer, + *, + out_start: int = 0, + out_end: Optional[int] = None, + in_start: int = 0, + in_end: Optional[int] = None + ) -> None: + """Write the bytes from ``out_buffer`` to the device selected by ``address``, generate no stop + bit, generate a repeated start and read into ``in_buffer``. ``out_buffer`` and + ``in_buffer`` can be the same buffer because they are used sequentially. + + If ``start`` or ``end`` is provided, then the corresponding buffer will be sliced + as if ``buffer[start:end]``. This will not cause an allocation like ``buf[start:end]`` + will so it saves memory. + + :param int address: 7-bit device address + :param ~_typing.ReadableBuffer out_buffer: buffer containing the bytes to write + :param ~_typing.WriteableBuffer in_buffer: buffer to write into + :param int out_start: Index to start writing from + :param int out_end: Index to read up to but not include. Defaults to ``len(buffer)`` + :param int in_start: Index to start writing at + :param int in_end: Index to write up to but not include. Defaults to ``len(buffer)``""" + +class OneWire: + """Lowest-level of the Maxim OneWire protocol + + :class:`~bitbangio.OneWire` implements the timing-sensitive foundation of + the Maxim (formerly Dallas Semi) OneWire protocol. + + Protocol definition is here: https://www.maximintegrated.com/en/app-notes/index.mvp/id/126""" + + def __init__(self, pin: microcontroller.Pin) -> None: + + """Create a OneWire object associated with the given pin. The object + implements the lowest level timing-sensitive bits of the protocol. + + :param ~microcontroller.Pin pin: Pin to read pulses from. + + Read a short series of pulses:: + + import bitbangio + import board + + onewire = bitbangio.OneWire(board.D7) + onewire.reset() + onewire.write_bit(True) + onewire.write_bit(False) + print(onewire.read_bit())""" + ... + def deinit(self) -> None: + """Deinitialize the OneWire bus and release any hardware resources for reuse.""" + ... + def __enter__(self) -> OneWire: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + def reset(self) -> bool: + """Reset the OneWire bus""" + ... + def read_bit(self) -> bool: + """Read in a bit + + :returns: bit state read + :rtype: bool""" + ... + def write_bit(self, value: bool) -> None: + """Write out a bit based on value.""" + ... + +class SPI: + """A 3-4 wire serial protocol + + SPI is a serial protocol that has exclusive pins for data in and out of the + main device. It is typically faster than :py:class:`~bitbangio.I2C` because a + separate pin is used to select a device rather than a transmitted + address. This class only manages three of the four SPI lines: `!clock`, + `!MOSI`, `!MISO`. Its up to the client to manage the appropriate + select line, often abbreviated `!CS` or `!SS`. (This is common because + multiple secondaries can share the `!clock`, `!MOSI` and `!MISO` lines + and therefore the hardware.)""" + + def __init__( + self, + clock: microcontroller.Pin, + MOSI: Optional[microcontroller.Pin] = None, + MISO: Optional[microcontroller.Pin] = None, + ) -> None: + """Construct an SPI object on the given pins. + + .. seealso:: Using this class directly requires careful lock management. + Instead, use :class:`~adafruit_bus_device.spi_device.SPIDevice` to + manage locks. + + .. seealso:: Using this class to directly read registers requires manual + bit unpacking. Instead, use an existing driver or make one with + :ref:`Register ` data descriptors. + + + :param ~microcontroller.Pin clock: the pin to use for the clock. + :param ~microcontroller.Pin MOSI: the Main Out Selected In pin. + :param ~microcontroller.Pin MISO: the Main In Selected Out pin.""" + ... + def deinit(self) -> None: + """Turn off the SPI bus.""" + ... + def __enter__(self) -> SPI: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + def configure( + self, + *, + baudrate: int = 100000, + polarity: int = 0, + phase: int = 0, + bits: int = 8 + ) -> None: + """Configures the SPI bus. Only valid when locked. + + :param int baudrate: the clock rate in Hertz + :param int polarity: the base state of the clock line (0 or 1) + :param int phase: the edge of the clock that data is captured. First (0) + or second (1). Rising or falling depends on clock polarity. + :param int bits: the number of bits per word""" + ... + def try_lock(self) -> bool: + """Attempts to grab the SPI lock. Returns True on success. + + :return: True when lock has been grabbed + :rtype: bool""" + ... + def unlock(self) -> None: + """Releases the SPI lock.""" + ... + def write(self, buf: ReadableBuffer) -> None: + """Write the data contained in ``buf``. Requires the SPI being locked. + If the buffer is empty, nothing happens.""" + ... + def readinto(self, buf: WriteableBuffer) -> None: + """Read into the buffer specified by ``buf`` while writing zeroes. + Requires the SPI being locked. + If the number of bytes to read is 0, nothing happens.""" + ... + def write_readinto( + self, + buffer_out: ReadableBuffer, + buffer_in: WriteableBuffer, + *, + out_start: int = 0, + out_end: Optional[int] = None, + in_start: int = 0, + in_end: Optional[int] = None + ) -> None: + """Write out the data in ``buffer_out`` while simultaneously reading data into ``buffer_in``. + The lengths of the slices defined by ``buffer_out[out_start:out_end]`` and ``buffer_in[in_start:in_end]`` + must be equal. + If buffer slice lengths are both 0, nothing happens. + + :param ~_typing.ReadableBuffer buffer_out: Write out the data in this buffer + :param ~_typing.WriteableBuffer buffer_in: Read data into this buffer + :param int out_start: Start of the slice of buffer_out to write out: ``buffer_out[out_start:out_end]`` + :param int out_end: End of the slice; this index is not included. Defaults to ``len(buffer_out)`` + :param int in_start: Start of the slice of ``buffer_in`` to read into: ``buffer_in[in_start:in_end]`` + :param int in_end: End of the slice; this index is not included. Defaults to ``len(buffer_in)``""" + ... diff --git a/stubs/board/__init__.pyi b/stubs/board/__init__.pyi new file mode 100644 index 0000000..fe6ee35 --- /dev/null +++ b/stubs/board/__init__.pyi @@ -0,0 +1,29 @@ +"""Board specific pin names + +Common container for board base pin names. These will vary from board to +board so don't expect portability when using this module. + +.. warning:: The board module varies by board. The APIs documented here may or may not be + available on a specific board.""" + +from __future__ import annotations + +import busio + +def I2C() -> busio.I2C: + """Returns the `busio.I2C` object for the board designated SDA and SCL pins. It is a singleton.""" + ... + +def SPI() -> busio.SPI: + """Returns the `busio.SPI` object for the board designated SCK, MOSI and MISO pins. It is a + singleton.""" + ... + +def UART() -> busio.UART: + """Returns the `busio.UART` object for the board designated TX and RX pins. It is a singleton. + + The object created uses the default parameter values for `busio.UART`. If you need to set + parameters that are not changeable after creation, such as ``receiver_buffer_size``, + do not use `board.UART()`; instead create a `busio.UART` object explicitly with the + desired parameters.""" + ... diff --git a/stubs/busio/__init__.pyi b/stubs/busio/__init__.pyi new file mode 100644 index 0000000..a03f3b6 --- /dev/null +++ b/stubs/busio/__init__.pyi @@ -0,0 +1,461 @@ +"""Hardware accelerated external bus access + +The `busio` module contains classes to support a variety of serial +protocols. + +When the microcontroller does not support the behavior in a hardware +accelerated fashion it may internally use a bitbang routine. However, if +hardware support is available on a subset of pins but not those provided, +then a RuntimeError will be raised. Use the `bitbangio` module to explicitly +bitbang a serial protocol on any general purpose pins. + +All classes change hardware state and should be deinitialized when they +are no longer needed if the program continues after use. To do so, either +call :py:meth:`!deinit` or use a context manager. See +:ref:`lifetime-and-contextmanagers` for more info. + +For example:: + + import busio + from board import * + + i2c = busio.I2C(SCL, SDA) + print(i2c.scan()) + i2c.deinit() + +This example will initialize the the device, run +:py:meth:`~busio.I2C.scan` and then :py:meth:`~busio.I2C.deinit` the +hardware. The last step is optional because CircuitPython automatically +resets hardware after a program finishes.""" + +from __future__ import annotations + +from typing import List, Optional + +import microcontroller +from _typing import ReadableBuffer, WriteableBuffer + +class I2C: + """Two wire serial protocol""" + + def __init__( + self, + scl: microcontroller.Pin, + sda: microcontroller.Pin, + *, + frequency: int = 100000, + timeout: int = 255 + ) -> None: + + """I2C is a two-wire protocol for communicating between devices. At the + physical level it consists of 2 wires: SCL and SDA, the clock and data + lines respectively. + + .. seealso:: Using this class directly requires careful lock management. + Instead, use :class:`~adafruit_bus_device.i2c_device.I2CDevice` to + manage locks. + + .. seealso:: Using this class to directly read registers requires manual + bit unpacking. Instead, use an existing driver or make one with + :ref:`Register ` data descriptors. + + :param ~microcontroller.Pin scl: The clock pin + :param ~microcontroller.Pin sda: The data pin + :param int frequency: The clock frequency in Hertz + :param int timeout: The maximum clock stretching timeut - (used only for bitbangio.I2C; ignored for busio.I2C) + + .. note:: On the nRF52840, only one I2C object may be created, + except on the Circuit Playground Bluefruit, which allows two, + one for the onboard accelerometer, and one for offboard use.""" + ... + def deinit(self) -> None: + """Releases control of the underlying hardware so other classes can use it.""" + ... + def __enter__(self) -> I2C: + """No-op used in Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware on context exit. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + def scan(self) -> List[int]: + """Scan all I2C addresses between 0x08 and 0x77 inclusive and return a + list of those that respond. + + :return: List of device ids on the I2C bus + :rtype: list""" + ... + def try_lock(self) -> bool: + """Attempts to grab the I2C lock. Returns True on success. + + :return: True when lock has been grabbed + :rtype: bool""" + ... + def unlock(self) -> None: + """Releases the I2C lock.""" + ... + def readfrom_into( + self, + address: int, + buffer: WriteableBuffer, + *, + start: int = 0, + end: Optional[int] = None + ) -> None: + """Read into ``buffer`` from the device selected by ``address``. + The number of bytes read will be the length of ``buffer``. + At least one byte must be read. + + If ``start`` or ``end`` is provided, then the buffer will be sliced + as if ``buffer[start:end]``. This will not cause an allocation like + ``buf[start:end]`` will so it saves memory. + + :param int address: 7-bit device address + :param ~_typing.WriteableBuffer buffer: buffer to write into + :param int start: Index to start writing at + :param int end: Index to write up to but not include. Defaults to ``len(buffer)``""" + ... + def writeto( + self, + address: int, + buffer: ReadableBuffer, + *, + start: int = 0, + end: Optional[int] = None, + stop: bool = True + ) -> None: + """Write the bytes from ``buffer`` to the device selected by ``address`` and + then transmit a stop bit. + + If ``start`` or ``end`` is provided, then the buffer will be sliced + as if ``buffer[start:end]``. This will not cause an allocation like + ``buffer[start:end]`` will so it saves memory. + + Writing a buffer or slice of length zero is permitted, as it can be used + to poll for the existence of a device. + + :param int address: 7-bit device address + :param ~_typing.ReadbleBuffer buffer: buffer containing the bytes to write + :param int start: Index to start writing from + :param int end: Index to read up to but not include. Defaults to ``len(buffer)``""" + ... + def writeto_then_readfrom( + self, + address: int, + out_buffer: ReadableBuffer, + in_buffer: WriteableBuffer, + *, + out_start: int = 0, + out_end: Optional[int] = None, + in_start: int = 0, + in_end: Optional[int] = None + ) -> None: + """Write the bytes from ``out_buffer`` to the device selected by ``address``, generate no stop + bit, generate a repeated start and read into ``in_buffer``. ``out_buffer`` and + ``in_buffer`` can be the same buffer because they are used sequentially. + + if ``start`` or ``end`` is provided, then the corresponding buffer will be sliced + as if ``buffer[start:end]``. This will not cause an allocation like ``buf[start:end]`` + will so it saves memory. + + :param int address: 7-bit device address + :param ~_typing.ReadbleBuffer out_buffer: buffer containing the bytes to write + :param ~_typing.WriteableBuffer in_buffer: buffer to write into + :param int out_start: Index to start writing from + :param int out_end: Index to read up to but not include. Defaults to ``len(buffer)`` + :param int in_start: Index to start writing at + :param int in_end: Index to write up to but not include. Defaults to ``len(buffer)``""" + ... + +class OneWire: + """Lowest-level of the Maxim OneWire protocol""" + + def __init__(self, pin: microcontroller.Pin) -> None: + """(formerly Dallas Semi) OneWire protocol. + + Protocol definition is here: https://www.maximintegrated.com/en/app-notes/index.mvp/id/126 + + .. class:: OneWire(pin) + + Create a OneWire object associated with the given pin. The object + implements the lowest level timing-sensitive bits of the protocol. + + :param ~microcontroller.Pin pin: Pin connected to the OneWire bus + + Read a short series of pulses:: + + import busio + import board + + onewire = busio.OneWire(board.D7) + onewire.reset() + onewire.write_bit(True) + onewire.write_bit(False) + print(onewire.read_bit())""" + ... + def deinit(self) -> None: + """Deinitialize the OneWire bus and release any hardware resources for reuse.""" + ... + def __enter__(self) -> OneWire: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + def reset(self) -> bool: + """Reset the OneWire bus and read presence + + :returns: False when at least one device is present + :rtype: bool""" + ... + def read_bit(self) -> bool: + """Read in a bit + + :returns: bit state read + :rtype: bool""" + ... + def write_bit(self, value: bool) -> None: + """Write out a bit based on value.""" + ... + +class SPI: + """A 3-4 wire serial protocol + + SPI is a serial protocol that has exclusive pins for data in and out of the + main device. It is typically faster than :py:class:`~bitbangio.I2C` because a + separate pin is used to select a device rather than a transmitted + address. This class only manages three of the four SPI lines: `!clock`, + `!MOSI`, `!MISO`. Its up to the client to manage the appropriate + select line, often abbreviated `!CS` or `!SS`. (This is common because + multiple secondaries can share the `!clock`, `!MOSI` and `!MISO` lines + and therefore the hardware.)""" + + def __init__( + self, + clock: microcontroller.Pin, + MOSI: Optional[microcontroller.Pin] = None, + MISO: Optional[microcontroller.Pin] = None, + ) -> None: + + """Construct an SPI object on the given pins. + + ..note:: The SPI peripherals allocated in order of desirability, if possible, + such as highest speed and not shared use first. For instance, on the nRF52840, + there is a single 32MHz SPI peripheral, and multiple 8MHz peripherals, + some of which may also be used for I2C. The 32MHz SPI peripheral is returned + first, then the exclusive 8MHz SPI peripheral, and finally the shared 8MHz + peripherals. + + .. seealso:: Using this class directly requires careful lock management. + Instead, use :class:`~adafruit_bus_device.spi_device.SPIDevice` to + manage locks. + + .. seealso:: Using this class to directly read registers requires manual + bit unpacking. Instead, use an existing driver or make one with + :ref:`Register ` data descriptors. + + :param ~microcontroller.Pin clock: the pin to use for the clock. + :param ~microcontroller.Pin MOSI: the Main Out Selected In pin. + :param ~microcontroller.Pin MISO: the Main In Selected Out pin.""" + ... + def deinit(self) -> None: + """Turn off the SPI bus.""" + ... + def __enter__(self) -> SPI: + """No-op used by Context Managers. + Provided by context manager helper.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + def configure( + self, + *, + baudrate: int = 100000, + polarity: int = 0, + phase: int = 0, + bits: int = 8 + ) -> None: + """Configures the SPI bus. The SPI object must be locked. + + :param int baudrate: the desired clock rate in Hertz. The actual clock rate may be higher or lower + due to the granularity of available clock settings. + Check the `frequency` attribute for the actual clock rate. + :param int polarity: the base state of the clock line (0 or 1) + :param int phase: the edge of the clock that data is captured. First (0) + or second (1). Rising or falling depends on clock polarity. + :param int bits: the number of bits per word + + .. note:: On the SAMD21, it is possible to set the baudrate to 24 MHz, but that + speed is not guaranteed to work. 12 MHz is the next available lower speed, and is + within spec for the SAMD21. + + .. note:: On the nRF52840, these baudrates are available: 125kHz, 250kHz, 1MHz, 2MHz, 4MHz, + and 8MHz. + If you pick a a baudrate other than one of these, the nearest lower + baudrate will be chosen, with a minimum of 125kHz. + Two SPI objects may be created, except on the Circuit Playground Bluefruit, + which allows only one (to allow for an additional I2C object).""" + ... + def try_lock(self) -> bool: + """Attempts to grab the SPI lock. Returns True on success. + + :return: True when lock has been grabbed + :rtype: bool""" + ... + def unlock(self) -> None: + """Releases the SPI lock.""" + ... + def write( + self, buffer: ReadableBuffer, *, start: int = 0, end: Optional[int] = None + ) -> None: + """Write the data contained in ``buffer``. The SPI object must be locked. + If the buffer is empty, nothing happens. + + :param ~_typing.ReadableBuffer buffer: Write out the data in this buffer + :param int start: Start of the slice of ``buffer`` to write out: ``buffer[start:end]`` + :param int end: End of the slice; this index is not included. Defaults to ``len(buffer)``""" + ... + def readinto( + self, + buffer: WriteableBuffer, + *, + start: int = 0, + end: Optional[int] = None, + write_value: int = 0 + ) -> None: + """Read into ``buffer`` while writing ``write_value`` for each byte read. + The SPI object must be locked. + If the number of bytes to read is 0, nothing happens. + + :param ~_typing.WriteableBuffer buffer: Read data into this buffer + :param int start: Start of the slice of ``buffer`` to read into: ``buffer[start:end]`` + :param int end: End of the slice; this index is not included. Defaults to ``len(buffer)`` + :param int write_value: Value to write while reading. (Usually ignored.)""" + ... + def write_readinto( + self, + buffer_out: ReadableBuffer, + buffer_in: WriteableBuffer, + *, + out_start: int = 0, + out_end: Optional[int] = None, + in_start: int = 0, + in_end: Optional[int] = None + ) -> None: + """Write out the data in ``buffer_out`` while simultaneously reading data into ``buffer_in``. + The SPI object must be locked. + The lengths of the slices defined by ``buffer_out[out_start:out_end]`` and ``buffer_in[in_start:in_end]`` + must be equal. + If buffer slice lengths are both 0, nothing happens. + + :param ~_typing.ReadableBuffer buffer_out: Write out the data in this buffer + :param ~_typing.WriteableBuffer buffer_in: Read data into this buffer + :param int out_start: Start of the slice of buffer_out to write out: ``buffer_out[out_start:out_end]`` + :param int out_end: End of the slice; this index is not included. Defaults to ``len(buffer_out)`` + :param int in_start: Start of the slice of ``buffer_in`` to read into: ``buffer_in[in_start:in_end]`` + :param int in_end: End of the slice; this index is not included. Defaults to ``len(buffer_in)``""" + ... + frequency: int + """The actual SPI bus frequency. This may not match the frequency requested + due to internal limitations.""" + +class UART: + """A bidirectional serial protocol""" + + def __init__( + self, + tx: microcontroller.Pin, + rx: microcontroller.Pin, + *, + baudrate: int = 9600, + bits: int = 8, + parity: Optional[Parity] = None, + stop: int = 1, + timeout: float = 1, + receiver_buffer_size: int = 64 + ) -> None: + """A common bidirectional serial protocol that uses an an agreed upon speed + rather than a shared clock line. + + :param ~microcontroller.Pin tx: the pin to transmit with, or ``None`` if this ``UART`` is receive-only. + :param ~microcontroller.Pin rx: the pin to receive on, or ``None`` if this ``UART`` is transmit-only. + :param ~microcontroller.Pin rts: the pin for rts, or ``None`` if rts not in use. + :param ~microcontroller.Pin cts: the pin for cts, or ``None`` if cts not in use. + :param ~microcontroller.Pin rs485_dir: the output pin for rs485 direction setting, or ``None`` if rs485 not in use. + :param bool rs485_invert: rs485_dir pin active high when set. Active low otherwise. + :param int baudrate: the transmit and receive speed. + :param int bits: the number of bits per byte, 7, 8 or 9. + :param Parity parity: the parity used for error checking. + :param int stop: the number of stop bits, 1 or 2. + :param float timeout: the timeout in seconds to wait for the first character and between subsequent characters when reading. Raises ``ValueError`` if timeout >100 seconds. + :param int receiver_buffer_size: the character length of the read buffer (0 to disable). (When a character is 9 bits the buffer will be 2 * receiver_buffer_size bytes.) + + *New in CircuitPython 4.0:* ``timeout`` has incompatibly changed units from milliseconds to seconds. + The new upper limit on ``timeout`` is meant to catch mistaken use of milliseconds.""" + ... + def deinit(self) -> None: + """Deinitialises the UART and releases any hardware resources for reuse.""" + ... + def __enter__(self) -> UART: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + def read(self, nbytes: Optional[int] = None) -> Optional[bytes]: + """Read characters. If ``nbytes`` is specified then read at most that many + bytes. Otherwise, read everything that arrives until the connection + times out. Providing the number of bytes expected is highly recommended + because it will be faster. + + :return: Data read + :rtype: bytes or None""" + ... + def readinto(self, buf: WriteableBuffer) -> Optional[int]: + """Read bytes into the ``buf``. Read at most ``len(buf)`` bytes. + + :return: number of bytes read and stored into ``buf`` + :rtype: int or None (on a non-blocking error) + + *New in CircuitPython 4.0:* No length parameter is permitted.""" + ... + def readline(self) -> bytes: + """Read a line, ending in a newline character, or + return None if a timeout occurs sooner, or + return everything readable if no newline is found and timeout=0 + + :return: the line read + :rtype: bytes or None""" + ... + def write(self, buf: WriteableBuffer) -> Optional[int]: + """Write the buffer of bytes to the bus. + + *New in CircuitPython 4.0:* ``buf`` must be bytes, not a string. + + :return: the number of bytes written + :rtype: int or None""" + ... + baudrate: int + """The current baudrate.""" + + in_waiting: int + """The number of bytes in the input buffer, available to be read""" + + timeout: float + """The current timeout, in seconds (float).""" + def reset_input_buffer(self) -> None: + """Discard any unread characters in the input buffer.""" + ... + +class Parity: + """Enum-like class to define the parity used to verify correct data transfer.""" + + ODD: int + """Total number of ones should be odd.""" + + EVEN: int + """Total number of ones should be even.""" diff --git a/stubs/camera/__init__.pyi b/stubs/camera/__init__.pyi new file mode 100644 index 0000000..d65949e --- /dev/null +++ b/stubs/camera/__init__.pyi @@ -0,0 +1,58 @@ +"""Support for camera input + +The `camera` module contains classes to control the camera and take pictures.""" + +from __future__ import annotations + +from _typing import WriteableBuffer + +class Camera: + """The class to control camera. + + Usage:: + + import board + import sdioio + import storage + import camera + + sd = sdioio.SDCard( + clock=board.SDIO_CLOCK, + command=board.SDIO_COMMAND, + data=board.SDIO_DATA, + frequency=25000000) + vfs = storage.VfsFat(sd) + storage.mount(vfs, '/sd') + + cam = camera.Camera() + + buffer = bytearray(512 * 1024) + file = open("/sd/image.jpg","wb") + size = cam.take_picture(buffer, width=1920, height=1080, format=camera.ImageFormat.JPG) + file.write(buffer, size) + file.close()""" + + def __init__(self) -> None: + """Initialize camera.""" + ... + def deinit(self) -> None: + """De-initialize camera.""" + ... + def take_picture(self, buf: WriteableBuffer, format: ImageFormat) -> int: + """Take picture and save to ``buf`` in the given ``format``. The size of the picture + taken is ``width`` by ``height`` in pixels. + + :return: the number of bytes written into buf + :rtype: int""" + ... + +class ImageFormat: + """Image format""" + + def __init__(self) -> None: + """Enum-like class to define the image format.""" + JPG: ImageFormat + """JPG format.""" + + RGB565: ImageFormat + """RGB565 format.""" diff --git a/stubs/canio/__init__.pyi b/stubs/canio/__init__.pyi new file mode 100644 index 0000000..a798d3d --- /dev/null +++ b/stubs/canio/__init__.pyi @@ -0,0 +1,277 @@ +"""CAN bus access + +The `canio` module contains low level classes to support the CAN bus +protocol. + +CAN and Listener classes change hardware state and should be deinitialized when they +are no longer needed if the program continues after use. To do so, either +call :py:meth:`!deinit` or use a context manager. See +:ref:`lifetime-and-contextmanagers` for more info. + +For example:: + + import canio + from board import * + + can = canio.CAN(board.CAN_RX, board.CAN_TX, baudrate=1000000) + message = canio.Message(id=0x0408, data=b"adafruit") + can.send(message) + can.deinit() + +This example will write the data 'adafruit' onto the CAN bus to any +device listening for message id 0x0408. + +A CAN bus involves a transceiver, which is often a separate chip with a "standby" pin. +If your board has a CAN_STANDBY pin, ensure to set it to an output with the value False +to enable the transceiver. + +Other implementations of the CAN device may exist (for instance, attached +via an SPI bus). If so their constructor arguments may differ, but +otherwise we encourage implementors to follow the API that the core uses. +""" + +from __future__ import annotations + +from types import TracebackType +from typing import Optional, Sequence, Type, Union + +import microcontroller + +class BusState: + """The state of the CAN bus""" + + ERROR_ACTIVE: object + """The bus is in the normal (active) state""" + + ERROR_WARNING: object + """The bus is in the normal (active) state, but a moderate number of errors have occurred recently. + + NOTE: Not all implementations may use ERROR_WARNING. Do not rely on seeing ERROR_WARNING before ERROR_PASSIVE.""" + + ERROR_PASSIVE: object + """The bus is in the passive state due to the number of errors that have occurred recently. + + This device will acknowledge packets it receives, but cannot transmit messages. + If additional errors occur, this device may progress to BUS_OFF. + If it successfully acknowledges other packets on the bus, it can return to ERROR_WARNING or ERROR_ACTIVE and transmit packets. + """ + + BUS_OFF: object + """The bus has turned off due to the number of errors that have + occurred recently. It must be restarted before it will send or receive + packets. This device will neither send or acknowledge packets on the bus.""" + +class CAN: + """CAN bus protocol""" + + def __init__( + self, + tx: microcontroller.Pin, + rx: microcontroller.Pin, + *, + baudrate: int = 250000, + loopback: bool = False, + silent: bool = False, + auto_restart: bool = False, + ) -> None: + """A common shared-bus protocol. The rx and tx pins are generally + connected to a transceiver which controls the H and L pins on a + shared bus. + + :param ~microcontroller.Pin rx: the pin to receive with + :param ~microcontroller.Pin tx: the pin to transmit with + :param int baudrate: The bit rate of the bus in Hz. All devices on the bus must agree on this value. + :param bool loopback: When True the ``rx`` pin's value is ignored, and the device receives the packets it sends. + :param bool silent: When True the ``tx`` pin is always driven to the high logic level. This mode can be used to "sniff" a CAN bus without interfering. + :param bool auto_restart: If True, will restart communications after entering bus-off state + """ + ... + auto_restart: bool + """If True, will restart communications after entering bus-off state""" + + baudrate: int + """The baud rate (read-only)""" + + transmit_error_count: int + """The number of transmit errors (read-only). Increased for a detected transmission error, decreased for successful transmission. Limited to the range from 0 to 255 inclusive. Also called TEC.""" + + receive_error_count: int + """The number of receive errors (read-only). Increased for a detected reception error, decreased for successful reception. Limited to the range from 0 to 255 inclusive. Also called REC.""" + + state: BusState + """The current state of the bus. (read-only)""" + def restart(self) -> None: + """If the device is in the bus off state, restart it.""" + ... + def listen( + self, matches: Optional[Sequence[Match]] = None, *, timeout: float = 10 + ) -> Listener: + """Start receiving messages that match any one of the filters. + + Creating a listener is an expensive operation and can interfere with reception of messages by other listeners. + + There is an implementation-defined maximum number of listeners and limit to the complexity of the filters. + + If the hardware cannot support all the requested matches, a ValueError is raised. Note that generally there are some number of hardware filters shared among all fifos. + + A message can be received by at most one Listener. If more than one listener matches a message, it is undefined which one actually receives it. + + An empty filter list causes all messages to be accepted. + + Timeout dictates how long receive() and next() will block. + + Platform specific notes: + + SAM E5x supports two Listeners. Filter blocks are shared between the two + listeners. There are 4 standard filter blocks and 4 extended filter blocks. + Each block can either match 2 single addresses or a mask of addresses. + The number of filter blocks can be increased, up to a hardware maximum, by + rebuilding CircuitPython, but this decreases the CircuitPython free + memory even if canio is not used. + + STM32F405 supports two Listeners. Filter blocks are shared between the two listeners. + There are 14 filter blocks. Each block can match 2 standard addresses with + mask or 1 extended address with mask. + + ESP32S2 supports one Listener. There is a single filter block, which can either match a + standard address with mask or an extended address with mask. + """ + ... + loopback: bool + """True if the device was created in loopback mode, False + otherwise (read-only)""" + def send(self, message: Union[RemoteTransmissionRequest, Message]) -> None: + """Send a message on the bus with the given data and id. + If the message could not be sent due to a full fifo or a bus error condition, RuntimeError is raised. + """ + ... + silent: bool + """True if the device was created in silent mode, False + otherwise (read-only)""" + def deinit(self) -> None: + """Deinitialize this object, freeing its hardware resources""" + ... + def __enter__(self) -> CAN: + """Returns self, to allow the object to be used in a `with` statement for resource control""" + ... + def __exit__( + self, + unused1: Optional[Type[BaseException]], + unused2: Optional[BaseException], + unused3: Optional[TracebackType], + ) -> None: + """Calls deinit()""" + ... + +class Listener: + """Listens for CAN message + + `canio.Listener` is not constructed directly, but instead by calling + `canio.CAN.listen`. + + In addition to using the `receive` method to retrieve a message or + the `in_waiting` method to check for an available message, a + listener can be used as an iterable, yielding messages until no + message arrives within ``self.timeout`` seconds.""" + + def receive(self) -> Optional[Union[RemoteTransmissionRequest, Message]]: + """Reads a message, after waiting up to ``self.timeout`` seconds + + If no message is received in time, `None` is returned. Otherwise, + a `Message` or `RemoteTransmissionRequest` is returned.""" + ... + def in_waiting(self) -> int: + """Returns the number of messages (including remote + transmission requests) waiting""" + ... + def __iter__(self) -> Listener: + """Returns self + + This method exists so that `Listener` can be used as an + iterable""" + ... + def __next__(self) -> Union[RemoteTransmissionRequest, Message]: + """Reads a message, after waiting up to self.timeout seconds + + If no message is received in time, raises StopIteration. Otherwise, + a Message or is returned. + + This method enables the `Listener` to be used as an + iterable, for instance in a for-loop.""" + ... + def deinit(self) -> None: + """Deinitialize this object, freeing its hardware resources""" + ... + def __enter__(self) -> CAN: + """Returns self, to allow the object to be used in a `with` statement for resource control""" + ... + def __exit__( + self, + unused1: Optional[Type[BaseException]], + unused2: Optional[BaseException], + unused3: Optional[TracebackType], + ) -> None: + """Calls deinit()""" + ... + timeout: float + +class Match: + """Describe CAN bus messages to match""" + + def __init__( + self, id: int, *, mask: Optional[int] = None, extended: bool = False + ) -> None: + """Construct a Match with the given properties. + + If mask is not None, then the filter is for any id which matches all + the nonzero bits in mask. Otherwise, it matches exactly the given id. + If extended is true then only extended ids are matched, otherwise + only standard ids are matched.""" + id: int + """The id to match""" + + mask: int + """The optional mask of ids to match""" + + extended: bool + """True to match extended ids, False to match standard ides""" + +class Message: + def __init__(self, id: int, data: bytes, *, extended: bool = False) -> None: + """Construct a Message to send on a CAN bus. + + :param int id: The numeric ID of the message + :param bytes data: The content of the message + :param bool extended: True if the message has an extended identifier, False if it has a standard identifier + + In CAN, messages can have a length from 0 to 8 bytes. + """ + ... + id: int + """The numeric ID of the message""" + + data: bytes + """The content of the message""" + + extended: bool + """True if the message's id is an extended id""" + +class RemoteTransmissionRequest: + def __init__(self, id: int, length: int, *, extended: bool = False) -> None: + """Construct a RemoteTransmissionRequest to send on a CAN bus. + + :param int id: The numeric ID of the requested message + :param int length: The length of the requested message + :param bool extended: True if the message has an extended identifier, False if it has a standard identifier + + In CAN, messages can have a length from 0 to 8 bytes. + """ + ... + id: int + """The numeric ID of the message""" + + extended: bool + """True if the message's id is an extended id""" + + length: int + """The length of the requested message.""" diff --git a/stubs/countio/__init__.pyi b/stubs/countio/__init__.pyi new file mode 100644 index 0000000..5012487 --- /dev/null +++ b/stubs/countio/__init__.pyi @@ -0,0 +1,50 @@ +"""Support for edge counting + +The `countio` module contains logic to read and count edge transistions + +.. warning:: This module is not available in some SAMD21 (aka M0) builds. See the + :ref:`module-support-matrix` for more info. + +All classes change hardware state and should be deinitialized when they +are no longer needed if the program continues after use. To do so, either +call :py:meth:`!deinit` or use a context manager. See +:ref:`lifetime-and-contextmanagers` for more info.""" + +from __future__ import annotations + +import microcontroller + +class Counter: + """Counter will keep track of the number of falling edge transistions (pulses) on a + given pin""" + + def __init__(self, pin_a: microcontroller.Pin) -> None: + """Create a Counter object associated with the given pin. It tracks the number of + falling pulses relative when the object is constructed. + + :param ~microcontroller.Pin pin_a: Pin to read pulses from. + + + For example:: + + import countio + import time + from board import * + + pin_counter = countio.Counter(board.D1) + #reset the count after 100 counts + while True: + if pin_counter.count == 100: + pin_counter.reset() + print(pin_counter.count)""" + def deinit(self) -> None: + """Deinitializes the Counter and releases any hardware resources for reuse.""" + def __enter__(self) -> Counter: + """No-op used by Context Managers.""" + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + count: int + """The current count in terms of pulses.""" + def reset(self) -> None: + """Resets the count back to 0.""" diff --git a/stubs/digitalio/__init__.pyi b/stubs/digitalio/__init__.pyi new file mode 100644 index 0000000..7cb0acc --- /dev/null +++ b/stubs/digitalio/__init__.pyi @@ -0,0 +1,158 @@ +"""Basic digital pin support + +The `digitalio` module contains classes to provide access to basic digital IO. + +All classes change hardware state and should be deinitialized when they +are no longer needed if the program continues after use. To do so, either +call :py:meth:`!deinit` or use a context manager. See +:ref:`lifetime-and-contextmanagers` for more info. + +For example:: + + import digitalio + from board import * + + pin = digitalio.DigitalInOut(D13) + print(pin.value) + +This example will initialize the the device, read +:py:data:`~digitalio.DigitalInOut.value` and then +:py:meth:`~digitalio.DigitalInOut.deinit` the hardware. + +Here is blinky:: + + import digitalio + from board import * + import time + + led = digitalio.DigitalInOut(D13) + led.direction = digitalio.Direction.OUTPUT + while True: + led.value = True + time.sleep(0.1) + led.value = False + time.sleep(0.1)""" + +from __future__ import annotations + +from typing import Optional + +import microcontroller + +class DriveMode: + """Defines the drive mode of a digital pin""" + + def __init__(self) -> None: + """Enum-like class to define the drive mode used when outputting + digital values.""" + ... + PUSH_PULL: DriveMode + """Output both high and low digital values""" + + OPEN_DRAIN: DriveMode + """Output low digital values but go into high z for digital high. This is + useful for i2c and other protocols that share a digital line.""" + +class DigitalInOut: + """Digital input and output + + A DigitalInOut is used to digitally control I/O pins. For analog control of + a pin, see the :py:class:`analogio.AnalogIn` and + :py:class:`analogio.AnalogOut` classes.""" + + def __init__(self, pin: microcontroller.Pin) -> None: + """Create a new DigitalInOut object associated with the pin. Defaults to input + with no pull. Use :py:meth:`switch_to_input` and + :py:meth:`switch_to_output` to change the direction. + + :param ~microcontroller.Pin pin: The pin to control""" + ... + def deinit(self) -> None: + """Turn off the DigitalInOut and release the pin for other use.""" + ... + def __enter__(self) -> DigitalInOut: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + def switch_to_output( + self, value: bool = False, drive_mode: DriveMode = DriveMode.PUSH_PULL + ) -> None: + """Set the drive mode and value and then switch to writing out digital + values. + + :param bool value: default value to set upon switching + :param ~digitalio.DriveMode drive_mode: drive mode for the output + """ + ... + def switch_to_input(self, pull: Optional[Pull] = None) -> None: + """Set the pull and then switch to read in digital values. + + :param Pull pull: pull configuration for the input + + Example usage:: + + import digitalio + import board + + switch = digitalio.DigitalInOut(board.SLIDE_SWITCH) + switch.switch_to_input(pull=digitalio.Pull.UP) + # Or, after switch_to_input + switch.pull = digitalio.Pull.UP + print(switch.value)""" + ... + direction: Direction + """The direction of the pin. + + Setting this will use the defaults from the corresponding + :py:meth:`switch_to_input` or :py:meth:`switch_to_output` method. If + you want to set pull, value or drive mode prior to switching, then use + those methods instead.""" + + value: bool + """The digital logic level of the pin.""" + + drive_mode: DriveMode + """The pin drive mode. One of: + + - `digitalio.DriveMode.PUSH_PULL` + - `digitalio.DriveMode.OPEN_DRAIN`""" + + pull: Optional[Pull] + """The pin pull direction. One of: + + - `digitalio.Pull.UP` + - `digitalio.Pull.DOWN` + - `None` + + :raises AttributeError: if `direction` is :py:data:`~digitalio.Direction.OUTPUT`.""" + +class Direction: + """Defines the direction of a digital pin""" + + def __init__(self) -> None: + """Enum-like class to define which direction the digital values are + going.""" + ... + INPUT: Direction + """Read digital data in""" + + OUTPUT: Direction + """Write digital data out""" + +class Pull: + """Defines the pull of a digital input pin""" + + def __init__(self) -> None: + """Enum-like class to define the pull value, if any, used while reading + digital values in.""" + ... + UP: Pull + """When the input line isn't being driven the pull up can pull the state + of the line high so it reads as true.""" + + DOWN: Pull + """When the input line isn't being driven the pull down can pull the + state of the line low so it reads as false.""" diff --git a/stubs/displayio/__init__.pyi b/stubs/displayio/__init__.pyi new file mode 100644 index 0000000..da126e4 --- /dev/null +++ b/stubs/displayio/__init__.pyi @@ -0,0 +1,735 @@ +"""Native helpers for driving displays + +The `displayio` module contains classes to manage display output +including synchronizing with refresh rates and partial updating.""" + +from __future__ import annotations + +import typing +from typing import Optional, Tuple, Union + +import busio +import microcontroller +import vectorio +from _typing import ReadableBuffer, WriteableBuffer + +def release_displays() -> None: + """Releases any actively used displays so their busses and pins can be used again. This will also + release the builtin display on boards that have one. You will need to reinitialize it yourself + afterwards. This may take seconds to complete if an active EPaperDisplay is refreshing. + + Use this once in your code.py if you initialize a display. Place it right before the + initialization so the display is active as long as possible.""" + ... + +class Bitmap: + """Stores values of a certain size in a 2D array""" + + def __init__(self, width: int, height: int, value_count: int) -> None: + """Create a Bitmap object with the given fixed size. Each pixel stores a value that is used to + index into a corresponding palette. This enables differently colored sprites to share the + underlying Bitmap. value_count is used to minimize the memory used to store the Bitmap. + + :param int width: The number of values wide + :param int height: The number of values high + :param int value_count: The number of possible pixel values.""" + ... + width: int + """Width of the bitmap. (read only)""" + + height: int + """Height of the bitmap. (read only)""" + def __getitem__(self, index: Union[Tuple[int, int], int]) -> int: + """Returns the value at the given index. The index can either be an x,y tuple or an int equal + to ``y * width + x``. + + This allows you to:: + + print(bitmap[0,1])""" + ... + def __setitem__(self, index: Union[Tuple[int, int], int], value: int) -> None: + """Sets the value at the given index. The index can either be an x,y tuple or an int equal + to ``y * width + x``. + + This allows you to:: + + bitmap[0,1] = 3""" + ... + def blit( + self, + x: int, + y: int, + source_bitmap: Bitmap, + *, + x1: int, + y1: int, + x2: int, + y2: int, + skip_index: int + ) -> None: + """Inserts the source_bitmap region defined by rectangular boundaries + (x1,y1) and (x2,y2) into the bitmap at the specified (x,y) location. + + :param int x: Horizontal pixel location in bitmap where source_bitmap upper-left + corner will be placed + :param int y: Vertical pixel location in bitmap where source_bitmap upper-left + corner will be placed + :param bitmap source_bitmap: Source bitmap that contains the graphical region to be copied + :param int x1: Minimum x-value for rectangular bounding box to be copied from the source bitmap + :param int y1: Minimum y-value for rectangular bounding box to be copied from the source bitmap + :param int x2: Maximum x-value (exclusive) for rectangular bounding box to be copied from the source bitmap + :param int y2: Maximum y-value (exclusive) for rectangular bounding box to be copied from the source bitmap + :param int skip_index: bitmap palette index in the source that will not be copied, + set to None to copy all pixels""" + ... + def fill(self, value: int) -> None: + """Fills the bitmap with the supplied palette index value.""" + ... + +class ColorConverter: + """Converts one color format to another.""" + + def __init__(self, *, dither: bool = False) -> None: + """Create a ColorConverter object to convert color formats. Only supports RGB888 to RGB565 + currently. + :param bool dither: Adds random noise to dither the output image""" + ... + def convert(self, color: int) -> int: + """Converts the given RGB888 color to RGB565""" + ... + dither: bool + """When true the color converter dithers the output by adding random noise when + truncating to display bitdepth""" + def make_transparent(self, pixel: int) -> None: + """Sets a pixel to not opaque.""" + def make_opaque(self, pixel: int) -> None: + """Sets a pixel to opaque.""" + +_DisplayBus = Union["FourWire", "ParallelBus", "I2CDisplay"] +""":py:class:`FourWire`, :py:class:`ParallelBus` or :py:class:`I2CDisplay`""" + +class Display: + """Manage updating a display over a display bus + + This initializes a display and connects it into CircuitPython. Unlike other + objects in CircuitPython, Display objects live until `displayio.release_displays()` + is called. This is done so that CircuitPython can use the display itself. + + Most people should not use this class directly. Use a specific display driver instead that will + contain the initialization sequence at minimum.""" + + def __init__( + self, + display_bus: _DisplayBus, + init_sequence: ReadableBuffer, + *, + width: int, + height: int, + colstart: int = 0, + rowstart: int = 0, + rotation: int = 0, + color_depth: int = 16, + grayscale: bool = False, + pixels_in_byte_share_row: bool = True, + bytes_per_cell: int = 1, + reverse_pixels_in_byte: bool = False, + set_column_command: int = 0x2A, + set_row_command: int = 0x2B, + write_ram_command: int = 0x2C, + set_vertical_scroll: int = 0, + backlight_pin: Optional[microcontroller.Pin] = None, + brightness_command: Optional[int] = None, + brightness: float = 1.0, + auto_brightness: bool = False, + single_byte_bounds: bool = False, + data_as_commands: bool = False, + auto_refresh: bool = True, + native_frames_per_second: int = 60 + ) -> None: + r"""Create a Display object on the given display bus (`FourWire`, `ParallelBus` or `I2CDisplay`). + + The ``init_sequence`` is bitpacked to minimize the ram impact. Every command begins with a + command byte followed by a byte to determine the parameter count and delay. When the top bit + of the second byte is 1 (0x80), a delay will occur after the command parameters are sent. + The remaining 7 bits are the parameter count excluding any delay byte. The bytes following + are the parameters. When the delay bit is set, a single byte after the parameters specifies + the delay duration in milliseconds. The value 0xff will lead to an extra long 500 ms delay + instead of 255 ms. The next byte will begin a new command definition. + Here is an example: + + .. code-block:: python + + init_sequence = (b"\xe1\x0f\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F" # Set Gamma + b"\x11\x80\x78"# Exit Sleep then delay 0x78 (120ms) + b"\x29\x81\xaa\x78"# Display on then delay 0x78 (120ms) + ) + display = displayio.Display(display_bus, init_sequence, width=320, height=240) + + The first command is 0xe1 with 15 (0xf) parameters following. The second is 0x11 with 0 + parameters and a 120ms (0x78) delay. The third command is 0x29 with one parameter 0xaa and a + 120ms delay (0x78). Multiple byte literals (b"") are merged together on load. The parens + are needed to allow byte literals on subsequent lines. + + The initialization sequence should always leave the display memory access inline with the scan + of the display to minimize tearing artifacts. + + :param display_bus: The bus that the display is connected to + :type _DisplayBus: FourWire, ParallelBus or I2CDisplay + :param ~_typing.ReadableBuffer init_sequence: Byte-packed initialization sequence. + :param int width: Width in pixels + :param int height: Height in pixels + :param int colstart: The index if the first visible column + :param int rowstart: The index if the first visible row + :param int rotation: The rotation of the display in degrees clockwise. Must be in 90 degree increments (0, 90, 180, 270) + :param int color_depth: The number of bits of color per pixel transmitted. (Some displays + support 18 bit but 16 is easier to transmit. The last bit is extrapolated.) + :param bool grayscale: True if the display only shows a single color. + :param bool pixels_in_byte_share_row: True when pixels are less than a byte and a byte includes pixels from the same row of the display. When False, pixels share a column. + :param int bytes_per_cell: Number of bytes per addressable memory location when color_depth < 8. When greater than one, bytes share a row or column according to pixels_in_byte_share_row. + :param bool reverse_pixels_in_byte: Reverses the pixel order within each byte when color_depth < 8. Does not apply across multiple bytes even if there is more than one byte per cell (bytes_per_cell.) + :param bool reverse_bytes_in_word: Reverses the order of bytes within a word when color_depth == 16 + :param int set_column_command: Command used to set the start and end columns to update + :param int set_row_command: Command used so set the start and end rows to update + :param int write_ram_command: Command used to write pixels values into the update region. Ignored if data_as_commands is set. + :param int set_vertical_scroll: Command used to set the first row to show + :param microcontroller.Pin backlight_pin: Pin connected to the display's backlight + :param int brightness_command: Command to set display brightness. Usually available in OLED controllers. + :param float brightness: Initial display brightness. This value is ignored if auto_brightness is True. + :param bool auto_brightness: If True, brightness is controlled via an ambient light sensor or other mechanism. + :param bool single_byte_bounds: Display column and row commands use single bytes + :param bool data_as_commands: Treat all init and boundary data as SPI commands. Certain displays require this. + :param bool SH1107_addressing: Special quirk for SH1107, use upper/lower column set and page set + :param bool auto_refresh: Automatically refresh the screen + :param int native_frames_per_second: Number of display refreshes per second that occur with the given init_sequence. + :param bool backlight_on_high: If True, pulling the backlight pin high turns the backlight on.""" + ... + def show(self, group: Group) -> None: + """Switches to displaying the given group of layers. When group is None, the default + CircuitPython terminal will be shown. + + :param Group group: The group to show.""" + ... + def refresh( + self, + *, + target_frames_per_second: Optional[int] = None, + minimum_frames_per_second: int = 1 + ) -> bool: + """When auto refresh is off, waits for the target frame rate and then refreshes the display, + returning True. If the call has taken too long since the last refresh call for the given + target frame rate, then the refresh returns False immediately without updating the screen to + hopefully help getting caught up. + + If the time since the last successful refresh is below the minimum frame rate, then an + exception will be raised. Set ``minimum_frames_per_second`` to 0 to disable. + + When auto refresh is off, ``display.refresh()`` or ``display.refresh(target_frames_per_second=None)`` + will update the display immediately. + + When auto refresh is on, updates the display immediately. (The display will also update + without calls to this.) + + :param int target_frames_per_second: How many times a second `refresh` should be called and the screen updated. + Set to `None` for immediate refresh. + :param int minimum_frames_per_second: The minimum number of times the screen should be updated per second.""" + ... + auto_refresh: bool + """True when the display is refreshed automatically.""" + + brightness: float + """The brightness of the display as a float. 0.0 is off and 1.0 is full brightness. When + `auto_brightness` is True, the value of `brightness` will change automatically. + If `brightness` is set, `auto_brightness` will be disabled and will be set to False.""" + + auto_brightness: bool + """True when the display brightness is adjusted automatically, based on an ambient + light sensor or other method. Note that some displays may have this set to True by default, + but not actually implement automatic brightness adjustment. `auto_brightness` is set to False + if `brightness` is set manually.""" + + width: int + """Gets the width of the board""" + + height: int + """Gets the height of the board""" + + rotation: int + """The rotation of the display as an int in degrees.""" + + bus: _DisplayBus + """The bus being used by the display""" + def fill_row(self, y: int, buffer: WriteableBuffer) -> WriteableBuffer: + """Extract the pixels from a single row + + :param int y: The top edge of the area + :param ~_typing.WriteableBuffer buffer: The buffer in which to place the pixel data""" + ... + +class EPaperDisplay: + """Manage updating an epaper display over a display bus + + This initializes an epaper display and connects it into CircuitPython. Unlike other + objects in CircuitPython, EPaperDisplay objects live until `displayio.release_displays()` + is called. This is done so that CircuitPython can use the display itself. + + Most people should not use this class directly. Use a specific display driver instead that will + contain the startup and shutdown sequences at minimum.""" + + def __init__( + self, + display_bus: _DisplayBus, + start_sequence: ReadableBuffer, + stop_sequence: ReadableBuffer, + *, + width: int, + height: int, + ram_width: int, + ram_height: int, + colstart: int = 0, + rowstart: int = 0, + rotation: int = 0, + set_column_window_command: Optional[int] = None, + set_row_window_command: Optional[int] = None, + single_byte_bounds: bool = False, + write_black_ram_command: int, + black_bits_inverted: bool = False, + write_color_ram_command: Optional[int] = None, + color_bits_inverted: bool = False, + highlight_color: int = 0x000000, + refresh_display_command: int, + refresh_time: float = 40, + busy_pin: Optional[microcontroller.Pin] = None, + busy_state: bool = True, + seconds_per_frame: float = 180, + always_toggle_chip_select: bool = False, + grayscale: bool = False + ) -> None: + """Create a EPaperDisplay object on the given display bus (`displayio.FourWire` or `displayio.ParallelBus`). + + The ``start_sequence`` and ``stop_sequence`` are bitpacked to minimize the ram impact. Every + command begins with a command byte followed by a byte to determine the parameter count and + delay. When the top bit of the second byte is 1 (0x80), a delay will occur after the command + parameters are sent. The remaining 7 bits are the parameter count excluding any delay + byte. The bytes following are the parameters. When the delay bit is set, a single byte after + the parameters specifies the delay duration in milliseconds. The value 0xff will lead to an + extra long 500 ms delay instead of 255 ms. The next byte will begin a new command definition. + + :param display_bus: The bus that the display is connected to + :type _DisplayBus: displayio.FourWire or displayio.ParallelBus + :param ~_typing.ReadableBuffer start_sequence: Byte-packed initialization sequence. + :param ~_typing.ReadableBuffer stop_sequence: Byte-packed initialization sequence. + :param int width: Width in pixels + :param int height: Height in pixels + :param int ram_width: RAM width in pixels + :param int ram_height: RAM height in pixels + :param int colstart: The index if the first visible column + :param int rowstart: The index if the first visible row + :param int rotation: The rotation of the display in degrees clockwise. Must be in 90 degree increments (0, 90, 180, 270) + :param int set_column_window_command: Command used to set the start and end columns to update + :param int set_row_window_command: Command used so set the start and end rows to update + :param int set_current_column_command: Command used to set the current column location + :param int set_current_row_command: Command used to set the current row location + :param int write_black_ram_command: Command used to write pixels values into the update region + :param bool black_bits_inverted: True if 0 bits are used to show black pixels. Otherwise, 1 means to show black. + :param int write_color_ram_command: Command used to write pixels values into the update region + :param bool color_bits_inverted: True if 0 bits are used to show the color. Otherwise, 1 means to show color. + :param int highlight_color: RGB888 of source color to highlight with third ePaper color. + :param int refresh_display_command: Command used to start a display refresh + :param float refresh_time: Time it takes to refresh the display before the stop_sequence should be sent. Ignored when busy_pin is provided. + :param microcontroller.Pin busy_pin: Pin used to signify the display is busy + :param bool busy_state: State of the busy pin when the display is busy + :param float seconds_per_frame: Minimum number of seconds between screen refreshes + :param bool always_toggle_chip_select: When True, chip select is toggled every byte + :param bool grayscale: When true, the color ram is the low bit of 2-bit grayscale""" + ... + def show(self, group: Group) -> None: + """Switches to displaying the given group of layers. When group is None, the default + CircuitPython terminal will be shown. + + :param Group group: The group to show.""" + ... + def refresh(self) -> None: + """Refreshes the display immediately or raises an exception if too soon. Use + ``time.sleep(display.time_to_refresh)`` to sleep until a refresh can occur.""" + ... + time_to_refresh: float + """Time, in fractional seconds, until the ePaper display can be refreshed.""" + + busy: bool + """True when the display is refreshing. This uses the ``busy_pin`` when available or the + ``refresh_time`` otherwise.""" + + width: int + """Gets the width of the display in pixels""" + + height: int + """Gets the height of the display in pixels""" + + rotation: int + """The rotation of the display as an int in degrees.""" + + bus: _DisplayBus + """The bus being used by the display""" + +class FourWire: + """Manage updating a display over SPI four wire protocol in the background while Python code runs. + It doesn't handle display initialization.""" + + def __init__( + self, + spi_bus: busio.SPI, + *, + command: microcontroller.Pin, + chip_select: microcontroller.Pin, + reset: Optional[microcontroller.Pin] = None, + baudrate: int = 24000000, + polarity: int = 0, + phase: int = 0 + ) -> None: + """Create a FourWire object associated with the given pins. + + The SPI bus and pins are then in use by the display until `displayio.release_displays()` is + called even after a reload. (It does this so CircuitPython can use the display after your code + is done.) So, the first time you initialize a display bus in code.py you should call + :py:func`displayio.release_displays` first, otherwise it will error after the first code.py run. + + :param busio.SPI spi_bus: The SPI bus that make up the clock and data lines + :param microcontroller.Pin command: Data or command pin + :param microcontroller.Pin chip_select: Chip select pin + :param microcontroller.Pin reset: Reset pin. When None only software reset can be used + :param int baudrate: Maximum baudrate in Hz for the display on the bus + :param int polarity: the base state of the clock line (0 or 1) + :param int phase: the edge of the clock that data is captured. First (0) + or second (1). Rising or falling depends on clock polarity.""" + ... + def reset(self) -> None: + """Performs a hardware reset via the reset pin. Raises an exception if called when no reset pin + is available.""" + ... + def send( + self, command: int, data: FourWire, *, toggle_every_byte: bool = False + ) -> None: + """Sends the given command value followed by the full set of data. Display state, such as + vertical scroll, set via ``send`` may or may not be reset once the code is done.""" + ... + +class Group: + """Manage a group of sprites and groups and how they are inter-related.""" + + def __init__( + self, *, max_size: int = 4, scale: int = 1, x: int = 0, y: int = 0 + ) -> None: + """Create a Group of a given size and scale. Scale is in one dimension. For example, scale=2 + leads to a layer's pixel being 2x2 pixels when in the group. + + :param int max_size: The maximum group size. + :param int scale: Scale of layer pixels in one dimension. + :param int x: Initial x position within the parent. + :param int y: Initial y position within the parent.""" + ... + hidden: bool + """True when the Group and all of it's layers are not visible. When False, the Group's layers + are visible if they haven't been hidden.""" + + scale: int + """Scales each pixel within the Group in both directions. For example, when scale=2 each pixel + will be represented by 2x2 pixels.""" + + x: int + """X position of the Group in the parent.""" + + y: int + """Y position of the Group in the parent.""" + def append(self, layer: Union[vectorio.VectorShape, Group, TileGrid]) -> None: + """Append a layer to the group. It will be drawn above other layers.""" + ... + def insert( + self, index: int, layer: Union[vectorio.VectorShape, Group, TileGrid] + ) -> None: + """Insert a layer into the group.""" + ... + def index(self, layer: Union[vectorio.VectorShape, Group, TileGrid]) -> int: + """Returns the index of the first copy of layer. Raises ValueError if not found.""" + ... + def pop(self, i: int = -1) -> Union[vectorio.VectorShape, Group, TileGrid]: + """Remove the ith item and return it.""" + ... + def remove(self, layer: Union[vectorio.VectorShape, Group, TileGrid]) -> None: + """Remove the first copy of layer. Raises ValueError if it is not present.""" + ... + def __bool__(self) -> bool: ... + def __len__(self) -> int: + """Returns the number of layers in a Group""" + ... + def __getitem__(self, index: int) -> Union[vectorio.VectorShape, Group, TileGrid]: + """Returns the value at the given index. + + This allows you to:: + + print(group[0])""" + ... + def __setitem__( + self, index: int, value: Union[vectorio.VectorShape, Group, TileGrid] + ) -> None: + """Sets the value at the given index. + + This allows you to:: + + group[0] = sprite""" + ... + def __delitem__(self, index: int) -> None: + """Deletes the value at the given index. + + This allows you to:: + + del group[0]""" + ... + +class I2CDisplay: + """Manage updating a display over I2C in the background while Python code runs. + It doesn't handle display initialization.""" + + def __init__( + self, + i2c_bus: busio.I2C, + *, + device_address: int, + reset: Optional[microcontroller.Pin] = None + ) -> None: + """Create a I2CDisplay object associated with the given I2C bus and reset pin. + + The I2C bus and pins are then in use by the display until `displayio.release_displays()` is + called even after a reload. (It does this so CircuitPython can use the display after your code + is done.) So, the first time you initialize a display bus in code.py you should call + :py:func`displayio.release_displays` first, otherwise it will error after the first code.py run. + + :param busio.I2C i2c_bus: The I2C bus that make up the clock and data lines + :param int device_address: The I2C address of the device + :param microcontroller.Pin reset: Reset pin. When None only software reset can be used""" + ... + def reset(self) -> None: + """Performs a hardware reset via the reset pin. Raises an exception if called when no reset pin + is available.""" + ... + def send(self, command: int, data: ReadableBuffer) -> None: + """Sends the given command value followed by the full set of data. Display state, such as + vertical scroll, set via ``send`` may or may not be reset once the code is done.""" + ... + +class OnDiskBitmap: + """Loads values straight from disk. This minimizes memory use but can lead to + much slower pixel load times. These load times may result in frame tearing where only part of + the image is visible. + + It's easiest to use on a board with a built in display such as the `Hallowing M0 Express + `_. + + .. code-block:: Python + + import board + import displayio + import time + import pulseio + + board.DISPLAY.auto_brightness = False + board.DISPLAY.brightness = 0 + splash = displayio.Group() + board.DISPLAY.show(splash) + + with open("/sample.bmp", "rb") as f: + odb = displayio.OnDiskBitmap(f) + face = displayio.TileGrid(odb, pixel_shader=displayio.ColorConverter()) + splash.append(face) + # Wait for the image to load. + board.DISPLAY.refresh(target_frames_per_second=60) + + # Fade up the backlight + for i in range(100): + board.DISPLAY.brightness = 0.01 * i + time.sleep(0.05) + + # Wait forever + while True: + pass""" + + def __init__(self, file: typing.BinaryIO) -> None: + """Create an OnDiskBitmap object with the given file. + + :param file file: The open bitmap file""" + ... + width: int + """Width of the bitmap. (read only)""" + + height: int + """Height of the bitmap. (read only)""" + +class Palette: + """Map a pixel palette_index to a full color. Colors are transformed to the display's format internally to + save memory.""" + + def __init__(self, color_count: int) -> None: + """Create a Palette object to store a set number of colors. + + :param int color_count: The number of colors in the Palette""" + ... + def __bool__(self) -> bool: ... + def __len__(self) -> int: + """Returns the number of colors in a Palette""" + ... + def __getitem__(self, index: int) -> Optional[int]: + r"""Return the pixel color at the given index as an integer.""" + ... + def __setitem__( + self, index: int, value: Union[int, ReadableBuffer, Tuple[int, int, int]] + ) -> None: + r"""Sets the pixel color at the given index. The index should be an integer in the range 0 to color_count-1. + + The value argument represents a color, and can be from 0x000000 to 0xFFFFFF (to represent an RGB value). + Value can be an int, bytes (3 bytes (RGB) or 4 bytes (RGB + pad byte)), bytearray, + or a tuple or list of 3 integers. + + This allows you to:: + + palette[0] = 0xFFFFFF # set using an integer + palette[1] = b'\xff\xff\x00' # set using 3 bytes + palette[2] = b'\xff\xff\x00\x00' # set using 4 bytes + palette[3] = bytearray(b'\x00\x00\xFF') # set using a bytearay of 3 or 4 bytes + palette[4] = (10, 20, 30) # set using a tuple of 3 integers""" + ... + def make_transparent(self, palette_index: int) -> None: ... + def make_opaque(self, palette_index: int) -> None: ... + +class ParallelBus: + """Manage updating a display over 8-bit parallel bus in the background while Python code runs. This + protocol may be refered to as 8080-I Series Parallel Interface in datasheets. It doesn't handle + display initialization.""" + + def __init__( + self, + *, + data0: microcontroller.Pin, + command: microcontroller.Pin, + chip_select: microcontroller.Pin, + write: microcontroller.Pin, + read: microcontroller.Pin, + reset: microcontroller.Pin + ) -> None: + """Create a ParallelBus object associated with the given pins. The bus is inferred from data0 + by implying the next 7 additional pins on a given GPIO port. + + The parallel bus and pins are then in use by the display until `displayio.release_displays()` + is called even after a reload. (It does this so CircuitPython can use the display after your + code is done.) So, the first time you initialize a display bus in code.py you should call + :py:func`displayio.release_displays` first, otherwise it will error after the first code.py run. + + :param microcontroller.Pin data0: The first data pin. The rest are implied + :param microcontroller.Pin command: Data or command pin + :param microcontroller.Pin chip_select: Chip select pin + :param microcontroller.Pin write: Write pin + :param microcontroller.Pin read: Read pin + :param microcontroller.Pin reset: Reset pin""" + ... + def reset(self) -> None: + """Performs a hardware reset via the reset pin. Raises an exception if called when no reset pin + is available.""" + ... + def send(self, command: int, data: ReadableBuffer) -> None: + """Sends the given command value followed by the full set of data. Display state, such as + vertical scroll, set via ``send`` may or may not be reset once the code is done.""" + ... + +class Shape: + """Represents a shape made by defining boundaries that may be mirrored.""" + + def __init__( + self, width: int, height: int, *, mirror_x: bool = False, mirror_y: bool = False + ) -> None: + """Create a Shape object with the given fixed size. Each pixel is one bit and is stored by the + column boundaries of the shape on each row. Each row's boundary defaults to the full row. + + :param int width: The number of pixels wide + :param int height: The number of pixels high + :param bool mirror_x: When true the left boundary is mirrored to the right. + :param bool mirror_y: When true the top boundary is mirrored to the bottom.""" + ... + def set_boundary(self, y: int, start_x: int, end_x: int) -> None: + """Loads pre-packed data into the given row.""" + ... + +class TileGrid: + """A grid of tiles sourced out of one bitmap + + Position a grid of tiles sourced from a bitmap and pixel_shader combination. Multiple grids + can share bitmaps and pixel shaders. + + A single tile grid is also known as a Sprite.""" + + def __init__( + self, + bitmap: Bitmap, + *, + pixel_shader: Union[ColorConverter, Palette], + width: int = 1, + height: int = 1, + tile_width: Optional[int] = None, + tile_height: Optional[int] = None, + default_tile: int = 0, + x: int = 0, + y: int = 0 + ) -> None: + """Create a TileGrid object. The bitmap is source for 2d pixels. The pixel_shader is used to + convert the value and its location to a display native pixel color. This may be a simple color + palette lookup, a gradient, a pattern or a color transformer. + + tile_width and tile_height match the height of the bitmap by default. + + :param Bitmap bitmap: The bitmap storing one or more tiles. + :param ColorConverter or Palette pixel_shader: The pixel shader that produces colors from values + :param int width: Width of the grid in tiles. + :param int height: Height of the grid in tiles. + :param int tile_width: Width of a single tile in pixels. Defaults to the full Bitmap and must evenly divide into the Bitmap's dimensions. + :param int tile_height: Height of a single tile in pixels. Defaults to the full Bitmap and must evenly divide into the Bitmap's dimensions. + :param int default_tile: Default tile index to show. + :param int x: Initial x position of the left edge within the parent. + :param int y: Initial y position of the top edge within the parent.""" + hidden: bool + """True when the TileGrid is hidden. This may be False even when a part of a hidden Group.""" + + x: int + """X position of the left edge in the parent.""" + + y: int + """Y position of the top edge in the parent.""" + + flip_x: bool + """If true, the left edge rendered will be the right edge of the right-most tile.""" + + flip_y: bool + """If true, the top edge rendered will be the bottom edge of the bottom-most tile.""" + + transpose_xy: bool + """If true, the TileGrid's axis will be swapped. When combined with mirroring, any 90 degree + rotation can be achieved along with the corresponding mirrored version.""" + + pixel_shader: Union[ColorConverter, Palette] + """The pixel shader of the tilegrid.""" + def __getitem__(self, index: Union[Tuple[int, int], int]) -> int: + """Returns the tile index at the given index. The index can either be an x,y tuple or an int equal + to ``y * width + x``. + + This allows you to:: + + print(grid[0])""" + ... + def __setitem__(self, index: Union[Tuple[int, int], int], value: int) -> None: + """Sets the tile index at the given index. The index can either be an x,y tuple or an int equal + to ``y * width + x``. + + This allows you to:: + + grid[0] = 10 + + or:: + + grid[0,0] = 10""" + ... diff --git a/stubs/fontio/__init__.pyi b/stubs/fontio/__init__.pyi new file mode 100644 index 0000000..76d6f48 --- /dev/null +++ b/stubs/fontio/__init__.pyi @@ -0,0 +1,52 @@ +"""Core font related data structures""" + +from __future__ import annotations + +from typing import Tuple + +import displayio + +class BuiltinFont: + """A font built into CircuitPython""" + + def __init__(self) -> None: + """Creation not supported. Available fonts are defined when CircuitPython is built. See the + `Adafruit_CircuitPython_Bitmap_Font `_ + library for dynamically loaded fonts.""" + ... + bitmap: displayio.Bitmap + """Bitmap containing all font glyphs starting with ASCII and followed by unicode. Use + `get_glyph` in most cases. This is useful for use with `displayio.TileGrid` and + `terminalio.Terminal`.""" + def get_bounding_box(self) -> Tuple[int, int]: + """Returns the maximum bounds of all glyphs in the font in a tuple of two values: width, height.""" + ... + def get_glyph(self, codepoint: int) -> Glyph: + """Returns a `fontio.Glyph` for the given codepoint or None if no glyph is available.""" + ... + +class Glyph: + """Storage of glyph info""" + + def __init__( + self, + bitmap: displayio.Bitmap, + tile_index: int, + width: int, + height: int, + dx: int, + dy: int, + shift_x: int, + shift_y: int, + ) -> None: + """Named tuple used to capture a single glyph and its attributes. + + :param bitmap: the bitmap including the glyph + :param tile_index: the tile index within the bitmap + :param width: the width of the glyph's bitmap + :param height: the height of the glyph's bitmap + :param dx: x adjustment to the bitmap's position + :param dy: y adjustment to the bitmap's position + :param shift_x: the x difference to the next glyph + :param shift_y: the y difference to the next glyph""" + ... diff --git a/stubs/framebufferio/__init__.pyi b/stubs/framebufferio/__init__.pyi new file mode 100644 index 0000000..2d0d0a3 --- /dev/null +++ b/stubs/framebufferio/__init__.pyi @@ -0,0 +1,88 @@ +"""Native framebuffer display driving + +The `framebufferio` module contains classes to manage display output +including synchronizing with refresh rates and partial updating. +It is used in conjunction with classes from `displayio` to actually +place items on the display; and classes like `RGBMatrix` to actually +drive the display.""" + +from __future__ import annotations + +import _typing +import displayio +from _typing import WriteableBuffer + +class FramebufferDisplay: + """Manage updating a display with framebuffer in RAM + + This initializes a display and connects it into CircuitPython. Unlike other + objects in CircuitPython, Display objects live until `displayio.release_displays()` + is called. This is done so that CircuitPython can use the display itself.""" + + def __init__( + self, + framebuffer: _typing.FrameBuffer, + *, + rotation: int = 0, + auto_refresh: bool = True + ) -> None: + """Create a Display object with the given framebuffer (a buffer, array, ulab.array, etc) + + :param ~_typing.FrameBuffer framebuffer: The framebuffer that the display is connected to + :param bool auto_refresh: Automatically refresh the screen + :param int rotation: The rotation of the display in degrees clockwise. Must be in 90 degree increments (0, 90, 180, 270)""" + ... + def show(self, group: displayio.Group) -> None: + """Switches to displaying the given group of layers. When group is None, the default + CircuitPython terminal will be shown. + + :param Group group: The group to show.""" + ... + def refresh( + self, *, target_frames_per_second: int = 60, minimum_frames_per_second: int = 1 + ) -> bool: + """When auto refresh is off, waits for the target frame rate and then refreshes the display, + returning True. If the call has taken too long since the last refresh call for the given + target frame rate, then the refresh returns False immediately without updating the screen to + hopefully help getting caught up. + + If the time since the last successful refresh is below the minimum frame rate, then an + exception will be raised. Set minimum_frames_per_second to 0 to disable. + + When auto refresh is on, updates the display immediately. (The display will also update + without calls to this.) + + :param int target_frames_per_second: How many times a second `refresh` should be called and the screen updated. + :param int minimum_frames_per_second: The minimum number of times the screen should be updated per second.""" + ... + auto_refresh: bool + """True when the display is refreshed automatically.""" + + brightness: float + """The brightness of the display as a float. 0.0 is off and 1.0 is full brightness. When + `auto_brightness` is True, the value of `brightness` will change automatically. + If `brightness` is set, `auto_brightness` will be disabled and will be set to False.""" + + auto_brightness: bool + """True when the display brightness is adjusted automatically, based on an ambient + light sensor or other method. Note that some displays may have this set to True by default, + but not actually implement automatic brightness adjustment. `auto_brightness` is set to False + if `brightness` is set manually.""" + + width: int + """Gets the width of the framebuffer""" + + height: int + """Gets the height of the framebuffer""" + + rotation: int + """The rotation of the display as an int in degrees.""" + + framebuffer: _typing.FrameBuffer + """The framebuffer being used by the display""" + def fill_row(self, y: int, buffer: WriteableBuffer) -> WriteableBuffer: + """Extract the pixels from a single row + + :param int y: The top edge of the area + :param ~_typing.WriteableBuffer buffer: The buffer in which to place the pixel data""" + ... diff --git a/stubs/frequencyio/__init__.pyi b/stubs/frequencyio/__init__.pyi new file mode 100644 index 0000000..d332448 --- /dev/null +++ b/stubs/frequencyio/__init__.pyi @@ -0,0 +1,94 @@ +"""Support for frequency based protocols + +.. warning:: This module is not available in SAMD21 builds. See the + :ref:`module-support-matrix` for more info. + +All classes change hardware state and should be deinitialized when they +are no longer needed if the program continues after use. To do so, either +call :py:meth:`!deinit` or use a context manager. See +:ref:`lifetime-and-contextmanagers` for more info. + +For example:: + + import frequencyio + import time + from board import * + + frequency = frequencyio.FrequencyIn(D13) + frequency.capture_period = 15 + time.sleep(0.1) + +This example will initialize the the device, set +:py:data:`~frequencyio.FrequencyIn.capture_period`, and then sleep 0.1 seconds. +CircuitPython will automatically turn off FrequencyIn capture when it resets all +hardware after program completion. Use ``deinit()`` or a ``with`` statement +to do it yourself.""" + +from __future__ import annotations + +import microcontroller + +class FrequencyIn: + """Read a frequency signal + + FrequencyIn is used to measure the frequency, in hertz, of a digital signal + on an incoming pin. Accuracy has shown to be within 10%, if not better. It + is recommended to utilize an average of multiple samples to smooth out readings. + + Frequencies below 1KHz are not currently detectable. + + FrequencyIn will not determine pulse width (use ``PulseIn``).""" + + def __init__(self, pin: microcontroller.Pin, capture_period: int = 10) -> None: + """Create a FrequencyIn object associated with the given pin. + + :param ~microcontroller.Pin pin: Pin to read frequency from. + :param int capture_period: Keyword argument to set the measurement period, in + milliseconds. Default is 10ms; range is 1ms - 500ms. + + Read the incoming frequency from a pin:: + + import frequencyio + import board + + frequency = frequencyio.FrequencyIn(board.D11) + + # Loop while printing the detected frequency + while True: + print(frequency.value) + + # Optional clear() will reset the value + # to zero. Without this, if the incoming + # signal stops, the last reading will remain + # as the value. + frequency.clear()""" + ... + def deinit(self) -> None: + """Deinitialises the FrequencyIn and releases any hardware resources for reuse.""" + ... + def __enter__(self) -> FrequencyIn: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + def pause(self) -> None: + """Pause frequency capture.""" + ... + def resume(self) -> None: + """Resumes frequency capture.""" + ... + def clear(self) -> None: + """Clears the last detected frequency capture value.""" + ... + capture_period: int + """The capture measurement period. Lower incoming frequencies will be measured + more accurately with longer capture periods. Higher frequencies are more + accurate with shorter capture periods. + + .. note:: When setting a new ``capture_period``, all previous capture information is + cleared with a call to ``clear()``.""" + def __get__(self, index: int) -> int: + """Returns the value of the last frequency captured.""" + ... diff --git a/stubs/gamepad/__init__.pyi b/stubs/gamepad/__init__.pyi new file mode 100644 index 0000000..32f2cf1 --- /dev/null +++ b/stubs/gamepad/__init__.pyi @@ -0,0 +1,83 @@ +"""Button handling in the background""" + +from __future__ import annotations + +import digitalio + +class GamePad: + """Scan buttons for presses + + Usage:: + + import board + import digitalio + import gamepad + import time + + B_UP = 1 << 0 + B_DOWN = 1 << 1 + + + pad = gamepad.GamePad( + digitalio.DigitalInOut(board.D10), + digitalio.DigitalInOut(board.D11), + ) + + y = 0 + while True: + buttons = pad.get_pressed() + if buttons & B_UP: + y -= 1 + print(y) + elif buttons & B_DOWN: + y += 1 + print(y) + time.sleep(0.1) + while buttons: + # Wait for all buttons to be released. + buttons = pad.get_pressed() + time.sleep(0.1)""" + + def __init__( + self, + b1: digitalio.DigitalInOut, + b2: digitalio.DigitalInOut, + b3: digitalio.DigitalInOut, + b4: digitalio.DigitalInOut, + b5: digitalio.DigitalInOut, + b6: digitalio.DigitalInOut, + b7: digitalio.DigitalInOut, + b8: digitalio.DigitalInOut, + ) -> None: + """Initializes button scanning routines. + + The ``b1``-``b8`` parameters are ``DigitalInOut`` objects, which + immediately get switched to input with a pull-up, (unless they already + were set to pull-down, in which case they remain so), and then scanned + regularly for button presses. The order is the same as the order of + bits returned by the ``get_pressed`` function. You can re-initialize + it with different keys, then the new object will replace the previous + one. + + The basic feature required here is the ability to poll the keys at + regular intervals (so that de-bouncing is consistent) and fast enough + (so that we don't miss short button presses) while at the same time + letting the user code run normally, call blocking functions and wait + on delays. + + They button presses are accumulated, until the ``get_pressed`` method + is called, at which point the button state is cleared, and the new + button presses start to be recorded.""" + ... + def get_pressed(self) -> int: + """Get the status of buttons pressed since the last call and clear it. + + Returns an 8-bit number, with bits that correspond to buttons, + which have been pressed (or held down) since the last call to this + function set to 1, and the remaining bits set to 0. Then it clears + the button state, so that new button presses (or buttons that are + held down) can be recorded for the next call.""" + ... + def deinit(self) -> None: + """Disable button scanning.""" + ... diff --git a/stubs/gamepadshift/__init__.pyi b/stubs/gamepadshift/__init__.pyi new file mode 100644 index 0000000..68e4d2c --- /dev/null +++ b/stubs/gamepadshift/__init__.pyi @@ -0,0 +1,39 @@ +"""Tracks button presses read through a shift register""" + +from __future__ import annotations + +import digitalio + +class GamePadShift: + """Scan buttons for presses through a shift register""" + + def __init__( + self, + clock: digitalio.DigitalInOut, + data: digitalio.DigitalInOut, + latch: digitalio.DigitalInOut, + ) -> None: + """Initializes button scanning routines. + + The ``clock``, ``data`` and ``latch`` parameters are ``DigitalInOut`` + objects connected to the shift register controlling the buttons. + + They button presses are accumulated, until the ``get_pressed`` method + is called, at which point the button state is cleared, and the new + button presses start to be recorded. + + Only one gamepad (`gamepad.GamePad` or `gamepadshift.GamePadShift`) + may be used at a time.""" + ... + def get_pressed(self) -> int: + """Get the status of buttons pressed since the last call and clear it. + + Returns an 8-bit number, with bits that correspond to buttons, + which have been pressed (or held down) since the last call to this + function set to 1, and the remaining bits set to 0. Then it clears + the button state, so that new button presses (or buttons that are + held down) can be recorded for the next call.""" + ... + def deinit(self) -> None: + """Disable button scanning.""" + ... diff --git a/stubs/gnss/__init__.pyi b/stubs/gnss/__init__.pyi new file mode 100644 index 0000000..489119a --- /dev/null +++ b/stubs/gnss/__init__.pyi @@ -0,0 +1,89 @@ +"""Global Navigation Satellite System + +The `gnss` module contains classes to control the GNSS and acquire positioning information.""" + +from __future__ import annotations + +import time +from typing import List, Union + +class GNSS: + """Get updated positioning information from Global Navigation Satellite System (GNSS) + + Usage:: + + import gnss + import time + + nav = gnss.GNSS([gnss.SatelliteSystem.GPS, gnss.SatelliteSystem.GLONASS]) + last_print = time.monotonic() + while True: + nav.update() + current = time.monotonic() + if current - last_print >= 1.0: + last_print = current + if nav.fix is gnss.PositionFix.INVALID: + print("Waiting for fix...") + continue + print("Latitude: {0:.6f} degrees".format(nav.latitude)) + print("Longitude: {0:.6f} degrees".format(nav.longitude))""" + + def __init__(self, system: Union[SatelliteSystem, List[SatelliteSystem]]) -> None: + """Turn on the GNSS. + + :param system: satellite system to use""" + ... + def deinit(self) -> None: + """Turn off the GNSS.""" + ... + def update(self) -> None: + """Update GNSS positioning information.""" + ... + latitude: float + """Latitude of current position in degrees (float).""" + + longitude: float + """Longitude of current position in degrees (float).""" + + altitude: float + """Altitude of current position in meters (float).""" + + timestamp: time.struct_time + """Time when the position data was updated.""" + + fix: PositionFix + """Fix mode.""" + +class PositionFix: + """Position fix mode""" + + def __init__(self) -> None: + """Enum-like class to define the position fix mode.""" + INVALID: PositionFix + """No measurement.""" + + FIX_2D: PositionFix + """2D fix.""" + + FIX_3D: PositionFix + """3D fix.""" + +class SatelliteSystem: + """Satellite system type""" + + def __init__(self) -> None: + """Enum-like class to define the satellite system type.""" + GPS: SatelliteSystem + """Global Positioning System.""" + + GLONASS: SatelliteSystem + """GLObal NAvigation Satellite System.""" + + SBAS: SatelliteSystem + """Satellite Based Augmentation System.""" + + QZSS_L1CA: SatelliteSystem + """Quasi-Zenith Satellite System L1C/A.""" + + QZSS_L1S: SatelliteSystem + """Quasi-Zenith Satellite System L1S.""" diff --git a/stubs/i2cperipheral/__init__.pyi b/stubs/i2cperipheral/__init__.pyi new file mode 100644 index 0000000..8e2d322 --- /dev/null +++ b/stubs/i2cperipheral/__init__.pyi @@ -0,0 +1,150 @@ +"""Two wire serial protocol peripheral + +The `i2cperipheral` module contains classes to support an I2C peripheral. + +Example emulating a peripheral with 2 addresses (read and write):: + + import board + from i2cperipheral import I2CPeripheral + + regs = [0] * 16 + index = 0 + + with I2CPeripheral(board.SCL, board.SDA, (0x40, 0x41)) as device: + while True: + r = device.request() + if not r: + # Maybe do some housekeeping + continue + with r: # Closes the transfer if necessary by sending a NACK or feeding dummy bytes + if r.address == 0x40: + if not r.is_read: # Main write which is Selected read + b = r.read(1) + if not b or b[0] > 15: + break + index = b[0] + b = r.read(1) + if b: + regs[index] = b[0] + elif r.is_restart: # Combined transfer: This is the Main read message + n = r.write(bytes([regs[index]])) + #else: + # A read transfer is not supported in this example + # If the microcontroller tries, it will get 0xff byte(s) by the ctx manager (r.close()) + elif r.address == 0x41: + if not r.is_read: + b = r.read(1) + if b and b[0] == 0xde: + # do something + pass + +This example sets up an I2C device that can be accessed from Linux like this:: + + $ i2cget -y 1 0x40 0x01 + 0x00 + $ i2cset -y 1 0x40 0x01 0xaa + $ i2cget -y 1 0x40 0x01 + 0xaa + +.. warning:: + I2CPeripheral makes use of clock stretching in order to slow down + the host. + Make sure the I2C host supports this. + + Raspberry Pi in particular does not support this with its I2C hw block. + This can be worked around by using the ``i2c-gpio`` bit banging driver. + Since the RPi firmware uses the hw i2c, it's not possible to emulate a HAT eeprom.""" + +from __future__ import annotations + +from typing import Sequence + +import i2cperipheral +import microcontroller +from _typing import ReadableBuffer + +class I2CPeripheral: + """Two wire serial protocol peripheral""" + + def __init__( + self, + scl: microcontroller.Pin, + sda: microcontroller.Pin, + addresses: Sequence[int], + smbus: bool = False, + ) -> None: + """I2C is a two-wire protocol for communicating between devices. + This implements the peripheral (sensor, secondary) side. + + :param ~microcontroller.Pin scl: The clock pin + :param ~microcontroller.Pin sda: The data pin + :param addresses: The I2C addresses to respond to (how many is hw dependent). + :type addresses: list[int] + :param bool smbus: Use SMBUS timings if the hardware supports it""" + ... + def deinit(self) -> None: + """Releases control of the underlying hardware so other classes can use it.""" + ... + def __enter__(self) -> I2CPeripheral: + """No-op used in Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware on context exit. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + def request(self, timeout: float = -1) -> I2CPeripheralRequest: + """Wait for an I2C request. + + :param float timeout: Timeout in seconds. Zero means wait forever, a negative value means check once + :return: I2C Slave Request or None if timeout=-1 and there's no request + :rtype: ~i2cperipheral.I2CPeripheralRequest""" + +class I2CPeripheralRequest: + def __init__( + self, + peripheral: i2cperipheral.I2CPeripheral, + address: int, + is_read: bool, + is_restart: bool, + ) -> None: + """Information about an I2C transfer request + This cannot be instantiated directly, but is returned by :py:meth:`I2CPeripheral.request`. + + :param peripheral: The I2CPeripheral object receiving this request + :param address: I2C address + :param is_read: True if the main peripheral is requesting data + :param is_restart: Repeated Start Condition""" + def __enter__(self) -> I2CPeripheralRequest: + """No-op used in Context Managers.""" + ... + def __exit__(self) -> None: + """Close the request.""" + ... + address: int + """The I2C address of the request.""" + + is_read: bool + """The I2C main controller is reading from this peripheral.""" + + is_restart: bool + """Is Repeated Start Condition.""" + def read(self, n: int = -1, ack: bool = True) -> bytearray: + """Read data. + If ack=False, the caller is responsible for calling :py:meth:`I2CPeripheralRequest.ack`. + + :param n: Number of bytes to read (negative means all) + :param ack: Whether or not to send an ACK after the n'th byte + :return: Bytes read""" + ... + def write(self, buffer: ReadableBuffer) -> int: + """Write the data contained in buffer. + + :param ~_typing.ReadableBuffer buffer: Write out the data in this buffer + :return: Number of bytes written""" + ... + def ack(self, ack: bool = True) -> None: + """Acknowledge or Not Acknowledge last byte received. + Use together with :py:meth:`I2CPeripheralRequest.read` ack=False. + + :param ack: Whether to send an ACK or NACK""" + ... diff --git a/stubs/ipaddress/__init__.pyi b/stubs/ipaddress/__init__.pyi new file mode 100644 index 0000000..bd06044 --- /dev/null +++ b/stubs/ipaddress/__init__.pyi @@ -0,0 +1,32 @@ +""" +The `ipaddress` module provides types for IP addresses. It is a subset of CPython's ipaddress +module. +""" + +from __future__ import annotations + +from typing import Union + +def ip_address(obj: Union[int]) -> IPv4Address: + """Return a corresponding IP address object or raise ValueError if not possible.""" + ... + +class IPv4Address: + """Encapsulates an IPv4 address.""" + + def __init__(self, address: Union[int, str, bytes]) -> None: + """Create a new IPv4Address object encapsulating the address value. + + The value itself can either be bytes or a string formatted address.""" + ... + packed: bytes + """The bytes that make up the address (read-only).""" + + version: int + """4 for IPv4, 6 for IPv6""" + def __eq__(self, other: object) -> bool: + """Two Address objects are equal if their addresses and address types are equal.""" + ... + def __hash__(self) -> int: + """Returns a hash for the IPv4Address data.""" + ... diff --git a/stubs/math/__init__.pyi b/stubs/math/__init__.pyi new file mode 100644 index 0000000..cc165a8 --- /dev/null +++ b/stubs/math/__init__.pyi @@ -0,0 +1,164 @@ +"""mathematical functions + +The `math` module provides some basic mathematical functions for +working with floating-point numbers.""" + +from __future__ import annotations + +from typing import Tuple + +e: float +"""base of the natural logarithm""" + +pi: float +"""the ratio of a circle's circumference to its diameter""" + +def acos(x: float) -> float: + """Return the inverse cosine of ``x``.""" + ... + +def asin(x: float) -> float: + """Return the inverse sine of ``x``.""" + ... + +def atan(x: float) -> float: + """Return the inverse tangent of ``x``.""" + ... + +def atan2(y: float, x: float) -> float: + """Return the principal value of the inverse tangent of ``y/x``.""" + ... + +def ceil(x: float) -> int: + """Return an integer, being ``x`` rounded towards positive infinity.""" + ... + +def copysign(x: float, y: float) -> float: + """Return ``x`` with the sign of ``y``.""" + ... + +def cos(x: float) -> float: + """Return the cosine of ``x``.""" + ... + +def degrees(x: float) -> float: + """Return radians ``x`` converted to degrees.""" + ... + +def exp(x: float) -> float: + """Return the exponential of ``x``.""" + ... + +def fabs(x: float) -> float: + """Return the absolute value of ``x``.""" + ... + +def floor(x: float) -> int: + """Return an integer, being ``x`` rounded towards negative infinity.""" + ... + +def fmod(x: float, y: float) -> int: + """Return the remainder of ``x/y``.""" + ... + +def frexp(x: float) -> Tuple[int, int]: + """Decomposes a floating-point number into its mantissa and exponent. + The returned value is the tuple ``(m, e)`` such that ``x == m * 2**e`` + exactly. If ``x == 0`` then the function returns ``(0.0, 0)``, otherwise + the relation ``0.5 <= abs(m) < 1`` holds.""" + ... + +def isfinite(x: float) -> bool: + """Return ``True`` if ``x`` is finite.""" + ... + +def isinf(x: float) -> bool: + """Return ``True`` if ``x`` is infinite.""" + ... + +def isnan(x: float) -> bool: + """Return ``True`` if ``x`` is not-a-number""" + ... + +def ldexp(x: float, exp: float) -> float: + """Return ``x * (2**exp)``.""" + ... + +def modf(x: float) -> Tuple[float, float]: + """Return a tuple of two floats, being the fractional and integral parts of + ``x``. Both return values have the same sign as ``x``.""" + ... + +def pow(x: float, y: float) -> float: + """Returns ``x`` to the power of ``y``.""" + +def radians(x: float) -> float: + """Return degrees ``x`` converted to radians.""" + +def sin(x: float) -> float: + """Return the sine of ``x``.""" + ... + +def sqrt(x: float) -> float: + """Returns the square root of ``x``.""" + ... + +def tan(x: float) -> float: + """Return the tangent of ``x``.""" + ... + +def trunc(x: float) -> int: + """Return an integer, being ``x`` rounded towards 0.""" + ... + +def expm1(x: float) -> float: + """Return ``exp(x) - 1``.""" + ... + +def log2(x: float) -> float: + """Return the base-2 logarithm of ``x``.""" + ... + +def log10(x: float) -> float: + """Return the base-10 logarithm of ``x``.""" + ... + +def cosh(x: float) -> float: + """Return the hyperbolic cosine of ``x``.""" + ... + +def sinh(x: float) -> float: + """Return the hyperbolic sine of ``x``.""" + ... + +def tanh(x: float) -> float: + """Return the hyperbolic tangent of ``x``.""" + ... + +def acosh(x: float) -> float: + """Return the inverse hyperbolic cosine of ``x``.""" + ... + +def asinh(x: float) -> float: + """Return the inverse hyperbolic sine of ``x``.""" + ... + +def atanh(x: float) -> float: + """Return the inverse hyperbolic tangent of ``x``.""" + ... + +def erf(x: float) -> float: + """Return the error function of ``x``.""" + ... + +def erfc(x: float) -> float: + """Return the complementary error function of ``x``.""" + ... + +def gamma(x: float) -> float: + """Return the gamma function of ``x``.""" + ... + +def lgamma(x: float) -> float: + """Return the natural logarithm of the gamma function of ``x``.""" + ... diff --git a/stubs/memorymonitor/__init__.pyi b/stubs/memorymonitor/__init__.pyi new file mode 100644 index 0000000..f49af1d --- /dev/null +++ b/stubs/memorymonitor/__init__.pyi @@ -0,0 +1,104 @@ +"""Memory monitoring helpers""" + +from __future__ import annotations + +from typing import Optional + +class AllocationError(Exception): + """Catchall exception for allocation related errors.""" + + ... + +class AllocationAlarm: + def __init__(self, *, minimum_block_count: int = 1) -> None: + """Throw an exception when an allocation of ``minimum_block_count`` or more blocks + occurs while active. + + Track allocations:: + + import memorymonitor + + aa = memorymonitor.AllocationAlarm(minimum_block_count=2) + x = 2 + # Should not allocate any blocks. + with aa: + x = 5 + + # Should throw an exception when allocating storage for the 20 bytes. + with aa: + x = bytearray(20) + + """ + ... + def ignore(self, count: int) -> AllocationAlarm: + """Sets the number of applicable allocations to ignore before raising the exception. + Automatically set back to zero at context exit. + + Use it within a ``with`` block:: + + # Will not alarm because the bytearray allocation will be ignored. + with aa.ignore(2): + x = bytearray(20) + """ + ... + def __enter__(self) -> AllocationAlarm: + """Enables the alarm.""" + ... + def __exit__(self) -> None: + """Automatically disables the allocation alarm when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + +class AllocationSize: + def __init__(self) -> None: + """Tracks the number of allocations in power of two buckets. + + It will have 16 16-bit buckets to track allocation counts. It is total allocations + meaning frees are ignored. Reallocated memory is counted twice, at allocation and when + reallocated with the larger size. + + The buckets are measured in terms of blocks which is the finest granularity of the heap. + This means bucket 0 will count all allocations less than or equal to the number of bytes + per block, typically 16. Bucket 2 will be less than or equal to 4 blocks. See + `bytes_per_block` to convert blocks to bytes. + + Multiple AllocationSizes can be used to track different code boundaries. + + Track allocations:: + + import memorymonitor + + mm = memorymonitor.AllocationSize() + with mm: + print("hello world" * 3) + + for bucket, count in enumerate(mm): + print("<", 2 ** bucket, count) + + """ + ... + def __enter__(self) -> AllocationSize: + """Clears counts and resumes tracking.""" + ... + def __exit__(self) -> None: + """Automatically pauses allocation tracking when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + bytes_per_block: int + """Number of bytes per block""" + def __len__(self) -> int: + """Returns the number of allocation buckets. + + This allows you to:: + + mm = memorymonitor.AllocationSize() + print(len(mm))""" + ... + def __getitem__(self, index: int) -> Optional[int]: + """Returns the allocation count for the given bucket. + + This allows you to:: + + mm = memorymonitor.AllocationSize() + print(mm[0])""" + ... diff --git a/stubs/microcontroller/__init__.pyi b/stubs/microcontroller/__init__.pyi new file mode 100644 index 0000000..3f46502 --- /dev/null +++ b/stubs/microcontroller/__init__.pyi @@ -0,0 +1,147 @@ +"""Pin references and cpu functionality + +The `microcontroller` module defines the pins from the perspective of the +microcontroller. See `board` for board-specific pin mappings.""" + +from __future__ import annotations + +from typing import Optional + +import microcontroller +from nvm import ByteArray +from watchdog import WatchDogTimer + +cpu: Processor +"""CPU information and control, such as ``cpu.temperature`` and ``cpu.frequency`` +(clock frequency). +This object is the sole instance of `microcontroller.Processor`.""" + +def delay_us(delay: int) -> None: + """Dedicated delay method used for very short delays. **Do not** do long delays + because this stops all other functions from completing. Think of this as an empty + ``while`` loop that runs for the specified ``(delay)`` time. If you have other + code or peripherals (e.g audio recording) that require specific timing or + processing while you are waiting, explore a different avenue such as using + `time.sleep()`.""" + ... + +def disable_interrupts() -> None: + """Disable all interrupts. Be very careful, this can stall everything.""" + ... + +def enable_interrupts() -> None: + """Enable the interrupts that were enabled at the last disable.""" + ... + +def on_next_reset(run_mode: microcontroller.RunMode) -> None: + """Configure the run mode used the next time the microcontroller is reset but + not powered down. + + :param ~microcontroller.RunMode run_mode: The next run mode""" + ... + +def reset() -> None: + """Reset the microcontroller. After reset, the microcontroller will enter the + run mode last set by `on_next_reset`. + + .. warning:: This may result in file system corruption when connected to a + host computer. Be very careful when calling this! Make sure the device + "Safely removed" on Windows or "ejected" on Mac OSX and Linux.""" + ... + +nvm: Optional[ByteArray] +"""Available non-volatile memory. +This object is the sole instance of `nvm.ByteArray` when available or ``None`` otherwise. + +:type: nvm.ByteArray or None""" + +watchdog: Optional[WatchDogTimer] +"""Available watchdog timer. +This object is the sole instance of `watchdog.WatchDogTimer` when available or ``None`` otherwise.""" + +class Pin: + """Identifies an IO pin on the microcontroller.""" + + def __init__(self) -> None: + """Identifies an IO pin on the microcontroller. They are fixed by the + hardware so they cannot be constructed on demand. Instead, use + :mod:`board` or :mod:`microcontroller.pin` to reference the desired pin.""" + ... + +class Processor: + """Microcontroller CPU information and control + + Usage:: + + import microcontroller + print(microcontroller.cpu.frequency) + print(microcontroller.cpu.temperature)""" + + def __init__(self) -> None: + """You cannot create an instance of `microcontroller.Processor`. + Use `microcontroller.cpu` to access the sole instance available.""" + ... + frequency: int + """The CPU operating frequency in Hertz. (read-only)""" + + reset_reason: microcontroller.ResetReason + """The reason the microcontroller started up from reset state.""" + + temperature: Optional[float] + """The on-chip temperature, in Celsius, as a float. (read-only) + + Is `None` if the temperature is not available.""" + + uid: bytearray + """The unique id (aka serial number) of the chip as a `bytearray`. (read-only)""" + + voltage: Optional[float] + """The input voltage to the microcontroller, as a float. (read-only) + + Is `None` if the voltage is not available.""" + +class ResetReason: + """The reason the microntroller was last reset""" + + POWER_ON: object + """The microntroller was started from power off.""" + + BROWNOUT: object + """The microntroller was reset due to too low a voltage.""" + + SOFTWARE: object + """The microntroller was reset from software.""" + + DEEP_SLEEP_ALARM: object + """The microntroller was reset for deep sleep and restarted by an alarm.""" + + RESET_PIN: object + """The microntroller was reset by a signal on its reset pin. The pin might be connected to a reset button.""" + + WATCHDOG: object + """The microcontroller was reset by its watchdog timer.""" + + UNKNOWN: object + """The microntroller restarted for an unknown reason.""" + +class RunMode: + """run state of the microcontroller""" + + def __init__(self) -> None: + """Enum-like class to define the run mode of the microcontroller and + CircuitPython.""" + NORMAL: RunMode + """Run CircuitPython as normal. + + :type microcontroller.RunMode:""" + + SAFE_MODE: RunMode + """Run CircuitPython in safe mode. User code will not be run and the + file system will be writeable over USB. + + :type microcontroller.RunMode:""" + + BOOTLOADER: RunMode + """Run the bootloader. + + :type microcontroller.RunMode:""" diff --git a/stubs/multiterminal/__init__.pyi b/stubs/multiterminal/__init__.pyi new file mode 100644 index 0000000..0e4de7b --- /dev/null +++ b/stubs/multiterminal/__init__.pyi @@ -0,0 +1,33 @@ +"""Manage additional terminal sources + +The `multiterminal` module allows you to configure an additional serial +terminal source. Incoming characters are accepted from both the internal +serial connection and the optional secondary connection.""" + +from __future__ import annotations + +import socket +import typing +from typing import Optional + +def get_secondary_terminal() -> Optional[typing.BinaryIO]: + """Returns the current secondary terminal.""" + ... + +def set_secondary_terminal(stream: typing.BinaryIO) -> None: + """Read additional input from the given stream and write out back to it. + This doesn't replace the core stream (usually UART or native USB) but is + mixed in instead. + + :param stream stream: secondary stream""" + ... + +def clear_secondary_terminal() -> None: + """Clears the secondary terminal.""" + ... + +def schedule_secondary_terminal_read(socket: socket.socket) -> None: + """In cases where the underlying OS is doing task scheduling, this notifies + the OS when more data is available on the socket to read. This is useful + as a callback for lwip sockets.""" + ... diff --git a/stubs/neopixel_write/__init__.pyi b/stubs/neopixel_write/__init__.pyi new file mode 100644 index 0000000..15da1f6 --- /dev/null +++ b/stubs/neopixel_write/__init__.pyi @@ -0,0 +1,30 @@ +"""Low-level neopixel implementation + +The `neopixel_write` module contains a helper method to write out bytes in +the 800khz neopixel protocol. + +For example, to turn off a single neopixel (like the status pixel on Express +boards.) + +.. code-block:: python + + import board + import neopixel_write + import digitalio + + pin = digitalio.DigitalInOut(board.NEOPIXEL) + pin.direction = digitalio.Direction.OUTPUT + pixel_off = bytearray([0, 0, 0]) + neopixel_write.neopixel_write(pin, pixel_off)""" + +from __future__ import annotations + +import digitalio +from _typing import ReadableBuffer + +def neopixel_write(digitalinout: digitalio.DigitalInOut, buf: ReadableBuffer) -> None: + """Write buf out on the given DigitalInOut. + + :param ~digitalio.DigitalInOut digitalinout: the DigitalInOut to output with + :param ~_typing.ReadableBuffer buf: The bytes to clock out. No assumption is made about color order""" + ... diff --git a/stubs/network/__init__.pyi b/stubs/network/__init__.pyi new file mode 100644 index 0000000..749fc33 --- /dev/null +++ b/stubs/network/__init__.pyi @@ -0,0 +1,16 @@ +"""Network Interface Management + +.. warning:: This module is disabled in 6.x and will removed in 7.x. Please use networking + libraries instead. + +This module provides a registry of configured NICs. +It is used by the 'socket' module to look up a suitable +NIC when a socket is created.""" + +from __future__ import annotations + +from typing import List + +def route() -> List[object]: + """Returns a list of all configured NICs.""" + ... diff --git a/stubs/nvm/__init__.pyi b/stubs/nvm/__init__.pyi new file mode 100644 index 0000000..f99e6ff --- /dev/null +++ b/stubs/nvm/__init__.pyi @@ -0,0 +1,45 @@ +"""Non-volatile memory + +The `nvm` module allows you to store whatever raw bytes you wish in a +reserved section non-volatile memory. + +Note that this module can't be imported and used directly. The sole +instance of :class:`ByteArray` is available at +:attr:`microcontroller.nvm`.""" + +from __future__ import annotations + +from typing import overload + +from _typing import ReadableBuffer + +class ByteArray: + r"""Presents a stretch of non-volatile memory as a bytearray. + + Non-volatile memory is available as a byte array that persists over reloads + and power cycles. Each assignment causes an erase and write cycle so its recommended to assign + all values to change at once. + + Usage:: + + import microcontroller + microcontroller.nvm[0:3] = b\"\xcc\x10\x00\" """ + def __init__(self) -> None: + """Not currently dynamically supported. Access the sole instance through `microcontroller.nvm`.""" + ... + def __bool__(self) -> bool: ... + def __len__(self) -> int: + """Return the length. This is used by (`len`)""" + ... + @overload + def __getitem__(self, index: slice) -> bytearray: ... + @overload + def __getitem__(self, index: int) -> int: + """Returns the value at the given index.""" + ... + @overload + def __setitem__(self, index: slice, value: ReadableBuffer) -> None: ... + @overload + def __setitem__(self, index: int, value: int) -> None: + """Set the value at the given index.""" + ... diff --git a/stubs/os/__init__.pyi b/stubs/os/__init__.pyi new file mode 100644 index 0000000..4de2ca3 --- /dev/null +++ b/stubs/os/__init__.pyi @@ -0,0 +1,94 @@ +"""functions that an OS normally provides + +The `os` module is a strict subset of the CPython `cpython:os` module. So, +code written in CircuitPython will work in CPython but not necessarily the +other way around.""" + +from __future__ import annotations + +import typing +from typing import Tuple + +def uname() -> _Uname: + """Returns a named tuple of operating specific and CircuitPython port + specific information.""" + ... + +class _Uname(typing.NamedTuple): + """The type of values that :py:func:`.uname()` returns""" + + sysname: str + nodename: str + release: str + version: str + machine: str + +def chdir(path: str) -> None: + """Change current directory.""" + ... + +def getcwd() -> str: + """Get the current directory.""" + ... + +def listdir(dir: str) -> str: + """With no argument, list the current directory. Otherwise list the given directory.""" + ... + +def mkdir(path: str) -> None: + """Create a new directory.""" + ... + +def remove(path: str) -> None: + """Remove a file.""" + ... + +def rmdir(path: str) -> None: + """Remove a directory.""" + ... + +def rename(old_path: str, new_path: str) -> str: + """Rename a file.""" + ... + +def stat(path: str) -> Tuple[int, int, int, int, int, int, int, int, int, int]: + """Get the status of a file or directory. + + .. note:: On builds without long integers, the number of seconds + for contemporary dates will not fit in a small integer. + So the time fields return 946684800, + which is the number of seconds corresponding to 1999-12-31.""" + ... + +def statvfs(path: str) -> Tuple[int, int, int, int, int, int, int, int, int, int]: + """Get the status of a filesystem. + + Returns a tuple with the filesystem information in the following order: + + * ``f_bsize`` -- file system block size + * ``f_frsize`` -- fragment size + * ``f_blocks`` -- size of fs in f_frsize units + * ``f_bfree`` -- number of free blocks + * ``f_bavail`` -- number of free blocks for unprivileged users + * ``f_files`` -- number of inodes + * ``f_ffree`` -- number of free inodes + * ``f_favail`` -- number of free inodes for unprivileged users + * ``f_flag`` -- mount flags + * ``f_namemax`` -- maximum filename length + + Parameters related to inodes: ``f_files``, ``f_ffree``, ``f_avail`` + and the ``f_flags`` parameter may return ``0`` as they can be unavailable + in a port-specific implementation.""" + ... + +def sync() -> None: + """Sync all filesystems.""" + ... + +def urandom(size: int) -> str: + """Returns a string of *size* random bytes based on a hardware True Random + Number Generator. When not available, it will raise a NotImplementedError.""" + ... + +sep: str +"""Separator used to delineate path components such as folder and file names.""" diff --git a/stubs/ps2io/__init__.pyi b/stubs/ps2io/__init__.pyi new file mode 100644 index 0000000..133f98b --- /dev/null +++ b/stubs/ps2io/__init__.pyi @@ -0,0 +1,108 @@ +"""Support for PS/2 protocol + +The `ps2io` module contains classes to provide PS/2 communication. + +.. warning:: This module is not available in some SAMD21 builds. See the + :ref:`module-support-matrix` for more info. + +All classes change hardware state and should be deinitialized when they +are no longer needed if the program continues after use. To do so, either +call :py:meth:`!deinit` or use a context manager. See +:ref:`lifetime-and-contextmanagers` for more info.""" + +from __future__ import annotations + +import microcontroller + +class Ps2: + """Communicate with a PS/2 keyboard or mouse + + Ps2 implements the PS/2 keyboard/mouse serial protocol, used in + legacy devices. It is similar to UART but there are only two + lines (Data and Clock). PS/2 devices are 5V, so bidirectional + level converters must be used to connect the I/O lines to pins + of 3.3V boards.""" + + def __init__( + self, data_pin: microcontroller.Pin, clock_pin: microcontroller.Pin + ) -> None: + """Create a Ps2 object associated with the given pins. + + :param ~microcontroller.Pin data_pin: Pin tied to data wire. + :param ~microcontroller.Pin clock_pin: Pin tied to clock wire. + This pin must support interrupts. + + Read one byte from PS/2 keyboard and turn on Scroll Lock LED:: + + import ps2io + import board + + kbd = ps2io.Ps2(board.D10, board.D11) + + while len(kbd) == 0: + pass + + print(kbd.popleft()) + print(kbd.sendcmd(0xed)) + print(kbd.sendcmd(0x01))""" + ... + def deinit(self) -> None: + """Deinitialises the Ps2 and releases any hardware resources for reuse.""" + ... + def __enter__(self) -> Ps2: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + def popleft(self) -> int: + """Removes and returns the oldest received byte. When buffer + is empty, raises an IndexError exception.""" + ... + def sendcmd(self, byte: int) -> int: + """Sends a command byte to PS/2. Returns the response byte, typically + the general ack value (0xFA). Some commands return additional data + which is available through :py:func:`popleft()`. + + Raises a RuntimeError in case of failure. The root cause can be found + by calling :py:func:`clear_errors()`. It is advisable to call + :py:func:`clear_errors()` before :py:func:`sendcmd()` to flush any + previous errors. + + :param int byte: byte value of the command""" + ... + def clear_errors(self) -> None: + """Returns and clears a bitmap with latest recorded communication errors. + + Reception errors (arise asynchronously, as data is received): + + 0x01: start bit not 0 + + 0x02: timeout + + 0x04: parity bit error + + 0x08: stop bit not 1 + + 0x10: buffer overflow, newest data discarded + + Transmission errors (can only arise in the course of sendcmd()): + + 0x100: clock pin didn't go to LO in time + + 0x200: clock pin didn't go to HI in time + + 0x400: data pin didn't ACK + + 0x800: clock pin didn't ACK + + 0x1000: device didn't respond to RTS + + 0x2000: device didn't send a response byte in time""" + ... + def __bool__(self) -> bool: ... + def __len__(self) -> int: + """Returns the number of received bytes in buffer, available + to :py:func:`popleft()`.""" + ... diff --git a/stubs/pulseio/__init__.pyi b/stubs/pulseio/__init__.pyi new file mode 100644 index 0000000..df4ea7f --- /dev/null +++ b/stubs/pulseio/__init__.pyi @@ -0,0 +1,167 @@ +"""Support for individual pulse based protocols + +The `pulseio` module contains classes to provide access to basic pulse IO. +Individual pulses are commonly used in infrared remotes and in DHT +temperature sensors. + + +.. warning:: PWMOut is moving to `pwmio` and will be removed from `pulseio` + in CircuitPython 7. + +All classes change hardware state and should be deinitialized when they +are no longer needed if the program continues after use. To do so, either +call :py:meth:`!deinit` or use a context manager. See +:ref:`lifetime-and-contextmanagers` for more info.""" + +from __future__ import annotations + +from typing import Optional + +import microcontroller +import pwmio +from _typing import ReadableBuffer + +class PulseIn: + """Measure a series of active and idle pulses. This is commonly used in infrared receivers + and low cost temperature sensors (DHT). The pulsed signal consists of timed active and + idle periods. Unlike PWM, there is no set duration for active and idle pairs.""" + + def __init__( + self, pin: microcontroller.Pin, maxlen: int = 2, *, idle_state: bool = False + ) -> None: + """Create a PulseIn object associated with the given pin. The object acts as + a read-only sequence of pulse lengths with a given max length. When it is + active, new pulse lengths are added to the end of the list. When there is + no more room (len() == `maxlen`) the oldest pulse length is removed to + make room. + + :param ~microcontroller.Pin pin: Pin to read pulses from. + :param int maxlen: Maximum number of pulse durations to store at once + :param bool idle_state: Idle state of the pin. At start and after `resume` + the first recorded pulse will the opposite state from idle. + + Read a short series of pulses:: + + import pulseio + import board + + pulses = pulseio.PulseIn(board.D7) + + # Wait for an active pulse + while len(pulses) == 0: + pass + # Pause while we do something with the pulses + pulses.pause() + + # Print the pulses. pulses[0] is an active pulse unless the length + # reached max length and idle pulses are recorded. + print(pulses) + + # Clear the rest + pulses.clear() + + # Resume with an 80 microsecond active pulse + pulses.resume(80)""" + ... + def deinit(self) -> None: + """Deinitialises the PulseIn and releases any hardware resources for reuse.""" + ... + def __enter__(self) -> PulseIn: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + def pause(self) -> None: + """Pause pulse capture""" + ... + def resume(self, trigger_duration: int = 0) -> None: + """Resumes pulse capture after an optional trigger pulse. + + .. warning:: Using trigger pulse with a device that drives both high and + low signals risks a short. Make sure your device is open drain (only + drives low) when using a trigger pulse. You most likely added a + "pull-up" resistor to your circuit to do this. + + :param int trigger_duration: trigger pulse duration in microseconds""" + ... + def clear(self) -> None: + """Clears all captured pulses""" + ... + def popleft(self) -> int: + """Removes and returns the oldest read pulse.""" + ... + maxlen: int + """The maximum length of the PulseIn. When len() is equal to maxlen, + it is unclear which pulses are active and which are idle.""" + + paused: bool + """True when pulse capture is paused as a result of :py:func:`pause` or an error during capture + such as a signal that is too fast.""" + def __bool__(self) -> bool: ... + def __len__(self) -> int: + """Returns the current pulse length + + This allows you to:: + + pulses = pulseio.PulseIn(pin) + print(len(pulses))""" + ... + def __getitem__(self, index: int) -> Optional[int]: + """Returns the value at the given index or values in slice. + + This allows you to:: + + pulses = pulseio.PulseIn(pin) + print(pulses[0])""" + ... + +class PulseOut: + """Pulse PWM "carrier" output on and off. This is commonly used in infrared remotes. The + pulsed signal consists of timed on and off periods. Unlike PWM, there is no set duration + for on and off pairs.""" + + def __init__(self, carrier: pwmio.PWMOut) -> None: + """Create a PulseOut object associated with the given PWMout object. + + :param ~pwmio.PWMOut carrier: PWMOut that is set to output on the desired pin. + + Send a short series of pulses:: + + import array + import pulseio + import pwmio + import board + + # 50% duty cycle at 38kHz. + pwm = pwmio.PWMOut(board.D13, frequency=38000, duty_cycle=32768) + pulse = pulseio.PulseOut(pwm) + # on off on off on + pulses = array.array('H', [65000, 1000, 65000, 65000, 1000]) + pulse.send(pulses) + + # Modify the array of pulses. + pulses[0] = 200 + pulse.send(pulses)""" + ... + def deinit(self) -> None: + """Deinitialises the PulseOut and releases any hardware resources for reuse.""" + ... + def __enter__(self) -> PulseOut: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + def send(self, pulses: ReadableBuffer) -> None: + """Pulse alternating on and off durations in microseconds starting with on. + ``pulses`` must be an `array.array` with data type 'H' for unsigned + halfword (two bytes). + + This method waits until the whole array of pulses has been sent and + ensures the signal is off afterwards. + + :param array.array pulses: pulse durations in microseconds""" + ... diff --git a/stubs/pwmio/__init__.pyi b/stubs/pwmio/__init__.pyi new file mode 100644 index 0000000..b359b20 --- /dev/null +++ b/stubs/pwmio/__init__.pyi @@ -0,0 +1,110 @@ +"""Support for PWM based protocols + +The `pwmio` module contains classes to provide access to basic pulse IO. + +All classes change hardware state and should be deinitialized when they +are no longer needed if the program continues after use. To do so, either +call :py:meth:`!deinit` or use a context manager. See +:ref:`lifetime-and-contextmanagers` for more info. + +For example:: + + import pwmio + import time + from board import * + + pwm = pwmio.PWMOut(D13) + pwm.duty_cycle = 2 ** 15 + time.sleep(0.1) + +This example will initialize the the device, set +:py:data:`~pwmio.PWMOut.duty_cycle`, and then sleep 0.1 seconds. +CircuitPython will automatically turn off the PWM when it resets all +hardware after program completion. Use ``deinit()`` or a ``with`` statement +to do it yourself.""" + +from __future__ import annotations + +import microcontroller + +class PWMOut: + """Output a Pulse Width Modulated signal on a given pin.""" + + def __init__( + self, + pin: microcontroller.Pin, + *, + duty_cycle: int = 0, + frequency: int = 500, + variable_frequency: bool = False + ) -> None: + """Create a PWM object associated with the given pin. This allows you to + write PWM signals out on the given pin. Frequency is fixed after init + unless ``variable_frequency`` is True. + + .. note:: When ``variable_frequency`` is True, further PWM outputs may be + limited because it may take more internal resources to be flexible. So, + when outputting both fixed and flexible frequency signals construct the + fixed outputs first. + + :param ~microcontroller.Pin pin: The pin to output to + :param int duty_cycle: The fraction of each pulse which is high. 16-bit + :param int frequency: The target frequency in Hertz (32-bit) + :param bool variable_frequency: True if the frequency will change over time + + Simple LED fade:: + + import pwmio + import board + + pwm = pwmio.PWMOut(board.D13) # output on D13 + pwm.duty_cycle = 2 ** 15 # Cycles the pin with 50% duty cycle (half of 2 ** 16) at the default 500hz + + PWM at specific frequency (servos and motors):: + + import pwmio + import board + + pwm = pwmio.PWMOut(board.D13, frequency=50) + pwm.duty_cycle = 2 ** 15 # Cycles the pin with 50% duty cycle (half of 2 ** 16) at 50hz + + Variable frequency (usually tones):: + + import pwmio + import board + import time + + pwm = pwmio.PWMOut(board.D13, duty_cycle=2 ** 15, frequency=440, variable_frequency=True) + time.sleep(0.2) + pwm.frequency = 880 + time.sleep(0.1)""" + ... + def deinit(self) -> None: + """Deinitialises the PWMOut and releases any hardware resources for reuse.""" + ... + def __enter__(self) -> PWMOut: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + duty_cycle: int + """16 bit value that dictates how much of one cycle is high (1) versus low + (0). 0xffff will always be high, 0 will always be low and 0x7fff will + be half high and then half low. + + Depending on how PWM is implemented on a specific board, the internal + representation for duty cycle might have less than 16 bits of resolution. + Reading this property will return the value from the internal representation, + so it may differ from the value set.""" + + frequency: int + """32 bit value that dictates the PWM frequency in Hertz (cycles per + second). Only writeable when constructed with ``variable_frequency=True``. + + Depending on how PWM is implemented on a specific board, the internal value + for the PWM's duty cycle may need to be recalculated when the frequency + changes. In these cases, the duty cycle is automatically recalculated + from the original duty cycle value. This should happen without any need + to manually re-set the duty cycle.""" diff --git a/stubs/random/__init__.pyi b/stubs/random/__init__.pyi new file mode 100644 index 0000000..b810e20 --- /dev/null +++ b/stubs/random/__init__.pyi @@ -0,0 +1,50 @@ +"""pseudo-random numbers and choices + +The `random` module is a strict subset of the CPython `cpython:random` +module. So, code written in CircuitPython will work in CPython but not +necessarily the other way around. + +Like its CPython cousin, CircuitPython's random seeds itself on first use +with a true random from os.urandom() when available or the uptime otherwise. +Once seeded, it will be deterministic, which is why its bad for cryptography. + +.. warning:: Numbers from this module are not cryptographically strong! Use + bytes from `os.urandom` directly for true randomness.""" + +from __future__ import annotations + +from typing import Sequence, Tuple, TypeVar + +_T = TypeVar("_T") + +def seed(seed: int) -> None: + """Sets the starting seed of the random number generation. Further calls to + `random` will return deterministic results afterwards.""" + ... + +def getrandbits(k: int) -> int: + """Returns an integer with *k* random bits.""" + ... + +def randrange(stop: Tuple[int, int, int]) -> int: + """Returns a randomly selected integer from ``range(start, stop, step)``.""" + ... + +def randint(a: int, b: int) -> int: + """Returns a randomly selected integer between a and b inclusive. Equivalent + to ``randrange(a, b + 1, 1)``""" + ... + +def choice(seq: Sequence[_T]) -> _T: + """Returns a randomly selected element from the given sequence. Raises + IndexError when the sequence is empty.""" + ... + +def random() -> float: + """Returns a random float between 0 and 1.0.""" + ... + +def uniform(a: float, b: float) -> float: + """Returns a random float between a and b. It may or may not be inclusive + depending on float rounding.""" + ... diff --git a/stubs/rgbmatrix/__init__.pyi b/stubs/rgbmatrix/__init__.pyi new file mode 100644 index 0000000..8ae9857 --- /dev/null +++ b/stubs/rgbmatrix/__init__.pyi @@ -0,0 +1,78 @@ +"""Low-level routines for bitbanged LED matrices""" + +from __future__ import annotations + +from typing import Optional, Sequence + +import digitalio +from _typing import WriteableBuffer + +class RGBMatrix: + """Displays an in-memory framebuffer to a HUB75-style RGB LED matrix.""" + + def __init__( + self, + *, + width: int, + bit_depth: int, + rgb_pins: Sequence[digitalio.DigitalInOut], + addr_pins: Sequence[digitalio.DigitalInOut], + clock_pin: digitalio.DigitalInOut, + latch_pin: digitalio.DigitalInOut, + output_enable_pin: digitalio.DigitalInOut, + doublebuffer: bool = True, + framebuffer: Optional[WriteableBuffer] = None, + height: int = 0 + ) -> None: + """Create a RGBMatrix object with the given attributes. The height of + the display is determined by the number of rgb and address pins: + len(rgb_pins) // 3 * 2 ** len(address_pins). With 6 RGB pins and 4 + address lines, the display will be 32 pixels tall. If the optional height + parameter is specified and is not 0, it is checked against the calculated + height. + + Up to 30 RGB pins and 8 address pins are supported. + + The RGB pins must be within a single "port" and performance and memory + usage are best when they are all within "close by" bits of the port. + The clock pin must also be on the same port as the RGB pins. See the + documentation of the underlying protomatter C library for more + information. Generally, Adafruit's interface boards are designed so + that these requirements are met when matched with the intended + microcontroller board. For instance, the Feather M4 Express works + together with the RGB Matrix Feather. + + The framebuffer is in "RGB565" format. + + "RGB565" means that it is organized as a series of 16-bit numbers + where the highest 5 bits are interpreted as red, the next 6 as + green, and the final 5 as blue. The object can be any buffer, but + `array.array` and `ulab.array` objects are most often useful. + To update the content, modify the framebuffer and call refresh. + + If a framebuffer is not passed in, one is allocated and initialized + to all black. In any case, the framebuffer can be retrieved + by passing the RGBMatrix object to memoryview(). + + If doublebuffer is False, some memory is saved, but the display may + flicker during updates. + + A RGBMatrix is often used in conjunction with a + `framebufferio.FramebufferDisplay`.""" + def deinit(self) -> None: + """Free the resources (pins, timers, etc.) associated with this + rgbmatrix instance. After deinitialization, no further operations + may be performed.""" + ... + brightness: float + """In the current implementation, 0.0 turns the display off entirely + and any other value up to 1.0 turns the display on fully.""" + def refresh(self) -> None: + """Transmits the color data in the buffer to the pixels so that + they are shown.""" + ... + width: int + """The width of the display, in pixels""" + + height: int + """The height of the display, in pixels""" diff --git a/stubs/rotaryio/__init__.pyi b/stubs/rotaryio/__init__.pyi new file mode 100644 index 0000000..9fe8992 --- /dev/null +++ b/stubs/rotaryio/__init__.pyi @@ -0,0 +1,53 @@ +"""Support for reading rotation sensors + +The `rotaryio` module contains classes to read different rotation encoding schemes. See +`Wikipedia's Rotary Encoder page `_ for more +background. + +All classes change hardware state and should be deinitialized when they +are no longer needed if the program continues after use. To do so, either +call :py:meth:`!deinit` or use a context manager. See +:ref:`lifetime-and-contextmanagers` for more info.""" + +from __future__ import annotations + +import microcontroller + +class IncrementalEncoder: + """IncrementalEncoder determines the relative rotational position based on two series of pulses.""" + + def __init__(self, pin_a: microcontroller.Pin, pin_b: microcontroller.Pin) -> None: + """Create an IncrementalEncoder object associated with the given pins. It tracks the positional + state of an incremental rotary encoder (also known as a quadrature encoder.) Position is + relative to the position when the object is contructed. + + :param ~microcontroller.Pin pin_a: First pin to read pulses from. + :param ~microcontroller.Pin pin_b: Second pin to read pulses from. + + For example:: + + import rotaryio + import time + from board import * + + enc = rotaryio.IncrementalEncoder(D1, D2) + last_position = None + while True: + position = enc.position + if last_position == None or position != last_position: + print(position) + last_position = position""" + ... + def deinit(self) -> None: + """Deinitializes the IncrementalEncoder and releases any hardware resources for reuse.""" + ... + def __enter__(self) -> IncrementalEncoder: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + position: int + """The current position in terms of pulses. The number of pulses per rotation is defined by the + specific hardware.""" diff --git a/stubs/rtc/__init__.pyi b/stubs/rtc/__init__.pyi new file mode 100644 index 0000000..da57494 --- /dev/null +++ b/stubs/rtc/__init__.pyi @@ -0,0 +1,57 @@ +"""Real Time Clock + +The `rtc` module provides support for a Real Time Clock. You can access and manage the +RTC using :class:`rtc.RTC`. It also backs the :func:`time.time` and :func:`time.localtime` +functions using the onboard RTC if present.""" + +from __future__ import annotations + +import time + +def set_time_source(rtc: RTC) -> None: + """Sets the RTC time source used by :func:`time.localtime`. + The default is :class:`rtc.RTC`, but it's useful to use this to override the + time source for testing purposes. For example:: + + import rtc + import time + + class RTC(object): + @property + def datetime(self): + return time.struct_time((2018, 3, 17, 21, 1, 47, 0, 0, 0)) + + r = RTC() + rtc.set_time_source(r)""" + ... + +class RTC: + """Real Time Clock""" + + def __init__(self) -> None: + """This class represents the onboard Real Time Clock. It is a singleton and will always return the same instance.""" + ... + datetime: time.struct_time + """The current date and time of the RTC as a `time.struct_time`. + + This must be set to the current date and time whenever the board loses power:: + + import rtc + import time + + r = rtc.RTC() + r.datetime = time.struct_time((2019, 5, 29, 15, 14, 15, 0, -1, -1)) + + + Once set, the RTC will automatically update this value as time passes. You can read this + property to get a snapshot of the current time:: + + current_time = r.datetime + print(current_time) + # struct_time(tm_year=2019, tm_month=5, ...)""" + + calibration: int + """The RTC calibration value as an `int`. + + A positive value speeds up the clock and a negative value slows it down. + Range and value is hardware specific, but one step is often approximately 1 ppm.""" diff --git a/stubs/samd/__init__.pyi b/stubs/samd/__init__.pyi new file mode 100644 index 0000000..2aebcee --- /dev/null +++ b/stubs/samd/__init__.pyi @@ -0,0 +1,32 @@ +"""SAMD implementation settings""" + +from __future__ import annotations + +from typing import Union + +""":mod:`samd.clock` --- samd clock names +-------------------------------------------------------- + +.. module:: samd.clock + :synopsis: samd clock names + :platform: SAMD21 + +References to clocks as named by the microcontroller""" + +class Clock: + """Identifies a clock on the microcontroller. + + They are fixed by the hardware so they cannot be constructed on demand. Instead, use + ``samd.clock`` to reference the desired clock.""" + + enabled: bool + """Is the clock enabled? (read-only)""" + + parent: Union[Clock, None] + """Clock parent. (read-only)""" + + frequency: int + """Clock frequency in Herz. (read-only)""" + + calibration: int + """Clock calibration. Not all clocks can be calibrated.""" diff --git a/stubs/sdcardio/__init__.pyi b/stubs/sdcardio/__init__.pyi new file mode 100644 index 0000000..46ed756 --- /dev/null +++ b/stubs/sdcardio/__init__.pyi @@ -0,0 +1,69 @@ +"""Interface to an SD card via the SPI bus""" + +from __future__ import annotations + +import busio +import microcontroller +from _typing import ReadableBuffer, WriteableBuffer + +class SDCard: + """SD Card Block Interface + + Controls an SD card over SPI. This built-in module has higher read + performance than the library adafruit_sdcard, but it is only compatible with + `busio.SPI`, not `bitbangio.SPI`. Usually an SDCard object is used + with ``storage.VfsFat`` to allow file I/O to an SD card.""" + + def __init__( + self, bus: busio.SPI, cs: microcontroller.Pin, baudrate: int = 8000000 + ) -> None: + """Construct an SPI SD Card object with the given properties + + :param busio.SPI spi: The SPI bus + :param microcontroller.Pin cs: The chip select connected to the card + :param int baudrate: The SPI data rate to use after card setup + + Note that during detection and configuration, a hard-coded low baudrate is used. + Data transfers use the specified baurate (rounded down to one that is supported by + the microcontroller) + + Example usage: + + .. code-block:: python + + import os + + import board + import sdcardio + import storage + + sd = sdcardio.SDCard(board.SPI(), board.SD_CS) + vfs = storage.VfsFat(sd) + storage.mount(vfs, '/sd') + os.listdir('/sd')""" + def count(self) -> int: + """Returns the total number of sectors + + Due to technical limitations, this is a function and not a property. + + :return: The number of 512-byte blocks, as a number""" + def deinit(self) -> None: + """Disable permanently. + + :return: None""" + def readblocks(self, start_block: int, buf: WriteableBuffer) -> None: + + """Read one or more blocks from the card + + :param int start_block: The block to start reading from + :param ~_typing.WriteableBuffer buf: The buffer to write into. Length must be multiple of 512. + + :return: None""" + def writeblocks(self, start_block: int, buf: ReadableBuffer) -> None: + + """Write one or more blocks to the card + + :param int start_block: The block to start writing from + :param ~_typing.ReadableBuffer buf: The buffer to read from. Length must be multiple of 512. + + :return: None""" diff --git a/stubs/sdioio/__init__.pyi b/stubs/sdioio/__init__.pyi new file mode 100644 index 0000000..bec84cc --- /dev/null +++ b/stubs/sdioio/__init__.pyi @@ -0,0 +1,101 @@ +"""Interface to an SD card via the SDIO bus""" + +from __future__ import annotations + +from typing import Sequence + +import microcontroller +from _typing import ReadableBuffer, WriteableBuffer + +class SDCard: + """SD Card Block Interface with SDIO + + Controls an SD card over SDIO. SDIO is a parallel protocol designed + for SD cards. It uses a clock pin, a command pin, and 1 or 4 + data pins. It can be operated at a high frequency such as + 25MHz. Usually an SDCard object is used with ``storage.VfsFat`` + to allow file I/O to an SD card.""" + + def __init__( + self, + clock: microcontroller.Pin, + command: microcontroller.Pin, + data: Sequence[microcontroller.Pin], + frequency: int, + ) -> None: + """Construct an SDIO SD Card object with the given properties + + :param ~microcontroller.Pin clock: the pin to use for the clock. + :param ~microcontroller.Pin command: the pin to use for the command. + :param data: A sequence of pins to use for data. + :param frequency: The frequency of the bus in Hz + + Example usage: + + .. code-block:: python + + import os + + import board + import sdioio + import storage + + sd = sdioio.SDCard( + clock=board.SDIO_CLOCK, + command=board.SDIO_COMMAND, + data=board.SDIO_DATA, + frequency=25000000) + vfs = storage.VfsFat(sd) + storage.mount(vfs, '/sd') + os.listdir('/sd')""" + ... + def configure(self, frequency: int = 0, width: int = 0) -> None: + """Configures the SDIO bus. + + :param int frequency: the desired clock rate in Hertz. The actual clock rate may be higher or lower due to the granularity of available clock settings. Check the `frequency` attribute for the actual clock rate. + :param int width: the number of data lines to use. Must be 1 or 4 and must also not exceed the number of data lines at construction + + .. note:: Leaving a value unspecified or 0 means the current setting is kept""" + def count(self) -> int: + """Returns the total number of sectors + + Due to technical limitations, this is a function and not a property. + + :return: The number of 512-byte blocks, as a number""" + def readblocks(self, start_block: int, buf: WriteableBuffer) -> None: + + """Read one or more blocks from the card + + :param int start_block: The block to start reading from + :param ~_typing.WriteableBuffer buf: The buffer to write into. Length must be multiple of 512. + + :return: None""" + def writeblocks(self, start_block: int, buf: ReadableBuffer) -> None: + + """Write one or more blocks to the card + + :param int start_block: The block to start writing from + :param ~_typing.ReadableBuffer buf: The buffer to read from. Length must be multiple of 512. + + :return: None""" + @property + def frequency(self) -> int: + """The actual SDIO bus frequency. This may not match the frequency + requested due to internal limitations.""" + ... + @property + def width(self) -> int: + """The actual SDIO bus width, in bits""" + ... + def deinit(self) -> None: + """Disable permanently. + + :return: None""" + def __enter__(self) -> SDCard: + """No-op used by Context Managers. + Provided by context manager helper.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... diff --git a/stubs/sharpdisplay/__init__.pyi b/stubs/sharpdisplay/__init__.pyi new file mode 100644 index 0000000..846c0f3 --- /dev/null +++ b/stubs/sharpdisplay/__init__.pyi @@ -0,0 +1,3 @@ +"""Support for Sharp Memory Display framebuffers""" + +from __future__ import annotations diff --git a/stubs/socket/__init__.pyi b/stubs/socket/__init__.pyi new file mode 100644 index 0000000..182f883 --- /dev/null +++ b/stubs/socket/__init__.pyi @@ -0,0 +1,116 @@ +"""TCP, UDP and RAW socket support + +.. warning:: This module is disabled in 6.x and will removed in 7.x. Please use networking + libraries instead. (Native networking will provide a socket compatible class.) + +Create TCP, UDP and RAW sockets for communicating over the Internet.""" + +from __future__ import annotations + +from typing import Optional, Tuple + +from _typing import ReadableBuffer, WriteableBuffer + +class socket: + + AF_INET: int + AF_INET6: int + SOCK_STREAM: int + SOCK_DGRAM: int + SOCK_RAW: int + IPPROTO_TCP: int + def __init__( + self, family: int = AF_INET, type: int = SOCK_STREAM, proto: int = IPPROTO_TCP + ) -> None: + """Create a new socket + + :param int family: AF_INET or AF_INET6 + :param int type: SOCK_STREAM, SOCK_DGRAM or SOCK_RAW + :param int proto: IPPROTO_TCP, IPPROTO_UDP or IPPROTO_RAW (ignored)""" + ... + def bind(self, address: Tuple[str, int]) -> None: + """Bind a socket to an address + + :param address: tuple of (remote_address, remote_port) + :type address: tuple(str, int)""" + ... + def listen(self, backlog: int) -> None: + """Set socket to listen for incoming connections + + :param int backlog: length of backlog queue for waiting connetions""" + ... + def accept(self) -> Tuple[socket, str]: + """Accept a connection on a listening socket of type SOCK_STREAM, + creating a new socket of type SOCK_STREAM. + Returns a tuple of (new_socket, remote_address)""" + def connect(self, address: Tuple[str, int]) -> None: + """Connect a socket to a remote address + + :param address: tuple of (remote_address, remote_port) + :type address: tuple(str, int)""" + ... + def send(self, bytes: ReadableBuffer) -> int: + """Send some bytes to the connected remote address. + Suits sockets of type SOCK_STREAM + + :param ~_typing.ReadableBuffer bytes: some bytes to send""" + ... + def recv_into(self, buffer: WriteableBuffer, bufsize: int) -> int: + """Reads some bytes from the connected remote address, writing + into the provided buffer. If bufsize <= len(buffer) is given, + a maximum of bufsize bytes will be read into the buffer. If no + valid value is given for bufsize, the default is the length of + the given buffer. + + Suits sockets of type SOCK_STREAM + Returns an int of number of bytes read. + + :param ~_typing.WriteableBuffer buffer: buffer to receive into + :param int bufsize: optionally, a maximum number of bytes to read.""" + ... + def recv(self, bufsize: int) -> bytes: + """Reads some bytes from the connected remote address. + Suits sockets of type SOCK_STREAM + Returns a bytes() of length <= bufsize + + :param int bufsize: maximum number of bytes to receive""" + ... + def sendto(self, bytes: ReadableBuffer, address: Tuple[str, int]) -> int: + """Send some bytes to a specific address. + Suits sockets of type SOCK_DGRAM + + :param ~_typing.ReadableBuffer bytes: some bytes to send + :param address: tuple of (remote_address, remote_port) + :type address: tuple(str, int)""" + ... + def recvfrom(self, bufsize: int) -> Tuple[bytes, Tuple[str, int]]: + """Reads some bytes from the connected remote address. + Suits sockets of type SOCK_STREAM + + Returns a tuple containing + * a bytes() of length <= bufsize + * a remote_address, which is a tuple of ip address and port number + + :param int bufsize: maximum number of bytes to receive""" + ... + def setsockopt(self, level: int, optname: int, value: int) -> None: + """Sets socket options""" + ... + def settimeout(self, value: int) -> None: + """Set the timeout value for this socket. + + :param int value: timeout in seconds. 0 means non-blocking. None means block indefinitely.""" + ... + def setblocking(self, flag: bool) -> Optional[int]: + """Set the blocking behaviour of this socket. + + :param bool flag: False means non-blocking, True means block indefinitely.""" + ... + +def getaddrinfo(host: str, port: int) -> Tuple[int, int, int, str, str]: + """Gets the address information for a hostname and port + + Returns the appropriate family, socket type, socket protocol and + address information to call socket.socket() and socket.connect() with, + as a tuple.""" + ... diff --git a/stubs/socketpool/__init__.pyi b/stubs/socketpool/__init__.pyi new file mode 100644 index 0000000..1f96a61 --- /dev/null +++ b/stubs/socketpool/__init__.pyi @@ -0,0 +1,109 @@ +""" +The `socketpool` module provides sockets through a pool. The pools themselves +act like CPython's `socket` module. +""" + +from __future__ import annotations + +from typing import Tuple + +import socketpool +from _typing import ReadableBuffer, WriteableBuffer + +class Socket: + """TCP, UDP and RAW socket. Cannot be created directly. Instead, call + `SocketPool.socket()`. + + Provides a subset of CPython's `socket.socket` API. It only implements the versions of + recv that do not allocate bytes objects.""" + + def __enter__(self) -> Socket: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically closes the Socket when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + def close(self) -> None: + """Closes this Socket and makes its resources available to its SocketPool.""" + def connect(self, address: Tuple[str, int]) -> None: + """Connect a socket to a remote address + + :param ~tuple address: tuple of (remote_address, remote_port)""" + ... + def send(self, bytes: ReadableBuffer) -> int: + """Send some bytes to the connected remote address. + Suits sockets of type SOCK_STREAM + + :param ~bytes bytes: some bytes to send""" + ... + def recv_into(self, buffer: WriteableBuffer, bufsize: int) -> int: + """Reads some bytes from the connected remote address, writing + into the provided buffer. If bufsize <= len(buffer) is given, + a maximum of bufsize bytes will be read into the buffer. If no + valid value is given for bufsize, the default is the length of + the given buffer. + + Suits sockets of type SOCK_STREAM + Returns an int of number of bytes read. + + :param bytearray buffer: buffer to receive into + :param int bufsize: optionally, a maximum number of bytes to read.""" + ... + def sendto(self, bytes: ReadableBuffer, address: Tuple[str, int]) -> int: + """Send some bytes to a specific address. + Suits sockets of type SOCK_DGRAM + + :param ~bytes bytes: some bytes to send + :param ~tuple address: tuple of (remote_address, remote_port)""" + ... + def recvfrom_into(self, buffer: WriteableBuffer) -> Tuple[int, Tuple[str, int]]: + """Reads some bytes from a remote address. + + Returns a tuple containing + * the number of bytes received into the given buffer + * a remote_address, which is a tuple of ip address and port number + + :param object buffer: buffer to read into""" + ... + def settimeout(self, value: int) -> None: + """Set the timeout value for this socket. + + :param ~int value: timeout in seconds. 0 means non-blocking. None means block indefinitely.""" + ... + def __hash__(self) -> int: + """Returns a hash for the Socket.""" + ... + +class SocketPool: + """A pool of socket resources available for the given radio. Only one + SocketPool can be created for each radio. + + SocketPool should be used in place of CPython's socket which provides + a pool of sockets provided by the underlying OS.""" + + AF_INET: int + AF_INET6: int + SOCK_STREAM: int + SOCK_DGRAM: int + SOCK_RAW: int + IPPROTO_TCP: int + def socket( + self, family: int = AF_INET, type: int = SOCK_STREAM, proto: int = IPPROTO_TCP + ) -> socketpool.Socket: + """Create a new socket + + :param ~int family: AF_INET or AF_INET6 + :param ~int type: SOCK_STREAM, SOCK_DGRAM or SOCK_RAW + :param ~int proto: IPPROTO_TCP, IPPROTO_UDP or IPPROTO_RAW (ignored)""" + ... + +def getaddrinfo( + host: str, port: int, family: int = 0, type: int = 0, proto: int = 0, flags: int = 0 +) -> Tuple[int, int, int, str, Tuple[str, int]]: + """Gets the address information for a hostname and port + + Returns the appropriate family, socket type, socket protocol and + address information to call socket.socket() and socket.connect() with, + as a tuple.""" + ... diff --git a/stubs/ssl/__init__.pyi b/stubs/ssl/__init__.pyi new file mode 100644 index 0000000..7a4ee8c --- /dev/null +++ b/stubs/ssl/__init__.pyi @@ -0,0 +1,29 @@ +""" +The `ssl` module provides SSL contexts to wrap sockets in. +""" + +from __future__ import annotations + +import ssl +from typing import Optional + +import socketpool + +def create_default_context() -> ssl.SSLContext: + """Return the default SSLContext.""" + ... + +class SSLContext: + """Settings related to SSL that can be applied to a socket by wrapping it. + This is useful to provide SSL certificates to specific connections + rather than all of them.""" + +def wrap_socket( + sock: socketpool.Socket, + *, + server_side: bool = False, + server_hostname: Optional[str] = None +) -> socketpool.Socket: + """Wraps the socket into a socket-compatible class that handles SSL negotiation. + The socket must be of type SOCK_STREAM.""" + ... diff --git a/stubs/storage/__init__.pyi b/stubs/storage/__init__.pyi new file mode 100644 index 0000000..aa3acad --- /dev/null +++ b/stubs/storage/__init__.pyi @@ -0,0 +1,103 @@ +"""Storage management + +The `storage` provides storage management functionality such as mounting and +unmounting which is typically handled by the operating system hosting Python. +CircuitPython does not have an OS, so this module provides this functionality +directly.""" + +from __future__ import annotations + +from typing import AnyStr, Iterator, Tuple, Union + +def mount(filesystem: VfsFat, mount_path: str, *, readonly: bool = False) -> None: + """Mounts the given filesystem object at the given path. + + This is the CircuitPython analog to the UNIX ``mount`` command. + + :param bool readonly: True when the filesystem should be readonly to CircuitPython.""" + ... + +def umount(mount: Union[str, VfsFat]) -> None: + """Unmounts the given filesystem object or if *mount* is a path, then unmount + the filesystem mounted at that location. + + This is the CircuitPython analog to the UNIX ``umount`` command.""" + ... + +def remount( + mount_path: str, + readonly: bool = False, + *, + disable_concurrent_write_protection: bool = False +) -> None: + """Remounts the given path with new parameters. + + :param bool readonly: True when the filesystem should be readonly to CircuitPython. + :param bool disable_concurrent_write_protection: When True, the check that makes sure the + underlying filesystem data is written by one computer is disabled. Disabling the protection + allows CircuitPython and a host to write to the same filesystem with the risk that the + filesystem will be corrupted.""" + ... + +def getmount(mount_path: str) -> VfsFat: + """Retrieves the mount object associated with the mount path""" + ... + +def erase_filesystem() -> None: + """Erase and re-create the ``CIRCUITPY`` filesystem. + + On boards that present USB-visible ``CIRCUITPY`` drive (e.g., SAMD21 and SAMD51), + then call `microcontroller.reset()` to restart CircuitPython and have the + host computer remount CIRCUITPY. + + This function can be called from the REPL when ``CIRCUITPY`` + has become corrupted. + + .. warning:: All the data on ``CIRCUITPY`` will be lost, and + CircuitPython will restart on certain boards.""" + ... + +class VfsFat: + def __init__(self, block_device: str) -> None: + """Create a new VfsFat filesystem around the given block device. + + :param block_device: Block device the the filesystem lives on""" + label: str + """The filesystem label, up to 11 case-insensitive bytes. Note that + this property can only be set when the device is writable by the + microcontroller.""" + ... + def mkfs(self) -> None: + """Format the block device, deleting any data that may have been there""" + ... + def open(self, path: str, mode: str) -> None: + """Like builtin ``open()``""" + ... + def ilistdir( + self, path: str + ) -> Iterator[Union[Tuple[AnyStr, int, int, int], Tuple[AnyStr, int, int]]]: + """Return an iterator whose values describe files and folders within + ``path``""" + ... + def mkdir(self, path: str) -> None: + """Like `os.mkdir`""" + ... + def rmdir(self, path: str) -> None: + """Like `os.rmdir`""" + ... + def stat( + self, path: str + ) -> Tuple[int, int, int, int, int, int, int, int, int, int]: + """Like `os.stat`""" + ... + def statvfs( + self, path: int + ) -> Tuple[int, int, int, int, int, int, int, int, int, int]: + """Like `os.statvfs`""" + ... + def mount(self, readonly: bool, mkfs: VfsFat) -> None: + """Don't call this directly, call `storage.mount`.""" + ... + def umount(self) -> None: + """Don't call this directly, call `storage.umount`.""" + ... diff --git a/stubs/struct/__init__.pyi b/stubs/struct/__init__.pyi new file mode 100644 index 0000000..3c73365 --- /dev/null +++ b/stubs/struct/__init__.pyi @@ -0,0 +1,43 @@ +"""Manipulation of c-style data + +This module implements a subset of the corresponding CPython module, +as described below. For more information, refer to the original CPython +documentation: struct. + +Supported size/byte order prefixes: *@*, *<*, *>*, *!*. + +Supported format codes: *b*, *B*, *x*, *h*, *H*, *i*, *I*, *l*, *L*, *q*, *Q*, +*s*, *P*, *f*, *d* (the latter 2 depending on the floating-point support).""" + +from __future__ import annotations + +from typing import Any, Tuple + +from _typing import ReadableBuffer, WriteableBuffer + +def calcsize(fmt: str) -> int: + """Return the number of bytes needed to store the given fmt.""" + ... + +def pack(fmt: str, *values: Any) -> bytes: + """Pack the values according to the format string fmt. + The return value is a bytes object encoding the values.""" + ... + +def pack_into(fmt: str, buffer: WriteableBuffer, offset: int, *values: Any) -> None: + """Pack the values according to the format string fmt into a buffer + starting at offset. offset may be negative to count from the end of buffer.""" + ... + +def unpack(fmt: str, data: ReadableBuffer) -> Tuple[Any, ...]: + """Unpack from the data according to the format string fmt. The return value + is a tuple of the unpacked values. The buffer size must match the size + required by the format.""" + ... + +def unpack_from(fmt: str, data: ReadableBuffer, offset: int = 0) -> Tuple[Any, ...]: + """Unpack from the data starting at offset according to the format string fmt. + offset may be negative to count from the end of buffer. The return value is + a tuple of the unpacked values. The buffer size must be at least as big + as the size required by the form.""" + ... diff --git a/stubs/supervisor/__init__.pyi b/stubs/supervisor/__init__.pyi new file mode 100644 index 0000000..52babac --- /dev/null +++ b/stubs/supervisor/__init__.pyi @@ -0,0 +1,70 @@ +"""Supervisor settings""" + +from __future__ import annotations + +runtime: Runtime +"""Runtime information, such as ``runtime.serial_connected`` +(USB serial connection status). +This object is the sole instance of `supervisor.Runtime`.""" + +def enable_autoreload() -> None: + """Enable autoreload based on USB file write activity.""" + ... + +def disable_autoreload() -> None: + """Disable autoreload based on USB file write activity until + `enable_autoreload` is called.""" + ... + +def set_rgb_status_brightness(brightness: int) -> None: + """Set brightness of status neopixel from 0-255 + `set_rgb_status_brightness` is called.""" + ... + +def reload() -> None: + """Reload the main Python code and run it (equivalent to hitting Ctrl-D at the REPL).""" + ... + +def set_next_stack_limit(size: int) -> None: + """Set the size of the stack for the next vm run. If its too large, the default will be used.""" + ... + +class RunReason: + """The reason that CircuitPython started running.""" + + STARTUP: object + """CircuitPython started the microcontroller started up. See `microcontroller.Processor.reset_reason` + for more detail on why the microcontroller was started.""" + + AUTO_RELOAD: object + """CircuitPython restarted due to an external write to the filesystem.""" + + SUPERVISOR_RELOAD: object + """CircuitPython restarted due to a call to `supervisor.reload()`.""" + + REPL_RELOAD: object + """CircuitPython started due to the user typing CTRL-D in the REPL.""" + +class Runtime: + """Current status of runtime objects. + + Usage:: + + import supervisor + if supervisor.runtime.serial_connected: + print("Hello World!")""" + + def __init__(self) -> None: + """You cannot create an instance of `supervisor.Runtime`. + Use `supervisor.runtime` to access the sole instance available.""" + ... + serial_connected: bool + """Returns the USB serial communication status (read-only).""" + + serial_bytes_available: int + """Returns the whether any bytes are available to read + on the USB serial input. Allows for polling to see whether + to call the built-in input() or wait. (read-only)""" + + run_reason: RunReason + """Returns why CircuitPython started running this particular time.""" diff --git a/stubs/terminalio/__init__.pyi b/stubs/terminalio/__init__.pyi new file mode 100644 index 0000000..020ab86 --- /dev/null +++ b/stubs/terminalio/__init__.pyi @@ -0,0 +1,29 @@ +"""Displays text in a TileGrid + +The `terminalio` module contains classes to display a character stream on a display. The built +in font is available as ``terminalio.FONT``.""" + +from __future__ import annotations + +from typing import Optional + +import displayio +import fontio +from _typing import ReadableBuffer + +FONT: fontio.BuiltinFont +"""The built in font""" + +class Terminal: + """Display a character stream with a TileGrid""" + + def __init__(self, tilegrid: displayio.TileGrid, font: fontio.BuiltinFont) -> None: + """Terminal manages tile indices and cursor position based on VT100 commands. The font should be + a `fontio.BuiltinFont` and the TileGrid's bitmap should match the font's bitmap.""" + ... + def write(self, buf: ReadableBuffer) -> Optional[int]: + """Write the buffer of bytes to the bus. + + :return: the number of bytes written + :rtype: int or None""" + ... diff --git a/stubs/time/__init__.pyi b/stubs/time/__init__.pyi new file mode 100644 index 0000000..4818de5 --- /dev/null +++ b/stubs/time/__init__.pyi @@ -0,0 +1,76 @@ +"""time and timing related functions + +The `time` module is a strict subset of the CPython `cpython:time` module. So, code +written in MicroPython will work in CPython but not necessarily the other +way around.""" + +from __future__ import annotations + +from typing import Tuple + +def monotonic() -> float: + """Returns an always increasing value of time with an unknown reference + point. Only use it to compare against other values from `monotonic`. + + :return: the current monotonic time + :rtype: float""" + ... + +def sleep(seconds: float) -> None: + """Sleep for a given number of seconds. + + :param float seconds: the time to sleep in fractional seconds""" + ... + +class struct_time: + def __init__( + self, time_tuple: Tuple[int, int, int, int, int, int, int, int, int] + ) -> None: + """Structure used to capture a date and time. Note that it takes a tuple! + + :param tuple time_tuple: Tuple of time info: ``(tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, tm_wday, tm_yday, tm_isdst)`` + + * ``tm_year``: the year, 2017 for example + * ``tm_month``: the month, range [1, 12] + * ``tm_mday``: the day of the month, range [1, 31] + * ``tm_hour``: the hour, range [0, 23] + * ``tm_min``: the minute, range [0, 59] + * ``tm_sec``: the second, range [0, 61] + * ``tm_wday``: the day of the week, range [0, 6], Monday is 0 + * ``tm_yday``: the day of the year, range [1, 366], -1 indicates not known + * ``tm_isdst``: 1 when in daylight savings, 0 when not, -1 if unknown.""" + ... + +def time() -> int: + """Return the current time in seconds since since Jan 1, 1970. + + :return: the current time + :rtype: int""" + ... + +def monotonic_ns() -> int: + """Return the time of the monotonic clock, cannot go backward, in nanoseconds. + + :return: the current time + :rtype: int""" + ... + +def localtime(secs: int) -> struct_time: + """Convert a time expressed in seconds since Jan 1, 1970 to a struct_time in + local time. If secs is not provided or None, the current time as returned + by time() is used. + The earliest date for which it can generate a time is Jan 1, 2000. + + :return: the current time + :rtype: time.struct_time""" + ... + +def mktime(t: struct_time) -> int: + """This is the inverse function of localtime(). Its argument is the + struct_time or full 9-tuple (since the dst flag is needed; use -1 as the + dst flag if it is unknown) which expresses the time in local time, not UTC. + The earliest date for which it can generate a time is Jan 1, 2000. + + :return: seconds + :rtype: int""" + ... diff --git a/stubs/touchio/__init__.pyi b/stubs/touchio/__init__.pyi new file mode 100644 index 0000000..92beed3 --- /dev/null +++ b/stubs/touchio/__init__.pyi @@ -0,0 +1,70 @@ +"""Touch related IO + +The `touchio` module contains classes to provide access to touch IO typically +accelerated by hardware on the onboard microcontroller. + +All classes change hardware state and should be deinitialized when they +are no longer needed if the program continues after use. To do so, either +call :py:meth:`!deinit` or use a context manager. See +:ref:`lifetime-and-contextmanagers` for more info. + +For example:: + + import touchio + from board import * + + touch_pin = touchio.TouchIn(D6) + print(touch_pin.value) + +This example will initialize the the device, and print the +:py:data:`~touchio.TouchIn.value`.""" + +from __future__ import annotations + +from typing import Optional + +import microcontroller + +class TouchIn: + """Read the state of a capacitive touch sensor + + Usage:: + + import touchio + from board import * + + touch = touchio.TouchIn(A1) + while True: + if touch.value: + print("touched!")""" + + def __init__(self, pin: microcontroller.Pin) -> None: + """Use the TouchIn on the given pin. + + :param ~microcontroller.Pin pin: the pin to read from""" + ... + def deinit(self) -> None: + """Deinitialises the TouchIn and releases any hardware resources for reuse.""" + ... + def __enter__(self) -> TouchIn: + """No-op used by Context Managers.""" + ... + def __exit__(self) -> None: + """Automatically deinitializes the hardware when exiting a context. See + :ref:`lifetime-and-contextmanagers` for more info.""" + ... + value: bool + """Whether the touch pad is being touched or not. (read-only) + + True when `raw_value` > `threshold`.""" + + raw_value: int + """The raw touch measurement as an `int`. (read-only)""" + + threshold: Optional[int] + """Minimum `raw_value` needed to detect a touch (and for `value` to be `True`). + + When the **TouchIn** object is created, an initial `raw_value` is read from the pin, + and then `threshold` is set to be 100 + that value. + + You can adjust `threshold` to make the pin more or less sensitive.""" diff --git a/stubs/uheap/__init__.pyi b/stubs/uheap/__init__.pyi new file mode 100644 index 0000000..8476997 --- /dev/null +++ b/stubs/uheap/__init__.pyi @@ -0,0 +1,8 @@ +"""Heap size analysis""" + +from __future__ import annotations + +def info(object: object) -> int: + """Prints memory debugging info for the given object and returns the + estimated size.""" + ... diff --git a/stubs/ulab/__init__.pyi b/stubs/ulab/__init__.pyi new file mode 100644 index 0000000..186f44b --- /dev/null +++ b/stubs/ulab/__init__.pyi @@ -0,0 +1,315 @@ +"""Manipulate numeric data similar to numpy + +`ulab` is a numpy-like module for micropython, meant to simplify and +speed up common mathematical operations on arrays. The primary goal was to +implement a small subset of numpy that might be useful in the context of a +microcontroller. This means low-level data processing of linear (array) and +two-dimensional (matrix) data. + +`ulab` is adapted from micropython-ulab, and the original project's +documentation can be found at +https://micropython-ulab.readthedocs.io/en/latest/ + +`ulab` is modeled after numpy, and aims to be a compatible subset where +possible. Numpy's documentation can be found at +https://docs.scipy.org/doc/numpy/index.html""" + +from __future__ import annotations + +from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple, Union, overload + +import ulab + +_DType = int +"""`ulab.int8`, `ulab.uint8`, `ulab.int16`, `ulab.uint16`, `ulab.float` or `ulab.bool`""" + +_float = float +"""Type alias of the bulitin float""" + +_bool = bool +"""Type alias of the bulitin bool""" + +_Index = Union[int, slice, ulab.array, Tuple[Union[int, slice], ...]] + +class array: + """1- and 2- dimensional array""" + + def __init__( + self, + values: Union[array, Iterable[Union[_float, _bool, Iterable[Any]]]], + *, + dtype: _DType = ulab.float + ) -> None: + """:param sequence values: Sequence giving the initial content of the array. + :param ~ulab._DType dtype: The type of array values, `ulab.int8`, `ulab.uint8`, `ulab.int16`, `ulab.uint16`, `ulab.float` or `ulab.bool` + + The ``values`` sequence can either be another ~ulab.array, sequence of numbers + (in which case a 1-dimensional array is created), or a sequence where each + subsequence has the same length (in which case a 2-dimensional array is + created). + + Passing a `ulab.array` and a different dtype can be used to convert an array + from one dtype to another. + + In many cases, it is more convenient to create an array from a function + like `zeros` or `linspace`. + + `ulab.array` implements the buffer protocol, so it can be used in many + places an `array.array` can be used.""" + ... + shape: Tuple[int, ...] + """The size of the array, a tuple of length 1 or 2""" + + size: int + """The number of elements in the array""" + + itemsize: int + """The size of a single item in the array""" + + strides: Tuple[int, ...] + """Tuple of bytes to step in each dimension, a tuple of length 1 or 2""" + def copy(self) -> ulab.array: + """Return a copy of the array""" + ... + def flatten(self, *, order: str = "C") -> ulab.array: + """:param order: Whether to flatten by rows ('C') or columns ('F') + + Returns a new `ulab.array` object which is always 1 dimensional. + If order is 'C' (the default", then the data is ordered in rows; + If it is 'F', then the data is ordered in columns. "C" and "F" refer + to the typical storage organization of the C and Fortran languages.""" + ... + def reshape(self, shape: Tuple[int, ...]) -> ulab.array: + """Returns an array containing the same data with a new shape.""" + ... + def sort(self, *, axis: Optional[int] = 1) -> None: + """:param axis: Whether to sort elements within rows (0), columns (1), or elements (None)""" + ... + def tobytes(self) -> bytearray: + """Return the raw data bytes in the array""" + ... + def transpose(self) -> ulab.array: + """Swap the rows and columns of a 2-dimensional array""" + ... + def __add__(self, other: Union[array, _float]) -> ulab.array: + """Adds corresponding elements of the two arrays, or adds a number to all + elements of the array. If both arguments are arrays, their sizes must match.""" + ... + def __radd__(self, other: _float) -> ulab.array: ... + def __sub__(self, other: Union[array, _float]) -> ulab.array: + """Subtracts corresponding elements of the two arrays, or subtracts a number from all + elements of the array. If both arguments are arrays, their sizes must match.""" + ... + def __rsub__(self, other: _float) -> ulab.array: ... + def __mul__(self, other: Union[array, _float]) -> ulab.array: + """Multiplies corresponding elements of the two arrays, or multiplies + all elements of the array by a number. If both arguments are arrays, + their sizes must match.""" + ... + def __rmul__(self, other: _float) -> ulab.array: ... + def __div__(self, other: Union[array, _float]) -> ulab.array: + """Multiplies corresponding elements of the two arrays, or divides + all elements of the array by a number. If both arguments are arrays, + their sizes must match.""" + ... + def __rdiv__(self, other: _float) -> ulab.array: ... + def __pow__(self, other: Union[array, _float]) -> ulab.array: + """Computes the power (x**y) of corresponding elements of the the two arrays, + or one number and one array. If both arguments are arrays, their sizes + must match.""" + ... + def __rpow__(self, other: _float) -> ulab.array: ... + def __inv__(self) -> ulab.array: ... + def __neg__(self) -> ulab.array: ... + def __pos__(self) -> ulab.array: ... + def __abs__(self) -> ulab.array: ... + def __len__(self) -> int: ... + def __lt__(self, other: Union[array, _float]) -> ulab.array: ... + def __le__(self, other: Union[array, _float]) -> ulab.array: ... + def __gt__(self, other: Union[array, _float]) -> ulab.array: ... + def __ge__(self, other: Union[array, _float]) -> ulab.array: ... + def __iter__(self) -> Union[Iterator[array], Iterator[_float]]: ... + def __getitem__(self, index: _Index) -> Union[array, _float]: + """Retrieve an element of the array.""" + ... + def __setitem__(self, index: _Index, value: Union[array, _float]) -> None: + """Set an element of the array.""" + ... + +_ArrayLike = Union[array, List[_float], Tuple[_float], range] +"""`ulab.array`, ``List[float]``, ``Tuple[float]`` or `range`""" + +int8: _DType +"""Type code for signed integers in the range -128 .. 127 inclusive, like the 'b' typecode of `array.array`""" + +int16: _DType +"""Type code for signed integers in the range -32768 .. 32767 inclusive, like the 'h' typecode of `array.array`""" + +float: _DType +"""Type code for floating point values, like the 'f' typecode of `array.array`""" + +uint8: _DType +"""Type code for unsigned integers in the range 0 .. 255 inclusive, like the 'H' typecode of `array.array`""" + +uint16: _DType +"""Type code for unsigned integers in the range 0 .. 65535 inclusive, like the 'h' typecode of `array.array`""" + +bool: _DType +"""Type code for boolean values""" + +def get_printoptions() -> Dict[str, int]: + """Get printing options""" + ... + +def set_printoptions( + threshold: Optional[int] = None, edgeitems: Optional[int] = None +) -> None: + """Set printing options""" + ... + +def ndinfo(array: ulab.array) -> None: ... +@overload +def arange( + stop: _float, step: _float = 1, *, dtype: _DType = ulab.float +) -> ulab.array: ... +@overload +def arange( + start: _float, stop: _float, step: _float = 1, *, dtype: _DType = ulab.float +) -> ulab.array: + """ + .. param: start + First value in the array, optional, defaults to 0 + .. param: stop + Final value in the array + .. param: step + Difference between consecutive elements, optional, defaults to 1.0 + .. param: dtype + Type of values in the array + + Return a new 1-D array with elements ranging from ``start`` to ``stop``, with step size ``step``.""" + ... + +def concatenate(arrays: Tuple[ulab.array], *, axis: int = 0) -> ulab.array: + """ + .. param: arrays + tuple of ndarrays + .. param: axis + axis along which the arrays will be joined + + Join a sequence of arrays along an existing axis.""" + ... + +def diagonal(a: ulab.array, *, offset: int = 0) -> ulab.array: + """ + .. param: a + an ndarray + .. param: offset + Offset of the diagonal from the main diagonal. Can be positive or negative. + + Return specified diagonals.""" + ... + +def eye( + size: int, *, M: Optional[int] = None, k: int = 0, dtype: _DType = ulab.float +) -> ulab.array: + """Return a new square array of size, with the diagonal elements set to 1 + and the other elements set to 0.""" + ... + +def full( + shape: Union[int, Tuple[int, ...]], + fill_value: Union[_float, _bool], + *, + dtype: _DType = ulab.float +) -> ulab.array: + """ + .. param: shape + Shape of the array, either an integer (for a 1-D array) or a tuple of integers (for tensors of higher rank) + .. param: fill_value + scalar, the value with which the array is filled + .. param: dtype + Type of values in the array + + Return a new array of the given shape with all elements set to 0.""" + ... + +def linspace( + start: _float, + stop: _float, + *, + dtype: _DType = ulab.float, + num: int = 50, + endpoint: _bool = True, + retstep: _bool = False +) -> ulab.array: + """ + .. param: start + First value in the array + .. param: stop + Final value in the array + .. param int: num + Count of values in the array. + .. param: dtype + Type of values in the array + .. param bool: endpoint + Whether the ``stop`` value is included. Note that even when + endpoint=True, the exact ``stop`` value may not be included due to the + inaccuracy of floating point arithmetic. + If True, return (`samples`, `step`), where `step` is the spacing between samples. + + Return a new 1-D array with ``num`` elements ranging from ``start`` to ``stop`` linearly.""" + ... + +def logspace( + start: _float, + stop: _float, + *, + dtype: _DType = ulab.float, + num: int = 50, + endpoint: _bool = True, + base: _float = 10.0 +) -> ulab.array: + """ + .. param: start + First value in the array + .. param: stop + Final value in the array + .. param int: num + Count of values in the array. Defaults to 50. + .. param: base + The base of the log space. The step size between the elements in + ``ln(samples) / ln(base)`` (or ``log_base(samples)``) is uniform. Defaults to 10.0. + .. param: dtype + Type of values in the array + .. param bool: endpoint + Whether the ``stop`` value is included. Note that even when + endpoint=True, the exact ``stop`` value may not be included due to the + inaccuracy of floating point arithmetic. Defaults to True. + + Return a new 1-D array with ``num`` evenly spaced elements on a log scale. + The sequence starts at ``base ** start``, and ends with ``base ** stop``.""" + ... + +def ones( + shape: Union[int, Tuple[int, ...]], *, dtype: _DType = ulab.float +) -> ulab.array: + """ + .. param: shape + Shape of the array, either an integer (for a 1-D array) or a tuple of 2 integers (for a 2-D array) + .. param: dtype + Type of values in the array + + Return a new array of the given shape with all elements set to 1.""" + ... + +def zeros( + shape: Union[int, Tuple[int, ...]], *, dtype: _DType = ulab.float +) -> ulab.array: + """ + .. param: shape + Shape of the array, either an integer (for a 1-D array) or a tuple of 2 integers (for a 2-D array) + .. param: dtype + Type of values in the array + + Return a new array of the given shape with all elements set to 0.""" + ... diff --git a/stubs/ulab/approx/__init__.pyi b/stubs/ulab/approx/__init__.pyi new file mode 100644 index 0000000..c2e221b --- /dev/null +++ b/stubs/ulab/approx/__init__.pyi @@ -0,0 +1,95 @@ +"""Numerical approximation methods""" + +from __future__ import annotations + +from typing import Callable, Optional + +import ulab + +def bisect( + fun: Callable[[float], float], + a: float, + b: float, + *, + xtol: float = 2.4e-7, + maxiter: int = 100 +) -> float: + """ + :param callable f: The function to bisect + :param float a: The left side of the interval + :param float b: The right side of the interval + :param float xtol: The tolerance value + :param float maxiter: The maximum number of iterations to perform + + Find a solution (zero) of the function ``f(x)`` on the interval + (``a``..``b``) using the bisection method. The result is accurate to within + ``xtol`` unless more than ``maxiter`` steps are required.""" + ... + +def fmin( + fun: Callable[[float], float], + x0: float, + *, + xatol: float = 2.4e-7, + fatol: float = 2.4e-7, + maxiter: int = 200 +) -> float: + """ + :param callable f: The function to bisect + :param float x0: The initial x value + :param float xatol: The absolute tolerance value + :param float fatol: The relative tolerance value + + Find a minimum of the function ``f(x)`` using the downhill simplex method. + The located ``x`` is within ``fxtol`` of the actual minimum, and ``f(x)`` + is within ``fatol`` of the actual minimum unless more than ``maxiter`` + steps are requried.""" + ... + +def interp( + x: ulab.array, + xp: ulab.array, + fp: ulab.array, + *, + left: Optional[float] = None, + right: Optional[float] = None +) -> ulab.array: + """ + :param ulab.array x: The x-coordinates at which to evaluate the interpolated values. + :param ulab.array xp: The x-coordinates of the data points, must be increasing + :param ulab.array fp: The y-coordinates of the data points, same length as xp + :param left: Value to return for ``x < xp[0]``, default is ``fp[0]``. + :param right: Value to return for ``x > xp[-1]``, default is ``fp[-1]``. + + Returns the one-dimensional piecewise linear interpolant to a function with given discrete data points (xp, fp), evaluated at x.""" + ... + +def newton( + fun: Callable[[float], float], + x0: float, + *, + xtol: float = 2.4e-7, + rtol: float = 0.0, + maxiter: int = 50 +) -> float: + """ + :param callable f: The function to bisect + :param float x0: The initial x value + :param float xtol: The absolute tolerance value + :param float rtol: The relative tolerance value + :param float maxiter: The maximum number of iterations to perform + + Find a solution (zero) of the function ``f(x)`` using Newton's Method. + The result is accurate to within ``xtol * rtol * |f(x)|`` unless more than + ``maxiter`` steps are requried.""" + ... + +def trapz(y: ulab.array, x: Optional[ulab.array] = None, dx: float = 1.0) -> float: + """ + :param 1D ulab.array y: the values of the dependent variable + :param 1D ulab.array x: optional, the coordinates of the independent variable. Defaults to uniformly spaced values. + :param float dx: the spacing between sample points, if x=None + + Returns the integral of y(x) using the trapezoidal rule. + """ + ... diff --git a/stubs/ulab/compare/__init__.pyi b/stubs/ulab/compare/__init__.pyi new file mode 100644 index 0000000..a1dd080 --- /dev/null +++ b/stubs/ulab/compare/__init__.pyi @@ -0,0 +1,48 @@ +"""Comparison functions""" + +from __future__ import annotations + +from typing import List, Union + +import ulab + +def clip( + x1: Union[ulab.array, float], + x2: Union[ulab.array, float], + x3: Union[ulab.array, float], +) -> ulab.array: + """ + Constrain the values from ``x1`` to be between ``x2`` and ``x3``. + ``x2`` is assumed to be less than or equal to ``x3``. + + Arguments may be ulab arrays or numbers. All array arguments + must be the same size. If the inputs are all scalars, a 1-element + array is returned. + + Shorthand for ``ulab.maximum(x2, ulab.minimum(x1, x3))``""" + ... + +def equal(x1: Union[ulab.array, float], x2: Union[ulab.array, float]) -> List[bool]: + """Return an array of bool which is true where x1[i] == x2[i] and false elsewhere""" + ... + +def not_equal(x1: Union[ulab.array, float], x2: Union[ulab.array, float]) -> List[bool]: + """Return an array of bool which is false where x1[i] == x2[i] and true elsewhere""" + ... + +def maximum(x1: Union[ulab.array, float], x2: Union[ulab.array, float]) -> ulab.array: + """ + Compute the element by element maximum of the arguments. + + Arguments may be ulab arrays or numbers. All array arguments + must be the same size. If the inputs are both scalars, a number is + returned""" + ... + +def minimum(x1: Union[ulab.array, float], x2: Union[ulab.array, float]) -> ulab.array: + """Compute the element by element minimum of the arguments. + + Arguments may be ulab arrays or numbers. All array arguments + must be the same size. If the inputs are both scalars, a number is + returned""" + ... diff --git a/stubs/ulab/fft/__init__.pyi b/stubs/ulab/fft/__init__.pyi new file mode 100644 index 0000000..103a9ab --- /dev/null +++ b/stubs/ulab/fft/__init__.pyi @@ -0,0 +1,38 @@ +"""Frequency-domain functions""" + +from __future__ import annotations + +from typing import Optional, Tuple + +import ulab + +def fft(r: ulab.array, c: Optional[ulab.array] = None) -> Tuple[ulab.array, ulab.array]: + """ + :param ulab.array r: A 1-dimension array of values whose size is a power of 2 + :param ulab.array c: An optional 1-dimension array of values whose size is a power of 2, giving the complex part of the value + :return tuple (r, c): The real and complex parts of the FFT + + Perform a Fast Fourier Transform from the time domain into the frequency domain + + See also ~ulab.extras.spectrum, which computes the magnitude of the fft, + rather than separately returning its real and imaginary parts.""" + ... + +def ifft( + r: ulab.array, c: Optional[ulab.array] = None +) -> Tuple[ulab.array, ulab.array]: + """ + :param ulab.array r: A 1-dimension array of values whose size is a power of 2 + :param ulab.array c: An optional 1-dimension array of values whose size is a power of 2, giving the complex part of the value + :return tuple (r, c): The real and complex parts of the inverse FFT + + Perform an Inverse Fast Fourier Transform from the frequeny domain into the time domain""" + ... + +def spectrogram(r: ulab.array) -> ulab.array: + """ + :param ulab.array r: A 1-dimension array of values whose size is a power of 2 + + Computes the spectrum of the input signal. This is the absolute value of the (complex-valued) fft of the signal. + This function is similar to scipy's ``scipy.signal.spectrogram``.""" + ... diff --git a/stubs/ulab/filter/__init__.pyi b/stubs/ulab/filter/__init__.pyi new file mode 100644 index 0000000..6330e7a --- /dev/null +++ b/stubs/ulab/filter/__init__.pyi @@ -0,0 +1,47 @@ +"""Filtering functions""" + +from __future__ import annotations + +from typing import Tuple, overload + +import ulab +from ulab import _ArrayLike + +def convolve(a: ulab.array, v: ulab.array) -> ulab.array: + """ + :param ulab.array a: + :param ulab.array v: + + Returns the discrete, linear convolution of two one-dimensional sequences. + The result is always an array of float. Only the ``full`` mode is supported, + and the ``mode`` named parameter of numpy is not accepted. Note that all other + modes can be had by slicing a ``full`` result. + + Convolution filters can implement high pass, low pass, band pass, etc., + filtering operations. Convolution filters are typically constructed ahead + of time. This can be done using desktop python with scipy, or on web pages + such as https://fiiir.com/ + + Convolution is most time-efficient when both inputs are of float type.""" + ... + +@overload +def sosfilt(sos: _ArrayLike, x: _ArrayLike) -> ulab.array: ... +@overload +def sosfilt( + sos: _ArrayLike, x: _ArrayLike, *, zi: ulab.array +) -> Tuple[ulab.array, ulab.array]: + """ + :param ulab.array sos: Array of second-order filter coefficients, must have shape (n_sections, 6). Each row corresponds to a second-order section, with the first three columns providing the numerator coefficients and the last three providing the denominator coefficients. + :param ulab.array x: The data to be filtered + :param ulab.array zi: Optional initial conditions for the filter + :return: If ``zi`` is not specified, the filter result alone is returned. If ``zi`` is specified, the return value is a 2-tuple of the filter result and the final filter conditions. + + Filter data along one dimension using cascaded second-order sections. + + Filter a data sequence, x, using a digital IIR filter defined by sos. + + The filter function is implemented as a series of second-order filters with direct-form II transposed structure. It is designed to minimize numerical precision errors for high-order filters. + + Filter coefficients can be generated by using scipy's filter generators such as ``signal.ellip(..., output='sos')``.""" + ... diff --git a/stubs/ulab/linalg/__init__.pyi b/stubs/ulab/linalg/__init__.pyi new file mode 100644 index 0000000..0bec54c --- /dev/null +++ b/stubs/ulab/linalg/__init__.pyi @@ -0,0 +1,67 @@ +"""Linear algebra functions""" + +from __future__ import annotations + +from typing import Optional, Tuple, Union + +import ulab + +def cholesky(A: ulab.array) -> ulab.array: + """ + :param ~ulab.array A: a positive definite, symmetric square matrix + :return ~ulab.array L: a square root matrix in the lower triangular form + :raises ValueError: If the input does not fulfill the necessary conditions + + The returned matrix satisfies the equation m=LL*""" + ... + +def det(m: ulab.array) -> float: + """ + :param: m, a square matrix + :return float: The determinant of the matrix + + Computes the eigenvalues and eigenvectors of a square matrix""" + ... + +def dot(m1: ulab.array, m2: ulab.array) -> Union[ulab.array, float]: + """ + :param ~ulab.array m1: a matrix, or a vector + :param ~ulab.array m2: a matrix, or a vector + + Computes the product of two matrices, or two vectors. In the letter case, the inner product is returned.""" + ... + +def eig(m: ulab.array) -> Tuple[ulab.array, ulab.array]: + """ + :param m: a square matrix + :return tuple (eigenvectors, eigenvalues): + + Computes the eigenvalues and eigenvectors of a square matrix""" + ... + +def inv(m: ulab.array) -> ulab.array: + """ + :param ~ulab.array m: a square matrix + :return: The inverse of the matrix, if it exists + :raises ValueError: if the matrix is not invertible + + Computes the inverse of a square matrix""" + ... + +def norm(x: ulab.array) -> float: + """ + :param ~ulab.array x: a vector or a matrix + + Computes the 2-norm of a vector or a matrix, i.e., ``sqrt(sum(x*x))``, however, without the RAM overhead.""" + ... + +def size(array: ulab.array, *, axis: Optional[int] = None) -> int: + """Return the total number of elements in the array, as an integer.""" + ... + +def trace(m: ulab.array) -> float: + """ + :param m: a square matrix + + Compute the trace of the matrix, the sum of its diagonal elements.""" + ... diff --git a/stubs/ulab/numerical/__init__.pyi b/stubs/ulab/numerical/__init__.pyi new file mode 100644 index 0000000..3218fb0 --- /dev/null +++ b/stubs/ulab/numerical/__init__.pyi @@ -0,0 +1,70 @@ +"""Numerical and Statistical functions + +Most of these functions take an "axis" argument, which indicates whether to +operate over the flattened array (None), or a particular axis (integer).""" + +from __future__ import annotations + +from typing import Optional, Union + +import ulab +from ulab import _ArrayLike + +def argmax(array: _ArrayLike, *, axis: Optional[int] = None) -> int: + """Return the index of the maximum element of the 1D array""" + ... + +def argmin(array: _ArrayLike, *, axis: Optional[int] = None) -> int: + """Return the index of the minimum element of the 1D array""" + ... + +def argsort(array: ulab.array, *, axis: int = -1) -> ulab.array: + """Returns an array which gives indices into the input array from least to greatest.""" + ... + +def cross(a: ulab.array, b: ulab.array) -> ulab.array: + """Return the cross product of two vectors of length 3""" + ... + +def diff(array: ulab.array, *, n: int = 1, axis: int = -1) -> ulab.array: + """Return the numerical derivative of successive elements of the array, as + an array. axis=None is not supported.""" + ... + +def flip(array: ulab.array, *, axis: Optional[int] = None) -> ulab.array: + """Returns a new array that reverses the order of the elements along the + given axis, or along all axes if axis is None.""" + ... + +def max(array: _ArrayLike, *, axis: Optional[int] = None) -> float: + """Return the maximum element of the 1D array""" + ... + +def mean(array: _ArrayLike, *, axis: Optional[int] = None) -> float: + """Return the mean element of the 1D array, as a number if axis is None, otherwise as an array.""" + ... + +def min(array: _ArrayLike, *, axis: Optional[int] = None) -> float: + """Return the minimum element of the 1D array""" + ... + +def roll(array: ulab.array, distance: int, *, axis: Optional[int] = None) -> None: + """Shift the content of a vector by the positions given as the second + argument. If the ``axis`` keyword is supplied, the shift is applied to + the given axis. The array is modified in place.""" + ... + +def sort(array: ulab.array, *, axis: int = -1) -> ulab.array: + """Sort the array along the given axis, or along all axes if axis is None. + The array is modified in place.""" + ... + +def std(array: _ArrayLike, *, axis: Optional[int] = None, ddof: int = 0) -> float: + """Return the standard deviation of the array, as a number if axis is None, otherwise as an array.""" + ... + +def sum( + array: _ArrayLike, *, axis: Optional[int] = None +) -> Union[float, int, ulab.array]: + """Return the sum of the array, as a number if axis is None, otherwise as an array.""" + ... diff --git a/stubs/ulab/poly/__init__.pyi b/stubs/ulab/poly/__init__.pyi new file mode 100644 index 0000000..b4922b4 --- /dev/null +++ b/stubs/ulab/poly/__init__.pyi @@ -0,0 +1,19 @@ +"""Polynomial functions""" + +from __future__ import annotations + +from typing import overload + +import ulab +from ulab import _ArrayLike +@overload +def polyfit(y: _ArrayLike, degree: int) -> ulab.array: ... +@overload +def polyfit(x: _ArrayLike, y: _ArrayLike, degree: int) -> ulab.array: + """Return a polynomial of given degree that approximates the function + f(x)=y. If x is not supplied, it is the range(len(y)).""" + ... + +def polyval(p: _ArrayLike, x: _ArrayLike) -> ulab.array: + """Evaluate the polynomial p at the points x. x must be an array.""" + ... diff --git a/stubs/ulab/user/__init__.pyi b/stubs/ulab/user/__init__.pyi new file mode 100644 index 0000000..5db3904 --- /dev/null +++ b/stubs/ulab/user/__init__.pyi @@ -0,0 +1,3 @@ +"""This module should hold arbitrary user-defined functions.""" + +from __future__ import annotations diff --git a/stubs/ulab/vector/__init__.pyi b/stubs/ulab/vector/__init__.pyi new file mode 100644 index 0000000..c43f835 --- /dev/null +++ b/stubs/ulab/vector/__init__.pyi @@ -0,0 +1,140 @@ +"""Element-by-element functions + +These functions can operate on numbers, 1-D iterables, 1-D arrays, or 2-D arrays by +applying the function to every element in the array. This is typically +much more efficient than expressing the same operation as a Python loop.""" + +from __future__ import annotations + +from typing import Callable, Optional, Union + +import ulab +from ulab import _ArrayLike, _DType + +def acos(a: _ArrayLike) -> ulab.array: + """Computes the inverse cosine function""" + ... + +def acosh(a: _ArrayLike) -> ulab.array: + """Computes the inverse hyperbolic cosine function""" + ... + +def asin(a: _ArrayLike) -> ulab.array: + """Computes the inverse sine function""" + ... + +def asinh(a: _ArrayLike) -> ulab.array: + """Computes the inverse hyperbolic sine function""" + ... + +def around(a: _ArrayLike, *, decimals: int = 0) -> ulab.array: + """Returns a new float array in which each element is rounded to + ``decimals`` places.""" + ... + +def atan(a: _ArrayLike) -> ulab.array: + """Computes the inverse tangent function; the return values are in the + range [-pi/2,pi/2].""" + ... + +def arctan2(ya: _ArrayLike, xa: _ArrayLike) -> ulab.array: + """Computes the inverse tangent function of y/x; the return values are in + the range [-pi, pi].""" + ... + +def atanh(a: _ArrayLike) -> ulab.array: + """Computes the inverse hyperbolic tangent function""" + ... + +def ceil(a: _ArrayLike) -> ulab.array: + """Rounds numbers up to the next whole number""" + ... + +def cos(a: _ArrayLike) -> ulab.array: + """Computes the cosine function""" + ... + +def cosh(a: _ArrayLike) -> ulab.array: + """Computes the hyperbolic cosine function""" + ... + +def degrees(a: _ArrayLike) -> ulab.array: + """Converts angles from radians to degrees""" + ... + +def erf(a: _ArrayLike) -> ulab.array: + """Computes the error function, which has applications in statistics""" + ... + +def erfc(a: _ArrayLike) -> ulab.array: + """Computes the complementary error function, which has applications in statistics""" + ... + +def exp(a: _ArrayLike) -> ulab.array: + """Computes the exponent function.""" + ... + +def expm1(a: _ArrayLike) -> ulab.array: + """Computes $e^x-1$. In certain applications, using this function preserves numeric accuracy better than the `exp` function.""" + ... + +def floor(a: _ArrayLike) -> ulab.array: + """Rounds numbers up to the next whole number""" + ... + +def gamma(a: _ArrayLike) -> ulab.array: + """Computes the gamma function""" + ... + +def lgamma(a: _ArrayLike) -> ulab.array: + """Computes the natural log of the gamma function""" + ... + +def log(a: _ArrayLike) -> ulab.array: + """Computes the natural log""" + ... + +def log10(a: _ArrayLike) -> ulab.array: + """Computes the log base 10""" + ... + +def log2(a: _ArrayLike) -> ulab.array: + """Computes the log base 2""" + ... + +def radians(a: _ArrayLike) -> ulab.array: + """Converts angles from degrees to radians""" + ... + +def sin(a: _ArrayLike) -> ulab.array: + """Computes the sine function""" + ... + +def sinh(a: _ArrayLike) -> ulab.array: + """Computes the hyperbolic sine""" + ... + +def sqrt(a: _ArrayLike) -> ulab.array: + """Computes the square root""" + ... + +def tan(a: _ArrayLike) -> ulab.array: + """Computes the tangent""" + ... + +def tanh(a: _ArrayLike) -> ulab.array: + """Computes the hyperbolic tangent""" + ... + +def vectorize( + f: Union[Callable[[int], float], Callable[[float], float]], + *, + otypes: Optional[_DType] = None +) -> Callable[[_ArrayLike], ulab.array]: + """ + :param callable f: The function to wrap + :param otypes: List of array types that may be returned by the function. None is interpreted to mean the return value is float. + + Wrap a Python function ``f`` so that it can be applied to arrays. + The callable must return only values of the types specified by ``otypes``, or the result is undefined.""" + ... diff --git a/stubs/usb_hid/__init__.pyi b/stubs/usb_hid/__init__.pyi new file mode 100644 index 0000000..190d289 --- /dev/null +++ b/stubs/usb_hid/__init__.pyi @@ -0,0 +1,41 @@ +"""USB Human Interface Device + +The `usb_hid` module allows you to output data as a HID device.""" + +from __future__ import annotations + +from typing import Tuple + +from _typing import ReadableBuffer + +devices: Tuple[Device, ...] +"""Tuple of all active HID device interfaces.""" + +class Device: + """HID Device + + Usage:: + + import usb_hid + + mouse = usb_hid.devices[0] + + mouse.send_report()""" + + def __init__(self) -> None: + """Not currently dynamically supported.""" + ... + def send_report(self, buf: ReadableBuffer) -> None: + """Send a HID report.""" + ... + last_received_report: bytes + """The HID OUT report as a `bytes`. (read-only). `None` if nothing received.""" + + usage_page: int + """The usage page of the device as an `int`. Can be thought of a category. (read-only)""" + + usage: int + """The functionality of the device as an int. (read-only) + + For example, Keyboard is 0x06 within the generic desktop usage page 0x01. + Mouse is 0x02 within the same usage page.""" diff --git a/stubs/usb_midi/__init__.pyi b/stubs/usb_midi/__init__.pyi new file mode 100644 index 0000000..ffdb962 --- /dev/null +++ b/stubs/usb_midi/__init__.pyi @@ -0,0 +1,55 @@ +"""MIDI over USB + +The `usb_midi` module contains classes to transmit and receive MIDI messages over USB.""" + +from __future__ import annotations + +from typing import Optional, Tuple, Union + +from _typing import ReadableBuffer, WriteableBuffer + +ports: Tuple[Union[PortIn, PortOut], ...] +"""Tuple of all MIDI ports. Each item is ether `PortIn` or `PortOut`.""" + +class PortIn: + """Receives midi commands over USB""" + + def __init__(self) -> None: + """You cannot create an instance of `usb_midi.PortIn`. + + PortIn objects are constructed for every corresponding entry in the USB + descriptor and added to the ``usb_midi.ports`` tuple.""" + ... + def read(self, nbytes: Optional[int] = None) -> Optional[bytes]: + """Read characters. If ``nbytes`` is specified then read at most that many + bytes. Otherwise, read everything that arrives until the connection + times out. Providing the number of bytes expected is highly recommended + because it will be faster. + + :return: Data read + :rtype: bytes or None""" + ... + def readinto( + self, buf: WriteableBuffer, nbytes: Optional[int] = None + ) -> Optional[bytes]: + """Read bytes into the ``buf``. If ``nbytes`` is specified then read at most + that many bytes. Otherwise, read at most ``len(buf)`` bytes. + + :return: number of bytes read and stored into ``buf`` + :rtype: bytes or None""" + ... + +class PortOut: + """Sends midi messages to a computer over USB""" + + def __init__(self) -> None: + """You cannot create an instance of `usb_midi.PortOut`. + + PortOut objects are constructed for every corresponding entry in the USB + descriptor and added to the ``usb_midi.ports`` tuple.""" + def write(self, buf: ReadableBuffer) -> Optional[int]: + """Write the buffer of bytes to the bus. + + :return: the number of bytes written + :rtype: int or None""" + ... diff --git a/stubs/ustack/__init__.pyi b/stubs/ustack/__init__.pyi new file mode 100644 index 0000000..3060a6e --- /dev/null +++ b/stubs/ustack/__init__.pyi @@ -0,0 +1,18 @@ +"""Stack information and analysis""" + +from __future__ import annotations + +def max_stack_usage() -> int: + """Return the maximum excursion of the stack so far.""" + ... + +def stack_size() -> int: + """Return the size of the entire stack. + Same as in micropython.mem_info(), but returns a value instead + of just printing it.""" + ... + +def stack_usage() -> int: + """Return how much stack is currently in use. + Same as micropython.stack_use(); duplicated here for convenience.""" + ... diff --git a/stubs/vectorio/__init__.pyi b/stubs/vectorio/__init__.pyi new file mode 100644 index 0000000..df57498 --- /dev/null +++ b/stubs/vectorio/__init__.pyi @@ -0,0 +1,54 @@ +"""Lightweight 2d shapes for displays""" + +from __future__ import annotations + +from typing import List, Tuple, Union + +import displayio + +class Circle: + def __init__(self, radius: int) -> None: + """Circle is positioned on screen by its center point. + + :param radius: The radius of the circle in pixels""" + radius: int + """The radius of the circle in pixels.""" + +class Polygon: + def __init__(self, points: List[Tuple[int, int]]) -> None: + """Represents a closed shape by ordered vertices + + :param points: Vertices for the polygon""" + points: List[Tuple[int, int]] + """Set a new look and shape for this polygon""" + +class Rectangle: + def __init__(self, width: int, height: int) -> None: + """Represents a rectangle by defining its bounds + + :param width: The number of pixels wide + :param height: The number of pixels high""" + +class VectorShape: + def __init__( + self, + shape: Union[Polygon, Rectangle, Circle], + pixel_shader: Union[displayio.ColorConverter, displayio.Palette], + x: int = 0, + y: int = 0, + ) -> None: + """Binds a vector shape to a location and pixel color + + :param shape: The shape to draw. + :param pixel_shader: The pixel shader that produces colors from values + :param x: Initial x position of the center axis of the shape within the parent. + :param y: Initial y position of the center axis of the shape within the parent.""" + ... + x: int + """X position of the center point of the shape in the parent.""" + + y: int + """Y position of the center point of the shape in the parent.""" + + pixel_shader: Union[displayio.ColorConverter, displayio.Palette] + """The pixel shader of the shape.""" diff --git a/stubs/watchdog/__init__.pyi b/stubs/watchdog/__init__.pyi new file mode 100644 index 0000000..f987570 --- /dev/null +++ b/stubs/watchdog/__init__.pyi @@ -0,0 +1,74 @@ +"""Watchdog Timer + +The `watchdog` module provides support for a Watchdog Timer. This timer will reset the device +if it hasn't been fed after a specified amount of time. This is useful to ensure the board +has not crashed or locked up. Note that on some platforms the watchdog timer cannot be disabled +once it has been enabled. + +The `WatchDogTimer` is used to restart the system when the application crashes and ends +up into a non recoverable state. Once started it cannot be stopped or +reconfigured in any way. After enabling, the application must "feed" the +watchdog periodically to prevent it from expiring and resetting the system. + +Example usage:: + + from microcontroller import watchdog as w + from watchdog import WatchDogMode + w.timeout=2.5 # Set a timeout of 2.5 seconds + w.mode = WatchDogMode.RAISE + w.feed()""" + +from __future__ import annotations + +class WatchDogMode: + """run state of the watchdog timer""" + + def __init__(self) -> None: + """Enum-like class to define the run mode of the watchdog timer.""" + RAISE: WatchDogMode + """Raise an exception when the WatchDogTimer expires. + + :type WatchDogMode:""" + + RESET: WatchDogMode + """Reset the system if the WatchDogTimer expires. + + :type WatchDogMode:""" + +class WatchDogTimer: + """Timer that is used to detect code lock ups and automatically reset the microcontroller + when one is detected. + + A lock up is detected when the watchdog hasn't been fed after a given duration. So, make + sure to call `feed` within the timeout. + """ + + def __init__(self) -> None: + """Not currently dynamically supported. Access the sole instance through `microcontroller.watchdog`.""" + ... + def feed(self) -> None: + """Feed the watchdog timer. This must be called regularly, otherwise + the timer will expire.""" + ... + def deinit(self) -> None: + """Stop the watchdog timer. This may raise an error if the watchdog + timer cannot be disabled on this platform.""" + ... + timeout: float + """The maximum number of seconds that can elapse between calls + to feed()""" + + mode: WatchDogMode + """The current operating mode of the WatchDogTimer `watchdog.WatchDogMode`. + + Setting a WatchDogMode activates the WatchDog:: + + import microcontroller + import watchdog + + w = microcontroller.watchdog + w.timeout = 5 + w.mode = watchdog.WatchDogMode.RAISE + + + Once set, the WatchDogTimer will perform the specified action if the timer expires.""" diff --git a/stubs/wifi/__init__.pyi b/stubs/wifi/__init__.pyi new file mode 100644 index 0000000..5e7570a --- /dev/null +++ b/stubs/wifi/__init__.pyi @@ -0,0 +1,120 @@ +""" +The `wifi` module provides necessary low-level functionality for managing wifi +wifi connections. Use `socketpool` for communicating over the network.""" + +from __future__ import annotations + +import ipaddress +from typing import Iterable, Iterator, Optional + +from _typing import ReadableBuffer + +radio: Radio +"""Wifi radio used to manage both station and AP modes. +This object is the sole instance of `wifi.Radio`.""" + +class Network: + """A wifi network provided by a nearby access point.""" + + def __init__(self) -> None: + """You cannot create an instance of `wifi.Network`. They are returned by `wifi.Radio.start_scanning_networks`.""" + ... + ssid: str + """String id of the network""" + + bssid: bytes + """BSSID of the network (usually the AP's MAC address)""" + + rssi: int + """Signal strength of the network""" + + channel: int + """Channel number the network is operating on""" + +class Radio: + """Native wifi radio. + + This class manages the station and access point functionality of the native + Wifi radio. + + """ + + def __init__(self) -> None: + """You cannot create an instance of `wifi.Radio`. + Use `wifi.radio` to access the sole instance available.""" + ... + enabled: bool + """``True`` when the wifi radio is enabled. + If you set the value to ``False``, any open sockets will be closed. + """ + + mac_address: bytes + """MAC address of the wifi radio. (read-only)""" + def start_scanning_networks( + self, *, start_channel: int = 1, stop_channel: int = 11 + ) -> Iterable[Network]: + """Scans for available wifi networks over the given channel range. Make sure the channels are allowed in your country.""" + ... + def stop_scanning_networks(self) -> None: + """Stop scanning for Wifi networks and free any resources used to do it.""" + ... + hostname: ReadableBuffer + """Hostname for wifi interface. When the hostname is altered after interface started/connected + the changes would only be reflected once the interface restarts/reconnects.""" + def connect( + self, + ssid: ReadableBuffer, + password: ReadableBuffer = b"", + *, + channel: Optional[int] = 0, + bssid: Optional[ReadableBuffer] = b"", + timeout: Optional[float] = None + ) -> bool: + """Connects to the given ssid and waits for an ip address. Reconnections are handled + automatically once one connection succeeds. + + By default, this will scan all channels and connect to the access point (AP) with the + given ``ssid`` and greatest signal strength (rssi). + + If ``channel`` is given, the scan will begin with the given channel and connect to + the first AP with the given ``ssid``. This can speed up the connection time + significantly because a full scan doesn't occur. + + If ``bssid`` is given, the scan will start at the first channel or the one given and + connect to the AP with the given ``bssid`` and ``ssid``.""" + ... + ipv4_gateway: Optional[ipaddress.IPv4Address] + """IP v4 Address of the gateway when connected to an access point. None otherwise.""" + + ipv4_subnet: Optional[ipaddress.IPv4Address] + """IP v4 Address of the subnet when connected to an access point. None otherwise.""" + + ipv4_address: Optional[ipaddress.IPv4Address] + """IP v4 Address of the radio when connected to an access point. None otherwise.""" + + ipv4_dns: Optional[ipaddress.IPv4Address] + """IP v4 Address of the DNS server in use when connected to an access point. None otherwise.""" + + ap_info: Optional[Network] + """Network object containing BSSID, SSID, channel, and RSSI when connected to an access point. None otherwise.""" + def ping( + self, ip: ipaddress.IPv4Address, *, timeout: Optional[float] = 0.5 + ) -> float: + """Ping an IP to test connectivity. Returns echo time in seconds. + Returns None when it times out.""" + ... + +class ScannedNetworks: + """Iterates over all `wifi.Network` objects found while scanning. This object is always created + by a `wifi.Radio`: it has no user-visible constructor.""" + + def __init__(self) -> None: + """Cannot be instantiated directly. Use `wifi.Radio.start_scanning_networks`.""" + ... + def __iter__(self) -> Iterator[Network]: + """Returns itself since it is the iterator.""" + ... + def __next__(self) -> Network: + """Returns the next `wifi.Network`. + Raises `StopIteration` if scanning is finished and no other results are available.""" + ... diff --git a/stubs/wiznet/__init__.pyi b/stubs/wiznet/__init__.pyi new file mode 100644 index 0000000..83ab0f5 --- /dev/null +++ b/stubs/wiznet/__init__.pyi @@ -0,0 +1,53 @@ +"""Support for WizNet hardware, including the WizNet 5500 Ethernet adaptor. + + +.. warning:: This module is disabled in 6.x and will removed in 7.x. Please use networking + libraries instead. +""" + +from __future__ import annotations + +from typing import Optional, Tuple + +import busio +import microcontroller + +class WIZNET5K: + """Wrapper for Wiznet 5500 Ethernet interface""" + + def __init__( + self, + spi: busio.SPI, + cs: microcontroller.Pin, + rst: microcontroller.Pin, + dhcp: bool = True, + ) -> None: + """Create a new WIZNET5500 interface using the specified pins + + :param ~busio.SPI spi: spi bus to use + :param ~microcontroller.Pin cs: pin to use for Chip Select + :param ~microcontroller.Pin rst: pin to use for Reset (optional) + :param bool dhcp: boolean flag, whether to start DHCP automatically (optional, keyword only, default True) + + * The reset pin is optional: if supplied it is used to reset the + wiznet board before initialization. + * The SPI bus will be initialized appropriately by this library. + * At present, the WIZNET5K object is a singleton, so only one WizNet + interface is supported at a time.""" + ... + connected: bool + """(boolean, readonly) is this device physically connected?""" + + dhcp: bool + """(boolean, readwrite) is DHCP active on this device? + + * set to True to activate DHCP, False to turn it off""" + def ifconfig( + self, params: Optional[Tuple[str, str, str, str]] = None + ) -> Optional[Tuple[str, str, str, str]]: + """Called without parameters, returns a tuple of + (ip_address, subnet_mask, gateway_address, dns_server) + + Or can be called with the same tuple to set those parameters. + Setting ifconfig parameters turns DHCP off, if it was on.""" + ...