From bc0881111dbfcc0b3dc3cb61a0c8497708de69c8 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 28 Dec 2024 11:55:20 -0800 Subject: [PATCH 1/3] Deleted the Experimental Folder --- experimental/lib/pysquared_w5500.py | 1890 ----------------- experimental/sf_hop.py | 85 - experimental/tests/simple_client.py | 57 - experimental/tests/simple_server.py | 42 - .../lib => tests/repl}/packet_receiver.py | 0 5 files changed, 2074 deletions(-) delete mode 100644 experimental/lib/pysquared_w5500.py delete mode 100644 experimental/sf_hop.py delete mode 100644 experimental/tests/simple_client.py delete mode 100644 experimental/tests/simple_server.py rename {experimental/lib => tests/repl}/packet_receiver.py (100%) diff --git a/experimental/lib/pysquared_w5500.py b/experimental/lib/pysquared_w5500.py deleted file mode 100644 index 760350c..0000000 --- a/experimental/lib/pysquared_w5500.py +++ /dev/null @@ -1,1890 +0,0 @@ -# SPDX-FileCopyrightText: 2010 WIZnet -# SPDX-FileCopyrightText: 2010 Arduino LLC -# SPDX-FileCopyrightText: 2008 Bjoern Hartmann -# SPDX-FileCopyrightText: 2018 Paul Stoffregen -# SPDX-FileCopyrightText: 2020 Brent Rubell for Adafruit Industries -# SPDX-FileCopyrightText: 2021 Patrick Van Oosterwijck -# SPDX-FileCopyrightText: 2021 Adam Cummick -# SPDX-FileCopyrightText: 2023 Martin Stephens -# SPDX-FileCopyrightText: 2023 Nicole Maggard -# SPDX-License-Identifier: MIT -""" -`adafruit_wiznet5k` -================================================================================ -* Author(s): WIZnet, Arduino LLC, Bjoern Hartmann, Paul Stoffregen, Brent Rubell, - Patrick Van Oosterwijck, Martin Stephens, Nicole Maggard -""" - -from __future__ import annotations - -try: - from typing import TYPE_CHECKING, Optional, Union, List, Tuple - - if TYPE_CHECKING: - from circuitpython_typing import WriteableBuffer - import busio - import digitalio - - IpAddress4Raw = Union[bytes, Tuple[int, int, int, int]] - MacAddressRaw = Union[bytes, Tuple[int, int, int, int, int, int]] -except ImportError: - pass - -from random import randint -import time -import gc -from sys import byteorder -from micropython import const -from debugcolor import co - -from adafruit_bus_device.spi_device import SPIDevice - -# *** Wiznet Common Registers *** -_REG_MR = const(0x0000) -# Gateway IPv4 Address. -_REG_GAR = const(0x0001) -# Subnet Mask Address -_REG_SUBR = const(0x0005) -# Chip version. -_REG_VERSIONR = const(0x0039) -# Source Hardware Address -_REG_SHAR = const(0x0009) -# Source IP Address -_REG_SIPR = const(0x000F) -# Register with link status flag (PHYCFGR for 5xxxx, PHYSR for 6100). -_REG_LINK_FLAG = const(0x002E) -_REG_RCR = const(0x001B) -_REG_RTR = const(0x0019) - -# *** Wiznet Socket Registers *** -# Socket n Mode. -_REG_SNMR = const(0x0000) -# Socket n Command. -_REG_SNCR = const(0x0001) -# Socket n Interrupt. -_REG_SNIR = const(0x0002) -# Socket n Status. -_REG_SNSR = const(0x0003) -# Socket n Source Port. -_REG_SNPORT = const(0x0004) -# Destination IPv4 Address. -_REG_SNDIPR = const(0x000C) -# Destination Port. -_REG_SNDPORT = const(0x0010) -# RX Free Size. -_REG_SNRX_RSR = const(0x0026) -# Read Size Pointer. -_REG_SNRX_RD = const(0x0028) -# Socket n TX Free Size. -_REG_SNTX_FSR = const(0x0020) -# TX Write Pointer. -_REG_SNTX_WR = const(0x0024) -# SNSR Commands -SNSR_SOCK_CLOSED = const(0x00) -_SNSR_SOCK_INIT = const(0x13) -SNSR_SOCK_LISTEN = const(0x14) -_SNSR_SOCK_SYNSENT = const(0x15) -SNSR_SOCK_SYNRECV = const(0x16) -SNSR_SOCK_ESTABLISHED = const(0x17) -SNSR_SOCK_FIN_WAIT = const(0x18) -_SNSR_SOCK_CLOSING = const(0x1A) -SNSR_SOCK_TIME_WAIT = const(0x1B) -SNSR_SOCK_CLOSE_WAIT = const(0x1C) -_SNSR_SOCK_LAST_ACK = const(0x1D) -_SNSR_SOCK_UDP = const(0x22) -_SNSR_SOCK_IPRAW = const(0x32) -_SNSR_SOCK_MACRAW = const(0x42) -_SNSR_SOCK_PPPOE = const(0x5F) - -# Sock Commands (CMD) -_CMD_SOCK_OPEN = const(0x01) -_CMD_SOCK_LISTEN = const(0x02) -_CMD_SOCK_CONNECT = const(0x04) -_CMD_SOCK_DISCON = const(0x08) -_CMD_SOCK_CLOSE = const(0x10) -_CMD_SOCK_SEND = const(0x20) -_CMD_SOCK_SEND_MAC = const(0x21) -_CMD_SOCK_SEND_KEEP = const(0x22) -_CMD_SOCK_RECV = const(0x40) - -# Socket n Interrupt Register -_SNIR_SEND_OK = const(0x10) -SNIR_TIMEOUT = const(0x08) -_SNIR_RECV = const(0x04) -SNIR_DISCON = const(0x02) -_SNIR_CON = const(0x01) - -_CH_SIZE = const(0x100) -_SOCK_SIZE = const(0x800) # MAX W5k socket size -_SOCK_MASK = const(0x7FF) -# Register commands -_MR_RST = const(0x80) # Mode Register RST -# Socket mode register -_SNMR_CLOSE = const(0x00) -_SNMR_TCP = const(0x21) -SNMR_UDP = const(0x02) -_SNMR_IPRAW = const(0x03) -_SNMR_MACRAW = const(0x04) -_SNMR_PPPOE = const(0x05) - -_MAX_PACKET = const(4000) -_LOCAL_PORT = const(0x400) -# Default hardware MAC address -_DEFAULT_MAC = (0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED) - -# Maximum number of sockets to support, differs between chip versions. -_MAX_SOCK_NUM = const(0x08) -_SOCKET_INVALID = const(0xFF) - - -def _unprettyfy(data: str, seperator: str, correct_length: int) -> bytes: - """Helper for converting . or : delimited strings to bytes objects.""" - data = bytes(int(x) for x in data.split(seperator)) - if len(data) == correct_length: - return data - raise ValueError("Invalid IP or MAC address.") - - -class WIZNET5500: - """Interface for WIZNET5K module.""" - - _TCP_MODE = const(0x21) - _UDP_MODE = const(0x02) - - _sockets_reserved = [] - - def debug_print(self, statement: str = "") -> None: - """ - :param statement: message to get printed in debug print - """ - if self._debug: - print(co("[W5500]" + str(statement), "blue", "bold")) - - def __init__( - self, - spi_bus: busio.SPI, - cs: digitalio.DigitalInOut, # pylint: disable=invalid-name - reset: Optional[digitalio.DigitalInOut] = None, - mac: Union[MacAddressRaw, str] = _DEFAULT_MAC, - hostname: Optional[str] = None, - debug: bool = False, - ) -> None: - """ - :param busio.SPI spi_bus: The SPI bus the Wiznet module is connected to. - :param digitalio.DigitalInOut cs: Chip select pin. - :param digitalio.DigitalInOut reset: Optional reset pin, defaults to None. - :param Union[MacAddressRaw, str] mac: The Wiznet's MAC Address, defaults to - (0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED). - :param str hostname: The desired hostname, with optional {} to fill in the MAC - address, defaults to None. - :param bool debug: Enable debugging output, defaults to False. - """ - self._debug = debug - self._chip_type = None - self._device = SPIDevice(spi_bus, cs, baudrate=8000000, polarity=0, phase=0) - # init c.s. - self._cs = cs - - # Reset wiznet module prior to initialization. - if reset: - reset.value = False - time.sleep(0.1) - reset.value = True - time.sleep(0.1) - - # Setup chip_select pin. - time.sleep(1) - self._cs.switch_to_output() - self._cs.value = 1 - - # Buffer for reading params from module - self._pbuff = bytearray(8) - self._rxbuf = bytearray(_MAX_PACKET) - - # attempt to initialize the module - self._ch_base_msb = 0 - self._src_ports_in_use = [] - self._wiznet_chip_init() - - # Set MAC address - self.mac_address = mac - self.src_port = 0 - # udp related - self.udp_from_ip = [b"\x00\x00\x00\x00"] * self.max_sockets - self.udp_from_port = [0] * self.max_sockets - - # Wait to give the Ethernet link to initialise. - stop_time = time.monotonic() + 5 - while time.monotonic() < stop_time: - if self.link_status: - break - self.debug_print("Ethernet link is down…") - time.sleep(0.5) - - @property - def max_sockets(self) -> int: - """ - Maximum number of sockets supported by chip. - - :return int: Maximum supported sockets. - """ - return _MAX_SOCK_NUM - - @property - def chip(self) -> str: - """ - Ethernet controller chip type. - - :return str: The chip type. - """ - return self._chip_type - - @property - def ip_address(self) -> bytes: - """ - Configured IP address for the WIZnet Ethernet hardware. - - :return bytes: IP address as four bytes. - """ - return self._read(_REG_SIPR, 0x00, 4) - - @staticmethod - def pretty_ip(ipv4: bytes) -> str: - """ - Convert a 4 byte IP address to a dotted-quad string for printing. - - :param bytearray ipv4: A four byte IP address. - - :return str: The IP address (a string of the form '255.255.255.255'). - - :raises ValueError: If IP address is not 4 bytes. - """ - if len(ipv4) != 4: - raise ValueError("Wrong length for IPv4 address.") - return ".".join(f"{byte}" for byte in ipv4) - - @staticmethod - def unpretty_ip(ipv4: str) -> bytes: - """ - Convert a dotted-quad string to a four byte IP address. - - :param str ipv4: IPv4 address (a string of the form '255.255.255.255') to be converted. - - :return bytes: IPv4 address in four bytes. - - :raises ValueError: If IPv4 address is not 4 bytes. - """ - return _unprettyfy(ipv4, ".", 4) - - @property - def mac_address(self) -> bytes: - """ - The WIZnet Ethernet hardware MAC address. - - :return bytes: Six byte MAC address. - """ - return self._read(_REG_SHAR, 0x00, 6) - - @mac_address.setter - def mac_address(self, address: Union[MacAddressRaw, str]) -> None: - """ - Set the WIZnet hardware MAC address. - - :param Union[MacAddressRaw, str] address: A hardware MAC address. - - :raises ValueError: If the MAC address is invalid - """ - try: - address = [int(x, 16) for x in address.split(":")] - except AttributeError: - pass - try: - if len(address) != 6: - raise ValueError() - # Bytes conversion will raise ValueError if values are not 0-255 - self._write(_REG_SHAR, 0x04, bytes(address)) - except ValueError: - # pylint: disable=raise-missing-from - raise ValueError("Invalid MAC address.") - - @staticmethod - def pretty_mac(mac: bytes) -> str: - """ - Convert a bytes MAC address to a ':' seperated string for display. - - :param bytes mac: The MAC address. - - :return str: Mac Address in the form 00:00:00:00:00:00 - - :raises ValueError: If MAC address is not 6 bytes. - """ - if len(mac) != 6: - raise ValueError("Incorrect length for MAC address.") - return ":".join(f"{byte:02x}" for byte in mac) - - def remote_ip(self, socket_num: int) -> str: - """ - IP address of the host which sent the current incoming packet. - - :param int socket_num: ID number of the socket to check. - - :return str: The IPv4 address. - - :raises ValueError: If the socket number is out of range. - """ - self._sock_num_in_range(socket_num) - for octet in range(4): - self._pbuff[octet] = self._read_socket_register( - socket_num, _REG_SNDIPR + octet - ) - return self.pretty_ip(self._pbuff[:4]) - - def remote_port(self, socket_num: int) -> int: - """ - Port number of the host which sent the current incoming packet. - - :param int socket_num: ID number of the socket to check. - - :return int: The incoming port number of the socket connection. - - :raises ValueError: If the socket number is out of range. - """ - self._sock_num_in_range(socket_num) - return self._read_two_byte_sock_reg(socket_num, _REG_SNDPORT) - - @property - def link_status(self) -> bool: - """ - Physical hardware (PHY) connection status. - - Whether the WIZnet hardware is physically connected to an Ethernet network. - - :return bool: True if the link is up, False if the link is down. - """ - return bool(int.from_bytes(self._read(_REG_LINK_FLAG, 0x00), "big") & 0x01) - - @property - def ifconfig(self) -> Tuple[bytes, bytes, bytes, bytes]: - """ - Network configuration information. - - :return Tuple[bytes, bytes, bytes, bytes]: The IP address, subnet mask, gateway - address and DNS server address. - """ - return ( - self.ip_address, - self._read(_REG_SUBR, 0x00, 4), - self._read(_REG_GAR, 0x00, 4), - self._dns, - ) - - @ifconfig.setter - def ifconfig( - self, params: Tuple[IpAddress4Raw, IpAddress4Raw, IpAddress4Raw, IpAddress4Raw] - ) -> None: - """ - Set network configuration. - - :param Tuple[Address4Bytes, Address4Bytes, Address4Bytes, Address4Bytes]: Configuration - settings - (ip_address, subnet_mask, gateway_address, dns_server). - """ - for param in params: - if len(param) != 4: - raise ValueError("IPv4 address must be 4 bytes.") - ip_address, subnet_mask, gateway_address, dns_server = params - - self._write(_REG_SIPR, 0x04, bytes(ip_address)) - self._write(_REG_SUBR, 0x04, bytes(subnet_mask)) - self._write(_REG_GAR, 0x04, bytes(gateway_address)) - - self._dns = bytes(dns_server) - - # *** Public Socket Methods *** - - def socket_available(self, socket_num: int, sock_type: int = _SNMR_TCP) -> int: - """ - Number of bytes available to be read from the socket. - - :param int socket_num: Socket to check for available bytes. - :param int sock_type: Socket type. Use SNMR_TCP for TCP or SNMR_UDP for UDP, - defaults to SNMR_TCP. - - :return int: Number of bytes available to read. - - :raises ValueError: If the socket number is out of range. - :raises ValueError: If the number of bytes on a UDP socket is negative. - """ - self.debug_print( - f"socket_available called on socket {socket_num}, protocol {sock_type}" - ) - self._sock_num_in_range(socket_num) - - number_of_bytes = self._get_rx_rcv_size(socket_num) - if self._read_snsr(socket_num) == SNMR_UDP: - number_of_bytes -= 8 # Subtract UDP header from packet size. - if number_of_bytes < 0: - raise ValueError("Negative number of bytes found on socket.") - return number_of_bytes - - def socket_status(self, socket_num: int) -> int: - """ - Socket connection status. - - Can be: SNSR_SOCK_CLOSED, SNSR_SOCK_INIT, SNSR_SOCK_LISTEN, SNSR_SOCK_SYNSENT, - SNSR_SOCK_SYNRECV, SNSR_SYN_SOCK_ESTABLISHED, SNSR_SOCK_FIN_WAIT, - SNSR_SOCK_CLOSING, SNSR_SOCK_TIME_WAIT, SNSR_SOCK_CLOSE_WAIT, SNSR_LAST_ACK, - SNSR_SOCK_UDP, SNSR_SOCK_IPRAW, SNSR_SOCK_MACRAW, SNSR_SOCK_PPOE. - - :param int socket_num: ID of socket to check. - - :return int: The connection status. - """ - return self._read_snsr(socket_num) - - def socket_connect( - self, - socket_num: int, - dest: IpAddress4Raw, - port: int, - conn_mode: int = _SNMR_TCP, - ) -> int: - """ - Open and verify a connection from a socket to a destination IPv4 address - or hostname. A TCP connection is made by default. A UDP connection can also - be made. - - :param int socket_num: ID of the socket to be connected. - :param IpAddress4Raw dest: The destination as a host name or IP address. - :param int port: Port to connect to (0 - 65,535). - :param int conn_mode: The connection mode. Use SNMR_TCP for TCP or SNMR_UDP for UDP, - defaults to SNMR_TCP. - - :raises ValueError: if the socket number is out of range. - :raises ConnectionError: If the connection to the socket cannot be established. - """ - self._sock_num_in_range(socket_num) - self._check_link_status() - self.debug_print( - f"W5500 socket connect, protocol={conn_mode}, port={port}, ip={self.pretty_ip(dest)}" - ) - # initialize a socket and set the mode - self.socket_open(socket_num, conn_mode=conn_mode) - # set socket destination IP and port - self._write_sndipr(socket_num, dest) - self._write_sndport(socket_num, port) - self._write_sncr(socket_num, _CMD_SOCK_CONNECT) - if conn_mode == _SNMR_TCP: - # wait for tcp connection establishment - while self.socket_status(socket_num) != SNSR_SOCK_ESTABLISHED: - time.sleep(0.001) - self.debug_print(f"SNSR: {self.socket_status(socket_num)}") - if self.socket_status(socket_num) == SNSR_SOCK_CLOSED: - raise ConnectionError("Failed to establish connection.") - return 1 - - def get_socket(self, *, reserve_socket=False) -> int: - """ - Request, allocate and return a socket from the WIZnet 5k chip. - - Cycle through the sockets to find the first available one. If the called with - reserve_socket=True, update the list of reserved sockets (intended to be used with - socket.socket()). Note that reserved sockets must be released by calling - release_socket() once they are no longer needed. - - If all sockets are reserved, no sockets are available for DNS calls, etc. Therefore, - one socket cannot be reserved. Since socket 0 is the only socket that is capable of - operating in MacRAW mode, it is the non-reservable socket. - - :param bool reserve_socket: Whether to reserve the socket. - - :returns int: The first available socket. - - :raises RuntimeError: If no socket is available. - """ - self.debug_print("*** Get socket.") - # Prefer socket zero for none reserved calls as it cannot be reserved. - if not reserve_socket and self.socket_status(0) == SNSR_SOCK_CLOSED: - self.debug_print("Allocated socket # 0") - return 0 - # Then check the other sockets. - - # Call garbage collection to encourage socket.__del__() be called to on any - # destroyed instances. Not at all guaranteed to work! - gc.collect() - self.debug_print(f"Reserved sockets: {WIZNET5500._sockets_reserved}") - - for socket_number, reserved in enumerate(WIZNET5500._sockets_reserved, start=1): - if not reserved and self.socket_status(socket_number) == SNSR_SOCK_CLOSED: - if reserve_socket: - WIZNET5500._sockets_reserved[socket_number - 1] = True - self.debug_print(f"Allocated socket # {socket_number}.") - return socket_number - raise RuntimeError("All sockets in use.") - - def release_socket(self, socket_number): - """ - Update the socket reservation list when a socket is no longer reserved. - - :param int socket_number: The socket to release. - - :raises ValueError: If the socket number is out of range. - """ - self._sock_num_in_range(socket_number) - WIZNET5500._sockets_reserved[socket_number - 1] = False - - def socket_listen( - self, socket_num: int, port: int, conn_mode: int = _SNMR_TCP - ) -> None: - """ - Listen on a socket's port. - - :param int socket_num: ID of socket to listen on. - :param int port: Port to listen on (0 - 65,535). - :param int conn_mode: Connection mode SNMR_TCP for TCP or SNMR_UDP for - UDP, defaults to SNMR_TCP. - - :raises ValueError: If the socket number is out of range. - :raises ConnectionError: If the Ethernet link is down. - :raises RuntimeError: If unable to connect to a hardware socket. - """ - self._sock_num_in_range(socket_num) - self._check_link_status() - self.debug_print( - f"* Listening on port={port}, ip={self.pretty_ip(self.ip_address)}" - ) - # Initialize a socket and set the mode - self.src_port = port - self.socket_open(socket_num, conn_mode=conn_mode) - self.src_port = 0 - # Send listen command - self._write_sncr(socket_num, _CMD_SOCK_LISTEN) - # Wait until ready - status = SNSR_SOCK_CLOSED - while status not in ( - SNSR_SOCK_LISTEN, - SNSR_SOCK_ESTABLISHED, - _SNSR_SOCK_UDP, - ): - status = self._read_snsr(socket_num) - if status == SNSR_SOCK_CLOSED: - raise RuntimeError("Listening socket closed.") - - def socket_accept(self, socket_num: int) -> Tuple[int, Tuple[str, int]]: - """ - Destination IPv4 address and port from an incoming connection. - - Return the next socket number so listening can continue, along with - the IP address and port of the incoming connection. - - :param int socket_num: Socket number with connection to check. - - :return Tuple[int, Tuple[Union[str, bytearray], Union[int, bytearray]]]: - If successful, the next (socket number, (destination IP address, destination port)). - - :raises ValueError: If the socket number is out of range. - """ - self._sock_num_in_range(socket_num) - dest_ip = self.remote_ip(socket_num) - dest_port = self.remote_port(socket_num) - next_socknum = self.get_socket() - self.debug_print( - f"Dest is ({dest_ip}, {dest_port}), Next listen socknum is #{next_socknum}" - ) - return next_socknum, (dest_ip, dest_port) - - def socket_open(self, socket_num: int, conn_mode: int = _SNMR_TCP) -> None: - """ - Open an IP socket. - - The socket may connect via TCP or UDP protocols. - - :param int socket_num: The socket number to open. - :param int conn_mode: The protocol to use. Use SNMR_TCP for TCP or SNMR_UDP for \ - UDP, defaults to SNMR_TCP. - - :raises ValueError: If the socket number is out of range. - :raises ConnectionError: If the Ethernet link is down or no connection to socket. - :raises RuntimeError: If unable to open a socket in UDP or TCP mode. - """ - self._sock_num_in_range(socket_num) - self._check_link_status() - self.debug_print(f"*** Opening socket {socket_num}") - if self._read_snsr(socket_num) not in ( - SNSR_SOCK_CLOSED, - SNSR_SOCK_TIME_WAIT, - SNSR_SOCK_FIN_WAIT, - SNSR_SOCK_CLOSE_WAIT, - _SNSR_SOCK_CLOSING, - _SNSR_SOCK_UDP, - ): - raise ConnectionError("Failed to initialize a connection with the socket.") - self.debug_print(f"* Opening W5500 Socket, protocol={conn_mode}") - time.sleep(0.00025) - - self._write_snmr(socket_num, conn_mode) - self.write_snir(socket_num, 0xFF) - - if self.src_port > 0: - # write to socket source port - self._write_sock_port(socket_num, self.src_port) - else: - s_port = randint(49152, 65535) - while s_port in self._src_ports_in_use: - s_port = randint(49152, 65535) - self._write_sock_port(socket_num, s_port) - self._src_ports_in_use[socket_num] = s_port - - # open socket - self._write_sncr(socket_num, _CMD_SOCK_OPEN) - if self._read_snsr(socket_num) not in [_SNSR_SOCK_INIT, _SNSR_SOCK_UDP]: - raise RuntimeError("Could not open socket in TCP or UDP mode.") - - def socket_close(self, socket_num: int) -> None: - """ - Close a socket. - - :param int socket_num: The socket to close. - - :raises ValueError: If the socket number is out of range. - """ - self.debug_print(f"*** Closing socket {socket_num}") - self._sock_num_in_range(socket_num) - self._write_sncr(socket_num, _CMD_SOCK_CLOSE) - self.debug_print(" Waiting for socket to close…") - timeout = time.monotonic() + 5.0 - while self._read_snsr(socket_num) != SNSR_SOCK_CLOSED: - if time.monotonic() > timeout: - raise RuntimeError( - "W5500 failed to close socket, status = {}.".format( - self._read_snsr(socket_num) - ) - ) - time.sleep(0.0001) - self.debug_print(" Socket has closed.") - - def socket_disconnect(self, socket_num: int) -> None: - """ - Disconnect a TCP or UDP connection. - - :param int socket_num: The socket to close. - - :raises ValueError: If the socket number is out of range. - """ - self.debug_print(f"*** Disconnecting socket {socket_num}") - self._sock_num_in_range(socket_num) - self._write_sncr(socket_num, _CMD_SOCK_DISCON) - - def socket_read(self, socket_num: int, length: int) -> Tuple[int, bytes]: - """ - Read data from a hardware socket. Called directly by TCP socket objects and via - read_udp() for UDP socket objects. - - :param int socket_num: The socket to read data from. - :param int length: The number of bytes to read from the socket. - - :returns Tuple[int, bytes]: If the read was successful then the first - item of the tuple is the length of the data and the second is the data. - If the read was unsuccessful then 0, b"" is returned. - - :raises ValueError: If the socket number is out of range. - :raises ConnectionError: If the Ethernet link is down. - :raises RuntimeError: If the socket connection has been lost. - """ - self._sock_num_in_range(socket_num) - self._check_link_status() - - # Check if there is data available on the socket - bytes_on_socket = self._get_rx_rcv_size(socket_num) - self.debug_print(f"Bytes avail. on sock: {bytes_on_socket}") - if bytes_on_socket: - bytes_on_socket = length if bytes_on_socket > length else bytes_on_socket - self.debug_print(f"* Processing {bytes_on_socket} bytes of data") - # Read the starting save address of the received data. - pointer = self._read_snrx_rd(socket_num) - # Read data from the hardware socket. - bytes_read = self._chip_socket_read(socket_num, pointer, bytes_on_socket) - # After reading the received data, update Sn_RX_RD register. - pointer = (pointer + bytes_on_socket) & 0xFFFF - self._write_snrx_rd(socket_num, pointer) - self._write_sncr(socket_num, _CMD_SOCK_RECV) - else: - # no data on socket - if self._read_snmr(socket_num) in ( - SNSR_SOCK_LISTEN, - SNSR_SOCK_CLOSED, - SNSR_SOCK_CLOSE_WAIT, - ): - raise RuntimeError("Lost connection to peer.") - bytes_read = b"" - return bytes_on_socket, bytes_read - - def read_udp(self, socket_num: int, length: int) -> Tuple[int, bytes]: - """ - Read UDP socket's current message bytes. - - :param int socket_num: The socket to read data from. - :param int length: The number of bytes to read from the socket. - - :return Tuple[int, bytes]: If the read was successful then the first - item of the tuple is the length of the data and the second is the data. - If the read was unsuccessful then (0, b"") is returned. - - :raises ValueError: If the socket number is out of range. - """ - self._sock_num_in_range(socket_num) - bytes_on_socket, bytes_read = 0, b"" - # Parse the UDP packet header. - header_length, self._pbuff[:8] = self.socket_read(socket_num, 8) - if header_length: - if header_length != 8: - raise ValueError("Invalid UDP header.") - data_length = self._chip_parse_udp_header(socket_num) - # Read the UDP packet data. - if data_length: - if data_length <= length: - bytes_on_socket, bytes_read = self.socket_read( - socket_num, data_length - ) - else: - bytes_on_socket, bytes_read = self.socket_read(socket_num, length) - # just consume the rest, it is lost to the higher layers - self.socket_read(socket_num, data_length - length) - return bytes_on_socket, bytes_read - - def socket_write( - self, socket_num: int, buffer: bytearray, timeout: float = 0.0 - ) -> int: - """ - Write data to a socket. - - :param int socket_num: The socket to write to. - :param bytearray buffer: The data to write to the socket. - :param float timeout: Write data timeout in seconds, defaults to 0.0 which waits - indefinitely. - - :return int: The number of bytes written to the socket. - - :raises ConnectionError: If the Ethernet link is down. - :raises ValueError: If the socket number is out of range. - :raises RuntimeError: If the data cannot be sent. - """ - self._sock_num_in_range(socket_num) - self._check_link_status() - if len(buffer) > _SOCK_SIZE: - bytes_to_write = _SOCK_SIZE - else: - bytes_to_write = len(buffer) - stop_time = time.monotonic() + timeout - - # If buffer is available, start the transfer - free_size = self._get_tx_free_size(socket_num) - while free_size < bytes_to_write: - free_size = self._get_tx_free_size(socket_num) - status = self.socket_status(socket_num) - if status not in (SNSR_SOCK_ESTABLISHED, SNSR_SOCK_CLOSE_WAIT) or ( - timeout and time.monotonic() > stop_time - ): - raise RuntimeError("Unable to write data to the socket.") - - # Read the starting address for saving the transmitting data. - pointer = self._read_sntx_wr(socket_num) - offset = pointer & _SOCK_MASK - self._chip_socket_write(socket_num, offset, bytes_to_write, buffer) - # update sn_tx_wr to the value + data size - pointer = (pointer + bytes_to_write) & 0xFFFF - self._write_sntx_wr(socket_num, pointer) - self._write_sncr(socket_num, _CMD_SOCK_SEND) - - # check data was transferred correctly - while not self.read_snir(socket_num) & _SNIR_SEND_OK: - if self.socket_status(socket_num) in ( - SNSR_SOCK_CLOSED, - SNSR_SOCK_TIME_WAIT, - SNSR_SOCK_FIN_WAIT, - SNSR_SOCK_CLOSE_WAIT, - _SNSR_SOCK_CLOSING, - ): - raise RuntimeError("No data was sent, socket was closed.") - if timeout and time.monotonic() > stop_time: - raise RuntimeError("Operation timed out. No data sent.") - if self.read_snir(socket_num) & SNIR_TIMEOUT: - self.write_snir(socket_num, SNIR_TIMEOUT) - # TCP sockets are closed by the hardware timeout - # so that will be caught at the while statement. - # UDP sockets are 1:many so not closed thus return 0. - if self._read_snmr(socket_num) == SNMR_UDP: - return 0 - time.sleep(0.001) - self.write_snir(socket_num, _SNIR_SEND_OK) - return bytes_to_write - - def sw_reset(self) -> None: - """ - Soft reset and reinitialize the WIZnet chip. - - :raises RuntimeError: If reset fails. - """ - self._wiznet_chip_init() - - def _sw_reset_5x00(self) -> bool: - """ - Perform a soft reset on the WIZnet 5100s and 5500 chips. - - :returns bool: True if reset was success - """ - self._write_mr(_MR_RST) - time.sleep(0.05) - return self._read_mr() == 0x00 - - def _wiznet_chip_init(self) -> None: - """ - Detect and initialize a WIZnet 5k Ethernet module. - - :raises RuntimeError: If no WIZnet chip is detected. - """ - - def _setup_sockets() -> None: - """Initialise sockets for w5500 chips.""" - for sock_num in range(_MAX_SOCK_NUM): - ctrl_byte = 0x0C + (sock_num << 5) - self._write(0x1E, ctrl_byte, 2) - self._write(0x1F, ctrl_byte, 2) - self._ch_base_msb = 0x00 - WIZNET5500._sockets_reserved = [False] * (_MAX_SOCK_NUM - 1) - self._src_ports_in_use = [0] * _MAX_SOCK_NUM - - def _detect_and_reset_w5500() -> bool: - """ - Detect and reset a W5500 chip. Called at startup to initialize the - interface hardware. - - :return bool: True if a W5500 chip is detected, False if not. - """ - self._chip_type = "w5500" - if not self._sw_reset_5x00(): - return False - - self._write_mr(0x08) - if self._read_mr() != 0x08: - return False - - self._write_mr(0x10) - if self._read_mr() != 0x10: - return False - - self._write_mr(0x00) - if self._read_mr() != 0x00: - return False - - if self._read(_REG_VERSIONR, 0x00)[0] != 0x04: - return False - # Initialize w5500 - _setup_sockets() - return True - - for func in [ - _detect_and_reset_w5500, - ]: - if func(): - return - self._chip_type = None - raise RuntimeError("Failed to initialize WIZnet module.") - - def _sock_num_in_range(self, sock: int) -> None: - """Check that the socket number is in the range 0 - maximum sockets.""" - if not 0 <= sock < self.max_sockets: - raise ValueError("Socket number out of range.") - - def _check_link_status(self): - """Raise an exception if the link is down.""" - if not self.link_status: - raise ConnectionError("The Ethernet connection is down.") - - @staticmethod - def _read_socket_reservations() -> list[int]: - """Return the list of reserved sockets.""" - return WIZNET5500._sockets_reserved - - def _read_mr(self) -> int: - """Read from the Mode Register (MR).""" - return int.from_bytes(self._read(_REG_MR, 0x00), "big") - - def _write_mr(self, data: int) -> None: - """Write to the mode register (MR).""" - self._write(_REG_MR, 0x04, data) - - # *** Low Level Methods *** - - def _read( - self, - addr: int, - callback: int, - length: int = 1, - ) -> bytes: - """ - Read data from a register address. - - :param int addr: Register address to read. - :param int callback: Callback reference. - :param int length: Number of bytes to read from the register, defaults to 1. - - :return bytes: Data read from the chip. - """ - with self._device as bus_device: - self._chip_read(bus_device, addr, callback) - self._rxbuf = bytearray(length) - bus_device.readinto(self._rxbuf) - return bytes(self._rxbuf) - - def _write(self, addr: int, callback: int, data: Union[int, bytes]) -> None: - """ - Write data to a register address. - - :param int addr: Destination address. - :param int callback: Callback reference. - :param Union[int, bytes] data: Data to write to the register address, if data - is an integer, it must be 1 or 2 bytes. - - :raises OverflowError: if integer data is more than 2 bytes. - """ - with self._device as bus_device: - self._chip_write(bus_device, addr, callback) - try: - data = data.to_bytes(1, "big") - except OverflowError: - data = data.to_bytes(2, "big") - except AttributeError: - pass - bus_device.write(data) - - def _read_two_byte_sock_reg(self, sock: int, reg_address: int) -> int: - """Read a two byte socket register.""" - register = self._read_socket_register(sock, reg_address) << 8 - register += self._read_socket_register(sock, reg_address + 1) - return register - - def _write_two_byte_sock_reg(self, sock: int, reg_address: int, data: int) -> None: - """Write to a two byte socket register.""" - self._write_socket_register(sock, reg_address, data >> 8 & 0xFF) - self._write_socket_register(sock, reg_address + 1, data & 0xFF) - - # *** Socket Register Methods *** - - def _get_rx_rcv_size(self, sock: int) -> int: - """Size of received and saved in socket buffer.""" - val = 0 - val_1 = self._read_snrx_rsr(sock) - while val != val_1: - val_1 = self._read_snrx_rsr(sock) - if val_1 != 0: - val = self._read_snrx_rsr(sock) - return val - - def _get_tx_free_size(self, sock: int) -> int: - """Free size of socket's tx buffer block.""" - val = 0 - val_1 = self._read_sntx_fsr(sock) - while val != val_1: - val_1 = self._read_sntx_fsr(sock) - if val_1 != 0: - val = self._read_sntx_fsr(sock) - return val - - def _read_snrx_rd(self, sock: int) -> int: - """Read socket n RX Read Data Pointer Register.""" - return self._read_two_byte_sock_reg(sock, _REG_SNRX_RD) - - def _write_snrx_rd(self, sock: int, data: int) -> None: - """Write socket n RX Read Data Pointer Register.""" - self._write_two_byte_sock_reg(sock, _REG_SNRX_RD, data) - - def _read_sntx_wr(self, sock: int) -> int: - """Read the socket write buffer pointer for socket `sock`.""" - return self._read_two_byte_sock_reg(sock, _REG_SNTX_WR) - - def _write_sntx_wr(self, sock: int, data: int) -> None: - """Write the socket write buffer pointer for socket `sock`.""" - self._write_two_byte_sock_reg(sock, _REG_SNTX_WR, data) - - def _read_sntx_fsr(self, sock: int) -> int: - """Read socket n TX Free Size Register""" - return self._read_two_byte_sock_reg(sock, _REG_SNTX_FSR) - - def _read_snrx_rsr(self, sock: int) -> int: - """Read socket n Received Size Register""" - return self._read_two_byte_sock_reg(sock, _REG_SNRX_RSR) - - def _read_sndipr(self, sock) -> bytes: - """Read socket destination IP address.""" - data = [] - for offset in range(4): - data.append(self._read_socket_register(sock, _REG_SNDIPR + offset)) - return bytes(data) - - def _write_sndipr(self, sock: int, ip_addr: bytes) -> None: - """Write to socket destination IP Address.""" - for offset, value in enumerate(ip_addr): - self._write_socket_register(sock, _REG_SNDIPR + offset, value) - - def _read_sndport(self, sock: int) -> int: - """Read socket destination port.""" - return self._read_two_byte_sock_reg(sock, _REG_SNDPORT) - - def _write_sndport(self, sock: int, port: int) -> None: - """Write to socket destination port.""" - self._write_two_byte_sock_reg(sock, _REG_SNDPORT, port) - - def _read_snsr(self, sock: int) -> int: - """Read Socket n Status Register.""" - return self._read_socket_register(sock, _REG_SNSR) - - def read_snir(self, sock: int) -> int: - """Read Socket n Interrupt Register.""" - return self._read_socket_register(sock, _REG_SNIR) - - def write_snir(self, sock: int, data: int) -> None: - """Write to Socket n Interrupt Register.""" - self._write_socket_register(sock, _REG_SNIR, data) - - def _read_snmr(self, sock: int) -> int: - """Read the socket MR register.""" - return self._read_socket_register(sock, _REG_SNMR) - - def _write_snmr(self, sock: int, protocol: int) -> None: - """Write to Socket n Mode Register.""" - self._write_socket_register(sock, _REG_SNMR, protocol) - - def _write_sock_port(self, sock: int, port: int) -> None: - """Write to the socket port number.""" - self._write_two_byte_sock_reg(sock, _REG_SNPORT, port) - - def _write_sncr(self, sock: int, data: int) -> None: - """Write to socket command register.""" - self._write_socket_register(sock, _REG_SNCR, data) - # Wait for command to complete before continuing. - while self._read_socket_register(sock, _REG_SNCR): - pass - - @property - def rcr(self) -> int: - """Retry count register.""" - return int.from_bytes(self._read(_REG_RCR, 0x00), "big") - - @rcr.setter - def rcr(self, retry_count: int) -> None: - """Retry count register.""" - if 0 > retry_count > 255: - raise ValueError("Retries must be from 0 to 255.") - self._write(_REG_RCR, 0x04, retry_count) - - @property - def rtr(self) -> int: - """Retry time register.""" - return int.from_bytes(self._read(_REG_RTR, 0x00, 2), "big") - - @rtr.setter - def rtr(self, retry_time: int) -> None: - """Retry time register.""" - if 0 > retry_time >= 2**16: - raise ValueError("Retry time must be from 0 to 65535") - self._write(_REG_RTR, 0x04, retry_time) - - # *** Chip Specific Methods *** - - def _chip_read(self, device: "busio.SPI", address: int, call_back: int) -> None: - """Chip specific calls for _read method.""" - if self._chip_type in ("w5500", "w6100"): - device.write((address >> 8).to_bytes(1, "big")) - device.write((address & 0xFF).to_bytes(1, "big")) - device.write(call_back.to_bytes(1, "big")) - elif self._chip_type == "w5100s": - device.write((0x0F).to_bytes(1, "big")) - device.write((address >> 8).to_bytes(1, "big")) - device.write((address & 0xFF).to_bytes(1, "big")) - - def _chip_write(self, device: "busio.SPI", address: int, call_back: int) -> None: - """Chip specific calls for _write.""" - if self._chip_type in ("w5500", "w6100"): - device.write((address >> 8).to_bytes(1, "big")) - device.write((address & 0xFF).to_bytes(1, "big")) - device.write(call_back.to_bytes(1, "big")) - elif self._chip_type == "w5100s": - device.write((0xF0).to_bytes(1, "big")) - device.write((address >> 8).to_bytes(1, "big")) - device.write((address & 0xFF).to_bytes(1, "big")) - - def _chip_socket_read(self, socket_number, pointer, bytes_to_read): - """Chip specific calls for socket_read.""" - if self._chip_type in ("w5500", "w6100"): - # Read data from the starting address of snrx_rd - ctrl_byte = 0x18 + (socket_number << 5) - bytes_read = self._read(pointer, ctrl_byte, bytes_to_read) - elif self._chip_type == "w5100s": - offset = pointer & _SOCK_MASK - src_addr = offset + (socket_number * _SOCK_SIZE + 0x6000) - if offset + bytes_to_read > _SOCK_SIZE: - split_point = _SOCK_SIZE - offset - bytes_read = self._read(src_addr, 0x00, split_point) - split_point = bytes_to_read - split_point - src_addr = socket_number * _SOCK_SIZE + 0x6000 - bytes_read += self._read(src_addr, 0x00, split_point) - else: - bytes_read = self._read(src_addr, 0x00, bytes_to_read) - return bytes_read - - def _chip_socket_write( - self, socket_number: int, offset: int, bytes_to_write: int, buffer: bytes - ): - """Chip specific calls for socket_write.""" - if self._chip_type in ("w5500", "w6100"): - dst_addr = offset + (socket_number * _SOCK_SIZE + 0x8000) - cntl_byte = 0x14 + (socket_number << 5) - self._write(dst_addr, cntl_byte, buffer[:bytes_to_write]) - - elif self._chip_type == "w5100s": - dst_addr = offset + (socket_number * _SOCK_SIZE + 0x4000) - - if offset + bytes_to_write > _SOCK_SIZE: - split_point = _SOCK_SIZE - offset - self._write(dst_addr, 0x00, buffer[:split_point]) - dst_addr = socket_number * _SOCK_SIZE + 0x4000 - self._write(dst_addr, 0x00, buffer[split_point:bytes_to_write]) - else: - self._write(dst_addr, 0x00, buffer[:bytes_to_write]) - - def _chip_parse_udp_header(self, socket_num) -> int: - """ - Parse chip specific UDP header data for IPv4 packets. - - Sets the source IPv4 address and port number and returns the UDP data length. - - :return int: The UDP data length. - """ - if self._chip_type in ("w5100s", "w5500"): - self.udp_from_ip[socket_num] = self._pbuff[:4] - self.udp_from_port[socket_num] = int.from_bytes(self._pbuff[4:6], "big") - return int.from_bytes(self._pbuff[6:], "big") - if self._chip_type == "w6100": - self.udp_from_ip[socket_num] = self._pbuff[3:7] - self.udp_from_port[socket_num] = int.from_bytes(self._pbuff[6:], "big") - return int.from_bytes(self._pbuff[:2], "big") & 0x07FF - raise ValueError("Unsupported chip type.") - - def _write_socket_register(self, sock: int, address: int, data: int) -> None: - """Write to a WIZnet 5k socket register.""" - if self._chip_type in ("w5500", "w6100"): - cntl_byte = (sock << 5) + 0x0C - self._write(address, cntl_byte, data) - elif self._chip_type == "w5100s": - cntl_byte = 0 - self._write(self._ch_base_msb + sock * _CH_SIZE + address, cntl_byte, data) - - def _read_socket_register(self, sock: int, address: int) -> int: - """Read a WIZnet 5k socket register.""" - if self._chip_type in ("w5500", "w6100"): - cntl_byte = (sock << 5) + 0x08 - register = self._read(address, cntl_byte) - elif self._chip_type == "w5100s": - cntl_byte = 0 - register = self._read( - self._ch_base_msb + sock * _CH_SIZE + address, cntl_byte - ) - return int.from_bytes(register, "big") - - -_the_interface: Optional[WIZNET5500] = None -_default_socket_timeout = None - - -def _is_ipv4_string(ipv4_address: str) -> bool: - """Check for a valid IPv4 address in dotted-quad string format - (for example, "123.45.67.89"). - - :param: str ipv4_address: The string to test. - - :return bool: True if a valid IPv4 address, False otherwise. - """ - octets = ipv4_address.split(".", 3) - if len(octets) == 4 and "".join(octets).isdigit(): - if all((0 <= int(octet) <= 255 for octet in octets)): - return True - return False - - -def set_interface(iface: WIZNET5500) -> None: - """ - Helper to set the global internet interface. - - :param wiznet5k.adafruit_wiznet5k.WIZNET5K iface: The ethernet interface. - """ - global _the_interface # pylint: disable=global-statement, invalid-name - _the_interface = iface - - -def getdefaulttimeout() -> Optional[float]: - """ - Return the default timeout in seconds for new socket objects. A value of - None indicates that new socket objects have no timeout. When the socket module is - first imported, the default is None. - """ - return _default_socket_timeout - - -def setdefaulttimeout(timeout: Optional[float]) -> None: - """ - Set the default timeout in seconds (float) for new socket objects. When the socket - module is first imported, the default is None. See settimeout() for possible values - and their respective meanings. - - :param Optional[float] timeout: The default timeout in seconds or None. - """ - global _default_socket_timeout # pylint: disable=global-statement - if timeout is None or (isinstance(timeout, (int, float)) and timeout >= 0): - _default_socket_timeout = timeout - else: - raise ValueError("Timeout must be None, 0.0 or a positive numeric value.") - - -def htonl(x: int) -> int: - """ - Convert 32-bit positive integer from host to network byte order. - - :param int x: 32-bit positive integer from host. - - :return int: 32-bit positive integer in network byte order. - """ - if byteorder == "big": - return x - return int.from_bytes(x.to_bytes(4, "little"), "big") - - -def htons(x: int) -> int: - """ - Convert 16-bit positive integer from host to network byte order. - - :param int x: 16-bit positive integer from host. - - :return int: 16-bit positive integer in network byte order. - """ - if byteorder == "big": - return x - return ((x << 8) & 0xFF00) | ((x >> 8) & 0xFF) - - -def inet_aton(ip_address: str) -> bytes: - """ - Convert an IPv4 address from dotted-quad string format (for example, "123.45.67.89") - to 32-bit packed binary format, as a bytes object four characters in length. This is - useful when conversing with a program that uses the standard C library and needs - objects of type struct in_addr, which is the C type for the 32-bit packed binary this - function returns. - - :param str ip_address: The IPv4 address to convert. - - :return bytes: The converted IPv4 address. - """ - if not _is_ipv4_string(ip_address): - raise ValueError("The IPv4 address must be a dotted-quad string.") - return _the_interface.unpretty_ip(ip_address) - - -def inet_ntoa(ip_address: Union[bytes, bytearray]) -> str: - """ - Convert a 32-bit packed IPv4 address (a bytes-like object four bytes in length) to - its standard dotted-quad string representation (for example, ‘123.45.67.89’). This is - useful when conversing with a program that uses the standard C library and needs - objects of type struct in_addr, which is the C type for the 32-bit packed binary data - this function takes as an argument. - - :param Union[bytes, bytearray ip_address: The IPv4 address to convert. - - :return str: The converted ip_address: - """ - if len(ip_address) != 4: - raise ValueError("The IPv4 address must be 4 bytes.") - return _the_interface.pretty_ip(ip_address) - - -SOCK_STREAM = const(0x21) # TCP -SOCK_DGRAM = const(0x02) # UDP -AF_INET = const(3) - - -# pylint: disable=too-many-arguments, unused-argument -def getaddrinfo( - host: str, - port: int, - family: int = 0, - type: int = 0, - proto: int = 0, - flags: int = 0, -) -> List[Tuple[int, int, int, str, Tuple[str, int]]]: - """ - Translate the host/port argument into a sequence of 5-tuples that contain all the necessary - arguments for creating a socket connected to that service. - - :param str host: a domain name, a string representation of an IPv4 address or - None. - :param int port: Port number to connect to (0 - 65536). - :param int family: Ignored and hardcoded as 0x03 (the only family implemented) by the function. - :param int type: The type of socket, either SOCK_STREAM (0x21) for TCP or SOCK_DGRAM (0x02) - for UDP, defaults to 0. - :param int proto: Unused in this implementation of socket. - :param int flags: Unused in this implementation of socket. - - :return List[Tuple[int, int, int, str, Tuple[str, int]]]: Address info entries in the form - (family, type, proto, canonname, sockaddr). In these tuples, family, type, proto are meant - to be passed to the socket() function. canonname will always be an empty string, sockaddr - is a tuple describing a socket address, whose format is (address, port), and is meant to be - passed to the socket.connect() method. - """ - if not isinstance(port, int): - raise ValueError("Port must be an integer") - if not _is_ipv4_string(host): - host = gethostbyname(host) - return [(AF_INET, type, proto, "", (host, port))] - - -def gethostbyname(hostname: str) -> str: - """ - Translate a host name to IPv4 address format. The IPv4 address is returned as a string, such - as '100.50.200.5'. If the host name is an IPv4 address itself it is returned unchanged. - - :param str hostname: Hostname to lookup. - - :return str: IPv4 address (a string of the form '0.0.0.0'). - """ - if _is_ipv4_string(hostname): - return hostname - address = _the_interface.get_host_by_name(hostname) - address = "{}.{}.{}.{}".format(address[0], address[1], address[2], address[3]) - return address - - -class socket: - """ - A simplified implementation of the Python 'socket' class for connecting - to a Wiznet5k module. - """ - - def debug_print(self, statement: str = "") -> None: - """ - :param statement: message to get printed in debug print - """ - if self._debug: - print(co("[SOCKET]" + str(statement), "blue", "bold")) - - def __init__( - self, - family: int = AF_INET, - type: int = SOCK_STREAM, - proto: int = 0, - fileno: Optional[int] = None, - debug: bool = True, - ) -> None: - """ - :param int family: Socket address (and protocol) family, defaults to AF_INET. - :param int type: Socket type, use SOCK_STREAM for TCP and SOCK_DGRAM for UDP, - defaults to SOCK_STREAM. - :param int proto: Unused, retained for compatibility. - :param Optional[int] fileno: Unused, retained for compatibility. - """ - self._debug = debug - if family != AF_INET: - raise RuntimeError("Only AF_INET family supported by W5K modules.") - self._socket_closed = False - self._sock_type = type - self._buffer = b"" - self._timeout = _default_socket_timeout - self._listen_port = None - - self._socknum = _the_interface.get_socket(reserve_socket=True) - if self._socknum == _SOCKET_INVALID: - raise RuntimeError("Failed to allocate socket.") - - def __del__(self): - _the_interface.release_socket(self._socknum) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb) -> None: - _the_interface.release_socket(self._socknum) - if self._sock_type == SOCK_STREAM: - _the_interface.write_snir( - self._socknum, 0xFF - ) # Reset socket interrupt register. - _the_interface.socket_disconnect(self._socknum) - mask = SNIR_TIMEOUT | SNIR_DISCON - while not _the_interface.read_snir(self._socknum) & mask: - pass - _the_interface.write_snir( - self._socknum, 0xFF - ) # Reset socket interrupt register. - _the_interface.socket_close(self._socknum) - while _the_interface.socket_status(self._socknum) != SNSR_SOCK_CLOSED: - pass - - # This works around problems with using a class method as a decorator. - def _check_socket_closed(func): # pylint: disable=no-self-argument - """Decorator to check whether the socket object has been closed.""" - - def wrapper(self, *args, **kwargs): - if self._socket_closed: # pylint: disable=protected-access - raise RuntimeError("The socket has been closed.") - return func(self, *args, **kwargs) # pylint: disable=not-callable - - return wrapper - - @property - def _status(self) -> int: - """ - Return the status of the socket. - - :return int: Status of the socket. - """ - return _the_interface.socket_status(self._socknum) - - @property - def _connected(self) -> bool: - """ - Return whether connected to the socket. - - :return bool: Whether connected. - """ - # pylint: disable=protected-access - - if self._socknum >= _the_interface.max_sockets: - return False - status = _the_interface.socket_status(self._socknum) - if status == SNSR_SOCK_CLOSE_WAIT and self._available() == 0: - result = False - else: - result = status not in ( - SNSR_SOCK_CLOSED, - SNSR_SOCK_LISTEN, - SNSR_SOCK_TIME_WAIT, - SNSR_SOCK_FIN_WAIT, - ) - if not result and status != SNSR_SOCK_LISTEN: - self.close() - return result - - @_check_socket_closed - def getpeername(self) -> Tuple[str, int]: - """ - Return the remote address to which the socket is connected. - - :return Tuple[str, int]: IPv4 address and port the socket is connected to. - """ - return _the_interface.remote_ip(self._socknum), _the_interface.remote_port( - self._socknum - ) - - @_check_socket_closed - def bind(self, address: Tuple[Optional[str], int]) -> None: - """ - Bind the socket to address. The socket must not already be bound. - - The hardware sockets on WIZNET5K systems all share the same IPv4 address. The - address is assigned at startup. Ports can only be bound to this address. - - :param Tuple[Optional[str], int] address: Address as a (host, port) tuple. - - :raises ValueError: If the IPv4 address specified is not the address - assigned to the WIZNET5K interface. - """ - # Check to see if the socket is bound. - if self._listen_port: - raise ConnectionError("The socket is already bound.") - self._bind(address) - - def _bind(self, address: Tuple[Optional[str], int]) -> None: - """ - Helper function to allow bind() to check for an existing connection and for - accept() to generate a new socket connection. - - :param Tuple[Optional[str], int] address: Address as a (host, port) tuple. - """ - if address[0]: - if gethostbyname(address[0]) != _the_interface.pretty_ip( - _the_interface.ip_address - ): - raise ValueError( - "The IPv4 address requested must match {}, " - "the one assigned to the WIZNET5K interface.".format( - _the_interface.pretty_ip(_the_interface.ip_address) - ) - ) - self._listen_port = address[1] - # For UDP servers we need to open the socket here because we won't call - # listen - if self._sock_type == SOCK_DGRAM: - _the_interface.socket_listen( - self._socknum, - self._listen_port, - SNMR_UDP, - ) - self._buffer = b"" - - @_check_socket_closed - def listen(self, backlog: int = 0) -> None: - """ - Enable a server to accept connections. - - :param int backlog: Included for compatibility but ignored. - """ - if self._listen_port is None: - raise RuntimeError("Use bind to set the port before listen!") - _the_interface.socket_listen(self._socknum, self._listen_port) - self._buffer = b"" - - @_check_socket_closed - def accept( - self, - ) -> Tuple[socket, Tuple[str, int]]: - """ - Accept a connection. The socket must be bound to an address and listening for connections. - - :return Tuple[socket, Tuple[str, int]]: The return value is a pair - (conn, address) where conn is a new socket object to send and receive data on - the connection, and address is the address bound to the socket on the other - end of the connection. - """ - stamp = time.monotonic() - while self._status not in ( - SNSR_SOCK_SYNRECV, - SNSR_SOCK_ESTABLISHED, - ): - self.debug_print("SNSR: " + str(self._status)) - if self._timeout and 0 < self._timeout < time.monotonic() - stamp: - raise TimeoutError("Failed to accept connection.") - if self._status == SNSR_SOCK_CLOSED: - self.debug_print("Socket closed!") - self.close() - self.listen() - self.debug_print("SYN receievd or socket established!") - _, addr = _the_interface.socket_accept(self._socknum) - current_socknum = self._socknum - # Create a new socket object and swap socket nums, so we can continue listening - client_sock = socket() - self._socknum = client_sock._socknum # pylint: disable=protected-access - client_sock._socknum = current_socknum # pylint: disable=protected-access - self._bind((None, self._listen_port)) - self.listen() - while self._status != SNSR_SOCK_LISTEN: - raise RuntimeError("Failed to open new listening socket") - return client_sock, addr - - @_check_socket_closed - def connect(self, address: Tuple[str, int]) -> None: - """ - Connect to a remote socket at address. - - :param Tuple[str, int] address: Remote socket as a (host, port) tuple. - """ - if self._listen_port is not None: - _the_interface.src_port = self._listen_port - result = _the_interface.socket_connect( - self._socknum, - _the_interface.unpretty_ip(gethostbyname(address[0])), - address[1], - self._sock_type, - ) - _the_interface.src_port = 0 - if not result: - raise RuntimeError("Failed to connect to host ", address[0]) - self._buffer = b"" - - @_check_socket_closed - def send(self, data: Union[bytes, bytearray]) -> int: - """ - Send data to the socket. The socket must be connected to a remote socket. - Applications are responsible for checking that all data has been sent; if - only some of the data was transmitted, the application needs to attempt - delivery of the remaining data. - - :param bytearray data: Data to send to the socket. - - :return int: Number of bytes sent. - """ - timeout = 0 if self._timeout is None else self._timeout - bytes_sent = _the_interface.socket_write(self._socknum, data, timeout) - gc.collect() - return bytes_sent - - @_check_socket_closed - def sendto(self, data: bytearray, *flags_and_or_address: any) -> int: - """ - Send data to the socket. The socket should not be connected to a remote socket, since the - destination socket is specified by address. Return the number of bytes sent.. - - Either: - :param bytearray data: Data to send to the socket. - :param [Tuple[str, int]] address: Remote socket as a (host, port) tuple. - - Or: - :param bytearray data: Data to send to the socket. - :param int flags: Not implemented, kept for compatibility. - :param Tuple[int, Tuple(str, int)] address: Remote socket as a (host, port) tuple - """ - # May be called with (data, address) or (data, flags, address) - other_args = list(flags_and_or_address) - if len(other_args) in (1, 2): - address = other_args[-1] - else: - raise ValueError("Incorrect number of arguments, should be 2 or 3.") - self.connect(address) - return self.send(data) - - @_check_socket_closed - def recv( - # pylint: disable=too-many-branches - self, - bufsize: int, - flags: int = 0, - ) -> bytes: - """ - Receive data from the socket. The return value is a bytes object representing the data - received. The maximum amount of data to be received at once is specified by bufsize. - - :param int bufsize: Maximum number of bytes to receive. - :param int flags: ignored, present for compatibility. - - :return bytes: Data from the socket. - """ - stamp = time.monotonic() - while not self._available(): - if self._timeout and 0 < self._timeout < time.monotonic() - stamp: - break - time.sleep(0.05) - bytes_on_socket = self._available() - if not bytes_on_socket: - return b"" - bytes_to_read = min(bytes_on_socket, bufsize) - if self._sock_type == SOCK_STREAM: - bytes_read = _the_interface.socket_read(self._socknum, bytes_to_read)[1] - else: - bytes_read = _the_interface.read_udp(self._socknum, bytes_to_read)[1] - gc.collect() - return bytes(bytes_read) - - def _embed_recv( - self, bufsize: int = 0, flags: int = 0 - ) -> bytes: # pylint: disable=too-many-branches - """ - Read from the connected remote address. - - :param int bufsize: Maximum number of bytes to receive, ignored by the - function, defaults to 0. - :param int flags: ignored, present for compatibility. - - :return bytes: All data available from the connection. - """ - avail = self._available() - if avail: - if self._sock_type == SOCK_STREAM: - self._buffer += _the_interface.socket_read(self._socknum, avail)[1] - elif self._sock_type == SOCK_DGRAM: - self._buffer += _the_interface.read_udp(self._socknum, avail)[1] - gc.collect() - ret = self._buffer - self._buffer = b"" - gc.collect() - return ret - - @_check_socket_closed - def recvfrom(self, bufsize: int, flags: int = 0) -> Tuple[bytes, Tuple[str, int]]: - """ - Receive data from the socket. The return value is a pair (bytes, address) where bytes is - a bytes object representing the data received and address is the address of the socket - sending the data. - - :param int bufsize: Maximum number of bytes to receive. - :param int flags: Ignored, present for compatibility. - - :return Tuple[bytes, Tuple[str, int]]: a tuple (bytes, address) - where address is a tuple (address, port) - """ - return ( - self.recv(bufsize), - ( - _the_interface.pretty_ip(_the_interface.udp_from_ip[self._socknum]), - _the_interface.udp_from_port[self._socknum], - ), - ) - - @_check_socket_closed - def recv_into(self, buffer: bytearray, nbytes: int = 0, flags: int = 0) -> int: - """ - Receive up to nbytes bytes from the socket, storing the data into a buffer - rather than creating a new bytestring. - - :param bytearray buffer: Data buffer to read into. - :param nbytes: Maximum number of bytes to receive (if 0, use length of buffer). - :param int flags: ignored, present for compatibility. - - :return int: the number of bytes received - """ - if nbytes == 0: - nbytes = len(buffer) - bytes_received = self.recv(nbytes) - nbytes = len(bytes_received) - buffer[:nbytes] = bytes_received - return nbytes - - @_check_socket_closed - def recvfrom_into( - self, buffer: bytearray, nbytes: int = 0, flags: int = 0 - ) -> Tuple[int, Tuple[str, int]]: - """ - Receive data from the socket, writing it into buffer instead of creating a new bytestring. - The return value is a pair (nbytes, address) where nbytes is the number of bytes received - and address is the address of the socket sending the data. - - :param bytearray buffer: Data buffer. - :param int nbytes: Maximum number of bytes to receive. - :param int flags: Unused, present for compatibility. - - :return Tuple[int, Tuple[str, int]]: The number of bytes and address. - """ - return ( - self.recv_into(buffer, nbytes), - ( - _the_interface.remote_ip(self._socknum), - _the_interface.remote_port(self._socknum), - ), - ) - - def _readline(self) -> bytes: - """ - Read a line from the socket. - - Deprecated, will be removed in the future. - - Attempt to return as many bytes as we can up to but not including a carriage return and - linefeed character pair. - - :return bytes: The data read from the socket. - """ - stamp = time.monotonic() - while b"\r\n" not in self._buffer: - avail = self._available() - if avail: - if self._sock_type == SOCK_STREAM: - self._buffer += _the_interface.socket_read(self._socknum, avail)[1] - elif self._sock_type == SOCK_DGRAM: - self._buffer += _the_interface.read_udp(self._socknum, avail)[1] - if ( - self._timeout - and not avail - and 0 < self._timeout < time.monotonic() - stamp - ): - self.close() - raise RuntimeError("Didn't receive response, failing out...") - firstline, self._buffer = self._buffer.split(b"\r\n", 1) - gc.collect() - return firstline - - def _disconnect(self) -> None: - """Disconnect a TCP socket.""" - if self._sock_type != SOCK_STREAM: - raise RuntimeError("Socket must be a TCP socket.") - _the_interface.socket_disconnect(self._socknum) - - @_check_socket_closed - def close(self) -> None: - """ - Mark the socket closed. Once that happens, all future operations on the socket object - will fail. The remote end will receive no more data. - """ - _the_interface.release_socket(self._socknum) - _the_interface.socket_close(self._socknum) - self._socket_closed = True - - def _available(self) -> int: - """ - Return how many bytes of data are available to be read from the socket. - - :return int: Number of bytes available. - """ - return _the_interface.socket_available(self._socknum, self._sock_type) - - @_check_socket_closed - def settimeout(self, value: Optional[float]) -> None: - """ - Set a timeout on blocking socket operations. The value argument can be a - non-negative floating point number expressing seconds, or None. If a non-zero - value is given, subsequent socket operations will raise a timeout exception - if the timeout period value has elapsed before the operation has completed. - If zero is given, the socket is put in non-blocking mode. If None is given, - the socket is put in blocking mode.. - - :param Optional[float] value: Socket read timeout in seconds. - """ - if value is None or (isinstance(value, (int, float)) and value >= 0): - self._timeout = value - else: - raise ValueError("Timeout must be None, 0.0 or a positive numeric value.") - - @_check_socket_closed - def gettimeout(self) -> Optional[float]: - """ - Return the timeout in seconds (float) associated with socket operations, or None if no - timeout is set. This reflects the last call to setblocking() or settimeout(). - - :return Optional[float]: Timeout in seconds, or None if no timeout is set. - """ - return self._timeout - - @_check_socket_closed - def setblocking(self, flag: bool) -> None: - """ - Set blocking or non-blocking mode of the socket: if flag is false, the socket is set - to non-blocking, else to blocking mode. - - This method is a shorthand for certain settimeout() calls: - - sock.setblocking(True) is equivalent to sock.settimeout(None) - sock.setblocking(False) is equivalent to sock.settimeout(0.0) - - :param bool flag: The blocking mode of the socket. - - :raises TypeError: If flag is not a bool. - - """ - if flag is True: - self.settimeout(None) - elif flag is False: - self.settimeout(0.0) - else: - raise TypeError("Flag must be a boolean.") - - @_check_socket_closed - def getblocking(self) -> bool: - """ - Return True if socket is in blocking mode, False if in non-blocking. - - This is equivalent to checking socket.gettimeout() == 0. - - :return bool: Blocking mode of the socket. - """ - return self.gettimeout() == 0 - - @property - @_check_socket_closed - def family(self) -> int: - """Socket family (always 0x03 in this implementation).""" - return 3 - - @property - @_check_socket_closed - def type(self): - """Socket type.""" - return self._sock_type - - @property - @_check_socket_closed - def proto(self): - """Socket protocol (always 0x00 in this implementation).""" - return 0 diff --git a/experimental/sf_hop.py b/experimental/sf_hop.py deleted file mode 100644 index 5d9c541..0000000 --- a/experimental/sf_hop.py +++ /dev/null @@ -1,85 +0,0 @@ -import pysquared_rfm9x # Radio -import board -import digitalio -import time -import busio - -device = "sat" - -# Initialize SPI -spi = busio.SPI(board.SPI0_SCK, board.SPI0_MOSI, board.SPI0_MISO) - -# Initialize the radio -_rf_cs1 = digitalio.DigitalInOut(board.SPI0_CS) -_rf_rst1 = digitalio.DigitalInOut(board.RF_RESET) -enable_rf = digitalio.DigitalInOut(board.ENABLE_RF) -radio1_DIO0 = digitalio.DigitalInOut(board.RF_IO0) -radio1_DIO4 = digitalio.DigitalInOut(board.RF_IO4) - -enable_rf.direction = digitalio.Direction.OUTPUT -enable_rf.value = True - -radio1 = pysquared_rfm9x.RFM9x(spi, _rf_cs1, _rf_rst1, frequency=437.4) -radio1.dio0 = radio1_DIO0 -radio1.max_output = True -# set up LoRa spreading factors to try -spreading_factors = [7, 8, 9, 10, 11, 12] - -timeout = 1 - - -def find_spreading_factor(): - for spreading_factor in spreading_factors: - # set LoRa spreading factor - radio1.spreading_factor = spreading_factor - if spreading_factor > 10: - radio1.low_datarate_optimize = 0 - radio1.preamble_length = spreading_factor - - timeout = 3 - else: - radio1.low_datarate_optimize = 0 - timeout = 1 - radio1.preamble_length - 8 - print(f"Attempting Spreading Factor: {spreading_factor}") - - radio1.send("Hello World!") - - packet = radio1.receive(timeout=timeout) - - # check if LoRa module is receiving signals - if packet is not None: - print( - f"Received packet on spreading factor: {spreading_factor}: {packet}, RSSI: {radio1.last_rssi-137}" - ) - else: - print(f"No Device on Spreading Factor: {spreading_factor}") - - -def respond_to_ping(): - spreading_factor = 7 - radio1.spreading_factor = spreading_factor - - while True: - packet = radio1.receive(timeout=timeout) - if packet is not None: - print( - f"Received packet on spreading factor: {spreading_factor}: {packet}, RSSI: {radio1.last_rssi}" - ) - radio1.send("Good to see you!") - - if spreading_factor < 12: - spreading_factor += 1 - radio1.spreading_factor = spreading_factor - - else: - spreading_factor = 7 - else: - print(f"No Device on Spreading Factor: {spreading_factor}") - - -while True: - if device == "sat": - find_spreading_factor() - else: - respond_to_ping() diff --git a/experimental/tests/simple_client.py b/experimental/tests/simple_client.py deleted file mode 100644 index de2cc56..0000000 --- a/experimental/tests/simple_client.py +++ /dev/null @@ -1,57 +0,0 @@ -# SPDX-FileCopyrightText: 2023 ladyada -# -# SPDX-License-Identifier: MIT -#!/usr/bin/env python3 - -""" -This example client runs on CPython and connects to / sends data to the -simpleserver example. -""" -import board -import busio -import digitalio -import time -import pysquared_w5500 - -print("Wiznet5k SimpleServer Test") - -# For Adafruit Ethernet FeatherWing -cs = digitalio.DigitalInOut(board.D10) -# For Particle Ethernet FeatherWing -# cs = digitalio.DigitalInOut(board.D5) -spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) -MAC = (0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEE) -# Initialize ethernet interface -eth = pysquared_w5500.WIZNET5500(spi_bus, cs, mac=MAC, debug=True) - -# Initialize a socket for our server -pysquared_w5500.set_interface(eth) -print("Chip Version:", eth.chip) -print("A simple client for the wiznet5k_simpleserver.py example in this directory") -print( - "Run this on any device connected to the same network as the server, after " - "editing this script with the correct HOST & PORT\n" -) -# Or, use any TCP-based client that can easily send 1024 bytes. For example: -# python -c 'print("1234"*256)' | nc 192.168.10.1 50007 - -time.sleep(1) - -# edit host and port to match server -HOST = "48.48.67.48" -PORT = 50007 -TIMEOUT = 10 -INTERVAL = 5 -MAXBUF = 1024 -while True: - s = pysquared_w5500.socket(pysquared_w5500.AF_INET, pysquared_w5500.SOCK_STREAM) - s.settimeout(TIMEOUT) - print(f"Connecting to {HOST}:{PORT}") - s.connect((HOST, PORT)) - # wiznet5k_simpleserver.py wants exactly 1024 bytes - size = s.send(b"A5" * 512) - print("Sent", size, "bytes") - buf = s.recv(MAXBUF) - print("Received", buf) - s.close() - time.sleep(INTERVAL) diff --git a/experimental/tests/simple_server.py b/experimental/tests/simple_server.py deleted file mode 100644 index d43b457..0000000 --- a/experimental/tests/simple_server.py +++ /dev/null @@ -1,42 +0,0 @@ -# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries -# SPDX-FileCopyrightText: 2021 Adam Cummick -# -# SPDX-License-Identifier: MIT - -import board -import busio -import digitalio -import pysquared_w5500 - -print("W5500 SimpleServer Test") - -# For Adafruit Ethernet FeatherWing -cs = digitalio.DigitalInOut(board.D10) -# For Particle Ethernet FeatherWing -# cs = digitalio.DigitalInOut(board.D5) -spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) - -# Initialize ethernet interface -ip_address = (b"00C0", b"00A8", b"000A", b"0001") -eth = pysquared_w5500.WIZNET5500(spi_bus, cs, debug=True) -print("Chip Version:", eth.chip) -eth.ifconfig = ip_address - -# Initialize a socket for our server -pysquared_w5500.set_interface(eth) -server = pysquared_w5500.socket() # Allocate socket for the server -server_ip = eth.pretty_ip(eth.ip_address) # IP address of server -server_port = 50007 # Port to listen on -server.bind((server_ip, server_port)) # Bind to IP and Port -server.listen() # Begin listening for incoming clients - -while True: - print(f"Accepting connections on {server_ip}:{server_port}") - conn, addr = server.accept() # Wait for a connection from a client. - print(f"Connection accepted from {addr}, reading exactly 1024 bytes from client") - with conn: - data = conn.recv(1024) - if data: # Wait for receiving data - print(data) - conn.send(data) # Echo message back to client - print("Connection closed") diff --git a/experimental/lib/packet_receiver.py b/tests/repl/packet_receiver.py similarity index 100% rename from experimental/lib/packet_receiver.py rename to tests/repl/packet_receiver.py From d036d587305d5762e0903c7e0c3ae0acf9ace23f Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 28 Dec 2024 15:51:58 -0800 Subject: [PATCH 2/3] Removed Batt Board Files --- Batt_Board/README.md | 32 - Batt_Board/boot.py | 3 - Batt_Board/can_listen.py | 13 - Batt_Board/can_test.py | 13 - Batt_Board/code.py | 18 - Batt_Board/detumble.py | 23 - Batt_Board/lib/adafruit_ads1x15/__init__.py | 0 Batt_Board/lib/adafruit_ads1x15/ads1015.py | 72 -- Batt_Board/lib/adafruit_ads1x15/ads1115.py | 73 -- Batt_Board/lib/adafruit_ads1x15/ads1x15.py | 231 ----- Batt_Board/lib/adafruit_ads1x15/analog_in.py | 61 -- Batt_Board/lib/adafruit_ads1x15/py.typed | 0 Batt_Board/lib/adafruit_drv2605.py | 382 ------- Batt_Board/lib/adafruit_ina219.py | 535 ---------- Batt_Board/lib/adafruit_max31856.py | 451 --------- Batt_Board/lib/adafruit_mcp2515/__init__.py | 943 ------------------ .../lib/adafruit_mcp2515/canio/__init__.py | 220 ---- Batt_Board/lib/adafruit_mcp2515/timer.py | 29 - Batt_Board/lib/adafruit_mcp9808.py | 266 ----- Batt_Board/lib/adafruit_pca9685.py | 189 ---- Batt_Board/lib/adafruit_pct2075.py | 197 ---- Batt_Board/lib/adafruit_register/__init__.py | 0 .../lib/adafruit_register/i2c_bcd_alarm.mpy | Bin 1740 -> 0 bytes .../lib/adafruit_register/i2c_bcd_alarm.py | 198 ---- .../adafruit_register/i2c_bcd_datetime.mpy | Bin 1141 -> 0 bytes .../lib/adafruit_register/i2c_bcd_datetime.py | 114 --- Batt_Board/lib/adafruit_register/i2c_bit.mpy | Bin 801 -> 0 bytes Batt_Board/lib/adafruit_register/i2c_bit.py | 84 -- Batt_Board/lib/adafruit_register/i2c_bits.mpy | Bin 1053 -> 0 bytes Batt_Board/lib/adafruit_register/i2c_bits.py | 114 --- .../lib/adafruit_register/i2c_struct.mpy | Bin 1040 -> 0 bytes .../lib/adafruit_register/i2c_struct.py | 104 -- .../adafruit_register/i2c_struct_array.mpy | Bin 1008 -> 0 bytes .../lib/adafruit_register/i2c_struct_array.py | 114 --- Batt_Board/lib/adafruit_ticks.py | 139 --- Batt_Board/lib/adafruit_veml7700.py | 252 ----- Batt_Board/lib/adafruit_vl6180x.py | 411 -------- Batt_Board/lib/asyncio/__init__.py | 41 - Batt_Board/lib/asyncio/core.py | 375 ------- Batt_Board/lib/asyncio/event.py | 95 -- Batt_Board/lib/asyncio/funcs.py | 114 --- Batt_Board/lib/asyncio/lock.py | 87 -- Batt_Board/lib/asyncio/manifest.py | 24 - Batt_Board/lib/asyncio/stream.py | 263 ----- Batt_Board/lib/asyncio/task.py | 204 ---- Batt_Board/lib/battery_functions.py | 498 --------- Batt_Board/lib/bitflags.py | 72 -- Batt_Board/lib/can_bus_helper.py | 462 --------- Batt_Board/lib/debugcolor.py | 24 - Batt_Board/lib/neopixel.mpy | Bin 1318 -> 0 bytes Batt_Board/lib/pysquared_eps.py | 903 ----------------- Batt_Board/main.py | 237 ----- Batt_Board/safemode.py | 6 - 53 files changed, 8686 deletions(-) delete mode 100644 Batt_Board/README.md delete mode 100644 Batt_Board/boot.py delete mode 100644 Batt_Board/can_listen.py delete mode 100644 Batt_Board/can_test.py delete mode 100644 Batt_Board/code.py delete mode 100644 Batt_Board/detumble.py delete mode 100644 Batt_Board/lib/adafruit_ads1x15/__init__.py delete mode 100644 Batt_Board/lib/adafruit_ads1x15/ads1015.py delete mode 100644 Batt_Board/lib/adafruit_ads1x15/ads1115.py delete mode 100644 Batt_Board/lib/adafruit_ads1x15/ads1x15.py delete mode 100644 Batt_Board/lib/adafruit_ads1x15/analog_in.py delete mode 100644 Batt_Board/lib/adafruit_ads1x15/py.typed delete mode 100644 Batt_Board/lib/adafruit_drv2605.py delete mode 100644 Batt_Board/lib/adafruit_ina219.py delete mode 100644 Batt_Board/lib/adafruit_max31856.py delete mode 100644 Batt_Board/lib/adafruit_mcp2515/__init__.py delete mode 100644 Batt_Board/lib/adafruit_mcp2515/canio/__init__.py delete mode 100644 Batt_Board/lib/adafruit_mcp2515/timer.py delete mode 100644 Batt_Board/lib/adafruit_mcp9808.py delete mode 100644 Batt_Board/lib/adafruit_pca9685.py delete mode 100644 Batt_Board/lib/adafruit_pct2075.py delete mode 100644 Batt_Board/lib/adafruit_register/__init__.py delete mode 100644 Batt_Board/lib/adafruit_register/i2c_bcd_alarm.mpy delete mode 100644 Batt_Board/lib/adafruit_register/i2c_bcd_alarm.py delete mode 100644 Batt_Board/lib/adafruit_register/i2c_bcd_datetime.mpy delete mode 100644 Batt_Board/lib/adafruit_register/i2c_bcd_datetime.py delete mode 100644 Batt_Board/lib/adafruit_register/i2c_bit.mpy delete mode 100644 Batt_Board/lib/adafruit_register/i2c_bit.py delete mode 100644 Batt_Board/lib/adafruit_register/i2c_bits.mpy delete mode 100644 Batt_Board/lib/adafruit_register/i2c_bits.py delete mode 100644 Batt_Board/lib/adafruit_register/i2c_struct.mpy delete mode 100644 Batt_Board/lib/adafruit_register/i2c_struct.py delete mode 100644 Batt_Board/lib/adafruit_register/i2c_struct_array.mpy delete mode 100644 Batt_Board/lib/adafruit_register/i2c_struct_array.py delete mode 100644 Batt_Board/lib/adafruit_ticks.py delete mode 100644 Batt_Board/lib/adafruit_veml7700.py delete mode 100644 Batt_Board/lib/adafruit_vl6180x.py delete mode 100644 Batt_Board/lib/asyncio/__init__.py delete mode 100644 Batt_Board/lib/asyncio/core.py delete mode 100644 Batt_Board/lib/asyncio/event.py delete mode 100644 Batt_Board/lib/asyncio/funcs.py delete mode 100644 Batt_Board/lib/asyncio/lock.py delete mode 100644 Batt_Board/lib/asyncio/manifest.py delete mode 100644 Batt_Board/lib/asyncio/stream.py delete mode 100644 Batt_Board/lib/asyncio/task.py delete mode 100644 Batt_Board/lib/battery_functions.py delete mode 100644 Batt_Board/lib/bitflags.py delete mode 100644 Batt_Board/lib/can_bus_helper.py delete mode 100644 Batt_Board/lib/debugcolor.py delete mode 100755 Batt_Board/lib/neopixel.mpy delete mode 100644 Batt_Board/lib/pysquared_eps.py delete mode 100644 Batt_Board/main.py delete mode 100644 Batt_Board/safemode.py diff --git a/Batt_Board/README.md b/Batt_Board/README.md deleted file mode 100644 index 2c06af8..0000000 --- a/Batt_Board/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# flight_controller_software -Software for the flight controller board in the PROVES Kit. -- **boot.py** This is the code that runs on boot and initializes the stack limit -- **cdh.py** This is the code that handles all the commands. A majority of this code is pulled from the cdh file developed by Max Holliday at Stanford. -- **code.py** This code runs the main operating system of the satellite and handles errors on a high level allowing the system to be fault tolerant -- **detumble.py** This code implements the B-dot algorithm and returns outputs that allow the system to do a controlled detumble with the satellite's embedded magnetourquer coils -- **main.py** This code tasks all the functions the satellite should do in a semi-asynchronous manner utilizing the asyncio library -- **payload.py** This code implements any desired payload. On the Pleiades missions, the payload has been the BNO055 IMU. Since the use of a stemmaQT connector allows multiple devices on the same bus, a BNO IMU could be used in conjunction with several other sensors if desired. -- **safemode.py** This code is unimplemented pending new firmware releases that allow the microconrtoller to perform a routine when in safemode -## experimental -This software is completely experimental and is in development for helpful software related tasks. -- **sf_hop.py** This code is yet to be implemented in official flight software as it is desired to implement the ability to utilize several spreading factors to send different sized messages at different data rates -## lib -This software contains all of the libraries required to operate the sensors, pysquared board, and radio module. -- **asyncio** This is the library responsible for scheduling tasks in the main code -- **adafruit_bno055.py** This is the library that is responsible for obtaining data from the BNO055 IMU -- **adafruit_drv2605.mpy** This is the pre-compiled library that is responsible for driving the magnetorquer coils using the drv2605 motor driver -- **adafruit_ina219.py** This is the library that is responsible for obtaining data from the INA219 Power Monitor -- **adafruit_mcp9808.mpy** This is the pre-compiled library that is responsible for obtaining data from the MCP9808 Temperature Sensor -- **adafruit_pca9685.py** This is the library that is responsible or driving the power to the satellite faces using the pca9685 LED driver -- **adafruit_tca9548a.mpy** This is the pre-compiled library that multiplexes the I2C line to each satellite face using the tca9548a I2C Multiplexer -- **adafruit_veml7700.py** This is the library that is responsible for obtaining data from the veml7700 Light Sensor -- **adafruit_vl6180.py** This is the library that is responsible for obtaining data from the vl6180 LiDAR sensor -- **Big_Data.py** This is a class developed to obtain data from the sensors on the 5 solar faces. Since all the faces maintain all the same sensors, this class handles the individual face sensors and returns them all to the main code. -- **bitflags.py** This is code that allows for some registers within the microcontroller to be written to and saved through power cycles -- **debugcolor.py** This is code that allows for easier debugging and is used by individual classes. Each class utilizes a different color and makes debugging substantially easier -- **Field.py** This is code that implements the radio module for sending and listening -- **functions.py** This is a library of functions utilized by the satellite to obtain data, detumble, run the battery heater -- **pysquared.py** This is a library that initializes and maintains all the main functions for the pysquared architecture -- **pysquared_rfm9x.py** This is a library that implements all the radio hardware. This code is a modified version of the pycubed_rfm9x which is a modified version of the adafruit_rfm9x file. -## tests -This software is used for performing tests on the satellite diff --git a/Batt_Board/boot.py b/Batt_Board/boot.py deleted file mode 100644 index aafa105..0000000 --- a/Batt_Board/boot.py +++ /dev/null @@ -1,3 +0,0 @@ -import supervisor - -supervisor.set_next_stack_limit(4096 + 4096) diff --git a/Batt_Board/can_listen.py b/Batt_Board/can_listen.py deleted file mode 100644 index 71cca20..0000000 --- a/Batt_Board/can_listen.py +++ /dev/null @@ -1,13 +0,0 @@ -from pysquared_eps import cubesat as c -import battery_functions -import can_bus_helper -import time - -f = battery_functions.functions(c) - -cb = can_bus_helper.CanBusHelper(c.can_bus, f, True) - -while True: - cb.listen_messages() - time.sleep(1) - print("Listened to messages") diff --git a/Batt_Board/can_test.py b/Batt_Board/can_test.py deleted file mode 100644 index 42180a5..0000000 --- a/Batt_Board/can_test.py +++ /dev/null @@ -1,13 +0,0 @@ -from pysquared import cubesat as c -import functions -import can_bus_helper -import time - -f = functions.functions(c) - -cb = can_bus_helper.CanBusHelper(c.can_bus, f, True) - -while True: - cb.listen_messages() - time.sleep(1) - print("Listened to messages") diff --git a/Batt_Board/code.py b/Batt_Board/code.py deleted file mode 100644 index 332191b..0000000 --- a/Batt_Board/code.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -In this method the PyCubed will wait a pre-allotted loiter time before proceeding to execute -main. This loiter time is to allow for a keyboard interupt if needed. - -Authors: Nicole Maggard and Michael Pham -""" - -import time -import microcontroller - -try: - import main - -except Exception as e: - print(e) - # time.sleep(10) - # microcontroller.on_next_reset(microcontroller.RunMode.NORMAL) - # microcontroller.reset() diff --git a/Batt_Board/detumble.py b/Batt_Board/detumble.py deleted file mode 100644 index 59324b7..0000000 --- a/Batt_Board/detumble.py +++ /dev/null @@ -1,23 +0,0 @@ -def dot_product(vector1, vector2): - return sum([a * b for a, b in zip(vector1, vector2)]) - - -def x_product(vector1, vector2): - return [ - vector1[1] * vector2[2] - vector1[2] * vector2[1], - vector1[0] * vector2[2] - vector1[2] * vector2[0], - vector1[0] * vector2[1] - vector1[1] * vector2[0], - ] - - -def gain_func(): - return 1.0 - - -def magnetorquer_dipole(mag_field, ang_vel): - gain = gain_func() - scalar_coef = -gain / pow(dot_product(mag_field, mag_field), 0.5) - dipole_out = x_product(mag_field, ang_vel) - for i in range(3): - dipole_out[i] *= scalar_coef - return dipole_out diff --git a/Batt_Board/lib/adafruit_ads1x15/__init__.py b/Batt_Board/lib/adafruit_ads1x15/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/Batt_Board/lib/adafruit_ads1x15/ads1015.py b/Batt_Board/lib/adafruit_ads1x15/ads1015.py deleted file mode 100644 index f1ecdbe..0000000 --- a/Batt_Board/lib/adafruit_ads1x15/ads1015.py +++ /dev/null @@ -1,72 +0,0 @@ -# SPDX-FileCopyrightText: 2018 Carter Nelson for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -""" -`ads1015` -==================================================== - -CircuitPython driver for ADS1015 ADCs. - -* Author(s): Carter Nelson -""" -import struct - -try: - from typing import Dict, List - - from typing_extensions import Literal -except ImportError: - pass - -# pylint: disable=unused-import -from .ads1x15 import ADS1x15, Mode - -# Data sample rates -_ADS1015_CONFIG_DR = { - 128: 0x0000, - 250: 0x0020, - 490: 0x0040, - 920: 0x0060, - 1600: 0x0080, - 2400: 0x00A0, - 3300: 0x00C0, -} - -# Pins -P0 = 0 -"""Analog Pin 0""" -P1 = 1 -"""Analog Pin 1""" -P2 = 2 -"""Analog Pin 2""" -P3 = 3 -"""Analog Pin 3""" - - -class ADS1015(ADS1x15): - """Class for the ADS1015 12 bit ADC.""" - - @property - def bits(self) -> Literal[12]: - """The ADC bit resolution.""" - return 12 - - @property - def rates(self) -> List[int]: - """Possible data rate settings.""" - r = list(_ADS1015_CONFIG_DR.keys()) - r.sort() - return r - - @property - def rate_config(self) -> Dict[int, int]: - """Rate configuration masks.""" - return _ADS1015_CONFIG_DR - - def _data_rate_default(self) -> Literal[1600]: - return 1600 - - def _conversion_value(self, raw_adc: int) -> int: - value = struct.unpack(">h", raw_adc.to_bytes(2, "big"))[0] - return value >> 4 diff --git a/Batt_Board/lib/adafruit_ads1x15/ads1115.py b/Batt_Board/lib/adafruit_ads1x15/ads1115.py deleted file mode 100644 index a35149c..0000000 --- a/Batt_Board/lib/adafruit_ads1x15/ads1115.py +++ /dev/null @@ -1,73 +0,0 @@ -# SPDX-FileCopyrightText: 2018 Carter Nelson for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -""" -`ads1115` -==================================================== - -CircuitPython driver for 1115 ADCs. - -* Author(s): Carter Nelson -""" -import struct - -try: - from typing import Dict, List - - from typing_extensions import Literal -except ImportError: - pass - -# pylint: disable=unused-import -from .ads1x15 import ADS1x15, Mode - -# Data sample rates -_ADS1115_CONFIG_DR = { - 8: 0x0000, - 16: 0x0020, - 32: 0x0040, - 64: 0x0060, - 128: 0x0080, - 250: 0x00A0, - 475: 0x00C0, - 860: 0x00E0, -} - -# Pins -P0 = 0 -"""Analog Pin 0""" -P1 = 1 -"""Analog Pin 1""" -P2 = 2 -"""Analog Pin 2""" -P3 = 3 -"""Analog Pin 3""" - - -class ADS1115(ADS1x15): - """Class for the ADS1115 16 bit ADC.""" - - @property - def bits(self) -> Literal[16]: - """The ADC bit resolution.""" - return 16 - - @property - def rates(self) -> List[int]: - """Possible data rate settings.""" - r = list(_ADS1115_CONFIG_DR.keys()) - r.sort() - return r - - @property - def rate_config(self) -> Dict[int, int]: - """Rate configuration masks.""" - return _ADS1115_CONFIG_DR - - def _data_rate_default(self) -> Literal[128]: - return 128 - - def _conversion_value(self, raw_adc: int) -> int: - value = struct.unpack(">h", raw_adc.to_bytes(2, "big"))[0] - return value diff --git a/Batt_Board/lib/adafruit_ads1x15/ads1x15.py b/Batt_Board/lib/adafruit_ads1x15/ads1x15.py deleted file mode 100644 index 80f8f0b..0000000 --- a/Batt_Board/lib/adafruit_ads1x15/ads1x15.py +++ /dev/null @@ -1,231 +0,0 @@ -# SPDX-FileCopyrightText: 2018 Carter Nelson for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -""" -`ads1x15` -==================================================== - -CircuitPython base class driver for ADS1015/1115 ADCs. - -* Author(s): Carter Nelson -""" - -__version__ = "0.0.0+auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ADS1x15.git" - -import time - -from adafruit_bus_device.i2c_device import I2CDevice -from micropython import const - -try: - from typing import Dict, List, Optional - - from busio import I2C - from microcontroller import Pin -except ImportError: - pass - -_ADS1X15_DEFAULT_ADDRESS = const(0x48) -_ADS1X15_POINTER_CONVERSION = const(0x00) -_ADS1X15_POINTER_CONFIG = const(0x01) -_ADS1X15_CONFIG_OS_SINGLE = const(0x8000) -_ADS1X15_CONFIG_MUX_OFFSET = const(12) -_ADS1X15_CONFIG_COMP_QUE_DISABLE = const(0x0003) -_ADS1X15_CONFIG_GAIN = { - 2 / 3: 0x0000, - 1: 0x0200, - 2: 0x0400, - 4: 0x0600, - 8: 0x0800, - 16: 0x0A00, -} - - -class Mode: - """An enum-like class representing possible ADC operating modes.""" - - # See datasheet "Operating Modes" section - # values here are masks for setting MODE bit in Config Register - # pylint: disable=too-few-public-methods - CONTINUOUS = 0x0000 - """Continuous Mode""" - SINGLE = 0x0100 - """Single-Shot Mode""" - - -class ADS1x15: - """Base functionality for ADS1x15 analog to digital converters. - - :param ~busio.I2C i2c: The I2C bus the device is connected to. - :param float gain: The ADC gain. - :param int data_rate: The data rate for ADC conversion in samples per second. - Default value depends on the device. - :param Mode mode: The conversion mode, defaults to `Mode.SINGLE`. - :param int address: The I2C address of the device. - """ - - def __init__( - self, - i2c: I2C, - gain: float = 1, - data_rate: Optional[int] = None, - mode: int = Mode.SINGLE, - address: int = _ADS1X15_DEFAULT_ADDRESS, - ): - # pylint: disable=too-many-arguments - self._last_pin_read = None - self.buf = bytearray(3) - self.gain = gain - self.data_rate = self._data_rate_default() if data_rate is None else data_rate - self.mode = mode - self.i2c_device = I2CDevice(i2c, address) - - @property - def bits(self) -> int: - """The ADC bit resolution.""" - raise NotImplementedError("Subclass must implement bits property.") - - @property - def data_rate(self) -> int: - """The data rate for ADC conversion in samples per second.""" - return self._data_rate - - @data_rate.setter - def data_rate(self, rate: int) -> None: - possible_rates = self.rates - if rate not in possible_rates: - raise ValueError("Data rate must be one of: {}".format(possible_rates)) - self._data_rate = rate - - @property - def rates(self) -> List[int]: - """Possible data rate settings.""" - raise NotImplementedError("Subclass must implement rates property.") - - @property - def rate_config(self) -> Dict[int, int]: - """Rate configuration masks.""" - raise NotImplementedError("Subclass must implement rate_config property.") - - @property - def gain(self) -> float: - """The ADC gain.""" - return self._gain - - @gain.setter - def gain(self, gain: float) -> None: - possible_gains = self.gains - if gain not in possible_gains: - raise ValueError("Gain must be one of: {}".format(possible_gains)) - self._gain = gain - - @property - def gains(self) -> List[float]: - """Possible gain settings.""" - g = list(_ADS1X15_CONFIG_GAIN.keys()) - g.sort() - return g - - @property - def mode(self) -> int: - """The ADC conversion mode.""" - return self._mode - - @mode.setter - def mode(self, mode: int) -> None: - if mode not in (Mode.CONTINUOUS, Mode.SINGLE): - raise ValueError("Unsupported mode.") - self._mode = mode - - def read(self, pin: Pin, is_differential: bool = False) -> int: - """I2C Interface for ADS1x15-based ADCs reads. - - :param ~microcontroller.Pin pin: individual or differential pin. - :param bool is_differential: single-ended or differential read. - """ - pin = pin if is_differential else pin + 0x04 - return self._read(pin) - - def _data_rate_default(self) -> int: - """Retrieve the default data rate for this ADC (in samples per second). - Should be implemented by subclasses. - """ - raise NotImplementedError("Subclasses must implement _data_rate_default!") - - def _conversion_value(self, raw_adc: int) -> int: - """Subclasses should override this function that takes the 16 raw ADC - values of a conversion result and returns a signed integer value. - """ - raise NotImplementedError("Subclass must implement _conversion_value function!") - - def _read(self, pin: Pin) -> int: - """Perform an ADC read. Returns the signed integer result of the read.""" - # Immediately return conversion register result if in CONTINUOUS mode - # and pin has not changed - if self.mode == Mode.CONTINUOUS and self._last_pin_read == pin: - return self._conversion_value(self.get_last_result(True)) - - # Assign last pin read if in SINGLE mode or first sample in CONTINUOUS mode on this pin - self._last_pin_read = pin - - # Configure ADC every time before a conversion in SINGLE mode - # or changing channels in CONTINUOUS mode - if self.mode == Mode.SINGLE: - config = _ADS1X15_CONFIG_OS_SINGLE - else: - config = 0 - config |= (pin & 0x07) << _ADS1X15_CONFIG_MUX_OFFSET - config |= _ADS1X15_CONFIG_GAIN[self.gain] - config |= self.mode - config |= self.rate_config[self.data_rate] - config |= _ADS1X15_CONFIG_COMP_QUE_DISABLE - self._write_register(_ADS1X15_POINTER_CONFIG, config) - - # Wait for conversion to complete - # ADS1x1x devices settle within a single conversion cycle - if self.mode == Mode.SINGLE: - # Continuously poll conversion complete status bit - while not self._conversion_complete(): - pass - else: - # Can't poll registers in CONTINUOUS mode - # Wait expected time for two conversions to complete - time.sleep(2 / self.data_rate) - - return self._conversion_value(self.get_last_result(False)) - - def _conversion_complete(self) -> int: - """Return status of ADC conversion.""" - # OS is bit 15 - # OS = 0: Device is currently performing a conversion - # OS = 1: Device is not currently performing a conversion - return self._read_register(_ADS1X15_POINTER_CONFIG) & 0x8000 - - def get_last_result(self, fast: bool = False) -> int: - """Read the last conversion result when in continuous conversion mode. - Will return a signed integer value. If fast is True, the register - pointer is not updated as part of the read. This reduces I2C traffic - and increases possible read rate. - """ - return self._read_register(_ADS1X15_POINTER_CONVERSION, fast) - - def _write_register(self, reg: int, value: int): - """Write 16 bit value to register.""" - self.buf[0] = reg - self.buf[1] = (value >> 8) & 0xFF - self.buf[2] = value & 0xFF - with self.i2c_device as i2c: - i2c.write(self.buf) - - def _read_register(self, reg: int, fast: bool = False) -> int: - """Read 16 bit register value. If fast is True, the pointer register - is not updated. - """ - with self.i2c_device as i2c: - if fast: - i2c.readinto(self.buf, end=2) - else: - i2c.write_then_readinto(bytearray([reg]), self.buf, in_end=2) - return self.buf[0] << 8 | self.buf[1] diff --git a/Batt_Board/lib/adafruit_ads1x15/analog_in.py b/Batt_Board/lib/adafruit_ads1x15/analog_in.py deleted file mode 100644 index 310c092..0000000 --- a/Batt_Board/lib/adafruit_ads1x15/analog_in.py +++ /dev/null @@ -1,61 +0,0 @@ -# SPDX-FileCopyrightText: 2018 Carter Nelson for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -""" -`analog_in` -============================== -AnalogIn for single-ended and -differential ADC readings. - -* Author(s): Carter Nelson, adapted from MCP3xxx original by Brent Rubell -""" - -try: - from typing import Optional - from .ads1x15 import ADS1x15 -except ImportError: - pass - -_ADS1X15_DIFF_CHANNELS = {(0, 1): 0, (0, 3): 1, (1, 3): 2, (2, 3): 3} -_ADS1X15_PGA_RANGE = {2 / 3: 6.144, 1: 4.096, 2: 2.048, 4: 1.024, 8: 0.512, 16: 0.256} - - -class AnalogIn: - """AnalogIn Mock Implementation for ADC Reads. - - :param ADS1x15 ads: The ads object. - :param int positive_pin: Required pin for single-ended. - :param int negative_pin: Optional pin for differential reads. - """ - - def __init__( - self, ads: ADS1x15, positive_pin: int, negative_pin: Optional[int] = None - ): - self._ads = ads - self._pin_setting = positive_pin - self._negative_pin = negative_pin - self.is_differential = False - if negative_pin is not None: - pins = (self._pin_setting, self._negative_pin) - if pins not in _ADS1X15_DIFF_CHANNELS: - raise ValueError( - "Differential channels must be one of: {}".format( - list(_ADS1X15_DIFF_CHANNELS.keys()) - ) - ) - self._pin_setting = _ADS1X15_DIFF_CHANNELS[pins] - self.is_differential = True - - @property - def value(self) -> int: - """Returns the value of an ADC pin as an integer.""" - return self._ads.read( - self._pin_setting, is_differential=self.is_differential - ) << (16 - self._ads.bits) - - @property - def voltage(self) -> float: - """Returns the voltage from the ADC pin as a floating point value.""" - volts = self.value * _ADS1X15_PGA_RANGE[self._ads.gain] / 32767 - return volts diff --git a/Batt_Board/lib/adafruit_ads1x15/py.typed b/Batt_Board/lib/adafruit_ads1x15/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/Batt_Board/lib/adafruit_drv2605.py b/Batt_Board/lib/adafruit_drv2605.py deleted file mode 100644 index a8efb34..0000000 --- a/Batt_Board/lib/adafruit_drv2605.py +++ /dev/null @@ -1,382 +0,0 @@ -# SPDX-FileCopyrightText: 2017 Tony DiCola for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_drv2605` -==================================================== - -CircuitPython module for the DRV2605 haptic feedback motor driver. See -examples/simpletest.py for a demo of the usage. - -* Author(s): Tony DiCola -""" -from micropython import const - -from adafruit_bus_device.i2c_device import I2CDevice - -try: - from typing import Union - from busio import I2C -except ImportError: - pass - -__version__ = "0.0.0+auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DRV2605.git" - - -# Internal constants: -_DRV2605_ADDR = const(0x5A) -_DRV2605_REG_STATUS = const(0x00) -_DRV2605_REG_MODE = const(0x01) -_DRV2605_REG_RTPIN = const(0x02) -_DRV2605_REG_LIBRARY = const(0x03) -_DRV2605_REG_WAVESEQ1 = const(0x04) -_DRV2605_REG_WAVESEQ2 = const(0x05) -_DRV2605_REG_WAVESEQ3 = const(0x06) -_DRV2605_REG_WAVESEQ4 = const(0x07) -_DRV2605_REG_WAVESEQ5 = const(0x08) -_DRV2605_REG_WAVESEQ6 = const(0x09) -_DRV2605_REG_WAVESEQ7 = const(0x0A) -_DRV2605_REG_WAVESEQ8 = const(0x0B) -_DRV2605_REG_GO = const(0x0C) -_DRV2605_REG_OVERDRIVE = const(0x0D) -_DRV2605_REG_SUSTAINPOS = const(0x0E) -_DRV2605_REG_SUSTAINNEG = const(0x0F) -_DRV2605_REG_BREAK = const(0x10) -_DRV2605_REG_AUDIOCTRL = const(0x11) -_DRV2605_REG_AUDIOLVL = const(0x12) -_DRV2605_REG_AUDIOMAX = const(0x13) -_DRV2605_REG_RATEDV = const(0x16) -_DRV2605_REG_CLAMPV = const(0x17) -_DRV2605_REG_AUTOCALCOMP = const(0x18) -_DRV2605_REG_AUTOCALEMP = const(0x19) -_DRV2605_REG_FEEDBACK = const(0x1A) -_DRV2605_REG_CONTROL1 = const(0x1B) -_DRV2605_REG_CONTROL2 = const(0x1C) -_DRV2605_REG_CONTROL3 = const(0x1D) -_DRV2605_REG_CONTROL4 = const(0x1E) -_DRV2605_REG_VBAT = const(0x21) -_DRV2605_REG_LRARESON = const(0x22) - -# User-facing mode value constants: -MODE_INTTRIG = 0x00 -MODE_EXTTRIGEDGE = 0x01 -MODE_EXTTRIGLVL = 0x02 -MODE_PWMANALOG = 0x03 -MODE_AUDIOVIBE = 0x04 -MODE_REALTIME = 0x05 -MODE_DIAGNOS = 0x06 -MODE_AUTOCAL = 0x07 -LIBRARY_EMPTY = 0x00 -LIBRARY_TS2200A = 0x01 -LIBRARY_TS2200B = 0x02 -LIBRARY_TS2200C = 0x03 -LIBRARY_TS2200D = 0x04 -LIBRARY_TS2200E = 0x05 -LIBRARY_LRA = 0x06 - - -class DRV2605: - """TI DRV2605 haptic feedback motor driver module. - - :param I2C i2c: The board I2C object - :param int address: The I2C address - """ - - # Class-level buffer for reading and writing data with the sensor. - # This reduces memory allocations but means the code is not re-entrant or - # thread safe! - _BUFFER = bytearray(2) - - def __init__(self, i2c: I2C, address: int = _DRV2605_ADDR) -> None: - self._device = I2CDevice(i2c, address) - # Check chip ID is 3 or 7 (DRV2605 or DRV2605L). - status = self._read_u8(_DRV2605_REG_STATUS) - device_id = (status >> 5) & 0x07 - if device_id not in (3, 7): - raise RuntimeError("Failed to find DRV2605, check wiring!") - # Configure registers to initialize chip. - self._write_u8(_DRV2605_REG_MODE, 0x00) # out of standby - self._write_u8(_DRV2605_REG_RTPIN, 0x00) # no real-time-playback - self._write_u8(_DRV2605_REG_WAVESEQ1, 1) # strong click - self._write_u8(_DRV2605_REG_WAVESEQ2, 0) - self._write_u8(_DRV2605_REG_OVERDRIVE, 0) # no overdrive - self._write_u8(_DRV2605_REG_SUSTAINPOS, 0) - self._write_u8(_DRV2605_REG_SUSTAINNEG, 0) - self._write_u8(_DRV2605_REG_BREAK, 0) - self._write_u8(_DRV2605_REG_AUDIOMAX, 0x64) - # Set ERM open-loop mode. - self.use_ERM() - # turn on ERM_OPEN_LOOP - control3 = self._read_u8(_DRV2605_REG_CONTROL3) - self._write_u8(_DRV2605_REG_CONTROL3, control3 | 0x20) - # Default to internal trigger mode and TS2200 A library. - self.mode = MODE_INTTRIG - self.library = LIBRARY_TS2200A - self._sequence = _DRV2605_Sequence(self) - - def _read_u8(self, address: int) -> int: - # Read an 8-bit unsigned value from the specified 8-bit address. - with self._device as i2c: - self._BUFFER[0] = address & 0xFF - i2c.write_then_readinto(self._BUFFER, self._BUFFER, out_end=1, in_end=1) - return self._BUFFER[0] - - def _write_u8(self, address: int, val: int) -> None: - # Write an 8-bit unsigned value to the specified 8-bit address. - with self._device as i2c: - self._BUFFER[0] = address & 0xFF - self._BUFFER[1] = val & 0xFF - i2c.write(self._BUFFER, end=2) - - def play(self) -> None: - """Play back the select effect(s) on the motor.""" - self._write_u8(_DRV2605_REG_GO, 1) - - def stop(self) -> None: - """Stop vibrating the motor.""" - self._write_u8(_DRV2605_REG_GO, 0) - - @property - def mode(self) -> int: - """ - The mode of the chip. Should be a value of: - - * ``MODE_INTTRIG``: Internal triggering, vibrates as soon as you call - play(). Default mode. - * ``MODE_EXTTRIGEDGE``: External triggering, edge mode. - * ``MODE_EXTTRIGLVL``: External triggering, level mode. - * ``MODE_PWMANALOG``: PWM/analog input mode. - * ``MODE_AUDIOVIBE``: Audio-to-vibration mode. - * ``MODE_REALTIME``: Real-time playback mode. - * ``MODE_DIAGNOS``: Diagnostics mode. - * ``MODE_AUTOCAL``: Auto-calibration mode. - - See the datasheet for the meaning of modes beyond MODE_INTTRIG. - """ - return self._read_u8(_DRV2605_REG_MODE) - - @mode.setter - def mode(self, val: int) -> None: - if not 0 <= val <= 7: - raise ValueError("Mode must be a value within 0-7!") - self._write_u8(_DRV2605_REG_MODE, val) - - @property - def library(self) -> int: - """ - The library selected for waveform playback. Should be - a value of: - - * ``LIBRARY_EMPTY``: Empty - * ``LIBRARY_TS2200A``: TS2200 library A (the default) - * ``LIBRARY_TS2200B``: TS2200 library B - * ``LIBRARY_TS2200C``: TS2200 library C - * ``LIBRARY_TS2200D``: TS2200 library D - * ``LIBRARY_TS2200E``: TS2200 library E - * ``LIBRARY_LRA``: LRA library - - See the datasheet for the meaning and description of effects in each - library. - """ - return self._read_u8(_DRV2605_REG_LIBRARY) & 0x07 - - @library.setter - def library(self, val: int) -> None: - if not 0 <= val <= 6: - raise ValueError("Library must be a value within 0-6!") - self._write_u8(_DRV2605_REG_LIBRARY, val) - - @property - def sequence(self) -> "_DRV2605_Sequence": - """List-like sequence of waveform effects. - Get or set an effect waveform for slot 0-7 by indexing the sequence - property with the slot number. A slot must be set to either an :class:`~Effect` - or :class:`~Pause` class. See the datasheet for a complete table of effect ID - values and the associated waveform / effect. - - E.g.: - - .. code-block:: python - - # Getting the effect stored in a slot - slot_0_effect = drv.sequence[0] - - # Setting an Effect in the first sequence slot - drv.sequence[0] = Effect(88) - """ - return self._sequence - - @property - def realtime_value(self) -> int: - """The output value used in Real-Time Playback mode. When the device is - switched to ``MODE_REALTIME``, the motor is driven continuously with an - amplitude/direction determined by this value. - - By default, the device expects a SIGNED 8-bit integer, and its exact - effect depends on both the type of motor (ERM/LRA) and whether the device - is operating in open- or closed-loop (unidirectional/bidirectional) mode. - - See the datasheet for more information! - - E.g.: - - .. code-block:: python - - # Start real-time playback - drv.realtime_value = 0 - drv.mode = adafruit_drv2605.MODE_REALTIME - - # Buzz the motor briefly at 50% and 100% amplitude - drv.realtime_value = 64 - time.sleep(0.5) - drv.realtime_value = 127 - time.sleep(0.5) - - # Stop real-time playback - drv.realtime_value = 0 - drv.mode = adafruit_drv2605.MODE_INTTRIG - """ - return self._read_u8(_DRV2605_REG_RTPIN) - - @realtime_value.setter - def realtime_value(self, val: int) -> None: - if not -127 <= val <= 255: - raise ValueError("Real-Time Playback value must be between -127 and 255!") - self._write_u8(_DRV2605_REG_RTPIN, val) - - def set_waveform(self, effect_id: int, slot: int = 0) -> None: - """Select an effect waveform for the specified slot (default is slot 0, - but up to 8 effects can be combined with slot values 0 to 7). See the - datasheet for a complete table of effect ID values and the associated - waveform / effect. - - :param int effect_id: The effect ID of the waveform - :param int slot: The sequence slot to use - """ - if not 0 <= effect_id <= 123: - raise ValueError("Effect ID must be a value within 0-123!") - if not 0 <= slot <= 7: - raise ValueError("Slot must be a value within 0-7!") - self._write_u8(_DRV2605_REG_WAVESEQ1 + slot, effect_id) - - # pylint: disable=invalid-name - def use_ERM(self) -> None: - """Use an eccentric rotating mass motor (the default).""" - feedback = self._read_u8(_DRV2605_REG_FEEDBACK) - self._write_u8(_DRV2605_REG_FEEDBACK, feedback & 0x7F) - - # pylint: disable=invalid-name - def use_LRM(self) -> None: - """Use a linear resonance actuator motor.""" - feedback = self._read_u8(_DRV2605_REG_FEEDBACK) - self._write_u8(_DRV2605_REG_FEEDBACK, feedback | 0x80) - - -class Effect: - """DRV2605 waveform sequence effect. - - :param int effect_id: The ID number of the effect - """ - - def __init__(self, effect_id: int) -> None: - self._effect_id = 0 - # pylint: disable=invalid-name - self.id = effect_id - - @property - def raw_value(self) -> int: - """Raw effect ID.""" - return self._effect_id - - @property - # pylint: disable=invalid-name - def id(self) -> int: - """Effect ID.""" - return self._effect_id - - @id.setter - # pylint: disable=invalid-name - def id(self, effect_id: int) -> None: - """Set the effect ID.""" - if not 0 <= effect_id <= 123: - raise ValueError("Effect ID must be a value within 0-123!") - self._effect_id = effect_id - - def __repr__(self) -> str: - return "{}({})".format(type(self).__qualname__, self.id) - - -class Pause: - """DRV2605 waveform sequence timed delay. - - :param float duration: The duration of the pause in seconds - """ - - def __init__(self, duration: float) -> None: - # Bit 7 must be set for a slot to be interpreted as a delay - self._duration = 0x80 - self.duration = duration - - @property - def raw_value(self) -> int: - """Raw pause duration.""" - return self._duration - - @property - def duration(self) -> float: - """Pause duration in seconds.""" - # Remove wait time flag bit and convert duration to seconds - return (self._duration & 0x7F) / 100.0 - - @duration.setter - def duration(self, duration: float) -> None: - """Sets the pause duration in seconds.""" - if not 0.0 <= duration <= 1.27: - raise ValueError("Pause duration must be a value within 0.0-1.27!") - # Add wait time flag bit and convert duration to centiseconds - self._duration = 0x80 | round(duration * 100.0) - - def __repr__(self) -> str: - return "{}({})".format(type(self).__qualname__, self.duration) - - -class _DRV2605_Sequence: - """Class to enable List-like indexing of the waveform sequence slots. - - :param DRV2605 DRV2605_instance: The DRV2605 instance - """ - - def __init__( - self, DRV2605_instance: DRV2605 # pylint: disable=invalid-name - ) -> None: - self._drv2605 = DRV2605_instance - - def __setitem__(self, slot: int, effect: Union[Effect, Pause]) -> None: - """Write an Effect or Pause to a slot.""" - if not 0 <= slot <= 7: - raise IndexError("Slot must be a value within 0-7!") - if not isinstance(effect, (Effect, Pause)): - raise TypeError("Effect must be either an Effect or Pause!") - # pylint: disable=protected-access - self._drv2605._write_u8(_DRV2605_REG_WAVESEQ1 + slot, effect.raw_value) - - def __getitem__(self, slot: int) -> Union[Effect, Pause]: - """Read an effect ID from a slot. Returns either a Pause or Effect class.""" - if not 0 <= slot <= 7: - raise IndexError("Slot must be a value within 0-7!") - # pylint: disable=protected-access - slot_contents = self._drv2605._read_u8(_DRV2605_REG_WAVESEQ1 + slot) - if slot_contents & 0x80: - return Pause((slot_contents & 0x7F) / 100.0) - return Effect(slot_contents) - - def __iter__(self) -> Union[Effect, Pause]: - """Returns an iterator over the waveform sequence slots.""" - for slot in range(0, 8): - yield self[slot] - - def __repr__(self) -> str: - """Return a string representation of all slot's effects.""" - return repr(list(self)) diff --git a/Batt_Board/lib/adafruit_ina219.py b/Batt_Board/lib/adafruit_ina219.py deleted file mode 100644 index 7dc1080..0000000 --- a/Batt_Board/lib/adafruit_ina219.py +++ /dev/null @@ -1,535 +0,0 @@ -# SPDX-FileCopyrightText: 2017 Dean Miller for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_ina219` -==================================================== - -CircuitPython driver for the INA219 current sensor. - -* Author(s): Dean Miller - -Implementation Notes --------------------- - -**Hardware:** - -* `Adafruit INA219 High Side DC Current Sensor Breakout `_ - -* `Adafruit INA219 FeatherWing `_ - -**Software and Dependencies:** - -* Adafruit CircuitPython firmware (2.2.0+) for the ESP8622 and M0-based boards: - https://github.com/adafruit/circuitpython/releases -* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice -""" - -from micropython import const -from adafruit_bus_device import i2c_device - -from adafruit_register.i2c_struct import ROUnaryStruct, UnaryStruct -from adafruit_register.i2c_bits import ROBits, RWBits -from adafruit_register.i2c_bit import ROBit - -try: - import typing # pylint: disable=unused-import - from busio import I2C -except ImportError: - pass - -__version__ = "0.0.0+auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_INA219.git" - -# Bits -# pylint: disable=too-few-public-methods - -# Config Register (R/W) -_REG_CONFIG = const(0x00) - - -class BusVoltageRange: - """Constants for ``bus_voltage_range``""" - - RANGE_16V = 0x00 # set bus voltage range to 16V - RANGE_32V = 0x01 # set bus voltage range to 32V (default) - - -class Gain: - """Constants for ``gain``""" - - DIV_1_40MV = 0x00 # shunt prog. gain set to 1, 40 mV range - DIV_2_80MV = 0x01 # shunt prog. gain set to /2, 80 mV range - DIV_4_160MV = 0x02 # shunt prog. gain set to /4, 160 mV range - DIV_8_320MV = 0x03 # shunt prog. gain set to /8, 320 mV range - - -class ADCResolution: - """Constants for ``bus_adc_resolution`` or ``shunt_adc_resolution``""" - - ADCRES_9BIT_1S = 0x00 # 9bit, 1 sample, 84us - ADCRES_10BIT_1S = 0x01 # 10bit, 1 sample, 148us - ADCRES_11BIT_1S = 0x02 # 11 bit, 1 sample, 276us - ADCRES_12BIT_1S = 0x03 # 12 bit, 1 sample, 532us - ADCRES_12BIT_2S = 0x09 # 12 bit, 2 samples, 1.06ms - ADCRES_12BIT_4S = 0x0A # 12 bit, 4 samples, 2.13ms - ADCRES_12BIT_8S = 0x0B # 12bit, 8 samples, 4.26ms - ADCRES_12BIT_16S = 0x0C # 12bit, 16 samples, 8.51ms - ADCRES_12BIT_32S = 0x0D # 12bit, 32 samples, 17.02ms - ADCRES_12BIT_64S = 0x0E # 12bit, 64 samples, 34.05ms - ADCRES_12BIT_128S = 0x0F # 12bit, 128 samples, 68.10ms - - -class Mode: - """Constants for ``mode``""" - - POWERDOWN = 0x00 # power down - SVOLT_TRIGGERED = 0x01 # shunt voltage triggered - BVOLT_TRIGGERED = 0x02 # bus voltage triggered - SANDBVOLT_TRIGGERED = 0x03 # shunt and bus voltage triggered - ADCOFF = 0x04 # ADC off - SVOLT_CONTINUOUS = 0x05 # shunt voltage continuous - BVOLT_CONTINUOUS = 0x06 # bus voltage continuous - SANDBVOLT_CONTINUOUS = 0x07 # shunt and bus voltage continuous - - -# SHUNT VOLTAGE REGISTER (R) -_REG_SHUNTVOLTAGE = const(0x01) - -# BUS VOLTAGE REGISTER (R) -_REG_BUSVOLTAGE = const(0x02) - -# POWER REGISTER (R) -_REG_POWER = const(0x03) - -# CURRENT REGISTER (R) -_REG_CURRENT = const(0x04) - -# CALIBRATION REGISTER (R/W) -_REG_CALIBRATION = const(0x05) -# pylint: enable=too-few-public-methods - - -def _to_signed(num: int) -> int: - if num > 0x7FFF: - num -= 0x10000 - return num - - -class INA219: - """Driver for the INA219 current sensor""" - - # Basic API: - - # INA219( i2c_bus, addr) Create instance of INA219 sensor - # :param i2c_bus The I2C bus the INA219is connected to - # :param addr (0x40) Address of the INA219 on the bus (default 0x40) - - # shunt_voltage RO : shunt voltage scaled to Volts - # bus_voltage RO : bus voltage (V- to GND) scaled to volts (==load voltage) - # current RO : current through shunt, scaled to mA - # power RO : power consumption of the load, scaled to Watt - # set_calibration_32V_2A() Initialize chip for 32V max and up to 2A (default) - # set_calibration_32V_1A() Initialize chip for 32V max and up to 1A - # set_calibration_16V_400mA() Initialize chip for 16V max and up to 400mA - - # Advanced API: - # config register break-up - # reset WO : Write Reset.RESET to reset the chip (must recalibrate) - # bus_voltage_range RW : Bus Voltage Range field (use BusVoltageRange.XXX constants) - # gain RW : Programmable Gain field (use Gain.XXX constants) - # bus_adc_resolution RW : Bus ADC resolution and averaging modes (ADCResolution.XXX) - # shunt_adc_resolution RW : Shunt ADC resolution and averaging modes (ADCResolution.XXX) - # mode RW : operating modes in config register (use Mode.XXX constants) - - # raw_shunt_voltage RO : Shunt Voltage register (not scaled) - # raw_bus_voltage RO : Bus Voltage field in Bus Voltage register (not scaled) - # conversion_ready RO : Conversion Ready bit in Bus Voltage register - # overflow RO : Math Overflow bit in Bus Voltage register - # raw_power RO : Power register (not scaled) - # raw_current RO : Current register (not scaled) - # calibration RW : calibration register (note: value is cached) - - def __init__(self, i2c_bus: I2C, addr: int = 0x40) -> None: - self.i2c_device = i2c_device.I2CDevice(i2c_bus, addr) - self.i2c_addr = addr - - # Set chip to known config values to start - self._cal_value = 0 - self._current_lsb = 0 - self._power_lsb = 0 - self.set_calibration_32V_2A() - - # config register break-up - reset = RWBits(1, _REG_CONFIG, 15, 2, False) - bus_voltage_range = RWBits(1, _REG_CONFIG, 13, 2, False) - gain = RWBits(2, _REG_CONFIG, 11, 2, False) - bus_adc_resolution = RWBits(4, _REG_CONFIG, 7, 2, False) - shunt_adc_resolution = RWBits(4, _REG_CONFIG, 3, 2, False) - mode = RWBits(3, _REG_CONFIG, 0, 2, False) - - # shunt voltage register - raw_shunt_voltage = ROUnaryStruct(_REG_SHUNTVOLTAGE, ">h") - - # bus voltage register - raw_bus_voltage = ROBits(13, _REG_BUSVOLTAGE, 3, 2, False) - conversion_ready = ROBit(_REG_BUSVOLTAGE, 1, 2, False) - overflow = ROBit(_REG_BUSVOLTAGE, 0, 2, False) - - # power and current registers - raw_power = ROUnaryStruct(_REG_POWER, ">H") - raw_current = ROUnaryStruct(_REG_CURRENT, ">h") - - # calibration register - _raw_calibration = UnaryStruct(_REG_CALIBRATION, ">H") - - @property - def calibration(self) -> int: - """Calibration register (cached value)""" - return self._cal_value # return cached value - - @calibration.setter - def calibration(self, cal_value: int) -> None: - self._cal_value = ( - cal_value # value is cached for ``current`` and ``power`` properties - ) - self._raw_calibration = self._cal_value - - @property - def shunt_voltage(self) -> float: - """The shunt voltage (between V+ and V-) in Volts (so +-.327V)""" - # The least signficant bit is 10uV which is 0.00001 volts - return self.raw_shunt_voltage * 0.00001 - - @property - def bus_voltage(self) -> float: - """The bus voltage (between V- and GND) in Volts""" - # Shift to the right 3 to drop CNVR and OVF and multiply by LSB - # Each least signficant bit is 4mV - return self.raw_bus_voltage * 0.004 - - @property - def current(self) -> float: - """The current through the shunt resistor in milliamps.""" - # Sometimes a sharp load will reset the INA219, which will - # reset the cal register, meaning CURRENT and POWER will - # not be available ... always setting a cal - # value even if it's an unfortunate extra step - self._raw_calibration = self._cal_value - # Now we can safely read the CURRENT register! - return self.raw_current * self._current_lsb - - @property - def power(self) -> float: - """The power through the load in Watt.""" - # Sometimes a sharp load will reset the INA219, which will - # reset the cal register, meaning CURRENT and POWER will - # not be available ... always setting a cal - # value even if it's an unfortunate extra step - self._raw_calibration = self._cal_value - # Now we can safely read the CURRENT register! - return self.raw_power * self._power_lsb - - def set_calibration_32V_2A(self) -> None: # pylint: disable=invalid-name - """Configures to INA219 to be able to measure up to 32V and 2A of current. Counter - overflow occurs at 3.2A. - - .. note:: These calculations assume a 0.1 shunt ohm resistor is present - """ - # By default we use a pretty huge range for the input voltage, - # which probably isn't the most appropriate choice for system - # that don't use a lot of power. But all of the calculations - # are shown below if you want to change the settings. You will - # also need to change any relevant register settings, such as - # setting the VBUS_MAX to 16V instead of 32V, etc. - - # VBUS_MAX = 32V (Assumes 32V, can also be set to 16V) - # VSHUNT_MAX = 0.32 (Assumes Gain 8, 320mV, can also be 0.16, 0.08, 0.04) - # RSHUNT = 0.1 (Resistor value in ohms) - - # 1. Determine max possible current - # MaxPossible_I = VSHUNT_MAX / RSHUNT - # MaxPossible_I = 3.2A - - # 2. Determine max expected current - # MaxExpected_I = 2.0A - - # 3. Calculate possible range of LSBs (Min = 15-bit, Max = 12-bit) - # MinimumLSB = MaxExpected_I/32767 - # MinimumLSB = 0.000061 (61uA per bit) - # MaximumLSB = MaxExpected_I/4096 - # MaximumLSB = 0,000488 (488uA per bit) - - # 4. Choose an LSB between the min and max values - # (Preferrably a roundish number close to MinLSB) - # CurrentLSB = 0.0001 (100uA per bit) - self._current_lsb = 0.1 # Current LSB = 100uA per bit - - # 5. Compute the calibration register - # Cal = trunc (0.04096 / (Current_LSB * RSHUNT)) - # Cal = 4096 (0x1000) - - self._cal_value = 4096 - - # 6. Calculate the power LSB - # PowerLSB = 20 * CurrentLSB - # PowerLSB = 0.002 (2mW per bit) - self._power_lsb = 0.002 # Power LSB = 2mW per bit - - # 7. Compute the maximum current and shunt voltage values before overflow - # - # Max_Current = Current_LSB * 32767 - # Max_Current = 3.2767A before overflow - # - # If Max_Current > Max_Possible_I then - # Max_Current_Before_Overflow = MaxPossible_I - # Else - # Max_Current_Before_Overflow = Max_Current - # End If - # - # Max_ShuntVoltage = Max_Current_Before_Overflow * RSHUNT - # Max_ShuntVoltage = 0.32V - # - # If Max_ShuntVoltage >= VSHUNT_MAX - # Max_ShuntVoltage_Before_Overflow = VSHUNT_MAX - # Else - # Max_ShuntVoltage_Before_Overflow = Max_ShuntVoltage - # End If - - # 8. Compute the Maximum Power - # MaximumPower = Max_Current_Before_Overflow * VBUS_MAX - # MaximumPower = 3.2 * 32V - # MaximumPower = 102.4W - - # Set Calibration register to 'Cal' calculated above - self._raw_calibration = self._cal_value - - # Set Config register to take into account the settings above - self.bus_voltage_range = BusVoltageRange.RANGE_32V - self.gain = Gain.DIV_8_320MV - self.bus_adc_resolution = ADCResolution.ADCRES_12BIT_1S - self.shunt_adc_resolution = ADCResolution.ADCRES_12BIT_1S - self.mode = Mode.SANDBVOLT_CONTINUOUS - - def set_calibration_32V_1A(self) -> None: # pylint: disable=invalid-name - """Configures to INA219 to be able to measure up to 32V and 1A of current. Counter overflow - occurs at 1.3A. - - .. note:: These calculations assume a 0.1 ohm shunt resistor is present""" - # By default we use a pretty huge range for the input voltage, - # which probably isn't the most appropriate choice for system - # that don't use a lot of power. But all of the calculations - # are shown below if you want to change the settings. You will - # also need to change any relevant register settings, such as - # setting the VBUS_MAX to 16V instead of 32V, etc. - - # VBUS_MAX = 32V (Assumes 32V, can also be set to 16V) - # VSHUNT_MAX = 0.32 (Assumes Gain 8, 320mV, can also be 0.16, 0.08, 0.04) - # RSHUNT = 0.1 (Resistor value in ohms) - - # 1. Determine max possible current - # MaxPossible_I = VSHUNT_MAX / RSHUNT - # MaxPossible_I = 3.2A - - # 2. Determine max expected current - # MaxExpected_I = 1.0A - - # 3. Calculate possible range of LSBs (Min = 15-bit, Max = 12-bit) - # MinimumLSB = MaxExpected_I/32767 - # MinimumLSB = 0.0000305 (30.5uA per bit) - # MaximumLSB = MaxExpected_I/4096 - # MaximumLSB = 0.000244 (244uA per bit) - - # 4. Choose an LSB between the min and max values - # (Preferrably a roundish number close to MinLSB) - # CurrentLSB = 0.0000400 (40uA per bit) - self._current_lsb = 0.04 # In milliamps - - # 5. Compute the calibration register - # Cal = trunc (0.04096 / (Current_LSB * RSHUNT)) - # Cal = 10240 (0x2800) - - self._cal_value = 10240 - - # 6. Calculate the power LSB - # PowerLSB = 20 * CurrentLSB - # PowerLSB = 0.0008 (800uW per bit) - self._power_lsb = 0.0008 - - # 7. Compute the maximum current and shunt voltage values before overflow - # - # Max_Current = Current_LSB * 32767 - # Max_Current = 1.31068A before overflow - # - # If Max_Current > Max_Possible_I then - # Max_Current_Before_Overflow = MaxPossible_I - # Else - # Max_Current_Before_Overflow = Max_Current - # End If - # - # ... In this case, we're good though since Max_Current is less than MaxPossible_I - # - # Max_ShuntVoltage = Max_Current_Before_Overflow * RSHUNT - # Max_ShuntVoltage = 0.131068V - # - # If Max_ShuntVoltage >= VSHUNT_MAX - # Max_ShuntVoltage_Before_Overflow = VSHUNT_MAX - # Else - # Max_ShuntVoltage_Before_Overflow = Max_ShuntVoltage - # End If - - # 8. Compute the Maximum Power - # MaximumPower = Max_Current_Before_Overflow * VBUS_MAX - # MaximumPower = 1.31068 * 32V - # MaximumPower = 41.94176W - - # Set Calibration register to 'Cal' calculated above - self._raw_calibration = self._cal_value - - # Set Config register to take into account the settings above - self.bus_voltage_range = BusVoltageRange.RANGE_32V - self.gain = Gain.DIV_8_320MV - self.bus_adc_resolution = ADCResolution.ADCRES_12BIT_1S - self.shunt_adc_resolution = ADCResolution.ADCRES_12BIT_1S - self.mode = Mode.SANDBVOLT_CONTINUOUS - - def set_calibration_16V_400mA(self) -> None: # pylint: disable=invalid-name - """Configures to INA219 to be able to measure up to 16V and 400mA of current. Counter - overflow occurs at 1.6A. - - .. note:: These calculations assume a 0.1 ohm shunt resistor is present""" - # Calibration which uses the highest precision for - # current measurement (0.1mA), at the expense of - # only supporting 16V at 400mA max. - - # VBUS_MAX = 16V - # VSHUNT_MAX = 0.04 (Assumes Gain 1, 40mV) - # RSHUNT = 0.1 (Resistor value in ohms) - - # 1. Determine max possible current - # MaxPossible_I = VSHUNT_MAX / RSHUNT - # MaxPossible_I = 0.4A - - # 2. Determine max expected current - # MaxExpected_I = 0.4A - - # 3. Calculate possible range of LSBs (Min = 15-bit, Max = 12-bit) - # MinimumLSB = MaxExpected_I/32767 - # MinimumLSB = 0.0000122 (12uA per bit) - # MaximumLSB = MaxExpected_I/4096 - # MaximumLSB = 0.0000977 (98uA per bit) - - # 4. Choose an LSB between the min and max values - # (Preferrably a roundish number close to MinLSB) - # CurrentLSB = 0.00005 (50uA per bit) - self._current_lsb = 0.05 # in milliamps - - # 5. Compute the calibration register - # Cal = trunc (0.04096 / (Current_LSB * RSHUNT)) - # Cal = 8192 (0x2000) - - self._cal_value = 8192 - - # 6. Calculate the power LSB - # PowerLSB = 20 * CurrentLSB - # PowerLSB = 0.001 (1mW per bit) - self._power_lsb = 0.001 - - # 7. Compute the maximum current and shunt voltage values before overflow - # - # Max_Current = Current_LSB * 32767 - # Max_Current = 1.63835A before overflow - # - # If Max_Current > Max_Possible_I then - # Max_Current_Before_Overflow = MaxPossible_I - # Else - # Max_Current_Before_Overflow = Max_Current - # End If - # - # Max_Current_Before_Overflow = MaxPossible_I - # Max_Current_Before_Overflow = 0.4 - # - # Max_ShuntVoltage = Max_Current_Before_Overflow * RSHUNT - # Max_ShuntVoltage = 0.04V - # - # If Max_ShuntVoltage >= VSHUNT_MAX - # Max_ShuntVoltage_Before_Overflow = VSHUNT_MAX - # Else - # Max_ShuntVoltage_Before_Overflow = Max_ShuntVoltage - # End If - # - # Max_ShuntVoltage_Before_Overflow = VSHUNT_MAX - # Max_ShuntVoltage_Before_Overflow = 0.04V - - # 8. Compute the Maximum Power - # MaximumPower = Max_Current_Before_Overflow * VBUS_MAX - # MaximumPower = 0.4 * 16V - # MaximumPower = 6.4W - - # Set Calibration register to 'Cal' calculated above - self._raw_calibration = self._cal_value - - # Set Config register to take into account the settings above - self.bus_voltage_range = BusVoltageRange.RANGE_16V - self.gain = Gain.DIV_1_40MV - self.bus_adc_resolution = ADCResolution.ADCRES_12BIT_1S - self.shunt_adc_resolution = ADCResolution.ADCRES_12BIT_1S - self.mode = Mode.SANDBVOLT_CONTINUOUS - - def set_calibration_16V_5A(self) -> None: # pylint: disable=invalid-name - """Configures to INA219 to be able to measure up to 16V and 5000mA of current. Counter - overflow occurs at 8.0A. - - .. note:: These calculations assume a 0.02 ohm shunt resistor is present""" - # Calibration which uses the highest precision for - # current measurement (0.1mA), at the expense of - # only supporting 16V at 5000mA max. - - # VBUS_MAX = 16V - # VSHUNT_MAX = 0.16 (Assumes Gain 3, 160mV) - # RSHUNT = 0.02 (Resistor value in ohms) - - # 1. Determine max possible current - # MaxPossible_I = VSHUNT_MAX / RSHUNT - # MaxPossible_I = 8.0A - - # 2. Determine max expected current - # MaxExpected_I = 5.0A - - # 3. Calculate possible range of LSBs (Min = 15-bit, Max = 12-bit) - # MinimumLSB = MaxExpected_I/32767 - # MinimumLSB = 0.0001529 (uA per bit) - # MaximumLSB = MaxExpected_I/4096 - # MaximumLSB = 0.0012207 (uA per bit) - - # 4. Choose an LSB between the min and max values - # (Preferrably a roundish number close to MinLSB) - # CurrentLSB = 0.00016 (uA per bit) - self._current_lsb = 0.1524 # in milliamps - - # 5. Compute the calibration register - # Cal = trunc (0.04096 / (Current_LSB * RSHUNT)) - # Cal = 13434 (0x347a) - - self._cal_value = 13434 - - # 6. Calculate the power LSB - # PowerLSB = 20 * CurrentLSB - # PowerLSB = 0.003 (3.048mW per bit) - self._power_lsb = 0.003048 - - # 7. Compute the maximum current and shunt voltage values before overflow - # - # 8. Compute the Maximum Power - # - - # Set Calibration register to 'Cal' calcutated above - self._raw_calibration = self._cal_value - - # Set Config register to take into account the settings above - self.bus_voltage_range = BusVoltageRange.RANGE_16V - self.gain = Gain.DIV_4_160MV - self.bus_adc_resolution = ADCResolution.ADCRES_12BIT_1S - self.shunt_adc_resolution = ADCResolution.ADCRES_12BIT_1S - self.mode = Mode.SANDBVOLT_CONTINUOUS diff --git a/Batt_Board/lib/adafruit_max31856.py b/Batt_Board/lib/adafruit_max31856.py deleted file mode 100644 index 4427d4b..0000000 --- a/Batt_Board/lib/adafruit_max31856.py +++ /dev/null @@ -1,451 +0,0 @@ -# SPDX-FileCopyrightText: 2018 Bryan Siepert for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -""" -`MAX31856` -==================================================== - -CircuitPython module for the MAX31856 Universal Thermocouple Amplifier. See -examples/simpletest.py for an example of the usage. - -* Author(s): Bryan Siepert - -Implementation Notes --------------------- - -**Hardware:** - -* Adafruit `Universal Thermocouple Amplifier MAX31856 Breakout - `_ (Product ID: 3263) - -**Software and Dependencies:** - -* Adafruit CircuitPython firmware for the supported boards: - https://circuitpython.org/downloads - -* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice - -""" - -from time import sleep -from micropython import const -from adafruit_bus_device.spi_device import SPIDevice - -try: - from typing import Dict, Tuple - from typing_extensions import Literal - from busio import SPI - from digitalio import DigitalInOut -except ImportError: - pass - -try: - from struct import unpack -except ImportError: - from ustruct import unpack - -__version__ = "0.0.0+auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MAX31856.git" - -# Register constants -_MAX31856_CR0_REG = const(0x00) -_MAX31856_CR0_AUTOCONVERT = const(0x80) -_MAX31856_CR0_1SHOT = const(0x40) -_MAX31856_CR0_OCFAULT1 = const(0x20) -_MAX31856_CR0_OCFAULT0 = const(0x10) -_MAX31856_CR0_CJ = const(0x08) -_MAX31856_CR0_FAULT = const(0x04) -_MAX31856_CR0_FAULTCLR = const(0x02) -_MAX31856_CR0_50HZ = const(0x01) - -_MAX31856_CR1_REG = const(0x01) -_MAX31856_MASK_REG = const(0x02) -_MAX31856_CJHF_REG = const(0x03) -_MAX31856_CJLF_REG = const(0x04) -_MAX31856_LTHFTH_REG = const(0x05) -_MAX31856_LTHFTL_REG = const(0x06) -_MAX31856_LTLFTH_REG = const(0x07) -_MAX31856_LTLFTL_REG = const(0x08) -_MAX31856_CJTO_REG = const(0x09) -_MAX31856_CJTH_REG = const(0x0A) -_MAX31856_CJTL_REG = const(0x0B) -_MAX31856_LTCBH_REG = const(0x0C) -_MAX31856_LTCBM_REG = const(0x0D) -_MAX31856_LTCBL_REG = const(0x0E) -_MAX31856_SR_REG = const(0x0F) - -# fault types -_MAX31856_FAULT_CJRANGE = const(0x80) -_MAX31856_FAULT_TCRANGE = const(0x40) -_MAX31856_FAULT_CJHIGH = const(0x20) -_MAX31856_FAULT_CJLOW = const(0x10) -_MAX31856_FAULT_TCHIGH = const(0x08) -_MAX31856_FAULT_TCLOW = const(0x04) -_MAX31856_FAULT_OVUV = const(0x02) -_MAX31856_FAULT_OPEN = const(0x01) - -_AVGSEL_CONSTS = {1: 0x00, 2: 0x10, 4: 0x20, 8: 0x30, 16: 0x40} - - -class ThermocoupleType: # pylint: disable=too-few-public-methods - """An enum-like class representing the different types of thermocouples that the MAX31856 can - use. The values can be referenced like ``ThermocoupleType.K`` or ``ThermocoupleType.S`` - Possible values are - - - ``ThermocoupleType.B`` - - ``ThermocoupleType.E`` - - ``ThermocoupleType.J`` - - ``ThermocoupleType.K`` - - ``ThermocoupleType.N`` - - ``ThermocoupleType.R`` - - ``ThermocoupleType.S`` - - ``ThermocoupleType.T`` - - """ - - # pylint: disable=invalid-name - B = 0b0000 - E = 0b0001 - J = 0b0010 - K = 0b0011 - N = 0b0100 - R = 0b0101 - S = 0b0110 - T = 0b0111 - G8 = 0b1000 - G32 = 0b1100 - - -class MAX31856: - """Driver for the MAX31856 Universal Thermocouple Amplifier - - :param ~busio.SPI spi: The SPI bus the MAX31856 is connected to. - :param ~microcontroller.Pin cs: The pin used for the CS signal. - :param ~adafruit_max31856.ThermocoupleType thermocouple_type: The type of thermocouple.\ - Default is Type K. - :param ~int baudrate: The SPI baudrate. Default is 500000. - - **Quickstart: Importing and using the MAX31856** - - Here is an example of using the :class:`MAX31856` class. - First you will need to import the libraries to use the sensor - - .. code-block:: python - - import board - from digitalio import DigitalInOut, Direction - import adafruit_max31856 - - Once this is done you can define your `board.SPI` object and define your sensor object - - .. code-block:: python - - spi = board.SPI() - cs = digitalio.DigitalInOut(board.D5) # Chip select of the MAX31856 board. - sensor = adafruit_max31856.MAX31856(spi, cs) - - - Now you have access to the :attr:`temperature` attribute - - .. code-block:: python - - temperature = sensor.temperature - - """ - - # A class level buffer to reduce allocations for reading and writing. - # Tony says this isn't re-entrant or thread safe! - _BUFFER = bytearray(4) - - def __init__( - self, - spi: SPI, - cs: DigitalInOut, # pylint: disable=invalid-name - thermocouple_type: int = ThermocoupleType.K, - baudrate: int = 500000, - ) -> None: - self._device = SPIDevice(spi, cs, baudrate=baudrate, polarity=0, phase=1) - - # assert on any fault - self._write_u8(_MAX31856_MASK_REG, 0x0) - # configure open circuit faults - self._write_u8(_MAX31856_CR0_REG, _MAX31856_CR0_OCFAULT0) - - # set thermocouple type - self._set_thermocouple_type(thermocouple_type) - - def _set_thermocouple_type(self, thermocouple_type: ThermocoupleType) -> None: - # get current value of CR1 Reg - conf_reg_1 = self._read_register(_MAX31856_CR1_REG, 1)[0] - conf_reg_1 &= 0xF0 # mask off bottom 4 bits - # add the new value for the TC type - conf_reg_1 |= int(thermocouple_type) & 0x0F - self._write_u8(_MAX31856_CR1_REG, conf_reg_1) - - @property - def averaging(self) -> int: - """Number of samples averaged together in each result. - Must be 1, 2, 4, 8, or 16. Default is 1 (no averaging). - """ - conf_reg_1 = self._read_register(_MAX31856_CR1_REG, 1)[0] - avgsel = conf_reg_1 & ~0b10001111 # clear bits other than 4-6 - # check which byte this corresponds to - for key, value in _AVGSEL_CONSTS.items(): - if value == avgsel: - return key - raise KeyError(f"AVGSEL bit pattern was not recognised ({avgsel:>08b})") - - @averaging.setter - def averaging(self, num_samples: int) -> None: - # This option is set in bits 4-6 of register CR1. - if num_samples not in _AVGSEL_CONSTS: - raise ValueError("Num_samples must be one of 1,2,4,8,16") - avgsel = _AVGSEL_CONSTS[num_samples] - # get current value of CR1 Reg - conf_reg_1 = self._read_register(_MAX31856_CR1_REG, 1)[0] - conf_reg_1 &= 0b10001111 # clear bits 4-6 - # OR the AVGSEL bits (4-6) - conf_reg_1 |= avgsel - self._write_u8(_MAX31856_CR1_REG, conf_reg_1) - - @property - def noise_rejection(self) -> Literal[50, 60]: - """ - The frequency (Hz) to be used by the noise rejection filter. - Must be 50 or 60. Default is 60.""" - # this value is stored in bit 0 of register CR0. - conf_reg_0 = self._read_register(_MAX31856_CR0_REG, 1)[0] - if conf_reg_0 & _MAX31856_CR0_50HZ: - return 50 - return 60 - - @noise_rejection.setter - def noise_rejection(self, frequency: Literal[50, 60]) -> None: - conf_reg_0 = self._read_register(_MAX31856_CR0_REG, 1)[0] - if frequency == 50: - conf_reg_0 |= _MAX31856_CR0_50HZ # set the 50hz bit - elif frequency == 60: - conf_reg_0 &= ~_MAX31856_CR0_50HZ # clear the 50hz bit - else: - raise ValueError("Frequency must be 50 or 60") - self._write_u8(_MAX31856_CR0_REG, conf_reg_0) - - @property - def temperature(self) -> float: - """Measure the temperature of the sensor and wait for the result. - Return value is in degrees Celsius. (read-only)""" - self._perform_one_shot_measurement() - return self.unpack_temperature() - - def unpack_temperature(self) -> float: - """Reads the probe temperature from the register""" - # unpack the 3-byte temperature as 4 bytes - raw_temp = unpack( - ">i", self._read_register(_MAX31856_LTCBH_REG, 3) + bytes([0]) - )[0] - - # shift to remove extra byte from unpack needing 4 bytes - raw_temp >>= 8 - - # effectively shift raw_read >> 12 to convert pseudo-float - temp_float = raw_temp / 4096.0 - - return temp_float - - def read_high_res_temp(self) -> float: - """Reads 19-bit temperature data from the sensor and returns it in degrees Celsius. - - Reading must have already been initiated via: - `initiate_one_shot_measurement` or `start_autoconverting`. - - Returns: - float: temperature in degrees Celsius - """ - # Per datasheet, temperature resolution in °C per LSB - resolution = 0.0078125 - - # Read the temperature registers - raw_bytes = self._read_sequential_registers(_MAX31856_LTCBH_REG, 3) - # Extract individual bytes from the byte array - high_byte = raw_bytes[0] # First byte - mid_byte = raw_bytes[1] # Second byte - low_byte = raw_bytes[2] # Third byte - - # Combine the bytes into a single 19-bit value - combined = (high_byte << 11) | (mid_byte << 3) | (low_byte >> 5) - - # Adjust for two's complement (sign extension for negative values) - if combined & 0x40000: # Check if 19th bit is set (negative temperature) - combined = combined - 0x80000 - - # Convert to temperature using the resolution - return combined * resolution - - @property - def reference_temperature(self) -> float: - """Wait to retrieve temperature of the cold junction in degrees Celsius. (read-only)""" - self._perform_one_shot_measurement() - return self.unpack_reference_temperature() - - def unpack_reference_temperature(self) -> float: - """Reads the reference temperature from the register""" - raw_read = unpack(">h", self._read_register(_MAX31856_CJTH_REG, 2))[0] - - # effectively shift raw_read >> 8 to convert pseudo-float - cold_junction_temp = raw_read / 256.0 - - return cold_junction_temp - - @property - def temperature_thresholds(self) -> Tuple[float, float]: - """The thermocouple's low and high temperature thresholds - as a ``(low_temp, high_temp)`` tuple - """ - - raw_low = unpack(">h", self._read_register(_MAX31856_LTLFTH_REG, 2)) - raw_high = unpack(">h", self._read_register(_MAX31856_LTHFTH_REG, 2)) - - return (round(raw_low[0] / 16.0, 1), round(raw_high[0] / 16.0, 1)) - - @temperature_thresholds.setter - def temperature_thresholds(self, val: Tuple[float, float]) -> None: - int_low = int(val[0] * 16) - int_high = int(val[1] * 16) - - self._write_u8(_MAX31856_LTHFTH_REG, int_high >> 8) - self._write_u8(_MAX31856_LTHFTL_REG, int_high) - - self._write_u8(_MAX31856_LTLFTH_REG, int_low >> 8) - self._write_u8(_MAX31856_LTLFTL_REG, int_low) - - @property - def reference_temperature_thresholds( # pylint: disable=invalid-name, - self, - ) -> Tuple[float, float]: - """The cold junction's low and high temperature thresholds - as a ``(low_temp, high_temp)`` tuple - """ - return ( - float(unpack("b", self._read_register(_MAX31856_CJLF_REG, 1))[0]), - float(unpack("b", self._read_register(_MAX31856_CJHF_REG, 1))[0]), - ) - - @reference_temperature_thresholds.setter - def reference_temperature_thresholds( # pylint: disable=invalid-name, - self, val: Tuple[float, float] - ) -> None: - self._write_u8(_MAX31856_CJLF_REG, int(val[0])) - self._write_u8(_MAX31856_CJHF_REG, int(val[1])) - - @property - def fault(self) -> Dict[str, bool]: - """A dictionary with the status of each fault type where the key is the fault type and the - value is a bool if the fault is currently active - - =================== ================================= - Key Fault type - =================== ================================= - "cj_range" Cold junction range fault - "tc_range" Thermocouple range fault - "cj_high" Cold junction high threshold fault - "cj_low" Cold junction low threshold fault - "tc_high" Thermocouple high threshold fault - "tc_low" Thermocouple low threshold fault - "voltage" Over/under voltage fault - "open_tc" Thermocouple open circuit fault - =================== ================================= - - """ - faults = self._read_register(_MAX31856_SR_REG, 1)[0] - - return { - "cj_range": bool(faults & _MAX31856_FAULT_CJRANGE), - "tc_range": bool(faults & _MAX31856_FAULT_TCRANGE), - "cj_high": bool(faults & _MAX31856_FAULT_CJHIGH), - "cj_low": bool(faults & _MAX31856_FAULT_CJLOW), - "tc_high": bool(faults & _MAX31856_FAULT_TCHIGH), - "tc_low": bool(faults & _MAX31856_FAULT_TCLOW), - "voltage": bool(faults & _MAX31856_FAULT_OVUV), - "open_tc": bool(faults & _MAX31856_FAULT_OPEN), - } - - def _perform_one_shot_measurement(self) -> None: - self.initiate_one_shot_measurement() - # wait for the measurement to complete - self._wait_for_oneshot() - - def initiate_one_shot_measurement(self) -> None: - """Starts a one-shot measurement and returns immediately. - A measurement takes approximately 160ms. - Check the status of the measurement with `oneshot_pending`; when it is false, - the measurement is complete and the value can be read with `unpack_temperature`. - """ - # read the current value of the first config register - conf_reg_0 = self._read_register(_MAX31856_CR0_REG, 1)[0] - - # and the complement to guarantee the autoconvert bit is unset - conf_reg_0 &= ~_MAX31856_CR0_AUTOCONVERT - # or the oneshot bit to ensure it is set - conf_reg_0 |= _MAX31856_CR0_1SHOT - - # write it back with the new values, prompting the sensor to perform a measurement - self._write_u8(_MAX31856_CR0_REG, conf_reg_0) - - def start_autoconverting(self) -> None: # pylint: disable=no-self-use - """Starts autoconverting temperature measurements. - The sensor will perform a measurement every ~100ms. - """ - # read the current value of the first config register - conf_reg_0 = self._read_register(_MAX31856_CR0_REG, 1)[0] - - # and the complement to guarantee the oneshot bit is unset - conf_reg_0 &= ~_MAX31856_CR0_1SHOT - # or the autoconvert bit to ensure it is set - conf_reg_0 |= _MAX31856_CR0_AUTOCONVERT - - # write it back with the new values, prompting the sensor to perform a measurement - self._write_u8(_MAX31856_CR0_REG, conf_reg_0) - - @property - def oneshot_pending(self) -> bool: - """A boolean indicating the status of the one-shot flag. - A True value means the measurement is still ongoing. - A False value means measurement is complete.""" - oneshot_flag = ( - self._read_register(_MAX31856_CR0_REG, 1)[0] & _MAX31856_CR0_1SHOT - ) - return bool(oneshot_flag) - - def _wait_for_oneshot(self) -> None: - while self.oneshot_pending: - sleep(0.01) - - def _read_register(self, address: int, length: int) -> bytearray: - # pylint: disable=no-member - # Read a 16-bit BE unsigned value from the specified 8-bit address. - with self._device as device: - self._BUFFER[0] = address & 0x7F - device.write(self._BUFFER, end=1) - device.readinto(self._BUFFER, end=length) - return self._BUFFER[:length] - - def _read_sequential_registers(self, start_addr, num_registers=3) -> bytearray: - """ - Read a sequence of `num_registers` registers, starting from `start_addr`. - """ - assert num_registers >= 1, "Number of registers to read must be at least 1" - buf = bytearray(num_registers) - with self._device as device: - # Send read command and start address - device.write(bytearray([start_addr & 0x7F])) - # Read the specified number of registers into the buffer - device.readinto(buf) - return buf - - def _write_u8(self, address: int, val: int) -> None: - # Write an 8-bit unsigned value to the specified 8-bit address. - with self._device as device: - self._BUFFER[0] = (address | 0x80) & 0xFF - self._BUFFER[1] = val & 0xFF - device.write(self._BUFFER, end=2) # pylint: disable=no-member diff --git a/Batt_Board/lib/adafruit_mcp2515/__init__.py b/Batt_Board/lib/adafruit_mcp2515/__init__.py deleted file mode 100644 index 45490f6..0000000 --- a/Batt_Board/lib/adafruit_mcp2515/__init__.py +++ /dev/null @@ -1,943 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2020 Bryan Siepert for Adafruit Industries -# -# SPDX-License-Identifier: MIT -""" -`adafruit_mcp2515` -================================================================================ - -A CircuitPython library for working with the MCP2515 CAN bus controller using the -CircuitPython `canio` API - - -* Author(s): Bryan Siepert - -Implementation Notes --------------------- - -**Hardware:** - -**Software and Dependencies:** - -* Adafruit CircuitPython firmware for the supported boards: - https://github.com/adafruit/circuitpython/releases - -* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice -""" - -from collections import namedtuple -from struct import unpack_from, pack_into -from time import sleep -from micropython import const -from adafruit_bus_device import spi_device -from .canio import * -from .timer import Timer - -try: - from typing_extensions import Literal -except ImportError: - pass - -__version__ = "0.0.0+auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MCP2515.git" - -# modes -_MODE_NORMAL = const(0x00) -_MODE_SLEEP = const(0x20) -_MODE_LOOPBACK = const(0x40) -_MODE_LISTENONLY = const(0x60) -_MODE_CONFIG = const(0x80) - -# commands -_RESET = const(0xC0) -_WRITE = const(0x02) -_READ = const(0x03) -_BITMOD = const(0x05) - -_LOAD_TX0 = const(0x40) -_LOAD_TX1 = const(0x42) -_LOAD_TX2 = const(0x44) -_READ_STATUS = const(0xA0) - -_SEND_TX0 = const(0x81) -_SEND_TX1 = const(0x82) -_SEND_TX2 = const(0x84) -_SEND_ALL = const(0x87) - -_READ_RX0 = const(0x90) -_READ_RX1 = const(0x94) - -# Registers - -_CANINTE = const(0x2B) -_CANINTF = const(0x2C) -_CANSTAT = const(0x0E) -_CANCTRL = const(0x0F) - -_CNF3 = const(0x28) -_CNF2 = const(0x29) -_CNF1 = const(0x2A) - -_TXB0CTRL = const(0x30) -_TXB0SIDH = const(0x31) - -_TXB1CTRL = const(0x40) -_TXB1SIDH = const(0x41) - -_TXB2CTRL = const(0x50) -_TXB2SIDH = const(0x51) - -_RXB0CTRL = const(0x60) -_RXB0SIDH = const(0x61) - -_RXB1CTRL = const(0x70) -_RXB1SIDH = const(0x71) - -_TX0IF = const(0x04) -_TX1IF = const(0x08) -_TX2IF = const(0x10) - -# Filters & Masks -_RXM0SIDH = const(0x20) -_RXM1SIDH = const(0x24) -MASKS = [_RXM0SIDH, _RXM1SIDH] - -_RXF0SIDH = const(0x00) -_RXF1SIDH = const(0x04) -_RXF2SIDH = const(0x08) -_RXF3SIDH = const(0x10) -_RXF4SIDH = const(0x14) -_RXF5SIDH = const(0x18) -FILTERS = [[_RXF0SIDH, _RXF1SIDH], [_RXF2SIDH, _RXF3SIDH, _RXF4SIDH, _RXF5SIDH]] -# bits/flags -_RX0IF = const(0x01) -_RX1IF = const(0x02) -_WAKIF = const(0x40) -# _MERRF = const(0x80) - -# Standard/Extended ID Buffers, Masks, Flags -_TXB_EXIDE_M_16 = const(0x08) -_TXB_TXREQ_M = const(0x08) # TX request/completion bit - -EXTID_TOP_11_WRITE_MASK = 0x1FFC0000 -EXTID_TOP_11_READ_MASK = 0xFFE00000 - -EXTID_BOTTOM_29_MASK = (1 << 29) - 1 # bottom 18 bits -EXTID_BOTTOM_18_MASK = (1 << 18) - 1 # bottom 18 bits -STDID_BOTTOM_11_MASK = 0x7FF - -EXTID_FLAG_MASK = ( - 1 << 19 -) # to set/get the "is an extended id?" flag from a 4-byte ID buffer - -# masks -_MODE_MASK = const(0xE0) - -_RXB_RX_MASK = const(0x60) -_RXB_BUKT_MASK = const((1 << 2)) -_RXB_RX_STDEXT = const(0x00) - -_STAT_RXIF_MASK = const(0x03) -_RTR_MASK = const(0x40) - -_STAT_TXIF_MASK = const(0xA8) -_STAT_TX0_PENDING = const(0x04) -_STAT_TX1_PENDING = const(0x10) -_STAT_TX2_PENDING = const(0x40) - -_STAT_TX_PENDING_MASK = const(_STAT_TX0_PENDING | _STAT_TX1_PENDING | _STAT_TX2_PENDING) - -###### Bus State and Error Counts ########## - -# TEC: TRANSMIT ERROR COUNTER REGISTER (ADDRESS: 1Ch) -_TEC = const(0x1C) -_REC = const(0x1D) -# REC: RECEIVE ERROR COUNTER REGISTER (ADDRESS: 1Dh) -_EFLG = const(0x2D) - -############ Misc Consts ######### -_SEND_TIMEOUT_MS = const(5) # 500ms -_MAX_CAN_MSG_LEN = 8 # ?! -# perhaps this will be stateful later? -_TransmitBuffer = namedtuple( - "_TransmitBuffer", - ["CTRL_REG", "STD_ID_REG", "INT_FLAG_MASK", "LOAD_CMD", "SEND_CMD"], -) - -# --- Baud Rates Table --- -# Values for the 8MHz and 10MHz Crystal Oscillator are based on this calculator: -# https://www.kvaser.com/support/calculators/bit-timing-calculator/ -# - MCP2510 can be used to calculate the timing values since the registers are allmost the same -# Only difference is the CNF3, which has an extra bit (CNF3[7] = SOF, Start of Frame signal bit) -# The SOF bit can be left on zero -# - A bit sample point (SP%) of 70% is used if nothing else is defined -# - A Synchronization Jump Width (SJW) of 1 Time Quanta (TQ) is used - -_BAUD_RATES = { - # This is magic, don't disturb the dragon - # expects a 16Mhz crystal - 16000000: { - # CNF1, CNF2, CNF3 - 1000000: (0x00, 0xD0, 0x82), - 500000: (0x00, 0xF0, 0x86), - 250000: (0x41, 0xF1, 0x85), - 200000: (0x01, 0xFA, 0x87), - 125000: (0x03, 0xF0, 0x86), - 100000: (0x03, 0xFA, 0x87), - 95000: (0x03, 0xAD, 0x07), - 83300: (0x03, 0xBE, 0x07), - 80000: (0x03, 0xFF, 0x87), - 50000: (0x07, 0xFA, 0x87), - 40000: (0x07, 0xFF, 0x87), - 33000: (0x09, 0xBE, 0x07), - 31250: (0x0F, 0xF1, 0x85), - 25000: (0x0F, 0xBA, 0x07), - 20000: (0x0F, 0xFF, 0x87), - 10000: (0x1F, 0xFF, 0x87), - 5000: (0x3F, 0xFF, 0x87), - 666000: (0x00, 0xA0, 0x04), - }, - # 10MHz Crystal oscillator (used on the MIKROE "CAN SPI click"-board) - 10000000: { - # CNF1, CNF2, CNF3 - 500000: (0x00, 0x92, 0x02), - 250000: (0x00, 0xB5, 0x05), - 200000: (0x00, 0xBF, 0x07), # SP is 68%! - 125000: (0x01, 0xA4, 0x04), # SP is 68.75%! - 100000: (0x01, 0xB5, 0x05), - 95000: (0x05, 0x89, 0x01), # SP is 71.43%, Baud rate is 95.238kbps! - 83300: (0x02, 0xA4, 0x04), # SP is 68.75%, Baud rate is 83.333kbps! - 80000: (0x04, 0x92, 0x02), - 50000: (0x03, 0xB5, 0x05), - 40000: (0x04, 0xB5, 0x05), - 33000: (0x05, 0xB5, 0x05), # Baud rate is 33.333kbps! - 31250: (0x07, 0xA4, 0x04), # SP is 68.75%! - 25000: (0x07, 0xB5, 0x05), - 20000: (0x09, 0xB5, 0x05), - 10000: (0x13, 0xB5, 0x05), - 5000: (0x27, 0xB5, 0x05), - }, - # 8MHz Crystal oscillator - 8000000: { - # CNF1, CNF2, CNF3 - 500000: (0x00, 0x91, 0x01), - 250000: (0x40, 0xB5, 0x01), - 200000: (0x00, 0xB6, 0x04), - 125000: (0x01, 0xAC, 0x03), - 100000: (0x01, 0xB6, 0x04), - 95000: (0x41, 0xBE, 0x04), - 83300: (0x02, 0xAC, 0x03), - 80000: (0x04, 0x9A, 0x01), - 50000: (0x03, 0xB6, 0x04), - 40000: (0x04, 0xB6, 0x04), - 33000: (0x0A, 0x9A, 0x02), - 31250: (0x07, 0xAC, 0x03), - 25000: (0x07, 0xB6, 0x04), - 20000: (0x09, 0xB6, 0x04), - 10000: (0x13, 0xB6, 0x04), - 5000: (0x27, 0xB6, 0x04), - 666000: (0x00, 0x88, 0x01), - }, -} - - -def _tx_buffer_status_decode(status_byte): - out_str = "Status: " - # when CAN_H is disconnected?: 0x18 - out_str += "\nStatus of chosen buffer: %s\n" % hex(status_byte) - if status_byte & 0x40: - out_str += " Message ABORTED" - if status_byte & 0x20: - out_str += " Message LOST ARBITRATION" - if status_byte & 0x10: - out_str += " TRANSMIT ERROR" - if status_byte & 0x8: - out_str += " Transmit Requested" - else: - out_str += " Message sent" - out_str += " Priority: " + ["LAST", "LOW", "MEDIUM", "HIGH"][status_byte & 0x3] - - return out_str - - -class MCP2515: # pylint:disable=too-many-instance-attributes - """A common shared-bus protocol. - - :param ~busio.SPI spi: The SPI bus used to communicate with the MCP2515 - :param ~digitalio.DigitalInOut cs_pin: SPI bus enable pin - :param int baudrate: The bit rate of the bus in Hz. All devices on\ - the bus must agree on this value. Defaults to 250000. - :param Literal crystal_freq: MCP2515 crystal frequency. Valid values are:\ - 16000000, 10000000 and 8000000. Defaults to 16000000 (16MHz).\ - :param bool loopback: Receive only packets sent from this device, and send only to this\ - device. Requires that `silent` is also set to `True`, but only prevents transmission to\ - other devices. Otherwise the send/receive behavior is normal. - :param bool silent: When `True` the controller does not transmit and all messages are\ - received, ignoring errors and filters. This mode can be used to “sniff” a CAN bus without\ - interfering. Defaults to `False`. - :param bool auto_restart: **Not supported by hardware. An `AttributeError` will be raised\ - if `auto_restart` is set to `True`** If `True`, will restart communications after entering\ - bus-off state. Defaults to `False`. - :param bool debug: If `True`, will enable printing debug information. Defaults to `False`. - """ - - def __init__( - self, - spi_bus, - cs_pin, - *, - baudrate: int = 250000, - crystal_freq: Literal[8000000, 10000000, 16000000] = 16000000, - loopback: bool = False, - silent: bool = False, - auto_restart: bool = False, - debug: bool = False, - ): - - if loopback and not silent: - raise AttributeError("Loopback mode requires silent to be set") - if auto_restart: - raise AttributeError("`auto-restart` is not supported by hardware") - - self._auto_restart = auto_restart - self._debug = debug - self._bus_device_obj = spi_device.SPIDevice(spi_bus, cs_pin) - self._cs_pin = cs_pin - self._buffer = bytearray(20) - self._id_buffer = bytearray(4) - self._unread_message_queue = [] - self._timer = Timer() - self._tx_buffers = [] - self._rx0_overflow = False - self._rx1_overflow = False - self._masks_in_use = [] - self._filters_in_use = [[], []] - self._mode = None - self._bus_state = BusState.ERROR_ACTIVE - self._baudrate = baudrate - self._crystal_freq = crystal_freq - self._loopback = loopback - self._silent = silent - - self._init_buffers() - self.initialize() - - def _init_buffers(self): - - self._tx_buffers = [ - _TransmitBuffer( - CTRL_REG=_TXB0CTRL, - STD_ID_REG=_TXB0SIDH, - INT_FLAG_MASK=_TX0IF, - LOAD_CMD=_LOAD_TX0, - SEND_CMD=_SEND_TX0, - ), - _TransmitBuffer( - CTRL_REG=_TXB1CTRL, - STD_ID_REG=_TXB1SIDH, - INT_FLAG_MASK=_TX1IF, - LOAD_CMD=_LOAD_TX1, - SEND_CMD=_SEND_TX1, - ), - _TransmitBuffer( - CTRL_REG=_TXB2CTRL, - STD_ID_REG=_TXB2SIDH, - INT_FLAG_MASK=_TX2IF, - LOAD_CMD=_LOAD_TX2, - SEND_CMD=_SEND_TX2, - ), - ] - - def initialize(self): - """Return the sensor to the default configuration""" - self._reset() - # our mode set skips checking for sleep - self._set_mode(_MODE_CONFIG) - - self._set_baud_rate() - - # intialize TX and RX registers - for idx in range(14): - self._set_register(_TXB0CTRL + idx, 0) - self._set_register(_TXB1CTRL + idx, 0) - self._set_register(_TXB2CTRL + idx, 0) - - self._set_register(_RXB0CTRL, 0) - self._set_register(_RXB1CTRL, 0) - - # # # interrupt mode - # TODO: WHAT IS THIS - self._set_register(_CANINTE, _RX0IF | _RX1IF) - sleep(0.010) - self._mod_register( - _RXB0CTRL, - _RXB_RX_MASK | _RXB_BUKT_MASK, - _RXB_RX_STDEXT | _RXB_BUKT_MASK, - ) - - self._mod_register(_RXB1CTRL, _RXB_RX_MASK, _RXB_RX_STDEXT) - if self.loopback: - new_mode = _MODE_LOOPBACK - elif self.silent: - new_mode = _MODE_LISTENONLY - else: - new_mode = _MODE_NORMAL - - self._set_mode(new_mode) - - def sleep(self): - """Put the MCP2515 to sleep""" - self._set_mode(_MODE_SLEEP) - - def send(self, message_obj): - """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. - - Args: - message (canio.Message): The message to send. Must be a valid `canio.Message` - """ - - # TODO: Timeout - tx_buff = self._get_tx_buffer() # info = addr. - if tx_buff is None: - raise RuntimeError("No transmit buffer available to send") - - return self._write_message(tx_buff, message_obj) - - @property - def unread_message_count(self): - """The number of messages that have been received but not read with `read_message` - - Returns: - int: The unread message count - """ - self._read_from_rx_buffers() - - return len(self._unread_message_queue) - - def read_message(self): - """Read the next available message - - Returns: - `canio.Message`: The next available message or None if one is not available - """ - if self.unread_message_count == 0: - return None - - return self._unread_message_queue.pop(0) - - def _read_rx_buffer(self, read_command): - for i in range(len(self._buffer)): # pylint: disable=consider-using-enumerate - self._buffer[i] = 0 - - # read from buffer - with self._bus_device_obj as spi: - self._buffer[0] = read_command - spi.write_readinto( - self._buffer, # because the reference does similar - self._buffer, - out_start=0, - out_end=1, - in_start=0, - in_end=1, - ) - - spi.readinto(self._buffer, end=15) - ######### Unpack IDs/ set Extended ####### - - raw_ids = unpack_from(">I", self._buffer)[0] - extended, sender_id = self._unload_ids(raw_ids) - ############# Length/RTR Size ######### - dlc = self._buffer[4] - # length is max 8 - message_length = min(8, dlc & 0xF) - - if (dlc & _RTR_MASK) > 0: - frame_obj = RemoteTransmissionRequest( - sender_id, message_length, extended=extended - ) - else: - frame_obj = Message( - sender_id, - data=bytes(self._buffer[5 : 5 + message_length]), - extended=extended, - ) - self._unread_message_queue.append(frame_obj) - - def _read_from_rx_buffers(self): - """Read the next available message into the given `bytearray` - - Args: - msg_buffer (bytearray): The buffer to load the message into - """ - status = self._read_status() - - # TODO: read and store all available messages - if status & 0b1: - self._read_rx_buffer(_READ_RX0) - - if status & 0b10: - self._read_rx_buffer(_READ_RX1) - - def _write_message(self, tx_buffer, message_obj): - - if tx_buffer is None: - raise RuntimeError("No transmit buffer available to send") - if isinstance(message_obj, RemoteTransmissionRequest): - dlc = message_obj.length - else: - dlc = len(message_obj.data) - - if dlc > _MAX_CAN_MSG_LEN: - raise AttributeError("Message/RTR length must be <=%d" % _MAX_CAN_MSG_LEN) - load_command = tx_buffer.LOAD_CMD - - if isinstance(message_obj, RemoteTransmissionRequest): - dlc |= _RTR_MASK - - # get id buffer segment - - self._load_id_buffer(message_obj.id, message_obj.extended) - - # this splits up the id header, dlc (len, rtr status), and message buffer - # TODO: check if we can send in one buffer, in which case `id_buffer` isn't needed - - with self._bus_device_obj as spi: - # send write command for the given buffer - self._buffer[0] = load_command - # spi.write(self._buffer, end=1) - spi.write_readinto( - self._buffer, # because the reference does similar - self._buffer, - out_start=0, - out_end=1, - in_start=0, - in_end=1, - ) - - # send id bytes - spi.write(self._id_buffer, end=4) - - # send DLC - - spi.write(bytearray([dlc])) - # send message bytes, limit to 8? - if isinstance(message_obj, Message): - spi.write(message_obj.data, end=8) - - # send the frame based on the current buffers - self._start_transmit(tx_buffer) - return True - - # TODO: Priority - def _start_transmit(self, tx_buffer): - # - self._buffer[0] = tx_buffer.SEND_CMD - with self._bus_device_obj as spi: - spi.write_readinto( - self._buffer, # because the reference does similar - self._buffer, - out_start=0, - out_end=1, - in_start=0, - in_end=1, - ) - - def _set_filter_register(self, filter_index, mask, extended): - filter_reg_addr = FILTERS[filter_index] - self._write_id_to_register(filter_reg_addr, mask, extended) - - def _set_mask_register(self, mask_index, mask, extended): - mask_reg_addr = MASKS[mask_index] - self._write_id_to_register(mask_reg_addr, mask, extended) - - @staticmethod - def _unload_ids(raw_ids): - """In=> 32-bit int packed with (StdID or ExTID top11 + bot18)+ extid bit - out=> id, extended flag""" - extended = (raw_ids & _TXB_EXIDE_M_16 << 16) > 0 - # std id field is most significant 11 bits of 4 bytes of id registers - top_chunk = raw_ids & EXTID_TOP_11_READ_MASK - if extended: - # get bottom 18 - bottom_chunk = raw_ids & EXTID_BOTTOM_18_MASK - # shift the top chunk back down 3 to start/end at bit 28=29th - top_chunk >>= 3 - sender_id = top_chunk | bottom_chunk - else: - # shift down the 3 [res+extid+res]+18 extid bits - sender_id = top_chunk >> (18 + 3) - return (extended, sender_id) - - def _load_id_buffer(self, can_id, extended=False): - self._id_buffer[0] = 0 - self._id_buffer[1] = 0 - self._id_buffer[2] = 0 - self._id_buffer[3] = 0 - - if extended: - extended_id = can_id - # mask off top 11 - high_11 = extended_id & EXTID_TOP_11_WRITE_MASK - # mask off bottom 18 - low_18 = extended_id & EXTID_BOTTOM_18_MASK - # shift up high piece to fill MSBits and make space for extended flag - high_11 <<= 3 - # or 'em together! - extended_id_shifted = high_11 | low_18 - final_id = extended_id_shifted | EXTID_FLAG_MASK - # set dat FLAG - - else: - std_id = can_id & STDID_BOTTOM_11_MASK # The actual ID? - # shift up to fit all 4 bytes - final_id = std_id << (16 + 5) - - # top = (final_id & EXTID_TOP_11_READ_MASK) >> 21 - # flags = (final_id & (0x7 << 18)) >> 18 - # bottom = final_id & EXTID_BOTTOM_18_MASK - # print( - # "final final_id: 0b{top:011b} {flags:03b} {bottom:018b}".format( - # top=top, flags=flags, bottom=bottom - # ) - # ) - pack_into(">I", self._id_buffer, 0, final_id) - - def _write_id_to_register(self, register, can_id, extended=False): - # load register in to ID buffer - - current_mode = self._mode - self._set_mode(_MODE_CONFIG) - # set the mask in the ID buffer - - self._load_id_buffer(can_id, extended) - - # write with buffer - with self._bus_device_obj as spi: - # send write command for the given bufferf - self._buffer[0] = _WRITE - self._buffer[1] = register - # spi.write(self._buffer, end=1) - spi.write_readinto( - self._buffer, # because the reference does similar - self._buffer, - out_start=0, - out_end=2, - in_start=0, - in_end=2, - ) - - # send id bytes - spi.write(self._id_buffer, end=4) - - self._set_mode(current_mode) - - @property - def _tx_buffers_in_use(self): - # the ref code allows for reserving buffers, but didn't see any way - # to use them. maybe un-reserve then use? - # TODO: this should return a tuple of busy states - # byte status = mcp2515_readStatus() & MCP_STAT_TX_PENDING_MASK - status = self._read_status() - self._dbg("Status byte:", "{:#010b}".format(status)) - return ( - bool(status & _STAT_TX0_PENDING), - bool(status & _STAT_TX1_PENDING), - bool(status & _STAT_TX2_PENDING), - ) - - def _get_tx_buffer(self): - """Get the address of the next available tx buffer and unset - its interrupt bit in _CANINTF""" - # check all buffers by looking for match on - txs_busy = self._tx_buffers_in_use - if all(txs_busy): - self._dbg("none available!") - return None - buffer_index = txs_busy.index(False) # => self._tx_buffers - tx_buffer = self._tx_buffers[buffer_index] - - self._mod_register(_CANINTF, tx_buffer.INT_FLAG_MASK, 0) - return tx_buffer - - def _set_baud_rate(self): - # ******* set baud rate *********** - if self._crystal_freq not in _BAUD_RATES: - raise ValueError( - f"Incorrect crystal frequency - must be one of: {tuple(_BAUD_RATES.keys())}" - ) - - cnf1, cnf2, cnf3 = _BAUD_RATES[self._crystal_freq][self.baudrate] - - self._set_register(_CNF1, cnf1) - self._set_register(_CNF2, cnf2) - self._set_register(_CNF3, cnf3) - sleep(0.010) - - def _reset(self): - self._buffer[0] = _RESET - with self._bus_device_obj as spi: - spi.write(self._buffer, end=1) - sleep(0.010) - - def _set_mode(self, mode): - stat_reg = self._read_register(_CANSTAT) - current_mode = stat_reg & _MODE_MASK - - if current_mode == mode: - return - self._timer.rewind_to(5) - while not self._timer.expired: - - new_mode_set = self._request_new_mode(mode) - if new_mode_set: - self._mode = mode - return - - raise RuntimeError("Unable to change mode") - - def _request_new_mode(self, mode): - self._timer.rewind_to(0.200) - while not self._timer.expired: - # Request new mode - # This is inside the loop as sometimes requesting the new mode once doesn't work - # (usually when attempting to sleep) - self._mod_register(_CANCTRL, _MODE_MASK, mode) - - status = self._read_register(_CANSTAT) - if (status & _MODE_MASK) == mode: - return True - - raise RuntimeError("Timeout setting Mode") - - def _mod_register(self, register_addr, mask, new_value): - """There appears to be an interface on the MCP2515 that allows for - setting a register using a mask""" - self._buffer[0] = _BITMOD - self._buffer[1] = register_addr - self._buffer[2] = mask - self._buffer[3] = new_value - with self._bus_device_obj as spi: - spi.write(self._buffer, end=4) - - def _read_register(self, regsiter_addr): - self._buffer[0] = _READ - self._buffer[1] = regsiter_addr - - with self._bus_device_obj as spi: - spi.write(self._buffer, end=2) - self._buffer[0] = 0 - spi.write_readinto( - self._buffer, self._buffer, out_start=0, out_end=1, in_start=0, in_end=1 - ) - - return self._buffer[0] - - def _read_status(self): - self._buffer[0] = _READ_STATUS - with self._bus_device_obj as spi: - spi.write(self._buffer, end=1) - spi.readinto(self._buffer, start=0, end=1) - return self._buffer[0] - - def _set_register(self, regsiter_addr, register_value): - self._buffer[0] = _WRITE - self._buffer[1] = regsiter_addr - self._buffer[2] = register_value - with self._bus_device_obj as spi: - spi.write(self._buffer, end=3) - - def _get_bus_status(self): - """Get the status flags that report the state of the bus""" - bus_flags = self._read_register(_EFLG) - - flags = [] - for idx in range(8): - bit_mask = 1 << idx - flags.append((bus_flags & bit_mask) > 0) - ( # pylint:disable=unbalanced-tuple-unpacking - error_warn, - _rx_error_warn, - _tx_err_warn, - rx_error_passive, - tx_error_passive, - buss_off, - self._rx0_overflow, - self._rx1_overflow, - ) = flags - if self._rx0_overflow or self._rx0_overflow: - self._mod_register( - _EFLG, 0xC0, 0 - ) # clear overflow bits now that we've recorded them - - if buss_off: - self._bus_state = BusState.BUS_OFF - elif tx_error_passive or rx_error_passive: - self._bus_state = BusState.ERROR_PASSIVE - elif error_warn: - self._bus_state = BusState.ERROR_WARNING - else: - self._bus_state = BusState.ERROR_ACTIVE - - def _create_mask(self, match): - mask = match.mask - if mask == 0: - if match.extended: - mask = EXTID_BOTTOM_29_MASK - else: - mask = STDID_BOTTOM_11_MASK - - masks_used = len(self._masks_in_use) - if masks_used < len(MASKS): - next_mask_index = masks_used - - self._set_mask_register(next_mask_index, mask, match.extended) - self._masks_in_use.append(MASKS[next_mask_index]) - return next_mask_index - - raise RuntimeError("No Masks Available") - - def _create_filter(self, match, mask_index): - - next_filter_index = len(self._filters_in_use[mask_index]) - if next_filter_index == len(FILTERS[mask_index]): - raise RuntimeError("No Filters Available") - - filter_register = FILTERS[mask_index][next_filter_index] - - self._write_id_to_register(filter_register, match.address, match.extended) - self._filters_in_use[mask_index].append(filter_register) - - def deinit_filtering_registers(self): - """Clears the Receive Mask and Filter Registers""" - - for mask_index, mask_reg in enumerate(MASKS): - self._set_register(mask_reg, 0) - - for filter_reg in FILTERS[mask_index]: - self._set_register(filter_reg, 0) - self._masks_in_use = [] - self._filters_in_use = [[], []] - - ######## CANIO API METHODS ############# - @property - def baudrate(self): - """The baud rate (read-only)""" - return self._baudrate - - @property - def transmit_error_count(self): - """ 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.""" - return self._read_register(_TEC) - - @property - def receive_error_count(self): - """ 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.""" - return self._read_register(_REC) - - @property - def error_warning_state_count(self): - """Not supported by hardware. Raises an `AttributeError` if called""" - raise AttributeError("`error_warning_state_count` not supported by hardware") - - @property - def error_passive_state_count(self): - """Not supported by hardware. Raises an `AttributeError` if called""" - raise AttributeError("`error_passive_state_count` not supported by hardware") - - @property - def bus_off_state_count(self): - """Not supported by hardware. Raises an `AttributeError` if called""" - raise AttributeError("`bus_off_state_count` not supported by hardware") - - @property - def state(self): # State - """The current state of the bus. (read-only)""" - self._get_bus_status() - return self._bus_state - - @property - def loopback(self): # bool - """True if the device was created in loopback mode, False otherwise. (read-only)""" - return self._loopback - - @property - def silent(self): # bool - """True if the device was created in silent mode, False otherwise. (read-only)""" - return self._silent - - def restart(self): - """If the device is in the bus off state, restart it.""" - self.initialize() - - def listen(self, matches=None, *, timeout: float = 10): - """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()`` will block. - - Args: - match (Optional[Sequence[Match]], optional): [description]. Defaults to None. - timeout (float, optional): [description]. Defaults to 10. - - Returns: - Listener: [description] - """ - if matches is None: - matches = [] - elif self.silent and not self.loopback: - raise AttributeError( - "Hardware does not support setting `matches` in when\ - `silent`==`True` and `loopback` == `False`" - ) - - for match in matches: - self._dbg("match:", match) - mask_index_used = self._create_mask(match) - self._create_filter(match, mask_index=mask_index_used) - - used_masks = len(self._masks_in_use) - # if matches were made and there are unused masks - # set the unused masks to prevent them from leaking packets - if len(matches) > 0 and used_masks < len(MASKS): - next_mask_index = used_masks - for idx in range(next_mask_index, len(MASKS)): - print("using unused mask index:", idx) - self._create_mask(matches[-1]) - - return Listener(self, timeout) - - def deinit(self): - """Deinitialize this object, freeing its hardware resources""" - self._cs_pin.deinit() - - def __enter__(self): - """Returns self, to allow the object to be used in a The with statement statement for \ - resource control""" - return self - - def __exit__(self, unused1, unused2, unused3): - """Calls deinit()""" - self.deinit() - - ##################### End canio API ################ - - def _dbg(self, *args, **kwargs): - if self._debug: - print("DBG::\t\t", *args, **kwargs) diff --git a/Batt_Board/lib/adafruit_mcp2515/canio/__init__.py b/Batt_Board/lib/adafruit_mcp2515/canio/__init__.py deleted file mode 100644 index 41ff222..0000000 --- a/Batt_Board/lib/adafruit_mcp2515/canio/__init__.py +++ /dev/null @@ -1,220 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2020 Bryan Siepert for Adafruit Industries -# -# SPDX-License-Identifier: MIT -"""Python implementation of the CircuitPython core `canio` API""" -# pylint:disable=too-few-public-methods, invalid-name, redefined-builtin -import time -from ..timer import Timer - - -class Message: - """A class representing a CANbus data frame - - :param int id: The numeric ID of the message - :param bytes data: The content of the message, from 0 to 8 bytes of data - :param bool extended: True if the message has an extended identifier, - False if it has a standard identifier - """ - - # pylint:disable=too-many-arguments,invalid-name,redefined-builtin - def __init__(self, id, data, extended=False): - self._data = None - self.id = id - self.data = data - self.extended = extended - - id: int - """The numeric ID of the message""" - - extended: bool - """Indicates whether the the message has an extended identifier""" - - @property - def data(self): - """The content of the message""" - return self._data - - @data.setter - def data(self, new_data): - if (new_data is None) or (not (type(new_data) in [bytes, bytearray])): - - raise AttributeError( - "non-RTR canio.Message must have a `data` argument of type `bytes`" - ) - if len(new_data) > 8: - raise AttributeError( - "`canio.Message` object data must be of length 8 or less" - ) - # self.rtr = False - # self._data = new_data - self._data = bytearray(new_data) - - -class RemoteTransmissionRequest: - """A class representing a CANbus remote frame - - :param int id: The numeric ID of the message - :param length int: The length of the requested message - :param bool extended: True if the message has an extended identifier, - False if it has a standard identifier - """ - - def __init__(self, id: int, length: int, *, extended: bool = False): - self.id = id - self.length = length - self.extended = extended - - id: int - """The numeric ID of the message""" - - extended: bool - """Indicates whether the the message has an extended identifier""" - - length: int - """The length of the requested message, from 0 to 8""" - - -# Replace the above implementation with core canio implementation if it is available -try: - from canio import Message, RemoteTransmissionRequest -except ImportError: - pass - - -class Listener: - """Listens for a CAN message - - canio.Listener is not constructed directly, but instead by calling the - ``listen`` method of a canio.CAN object. - """ - - def __init__(self, can_bus_obj, timeout=1.0): - self._timer = Timer() - self._can_bus_obj = can_bus_obj - self._timeout = None - self.timeout = timeout - - @property - def timeout(self): - """The maximum amount of time in seconds that ``read`` or ``readinto`` - will wait before giving up""" - return self._timeout - - @timeout.setter - def timeout(self, timeout): - self._timeout = float(timeout) - - def receive(self): - """Receives a message. If after waiting up to self.timeout seconds if no message is\ - received, None is returned. Otherwise, a Message is returned.""" - if self._can_bus_obj is None: - raise ValueError( - "Object has been deinitialized and can no longer be used. Create a new object." - ) - self._timer.rewind_to(self.timeout) - while not self._timer.expired: - if self._can_bus_obj.unread_message_count == 0: - continue - return self._can_bus_obj.read_message() - return None - - def in_waiting(self): - """Returns the number of messages waiting""" - if self._can_bus_obj is None: - raise ValueError( - "Object has been deinitialized and can no longer be used. Create a new object." - ) - return self._can_bus_obj.unread_message_count - - def __iter__(self): - """Returns self""" - if self._can_bus_obj is None: - raise ValueError( - "Object has been deinitialized and can no longer be used. Create a new object." - ) - return self - - def __next__(self): - """Receives a message, after waiting up to self.timeout seconds""" - if self._can_bus_obj is None: - raise ValueError( - "Object has been deinitialized and can no longer be used. Create a new object." - ) - return self.receive() - - def deinit(self): - """Deinitialize this object, freeing its hardware resources""" - self._can_bus_obj.deinit_filtering_registers() - self._timer = None - self._can_bus_obj = None - self._timeout = None - - def __enter__(self): - """Returns self, to allow the object to be used in a The with statement statement for\ - resource control""" - if self._can_bus_obj is None: - raise ValueError( - "Object has been deinitialized and can no longer be used. Create a new object." - ) - return self - - def __exit__(self, unused1, unused2, unused3): - """Calls deinit()""" - if not self._can_bus_obj: - raise ValueError( - "Object has been deinitialized and can no longer be used. Create a new object." - ) - self.deinit() - - -class BusState: - """The state of the CAN bus""" - - ERROR_ACTIVE = 0 - """The bus is in the normal (active) state""" - - ERROR_WARNING = 1 - """ 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 = 2 - """ 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 = 3 - """ 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 Match: - """A class representing an ID pattern to match against""" - - def __init__(self, address: int, *, mask: int = 0, extended: bool = False): - """Describe CAN bus messages to match - - Construct a Match with the given properties. - - If mask is nonzero, then the filter is for any sender which matches all the nonzero bits in\ - mask. Otherwise, it matches exactly the given address. If extended is true then only\ - extended addresses are matched, otherwise only standard addresses are matched. - - Args: - address (int): he address to match - mask (int, optional): The optional mask of addresses to match. Defaults to 0. - extended (bool, optional): True to match extended addresses, False to match standard\ - addresses. - - Returns: - [type]: [description] - """ - self.address = address - self.mask = mask - self.extended = extended diff --git a/Batt_Board/lib/adafruit_mcp2515/timer.py b/Batt_Board/lib/adafruit_mcp2515/timer.py deleted file mode 100644 index 814fb0c..0000000 --- a/Batt_Board/lib/adafruit_mcp2515/timer.py +++ /dev/null @@ -1,29 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2020 Bryan Siepert for Adafruit Industries -# -# SPDX-License-Identifier: MIT -"""Provides a simple timer class; see `Timer`""" -from time import monotonic - - -class Timer: - """A reusable class to track timeouts, like an egg timer""" - - def __init__(self, timeout=0.0): - self._timeout = None - self._start_time = None - if timeout: - self.rewind_to(timeout) - - @property - def expired(self): - """Returns the expiration status of the timer - - Returns: - bool: True if more than `timeout` seconds has past since it was set - """ - return (monotonic() - self._start_time) > self._timeout - - def rewind_to(self, new_timeout): - """Re-wind the timer to a new timeout and start ticking""" - self._timeout = float(new_timeout) - self._start_time = monotonic() diff --git a/Batt_Board/lib/adafruit_mcp9808.py b/Batt_Board/lib/adafruit_mcp9808.py deleted file mode 100644 index c6ee56d..0000000 --- a/Batt_Board/lib/adafruit_mcp9808.py +++ /dev/null @@ -1,266 +0,0 @@ -# SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries -# SPDX-FileCopyrightText: 2021 Jose David Montoya -# SPDX-License-Identifier: MIT - -""" -`adafruit_mcp9808` -==================================================== - -CircuitPython library to support MCP9808 high accuracy temperature sensor. - -* Author(s): Scott Shawcroft, Jose David M. - -Implementation Notes --------------------- - -**Hardware:** - -* `Adafruit MCP9808 High Accuracy I2C Temperature Sensor Breakout - `_ (Product ID: 1782) - -**Software and Dependencies:** - -* Adafruit CircuitPython firmware for the supported boards: - https://github.com/adafruit/circuitpython/releases - -* Adafruit's Bus Device library: - https://github.com/adafruit/Adafruit_CircuitPython_BusDevice - -* Adafruit's Register library: - https://github.com/adafruit/Adafruit_CircuitPython_Register - - -**Notes:** - -#. Datasheet: http://www.adafruit.com/datasheets/MCP9808.pdf - -""" - -from micropython import const -from adafruit_bus_device.i2c_device import I2CDevice -from adafruit_register.i2c_bits import RWBits -from adafruit_register.i2c_bit import ROBit - -try: - import typing # pylint: disable=unused-import - from typing_extensions import Literal - from busio import I2C -except ImportError: - pass - -__version__ = "0.0.0+auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MCP9808.git" - - -_MCP9808_DEFAULT_ADDRESS = const(0x18) -_MCP9808_DEVICE_ID = const(0x54) -_MCP9808_REG_CONFIGURATION = const(0x01) -_MCP9808_REG_UPPER_TEMP = const(0x02) -_MCP9808_REG_LOWER_TEMP = const(0x03) -_MCP9808_REG_CRITICAL_TEMP = const(0x04) -_MCP9808_REG__TEMP = const(0x05) -_MCP9808_REG_MANUFACTURER_ID = const(0x06) -_MCP9808_REG_DEVICE_ID = const(0x07) -_MCP9808_REG_RESOLUTION = const(0x08) - -# Resolution settings - -_MCP9808_RESOLUTION_HALF_C = const(0x0) -_MCP9808_RESOLUTION_QUARTER_C = const(0x1) -_MCP9808_RESOLUTION_EIGHTH_C = const(0x2) -_MCP9808_RESOLUTION_SIXTEENTH_C = const(0x3) - - -class MCP9808: - """Interface to the MCP9808 temperature sensor. - - :param ~busio.I2C i2c_bus: The I2C bus the MCP9808 is connected to. - :param int address: The I2C address of the device. Defaults to :const:`0x18` - - **MCP9808 Settings** - You could set the MCP9808 with different temperature limits and compare them with the - ambient temperature Ta - - - above_critical: this value will be set to `True` when Ta is above this limit - - above_upper: this value will be set to `True` when Ta is above this limit - - below_lower: this value will be set to `True` when Ta is below this limit - - To get this value, you will need to read the temperature, and then access the attribute - - - **Quickstart: Importing and using the MCP9808** - - Here is an example of using the :class:`MCP9808` class. - First you will need to import the libraries to use the sensor - - .. code-block:: python - - import board - import adafruit_mcp9808 - - Once this is done you can define your `board.I2C` object and define your sensor object - - .. code-block:: python - - i2c = board.I2C() # uses board.SCL and board.SDA - mcp = adafruit_mcp9808.MCP9808(i2c_bus) - - Now you have access to the change in temperature using the - :attr:`temperature` attribute. This temperature is in Celsius. - - .. code-block:: python - - temperature = mcp.temperature - - """ - - _MCP9808_REG_RESOLUTION_SET = RWBits(2, 0x08, 0, register_width=2) - above_critical = ROBit(_MCP9808_REG__TEMP, 7, register_width=1) - """True when the temperature is above the currently - set critical temperature. False Otherwise""" - - above_upper = ROBit(_MCP9808_REG__TEMP, 6, register_width=1) - """True when the temperature is above the currently - set high temperature. False Otherwise""" - - below_lower = ROBit(_MCP9808_REG__TEMP, 5, register_width=1) - """True when the temperature is below the currently - set lower temperature. False Otherwise""" - - def __init__(self, i2c_bus: I2C, address: int = _MCP9808_DEFAULT_ADDRESS) -> None: - self.i2c_device = I2CDevice(i2c_bus, address) - - # Verify the manufacturer and device ids to ensure we are talking to - # what we expect. - self.buf = bytearray(3) - self.buf[0] = _MCP9808_REG_MANUFACTURER_ID - with self.i2c_device as i2c: - i2c.write_then_readinto(self.buf, self.buf, out_end=1, in_start=1) - - ok = self.buf[2] == _MCP9808_DEVICE_ID and self.buf[1] == 0 - - # Check device id. - self.buf[0] = _MCP9808_REG_DEVICE_ID - with self.i2c_device as i2c: - i2c.write_then_readinto(self.buf, self.buf, out_end=1, in_start=1) - - if not ok or self.buf[1] != 0x04: - raise ValueError( - "Unable to find MCP9808 at i2c address " + str(hex(address)) - ) - - @property - def temperature(self) -> float: - """Temperature in Celsius. Read-only.""" - self.buf[0] = _MCP9808_REG__TEMP - with self.i2c_device as i2c: - i2c.write_then_readinto(self.buf, self.buf, out_end=1, in_start=1) - - return self._temp_conv() - - def _temp_conv(self) -> float: - """Internal function to convert temperature given by the sensor""" - # Clear flags from the value - self.buf[1] = self.buf[1] & 0x1F - if self.buf[1] & 0x10 == 0x10: - self.buf[1] = self.buf[1] & 0x0F - return (self.buf[1] * 16 + self.buf[2] / 16.0) - 256 - return self.buf[1] * 16 + self.buf[2] / 16.0 - - def _limit_temperatures( - self, temp: int, t_address: Literal[0x02, 0x03, 0x04] = 0x02 - ) -> None: - """Internal function to setup limit temperature - - :param int temp: temperature limit - :param int t_address: register address for the temperature limit - 0x02 : Upper Limit - 0x03 : Lower Limit - 0x04 : Critical Limit - """ - - if temp < 0: - negative = True - temp = abs(temp) - else: - negative = False - - self.buf[0] = t_address - - self.buf[1] = temp >> 4 - if negative: - self.buf[1] = self.buf[1] | 0x10 - - self.buf[2] = (temp & 0x0F) << 4 - - with self.i2c_device as i2c: - i2c.write(self.buf) - - def _get_temperature(self, address: Literal[0x02, 0x03, 0x04]) -> float: - self.buf[0] = address - with self.i2c_device as i2c: - i2c.write_then_readinto(self.buf, self.buf, out_end=1, in_start=1) - - return self._temp_conv() - - def _set_temperature(self, temp: int, address: Literal[0x02, 0x03, 0x04]) -> None: - self._limit_temperatures(temp, address) - - @property - def upper_temperature(self) -> float: - """Upper alarm temperature in Celsius""" - - return self._get_temperature(_MCP9808_REG_UPPER_TEMP) - - @upper_temperature.setter - def upper_temperature(self, temp: int) -> None: - """Setup Upper temperature""" - - self._limit_temperatures(temp, _MCP9808_REG_UPPER_TEMP) - - @property - def lower_temperature(self) -> float: - """Lower alarm temperature in Celsius""" - - return self._get_temperature(_MCP9808_REG_LOWER_TEMP) - - @lower_temperature.setter - def lower_temperature(self, temp: int) -> None: - """Setup Lower temperature""" - - self._limit_temperatures(temp, _MCP9808_REG_LOWER_TEMP) - - @property - def critical_temperature(self) -> float: - """Critical alarm temperature in Celsius""" - - return self._get_temperature(_MCP9808_REG_CRITICAL_TEMP) - - @critical_temperature.setter - def critical_temperature(self, temp: int) -> None: - """Setup Critical temperature""" - - self._limit_temperatures(temp, _MCP9808_REG_CRITICAL_TEMP) - - @property - def resolution(self) -> Literal[0, 1, 2, 3]: - """Temperature Resolution in Celsius - - ======= ============ ============== - Value Resolution Reading Time - ======= ============ ============== - 0 0.5°C 30 ms - 1 0.25°C 65 ms - 2 0.125°C 130 ms - 3 0.0625°C 250 ms - ======= ============ ============== - - """ - - return self._MCP9808_REG_RESOLUTION_SET - - @resolution.setter - def resolution(self, resol_value: Literal[0, 1, 2, 3] = 3) -> None: - """Setup Critical temperature""" - - self._MCP9808_REG_RESOLUTION_SET = resol_value # pylint: disable=invalid-name diff --git a/Batt_Board/lib/adafruit_pca9685.py b/Batt_Board/lib/adafruit_pca9685.py deleted file mode 100644 index 789c627..0000000 --- a/Batt_Board/lib/adafruit_pca9685.py +++ /dev/null @@ -1,189 +0,0 @@ -# SPDX-FileCopyrightText: 2016 Radomir Dopieralski for Adafruit Industries -# SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_pca9685` -==================================================== - -Driver for the PCA9685 PWM control IC. Its commonly used to control servos, leds and motors. - -.. seealso:: The `Adafruit CircuitPython Motor library - `_ can be used to control the PWM - outputs for specific uses instead of generic duty_cycle adjustments. - -* Author(s): Scott Shawcroft - -Implementation Notes --------------------- - -**Hardware:** - -* Adafruit `16-Channel 12-bit PWM/Servo Driver - I2C interface - PCA9685 - `_ (Product ID: 815) - -**Software and Dependencies:** - -* Adafruit CircuitPython firmware for the ESP8622 and M0-based boards: - https://github.com/adafruit/circuitpython/releases -* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice -* Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register -""" - -__version__ = "0.0.0+auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_PCA9685.git" - -import time - -from adafruit_register.i2c_struct import UnaryStruct -from adafruit_register.i2c_struct_array import StructArray -from adafruit_bus_device import i2c_device - -try: - from typing import Optional, Type - from types import TracebackType - from busio import I2C -except ImportError: - pass - - -class PWMChannel: - """A single PCA9685 channel that matches the :py:class:`~pwmio.PWMOut` API. - - :param PCA9685 pca: The PCA9685 object - :param int index: The index of the channel - """ - - def __init__(self, pca: "PCA9685", index: int): - self._pca = pca - self._index = index - - @property - def frequency(self) -> float: - """The overall PWM frequency in Hertz (read-only). - A PWMChannel's frequency cannot be set individually. - All channels share a common frequency, set by PCA9685.frequency.""" - return self._pca.frequency - - @frequency.setter - def frequency(self, _): - raise NotImplementedError("frequency cannot be set on individual channels") - - @property - def duty_cycle(self) -> 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. - """ - pwm = self._pca.pwm_regs[self._index] - if pwm[0] == 0x1000: - return 0xFFFF - return pwm[1] << 4 - - @duty_cycle.setter - def duty_cycle(self, value: int) -> None: - if not 0 <= value <= 0xFFFF: - raise ValueError(f"Out of range: value {value} not 0 <= value <= 65,535") - - if value == 0xFFFF: - self._pca.pwm_regs[self._index] = (0x1000, 0) - else: - # Shift our value by four because the PCA9685 is only 12 bits but our value is 16 - value = (value + 1) >> 4 - self._pca.pwm_regs[self._index] = (0, value) - - -class PCAChannels: # pylint: disable=too-few-public-methods - """Lazily creates and caches channel objects as needed. Treat it like a sequence. - - :param PCA9685 pca: The PCA9685 object - """ - - def __init__(self, pca: "PCA9685") -> None: - self._pca = pca - self._channels = [None] * len(self) - - def __len__(self) -> int: - return 16 - - def __getitem__(self, index: int) -> PWMChannel: - if not self._channels[index]: - self._channels[index] = PWMChannel(self._pca, index) - return self._channels[index] - - -class PCA9685: - """ - Initialise the PCA9685 chip at ``address`` on ``i2c_bus``. - - The internal reference clock is 25mhz but may vary slightly with environmental conditions and - manufacturing variances. Providing a more precise ``reference_clock_speed`` can improve the - accuracy of the frequency and duty_cycle computations. See the ``calibration.py`` example for - how to derive this value by measuring the resulting pulse widths. - - :param ~busio.I2C i2c_bus: The I2C bus which the PCA9685 is connected to. - :param int address: The I2C address of the PCA9685. - :param int reference_clock_speed: The frequency of the internal reference clock in Hertz. - """ - - # Registers: - mode1_reg = UnaryStruct(0x00, " None: - self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) - self.channels = PCAChannels(self) - """Sequence of 16 `PWMChannel` objects. One for each channel.""" - self.reference_clock_speed = reference_clock_speed - """The reference clock speed in Hz.""" - self.reset() - - def reset(self) -> None: - """Reset the chip.""" - self.mode1_reg = 0x00 # Mode1 - - @property - def frequency(self) -> float: - """The overall PWM frequency in Hertz.""" - prescale_result = self.prescale_reg - if prescale_result < 3: - raise ValueError( - "The device pre_scale register (0xFE) was not read or returned a value < 3" - ) - return self.reference_clock_speed / 4096 / prescale_result - - @frequency.setter - def frequency(self, freq: float) -> None: - prescale = int(self.reference_clock_speed / 4096.0 / freq + 0.5) - if prescale < 3: - raise ValueError("PCA9685 cannot output at the given frequency") - old_mode = self.mode1_reg # Mode 1 - self.mode1_reg = (old_mode & 0x7F) | 0x10 # Mode 1, sleep - self.prescale_reg = prescale # Prescale - self.mode1_reg = old_mode # Mode 1 - time.sleep(0.005) - # Mode 1, autoincrement on, fix to stop pca9685 from accepting commands at all addresses - self.mode1_reg = old_mode | 0xA0 - - def __enter__(self) -> "PCA9685": - return self - - def __exit__( - self, - exception_type: Optional[Type[type]], - exception_value: Optional[BaseException], - traceback: Optional[TracebackType], - ) -> None: - self.deinit() - - def deinit(self) -> None: - """Stop using the pca9685.""" - self.reset() diff --git a/Batt_Board/lib/adafruit_pct2075.py b/Batt_Board/lib/adafruit_pct2075.py deleted file mode 100644 index 84ad597..0000000 --- a/Batt_Board/lib/adafruit_pct2075.py +++ /dev/null @@ -1,197 +0,0 @@ -# SPDX-FileCopyrightText: 2019 Bryan Siepert for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_pct2075` -================================================================================ - -CircuitPython library for the NXP PCT2075 Digital Temperature Sensor - - -* Author(s): Bryan Siepert - -Implementation Notes --------------------- - -**Hardware:** - -* `Adafruit PCT2075 Temperature Sensor Breakout - `_ (Product ID: 4369) - -**Software and Dependencies:** - -* Adafruit CircuitPython firmware for the supported boards: - https://circuitpython.org/downloads - -* Adafruit's Bus Device library: - https://github.com/adafruit/Adafruit_CircuitPython_BusDevice - -* Adafruit's Register library: - https://github.com/adafruit/Adafruit_CircuitPython_Register - -""" - -from adafruit_register.i2c_struct import ROUnaryStruct, UnaryStruct -from adafruit_register.i2c_bits import RWBits -from adafruit_register.i2c_bit import RWBit -import adafruit_bus_device.i2c_device as i2cdevice - -try: - import typing # pylint: disable=unused-import - from busio import I2C -except ImportError: - pass - -__version__ = "0.0.0+auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_PCT2075.git" -# pylint: disable=too-few-public-methods -PCT2075_DEFAULT_ADDRESS = 0x37 # Address is configured with pins A0-A2 - -PCT2075_REGISTER_TEMP = 0 # Temperature register (read-only) -PCT2075_REGISTER_CONFIG = 1 # Configuration register -PCT2075_REGISTER_THYST = 2 # Hysterisis register -PCT2075_REGISTER_TOS = 3 # OS register -PCT2075_REGISTER_TIDLE = 4 # Measurement idle time register - - -class Mode: - """Options for `Mode`""" - - INTERRUPT = 1 - COMPARITOR = 0 - - -class FaultCount: - """Options for `faults_to_alert`""" - - FAULT_1 = 0 - FAULT_2 = 1 - FAULT_4 = 2 - FAULT_6 = 3 - - -# pylint: enable=too-few-public-methods - - -class PCT2075: - """Driver for the PCT2075 Digital Temperature Sensor and Thermal Watchdog. - - :param ~busio.I2C i2c_bus: The I2C bus the PCT2075 is connected to. - :param int address: The I2C device address. Default is :const:`0x37` - - **Quickstart: Importing and using the PCT2075 temperature sensor** - - Here is an example of using the :class:`PCT2075` class. - First you will need to import the libraries to use the sensor - - .. code-block:: python - - import board - import adafruit_pct2075 - - Once this is done you can define your `board.I2C` object and define your sensor object - - .. code-block:: python - - i2c = board.I2C() # uses board.SCL and board.SDA - pct = adafruit_pct2075.PCT2075(i2c) - - Now you have access to the temperature using the attribute :attr:`temperature`. - - .. code-block:: python - - temperature = pct.temperature - - """ - - def __init__(self, i2c_bus: I2C, address: int = PCT2075_DEFAULT_ADDRESS) -> None: - self.i2c_device = i2cdevice.I2CDevice(i2c_bus, address) - - _temperature = ROUnaryStruct(PCT2075_REGISTER_TEMP, ">h") - mode = RWBit(PCT2075_REGISTER_CONFIG, 1, register_width=1) - """Sets the alert mode. In comparator mode, the sensor acts like a thermostat and will activate - the INT pin according to `high_temp_active_high` when an alert is triggered. The INT pin will be - deactivated when the temperature falls below :attr:`temperature_hysteresis`. - In interrupt mode the INT pin is activated once when a temperature fault - is detected, and once more when the temperature falls below - :attr:`temperature_hysteresis`. In interrupt mode, the alert is cleared by - reading a property""" - - shutdown = RWBit(PCT2075_REGISTER_CONFIG, 0, 1) - """Set to True to turn off the temperature measurement circuitry in the sensor. While shut down - the configurations properties can still be read or written but the temperature will not be - measured""" - _fault_queue_length = RWBits(2, PCT2075_REGISTER_CONFIG, 3, register_width=1) - _high_temperature_threshold = UnaryStruct(PCT2075_REGISTER_TOS, ">h") - _temp_hysteresis = UnaryStruct(PCT2075_REGISTER_THYST, ">h") - _idle_time = RWBits(5, PCT2075_REGISTER_TIDLE, 0, register_width=1) - high_temp_active_high = RWBit(PCT2075_REGISTER_CONFIG, 2, register_width=1) - """Sets the alert polarity. When False the INT pin will be tied to ground when an alert is - triggered. If set to True it will be disconnected from ground when an alert is triggered.""" - - @property - def temperature(self) -> float: - """Returns the current temperature in degrees Celsius. - Resolution is 0.125 degrees Celsius""" - return (self._temperature >> 5) * 0.125 - - @property - def high_temperature_threshold(self) -> float: - """The temperature in degrees celsius that will trigger an alert on the INT pin if it is - exceeded. Resolution is 0.5 degrees Celsius""" - return (self._high_temperature_threshold >> 7) * 0.5 - - @high_temperature_threshold.setter - def high_temperature_threshold(self, value: float) -> None: - self._high_temperature_threshold = int(value * 2) << 7 - - @property - def temperature_hysteresis(self) -> float: - """The temperature hysteresis value defines the bottom - of the temperature range in degrees Celsius in which - the temperature is still considered high. - :attr:`temperature_hysteresis` must be lower than - :attr:`high_temperature_threshold`. - Resolution is 0.5 degrees Celsius - """ - return (self._temp_hysteresis >> 7) * 0.5 - - @temperature_hysteresis.setter - def temperature_hysteresis(self, value: float) -> None: - if value >= self.high_temperature_threshold: - raise ValueError( - "temperature_hysteresis must be less than high_temperature_threshold" - ) - self._temp_hysteresis = int(value * 2) << 7 - - @property - def faults_to_alert(self) -> int: - """The number of consecutive high temperature faults required to raise an alert. An fault - is tripped each time the sensor measures the temperature to be greater than - :attr:`high_temperature_threshold`. The rate at which the sensor measures the temperature - is defined by :attr:`delay_between_measurements`. - """ - - return self._fault_queue_length - - @faults_to_alert.setter - def faults_to_alert(self, value: int) -> None: - if value > 4 or value < 1: - raise ValueError("faults_to_alert must be an adafruit_pct2075.FaultCount") - self._fault_queue_length = value - - @property - def delay_between_measurements(self) -> int: - """The amount of time between measurements made by the sensor in milliseconds. The value - must be between 100 and 3100 and a multiple of 100""" - return self._idle_time * 100 - - @delay_between_measurements.setter - def delay_between_measurements(self, value: int) -> None: - if value > 3100 or value < 100 or value % 100 > 0: - raise AttributeError( - """"delay_between_measurements must be >= 100 or <= 3100\ - and a multiple of 100""" - ) - self._idle_time = int(value / 100) diff --git a/Batt_Board/lib/adafruit_register/__init__.py b/Batt_Board/lib/adafruit_register/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/Batt_Board/lib/adafruit_register/i2c_bcd_alarm.mpy b/Batt_Board/lib/adafruit_register/i2c_bcd_alarm.mpy deleted file mode 100644 index 882b011cd50c5d123ea1b7232c5f6ab8f898ec49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1740 zcmZ8fOLNm!6uwGgV^R}tWFri5LC6n`No+7CB<+I4mcyfY*>N1qzzA8siA7_{m1NR% zQ6v){Wz$7xy6!)yNkZDN>yQ9p+eJx-wkx{pFin^a%U)S=X4)Bz?m6H6&bjBD?q#qTtO&u{4B4OBb9lu-+4OHA35Cz9GX~00FG5OQI_~w z;Kpv+ z0)D+Rua#tpvmzqJ28Am&I4XcD5@e}3I}c!4l&TuSma`;R6@0cAdC_#|5c(9K9c5Y4 zO4tY`lI)<_Ko*yr*=Y?xv$0q~luS#MV%P&ds>G|D^@IwXh3fP)F3f8tEb{Z5TH+N{ z1c!HhjaM|#!ErN4<2c|pr)AX(f^!OPozqH4!qeo7qNK^7L#}EZl8OL|5~ntTDOFRd zg2pwbi8HBDCoea*=EC-!^Jd=vT4*VQ5=T{XpR+E0Xh6&MaC92 zlu4y0Q=`c|mjxrI28RbTBV2N9WPEHiHJatvOA~{M;S>wVT^^{VqCID8F1on@9A7Ld zNL7JB*yX}a+(F?c76t)hO17OZX<9}7C>os+wNkYZ5#(~TxkAyw<}M{IRmUw=o8yn* z7|?FF?P-c+v+u}evsp6Q>^rjB9J`v^9J`|1{5gKMis*7x)#w7ERrFaENdltfX?hk( zGg^t(=44IT zP=PH}$Ootsdb+EXYQ5O6!!~Gx_Hb+09&a1f=Cx5aubr~zb?68aU3S(Ffnn4pbp1cC>m#6aQt+aU=GC2P@Y5j6yx*z{QgkTe|LR}Wgi=Vt}mL$ zoAt%Iha%&T<3!za@KN1$P|q#ZJ)Y&d=TKVs_R9C2xx=xuy?QB_V;CmTlk!CvrpGtR zM1A87GZy!yeQMk{9t;JinFRgF)pg;(( z!HmAMn{i?{+Qb^~Zmgz%|CznGTHC(={$*nPiRleJscmf}*n2OH4YJjJl=+%BR)73q z<;m*p@ZMfxIou+A(>F6f$IeFd?@nBZ#QcHdL!t2Wr^Y^jGYJ>5h`_MSar6;Pt?w>ut0KW_2CR;V9ebu@Q-~n#M2}U3I%dE1tl}lv*b7P`+FnZMt?%ZTE a)@{&AYVrrn{o0qufA?|B?j;&~CHxCXi#~Ax diff --git a/Batt_Board/lib/adafruit_register/i2c_bcd_alarm.py b/Batt_Board/lib/adafruit_register/i2c_bcd_alarm.py deleted file mode 100644 index 4f18cbc..0000000 --- a/Batt_Board/lib/adafruit_register/i2c_bcd_alarm.py +++ /dev/null @@ -1,198 +0,0 @@ -# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries -# -# SPDX-License-Identifier: MIT -# pylint: disable=too-few-public-methods - -""" -`adafruit_register.i2c_bcd_alarm` -==================================================== - -Binary Coded Decimal alarm register - -* Author(s): Scott Shawcroft -""" - -__version__ = "0.0.0+auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" - -import time - -try: - from typing import Optional, Type, Tuple - from typing_extensions import Literal - from circuitpython_typing.device_drivers import I2CDeviceDriver - - FREQUENCY_T = Literal[ - "monthly", "weekly", "daily", "hourly", "secondly", "minutely" - ] -except ImportError: - pass - - -def _bcd2bin(value: int) -> int: - """Convert binary coded decimal to Binary - - :param value: the BCD value to convert to binary (required, no default) - """ - return value - 6 * (value >> 4) - - -def _bin2bcd(value: int) -> int: - """Convert a binary value to binary coded decimal. - - :param value: the binary value to convert to BCD. (required, no default) - """ - return value + 6 * (value // 10) - - -ALARM_COMPONENT_DISABLED = 0x80 -FREQUENCY = ["secondly", "minutely", "hourly", "daily", "weekly", "monthly"] - - -class BCDAlarmTimeRegister: - """ - Alarm date and time register using binary coded decimal structure. - - The byte order of the registers must* be: [second], minute, hour, day, - weekday. Each byte must also have a high enable bit where 1 is disabled and - 0 is enabled. - - * If weekday_shared is True, then weekday and day share a register. - * If has_seconds is True, then there is a seconds register. - - Values are a tuple of (`time.struct_time`, `str`) where the struct represents - a date and time that would alarm. The string is the frequency: - - * "secondly", once a second (only if alarm has_seconds) - * "minutely", once a minute when seconds match (if alarm doesn't seconds then when seconds = 0) - * "hourly", once an hour when ``tm_min`` and ``tm_sec`` match - * "daily", once a day when ``tm_hour``, ``tm_min`` and ``tm_sec`` match - * "weekly", once a week when ``tm_wday``, ``tm_hour``, ``tm_min``, ``tm_sec`` match - * "monthly", once a month when ``tm_mday``, ``tm_hour``, ``tm_min``, ``tm_sec`` match - - :param int register_address: The register address to start the read - :param bool has_seconds: True if the alarm can happen minutely. - :param bool weekday_shared: True if weekday and day share the same register - :param int weekday_start: 0 or 1 depending on the RTC's representation of the first day of the - week (Monday) - """ - - # Defaults are based on alarm1 of the DS3231. - def __init__( - self, - register_address: int, - has_seconds: bool = True, - weekday_shared: bool = True, - weekday_start: Literal[0, 1] = 1, - ) -> None: - buffer_size = 5 - if weekday_shared: - buffer_size -= 1 - if has_seconds: - buffer_size += 1 - self.has_seconds = has_seconds - self.buffer = bytearray(buffer_size) - self.buffer[0] = register_address - self.weekday_shared = weekday_shared - self.weekday_start = weekday_start - - def __get__( - self, - obj: Optional[I2CDeviceDriver], - objtype: Optional[Type[I2CDeviceDriver]] = None, - ) -> Tuple[time.struct_time, FREQUENCY_T]: - # Read the alarm register. - with obj.i2c_device as i2c: - i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) - - frequency = None - i = 1 - seconds = 0 - if self.has_seconds: - if (self.buffer[1] & 0x80) != 0: - frequency = "secondly" - else: - frequency = "minutely" - seconds = _bcd2bin(self.buffer[1] & 0x7F) - i = 2 - minute = 0 - if (self.buffer[i] & 0x80) == 0: - frequency = "hourly" - minute = _bcd2bin(self.buffer[i] & 0x7F) - - hour = 0 - if (self.buffer[i + 1] & 0x80) == 0: - frequency = "daily" - hour = _bcd2bin(self.buffer[i + 1] & 0x7F) - - mday = None - wday = None - if (self.buffer[i + 2] & 0x80) == 0: - # day of the month - if not self.weekday_shared or (self.buffer[i + 2] & 0x40) == 0: - frequency = "monthly" - mday = _bcd2bin(self.buffer[i + 2] & 0x3F) - else: # weekday - frequency = "weekly" - wday = _bcd2bin(self.buffer[i + 2] & 0x3F) - self.weekday_start - - # weekday - if not self.weekday_shared and (self.buffer[i + 3] & 0x80) == 0: - frequency = "monthly" - mday = _bcd2bin(self.buffer[i + 3] & 0x7F) - - if mday is not None: - wday = (mday - 2) % 7 - elif wday is not None: - mday = wday + 2 - else: - # Jan 1, 2017 was a Sunday (6) - wday = 6 - mday = 1 - - return ( - time.struct_time((2017, 1, mday, hour, minute, seconds, wday, mday, -1)), - frequency, - ) - - def __set__( - self, obj: I2CDeviceDriver, value: Tuple[time.struct_time, FREQUENCY_T] - ) -> None: - if len(value) != 2: - raise ValueError("Value must be sequence of length two") - # Turn all components off by default. - for i in range(len(self.buffer) - 1): - self.buffer[i + 1] = ALARM_COMPONENT_DISABLED - frequency_name = value[1] - error_message = "%s is not a supported frequency" % frequency_name - if frequency_name not in FREQUENCY: - raise ValueError(error_message) - - frequency = FREQUENCY.index(frequency_name) - if frequency <= 1 and not self.has_seconds: - raise ValueError(error_message) - - # i is the index of the minute byte - i = 2 if self.has_seconds else 1 - - if frequency > 0 and self.has_seconds: # minutely at least - self.buffer[1] = _bin2bcd(value[0].tm_sec) - - if frequency > 1: # hourly at least - self.buffer[i] = _bin2bcd(value[0].tm_min) - - if frequency > 2: # daily at least - self.buffer[i + 1] = _bin2bcd(value[0].tm_hour) - - if value[1] == "weekly": - if self.weekday_shared: - self.buffer[i + 2] = ( - _bin2bcd(value[0].tm_wday + self.weekday_start) | 0x40 - ) - else: - self.buffer[i + 3] = _bin2bcd(value[0].tm_wday + self.weekday_start) - elif value[1] == "monthly": - self.buffer[i + 2] = _bin2bcd(value[0].tm_mday) - - with obj.i2c_device: - obj.i2c_device.write(self.buffer) diff --git a/Batt_Board/lib/adafruit_register/i2c_bcd_datetime.mpy b/Batt_Board/lib/adafruit_register/i2c_bcd_datetime.mpy deleted file mode 100644 index 4ca5d752bf25244f7b933f3fd4797de2f412ae50..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1141 zcmYLG+j81g6g|Q>h%pF6R9~p=0H!v@$ic2B?Tgz$-Nw^SgKr&XnmG~D0hAPpkq&Wq zLc)Z&?L&V>Nj@PT(oe|L&h*vY=TPX3Mp|oWU)L_pqBSLOr0Qzj?idu?q+vLe*oslq za81*(u2Mpc7Ads5C>p+B__A>S0SYn&i9fZeVVUZ$D0td!6BMT1wqZ6<Z?4!`7g4yGxsviANa)orhqZ$2chauv9L?Ag9pw z_`eB}$mT2743i-RyArvi-xkFVOXiIgTPj*8Qj2nbv zj1p|^QFD~Nuwf5Oo5TcLRNXMCg(6mmVq)q@G)z3IB|FsaXcTi#pc1r$N@3apr15VH zW{d!DS{<8%3z&?976UA_WsO3*M72@>k&H2z!z_bRB8L8L3($}KZfz@`_&EGp~w?vt=cn~w)PAhC1~cLNom{pN>LgHZFXt}&1xy*qbuKxuU{J7;_>Jf zW2Xy{gJy%bz23pW+}bS~>iLxL^lLYiDIcba`7>=090bLHoJu1(?N(N%?!JGQxC_bf zh_WYs;_VLt)ygkV;!}ymco@{zXTB7FefD8bNd5WvnoC0_4VlF0D_;_Cm^7G3myBLc zXrIykgnEp66FPwWYbL!q``}Bln?0e|%Z2s@?ds*h>eANshTGW5=TpZ!`JHTh;jOd` zqqsc?je6&duYdzmw9j#p<4cY|g<{v7^A22F<(yB<`5Na#j@My9XwG&%XVxrK;hne# Ld|zOeRJ4Bq)KgmQ diff --git a/Batt_Board/lib/adafruit_register/i2c_bcd_datetime.py b/Batt_Board/lib/adafruit_register/i2c_bcd_datetime.py deleted file mode 100644 index 2bb458a..0000000 --- a/Batt_Board/lib/adafruit_register/i2c_bcd_datetime.py +++ /dev/null @@ -1,114 +0,0 @@ -# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries -# -# SPDX-License-Identifier: MIT -# pylint: disable=too-few-public-methods - -""" -`adafruit_register.i2c_bcd_datetime` -==================================================== - -Binary Coded Decimal date and time register - -* Author(s): Scott Shawcroft -""" - -__version__ = "0.0.0+auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" - -import time - -try: - from typing import Optional, Type - from typing_extensions import Literal - from circuitpython_typing.device_drivers import I2CDeviceDriver -except ImportError: - pass - - -def _bcd2bin(value: int) -> int: - """Convert binary coded decimal to Binary - - :param value: the BCD value to convert to binary (required, no default) - """ - return value - 6 * (value >> 4) - - -def _bin2bcd(value: int) -> int: - """Convert a binary value to binary coded decimal. - - :param value: the binary value to convert to BCD. (required, no default) - """ - return value + 6 * (value // 10) - - -class BCDDateTimeRegister: - """ - Date and time register using binary coded decimal structure. - - The byte order of the register must* be: second, minute, hour, weekday, day (1-31), month, year - (in years after 2000). - - * Setting weekday_first=False will flip the weekday/day order so that day comes first. - - Values are `time.struct_time` - - :param int register_address: The register address to start the read - :param bool weekday_first: True if weekday is in a lower register than the day of the month - (1-31) - :param int weekday_start: 0 or 1 depending on the RTC's representation of the first day of the - week - """ - - def __init__( - self, - register_address: int, - weekday_first: bool = True, - weekday_start: Literal[0, 1] = 1, - ) -> None: - self.buffer = bytearray(8) - self.buffer[0] = register_address - if weekday_first: - self.weekday_offset = 0 - else: - self.weekday_offset = 1 - self.weekday_start = weekday_start - # Masking value list n/a sec min hr day wkday mon year - self.mask_datetime = b"\xFF\x7F\x7F\x3F\x3F\x07\x1F\xFF" - - def __get__( - self, - obj: Optional[I2CDeviceDriver], - objtype: Optional[Type[I2CDeviceDriver]] = None, - ) -> time.struct_time: - # Read and return the date and time. - with obj.i2c_device as i2c: - i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) - return time.struct_time( - ( - _bcd2bin(self.buffer[7] & self.mask_datetime[7]) + 2000, - _bcd2bin(self.buffer[6] & self.mask_datetime[6]), - _bcd2bin(self.buffer[5 - self.weekday_offset] & self.mask_datetime[4]), - _bcd2bin(self.buffer[3] & self.mask_datetime[3]), - _bcd2bin(self.buffer[2] & self.mask_datetime[2]), - _bcd2bin(self.buffer[1] & self.mask_datetime[1]), - _bcd2bin( - (self.buffer[4 + self.weekday_offset] & self.mask_datetime[5]) - - self.weekday_start - ), - -1, - -1, - ) - ) - - def __set__(self, obj: I2CDeviceDriver, value: time.struct_time) -> None: - self.buffer[1] = _bin2bcd(value.tm_sec) & 0x7F # format conversions - self.buffer[2] = _bin2bcd(value.tm_min) - self.buffer[3] = _bin2bcd(value.tm_hour) - self.buffer[4 + self.weekday_offset] = _bin2bcd( - value.tm_wday + self.weekday_start - ) - self.buffer[5 - self.weekday_offset] = _bin2bcd(value.tm_mday) - self.buffer[6] = _bin2bcd(value.tm_mon) - self.buffer[7] = _bin2bcd(value.tm_year - 2000) - with obj.i2c_device: - obj.i2c_device.write(self.buffer) diff --git a/Batt_Board/lib/adafruit_register/i2c_bit.mpy b/Batt_Board/lib/adafruit_register/i2c_bit.mpy deleted file mode 100644 index 53762b9ba6e99bd7564a87edd81f375bbdd7644f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 801 zcmZvY(M}pc6ozL(5LSc{aEh{3E0#9IvK6VpUbHH-RgzXg)Dml^u)DxmWy$WKA!(Cc zstLXgN^g4IH|Ptr=?mD{r0Hb`s7)`LOmfbgobNy9{7E~Qyu&35a-pQv3B{UHCOTC# zk;EiiBs5xU0FP@fx)_~H0JvVGM6DJMfFswaDFD7uvkI+iRp6qH8mX4S#9AzwQjUnE zq%?A*XyBHk<8nmKTalb7p_gb*r>(K$9-B}@&h4pR-K{gj37W6m3RjijvOvO=b zPzAU#E-Msc;O{Qi*8t&TjZg*Cic)3E7i3bUDsZcHij}GiAgN-V7Bt$0^&Z?@Lm0Ew zban~Tf|y}7m1WPxdk>nvscQ#tL6$W|*MXg_1BAcjVcyy}9*Wl{i_6z;C;R4X^v%is@jl8JaQb$3oO``w zIA>DRi7n|&gcgKC0Nq#(8_)^|pepPs=$nhQ^^214vN;dXP$t4{ z&U45Uvmk#)vYvpCC*1guaC2gE%9yCp$)N%F8vR^NNx1NmtFGf?)J+1KmX6)cDFnJQC3;t ix>GWeISY8cwQBIa+j$g>f$phXCFtsn+w_LxK=} None: - self.bit_mask = 1 << (bit % 8) # the bitmask *within* the byte! - self.buffer = bytearray(1 + register_width) - self.buffer[0] = register_address - if lsb_first: - self.byte = bit // 8 + 1 # the byte number within the buffer - else: - self.byte = register_width - (bit // 8) # the byte number within the buffer - - def __get__( - self, - obj: Optional[I2CDeviceDriver], - objtype: Optional[Type[I2CDeviceDriver]] = None, - ) -> bool: - with obj.i2c_device as i2c: - i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) - return bool(self.buffer[self.byte] & self.bit_mask) - - def __set__(self, obj: I2CDeviceDriver, value: bool) -> None: - with obj.i2c_device as i2c: - i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) - if value: - self.buffer[self.byte] |= self.bit_mask - else: - self.buffer[self.byte] &= ~self.bit_mask - i2c.write(self.buffer) - - -class ROBit(RWBit): - """Single bit register that is read only. Subclass of `RWBit`. - - Values are `bool` - - :param int register_address: The register address to read the bit from - :param type bit: The bit index within the byte at ``register_address`` - :param int register_width: The number of bytes in the register. Defaults to 1. - - """ - - def __set__(self, obj: I2CDeviceDriver, value: bool) -> NoReturn: - raise AttributeError() diff --git a/Batt_Board/lib/adafruit_register/i2c_bits.mpy b/Batt_Board/lib/adafruit_register/i2c_bits.mpy deleted file mode 100644 index a9846d634f5635478949327a402bf6a65ec0c381..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1053 zcmZvZ-%}G;6vyu-1Weo{H=DSP5-|pnV1%TEtz&(ukSGXM2_g(LI=7qMOLi^nrn@&V z>2${3Oc`STnnePCfJXm-_R)uS+D_j(#^N8)n?-zL=FXgZ@A>iloO4Qp;L3#OmMDuG zMnfgYz%|t**eIw)2~|~M=Ic!mW~cIB=cjH0_^?h?T@yFKxs_%e1NcB+#-w3rfF;ek zs@1^Ay<(}1w^a$34RsqE;I5<^60KZs5=Gb0NmyQXLy+von1Eego26RRK6LFUq|#_h zG`|P2O??NOq|XA_YGY%A*5o$LD%wyDlK^O{HO+N~5UOE<5Qz2BPXs__$507IM8O(u zO_WuQ=pdvw2*R2SpsFF0hz0>P*>saz_jrUTw@KTiwIHHtb)DYs7bd@IMWNQ%a>oLa zr;|dWEF0J~LE`hzjw%xcuzfTv1Knl@_3AS!TYW~`$G~pdYtX=b73yx?nelz25K=d9 z6$&+#D2-}f(zgnyD=EyJ?xu7y$)%G?E`Qoop5_4GcuCYWog@`;8z;AP11IUg%p_4n z?K5f8RDZ-^_osXCy?{EFIwSW zqU~5@m`^}If7xDM%6T761O9v^$2^*5_(;*i^A*XvJOA4*lR7_gB^kULPFYh6*Rx}} z%$U93vyTsYPMme3tlepU?7b_`aigP*z~BQX9^}UC&VK^q#6vHgXvkVUbmHNo@|?YI zANOWwEGG^t(%$@DBo!G+XRONqJJJhf503LA>72Ec$xKu2$@I6`^0i!s(gy!})o0i_i{Wg5`&`!kge7>+ z=J@r`Zs+A6U6&LsM&5Q(_H)V>I$G_t|L)FC9>l(&uNzFi%Zg-AfIy%vTdcdD0Drj% R`paOHVb+EIfx9jM{6D?nP#gdN diff --git a/Batt_Board/lib/adafruit_register/i2c_bits.py b/Batt_Board/lib/adafruit_register/i2c_bits.py deleted file mode 100644 index 9a9f1d2..0000000 --- a/Batt_Board/lib/adafruit_register/i2c_bits.py +++ /dev/null @@ -1,114 +0,0 @@ -# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries -# -# SPDX-License-Identifier: MIT -# pylint: disable=too-few-public-methods - -""" -`adafruit_register.i2c_bits` -==================================================== - -Multi bit registers - -* Author(s): Scott Shawcroft -""" - -__version__ = "0.0.0+auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" - -try: - from typing import Optional, Type, NoReturn - from circuitpython_typing.device_drivers import I2CDeviceDriver -except ImportError: - pass - - -class RWBits: - """ - Multibit register (less than a full byte) that is readable and writeable. - This must be within a byte register. - - Values are `int` between 0 and 2 ** ``num_bits`` - 1. - - :param int num_bits: The number of bits in the field. - :param int register_address: The register address to read the bit from - :param int lowest_bit: The lowest bits index within the byte at ``register_address`` - :param int register_width: The number of bytes in the register. Defaults to 1. - :param bool lsb_first: Is the first byte we read from I2C the LSB? Defaults to true - :param bool signed: If True, the value is a "two's complement" signed value. - If False, it is unsigned. - """ - - def __init__( # pylint: disable=too-many-arguments - self, - num_bits: int, - register_address: int, - lowest_bit: int, - register_width: int = 1, - lsb_first: bool = True, - signed: bool = False, - ) -> None: - self.bit_mask = ((1 << num_bits) - 1) << lowest_bit - # print("bitmask: ",hex(self.bit_mask)) - if self.bit_mask >= 1 << (register_width * 8): - raise ValueError("Cannot have more bits than register size") - self.lowest_bit = lowest_bit - self.buffer = bytearray(1 + register_width) - self.buffer[0] = register_address - self.lsb_first = lsb_first - self.sign_bit = (1 << (num_bits - 1)) if signed else 0 - - def __get__( - self, - obj: Optional[I2CDeviceDriver], - objtype: Optional[Type[I2CDeviceDriver]] = None, - ) -> int: - with obj.i2c_device as i2c: - i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) - # read the number of bytes into a single variable - reg = 0 - order = range(len(self.buffer) - 1, 0, -1) - if not self.lsb_first: - order = reversed(order) - for i in order: - reg = (reg << 8) | self.buffer[i] - reg = (reg & self.bit_mask) >> self.lowest_bit - # If the value is signed and negative, convert it - if reg & self.sign_bit: - reg -= 2 * self.sign_bit - return reg - - def __set__(self, obj: I2CDeviceDriver, value: int) -> None: - value <<= self.lowest_bit # shift the value over to the right spot - with obj.i2c_device as i2c: - i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) - reg = 0 - order = range(len(self.buffer) - 1, 0, -1) - if not self.lsb_first: - order = range(1, len(self.buffer)) - for i in order: - reg = (reg << 8) | self.buffer[i] - # print("old reg: ", hex(reg)) - reg &= ~self.bit_mask # mask off the bits we're about to change - reg |= value # then or in our new value - # print("new reg: ", hex(reg)) - for i in reversed(order): - self.buffer[i] = reg & 0xFF - reg >>= 8 - i2c.write(self.buffer) - - -class ROBits(RWBits): - """ - Multibit register (less than a full byte) that is read-only. This must be - within a byte register. - - Values are `int` between 0 and 2 ** ``num_bits`` - 1. - - :param int num_bits: The number of bits in the field. - :param int register_address: The register address to read the bit from - :param type lowest_bit: The lowest bits index within the byte at ``register_address`` - :param int register_width: The number of bytes in the register. Defaults to 1. - """ - - def __set__(self, obj: I2CDeviceDriver, value: int) -> NoReturn: - raise AttributeError() diff --git a/Batt_Board/lib/adafruit_register/i2c_struct.mpy b/Batt_Board/lib/adafruit_register/i2c_struct.mpy deleted file mode 100644 index 21b38000c7d98d73c16da70e03ab7958c0c1f144..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1040 zcmZvZ-A)=o7=~v-5OKlb$0-U`E4o&Tpj%2?o3t9ys)@BIYDhHOWf@?t>~3}k4L#bW zCdIo!=^fbn=p{7iNl!a0k~EFU9L&%Az2En|v+f3y3FfU>63cpBCW4MDvO%z(l6grm zh+daSQfq(_b~*Vvx%>*Sy*mK+G$N~tcn&=IhK7MRU)RntaIYy106(bvnACLzu%w~M zN(D@8^XplBAxk)`%NJM&o06RaFssvSi7Y2qjx%0QSJ{5740^>W%xu`2OC% z$G9~QCGlJ`B`B6D*J_Rw$)dk`|ajV5MIz#b{27p1hhHI+cxRCKBXl|^z-cK-_mceBOLG7I|B|!@d?Sfz=F_SM(;y1Laja3nr6YjQLEZ94*koE3D21MLvZFu&7GI}*HUhl1u&PWoE?#lTQ} z?x~sLGB=^{`_a2jc$7ZU1(5|GS(z(GA$M|0>m#&2I8PtajFn3|4l@p^sU5?y_}qe- zUgD@?oGR$Mb=!J5Q0Tf1Q@1F+$uM1yoYV#IwN@y=RBPA<@I44%MdE%NU}L5`|4lm* z%Sl&Tf0(d6>+TZA@&7^HwL`IvdDQJ1`0+hF+UYzrf5*x=%|q$Ra~!tk&M$F`+`~@y pJRR#c(}NkJw$V->#YzCLzjb1IZ4AGSL9=J7QPy^@1fQh={tHqnAt(R< diff --git a/Batt_Board/lib/adafruit_register/i2c_struct.py b/Batt_Board/lib/adafruit_register/i2c_struct.py deleted file mode 100644 index 2f546a8..0000000 --- a/Batt_Board/lib/adafruit_register/i2c_struct.py +++ /dev/null @@ -1,104 +0,0 @@ -# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries -# -# SPDX-License-Identifier: MIT -# pylint: disable=too-few-public-methods - -""" -`adafruit_register.i2c_struct` -==================================================== - -Generic structured registers based on `struct` - -* Author(s): Scott Shawcroft -""" - -__version__ = "0.0.0+auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" - -import struct - -try: - from typing import Optional, Type, Tuple, Any, NoReturn - from circuitpython_typing.device_drivers import I2CDeviceDriver -except ImportError: - pass - - -class Struct: - """ - Arbitrary structure register that is readable and writeable. - - Values are tuples that map to the values in the defined struct. See struct - module documentation for struct format string and its possible value types. - - :param int register_address: The register address to read the bit from - :param str struct_format: The struct format string for this register. - """ - - def __init__(self, register_address: int, struct_format: str) -> None: - self.format = struct_format - self.buffer = bytearray(1 + struct.calcsize(self.format)) - self.buffer[0] = register_address - - def __get__( - self, - obj: Optional[I2CDeviceDriver], - objtype: Optional[Type[I2CDeviceDriver]] = None, - ) -> Tuple: - with obj.i2c_device as i2c: - i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) - return struct.unpack_from(self.format, memoryview(self.buffer)[1:]) - - def __set__(self, obj: I2CDeviceDriver, value: Tuple) -> None: - struct.pack_into(self.format, self.buffer, 1, *value) - with obj.i2c_device as i2c: - i2c.write(self.buffer) - - -class UnaryStruct: - """ - Arbitrary single value structure register that is readable and writeable. - - Values map to the first value in the defined struct. See struct - module documentation for struct format string and its possible value types. - - :param int register_address: The register address to read the bit from - :param str struct_format: The struct format string for this register. - """ - - def __init__(self, register_address: int, struct_format: str) -> None: - self.format = struct_format - self.address = register_address - - def __get__( - self, - obj: Optional[I2CDeviceDriver], - objtype: Optional[Type[I2CDeviceDriver]] = None, - ) -> Any: - buf = bytearray(1 + struct.calcsize(self.format)) - buf[0] = self.address - with obj.i2c_device as i2c: - i2c.write_then_readinto(buf, buf, out_end=1, in_start=1) - return struct.unpack_from(self.format, buf, 1)[0] - - def __set__(self, obj: I2CDeviceDriver, value: Any) -> None: - buf = bytearray(1 + struct.calcsize(self.format)) - buf[0] = self.address - struct.pack_into(self.format, buf, 1, value) - with obj.i2c_device as i2c: - i2c.write(buf) - - -class ROUnaryStruct(UnaryStruct): - """ - Arbitrary single value structure register that is read-only. - - Values map to the first value in the defined struct. See struct - module documentation for struct format string and its possible value types. - - :param int register_address: The register address to read the bit from - :param type struct_format: The struct format string for this register. - """ - - def __set__(self, obj: I2CDeviceDriver, value: Any) -> NoReturn: - raise AttributeError() diff --git a/Batt_Board/lib/adafruit_register/i2c_struct_array.mpy b/Batt_Board/lib/adafruit_register/i2c_struct_array.mpy deleted file mode 100644 index 8871367e98a10a4d912ad99c58c24a991646b91a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1008 zcmYk3ZEw?76vwagM4p=5*yb)Yp)E~F(*Vh0veF`?jiwZ+HVs3e%C_p(y>?ndYg_gW zD0`6XP)*&paS}cO-8a~aNxa#2Kzsmh;#T90b*%Hh=YRgc(~g1pW@t-Mm9E{>DYmr( z-JzP@&?SN$YWE1mift?Xy445SbhExzZ?1xL&;iNqo^_-F_@hO2(@>5;e7kRHAWi$0 zZXAHQ$5MMkd#w{~!`5GGHrOP(O_+_|B)HZ{LL@doJ2 zpOUv+?CQ4j|FR%v?!N@yWrPnjiuZfnF7tv!IUH>V zb%UB9WA-T43>84#V5t?G0@O1sg}lOD+dKwq{Is5G2alICimKY0;{Z5CgLM^T@YEXv zgfUC%FdP^Ih?$3Fvg(J%t#1ax!UbM@F=6(cao4nu6$-pH+2aNeGAs{+qXF;spx`y4 z-yTwGId>b41DziB_UptvZd{zKasT3W?EvnR0PfTCOV-&N5Y3z)bGq;PJiIen6O5IcUawSxTNpxKT zC@pa)Cxwt8h0zr$f{HSU4!|&i(v|E|6}mUdI`%0_Lw& zmT$W2OZ95-@{ILoS%2(}PVYoI!JNe3@5I7*g`2#W$&*C9SaBb*6^ZwIJd-;8-i7BN zQUM6L#yz{uq4^20svi;4l}Y|xmY-#xVcD-T4zR?DGi+BsKPR&Y|8U}bC40+#Rcn`I hzCQGYxmKvX!#5@spJd;wY%ONminPKV{_8+MJ_8bIDg^)l diff --git a/Batt_Board/lib/adafruit_register/i2c_struct_array.py b/Batt_Board/lib/adafruit_register/i2c_struct_array.py deleted file mode 100644 index 02d373c..0000000 --- a/Batt_Board/lib/adafruit_register/i2c_struct_array.py +++ /dev/null @@ -1,114 +0,0 @@ -# SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries -# -# SPDX-License-Identifier: MIT -# pylint: disable=too-few-public-methods - -""" -`adafruit_register.i2c_struct_array` -==================================================== - -Array of structured registers based on `struct` - -* Author(s): Scott Shawcroft -""" - -__version__ = "0.0.0+auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git" - -import struct - -try: - from typing import Tuple, Optional, Type - from circuitpython_typing.device_drivers import I2CDeviceDriver -except ImportError: - pass - - -class _BoundStructArray: - """ - Array object that `StructArray` constructs on demand. - - :param object obj: The device object to bind to. It must have a `i2c_device` attribute - :param int register_address: The register address to read the bit from - :param str struct_format: The struct format string for each register element - :param int count: Number of elements in the array - """ - - def __init__( - self, - obj: I2CDeviceDriver, - register_address: int, - struct_format: str, - count: int, - ) -> None: - self.format = struct_format - self.first_register = register_address - self.obj = obj - self.count = count - - def _get_buffer(self, index: int) -> bytearray: - """Shared bounds checking and buffer creation.""" - if not 0 <= index < self.count: - raise IndexError() - size = struct.calcsize(self.format) - # We create the buffer every time instead of keeping the buffer (which is 32 bytes at least) - # around forever. - buf = bytearray(size + 1) - buf[0] = self.first_register + size * index - return buf - - def __getitem__(self, index: int) -> Tuple: - buf = self._get_buffer(index) - with self.obj.i2c_device as i2c: - i2c.write_then_readinto(buf, buf, out_end=1, in_start=1) - return struct.unpack_from(self.format, buf, 1) # offset=1 - - def __setitem__(self, index: int, value: Tuple) -> None: - buf = self._get_buffer(index) - struct.pack_into(self.format, buf, 1, *value) - with self.obj.i2c_device as i2c: - i2c.write(buf) - - def __len__(self) -> int: - return self.count - - -class StructArray: - """ - Repeated array of structured registers that are readable and writeable. - - Based on the index, values are offset by the size of the structure. - - Values are tuples that map to the values in the defined struct. See struct - module documentation for struct format string and its possible value types. - - .. note:: This assumes the device addresses correspond to 8-bit bytes. This is not suitable for - devices with registers of other widths such as 16-bit. - - :param int register_address: The register address to begin reading the array from - :param str struct_format: The struct format string for this register. - :param int count: Number of elements in the array - """ - - def __init__(self, register_address: int, struct_format: str, count: int) -> None: - self.format = struct_format - self.address = register_address - self.count = count - self.array_id = "_structarray{}".format(register_address) - - def __get__( - self, - obj: Optional[I2CDeviceDriver], - objtype: Optional[Type[I2CDeviceDriver]] = None, - ) -> _BoundStructArray: - # We actually can't handle the indexing ourself due to data descriptor limits. So, we return - # an object that can instead. This object is bound to the object passed in here by its - # initializer and then cached on the object itself. That way its lifetime is tied to the - # lifetime of the object itself. - if not hasattr(obj, self.array_id): - setattr( - obj, - self.array_id, - _BoundStructArray(obj, self.address, self.format, self.count), - ) - return getattr(obj, self.array_id) diff --git a/Batt_Board/lib/adafruit_ticks.py b/Batt_Board/lib/adafruit_ticks.py deleted file mode 100644 index 5585d2b..0000000 --- a/Batt_Board/lib/adafruit_ticks.py +++ /dev/null @@ -1,139 +0,0 @@ -# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries -# SPDX-FileCopyrightText: Copyright (c) 2021 Jeff Epler for Adafruit Industries -# -# SPDX-License-Identifier: MIT -""" -`adafruit_ticks` -================================================================================ - -Work with intervals and deadlines in milliseconds - - -* Author(s): Jeff Epler - -Implementation Notes --------------------- - -**Software and Dependencies:** - -* Adafruit CircuitPython firmware for the supported boards: - https://github.com/adafruit/circuitpython/releases - -""" - -# imports -from micropython import const - -__version__ = "0.0.0+auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ticks.git" - -_TICKS_PERIOD = const(1 << 29) -_TICKS_MAX = const(_TICKS_PERIOD - 1) -_TICKS_HALFPERIOD = const(_TICKS_PERIOD // 2) - -# Get the correct implementation of ticks_ms. There are three possibilities: -# -# - supervisor.ticks_ms is present. This will be the case starting in CP7.0 -# -# - time.ticks_ms is present. This is the case for MicroPython & for the "unix -# port" of CircuitPython, used for some automated testing. -# -# - time.monotonic_ns is present, and works. This is the case on most -# Express boards in CP6.x, and most host computer versions of Python. -# -# - Otherwise, time.monotonic is assumed to be present. This is the case -# on most non-express boards in CP6.x, and some old host computer versions -# of Python. -# -# Note that on microcontrollers, this time source becomes increasingly -# inaccurate when the board has not been reset in a long time, losing the -# ability to measure 1ms intervals after about 1 hour, and losing the -# ability to meausre 128ms intervals after 6 days. The only solution is to -# either upgrade to a version with supervisor.ticks_ms, or to switch to a -# board with time.monotonic_ns. - -try: - from supervisor import ticks_ms # pylint: disable=unused-import -except (ImportError, NameError): - import time - - if _ticks_ms := getattr(time, "ticks_ms", None): - - def ticks_ms() -> int: - """Return the time in milliseconds since an unspecified moment, - wrapping after 2**29ms. - - The wrap value was chosen so that it is always possible to add or - subtract two `ticks_ms` values without overflow on a board without - long ints (or without allocating any long integer objects, on - boards with long ints). - - This ticks value comes from a low-accuracy clock internal to the - microcontroller, just like `time.monotonic`. Due to its low - accuracy and the fact that it "wraps around" every few days, it is - intended for working with short term events like advancing an LED - animation, not for long term events like counting down the time - until a holiday.""" - return _ticks_ms() & _TICKS_MAX # pylint: disable=not-callable - - else: - try: - from time import monotonic_ns as _monotonic_ns - - _monotonic_ns() # Check that monotonic_ns is usable - - def ticks_ms() -> int: - """Return the time in milliseconds since an unspecified moment, - wrapping after 2**29ms. - - The wrap value was chosen so that it is always possible to add or - subtract two `ticks_ms` values without overflow on a board without - long ints (or without allocating any long integer objects, on - boards with long ints). - - This ticks value comes from a low-accuracy clock internal to the - microcontroller, just like `time.monotonic`. Due to its low - accuracy and the fact that it "wraps around" every few days, it is - intended for working with short term events like advancing an LED - animation, not for long term events like counting down the time - until a holiday.""" - return (_monotonic_ns() // 1_000_000) & _TICKS_MAX - - except (ImportError, NameError, NotImplementedError): - from time import monotonic as _monotonic - - def ticks_ms() -> int: - """Return the time in milliseconds since an unspecified moment, - wrapping after 2**29ms. - - The wrap value was chosen so that it is always possible to add or - subtract two `ticks_ms` values without overflow on a board without - long ints (or without allocating any long integer objects, on - boards with long ints). - - This ticks value comes from a low-accuracy clock internal to the - microcontroller, just like `time.monotonic`. Due to its low - accuracy and the fact that it "wraps around" every few days, it is - intended for working with short term events like advancing an LED - animation, not for long term events like counting down the time - until a holiday.""" - return int(_monotonic() * 1000) & _TICKS_MAX - - -def ticks_add(ticks: int, delta: int) -> int: - "Add a delta to a base number of ticks, performing wraparound at 2**29ms." - return (ticks + delta) % _TICKS_PERIOD - - -def ticks_diff(ticks1: int, ticks2: int) -> int: - """Compute the signed difference between two ticks values, - assuming that they are within 2**28 ticks""" - diff = (ticks1 - ticks2) & _TICKS_MAX - diff = ((diff + _TICKS_HALFPERIOD) & _TICKS_MAX) - _TICKS_HALFPERIOD - return diff - - -def ticks_less(ticks1: int, ticks2: int) -> bool: - """Return true if ticks1 is before ticks2 and false otherwise, - assuming that they are within 2**28 ticks""" - return ticks_diff(ticks1, ticks2) < 0 diff --git a/Batt_Board/lib/adafruit_veml7700.py b/Batt_Board/lib/adafruit_veml7700.py deleted file mode 100644 index 658870e..0000000 --- a/Batt_Board/lib/adafruit_veml7700.py +++ /dev/null @@ -1,252 +0,0 @@ -# SPDX-FileCopyrightText: 2019 Kattni Rembor for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_veml7700` -================================================================================ - -CircuitPython driver for VEML7700 high precision I2C ambient light sensor. - - -* Author(s): Kattni Rembor - -Implementation Notes --------------------- - -**Hardware:** - -* `Adafruit VEML7700 Lux Sensor - I2C Light Sensor - `_ (Product ID: 4162) - -**Software and Dependencies:** - -* Adafruit CircuitPython firmware for the supported boards: - https://circuitpython.org/downloads - -* Adafruit's Bus Device library: - https://github.com/adafruit/Adafruit_CircuitPython_BusDevice - -* Adafruit's Register library: - https://github.com/adafruit/Adafruit_CircuitPython_Register -""" - -from micropython import const -import adafruit_bus_device.i2c_device as i2cdevice -from adafruit_register.i2c_struct import UnaryStruct, ROUnaryStruct -from adafruit_register.i2c_bits import RWBits -from adafruit_register.i2c_bit import RWBit, ROBit - -try: - import typing # pylint: disable=unused-import - from busio import I2C -except ImportError: - pass - -__version__ = "0.0.0+auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_VEML7700.git" - - -class VEML7700: - """Driver for the VEML7700 ambient light sensor. - - :param ~busio.I2C i2c_bus: The I2C bus the device is connected to - :param int address: The I2C device address. Defaults to :const:`0x10` - - """ - - # Ambient light sensor gain settings - ALS_GAIN_1 = const(0x0) - ALS_GAIN_2 = const(0x1) - ALS_GAIN_1_8 = const(0x2) - ALS_GAIN_1_4 = const(0x3) - - # Ambient light integration time settings - - ALS_25MS = const(0xC) - ALS_50MS = const(0x8) - ALS_100MS = const(0x0) - ALS_200MS = const(0x1) - ALS_400MS = const(0x2) - ALS_800MS = const(0x3) - - # Gain value integers - gain_values = { - ALS_GAIN_2: 2, - ALS_GAIN_1: 1, - ALS_GAIN_1_4: 0.25, - ALS_GAIN_1_8: 0.125, - } - - # Integration time value integers - integration_time_values = { - ALS_25MS: 25, - ALS_50MS: 50, - ALS_100MS: 100, - ALS_200MS: 200, - ALS_400MS: 400, - ALS_800MS: 800, - } - - # ALS - Ambient light sensor high resolution output data - light = ROUnaryStruct(0x04, " None: - self.i2c_device = i2cdevice.I2CDevice(i2c_bus, address) - for _ in range(3): - try: - self.light_shutdown = False # Enable the ambient light sensor - break - except OSError: - pass - else: - raise RuntimeError("Unable to enable VEML7700 device") - - def integration_time_value(self) -> int: - """Integration time value in integer form. Used for calculating :meth:`resolution`.""" - integration_time = self.light_integration_time - return self.integration_time_values[integration_time] - - def gain_value(self) -> float: - """Gain value in integer form. Used for calculating :meth:`resolution`.""" - gain = self.light_gain - return self.gain_values[gain] - - def resolution(self) -> float: - """Calculate the :meth:`resolution`` necessary to calculate lux. Based on - integration time and gain settings.""" - resolution_at_max = 0.0036 - gain_max = 2 - integration_time_max = 800 - - if ( - self.gain_value() == gain_max - and self.integration_time_value() == integration_time_max - ): - return resolution_at_max - return ( - resolution_at_max - * (integration_time_max / self.integration_time_value()) - * (gain_max / self.gain_value()) - ) - - @property - def lux(self) -> float: - """Light value in lux. - - This example prints the light data in lux. Cover the sensor to see the values change. - - .. code-block:: python - - import time - import board - import adafruit_veml7700 - - i2c = board.I2C() # uses board.SCL and board.SDA - veml7700 = adafruit_veml7700.VEML7700(i2c) - - while True: - print("Lux:", veml7700.lux) - time.sleep(0.1) - """ - return self.resolution() * self.light diff --git a/Batt_Board/lib/adafruit_vl6180x.py b/Batt_Board/lib/adafruit_vl6180x.py deleted file mode 100644 index baf9245..0000000 --- a/Batt_Board/lib/adafruit_vl6180x.py +++ /dev/null @@ -1,411 +0,0 @@ -# SPDX-FileCopyrightText: 2017 Tony DiCola for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -""" -`adafruit_vl6180x` -==================================================== - -CircuitPython module for the VL6180X distance sensor. See -examples/simpletest.py for a demo of the usage. - -* Author(s): Tony DiCola, Jonas Schatz - -Implementation Notes --------------------- - -**Hardware:** - -* Adafruit `VL6180X Time of Flight Distance Ranging Sensor (VL6180) - `_ (Product ID: 3316) - -**Software and Dependencies:** - -* Adafruit CircuitPython firmware for the ESP8622 and M0-based boards: - https://github.com/adafruit/circuitpython/releases -* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice -""" -import struct -import time - -from micropython import const - -from adafruit_bus_device import i2c_device - -try: - from typing import Optional, List - from busio import I2C -except ImportError: - pass - - -__version__ = "0.0.0+auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_VL6180X.git" - -# Registers -_VL6180X_REG_IDENTIFICATION_MODEL_ID = const(0x000) - -_VL6180X_REG_SYSTEM_HISTORY_CTRL = const(0x012) -_VL6180X_REG_SYSTEM_INTERRUPT_CONFIG = const(0x014) -_VL6180X_REG_SYSTEM_INTERRUPT_CLEAR = const(0x015) -_VL6180X_REG_SYSTEM_FRESH_OUT_OF_RESET = const(0x016) - -_VL6180X_REG_SYSRANGE_START = const(0x018) -_VL6180X_REG_SYSRANGE_INTERMEASUREMENT_PERIOD = const(0x01B) -_VL6180X_REG_SYSRANGE_PART_TO_PART_RANGE_OFFSET = const(0x024) - -_VL6180X_REG_SYSALS_START = const(0x038) -_VL6180X_REG_SYSALS_ANALOGUE_GAIN = const(0x03F) -_VL6180X_REG_SYSALS_INTEGRATION_PERIOD_HI = const(0x040) -_VL6180X_REG_SYSALS_INTEGRATION_PERIOD_LO = const(0x041) - -_VL6180X_REG_RESULT_RANGE_STATUS = const(0x04D) -_VL6180X_REG_RESULT_INTERRUPT_STATUS_GPIO = const(0x04F) -_VL6180X_REG_RESULT_ALS_VAL = const(0x050) -_VL6180X_REG_RESULT_HISTORY_BUFFER_0 = const(0x052) -_VL6180X_REG_RESULT_RANGE_VAL = const(0x062) - -# Internal constants: -_VL6180X_DEFAULT_I2C_ADDR = const(0x29) - -# User-facing constants: -ALS_GAIN_1 = const(0x06) -ALS_GAIN_1_25 = const(0x05) -ALS_GAIN_1_67 = const(0x04) -ALS_GAIN_2_5 = const(0x03) -ALS_GAIN_5 = const(0x02) -ALS_GAIN_10 = const(0x01) -ALS_GAIN_20 = const(0x00) -ALS_GAIN_40 = const(0x07) - -ERROR_NONE = const(0) -ERROR_SYSERR_1 = const(1) -ERROR_SYSERR_5 = const(5) -ERROR_ECEFAIL = const(6) -ERROR_NOCONVERGE = const(7) -ERROR_RANGEIGNORE = const(8) -ERROR_SNR = const(11) -ERROR_RAWUFLOW = const(12) -ERROR_RAWOFLOW = const(13) -ERROR_RANGEUFLOW = const(14) -ERROR_RANGEOFLOW = const(15) - - -class VL6180X: - """Create an instance of the VL6180X distance sensor. You must pass in - the following parameters: - - :param ~I2C i2c: An instance of the I2C bus connected to the sensor. - - Optionally you can specify: - - :param int address: The I2C address of the sensor. If not specified the sensor's - default value will be assumed. - :param int offset: The offset to be applied to measurements, in mm - """ - - def __init__( - self, i2c: I2C, address: int = _VL6180X_DEFAULT_I2C_ADDR, offset: int = 0 - ) -> None: - self._device = i2c_device.I2CDevice(i2c, address) - if self._read_8(_VL6180X_REG_IDENTIFICATION_MODEL_ID) != 0xB4: - raise RuntimeError("Could not find VL6180X, is it connected and powered?") - self._load_settings() - self._write_8(_VL6180X_REG_SYSTEM_FRESH_OUT_OF_RESET, 0x00) - self.offset = offset - - # Reset a sensor that crashed while in continuous mode - if self.continuous_mode_enabled: - self.stop_range_continuous() - time.sleep(0.1) - - # Activate history buffer for range measurement - self._write_8(_VL6180X_REG_SYSTEM_HISTORY_CTRL, 0x01) - - @property - def range(self) -> int: - """Read the range of an object in front of sensor and return it in mm.""" - if self.continuous_mode_enabled: - return self._read_range_continuous() - return self._read_range_single() - - @property - def range_from_history(self) -> Optional[int]: - """Read the latest range data from history - To do so, you don't have to wait for a complete measurement.""" - - if not self.range_history_enabled: - return None - - return self._read_8(_VL6180X_REG_RESULT_HISTORY_BUFFER_0) - - @property - def ranges_from_history(self) -> Optional[List[int]]: - """Read the last 16 range measurements from history""" - - if not self.range_history_enabled: - return None - - return [ - self._read_8(_VL6180X_REG_RESULT_HISTORY_BUFFER_0 + age) - for age in range(16) - ] - - @property - def range_history_enabled(self) -> bool: - """Checks if history buffer stores range data""" - - history_ctrl: int = self._read_8(_VL6180X_REG_SYSTEM_HISTORY_CTRL) - - if history_ctrl & 0x0: - print("History buffering not enabled") - return False - - if (history_ctrl > 1) & 0x1: - print("History buffer stores ALS data, not range") - return False - - return True - - def start_range_continuous(self, period: int = 100) -> None: - """Start continuous range mode - - :param int period: Time delay between measurements, in milliseconds; the value you - will be floored to the nearest 10 milliseconds (setting to 157 ms sets it to 150 - ms). Range is 10 - 2550 ms. - """ - # Set range between measurements - if not 10 <= period <= 2550: - raise ValueError( - "Delay must be in 10 millisecond increments between 10 and 2550 milliseconds" - ) - - period_reg = (period // 10) - 1 - self._write_8(_VL6180X_REG_SYSRANGE_INTERMEASUREMENT_PERIOD, period_reg) - - # Start continuous range measurement - self._write_8(_VL6180X_REG_SYSRANGE_START, 0x03) - - def stop_range_continuous(self) -> None: - """Stop continuous range mode. It is advised to wait for about 0.3s - afterwards to avoid issues with the interrupt flags""" - if self.continuous_mode_enabled: - self._write_8(_VL6180X_REG_SYSRANGE_START, 0x01) - - @property - def continuous_mode_enabled(self) -> bool: - """Checks if continuous mode is enabled""" - return self._read_8(_VL6180X_REG_SYSRANGE_START) > 1 & 0x1 - - @property - def offset(self) -> int: - """Read and sets the manual offset for the sensor, in millimeters""" - return self._offset - - @offset.setter - def offset(self, offset: int) -> None: - self._write_8( - _VL6180X_REG_SYSRANGE_PART_TO_PART_RANGE_OFFSET, struct.pack("b", offset)[0] - ) - self._offset = offset - - def _read_range_single(self) -> int: - """Read the range when in single-shot mode""" - while not self._read_8(_VL6180X_REG_RESULT_RANGE_STATUS) & 0x01: - pass - self._write_8(_VL6180X_REG_SYSRANGE_START, 0x01) - return self._read_range_continuous() - - def _read_range_continuous(self) -> int: - """Read the range when in continuous mode""" - - # Poll until bit 2 is set - while not self._read_8(_VL6180X_REG_RESULT_INTERRUPT_STATUS_GPIO) & 0x04: - pass - - # read range in mm - range_ = self._read_8(_VL6180X_REG_RESULT_RANGE_VAL) - - # clear interrupt - self._write_8(_VL6180X_REG_SYSTEM_INTERRUPT_CLEAR, 0x07) - - return range_ - - def read_lux(self, gain: int) -> float: - """Read the lux (light value) from the sensor and return it. Must - specify the gain value to use for the lux reading: - - ================= ===== - Setting Value - ================= ===== - ``ALS_GAIN_1`` 1x - ``ALS_GAIN_1_25`` 1.25x - ``ALS_GAIN_1_67`` 1.67x - ``ALS_GAIN_2_5`` 2.5x - ``ALS_GAIN_5`` 5x - ``ALS_GAIN_10`` 10x - ``ALS_GAIN_20`` 20x - ``ALS_GAIN_40`` 40x - ================= ===== - - :param int gain: The gain value to use - - """ - reg = self._read_8(_VL6180X_REG_SYSTEM_INTERRUPT_CONFIG) - reg &= ~0x38 - reg |= 0x4 << 3 # IRQ on ALS ready - self._write_8(_VL6180X_REG_SYSTEM_INTERRUPT_CONFIG, reg) - # 100 ms integration period - self._write_8(_VL6180X_REG_SYSALS_INTEGRATION_PERIOD_HI, 0) - self._write_8(_VL6180X_REG_SYSALS_INTEGRATION_PERIOD_LO, 100) - # analog gain - gain = min(gain, ALS_GAIN_40) - self._write_8(_VL6180X_REG_SYSALS_ANALOGUE_GAIN, 0x40 | gain) - # start ALS - self._write_8(_VL6180X_REG_SYSALS_START, 0x1) - # Poll until "New Sample Ready threshold event" is set - while ( - (self._read_8(_VL6180X_REG_RESULT_INTERRUPT_STATUS_GPIO) >> 3) & 0x7 - ) != 4: - pass - # read lux! - lux = self._read_16(_VL6180X_REG_RESULT_ALS_VAL) - # clear interrupt - self._write_8(_VL6180X_REG_SYSTEM_INTERRUPT_CLEAR, 0x07) - lux *= 0.32 # calibrated count/lux - if gain == ALS_GAIN_1: - pass - elif gain == ALS_GAIN_1_25: - lux /= 1.25 - elif gain == ALS_GAIN_1_67: - lux /= 1.67 - elif gain == ALS_GAIN_2_5: - lux /= 2.5 - elif gain == ALS_GAIN_5: - lux /= 5 - elif gain == ALS_GAIN_10: - lux /= 10 - elif gain == ALS_GAIN_20: - lux /= 20 - elif gain == ALS_GAIN_40: - lux /= 40 - lux *= 100 - lux /= 100 # integration time in ms - return lux - - @property - def range_status(self) -> int: - """Retrieve the status/error from a previous range read. This will - return a constant value such as: - - ===================== ============================== - Error Description - ===================== ============================== - ``ERROR_NONE`` No error - ``ERROR_SYSERR_1`` System error 1 (see datasheet) - ``ERROR_SYSERR_5`` System error 5 (see datasheet) - ``ERROR_ECEFAIL`` ECE failure - ``ERROR_NOCONVERGE`` No convergence - ``ERROR_RANGEIGNORE`` Outside range ignored - ``ERROR_SNR`` Too much noise - ``ERROR_RAWUFLOW`` Raw value underflow - ``ERROR_RAWOFLOW`` Raw value overflow - ``ERROR_RANGEUFLOW`` Range underflow - ``ERROR_RANGEOFLOW`` Range overflow - ===================== ============================== - - """ - return self._read_8(_VL6180X_REG_RESULT_RANGE_STATUS) >> 4 - - def _load_settings(self) -> None: - # private settings from page 24 of app note - self._write_8(0x0207, 0x01) - self._write_8(0x0208, 0x01) - self._write_8(0x0096, 0x00) - self._write_8(0x0097, 0xFD) - self._write_8(0x00E3, 0x00) - self._write_8(0x00E4, 0x04) - self._write_8(0x00E5, 0x02) - self._write_8(0x00E6, 0x01) - self._write_8(0x00E7, 0x03) - self._write_8(0x00F5, 0x02) - self._write_8(0x00D9, 0x05) - self._write_8(0x00DB, 0xCE) - self._write_8(0x00DC, 0x03) - self._write_8(0x00DD, 0xF8) - self._write_8(0x009F, 0x00) - self._write_8(0x00A3, 0x3C) - self._write_8(0x00B7, 0x00) - self._write_8(0x00BB, 0x3C) - self._write_8(0x00B2, 0x09) - self._write_8(0x00CA, 0x09) - self._write_8(0x0198, 0x01) - self._write_8(0x01B0, 0x17) - self._write_8(0x01AD, 0x00) - self._write_8(0x00FF, 0x05) - self._write_8(0x0100, 0x05) - self._write_8(0x0199, 0x05) - self._write_8(0x01A6, 0x1B) - self._write_8(0x01AC, 0x3E) - self._write_8(0x01A7, 0x1F) - self._write_8(0x0030, 0x00) - # Recommended : Public registers - See data sheet for more detail - self._write_8(0x0011, 0x10) # Enables polling for 'New Sample ready' - # when measurement completes - self._write_8(0x010A, 0x30) # Set the averaging sample period - # (compromise between lower noise and - # increased execution time) - self._write_8(0x003F, 0x46) # Sets the light and dark gain (upper - # nibble). Dark gain should not be - # changed. - self._write_8(0x0031, 0xFF) # sets the # of range measurements after - # which auto calibration of system is - # performed - self._write_8(0x0040, 0x63) # Set ALS integration time to 100ms - self._write_8(0x002E, 0x01) # perform a single temperature calibration - # of the ranging sensor - - # Optional: Public registers - See data sheet for more detail - self._write_8(0x001B, 0x09) # Set default ranging inter-measurement - # period to 100ms - self._write_8(0x003E, 0x31) # Set default ALS inter-measurement period - # to 500ms - self._write_8(0x0014, 0x24) # Configures interrupt on 'New Sample - # Ready threshold event' - - def _write_8(self, address: int, data: int) -> None: - # Write 1 byte of data from the specified 16-bit register address. - with self._device: - self._device.write(bytes([(address >> 8) & 0xFF, address & 0xFF, data])) - - def _write_16(self, address: int, data: int) -> None: - # Write a 16-bit big endian value to the specified 16-bit register - # address. - with self._device as i2c: - i2c.write( - bytes( - [ - (address >> 8) & 0xFF, - address & 0xFF, - (data >> 8) & 0xFF, - data & 0xFF, - ] - ) - ) - - def _read_8(self, address: int) -> int: - # Read and return a byte from the specified 16-bit register address. - with self._device as i2c: - result = bytearray(1) - i2c.write(bytes([(address >> 8) & 0xFF, address & 0xFF])) - i2c.readinto(result) - return result[0] - - def _read_16(self, address: int) -> int: - # Read and return a 16-bit unsigned big endian value read from the - # specified 16-bit register address. - with self._device as i2c: - result = bytearray(2) - i2c.write(bytes([(address >> 8) & 0xFF, address & 0xFF])) - i2c.readinto(result) - return (result[0] << 8) | result[1] diff --git a/Batt_Board/lib/asyncio/__init__.py b/Batt_Board/lib/asyncio/__init__.py deleted file mode 100644 index ce8837d..0000000 --- a/Batt_Board/lib/asyncio/__init__.py +++ /dev/null @@ -1,41 +0,0 @@ -# SPDX-FileCopyrightText: 2019 Damien P. George -# -# SPDX-License-Identifier: MIT -# -# MicroPython uasyncio module -# MIT license; Copyright (c) 2019 Damien P. George -# -# This code comes from MicroPython, and has not been run through black or pylint there. -# Altering these files significantly would make merging difficult, so we will not use -# pylint or black. -# pylint: skip-file -# fmt: off - -from .core import * - -__version__ = "0.0.0+auto.0" -__repo__ = "https://github.com/Adafruit/Adafruit_CircuitPython_asyncio.git" - -_attrs = { - "wait_for": "funcs", - "wait_for_ms": "funcs", - "gather": "funcs", - "Event": "event", - "ThreadSafeFlag": "event", - "Lock": "lock", - "open_connection": "stream", - "start_server": "stream", - "StreamReader": "stream", - "StreamWriter": "stream", -} - -# Lazy loader, effectively does: -# global attr -# from .mod import attr -def __getattr__(attr): - mod = _attrs.get(attr, None) - if mod is None: - raise AttributeError(attr) - value = getattr(__import__(mod, None, None, True, 1), attr) - globals()[attr] = value - return value diff --git a/Batt_Board/lib/asyncio/core.py b/Batt_Board/lib/asyncio/core.py deleted file mode 100644 index 33168e3..0000000 --- a/Batt_Board/lib/asyncio/core.py +++ /dev/null @@ -1,375 +0,0 @@ -# SPDX-FileCopyrightText: 2019 Damien P. George -# -# SPDX-License-Identifier: MIT -# -# MicroPython uasyncio module -# MIT license; Copyright (c) 2019 Damien P. George -# -# This code comes from MicroPython, and has not been run through black or pylint there. -# Altering these files significantly would make merging difficult, so we will not use -# pylint or black. -# pylint: skip-file -# fmt: off -""" -Core -==== -""" - -from adafruit_ticks import ticks_ms as ticks, ticks_diff, ticks_add -import sys, select, traceback - -# Import TaskQueue and Task, preferring built-in C code over Python code -try: - from _asyncio import TaskQueue, Task -except: - from .task import TaskQueue, Task - - -################################################################################ -# Exceptions - - -class CancelledError(BaseException): - """Injected into a task when calling `Task.cancel()`""" - - pass - - -class TimeoutError(Exception): - """Raised when waiting for a task longer than the specified timeout.""" - - pass - - -# Used when calling Loop.call_exception_handler -_exc_context = {"message": "Task exception wasn't retrieved", "exception": None, "future": None} - - -################################################################################ -# Sleep functions - -# "Yield" once, then raise StopIteration -class SingletonGenerator: - def __init__(self): - self.state = None - self.exc = StopIteration() - - def __iter__(self): - return self - - def __await__(self): - return self - - def __next__(self): - if self.state is not None: - _task_queue.push_sorted(cur_task, self.state) - self.state = None - return None - else: - self.exc.__traceback__ = None - raise self.exc - - -# Pause task execution for the given time (integer in milliseconds, uPy extension) -# Use a SingletonGenerator to do it without allocating on the heap -def sleep_ms(t, sgen=SingletonGenerator()): - """Sleep for *t* milliseconds. - - This is a coroutine, and a MicroPython extension. - """ - - assert sgen.state is None, "Check for a missing `await` in your code" - sgen.state = ticks_add(ticks(), max(0, t)) - return sgen - - -# Pause task execution for the given time (in seconds) -def sleep(t): - """Sleep for *t* seconds - - This is a coroutine. - """ - - return sleep_ms(int(t * 1000)) - - -################################################################################ -# Queue and poller for stream IO - - -class IOQueue: - def __init__(self): - self.poller = select.poll() - self.map = {} # maps id(stream) to [task_waiting_read, task_waiting_write, stream] - - def _enqueue(self, s, idx): - if id(s) not in self.map: - entry = [None, None, s] - entry[idx] = cur_task - self.map[id(s)] = entry - self.poller.register(s, select.POLLIN if idx == 0 else select.POLLOUT) - else: - sm = self.map[id(s)] - assert sm[idx] is None - assert sm[1 - idx] is not None - sm[idx] = cur_task - self.poller.modify(s, select.POLLIN | select.POLLOUT) - # Link task to this IOQueue so it can be removed if needed - cur_task.data = self - - def _dequeue(self, s): - del self.map[id(s)] - self.poller.unregister(s) - - def queue_read(self, s): - self._enqueue(s, 0) - - def queue_write(self, s): - self._enqueue(s, 1) - - def remove(self, task): - while True: - del_s = None - for k in self.map: # Iterate without allocating on the heap - q0, q1, s = self.map[k] - if q0 is task or q1 is task: - del_s = s - break - if del_s is not None: - self._dequeue(s) - else: - break - - def wait_io_event(self, dt): - for s, ev in self.poller.ipoll(dt): - sm = self.map[id(s)] - # print('poll', s, sm, ev) - if ev & ~select.POLLOUT and sm[0] is not None: - # POLLIN or error - _task_queue.push_head(sm[0]) - sm[0] = None - if ev & ~select.POLLIN and sm[1] is not None: - # POLLOUT or error - _task_queue.push_head(sm[1]) - sm[1] = None - if sm[0] is None and sm[1] is None: - self._dequeue(s) - elif sm[0] is None: - self.poller.modify(s, select.POLLOUT) - else: - self.poller.modify(s, select.POLLIN) - - -################################################################################ -# Main run loop - -# Ensure the awaitable is a task -def _promote_to_task(aw): - return aw if isinstance(aw, Task) else create_task(aw) - - -# Create and schedule a new task from a coroutine -def create_task(coro): - """Create a new task from the given coroutine and schedule it to run. - - Returns the corresponding `Task` object. - """ - - if not hasattr(coro, "send"): - raise TypeError("coroutine expected") - t = Task(coro, globals()) - _task_queue.push_head(t) - return t - - -# Keep scheduling tasks until there are none left to schedule -def run_until_complete(main_task=None): - """Run the given *main_task* until it completes.""" - - global cur_task - excs_all = (CancelledError, Exception) # To prevent heap allocation in loop - excs_stop = (CancelledError, StopIteration) # To prevent heap allocation in loop - while True: - # Wait until the head of _task_queue is ready to run - dt = 1 - while dt > 0: - dt = -1 - t = _task_queue.peek() - if t: - # A task waiting on _task_queue; "ph_key" is time to schedule task at - dt = max(0, ticks_diff(t.ph_key, ticks())) - elif not _io_queue.map: - # No tasks can be woken so finished running - return - # print('(poll {})'.format(dt), len(_io_queue.map)) - _io_queue.wait_io_event(dt) - - # Get next task to run and continue it - t = _task_queue.pop_head() - cur_task = t - try: - # Continue running the coroutine, it's responsible for rescheduling itself - exc = t.data - if not exc: - t.coro.send(None) - else: - # If the task is finished and on the run queue and gets here, then it - # had an exception and was not await'ed on. Throwing into it now will - # raise StopIteration and the code below will catch this and run the - # call_exception_handler function. - t.data = None - t.coro.throw(exc) - except excs_all as er: - # Check the task is not on any event queue - assert t.data is None - # This task is done, check if it's the main task and then loop should stop - if t is main_task: - if isinstance(er, StopIteration): - return er.value - raise er - if t.state: - # Task was running but is now finished. - waiting = False - if t.state is True: - # "None" indicates that the task is complete and not await'ed on (yet). - t.state = None - else: - # Schedule any other tasks waiting on the completion of this task. - while t.state.peek(): - _task_queue.push_head(t.state.pop_head()) - waiting = True - # "False" indicates that the task is complete and has been await'ed on. - t.state = False - if not waiting and not isinstance(er, excs_stop): - # An exception ended this detached task, so queue it for later - # execution to handle the uncaught exception if no other task retrieves - # the exception in the meantime (this is handled by Task.throw). - _task_queue.push_head(t) - # Save return value of coro to pass up to caller. - t.data = er - elif t.state is None: - # Task is already finished and nothing await'ed on the task, - # so call the exception handler. - _exc_context["exception"] = exc - _exc_context["future"] = t - Loop.call_exception_handler(_exc_context) - - -# Create a new task from a coroutine and run it until it finishes -def run(coro): - """Create a new task from the given coroutine and run it until it completes. - - Returns the value returned by *coro*. - """ - - return run_until_complete(create_task(coro)) - - -################################################################################ -# Event loop wrapper - - -async def _stopper(): - pass - - -_stop_task = None - - -class Loop: - """Class representing the event loop""" - - _exc_handler = None - - def create_task(coro): - """Create a task from the given *coro* and return the new `Task` object.""" - - return create_task(coro) - - def run_forever(): - """Run the event loop until `Loop.stop()` is called.""" - - global _stop_task - _stop_task = Task(_stopper(), globals()) - run_until_complete(_stop_task) - # TODO should keep running until .stop() is called, even if there're no tasks left - - def run_until_complete(aw): - """Run the given *awaitable* until it completes. If *awaitable* is not a task then - it will be promoted to one. - """ - - return run_until_complete(_promote_to_task(aw)) - - def stop(): - """Stop the event loop""" - - global _stop_task - if _stop_task is not None: - _task_queue.push_head(_stop_task) - # If stop() is called again, do nothing - _stop_task = None - - def close(): - """Close the event loop.""" - - pass - - def set_exception_handler(handler): - """Set the exception handler to call when a Task raises an exception that is not - caught. The *handler* should accept two arguments: ``(loop, context)`` - """ - - Loop._exc_handler = handler - - def get_exception_handler(): - """Get the current exception handler. Returns the handler, or ``None`` if no - custom handler is set. - """ - - return Loop._exc_handler - - def default_exception_handler(loop, context): - """The default exception handler that is called.""" - - exc = context["exception"] - traceback.print_exception(None, exc, exc.__traceback__) - - def call_exception_handler(context): - """Call the current exception handler. The argument *context* is passed through - and is a dictionary containing keys: - ``'message'``, ``'exception'``, ``'future'`` - """ - (Loop._exc_handler or Loop.default_exception_handler)(Loop, context) - - -# The runq_len and waitq_len arguments are for legacy uasyncio compatibility -def get_event_loop(runq_len=0, waitq_len=0): - """Return the event loop used to schedule and run tasks. See `Loop`.""" - - return Loop - - -def current_task(): - """Return the `Task` object associated with the currently running task.""" - - return cur_task - - -def new_event_loop(): - """Reset the event loop and return it. - - **NOTE**: Since MicroPython only has a single event loop, this function just resets - the loop's state, it does not create a new one - """ - - global _task_queue, _io_queue - # TaskQueue of Task instances - _task_queue = TaskQueue() - # Task queue and poller for stream IO - _io_queue = IOQueue() - return Loop - - -# Initialise default event loop -new_event_loop() diff --git a/Batt_Board/lib/asyncio/event.py b/Batt_Board/lib/asyncio/event.py deleted file mode 100644 index 04f6e15..0000000 --- a/Batt_Board/lib/asyncio/event.py +++ /dev/null @@ -1,95 +0,0 @@ -# SPDX-FileCopyrightText: 2019-2020 Damien P. George -# -# SPDX-License-Identifier: MIT -# -# MicroPython uasyncio module -# MIT license; Copyright (c) 2019-2020 Damien P. George -# -# This code comes from MicroPython, and has not been run through black or pylint there. -# Altering these files significantly would make merging difficult, so we will not use -# pylint or black. -# pylint: skip-file -# fmt: off -""" -Events -====== -""" - -from . import core - -# Event class for primitive events that can be waited on, set, and cleared -class Event: - """Create a new event which can be used to synchronize tasks. Events - start in the cleared state. - """ - - def __init__(self): - self.state = False # False=unset; True=set - self.waiting = ( - core.TaskQueue() - ) # Queue of Tasks waiting on completion of this event - - def is_set(self): - """Returns ``True`` if the event is set, ``False`` otherwise.""" - - return self.state - - def set(self): - """Set the event. Any tasks waiting on the event will be scheduled to run. - """ - - # Event becomes set, schedule any tasks waiting on it - # Note: This must not be called from anything except the thread running - # the asyncio loop (i.e. neither hard or soft IRQ, or a different thread). - while self.waiting.peek(): - core._task_queue.push_head(self.waiting.pop_head()) - self.state = True - - def clear(self): - """Clear the event.""" - - self.state = False - - async def wait(self): - """Wait for the event to be set. If the event is already set then it returns - immediately. - - This is a coroutine. - """ - - if not self.state: - # Event not set, put the calling task on the event's waiting queue - self.waiting.push_head(core.cur_task) - # Set calling task's data to the event's queue so it can be removed if needed - core.cur_task.data = self.waiting - await core.sleep(0) - return True - - -# MicroPython-extension: This can be set from outside the asyncio event loop, -# such as other threads, IRQs or scheduler context. Implementation is a stream -# that asyncio will poll until a flag is set. -# Note: Unlike Event, this is self-clearing. -try: - import uio - - class ThreadSafeFlag(uio.IOBase): - def __init__(self): - self._flag = 0 - - def ioctl(self, req, flags): - if req == 3: # MP_STREAM_POLL - return self._flag * flags - return None - - def set(self): - self._flag = 1 - - async def wait(self): - if not self._flag: - yield core._io_queue.queue_read(self) - self._flag = 0 - - -except ImportError: - pass diff --git a/Batt_Board/lib/asyncio/funcs.py b/Batt_Board/lib/asyncio/funcs.py deleted file mode 100644 index 2289d33..0000000 --- a/Batt_Board/lib/asyncio/funcs.py +++ /dev/null @@ -1,114 +0,0 @@ -# SPDX-FileCopyrightText: 2019-2020 Damien P. George -# -# SPDX-License-Identifier: MIT -# -# MicroPython uasyncio module -# MIT license; Copyright (c) 2019-2020 Damien P. George -# -# This code comes from MicroPython, and has not been run through black or pylint there. -# Altering these files significantly would make merging difficult, so we will not use -# pylint or black. -# pylint: skip-file -# fmt: off -""" -Functions -========= -""" - - -from . import core - - -async def wait_for(aw, timeout, sleep=core.sleep): - """Wait for the *aw* awaitable to complete, but cancel if it takes longer - than *timeout* seconds. If *aw* is not a task then a task will be created - from it. - - If a timeout occurs, it cancels the task and raises ``asyncio.TimeoutError``: - this should be trapped by the caller. - - Returns the return value of *aw*. - - This is a coroutine. - """ - - aw = core._promote_to_task(aw) - if timeout is None: - return await aw - - async def runner(waiter, aw): - nonlocal status, result - try: - result = await aw - s = True - except BaseException as er: - s = er - if status is None: - # The waiter is still waiting, set status for it and cancel it. - status = s - waiter.cancel() - - # Run aw in a separate runner task that manages its exceptions. - status = None - result = None - runner_task = core.create_task(runner(core.cur_task, aw)) - - try: - # Wait for the timeout to elapse. - await sleep(timeout) - except core.CancelledError as er: - if status is True: - # aw completed successfully and cancelled the sleep, so return aw's result. - return result - elif status is None: - # This wait_for was cancelled externally, so cancel aw and re-raise. - status = True - runner_task.cancel() - raise er - else: - # aw raised an exception, propagate it out to the caller. - raise status - - # The sleep finished before aw, so cancel aw and raise TimeoutError. - status = True - runner_task.cancel() - await runner_task - raise core.TimeoutError - - -def wait_for_ms(aw, timeout): - """Similar to `wait_for` but *timeout* is an integer in milliseconds. - - This is a coroutine, and a MicroPython extension. - """ - - return wait_for(aw, timeout, core.sleep_ms) - - -async def gather(*aws, return_exceptions=False): - """Run all *aws* awaitables concurrently. Any *aws* that are not tasks - are promoted to tasks. - - Returns a list of return values of all *aws* - - This is a coroutine. - """ - - ts = [core._promote_to_task(aw) for aw in aws] - for i in range(len(ts)): - try: - # TODO handle cancel of gather itself - # if ts[i].coro: - # iter(ts[i]).waiting.push_head(cur_task) - # try: - # yield - # except CancelledError as er: - # # cancel all waiting tasks - # raise er - ts[i] = await ts[i] - except Exception as er: - if return_exceptions: - ts[i] = er - else: - raise er - return ts diff --git a/Batt_Board/lib/asyncio/lock.py b/Batt_Board/lib/asyncio/lock.py deleted file mode 100644 index 3b93e6a..0000000 --- a/Batt_Board/lib/asyncio/lock.py +++ /dev/null @@ -1,87 +0,0 @@ -# SPDX-FileCopyrightText: 2019-2020 Damien P. George -# -# SPDX-License-Identifier: MIT -# -# MicroPython uasyncio module -# MIT license; Copyright (c) 2019-2020 Damien P. George -# -# This code comes from MicroPython, and has not been run through black or pylint there. -# Altering these files significantly would make merging difficult, so we will not use -# pylint or black. -# pylint: skip-file -# fmt: off -""" -Locks -===== -""" - -from . import core - -# Lock class for primitive mutex capability -class Lock: - """Create a new lock which can be used to coordinate tasks. Locks start in - the unlocked state. - - In addition to the methods below, locks can be used in an ``async with`` - statement. - """ - - def __init__(self): - # The state can take the following values: - # - 0: unlocked - # - 1: locked - # - : unlocked but this task has been scheduled to acquire the lock next - self.state = 0 - # Queue of Tasks waiting to acquire this Lock - self.waiting = core.TaskQueue() - - def locked(self): - """Returns ``True`` if the lock is locked, otherwise ``False``.""" - - return self.state == 1 - - def release(self): - """Release the lock. If any tasks are waiting on the lock then the next - one in the queue is scheduled to run and the lock remains locked. Otherwise, - no tasks are waiting and the lock becomes unlocked. - """ - - if self.state != 1: - raise RuntimeError("Lock not acquired") - if self.waiting.peek(): - # Task(s) waiting on lock, schedule next Task - self.state = self.waiting.pop_head() - core._task_queue.push_head(self.state) - else: - # No Task waiting so unlock - self.state = 0 - - async def acquire(self): - """Wait for the lock to be in the unlocked state and then lock it in an - atomic way. Only one task can acquire the lock at any one time. - - This is a coroutine. - """ - - if self.state != 0: - # Lock unavailable, put the calling Task on the waiting queue - self.waiting.push_head(core.cur_task) - # Set calling task's data to the lock's queue so it can be removed if needed - core.cur_task.data = self.waiting - try: - await core.sleep(0) - except core.CancelledError as er: - if self.state == core.cur_task: - # Cancelled while pending on resume, schedule next waiting Task - self.state = 1 - self.release() - raise er - # Lock available, set it as locked - self.state = 1 - return True - - async def __aenter__(self): - return await self.acquire() - - async def __aexit__(self, exc_type, exc, tb): - return self.release() diff --git a/Batt_Board/lib/asyncio/manifest.py b/Batt_Board/lib/asyncio/manifest.py deleted file mode 100644 index 24082ff..0000000 --- a/Batt_Board/lib/asyncio/manifest.py +++ /dev/null @@ -1,24 +0,0 @@ -# SPDX-FileCopyrightText: 2019 Damien P. George -# -# SPDX-License-Identifier: MIT -# -# -# This code comes from MicroPython, and has not been run through black or pylint there. -# Altering these files significantly would make merging difficult, so we will not use -# pylint or black. -# pylint: skip-file -# fmt: off - -# This list of frozen files doesn't include task.py because that's provided by the C module. -freeze( - "..", - ( - "uasyncio/__init__.py", - "uasyncio/core.py", - "uasyncio/event.py", - "uasyncio/funcs.py", - "uasyncio/lock.py", - "uasyncio/stream.py", - ), - opt=3, -) diff --git a/Batt_Board/lib/asyncio/stream.py b/Batt_Board/lib/asyncio/stream.py deleted file mode 100644 index 97dcf6a..0000000 --- a/Batt_Board/lib/asyncio/stream.py +++ /dev/null @@ -1,263 +0,0 @@ -# SPDX-FileCopyrightText: 2019-2020 Damien P. George -# -# SPDX-License-Identifier: MIT -# -# MicroPython uasyncio module -# MIT license; Copyright (c) 2019-2020 Damien P. George -# -# This code comes from MicroPython, and has not been run through black or pylint there. -# Altering these files significantly would make merging difficult, so we will not use -# pylint or black. -# pylint: skip-file -# fmt: off -""" -Streams -======= -""" - -from . import core - - -class Stream: - """This represents a TCP stream connection. To minimise code this class - implements both a reader and a writer, and both ``StreamReader`` and - ``StreamWriter`` alias to this class. - """ - - def __init__(self, s, e={}): - self.s = s - self.e = e - self.out_buf = b"" - - def get_extra_info(self, v): - """Get extra information about the stream, given by *v*. The valid - values for *v* are: ``peername``. - """ - - return self.e[v] - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc, tb): - await self.close() - - def close(self): - pass - - async def wait_closed(self): - """Wait for the stream to close. - - This is a coroutine. - """ - - # TODO yield? - self.s.close() - - async def read(self, n): - """Read up to *n* bytes and return them. - - This is a coroutine. - """ - - core._io_queue.queue_read(self.s) - await core.sleep(0) - return self.s.read(n) - - async def readinto(self, buf): - """Read up to n bytes into *buf* with n being equal to the length of *buf* - - Return the number of bytes read into *buf* - - This is a coroutine, and a MicroPython extension. - """ - - core._io_queue.queue_read(self.s) - await core.sleep(0) - return self.s.readinto(buf) - - async def readexactly(self, n): - """Read exactly *n* bytes and return them as a bytes object. - - Raises an ``EOFError`` exception if the stream ends before reading - *n* bytes. - - This is a coroutine. - """ - - r = b"" - while n: - core._io_queue.queue_read(self.s) - await core.sleep(0) - r2 = self.s.read(n) - if r2 is not None: - if not len(r2): - raise EOFError - r += r2 - n -= len(r2) - return r - - async def readline(self): - """Read a line and return it. - - This is a coroutine. - """ - - l = b"" - while True: - core._io_queue.queue_read(self.s) - await core.sleep(0) - l2 = self.s.readline() # may do multiple reads but won't block - l += l2 - if not l2 or l[-1] == 10: # \n (check l in case l2 is str) - return l - - def write(self, buf): - """Accumulated *buf* to the output buffer. The data is only flushed when - `Stream.drain` is called. It is recommended to call `Stream.drain` - immediately after calling this function. - """ - - self.out_buf += buf - - async def drain(self): - """Drain (write) all buffered output data out to the stream. - - This is a coroutine. - """ - - mv = memoryview(self.out_buf) - off = 0 - while off < len(mv): - yield core._io_queue.queue_write(self.s) - ret = self.s.write(mv[off:]) - if ret is not None: - off += ret - self.out_buf = b"" - - -# Stream can be used for both reading and writing to save code size -StreamReader = Stream -StreamWriter = Stream - - -# Create a TCP stream connection to a remote host -async def open_connection(host, port): - """Open a TCP connection to the given *host* and *port*. The *host* address will - be resolved using `socket.getaddrinfo`, which is currently a blocking call. - - Returns a pair of streams: a reader and a writer stream. Will raise a socket-specific - ``OSError`` if the host could not be resolved or if the connection could not be made. - - This is a coroutine. - """ - - from uerrno import EINPROGRESS - import usocket as socket - - ai = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM)[ - 0 - ] # TODO this is blocking! - s = socket.socket(ai[0], ai[1], ai[2]) - s.setblocking(False) - ss = Stream(s) - try: - s.connect(ai[-1]) - except OSError as er: - if er.errno != EINPROGRESS: - raise er - core._io_queue.queue_write(s) - await core.sleep(0) - return ss, ss - - -# Class representing a TCP stream server, can be closed and used in "async with" -class Server: - """This represents the server class returned from `start_server`. It can be used in - an ``async with`` statement to close the server upon exit. - """ - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc, tb): - self.close() - await self.wait_closed() - - def close(self): - """Close the server.""" - - self.task.cancel() - - async def wait_closed(self): - """Wait for the server to close. - - This is a coroutine. - """ - - await self.task - - async def _serve(self, s, cb): - # Accept incoming connections - while True: - try: - yield core._io_queue.queue_read(s) - except core.CancelledError: - # Shutdown server - s.close() - return - try: - s2, addr = s.accept() - except: - # Ignore a failed accept - continue - s2.setblocking(False) - s2s = Stream(s2, {"peername": addr}) - core.create_task(cb(s2s, s2s)) - - -# Helper function to start a TCP stream server, running as a new task -# TODO could use an accept-callback on socket read activity instead of creating a task -async def start_server(cb, host, port, backlog=5): - """Start a TCP server on the given *host* and *port*. The *cb* callback will be - called with incoming, accepted connections, and be passed 2 arguments: reader - writer streams for the connection. - - Returns a `Server` object. - - This is a coroutine. - """ - - import usocket as socket - - # Create and bind server socket. - host = socket.getaddrinfo(host, port)[0] # TODO this is blocking! - s = socket.socket() - s.setblocking(False) - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - s.bind(host[-1]) - s.listen(backlog) - - # Create and return server object and task. - srv = Server() - srv.task = core.create_task(srv._serve(s, cb)) - return srv - - -################################################################################ -# Legacy uasyncio compatibility - - -async def stream_awrite(self, buf, off=0, sz=-1): - if off != 0 or sz != -1: - buf = memoryview(buf) - if sz == -1: - sz = len(buf) - buf = buf[off : off + sz] - self.write(buf) - await self.drain() - - -Stream.aclose = Stream.wait_closed -Stream.awrite = stream_awrite -Stream.awritestr = stream_awrite # TODO explicitly convert to bytes? diff --git a/Batt_Board/lib/asyncio/task.py b/Batt_Board/lib/asyncio/task.py deleted file mode 100644 index 9a76497..0000000 --- a/Batt_Board/lib/asyncio/task.py +++ /dev/null @@ -1,204 +0,0 @@ -# SPDX-FileCopyrightText: 2019-2020 Damien P. George -# -# SPDX-License-Identifier: MIT -# -# MicroPython uasyncio module -# MIT license; Copyright (c) 2019-2020 Damien P. George -# -# This code comes from MicroPython, and has not been run through black or pylint there. -# Altering these files significantly would make merging difficult, so we will not use -# pylint or black. -# pylint: skip-file -# fmt: off -""" -Tasks -===== -""" - -# This file contains the core TaskQueue based on a pairing heap, and the core Task class. -# They can optionally be replaced by C implementations. - -from . import core - - -# pairing-heap meld of 2 heaps; O(1) -def ph_meld(h1, h2): - if h1 is None: - return h2 - if h2 is None: - return h1 - lt = core.ticks_diff(h1.ph_key, h2.ph_key) < 0 - if lt: - if h1.ph_child is None: - h1.ph_child = h2 - else: - h1.ph_child_last.ph_next = h2 - h1.ph_child_last = h2 - h2.ph_next = None - h2.ph_rightmost_parent = h1 - return h1 - else: - h1.ph_next = h2.ph_child - h2.ph_child = h1 - if h1.ph_next is None: - h2.ph_child_last = h1 - h1.ph_rightmost_parent = h2 - return h2 - - -# pairing-heap pairing operation; amortised O(log N) -def ph_pairing(child): - heap = None - while child is not None: - n1 = child - child = child.ph_next - n1.ph_next = None - if child is not None: - n2 = child - child = child.ph_next - n2.ph_next = None - n1 = ph_meld(n1, n2) - heap = ph_meld(heap, n1) - return heap - - -# pairing-heap delete of a node; stable, amortised O(log N) -def ph_delete(heap, node): - if node is heap: - child = heap.ph_child - node.ph_child = None - return ph_pairing(child) - # Find parent of node - parent = node - while parent.ph_next is not None: - parent = parent.ph_next - parent = parent.ph_rightmost_parent - # Replace node with pairing of its children - if node is parent.ph_child and node.ph_child is None: - parent.ph_child = node.ph_next - node.ph_next = None - return heap - elif node is parent.ph_child: - child = node.ph_child - next = node.ph_next - node.ph_child = None - node.ph_next = None - node = ph_pairing(child) - parent.ph_child = node - else: - n = parent.ph_child - while node is not n.ph_next: - n = n.ph_next - child = node.ph_child - next = node.ph_next - node.ph_child = None - node.ph_next = None - node = ph_pairing(child) - if node is None: - node = n - else: - n.ph_next = node - node.ph_next = next - if next is None: - node.ph_rightmost_parent = parent - parent.ph_child_last = node - return heap - - -# TaskQueue class based on the above pairing-heap functions. -class TaskQueue: - def __init__(self): - self.heap = None - - def peek(self): - return self.heap - - def push_sorted(self, v, key): - v.data = None - v.ph_key = key - v.ph_child = None - v.ph_next = None - self.heap = ph_meld(v, self.heap) - - def push_head(self, v): - self.push_sorted(v, core.ticks()) - - def pop_head(self): - v = self.heap - self.heap = ph_pairing(self.heap.ph_child) - return v - - def remove(self, v): - self.heap = ph_delete(self.heap, v) - - -# Task class representing a coroutine, can be waited on and cancelled. -class Task: - """This object wraps a coroutine into a running task. Tasks can be waited on - using ``await task``, which will wait for the task to complete and return the - return value of the task. - - Tasks should not be created directly, rather use ``create_task`` to create them. - """ - - def __init__(self, coro, globals=None): - self.coro = coro # Coroutine of this Task - self.data = None # General data for queue it is waiting on - self.state = True # None, False, True or a TaskQueue instance - self.ph_key = 0 # Pairing heap - self.ph_child = None # Paring heap - self.ph_child_last = None # Paring heap - self.ph_next = None # Paring heap - self.ph_rightmost_parent = None # Paring heap - - def __iter__(self): - if not self.state: - # Task finished, signal that is has been await'ed on. - self.state = False - elif self.state is True: - # Allocated head of linked list of Tasks waiting on completion of this task. - self.state = TaskQueue() - return self - - __await__ = __iter__ - - def __next__(self): - if not self.state: - # Task finished, raise return value to caller so it can continue. - raise self.data - else: - # Put calling task on waiting queue. - self.state.push_head(core.cur_task) - # Set calling task's data to this task that it waits on, to double-link it. - core.cur_task.data = self - - def done(self): - """Whether the task is complete.""" - - return not self.state - - def cancel(self): - """Cancel the task by injecting a ``CancelledError`` into it. The task - may or may not ignore this exception. - """ - - # Check if task is already finished. - if not self.state: - return False - # Can't cancel self (not supported yet). - if self is core.cur_task: - raise RuntimeError("can't cancel self") - # If Task waits on another task then forward the cancel to the one it's waiting on. - while isinstance(self.data, Task): - self = self.data - # Reschedule Task as a cancelled task. - if hasattr(self.data, "remove"): - # Not on the main running queue, remove the task from the queue it's on. - self.data.remove(self) - core._task_queue.push_head(self) - elif core.ticks_diff(self.ph_key, core.ticks()) > 0: - # On the main running queue but scheduled in the future, so bring it forward to now. - core._task_queue.remove(self) - core._task_queue.push_head(self) - self.data = core.CancelledError - return True diff --git a/Batt_Board/lib/battery_functions.py b/Batt_Board/lib/battery_functions.py deleted file mode 100644 index a20dc86..0000000 --- a/Batt_Board/lib/battery_functions.py +++ /dev/null @@ -1,498 +0,0 @@ -""" -This is the class that contains all of the functions for our CubeSat. -We pass the cubesat object to it for the definitions and then it executes -our will. -Authors: Nicole Maggard and Michael Pham 12/26/2023 -""" - -import time -import alarm -import gc -import traceback -from debugcolor import co # pylint: disable=import-error - -# Import for the CAN Bus Manager -from adafruit_mcp2515.canio import ( - Message, - RemoteTransmissionRequest, -) # pylint: disable=import-error - - -class functions: - - # Placeholders for the CAN Bus Manager - FILE_IDS = { - "file1": 0x01, - "file2": 0x02, - "file3": 0x03, - # Add more files as needed - } - - def debug_print(self, statement): - if self.debug: - print(co("[BATTERY][Functions]" + str(statement), "green", "bold")) - - def __init__(self, cubesat): - self.cubesat = cubesat - self.debug = cubesat.debug - self.debug_print("Initializing Functionalities") - self.error_count = 0 - self.face_id = 0x00AA - self.facestring = [] - self.state_list = [] - self.last_battery_temp = 20 - self.state_bool = False - self.detumble_enable_z = True - self.detumble_enable_x = True - self.detumble_enable_y = True - try: - self.cubesat.all_faces_on() - except Exception as e: - self.debug_print( - "Couldn't turn faces on: " + "".join(traceback.format_exception(e)) - ) # pylint: disable=no-value-for-parameter - self.cubesat.all_faces_off() - - # =======================================================# - # Satellite Management Functions # - # =======================================================# - - def battery_heater(self): - """ - Battery Heater Function reads temperature at the end of the thermocouple and tries to - warm the batteries until they are roughly +4C above what the batteries should normally sit(this - creates a band stop in which the battery heater never turns off) The battery heater should not run - forever, so a time based stop is implemented - """ - try: - try: - self.last_battery_temp = self.cubesat.battery_temperature() - except Exception as e: - self.debug_print( - "[ERROR] couldn't get thermocouple data!" - + "".join(traceback.format_exception(e)) - ) # pylint: disable=no-value-for-parameter - raise RuntimeError("Thermocouple failure!") from e - - if self.last_battery_temp < self.cubesat.NORMAL_BATT_TEMP: - end_time = 0 - self.cubesat.heater_on() - while ( - self.last_battery_temp < self.cubesat.NORMAL_BATT_TEMP + 4 - and end_time < 5 - ): - time.sleep(1) - self.last_battery_temp = self.cubesat.battery_temperature() - end_time += 1 - self.debug_print( - str( - f"Heater has been on for {end_time} seconds and the battery temp is {self.last_battery_temp}C" - ) - ) - return True - - else: - self.debug_print("Battery is already warm enough") - return False - - except Exception as e: - self.debug_print( - "Error Initiating Battery Heater" - + "".join(traceback.format_exception(e)) - ) # pylint: disable=no-value-for-parameter - return False - finally: - self.cubesat.heater_off() - - def current_check(self): - return self.cubesat.current_draw - - def check_reboot(self): - self.cubesat.UPTIME = self.cubesat.uptime - self.debug_print(str("Current up time: " + str(self.cubesat.UPTIME))) - if self.cubesat.UPTIME > 86400: - self.cubesat.reset_vbus() - - def battery_manager(self): - self.debug_print("Started to manage battery") - try: - vchrg = self.cubesat.charge_voltage - vbatt = self.cubesat.battery_voltage - ichrg = self.cubesat.charge_current - idraw = self.cubesat.current_draw - vsys = self.cubesat.system_voltage - micro_temp = self.cubesat.micro.cpu.temperature - - self.debug_print("MICROCONTROLLER Temp: {} C".format(micro_temp)) - self.debug_print( - f"Internal Temperature: {self.cubesat.internal_temperature} C" - ) - except Exception as e: - self.debug_print( - "Error obtaining battery data: " - + "".join(traceback.format_exception(e)) - ) # pylint: disable=no-value-for-parameter - - try: - self.debug_print(f"charge current: {ichrg}mA, and charge voltage: {vbatt}V") - self.debug_print( - "draw current: {}mA, and battery voltage: {}V".format(idraw, vbatt) - ) - self.debug_print("system voltage: {}V".format(vsys)) - - # This check is currently unused, just a notification for debugging - if idraw > ichrg: - self.debug_print( - "Beware! The Satellite is drawing more power than receiving" - ) - - if vbatt < self.cubesat.CRITICAL_BATTERY_VOLTAGE: - self.powermode("crit") - self.debug_print( - "CONTEXT SHIFT INTO CRITICAL POWER MODE: Attempting to shutdown ALL systems..." - ) - - elif vbatt < self.cubesat.NORMAL_BATTERY_VOLTAGE: - self.powermode("low") - self.debug_print( - "CONTEXT SHIFT INTO LOW POWER MODE: Attempting to shutdown unnecessary systems..." - ) - - elif vbatt > self.cubesat.NORMAL_BATTERY_VOLTAGE + 0.5: - self.powermode("max") - self.debug_print( - "CONTEXT SHIFT INTO MAXIMUM POWER MODE: Attempting to revive all systems..." - ) - - elif ( - vbatt < self.cubesat.NORMAL_BATTERY_VOLTAGE + 0.3 - and self.cubesat.power_mode == "maximum" - ): # We are passing data in a weird way here, please check - self.powermode("norm") - self.debug_print( - "CONTEXT SHIFT INTO NORMAL POWER MODE: Attempting to revive necessary systems..." - ) - - except Exception as e: - self.debug_print( - "Error in Battery Manager: " + "".join(traceback.format_exception(e)) - ) # pylint: disable=no-value-for-parameter - - def powermode(self, mode): - """ - Configure the hardware for minimum or normal power consumption - Add custom modes for mission-specific control - """ - try: - if "crit" in mode: - self.cubesat.neopixel.brightness = 0 - self.cubesat.enable_rf.value = False - self.cubesat.power_mode = "critical" - - elif "low" in mode: - self.cubesat.neopixel.brightness = 0 - self.cubesat.enable_rf.value = False - self.cubesat.power_mode = "low" - - elif "norm" in mode: - self.cubesat.enable_rf.value = True - self.cubesat.power_mode = "normal" - # don't forget to reconfigure radios, gps, etc... - - elif "max" in mode: - self.cubesat.enable_rf.value = True - self.cubesat.power_mode = "maximum" - - except Exception as e: - self.debug_print( - "Error in changing operations of powermode: " - + "".join(traceback.format_exception(e)) - ) # pylint: disable=no-value-for-parameter - - # =======================================================# - # State of Health Functions # - # =======================================================# - - def state_of_health(self): - self.state_list = [] - # list of state information - try: - self.state_list = [ - f"PM:{self.cubesat.power_mode}", - f"VB:{self.cubesat.battery_voltage}", - f"ID:{self.cubesat.current_draw}", - f"IC:{self.cubesat.charge_current}", - f"VS:{self.cubesat.system_voltage}", - f"UT:{self.cubesat.uptime}", - f"BN:{self.cubesat.c_boot}", - f"MT:{self.cubesat.micro.cpu.temperature}", - f"AT:{self.cubesat.internal_temperature}", - f"BT:{self.last_battery_temp}", - f"BO:{int(self.cubesat.f_brownout)}", - ] - except Exception as e: - self.debug_print( - "Couldn't aquire data for the state of health: " - + "".join(traceback.format_exception(e)) - ) # pylint: disable=no-value-for-parameter - try: - self.debug_print("Sending Face Data to FC") - self.send_can(self.state_list) - except Exception as e: - self.debug_print( - "Error Sending data over CAN bus" - + "".join(traceback.format_exception(e)) - ) # pylint: disable=no-value-for-parameter - - # =======================================================# - # Big Data Functions # - # =======================================================# - def face_toggle(self, face, state): - dutycycle = 0x0000 - if state: - duty_cycle = 0xFFFF - - if face == "Face0": - self.cubesat.Face0.duty_cycle = duty_cycle - elif face == "Face1": - self.cubesat.Face0.duty_cycle = duty_cycle - elif face == "Face2": - self.cubesat.Face0.duty_cycle = duty_cycle - elif face == "Face3": - self.cubesat.Face0.duty_cycle = duty_cycle - elif face == "Face4": - self.cubesat.Face0.duty_cycle = duty_cycle - elif face == "Face5": - self.cubesat.Face0.duty_cycle = duty_cycle - - def get_imu_data(self): - - self.cubesat.all_faces_on() - data = (0, 0, 0) - try: - self.debug_print("Attempting to get data from Flight Controller") - data = self.get_CAN("IMU") - except Exception as e: - self.debug_print( - "Error retrieving IMU data" + "".join(traceback.format_exception(e)) - ) - - return data - - # =======================================================# - # Interboard Communitication Functions # - # =======================================================# - - def send_face(self): - try: - self.debug_print("Sending Face Data to FC") - for x in self.facestring: - self.send_can(self.face_id, x) - except Exception as e: - self.debug_print( - "Error Sending data over CAN bus" - + "".join(traceback.format_exception(e)) - ) # pylint: disable=no-value-for-parameter - - # Example of how the calling class might handle the result - # can_helper = CanBusHelper(can_bus, owner, debug) - # - # result = can_helper.listen_messages(timeout=5) - # if result is not None: - # if result['type'] == 'RTR': - # # Handle Remote Transmission Request - # data_to_send = retrieve_data_based_on_rtr_id(result['id']) - # can_helper.send_can("DATA_RESPONSE", data_to_send) - # elif result['type'] == 'FAULT': - # # Handle Fault Message - # handle_fault(result['content']) - - def send_can(self, id, messages): - if not isinstance(messages, list): - messages = [messages] # If messages is not a list, make it a list - try: - for message in messages: - message = str(message) - if isinstance(message, str): - byte_message = bytes(message, "UTF-8") - else: - byte_message = bytes(message) - self.cubesat.can_bus.send(Message(id, byte_message)) - self.debug_print("Sent CAN message: " + str(message)) - except Exception as e: - self.debug_print( - "Error Sending data over CAN bus" - + "".join(traceback.format_exception(None, e, e.__traceback__)) - ) # pylint: disable=no-value-for-parameter - - # Made by CoPilot - Probably Not Working - def listen_can_messages(self): - with self.cubesat.can_bus.listen(timeout=1.0) as listener: - message_count = listener.in_waiting() - self.debug_print(str(message_count) + " messages available") - for _i in range(message_count): - msg = listener.receive() - self.debug_print("Message from " + hex(msg.id)) - - # We aren't sure if isinstance checks currently work - if isinstance(msg, Message): - self.debug_print("message data: " + str(msg.data)) - if isinstance(msg, RemoteTransmissionRequest): - self.debug_print("RTR length: " + str(msg.length)) - # Here you can process the RTR request - # For example, you might send a response with the requested data - response_data = self.get_data_for_rtr(msg.id) - if isinstance(response_data, list): - response_messages = [ - Message(id=msg.id, data=data, extended=True) - for data in response_data - ] - else: - response_messages = [ - Message(id=msg.id, data=response_data, extended=True) - ] - self.cubesat.send_can(response_messages) - - def get_data_for_rtr(self, id): - - if id == 0x01: # Replace with the actual ID for all_face_data - all_face_data = bytes( - self.all_face_data() - ) # This should return a bytes object - messages = [] - start_message = Message(id=0x01, data=b"start", extended=True) - messages.append(start_message) - for i in range(0, len(all_face_data), 8): - chunk = all_face_data[i : i + 8] - message = Message(id=0x02, data=chunk, extended=True) - messages.append(message) - stop_message = Message(id=0x03, data=b"stop", extended=True) - messages.append(stop_message) - return messages - - elif id == 0x02: # Replace with the actual ID for sensor 2 - return self.get_sensor_2_data() - elif id == 0x03: # Replace with the actual ID for sensor 3 - return self.get_sensor_3_data() - else: - # Default case if no matching ID is found - return bytes([0x01, 0x02, 0x03, 0x04]) - - def request_file(self, file_id, timeout=5.0): - # Send RTR for the file - rtr = RemoteTransmissionRequest(id=file_id) - self.cubesat.can_bus.send(rtr) - - # Listen for response and reconstruct the file - file_data = bytearray() - start_time = time.monotonic() - while True: - if time.monotonic() - start_time > timeout: - raise TimeoutError("No response received for file request") - msg = self.cubesat.can_bus.receive() - if msg is None: - continue # No message received, continue waiting - if isinstance(msg, Message) and msg.id == file_id: - if msg.data == b"start": - continue - elif msg.data == b"stop": - break - else: - file_data.extend(msg.data) - return file_data - - def OTA(self): - # resets file system to whatever new file is received - pass - - # =======================================================# - # Misc Functions # - # =======================================================# - - # Should be nuke the detumble function from this file? - def detumble(self, dur=7, margin=0.2, seq=118): - self.debug_print("Detumbling") - self.cubesat.RGB = (255, 255, 255) - self.cubesat.all_faces_on() - try: - import Big_Data - - a = Big_Data.AllFaces(self.debug, self.cubesat.tca) - except Exception as e: - self.debug_print( - "Error Importing Big Data: " + "".join(traceback.format_exception(e)) - ) - - try: - a.sequence = 52 - except Exception as e: - self.debug_print( - "Error setting motor driver sequences: " - + "".join(traceback.format_exception(e)) - ) - - def actuate(dipole, duration): - # TODO figure out if there is a way to reverse direction of sequence - if abs(dipole[0]) > 1: - a.Face2.drive = 52 - a.drvx_actuate(duration) - if abs(dipole[1]) > 1: - a.Face0.drive = 52 - a.drvy_actuate(duration) - if abs(dipole[2]) > 1: - a.Face4.drive = 52 - a.drvz_actuate(duration) - - def do_detumble(): - try: - import detumble - - for _ in range(3): - data = self.get_imu_data() - data[0] = list(data[0]) - for x in range(3): - if data[0][x] < 0.01: - data[0][x] = 0.0 - data[0] = tuple(data[0]) - dipole = detumble.magnetorquer_dipole(data[1], data[0]) - self.debug_print("Dipole: " + str(dipole)) - time.sleep(1) - actuate(dipole, dur) - - except Exception as e: - self.debug_print( - "Detumble error: " + "".join(traceback.format_exception(e)) - ) - - try: - self.debug_print("Attempting") - do_detumble() - except Exception as e: - self.debug_print( - "Detumble error: " + "".join(traceback.format_exception(e)) - ) - self.cubesat.RGB = (100, 100, 50) - - def Short_Hybernate(self): - self.debug_print("Short Hybernation Coming UP") - gc.collect() - # all should be off from cubesat powermode - self.cubesat.all_faces_off() - self.cubesat.enable_rf.value = False - self.cubesat.f_softboot = True - time.sleep(120) - self.cubesat.all_faces_on() - self.cubesat.enable_rf.value = True - return True - - def Long_Hybernate(self): - self.debug_print("LONG Hybernation Coming UP") - gc.collect() - # all should be off from cubesat powermode - self.cubesat.all_faces_off() - self.cubesat.enable_rf.value = False - self.cubesat.f_softboot = True - time.sleep(600) - self.cubesat.all_faces_on() - self.cubesat.enable_rf.value = True - return True diff --git a/Batt_Board/lib/bitflags.py b/Batt_Board/lib/bitflags.py deleted file mode 100644 index 5329150..0000000 --- a/Batt_Board/lib/bitflags.py +++ /dev/null @@ -1,72 +0,0 @@ -class bitFlag: - """ - Single bit register WITHIN a byte that can be read or set - values are 'bool' - - Assumes register is read MSB! - - """ - - def __init__(self, register, bit): - self.bit_mask = 1 << (bit % 8) # the bitmask *within* the byte! - self.byte = register - - def __get__(self, obj, objtype=None): - return bool(obj.micro.nvm[self.byte] & self.bit_mask) - - def __set__(self, obj, value): - if value: - obj.micro.nvm[self.byte] |= self.bit_mask - else: - obj.micro.nvm[self.byte] &= ~self.bit_mask - - -class multiBitFlag: - """ - Multi-bit value WITHIN a byte that can be read or set - values are int - - Assumes register is read MSB! - 0x80 = 0b10000000 - bit#:76543210 - """ - - def __init__(self, num_bits, register, lowest_bit): - self.maxval = (1 << num_bits) - 1 - self.bit_mask = self.maxval << lowest_bit - self.lowest_bit = lowest_bit - self.byte = register - # if self.bit_mask >= 1 << 8: - # raise ValueError("Cannot have more bits than register size") - - def __get__(self, obj, objtype=None): - return (obj.micro.nvm[self.byte] & self.bit_mask) >> self.lowest_bit - - def __set__(self, obj, value): - if value >= self.maxval: - value = self.maxval - value <<= self.lowest_bit - reg = obj.micro.nvm[self.byte] - reg &= ~self.bit_mask - obj.micro.nvm[self.byte] = reg | value - - -class multiByte: - """ - must be whole bytes - MSB - """ - - def __init__(self, num_bytes, lowest_register): - self.maxval = (1 << (8 * num_bytes)) - 1 - self.start = lowest_register - self.stop = lowest_register + num_bytes - self.num_bytes = num_bytes - - def __get__(self, obj, objtype=None): - return int.from_bytes(obj.micro.nvm[self.start : self.stop], "big") - - def __set__(self, obj, value): - if value >= self.maxval: - value = self.maxval - obj.micro.nvm[self.start : self.stop] = value.to_bytes(self.num_bytes, "big") diff --git a/Batt_Board/lib/can_bus_helper.py b/Batt_Board/lib/can_bus_helper.py deleted file mode 100644 index 043f6d7..0000000 --- a/Batt_Board/lib/can_bus_helper.py +++ /dev/null @@ -1,462 +0,0 @@ -import traceback -import time - -from adafruit_mcp2515.canio import ( - Message, - RemoteTransmissionRequest, -) # pylint: disable=import-error -from debugcolor import co # pylint: disable=import-error - -# There may be an AI induced error with the traceback statements - - -class CanBusHelper: - def __init__(self, can_bus, owner, debug): - self.can_bus = can_bus - self.owner = owner - self.debug = debug - self.multi_message_buffer = {} - self.current_id = 0x00 - self.MESSAGE_IDS = { - "BOOT_SEQUENCE": 0x01, - "CRITICAL_POWER_OPERATIONS": 0x02, - "LOW_POWER_OPERATIONS": 0x03, - "NORMAL_POWER_OPERATIONS": 0x04, - "FAULT_ID": 0x1A4, - "SOT_ID": 0xA5, - "EOT_ID": 0xA6, - # Add more message IDs as needed - } - - def debug_print(self, statement): - if self.debug: - print(co("[CAN_BUS][Communications]" + str(statement), "orange", "bold")) - - # BROKEN - def construct_messages(self, id, messages): - if not isinstance(messages, list): - messages = [messages] - message_objects = [] - sequence_number = 0 # Initialize sequence number - - for message in messages: - message = str(message) - byte_message = bytes(message, "UTF-8") - - for i in range(0, len(byte_message), 8): - chunk = byte_message[i : i + 8] - - if len(byte_message) > 8: - # Use the sequence number across all messages in the list - extended_id = ((id & 0x7F) << 22) | sequence_number - message_objects.append(Message(extended_id, chunk, extended=True)) - sequence_number += 1 # Increment sequence number for next chunk - else: - # For single message, keep the original ID - message_objects.append(Message(id, chunk)) - - return message_objects - - def send_can(self, id_str, data, timeout=5): - if id_str in self.MESSAGE_IDS: - id_byte = self.MESSAGE_IDS[id_str] - else: - # Handle the case where id_str is not in MESSAGE_IDS - raise ValueError(f"Invalid ID string: {id_str}") - - # Construct the messages - messages = self.construct_messages(id_byte, data) - - # Use SOT and EOT only for multi-message transmissions - if len(messages) > 1: - # Initiate handshake by sending SOT and waiting for ACK - if not self.send_sot(id_byte, len(messages)): - self.debug_print("Handshake failed: SOT not acknowledged") - return False - - # Send the messages and wait for ACK - if not self.send_messages(messages, timeout): - return False - - if len(messages) > 1: - # Send EOT after sending all messages - self.send_eot() - - return True - - def send_messages(self, messages, timeout=1): - """ - Sends the given messages and waits for acknowledgments. - """ - for i, message in enumerate(messages): - attempts = 0 - ack_received = False - while attempts < 3 and not ack_received: - try: - self.can_bus.send(message) - self.debug_print("Sent CAN message: " + str(message)) - ack_received = self.wait_for_ack( - expected_ack_id=message.id, timeout=1.0 - ) - if not ack_received: - attempts += 1 - self.debug_print( - f"ACK not received for message {i}. Attempt {attempts}" - ) - except Exception as e: - self.debug_print( - "Error Sending data over CAN bus" - + "".join(traceback.format_exception(None, e, e.__traceback__)) - ) - break - - if not ack_received: - self.debug_print( - f"Failed to receive ACK after {attempts} attempts for message {i}." - ) - return False - - return True - - def wait_for_ack(self, expected_ack_id, timeout): - """ - Waits for an ACK message with the specified ID within the given timeout period. - """ - start_time = time.monotonic() - while time.monotonic() - start_time < timeout: - ack_id = self.receive_ack_message() - - if ack_id is not None and ack_id == expected_ack_id: - return True - self.debug_print("No ACK received") - return False - - # =======================================================# - # ChatGPT Go Crazy # - # =======================================================# - - def listen_on_can_bus(self, process_message_callback, timeout=1.0): - """ - General purpose function to listen on the CAN bus and process messages using a callback function. - Add identical message rejection? - """ - with self.can_bus.listen(timeout=timeout) as listener: - message_count = listener.in_waiting() - for _ in range(message_count): - try: - msg = listener.receive() - result = process_message_callback(msg) - if result is not None: - return result - except Exception as e: - self.debug_print( - "Error processing message: " - + "".join(traceback.format_exception(None, e, e.__traceback__)) - ) - - def process_general_message(self, msg): - """ - Callback function for general message processing. - """ - # Send an ACK for the received message - self.send_ack(msg.id, is_extended=msg.extended) - - if isinstance(msg, RemoteTransmissionRequest): - return self.handle_remote_transmission_request(msg) - elif msg.id == self.MESSAGE_IDS["FAULT_ID"]: - return {"type": "FAULT", "content": msg.data} - elif msg.id == self.MESSAGE_IDS["SOT_ID"]: - self.handle_sot_message(msg) - elif msg.id == self.MESSAGE_IDS["EOT_ID"]: - self.handle_eot_message(msg) - elif msg.extended: - self.handle_multi_message(msg) - else: - self.handle_single_message(msg) - return None - - def send_ack(self, msg_id, is_extended=False): - """ - Sends an ACK message with the given message ID. - Args: - msg_id (int): The ID of the message to acknowledge. - is_extended (bool): True if the message ID is an extended ID, False otherwise. - """ - ack_data = b"ACK" # ACK message content - try: - ack_message = Message(id=msg_id, data=ack_data, extended=is_extended) - self.can_bus.send(ack_message) - self.debug_print(f"Sent ACK for message ID: {hex(msg_id)}") - except Exception as e: - self.debug_print( - "Error sending ACK: " - + "".join(traceback.format_exception(None, e, e.__traceback__)) - ) - - # =======================================================# - # Receive Handler Functions # - # =======================================================# - - def handle_remote_transmission_request(self, rtr): - """ - Handles a Remote Transmission Request and returns RTR info. - """ - # Implement handling of RTR - self.debug_print("RTR length: " + str(rtr.length)) - # Example: Return RTR ID - return {"type": "RTR", "id": rtr.id} - - def handle_multi_message(self, msg): - """ - Handles a part of a multi-message sequence. - """ - # Extract the original ID and the sequence number from the extended ID - original_id = msg.id >> 22 - sequence_number = msg.id & 0x3FFFFF # Mask to get the lower 22 bits - - self.debug_print( - f"Received multi-message chunk for ID: {original_id} with sequence number: {sequence_number}" - ) - - if ( - str(original_id) in self.multi_message_buffer - and not self.multi_message_buffer[str(original_id)]["is_complete"] - ): - # Store this chunk in the buffer - self.multi_message_buffer[str(original_id)]["received_chunks"][ - sequence_number - ] = msg.data - - # Check if all parts of the message have been received - # This can be done based on your protocol's specifics - if self.check_if_complete(original_id): - complete_message = self.reassemble_message(original_id) - # Process the complete message - self.process_complete_message(original_id, complete_message) - - else: - self.debug_print( - f"Unexpected multi-message chunk received for ID: {original_id}" - ) - - def check_if_complete(self, original_id): - """ - Checks if all parts of a multi-message sequence have been received. - """ - # Implement logic to determine if all parts are received - # This might involve checking sequence numbers, expected length, etc. - buffer = self.multi_message_buffer[str(original_id)] - return len(buffer["received_chunks"]) == buffer["expected_length"] - - def check_ack_message(self, msg): - return msg.id if msg.data == b"ACK" else None - - def reassemble_message(self, original_id): - """ - Reassembles all parts of a multi-message sequence into the complete message. - """ - buffer = self.multi_message_buffer[str(original_id)] - complete_message = b"".join( - buffer["received_chunks"][seq] for seq in sorted(buffer["received_chunks"]) - ) - buffer["is_complete"] = True - return complete_message - - def handle_single_message(self, msg): - """ - Handles a single message. Pretty much only used for debug. - """ - # Process a single, non-extended message - self.debug_print(f"Received single message with ID: {msg.id}") - self.debug_print(f"Message data: {msg.data}") - - def process_complete_message(self, original_id, message): - """ - Processes the complete reassembled message. - """ - # Implement your logic to handle the complete message - self.debug_print(f"Received complete message for ID: {original_id}") - self.debug_print(f"Message data: {message}") - - # =======================================================# - # Wrapper Functions # - # =======================================================# - - def receive_ack_message(self): - """ - Wrapper function to receive an ACK message. - """ - return self.listen_on_can_bus(self.check_ack_message, timeout=1.0) - - def listen_messages(self, timeout=1.0): - """ - Wrapper function to listen to general messages. - """ - return self.listen_on_can_bus(self.process_general_message, timeout) - - # =======================================================# - # Handshaking Functions # - # =======================================================# - - def send_sot(self, original_id, data_length): - """ - Sends a Start-of-Transmission message with the expected data length. - """ - sot_id = self.MESSAGE_IDS["SOT_ID"] - - # Combine the original_id and data_length into a single string, separated by a special character - data = f"{original_id}:{data_length}" - sot_message = Message(sot_id, data=bytes(data, "utf-8"), extended=False) - - try: - self.can_bus.send(sot_message) - self.debug_print( - f"Sent SOT for ID: {sot_id} with data length: {data_length}" - ) - except Exception as e: - self.debug_print( - "Error sending SOT: " - + "".join(traceback.format_exception(None, e, e.__traceback__)) - ) - - # Wait for ACK - return self.wait_for_ack(sot_id, 1.0) - - def handle_sot_message(self, msg): - """ - Processes the Start-of-Transmission (SOT) message. - """ - # Extract the data length from the message - try: - original_id, data_length = msg.data.decode("utf-8").split( - ":" - ) # Assuming data is sent as a string - data_length = int(data_length) - self.debug_print( - f"Received SOT for ID: {msg.id} with expected data length: {data_length}" - ) - except ValueError: - self.debug_print(f"Invalid data length format in SOT message: {msg.data}") - return - - # Send ACK for SOT message - self.send_ack(msg.id) - - # Initialize the buffer for the upcoming data stream - - if original_id not in self.multi_message_buffer: - self.multi_message_buffer[original_id] = { - "expected_length": data_length, - "received_chunks": {}, - "is_complete": False, - } - else: - # Reset the buffer if it already exists for this ID - self.multi_message_buffer[original_id]["expected_length"] = data_length - self.multi_message_buffer[original_id]["received_chunks"].clear() - self.multi_message_buffer[original_id]["is_complete"] = False - - self.debug_print(f"Initialized buffer for multi-message ID: {original_id}") - - def send_eot(self): - """ - Sends an End-of-Transmission message. - """ - eot_id = self.MESSAGE_IDS["EOT_ID"] - - eot_message = Message(eot_id, data=b"EOT", extended=False) - try: - self.can_bus.send(eot_message) - self.debug_print(f"Sent EOT for ID: {eot_id}") - except Exception as e: - self.debug_print( - "Error sending EOT: " - + "".join(traceback.format_exception(None, e, e.__traceback__)) - ) - - def handle_eot_message(self, msg): - """ - Processes the End-of-Transmission message. - """ - original_id = msg.id # Assuming the original ID is used in the EOT message - - # Validate the EOT message - - # Perform any cleanup or final processing - - # Send ACK for the EOT message - self.send_ack(msg.id) - - self.debug_print(f"Processed EOT for ID: {original_id}") - - # =======================================================# - # File Transfer Functions # - # =======================================================# - - def send_rtr_and_receive(self, rtr_id, timeout=5.0): - """ - Sends an RTR and waits for a response, which could be either single or multi-message. - """ - # Send RTR - rtr_message = RemoteTransmissionRequest(id=rtr_id) - try: - self.can_bus.send(rtr_message) - self.debug_print(f"Sent RTR with ID: {hex(rtr_id)}") - except Exception as e: - self.debug_print( - "Error sending RTR: " - + "".join(traceback.format_exception(None, e, e.__traceback__)) - ) - return None - - # Listen for responses - start_time = time.monotonic() - while time.monotonic() - start_time < timeout: - response = self.receive_response() - if response: - # Process response - if response["type"] == "SINGLE": - return response["data"] # Return single message data - elif response["type"] == "MULTI": - # Handle multi-message sequence - # You can either wait for the full sequence here or return and handle it elsewhere - pass - - return None - - def receive_response(self): - """ - Listens for a single message or the start of a multi-message sequence. - """ - msg = self.can_bus.receive() - if msg: - if msg.extended: - # Start of a multi-message sequence - self.handle_multi_message(msg) - return {"type": "MULTI"} - else: - # Single message response - return {"type": "SINGLE", "data": msg.data} - return None - - def request_file(self, file_id, timeout=5.0): - # Code from request_file goes here - rtr = RemoteTransmissionRequest(id=file_id) - self.can_bus.send(rtr) - - file_data = bytearray() - start_time = time.monotonic() - while True: - if time.monotonic() - start_time > timeout: - raise TimeoutError("No response received for file request") - msg = self.can_bus.receive() - if msg is None: - continue - if isinstance(msg, Message) and msg.id == file_id: - if msg.data == b"start": - continue - elif msg.data == b"stop": - break - else: - file_data.extend(msg.data) - return file_data diff --git a/Batt_Board/lib/debugcolor.py b/Batt_Board/lib/debugcolor.py deleted file mode 100644 index ca7bbb3..0000000 --- a/Batt_Board/lib/debugcolor.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -from debugcolor import co -print(co(msg='hello!',color='red')) -""" - -_h = "\033[" -_e = "\033[0;39;49m" - -_c = { - "red": "1", - "green": "2", - "orange": "3", - "blue": "4", - "pink": "5", - "teal": "6", - "white": "7", - "gray": "9", -} - -_f = {"normal": "0", "bold": "1", "ulined": "4"} - - -def co(msg, color="gray", fmt="normal"): - return _h + _f[fmt] + ";3" + _c[color] + "m" + msg + _e diff --git a/Batt_Board/lib/neopixel.mpy b/Batt_Board/lib/neopixel.mpy deleted file mode 100755 index e79666f32d940cbbc51a02556fc36be931333279..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1318 zcmY*WT~ixX7=AaOOB&(iWH|&9X-XiJ010eES|PTOY|4iS1VPMbXGt~-*)e2yHk(53 zHmM)Fxwf2Ce<{qF8S>5V@1k}A!bfKf5X}}e9<*;N_*7P>g&4orxZO#Sc6G`q$A$F*S;s5-H`EpGLO!r&}0*yU4mF|Gj(u~>fl?+3x z&c|X@^ir)5rTb%7GRKy#^iC_fM0MY)8zr?OUda-rJ-}LLqciq_D;~wI&ArUFxVrIf zW_u?ifW7zs8F;)t6=zJR=?sU%>-SUq)TT6A431uW`)1Z}Ar^XCiItujav!MO3cR}1Sjxm)SN%uubRUju|@iyI*a?@xlQgM zOx}2B3LYV@5Moe&VH=S`77Y|yH-I5No+0DBg+zEOndEI`$}`W~;|!VM9b}ew622v| zpOwG*!aQAE^erSqGh9CzF!fM$BD^*loZg6xh9aSn$Zon8jGU!6zJK&m5O7~Ag*`7) zIsE1sz8kj)TRfb_nP1T$H}q42d68PowtaqV@dmuFo4cR>Dg0sLd)SJGvvKRwm%=Yy zFJz(KZ|B*+em_sIbf#3xLy)AqE}7Q!~~sCTUG4f(>9y~fNEduze0O+rh2tLHgJ?cErcnvEp%DH7CNyon diff --git a/Batt_Board/lib/pysquared_eps.py b/Batt_Board/lib/pysquared_eps.py deleted file mode 100644 index d1626fd..0000000 --- a/Batt_Board/lib/pysquared_eps.py +++ /dev/null @@ -1,903 +0,0 @@ -""" -CircuitPython driver for PySquared satellite board. -PySquared Hardware Version: batteryboard v3c -CircuitPython Version: 9.0.0 alpha -Library Repo: - -* Author(s): Nicole Maggard and Michael Pham -""" - -# Common CircuitPython Libs -import board, microcontroller -import busio, time, sys, traceback -import digitalio, pwmio -from debugcolor import co -import gc - -# Hardware Specific Libs -import neopixel # RGB LED -import adafruit_pca9685 # LED Driver -import adafruit_pct2075 # Temperature Sensor -import adafruit_max31856 # Thermocouple -import adafruit_vl6180x # LiDAR Distance Sensor for Antenna -import adafruit_ina219 # Power Monitor - -# CAN Bus Import -from adafruit_mcp2515 import MCP2515 as CAN - -# Common CircuitPython Libs -from os import listdir, stat, statvfs, mkdir, chdir -from bitflags import bitFlag, multiBitFlag, multiByte -from micropython import const - -# NVM register numbers -_BOOTCNT = const(0) -_VBUSRST = const(6) -_STATECNT = const(7) -_TOUTS = const(9) -_ICHRG = const(11) -_DIST = const(13) -_FLAG = const(16) - -SEND_BUFF = bytearray(252) - - -class Satellite: - # General NVM counters - c_boot = multiBitFlag(register=_BOOTCNT, lowest_bit=0, num_bits=8) - c_vbusrst = multiBitFlag(register=_VBUSRST, lowest_bit=0, num_bits=8) - c_state_err = multiBitFlag(register=_STATECNT, lowest_bit=0, num_bits=8) - c_distance = multiBitFlag(register=_DIST, lowest_bit=0, num_bits=8) - c_ichrg = multiBitFlag(register=_ICHRG, lowest_bit=0, num_bits=8) - - # Define NVM flags - f_softboot = bitFlag(register=_FLAG, bit=0) - f_solar = bitFlag(register=_FLAG, bit=1) - f_burnarm = bitFlag(register=_FLAG, bit=2) - f_brownout = bitFlag(register=_FLAG, bit=3) - f_triedburn = bitFlag(register=_FLAG, bit=4) - f_shtdwn = bitFlag(register=_FLAG, bit=5) - f_burned = bitFlag(register=_FLAG, bit=6) - f_fsk = bitFlag(register=_FLAG, bit=7) - - # Turns all of the Faces On (Defined before init because this fuction is called by the init) - def all_faces_on(self): - # Faces MUST init in this order or the uController will brown out. Cause unknown - if self.hardware["FLD"]: - self.Face0.duty_cycle = 0xFFFF - self.hardware["Face0"] = True - self.Face1.duty_cycle = 0xFFFF - self.hardware["Face1"] = True - self.Face2.duty_cycle = 0xFFFF - self.hardware["Face2"] = True - self.Face3.duty_cycle = 0xFFFF - self.hardware["Face3"] = True - self.Face4.duty_cycle = 0xFFFF - self.hardware["Face4"] = True - self.cam.duty_cycle = 0xFFFF - - def all_faces_off(self): - # De-Power Faces - if self.hardware["FLD"]: - self.Face0.duty_cycle = 0x0000 - time.sleep(0.1) - self.hardware["Face0"] = False - self.Face1.duty_cycle = 0x0000 - time.sleep(0.1) - self.hardware["Face1"] = False - self.Face2.duty_cycle = 0x0000 - time.sleep(0.1) - self.hardware["Face2"] = False - self.Face3.duty_cycle = 0x0000 - time.sleep(0.1) - self.hardware["Face3"] = False - self.Face4.duty_cycle = 0x0000 - time.sleep(0.1) - self.hardware["Face4"] = False - time.sleep(0.1) - self.cam.duty_cycle = 0x0000 - - def debug_print(self, statement): - if self.debug: - print(co("[BATTERY][Pysquared]" + str(statement), "red", "bold")) - - def __init__(self): - """ - Big init routine as the whole board is brought up. - """ - self.debug = True # Define verbose output here. True or False - self.BOOTTIME = 1577836800 - self.debug_print(f"Boot time: {self.BOOTTIME}s") - self.CURRENTTIME = self.BOOTTIME - self.UPTIME = 0 - self.heating = False - self.NORMAL_TEMP = 20 - self.NORMAL_BATT_TEMP = 1 # Set to 0 BEFORE FLIGHT!!!!! - self.NORMAL_MICRO_TEMP = 20 - self.NORMAL_CHARGE_CURRENT = 0.5 - self.NORMAL_BATTERY_VOLTAGE = 6.9 # 6.9 - self.CRITICAL_BATTERY_VOLTAGE = 6.6 # 6.6 - self.urate = 115200 - self.send_buff = memoryview(SEND_BUFF) - self.micro = microcontroller - self.hardware = { - "WDT": False, - "NEO": False, - "SOLAR": False, - "PWR": False, - "FLD": False, - "TEMP": False, - "COUPLE": False, - "CAN": False, - "LIDAR": False, - "Face0": False, - "Face1": False, - "Face2": False, - "Face3": False, - "Face4": False, - } - - # Define burn wires: - self._relayA = digitalio.DigitalInOut(board.BURN_RELAY_A) - self._relayA.switch_to_output(drive_mode=digitalio.DriveMode.OPEN_DRAIN) - self._resetReg = digitalio.DigitalInOut(board.VBUS_RESET) - self._resetReg.switch_to_output(drive_mode=digitalio.DriveMode.OPEN_DRAIN) - - # Define 5V Enable - self._5V_enable = digitalio.DigitalInOut(board.ENABLE_5V) - self._5V_enable.switch_to_output(drive_mode=digitalio.DriveMode.OPEN_DRAIN) - try: - self._5V_enable.value = True - self.debug_print("5V Enabled") - except Exception as e: - self.debug_print( - "Error Setting 5V Enable: " + "".join(traceback.format_exception(e)) - ) - - # Define SPI,I2C,UART | paasing I2C1 to BigData - try: - self.i2c0 = busio.I2C(board.I2C0_SCL, board.I2C0_SDA, timeout=5) - except Exception as e: - self.debug_print( - "ERROR INITIALIZING I2C0: " + "".join(traceback.format_exception(e)) - ) - - try: - self.spi0 = busio.SPI(board.SPI0_SCK, board.SPI0_MOSI, board.SPI0_MISO) - except Exception as e: - self.debug_print( - "ERROR INITIALIZING SPI0: " + "".join(traceback.format_exception(e)) - ) - - try: - self.i2c1 = busio.I2C( - board.I2C1_SCL, board.I2C1_SDA, timeout=5, frequency=100000 - ) - except Exception as e: - self.debug_print( - "ERROR INITIALIZING I2C1: " + "".join(traceback.format_exception(e)) - ) - - try: - self.spi1 = busio.SPI(board.SPI1_SCK, board.SPI1_MOSI, board.SPI1_MISO) - except Exception as e: - self.debug_print( - "ERROR INITIALIZING SPI1: " + "".join(traceback.format_exception(e)) - ) - - try: - self.uart = busio.UART(board.TX, board.RX, baudrate=self.urate) - except Exception as e: - self.debug_print( - "ERROR INITIALIZING UART: " + "".join(traceback.format_exception(e)) - ) - - # Initialize LED Driver - try: - self.faces = adafruit_pca9685.PCA9685(self.i2c0, address=int(0x56)) - self.faces.frequency = 2000 - self.hardware["FLD"] = True - except Exception as e: - self.debug_print( - "[ERROR][LED Driver]" + "".join(traceback.format_exception(e)) - ) - - # Initialize all of the Faces and their sensors - try: - self.Face0 = self.faces.channels[0] - self.Face1 = self.faces.channels[1] - self.Face2 = self.faces.channels[2] - self.Face3 = self.faces.channels[3] - self.Face4 = self.faces.channels[4] - self.cam = self.faces.channels[5] - self.all_faces_on() - except Exception as e: - self.debug_print( - "ERROR INITIALIZING FACES: " + "".join(traceback.format_exception(e)) - ) - - if self.c_boot > 200: - self.c_boot = 0 - - if self.f_softboot: - self.f_softboot = False - - # Define radio - self.enable_rf = digitalio.DigitalInOut(board.ENABLE_RF) - - # self.enable_rf.switch_to_output(value=False) # if U21 - self.enable_rf.switch_to_output(value=True) # if U7 - - # Define Heater Pins - self.heater = pwmio.PWMOut(board.ENABLE_HEATER, frequency=1000, duty_cycle=0) - - # Initialize Neopixel - try: - self.neopixel = neopixel.NeoPixel( - board.NEOPIX, 1, brightness=0.2, pixel_order=neopixel.GRB - ) - self.neopixel[0] = (0, 0, 255) - self.hardware["NEO"] = True - except Exception as e: - self.debug_print( - "[WARNING][Neopixel]" + "".join(traceback.format_exception(e)) - ) - - # Initialize Power Monitor - try: - time.sleep(1) - self.pwr = adafruit_ina219.INA219(self.i2c0, addr=int(0x40)) - self.hardware["PWR"] = True - except Exception as e: - self.debug_print( - "[ERROR][Power Monitor]" + "".join(traceback.format_exception(e)) - ) - - # Initialize Solar Power Monitor - try: - time.sleep(1) - self.solar = adafruit_ina219.INA219(self.i2c0, addr=int(0x44)) - self.hardware["SOLAR"] = True - except Exception as e: - self.debug_print( - "[ERROR][SOLAR Power Monitor]" + "".join(traceback.format_exception(e)) - ) - - # Initialize LiDAR - try: - self.LiDAR = adafruit_vl6180x.VL6180X(self.i2c1, offset=0) - self.hardware["LiDAR"] = True - except Exception as e: - self.debug_print("[ERROR][LiDAR]" + "".join(traceback.format_exception(e))) - - # Define Charge Indicate Pin - self.charge_indicate = digitalio.DigitalInOut(board.CHRG) - self.charge_indicate.switch_to_input(pull=digitalio.Pull.DOWN) - - # Initialize PCT2075 Temperature Sensor - try: - self.pct = adafruit_pct2075.PCT2075(self.i2c0, address=0x4F) - self.hardware["TEMP"] = True - except Exception as e: - self.debug_print( - "[ERROR][TEMP SENSOR]" + "".join(traceback.format_exception(e)) - ) - - # Initialize Thermocouple ADC - self.spi1_cs0 = digitalio.DigitalInOut(board.SPI1_CS0) - self.spi1_cs0.direction = digitalio.Direction.OUTPUT - - try: - self.thermocouple = adafruit_max31856.MAX31856(self.spi1, self.spi1_cs0) - self.hardware["COUPLE"] = True - self.debug_print("[ACTIVE][Thermocouple]") - except Exception as e: - self.debug_print( - "[ERROR][THERMOCOUPLE]" + "".join(traceback.format_exception(e)) - ) - - # Initialize CAN Transceiver - try: - self.spi0cs0 = digitalio.DigitalInOut(board.SPI0_CS0) - self.spi0cs0.switch_to_output() - self.can_bus = CAN(self.spi0, self.spi0cs0, loopback=True, silent=True) - self.hardware["CAN"] = True - - except Exception as e: - self.debug_print( - "[ERROR][CAN TRANSCEIVER]" + "".join(traceback.format_exception(e)) - ) - - """ - Prints init State of PySquared Hardware - """ - self.debug_print("PySquared Hardware Initialization Complete!") - - if self.debug: - # Find the length of the longest key - max_key_length = max(len(key) for key in self.hardware.keys()) - - print("=" * 16) - print("Device | Status") - for key, value in self.hardware.items(): - padded_key = key + " " * (max_key_length - len(key)) - if value: - print(co(f"|{padded_key} | {value} |", "green")) - else: - print(co(f"|{padded_key} | {value}|", "red")) - print("=" * 16) - - # set PyCubed power mode - self.power_mode = "normal" - - def reinit(self, dev): - if dev == "pwr": - self.pwr.__init__(self.i2c0) - elif dev == "fld": - self.faces.__init__(self.i2c0) - else: - self.debug_print("Invalid Device? ->" + str(dev)) - - # =======================================================# - # RGB Setter / Getter # - # =======================================================# - @property - def RGB(self): - return self.neopixel[0] - - @RGB.setter - def RGB(self, value): - if self.hardware["NEO"]: - try: - self.neopixel[0] = value - except Exception as e: - self.debug_print("[ERROR]" + "".join(traceback.format_exception(e))) - else: - self.debug_print("[WARNING] neopixel not initialized") - - # =======================================================# - # Before Flight Flags # - # =======================================================# - # These flags should be set as follows before flight: # - # burnarm = True # - # burned = False # - # dist = 0 # - # =======================================================# - @property - def burnarm(self): - return self.f_burnarm - - @burnarm.setter - def burnarm(self, value): - self.f_burnarm = value - - @property - def burned(self): - return self.f_burned - - @burned.setter - def burned(self, value): - self.f_burned = value - - @property - def dist(self): - return self.c_distance - - @dist.setter - def dist(self, value): - self.c_distance = int(value) - - def arm_satellite(self): - self.burnarm = True - self.burned = False - self.f_triedburn = False - self.dist = 0 - print("[Satellite Armed]") - - def disarm_satellite(self): - self.burnarm = False - self.burned = True - self.dist = 0 - print("[Satellite Disarmed]") - - # =======================================================# - # Getting and Setting Power for Individual Faces # - # =======================================================# - @property - def Face0_state(self): - return self.hardware["Face0"] - - @Face0_state.setter - def Face0_state(self, value): - if self.hardware["FLD"]: - if value: - try: - self.Face0 = 0xFFFF - self.hardware["Face0"] = True - self.debug_print("z Face Powered On") - except Exception as e: - self.debug_print( - "[WARNING][Face0]" + "".join(traceback.format_exception(e)) - ) - self.hardware["Face0"] = False - else: - self.Face0 = 0x0000 - self.hardware["Face0"] = False - self.debug_print("z+ Face Powered Off") - else: - self.debug_print("[WARNING] LED Driver not initialized") - - @property - def Face1_state(self): - return self.hardware["Face1"] - - @Face1_state.setter - def Face1_state(self, value): - if self.hardware["FLD"]: - if value: - try: - self.Face1 = 0xFFFF - self.hardware["Face1"] = True - self.debug_print("z- Face Powered On") - except Exception as e: - self.debug_print( - "[WARNING][Face1]" + "".join(traceback.format_exception(e)) - ) - self.hardware["Face1"] = False - else: - self.Face1 = 0x0000 - self.hardware["Face1"] = False - self.debug_print("z- Face Powered Off") - else: - self.debug_print("[WARNING] LED Driver not initialized") - - @property - def Face2_state(self): - return self.hardware["Face2"] - - @Face2_state.setter - def Face2_state(self, value): - if self.hardware["FLD"]: - if value: - try: - self.Face2 = 0xFFFF - self.hardware["Face2"] = True - self.debug_print("y+ Face Powered On") - except Exception as e: - self.debug_print( - "[WARNING][Face2]" + "".join(traceback.format_exception(e)) - ) - self.hardware["Face2"] = False - else: - self.Face2 = 0x0000 - self.hardware["Face2"] = False - self.debug_print("y+ Face Powered Off") - else: - self.debug_print("[WARNING] LED Driver not initialized") - - @property - def Face3_state(self): - return self.hardware["Face3"] - - @Face3_state.setter - def Face3_state(self, value): - if self.hardware["FLD"]: - if value: - try: - self.Face3 = 0xFFFF - self.hardware["Face3"] = True - self.debug_print("x- Face Powered On") - except Exception as e: - self.debug_print( - "[WARNING][Face3]" + "".join(traceback.format_exception(e)) - ) - self.hardware["Face3"] = False - else: - self.Face3 = 0x0000 - self.hardware["Face3"] = False - self.debug_print("x- Face Powered Off") - else: - self.debug_print("[WARNING] LED Driver not initialized") - - @property - def Face4_state(self): - return self.hardware["Face4"] - - @Face4_state.setter - def Face4_state(self, value): - if self.hardware["FLD"]: - if value: - try: - self.Face4 = 0xFFFF - self.hardware["Face4"] = True - self.debug_print("x+ Face Powered On") - except Exception as e: - self.debug_print( - "[WARNING][Face4]" + "".join(traceback.format_exception(e)) - ) - self.hardware["Face4"] = False - else: - self.Face4 = 0x0000 - self.hardware["Face4"] = False - self.debug_print("x+ Face Powered Off") - else: - self.debug_print("[WARNING] LED Driver not initialized") - - # =======================================================# - # Getters for State of Health Monitoring # - # =======================================================# - @property - def battery_voltage(self): - if self.hardware["PWR"]: - voltage = 0 - try: - for _ in range(50): - voltage += self.pwr.bus_voltage - return voltage / 50 + 0.2 # volts and corection factor - except Exception as e: - self.debug_print( - "[WARNING][PWR Monitor]" + "".join(traceback.format_exception(e)) - ) - else: - self.debug_print("[WARNING] Power monitor not initialized") - - @property - def system_voltage(self): - if self.hardware["PWR"]: - voltage = 0 - try: - for _ in range(50): - voltage += self.pwr.bus_voltage + self.pwr.shunt_voltage - return voltage / 50 # volts - except Exception as e: - self.debug_print( - "[WARNING][PWR Monitor]" + "".join(traceback.format_exception(e)) - ) - else: - self.debug_print("[WARNING] Power monitor not initialized") - - @property - def current_draw(self): - if self.hardware["PWR"]: - idraw = 0 - try: - for _ in range(50): # average 50 readings - idraw += self.pwr.current - return idraw / 50 - except Exception as e: - self.debug_print( - "[WARNING][PWR Monitor]" + "".join(traceback.format_exception(e)) - ) - else: - self.debug_print("[WARNING] Power monitor not initialized") - - @property - def is_charging(self): - return not (self.charge_indicate.value) - - @property - def charge_voltage(self): - if self.hardware["SOLAR"]: - voltage = 0 - try: - for _ in range(50): - voltage += self.solar.bus_voltage - return voltage / 50 + 0.2 # volts and corection factor - except Exception as e: - self.debug_print( - "[WARNING][SOLAR PWR Monitor]" - + "".join(traceback.format_exception(e)) - ) - else: - self.debug_print("[WARNING] SOLAR Power monitor not initialized") - - @property - def charge_current(self): - if self.hardware["SOLAR"]: - ichrg = 0 - try: - for _ in range(50): # average 50 readings - ichrg += self.solar.current - return ichrg / 50 - except Exception as e: - self.debug_print( - "[WARNING][SOLAR PWR Monitor]" - + "".join(traceback.format_exception(e)) - ) - else: - self.debug_print("[WARNING] SOLAR Power monitor not initialized") - - @property - def uptime(self): - self.CURRENTTIME = const(time.time()) - return self.CURRENTTIME - self.BOOTTIME - - @property - def fc_wdt(self): - return self._5V_enable.value - - @fc_wdt.setter - def fc_wdt(self, value): - try: - self._5V_enable.value = value - except Exception as e: - self.debug_print( - "Error Setting FC Watchdog Status: " - + "".join(traceback.format_exception(e)) - ) - - @property - def reset_vbus(self): - try: - self._resetReg.drive_mode = digitalio.DriveMode.PUSH_PULL - self._resetReg.value = 1 - except Exception as e: - self.debug_print( - "vbus reset error: " + "".join(traceback.format_exception(e)) - ) - - # =======================================================# - # Thermal Management # - # =======================================================# - @property - def internal_temperature(self): - return self.pct.temperature - - @property - def battery_temperature(self): - if self.hardware["COUPLE"]: - return self.thermocouple.temperature - else: - self.debug_print("[WARNING] Thermocouple not initialized") - - def heater_on(self): - - try: - self._relayA.drive_mode = digitalio.DriveMode.PUSH_PULL - if self.f_brownout: - pass - else: - self.f_brownout = True - self.heating = True - self._relayA.value = 1 - self.RGB = (255, 165, 0) - # Pause to ensure relay is open - time.sleep(0.25) - self.heater.duty_cycle = 0x7FFF - except Exception as e: - self.debug_print( - "[ERROR] Cant turn on heater: " + "".join(traceback.format_exception(e)) - ) - self.heater.duty_cycle = 0x0000 - - def heater_off(self): - if self.hardware["FLD"]: - try: - self.heater.duty_cycle = 0x0000 - self._relayA.value = 0 - self._relayA.drive_mode = digitalio.DriveMode.OPEN_DRAIN - if self.heating == True: - self.heating = False - self.f_brownout = False - self.debug_print("Battery Heater off!") - self.RGB = (0, 0, 0) - except Exception as e: - self.debug_print( - "[ERROR] Cant turn off heater: " - + "".join(traceback.format_exception(e)) - ) - self.heater.duty_cycle = 0x0000 - else: - self.debug_print("[WARNING] LED Driver not initialized") - - # =======================================================# - # Burn Wire # - # =======================================================# - - def distance(self): - if self.hardware["LiDAR"]: - try: - distance_mm = 0 - for _ in range(10): - distance_mm += self.LiDAR.range - time.sleep(0.01) - self.debug_print("distance measured = {0}mm".format(distance_mm / 10)) - return distance_mm / 10 - except Exception as e: - self.debug_print( - "LiDAR error: " + "".join(traceback.format_exception(e)) - ) - return 0 - else: - self.debug_print("[WARNING] LiDAR not initialized") - return 0 - - def burn(self, burn_num, dutycycle=0, freq=1000, duration=1): - """ - Operate burn wire circuits. Wont do anything unless the a nichrome burn wire - has been installed. - - IMPORTANT: See "Burn Wire Info & Usage" of https://pycubed.org/resources - before attempting to use this function! - - burn_num: (string) which burn wire circuit to operate, must be either '1' or '2' - dutycycle: (float) duty cycle percent, must be 0.0 to 100 - freq: (float) frequency in Hz of the PWM pulse, default is 1000 Hz - duration: (float) duration in seconds the burn wire should be on - """ - try: - # convert duty cycle % into 16-bit fractional up time - dtycycl = int((dutycycle / 100) * (0xFFFF)) - self.debug_print("----- BURN WIRE CONFIGURATION -----") - self.debug_print( - "\tFrequency of: {}Hz\n\tDuty cycle of: {}% (int:{})\n\tDuration of {}sec".format( - freq, (100 * dtycycl / 0xFFFF), dtycycl, duration - ) - ) - # create our PWM object for the respective pin - # not active since duty_cycle is set to 0 (for now) - if "1" in burn_num: - burnwire = pwmio.PWMOut(board.BURN_ENABLE, frequency=freq, duty_cycle=0) - else: - return False - # Configure the relay control pin & open relay - self._relayA.drive_mode = digitalio.DriveMode.PUSH_PULL - self._relayA.value = 1 - self.RGB = (255, 165, 0) - # Pause to ensure relay is open - time.sleep(0.5) - # Set the duty cycle over 0% - # This starts the burn! - burnwire.duty_cycle = dtycycl - time.sleep(duration) - # Clean up - self._relayA.value = 0 - burnwire.duty_cycle = 0 - self.RGB = (0, 0, 0) - # burnwire.deinit() - self._relayA.drive_mode = digitalio.DriveMode.OPEN_DRAIN - return True - except Exception as e: - self.debug_print( - "Error with Burn Wire: " + "".join(traceback.format_exception(e)) - ) - return False - finally: - self._relayA.value = 0 - burnwire.duty_cycle = 0 - self.RGB = (0, 0, 0) - burnwire.deinit() - self._relayA.drive_mode = digitalio.DriveMode.OPEN_DRAIN - - def smart_burn(self, burn_num, dutycycle=0.1): - """ - Operate burn wire circuits. Wont do anything unless the a nichrome burn wire - has been installed. - - IMPORTANT: See "Burn Wire Info & Usage" of https://pycubed.org/resources - before attempting to use this function! - - burn_num: (string) which burn wire circuit to operate, must be either '1' or '2' - dutycycle: (float) duty cycle percent, must be 0.0 to 100 - freq: (float) frequency in Hz of the PWM pulse, default is 1000 Hz - duration: (float) duration in seconds the burn wire should be on - """ - - freq = 1000 - - distance1 = 0 - distance2 = 0 - # self.dist=self.distance() - - try: - # convert duty cycle % into 16-bit fractional up time - dtycycl = int((dutycycle / 100) * (0xFFFF)) - self.debug_print("----- SMART BURN WIRE CONFIGURATION -----") - self.debug_print( - "\tFrequency of: {}Hz\n\tDuty cycle of: {}% (int:{})".format( - freq, (100 * dtycycl / 0xFFFF), dtycycl - ) - ) - # create our PWM object for the respective pin - # not active since duty_cycle is set to 0 (for now) - if "1" in burn_num: - burnwire = pwmio.PWMOut(board.ENABLE_BURN, frequency=freq, duty_cycle=0) - else: - return False - - try: - distance1 = self.distance() - self.debug_print(str(distance1)) - if ( - distance1 > self.dist + 2 - and distance1 > 4 - or self.f_triedburn == True - ): - self.burned = True - self.f_brownout = True - raise TypeError( - "Wire seems to have burned and satellite browned out" - ) - else: - self.dist = int(distance1) - self.burnarm = True - if self.burnarm: - self.burnarm = False - self.f_triedburn = True - - # Configure the relay control pin & open relay - self.RGB = (0, 165, 0) - - self._relayA.drive_mode = digitalio.DriveMode.PUSH_PULL - self.RGB = (255, 165, 0) - self._relayA.value = 1 - - # Pause to ensure relay is open - time.sleep(0.5) - - # Start the Burn - burnwire.duty_cycle = dtycycl - - # Burn Timer - start_time = time.monotonic() - - # Monitor the burn - while not self.burned: - distance2 = self.distance() - self.debug_print(str(distance2)) - if distance2 > distance1 + 1 or distance2 > 10: - self._relayA.value = 0 - burnwire.duty_cycle = 0 - self.burned = True - self.f_triedburn = False - else: - distance1 = distance2 - time_elapsed = time.monotonic() - start_time - print("Time Elapsed: " + str(time_elapsed)) - if time_elapsed > 4: - self._relayA.value = 0 - burnwire.duty_cycle = 0 - self.burned = False - self.RGB = (0, 0, 255) - time.sleep(10) - self.f_triedburn = False - break - - time.sleep(5) - distance2 = self.distance() - else: - pass - if distance2 > distance1 + 2 or distance2 > 10: - self.burned = True - self.f_triedburn = False - except Exception as e: - self.debug_print( - "Error in Burn Sequence: " + "".join(traceback.format_exception(e)) - ) - self.debug_print("Error: " + str(e)) - if "no attribute 'LiDAR'" in str(e): - self.debug_print("Burning without LiDAR") - time.sleep(120) # Set to 120 for flight - self.burnarm = False - self.burned = True - self.f_triedburn = True - self.burn("1", dutycycle, freq, 4) - time.sleep(5) - - finally: - self._relayA.value = 0 - burnwire.duty_cycle = 0 - self.RGB = (0, 0, 0) - burnwire.deinit() - self._relayA.drive_mode = digitalio.DriveMode.OPEN_DRAIN - - return True - except Exception as e: - self.debug_print( - "Error with Burn Wire: " + "".join(traceback.format_exception(e)) - ) - return False - - -print("Initializing Power Management Systems...") -cubesat = Satellite() diff --git a/Batt_Board/main.py b/Batt_Board/main.py deleted file mode 100644 index 981df76..0000000 --- a/Batt_Board/main.py +++ /dev/null @@ -1,237 +0,0 @@ -""" -Created by Nicole Maggard 12/26/2023 -""" - -from pysquared_eps import cubesat as c # pylint: disable=import-error -import asyncio -import time -import traceback -import gc # Garbage collection -import microcontroller # pylint: disable=import-error -import battery_functions # pylint: disable=import-error -from debugcolor import co # pylint: disable=import-error - -f = battery_functions.functions(c) - - -def debug_print(statement): - if c.debug: - print(co("[BATTERY][MAIN]" + str(statement), "blue", "bold")) - - # Defining Operations Within The Power Modes - """These functions is called when the satellite transitions between various power states. - It generally thse modes manage the battery, checks the state of health of the system, - performs a reboot check, and then puts the system into a short hibernation - mode to conserve power. - - When in low or critical power modes, the satellite will not perform any complex tasks. - - The sub-functions that can be called are: - - `battery_manager()`: Manages and monitors the battery status. - - `state_of_health()`: Checks the overall health and status of the satellite. - - `check_reboot()`: Determines if a system reboot is necessary. - - `Short_Hybernate()`: Puts the system into a low-power hibernation state. - """ # pylint: disable=pointless-string-statement - - -def boot_sequence(): - """ - Perform the boot sequence. - - Returns: - None - """ - - debug_print("Boot number: " + str(c.c_boot)) - debug_print(str(gc.mem_free()) + " Bytes remaining") # pylint: disable=no-member - - # power cycle faces to ensure sensors are on: - c.all_faces_off() - time.sleep(1) - c.all_faces_on() - # test the battery: - f.battery_manager() - - # Consider Deprecating the Loiter Time - LOITER_TIME = 1 - for _ in range(LOITER_TIME): - c.RGB = (255, 0, 255) - time.sleep(0.5) - c.RGB = (0, 0, 0) - time.sleep(0.5) - c.RGB = (0, 0, 255) - - f.state_of_health() - f.battery_manager() - time.sleep(1) - f.battery_manager() # Second check to make sure we have enough power to continue - f.state_of_health() - - -def critical_power_operations(): - """ - Perform operations necessary during critical power mode. - - Returns: - None - """ - f.battery_manager() - f.state_of_health() - f.check_reboot() - f.Short_Hybernate() - - -def low_power_operations(): - f.battery_manager() - f.state_of_health() - f.check_reboot() - - -def normal_power_operations(): - - debug_print("Entering Norm Operations") - FaceData = [] # Clearing FaceData - # Defining L1 Tasks - - def check_power(): - gc.collect() - f.battery_manager() - f.check_reboot() - f.battery_manager() # Second check to make sure we have enough power to continue - - if c.power_mode == "normal" or c.power_mode == "maximum": - pwr = True - if c.power_mode == "normal": - c.RGB = (255, 255, 0) - else: - c.RGB = (0, 255, 0) - else: - pwr = False - - debug_print("power mode" + str(c.power_mode)) - gc.collect() - return pwr - - # Consider Deprecating this function or changing it to a logging function. Currently it serves no purpose outside of debug. - async def g_face_data(): - - while check_power(): - - FaceData = [] - - try: - debug_print("Getting face data...") - FaceData = f.all_face_data() - for _ in range(0, len(FaceData)): - debug_print("Face " + str(_) + ": " + str(FaceData[_])) - - except Exception as e_gf: - debug_print( - "Outta time! " + "".join(traceback.format_exception(e_gf)) - ) # pylint: disable=no-value-for-parameter - - gc.collect() - - await asyncio.sleep(60) - - async def s_face_data(): - - await asyncio.sleep(20) - - while check_power(): - try: - debug_print("Looking to send face data...") - f.send_face() - - except asyncio.TimeoutError as e_sf: - debug_print( - "Outta time! " + "".join(traceback.format_exception(e_sf)) - ) # pylint: disable=no-value-for-parameter - - gc.collect() - - await asyncio.sleep(200) - - async def detumble(): - - await asyncio.sleep(300) - - while check_power(): - try: - debug_print("Looking to detumble...") - f.detumble() - debug_print("Detumble complete") - - except Exception as e_de: - debug_print( - f"Outta time!" + "".join(traceback.format_exception(e_de)) - ) # pylint: disable=no-value-for-parameter - - gc.collect() - - await asyncio.sleep(300) - - async def main_loop(): - # log_face_data_task = asyncio.create_task(l_face_data()) - - t1 = asyncio.create_task(s_face_data()) - t2 = asyncio.create_task(g_face_data()) - t3 = asyncio.create_task(detumble()) - - await asyncio.gather(t1, t2, t3) - - asyncio.run(main_loop()) - - -######################### MAIN LOOP ############################## -try: - c.all_faces_on() - try: - boot_sequence() - - except Exception as e_bo: - debug_print( - "Error in Boot Sequence: " + "".join(traceback.format_exception(e_bo)) - ) # pylint: disable=no-value-for-parameter - - finally: - debug_print("All Faces off!") - c.all_faces_off() - - while True: - # L0 automatic tasks no matter the battery level - f.battery_manager() - f.check_reboot() - - if c.power_mode == "critical": - c.RGB = (0, 0, 0) - critical_power_operations() - - elif c.power_mode == "low": - c.RGB = (255, 0, 0) - low_power_operations() - - elif c.power_mode == "normal": - c.RGB = (255, 255, 0) - normal_power_operations() - - elif c.power_mode == "maximum": - c.RGB = (0, 255, 0) - normal_power_operations() - - else: - # If the power mode is not recognized, the system will default to critical power mode. - critical_power_operations() - -except Exception as e: - # This block of code soft resets the system if an error occurs in the main loop. Can lead to boot looping if the error is not fixed. - debug_print( - "Fatal Error in Main Loop: " + "".join(traceback.format_exception(e)) - ) # pylint: disable=no-value-for-parameter - time.sleep(15) - microcontroller.on_next_reset(microcontroller.RunMode.NORMAL) - microcontroller.reset() -finally: - debug_print("All Faces off!") - c.all_faces_off() - c.RGB = (0, 0, 0) diff --git a/Batt_Board/safemode.py b/Batt_Board/safemode.py deleted file mode 100644 index 8a0f6f3..0000000 --- a/Batt_Board/safemode.py +++ /dev/null @@ -1,6 +0,0 @@ -print("I am in safemode. Help!") -import microcontroller -import time - -time.sleep(10) -microcontroller.reset() From ef610953f3c849c1d043d427520343f691b9c027 Mon Sep 17 00:00:00 2001 From: Nate Gay Date: Tue, 7 Jan 2025 14:23:06 -0600 Subject: [PATCH 3/3] Update readme --- README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2ca79fe..e0bbf47 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) ![CI](https://github.com/texas-state-space-lab/pikvm-tailscale-certificate-renewer/actions/workflows/ci.yaml/badge.svg) -Software for CircuitPython flight software in the PROVES Kit. Currently this repo is reflecting PROVES Kit V1.5, where there is independent code for both the Flight Controller (FC) Board and Battery (Batt) Board. The file tree has been updated to reflect this. Please access either the **FC_Board (for Flight Controller software)** or **Batt_Board (For Battery Board Software)** as needed! +Software for CircuitPython flight software in the PROVES Kit. If this is your first time using CircuitPython, it is highly recommended that you check out Adafruit's [Welcome to CircuitPython](https://learn.adafruit.com/welcome-to-circuitpython/overview?gad_source=1&gbraid=0AAAAADx9JvTRorSR2psQubn32WqebKusM&gclid=CjwKCAiA-Oi7BhA1EiwA2rIu28YArt-jNTE3W3uwE055Tp7hyH9c9pE_NsqIOOh1aopnW00qXGBedRoChysQAvD_BwE) to help you get started! @@ -55,15 +55,9 @@ To run the precommit hook: ## General Structure: - **boot.py** This is the code that runs on boot and initializes the stack limit -- **cdh.py** This is the code that handles all the commands. A majority of this code is pulled from the cdh file developed by Max Holliday at Stanford. -- **code.py** This code runs the main operating system of the satellite and handles errors on a high level allowing the system to be fault tolerant -- **detumble.py** This code implements the B-dot algorithm and returns outputs that allow the system to do a controlled detumble with the satellite's embedded magnetourquer coils - **main.py** This code tasks all the functions the satellite should do in a semi-asynchronous manner utilizing the asyncio library - **payload.py** This code implements any desired payload. On the Pleiades missions, the payload has been the BNO055 IMU. Since the use of a stemmaQT connector allows multiple devices on the same bus, a BNO IMU could be used in conjunction with several other sensors if desired. - **safemode.py** This code is unimplemented pending new firmware releases that allow the microconrtoller to perform a routine when in safemode -## experimental -This software is completely experimental and is in development for helpful software related tasks. -- **sf_hop.py** This code is yet to be implemented in official flight software as it is desired to implement the ability to utilize several spreading factors to send different sized messages at different data rates ## lib This software contains all of the libraries required to operate the sensors, pysquared board, and radio module. - **asyncio** This is the library responsible for scheduling tasks in the main code @@ -82,6 +76,9 @@ This software contains all of the libraries required to operate the sensors, pys - **functions.py** This is a library of functions utilized by the satellite to obtain data, detumble, run the battery heater - **pysquared.py** This is a library that initializes and maintains all the main functions for the pysquared architecture - **pysquared_rfm9x.py** This is a library that implements all the radio hardware. This code is a modified version of the pycubed_rfm9x which is a modified version of the adafruit_rfm9x file. +- **cdh.py** This is the code that handles all the commands. A majority of this code is pulled from the cdh file developed by Max Holliday at Stanford. +- **detumble.py** This code implements the B-dot algorithm and returns outputs that allow the system to do a controlled detumble with the satellite's embedded magnetourquer coils + ## tests This software is used for performing tests on the satellite