def __init__( self, name: str, i2c_lock: threading.RLock, ) -> None: """Initializes ArduinoComms""" # Initialize logger logname = "ArduinoComms({})".format(name) self.logger = Logger(logname, __name__) self.bus = 2 self.address = 0x08 self.name = name # Initialize I2C try: self.i2c = I2C(name="Arduino-{}".format(name), i2c_lock=i2c_lock, bus=self.bus, address=self.address, mux=None) except I2CError as e: raise exceptions.InitError(logger=self.logger) from e
def __init__(self, state: State) -> None: """Initializes recipe manager.""" # Initialize parent class super().__init__() # Initialize logger self.logger = Logger("Recipe", "recipe") # Initialize state self.state = state # Initialize state machine transitions self.transitions = { modes.INIT: [modes.NORECIPE, modes.ERROR], modes.NORECIPE: [modes.START, modes.ERROR], modes.START: [modes.QUEUED, modes.ERROR], modes.QUEUED: [modes.NORMAL, modes.STOP, modes.ERROR], modes.NORMAL: [modes.PAUSE, modes.STOP, modes.ERROR], modes.PAUSE: [modes.START, modes.ERROR], modes.STOP: [modes.NORECIPE, modes.ERROR], modes.ERROR: [modes.RESET], modes.RESET: [modes.INIT], } # Start state machine from init mode self.mode = modes.INIT
def __init__( self, name: str, i2c_lock: threading.RLock, bus: int, address: int, mux: Optional[int] = None, channel: Optional[int] = None, simulate: bool = False, mux_simulator: Optional[MuxSimulator] = None, Simulator: Optional[PeripheralSimulator] = None, ) -> None: """ Initializes atlas driver. """ # Initialize parameters self.simulate = simulate # Initialize logger logname = "Driver({})".format(name) self.logger = Logger(logname, "peripherals") # Initialize I2C try: self.i2c = I2C( name=name, i2c_lock=i2c_lock, bus=bus, address=address, mux=mux, channel=channel, mux_simulator=mux_simulator, PeripheralSimulator=Simulator, ) except I2CError as e: raise exceptions.InitError(logger=self.logger)
def __init__( self, name: str, bus: int, device_addr: int, mux_address: Optional[int], mux_channel: Optional[int], mux_simulator: Optional[MuxSimulator], ) -> None: # Initialize parameters self.name = name self.bus = bus self.device_addr = device_addr self.mux_address = mux_address self.mux_channel = mux_channel self.mux_simulator = mux_simulator # Initialize logger logname = "Simulator({})".format(name) self.logger = Logger(logname, __name__) self.logger.debug("Initializing simulator") # Initialize buffer self.buffer: bytearray = bytearray([]) # mutable bytes # Initialize register self.registers: Dict[int, int] = {} self.writes: Dict[str, bytes] = {}
def __init__( self, name: str, i2c_lock: threading.RLock, address: int, bus: Optional[int] = None, mux: Optional[int] = None, channel: Optional[int] = None, mux_simulator: Optional[MuxSimulator] = None, PeripheralSimulator: Optional[PeripheralSimulator] = None, verify_device: bool = True, ) -> None: # Initialize passed in parameters self.name = name self.i2c_lock = i2c_lock self.bus = bus self.address = address self.mux = mux self.channel = channel # Initialize logger logname = "I2C({})".format(self.name) self.logger = Logger(logname, "i2c") self.logger.debug("Initializing communication") # Verify mux config if self.mux != None and self.channel == None: raise InitError( "Mux requires channel value to be set") from ValueError # Initialize io if PeripheralSimulator != None: self.logger.debug("Using simulated io stream") self.io = PeripheralSimulator( # type: ignore name, bus, address, mux, channel, mux_simulator) else: self.logger.debug("Using device io stream") with self.i2c_lock: self.io = DeviceIO(name, bus) # Verify mux exists if self.mux != None: self.verify_mux() # Verify device exists if verify_device: self.verify_device() # Successfully initialized! self.logger.debug("Initialization successful")
def __init__(self) -> None: """Initializes coordinator.""" # Initialize parent class super().__init__() # Initialize logger self.logger = Logger("Coordinator", "coordinator") self.logger.debug("Initializing coordinator") # Initialize state self.state = State() # Initialize environment state dict, TODO: remove this self.state.environment = { "sensor": {"desired": {}, "reported": {}}, "actuator": {"desired": {}, "reported": {}}, "reported_sensor_stats": { "individual": {"instantaneous": {}, "average": {}}, "group": {"instantaneous": {}, "average": {}}, }, } # Initialize recipe state dict, TODO: remove this self.state.recipe = { "recipe_uuid": None, "start_timestamp_minutes": None, "last_update_minute": None, } # Initialize managers self.recipe = RecipeManager(self.state) self.iot = IotManager(self.state, self.recipe) # type: ignore self.resource = ResourceManager(self.state, self.iot) # type: ignore self.network = NetworkManager(self.state) # type: ignore self.upgrade = UpgradeManager(self.state) # type: ignore # Initialize state machine transitions self.transitions = { modes.INIT: [modes.CONFIG, modes.ERROR, modes.SHUTDOWN], modes.CONFIG: [modes.SETUP, modes.ERROR, modes.SHUTDOWN], modes.SETUP: [modes.NORMAL, modes.ERROR, modes.SHUTDOWN], modes.NORMAL: [modes.LOAD, modes.ERROR, modes.SHUTDOWN], modes.LOAD: [modes.CONFIG, modes.ERROR, modes.SHUTDOWN], modes.RESET: [modes.INIT, modes.SHUTDOWN], modes.ERROR: [modes.RESET, modes.SHUTDOWN], } # Initialize state machine mode self.mode = modes.INIT
def __init__(self, name: str, bus: int) -> None: # Initialize parameters self.name = name self.bus = bus # Initialize logger logname = "DeviceIO({})".format(name) self.logger = Logger(logname, __name__) # Verify io exists self.logger.debug("Verifying io stream exists") self.open() self.close()
def __init__( self, name: str, panel_configs: List[Dict[str, Any]], panel_properties: Dict[str, Any], i2c_lock: threading.Lock, simulate: bool = False, mux_simulator: Optional[MuxSimulator] = None, ) -> None: """Initializes driver.""" # Initialize driver parameters self.panel_properties = panel_properties self.i2c_lock = i2c_lock self.simulate = simulate # Initialize logger self.logger = Logger(name="Driver({})".format(name), dunder_name=__name__) # Parse panel properties self.channels = self.panel_properties.get("channels") self.dac_map = self.panel_properties.get("dac_map") # Initialze num expected panels self.num_expected_panels = len(panel_configs) # Initialize panels self.panels: List[LEDDAC5578Panel] = [] for config in panel_configs: panel = LEDDAC5578Panel(name, config, i2c_lock, simulate, mux_simulator, self.logger) panel.initialize() self.panels.append(panel) # Check at least one panel is still active active_panels = [ panel for panel in self.panels if not panel.is_shutdown ] self.num_active_panels = len(active_panels) if self.num_active_panels < 1: raise NoActivePanelsError(logger=self.logger) # Successfully initialized message = "Successfully initialized with {} ".format( self.num_active_panels) message2 = "active panels, expected {}".format( self.num_expected_panels) self.logger.debug(message + message2)
class Arduino(object): def __init__( self, name: str, arduino_lock: threading.RLock, ) -> None: # Initialize passed in parameters self.name = name self.arduino_lock = arduino_lock # Initialize logger logname = "Arduino({})".format(self.name) self.logger = Logger(logname, "arduino") self.logger.debug("Initializing communication") self.io = DeviceIO(name) # Successfully initialized! self.logger.debug("Initialization successful") @retry(tries=5, delay=0.2, backoff=3) def write_output( self, pin: string, val: string, retry: bool = True ) -> None: """This tells the Arduino to set a pin to a value""" with self.arduino_lock: self.logger.debug("{}: write_output(pin={}, val={})".format(self.name, pin, val)) self.io.write_output(pin, val) @retry(tries=5, delay=0.2, backoff=3) def read_register( self, address: string, register: string ) -> string: """This informs the Arduino to read an I2C chip, and reports the value back""" with self.arduino_lock: self.logger.debug("{}: write_output(address={}, register={})".format(self.name, address, register)) return self.io.read_register(address, register) @retry(tries=5, delay=0.2, backoff=3) def write_register( self, address: string, register: string, message: string ) -> None: """Similar to `read_register`, but this writes a message""" with self.arduino_lock: self.logger.debug("{}: write_register(address={}, register={}, message={})".format(self.name, address, register, message)) self.io.write_register(self.address, register, message)
def __init__(self, name: str) -> None: # Initialize parameters self.name = name # Initialize logger logname = "DeviceIO({})".format(name) self.logger = Logger(logname, __name__) # Verify io exists self.logger.debug("Verifying io stream exists") UART.setup("UART1") self.ser = serial.Serial(port = "/dev/ttyO1", baudrate=9600) self.open() self.close()
def __init__( self, bus: int = 0, address: int = PCA9632_I2C_ADDRESS, mux: Optional[int] = None, channel: Optional[int] = None, simulate: bool = False, mux_simulator: Optional[MuxSimulator] = None, ) -> None: """Initializes Grove RGB LCD.""" # Initialize logger self.logger = Logger('LED', __name__) # Check if simulating if simulate: self.logger.info("Simulating LED") # Initialize I2C try: self.i2c = I2C( name="LED", i2c_lock=threading.RLock(), bus=bus, address=address, mux=mux, channel=channel, mux_simulator=mux_simulator, PeripheralSimulator=None, ) except I2CError as e: raise LEDError(logger=self.logger) from e # Initialize the LED try: self.init_data = [ 0x80, 0x80, 0x21, 0x00, 0x00, 0x00, 0x40, 0x80, 0x02, 0xEA ] # init and clear any LED values self.i2c.write(bytes(self.init_data)) except I2CError as e: raise LEDError(logger=self.logger) from e
def __init__( self, name: str, arduino_lock: threading.RLock, ) -> None: # Initialize passed in parameters self.name = name self.arduino_lock = arduino_lock # Initialize logger logname = "Arduino({})".format(self.name) self.logger = Logger(logname, "arduino") self.logger.debug("Initializing communication") self.io = DeviceIO(name) # Successfully initialized! self.logger.debug("Initialization successful")
def __init__( self, name: str, i2c_lock: threading.Lock, bus: int, address: int, mux: Optional[int] = None, channel: Optional[int] = None, simulate: Optional[bool] = False, mux_simulator: Optional[MuxSimulator] = None, ) -> None: """Initializes t6713 driver.""" # Initialize parameters self.simulate = simulate self.i2c_lock = i2c_lock # Initialize logger self.logger = Logger(name="Driver({})".format(name), dunder_name=__name__) # Check if simulating if simulate: self.logger.info("Simulating driver") Simulator = T6713Simulator else: Simulator = None # Initialize I2C try: self.i2c = I2C( name=name, i2c_lock=i2c_lock, bus=bus, address=address, mux=mux, channel=channel, mux_simulator=mux_simulator, PeripheralSimulator=Simulator, ) except I2CError as e: raise InitError(logger=self.logger) from e
def __init__(self, name: str, simulate: bool = False, ini_file: str = None, config_file: str = None, debug: bool = False) -> None: """Initializes bacpypes.""" self.logger = Logger(name + ".BACNet", __name__) if ini_file is None or config_file is None: raise exceptions.InitError(message="Missing file args", logger=self.logger) try: self.logger.info("driver init") self.bnet = BACNET.Bnet(self.logger, ini_file, config_file, debug) except Exception as e: raise exceptions.InitError(logger=self.logger) from e
def __init__( self, name: str, i2c_lock: threading.Lock, bus: int, address: int, mux: Optional[int] = None, channel: Optional[int] = None, simulate: Optional[bool] = False, mux_simulator: Optional[MuxSimulator] = None, ) -> None: """Initializes driver.""" # Initialize logger self.logger = Logger(name="Driver({})".format(name), dunder_name=__name__) # Check if simulating if simulate: self.logger.info("Simulating driver") Simulator = SHT25Simulator else: Simulator = None # Initialize I2C try: self.i2c = I2C( name=name, i2c_lock=i2c_lock, bus=bus, address=address, mux=mux, channel=channel, mux_simulator=mux_simulator, PeripheralSimulator=Simulator, verify_device=False, # need to write before device responds to read ) self.read_user_register(retry=True) except I2CError as e: raise InitError(logger=self.logger) from e
def __init__( self, name: str, i2c_lock: threading.RLock, bus: int, address: int, mux: Optional[int] = None, channel: Optional[int] = None, simulate: bool = False, mux_simulator: Optional[MuxSimulator] = None, ) -> None: """Initializes DAC5578.""" # Initialize logger logname = "DAC5578-({})".format(name) self.logger = Logger(logname, __name__) # Check if simulating if simulate: self.logger.info("Simulating driver") Simulator = DAC5578Simulator else: Simulator = None # Initialize I2C try: self.i2c = I2C( name="DAC5578-{}".format(name), i2c_lock=i2c_lock, bus=bus, address=address, mux=mux, channel=channel, mux_simulator=mux_simulator, PeripheralSimulator=Simulator, ) except I2CError as e: raise exceptions.InitError(logger=self.logger) from e
def __init__(self) -> None: """Initializes state machine manager.""" self.logger: Logger = Logger("StateMachineManager", __name__) self.thread: threading.Thread = threading.Thread(target=self.run) self.event_queue: queue.Queue = queue.Queue() self.is_shutdown: bool = False self._mode: str = modes.INIT self.transitions: Dict[str, List[str]] = { modes.INIT: [modes.NORMAL, modes.SHUTDOWN, modes.ERROR], modes.NORMAL: [modes.RESET, modes.SHUTDOWN, modes.ERROR], modes.RESET: [modes.INIT, modes.SHUTDOWN, modes.ERROR], modes.ERROR: [modes.RESET, modes.SHUTDOWN], } self.logger.debug("Initialized")
class MuxSimulator(object): """I2C mux simulator. Note connections is a dict because we could have multiple muxes on a device.""" # Initialize mux parameters valid_channel_bytes = [ 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 ] connections: Dict[int, int] = {} def __init__(self) -> None: """Initializes mux simulator.""" # Initialize logger self.logger = Logger("Simulator(Mux)", __name__) self.logger.debug("Initializing simulator") def set(self, address: int, channel_byte: int) -> None: """Sets mux at address to channel.""" message = "Setting addr 0x{:02X} to 0x{:02X}".format( address, channel_byte) self.logger.debug(message) # Verify valid channel byte: if channel_byte not in self.valid_channel_bytes: message = "Unable to set mux, invalid channel byte: 0x{:02X}".format( channel_byte) raise MuxError(message) # Set mux to channel self.connections[address] = channel_byte def verify(self, address: int, channel: int) -> None: """Verifies if mux at address is set to correct channel.""" self.logger.debug("Verifying mux connection") # Check mux exists if address not in self.connections: message = "Mux 0x{:02X} has never been set".format(address) raise MuxError(message, logger=self.logger) # Convert channel to channel byte channel_byte = 0x01 << channel if self.connections[address] != channel_byte: message = "Mux channel mismatch, stored: 0x{:02X}, received: 0x{:02X}".format( self.connections[address], channel_byte) raise MuxError(message, logger=self.logger)
class PeripheralSimulator: """I2C peripheral simulator base class.""" def __init__( self, name: str, bus: int, device_addr: int, mux_address: Optional[int], mux_channel: Optional[int], mux_simulator: Optional[MuxSimulator], ) -> None: # Initialize parameters self.name = name self.bus = bus self.device_addr = device_addr self.mux_address = mux_address self.mux_channel = mux_channel self.mux_simulator = mux_simulator # Initialize logger logname = "Simulator({})".format(name) self.logger = Logger(logname, __name__) self.logger.debug("Initializing simulator") # Initialize buffer self.buffer: bytearray = bytearray([]) # mutable bytes # Initialize register self.registers: Dict[int, int] = {} self.writes: Dict[str, bytes] = {} def __enter__(self) -> object: """Context manager enter function.""" return self def __exit__(self, exc_type: ET, exc_val: EV, exc_tb: EB) -> bool: """Context manager exit function, ensures resources are cleaned up.""" return False # Don't suppress exceptions @verify_mux def read(self, device_addr: int, num_bytes: int) -> bytes: """Reads bytes from buffer. Returns 0x00 if buffer is empty.""" msg = "Reading {} bytes, buffer: {}".format(num_bytes, byte_str(self.buffer)) self.logger.debug(msg) # Check device address matches if device_addr != self.device_addr: message = "Address not found: 0x{:02X}".format(device_addr) raise ReadError(message) # Pop bytes from buffer and return bytes_ = [] while num_bytes > 0: # Check for empty buffer or pop byte from buffer if len(self.buffer) == 0: bytes_.append(0x00) else: bytes_.append(self.buffer.pop()) # Decrement num bytes to read num_bytes = num_bytes - 1 # Successfully read bytes return bytes(bytes_) def write(self, address: int, bytes_: bytes) -> None: """Writes bytes to buffer.""" # Check if writing to mux if address == self.mux_address: # Check if mux command valid if len(bytes_) > 1: raise MuxError("Unable to set mux, only 1 command byte is allowed") # Set mux to channel self.mux_simulator.set(self.mux_address, bytes_[0]) # type: ignore # Check if writing to device elif address == self.device_addr: # Verify mux connection if self.mux_address != None: address = self.mux_address # type: ignore channel = self.mux_channel self.mux_simulator.verify(address, channel) # type: ignore # Get response bytes response_bytes = self.writes.get(byte_str(bytes_), None) # Verify known write bytes if response_bytes == None: raise WriteError("Unknown write bytes: {}".format(byte_str(bytes_))) # Write response bytes to buffer response_byte_string = byte_str(response_bytes) # type: ignore self.logger.debug("Response bytes: {}".format(response_byte_string)) for byte in response_bytes: # type: ignore self.buffer.insert(0, byte) self.logger.debug("Buffer: {}".format(byte_str(self.buffer))) # Check for invalid address else: message = "Address not found: 0x{:02X}".format(address) raise WriteError(message) @verify_mux def read_register(self, device_addr: int, register_addr: int) -> int: """Reads register byte.""" # Check address matches if device_addr != self.device_addr: message = "Address not found: 0x{:02X}".format(device_addr) raise ReadError(message) # Check register within range if register_addr not in range(256): message = "Invalid register addrress: {}, must be 0-255".format( register_addr ) raise ReadError(message) # Read register value from register dict try: return self.registers[register_addr] except KeyError: message = "Register address not found: 0x{:02X}".format(register_addr) raise ReadError(message) @verify_mux def write_register(self, device_addr: int, register_addr: int, value: int) -> None: """Writes byte to register.""" # Check address matches if device_addr != self.device_addr: message = "Device address not found: 0x{:02X}".format(device_addr) raise WriteError(message) # Check register within range if register_addr not in range(256): message = "Invalid register addrress: {}, must be 0-255".format( register_addr ) raise WriteError(message) # Check value within range if value not in range(256): message = "Invalid register value: {}, must be 0-255".format(value) raise WriteError(message) # Write value to register self.registers[register_addr] = value
class CCS811Driver: """Driver for atlas ccs811 carbon dioxide and total volatile organic compounds sensor.""" # Initialize variable properties min_co2 = 400.0 # ppm max_co2 = 8192.0 # ppm min_tvoc = 0.0 # ppb max_tvoc = 1187.0 # ppb def __init__( self, name: str, i2c_lock: threading.Lock, bus: int, address: int, mux: Optional[int] = None, channel: Optional[int] = None, simulate: bool = False, mux_simulator: Optional[MuxSimulator] = None, ) -> None: # Initialize simulation mode self.simulate = simulate # Initialize logger self.logger = Logger(name="Driver({})".format(name), dunder_name=__name__) self.logger.info("Initializing driver") # Check if simulating if simulate: self.logger.info("Simulating driver") Simulator = CCS811Simulator else: Simulator = None # Initialize I2C try: self.i2c = I2C( name=name, i2c_lock=i2c_lock, bus=bus, address=address, mux=mux, channel=channel, mux_simulator=mux_simulator, PeripheralSimulator=Simulator, ) except I2CError as e: raise InitError(logger=self.logger) from e def setup(self, retry: bool = True) -> None: """Setups sensor.""" try: self.reset(retry=retry) self.check_hardware_id(retry=retry) self.start_app(retry=retry) # self.check_for_errors(retry=retry) self.write_measurement_mode(1, False, False, retry=retry) # Wait 20 minutes for sensor to stabilize start_time = time.time() while time.time() - start_time < 1200: # Keep logs active self.logger.info("Warming up, waiting for 20 minutes") # Update every 30 seconds time.sleep(30) # Break out if simulating if self.simulate: break except DriverError as e: raise SetupError(logger=self.logger) from e def start_app(self, retry: bool = True) -> None: """Starts app by writing a byte to the app start register.""" self.logger.info("Starting app") try: self.i2c.write(bytes([0xF4]), retry=retry) except I2CError as e: raise StartAppError(logger=self.logger) from e def read_hardware_id(self, retry: bool = True) -> int: """Reads hardware ID from sensor.""" self.logger.info("Reading hardware ID") try: return int(self.i2c.read_register(0x20, retry=retry)) except I2CError as e: raise ReadRegisterError(message="hw id reg", logger=self.logger) from e def check_hardware_id(self, retry: bool = True) -> None: """Checks for valid id in hardware id register.""" self.logger.info("Checking hardware ID") hardware_id = self.read_hardware_id(retry=retry) if hardware_id != 0x81: raise HardwareIDError(logger=self.logger) def read_status_register(self, retry: bool = True) -> StatusRegister: """Reads status of sensor.""" self.logger.info("Reading status register") try: byte = self.i2c.read_register(0x00, retry=retry) except I2CError as e: raise ReadRegisterError(message="status reg", logger=self.logger) from e # Parse status register byte status_register = StatusRegister( firmware_mode=bitwise.get_bit_from_byte(7, byte), app_valid=bool(bitwise.get_bit_from_byte(4, byte)), data_ready=bool(bitwise.get_bit_from_byte(3, byte)), error=bool(bitwise.get_bit_from_byte(0, byte)), ) self.logger.debug(status_register) return status_register def check_for_errors(self, retry: bool = True) -> None: """Checks for errors in status register.""" self.logger.info("Checking for errors") status_register = self.read_status_register(retry=retry) if status_register.error: raise StatusError(message=status_register, logger=self.logger) def read_error_register(self, retry: bool = True) -> ErrorRegister: """Reads error register.""" self.logger.info("Reading error register") try: byte = self.i2c.read_register(0x0E, retry=retry) except I2CError as e: raise ReadRegisterError(message="error reg", logger=self.logger) from e # Parse error register byte return ErrorRegister( write_register_invalid=bool(bitwise.get_bit_from_byte(0, byte)), read_register_invalid=bool(bitwise.get_bit_from_byte(1, byte)), measurement_mode_invalid=bool(bitwise.get_bit_from_byte(2, byte)), max_resistance=bool(bitwise.get_bit_from_byte(3, byte)), heater_fault=bool(bitwise.get_bit_from_byte(4, byte)), heater_supply=bool(bitwise.get_bit_from_byte(5, byte)), ) def write_measurement_mode( self, drive_mode: int, enable_data_ready_interrupt: bool, enable_threshold_interrupt: bool, retry: bool = True, ) -> None: """Writes measurement mode to the sensor.""" self.logger.debug("Writing measurement mode") # Initialize bits bits = {7: 0, 1: 0, 0: 0} # Set drive mode if drive_mode == 0: bits.update({6: 0, 5: 0, 4: 0}) elif drive_mode == 1: bits.update({6: 0, 5: 0, 4: 1}) elif drive_mode == 2: bits.update({6: 0, 5: 1, 4: 0}) elif drive_mode == 3: bits.update({6: 0, 5: 1, 4: 1}) elif drive_mode == 4: bits.update({6: 1, 5: 0, 4: 0}) else: raise ValueError("Invalid drive mode") # Set data ready interrupt bits.update({3: int(enable_data_ready_interrupt)}) # Set threshold interrupt bits.update({2: int(enable_data_ready_interrupt)}) # Convert bits to byte sbits = {} for key in sorted(bits.keys(), reverse=True): sbits[key] = bits[key] self.logger.error("bits = {}".format(sbits)) # TODO: remove write_byte = bitwise.get_byte_from_bits(bits) self.logger.error("write_byte = 0x{:02X}".format(write_byte)) # TODO: remove # Write measurement mode to sensor try: self.i2c.write(bytes([0x01, write_byte]), retry=retry) except I2CError as e: raise WriteMeasurementModeError(logger=self.logger) from e def write_environment_data( self, temperature: Optional[float] = None, humidity: Optional[float] = None, retry: bool = True, ) -> None: """Writes compensation temperature and / or humidity to sensor.""" self.logger.debug("Writing environment data") # Check valid environment values if temperature == None and humidity == None: raise ValueError("Temperature and/or humidity value required") # Calculate temperature bytes if temperature != None: t = temperature temp_msb, temp_lsb = bitwise.convert_base_1_512(t + 25) # type: ignore else: temp_msb = 0x64 temp_lsb = 0x00 # Calculate humidity bytes if humidity != None: hum_msb, hum_lsb = bitwise.convert_base_1_512(humidity) else: hum_msb = 0x64 hum_lsb = 0x00 # Write environment data to sensor bytes_ = [0x05, hum_msb, hum_lsb, temp_msb, temp_lsb] try: self.i2c.write(bytes(bytes_), retry=retry) except I2CError as e: raise WriteEnvironmentDataError(logger=self.logger) from e def read_algorithm_data( self, retry: bool = True, reread: int = 5 ) -> Tuple[float, float]: """Reads algorighm data from sensor hardware.""" self.logger.debug("Reading co2/tvoc algorithm data") # Read status register try: status = self.read_status_register() except ReadRegisterError as e: raise ReadAlgorithmDataError(logger=self.logger) from e # Check if data is ready if not status.data_ready: if reread: self.logger.debug("Data not ready yet, re-reading in 1 second") time.sleep(1) self.read_algorithm_data(retry=retry, reread=reread - 1) else: message = "data not ready" raise ReadAlgorithmDataError(message=message, logger=self.logger) # Get algorithm data try: self.i2c.write(bytes([0x02]), retry=retry) bytes_ = self.i2c.read(4) except I2CError: raise ReadAlgorithmDataError(logger=self.logger) from e # Parse data bytes co2 = float(bytes_[0] * 255 + bytes_[1]) tvoc = float(bytes_[2] * 255 + bytes_[3]) # Verify co2 value within valid range if co2 > self.min_co2 and co2 < self.min_co2: message = "CO2 reading outside of valid range" raise ReadAlgorithmDataError(message=message, logger=self.logger) # Verify tvos within valid range if tvoc > self.min_tvoc and tvoc < self.min_tvoc: message = "TVOC reading outside of valid range" raise ReadAlgorithmDataError(message=message, logger=self.logger) # Successfully read sensor data! self.logger.debug("CO2: {} ppm".format(co2)) self.logger.debug("TVOC: {} ppb".format(tvoc)) return co2, tvoc def read_raw_data(self) -> None: """Reads raw data from sensor.""" ... def read_ntc(self) -> None: """ Read value of NTC. Can be used to calculate temperature. """ ... def reset(self, retry: bool = True) -> None: """Resets sensor and places into boot mode.""" self.logger.debug("Resetting sensor") # Write reset bytes to sensor bytes_ = [0xFF, 0x11, 0xE5, 0x72, 0x8A] try: self.i2c.write(bytes(bytes_), retry=retry) except I2CError as e: raise ResetError(logger=self.logger) from e
class CoordinatorManager(StateMachineManager): """Manages device state machine thread that spawns child threads to run recipes, read sensors, set actuators, manage control loops, sync data, and manage external events.""" # Initialize vars latest_publish_timestamp = 0.0 peripherals: Dict[str, StateMachineManager] = {} controllers: Dict[str, StateMachineManager] = {} new_config: bool = False def __init__(self) -> None: """Initializes coordinator.""" # Initialize parent class super().__init__() # Initialize logger self.logger = Logger("Coordinator", "coordinator") self.logger.debug("Initializing coordinator") # Initialize state self.state = State() # Initialize environment state dict, TODO: remove this self.state.environment = { "sensor": { "desired": {}, "reported": {} }, "actuator": { "desired": {}, "reported": {} }, "reported_sensor_stats": { "individual": { "instantaneous": {}, "average": {} }, "group": { "instantaneous": {}, "average": {} }, }, } # Initialize recipe state dict, TODO: remove this self.state.recipe = { "recipe_uuid": None, "start_timestamp_minutes": None, "last_update_minute": None, } # Initialize managers self.recipe = RecipeManager(self.state) self.iot = IotManager(self.state, self.recipe) # type: ignore self.recipe.set_iot(self.iot) self.resource = ResourceManager(self.state, self.iot) # type: ignore self.network = NetworkManager(self.state) # type: ignore self.upgrade = UpgradeManager(self.state) # type: ignore # Initialize state machine transitions self.transitions = { modes.INIT: [modes.CONFIG, modes.ERROR, modes.SHUTDOWN], modes.CONFIG: [modes.SETUP, modes.ERROR, modes.SHUTDOWN], modes.SETUP: [modes.NORMAL, modes.ERROR, modes.SHUTDOWN], modes.NORMAL: [modes.LOAD, modes.ERROR, modes.SHUTDOWN], modes.LOAD: [modes.CONFIG, modes.ERROR, modes.SHUTDOWN], modes.RESET: [modes.INIT, modes.SHUTDOWN], modes.ERROR: [modes.RESET, modes.SHUTDOWN], } # Initialize state machine mode self.mode = modes.INIT @property def mode(self) -> str: """Gets mode.""" return self._mode @mode.setter def mode(self, value: str) -> None: """Safely updates mode in state object.""" self._mode = value with self.state.lock: self.state.device["mode"] = value @property def config_uuid(self) -> Optional[str]: """ Gets config uuid from shared state. """ return self.state.device.get("config_uuid") # type: ignore @config_uuid.setter def config_uuid(self, value: Optional[str]) -> None: """ Safely updates config uuid in state. """ with self.state.lock: self.state.device["config_uuid"] = value @property def config_dict(self) -> Dict[str, Any]: """Gets config dict for config uuid in device config table.""" if self.config_uuid == None: return {} config = models.DeviceConfigModel.objects.get(uuid=self.config_uuid) return json.loads(config.json) # type: ignore @property def latest_environment_timestamp(self) -> float: """Gets latest environment timestamp from environment table.""" if not models.EnvironmentModel.objects.all(): return 0.0 else: environment = models.EnvironmentModel.objects.latest() return float(environment.timestamp.timestamp()) @property def manager_modes(self) -> Dict[str, str]: """Gets manager modes.""" self.logger.debug("Getting manager modes") # Get known manager modes modes = { "Coordinator": self.mode, "Recipe": self.recipe.mode, "Network": self.network.mode, "IoT": self.iot.mode, "Resource": self.resource.mode, } # Get peripheral manager modes for peripheral_name, peripheral_manager in self.peripherals.items(): modes[peripheral_name] = peripheral_manager.mode # Get controller manager modes for controller_name, controller_manager in self.controllers.items(): modes[controller_name] = controller_manager.mode # Return modes self.logger.debug("Returning modes: {}".format(modes)) return modes @property def manager_healths(self) -> Dict[str, str]: """Gets manager healths.""" self.logger.debug("Getting manager healths") # Initialize healths healths = {} # Get peripheral manager modes for peripheral_name, peripheral_manager in self.peripherals.items(): healths[ peripheral_name] = peripheral_manager.health # type: ignore # Return modes self.logger.debug("Returning healths: {}".format(healths)) return healths ##### STATE MACHINE FUNCTIONS ###################################################### def run(self) -> None: """Runs device state machine.""" # Loop forever while True: # Check if thread is shutdown if self.is_shutdown: break # Check for transitions if self.mode == modes.INIT: self.run_init_mode() elif self.mode == modes.CONFIG: self.run_config_mode() elif self.mode == modes.SETUP: self.run_setup_mode() elif self.mode == modes.NORMAL: self.run_normal_mode() elif self.mode == modes.LOAD: self.run_load_mode() elif self.mode == modes.ERROR: self.run_error_mode() elif self.mode == modes.RESET: self.run_reset_mode() elif self.mode == modes.SHUTDOWN: self.run_shutdown_mode() else: self.logger.critical("Invalid state machine mode") self.mode = modes.INVALID self.is_shutdown = True break def run_init_mode(self) -> None: """Runs init mode. Loads local data files and stored database state then transitions to config mode.""" self.logger.info("Entered INIT") # Load local data files and stored db state self.load_local_data_files() self.load_database_stored_state() # Transition to config mode on next state machine update self.mode = modes.CONFIG def run_config_mode(self) -> None: """Runs configuration mode. If device config is not set, loads 'unspecified' config then transitions to setup mode.""" self.logger.info("Entered CONFIG") # Check device config specifier file exists in repo try: with open(DEVICE_CONFIG_PATH) as f: config_name = f.readline().strip() except: env_dev_type = os.getenv("OPEN_AG_DEVICE_TYPE") if env_dev_type is None: config_name = "unspecified" message = "Unable to read {}, using unspecified config".format( DEVICE_CONFIG_PATH) else: config_name = env_dev_type message = "Unable to read {}, using {} config from env".format( DEVICE_CONFIG_PATH, config_name) self.logger.warning(message) # Create the directories if needed os.makedirs(os.path.dirname(DEVICE_CONFIG_PATH), exist_ok=True) # Write `unspecified` to device.txt with open(DEVICE_CONFIG_PATH, "w") as f: f.write("{}\n".format(config_name)) # Load device config self.logger.debug("Loading device config file: {}".format(config_name)) device_config = json.load( open("data/devices/{}.json".format(config_name))) # Check if config uuid changed, if so, adjust state if self.config_uuid != device_config["uuid"]: with self.state.lock: self.state.peripherals = {} self.state.controllers = {} set_nested_dict_safely( self.state.environment, ["reported_sensor_stats"], {}, self.state.lock, ) set_nested_dict_safely(self.state.environment, ["sensor", "reported"], {}, self.state.lock) self.config_uuid = device_config["uuid"] # Transition to setup mode on next state machine update self.mode = modes.SETUP def run_setup_mode(self) -> None: """Runs setup mode. Creates and spawns recipe, peripheral, and controller threads, waits for all threads to initialize then transitions to normal mode.""" self.logger.info("Entered SETUP") config_uuid = self.state.device["config_uuid"] # Spawn managers if not self.new_config: self.recipe.spawn() self.iot.spawn() self.resource.spawn() self.network.spawn() self.upgrade.spawn() # Create and spawn peripherals self.logger.debug("Creating and spawning peripherals") self.create_peripherals() self.spawn_peripherals() # Create and spawn controllers self.create_controllers() self.spawn_controllers() # Wait for all threads to initialize while not self.all_managers_initialized(): time.sleep(0.2) # Unset new config flag self.new_config = False # Transition to normal mode on next state machine update self.mode = modes.NORMAL def run_normal_mode(self) -> None: """Runs normal operation mode. Updates device state summary and stores device state in database, checks for new events and transitions.""" self.logger.info("Entered NORMAL") while True: # Overwrite system state in database every 100ms self.update_state() # Store environment state in every 10 minutes if time.time() - self.latest_environment_timestamp > 60 * 10: self.store_environment() # Check for events self.check_events() # Check for transitions if self.new_transition(modes.NORMAL): break # Update every 100ms time.sleep(0.1) def run_load_mode(self) -> None: """Runs load mode, shutsdown peripheral and controller threads then transitions to config mode.""" self.logger.info("Entered LOAD") # Shutdown peripherals and controllers self.shutdown_peripheral_threads() self.shutdown_controller_threads() # Initialize timeout parameters timeout = 10 start_time = time.time() # Loop forever while True: # Check if peripherals and controllers are shutdown if self.all_peripherals_shutdown( ) and self.all_controllers_shutdown(): self.logger.debug("All peripherals and controllers shutdown") break # Check for timeout if time.time() - start_time > timeout: self.logger.critical("Config threads did not shutdown") self.mode = modes.ERROR return # Update every 100ms time.sleep(0.1) # Set new config flag self.new_config = True # Transition to config mode on next state machine update self.mode = modes.CONFIG def run_reset_mode(self) -> None: """Runs reset mode. Shutsdown child threads then transitions to init.""" self.logger.info("Entered RESET") # Shutdown managers self.shutdown_peripheral_threads() self.shutdown_controller_threads() self.recipe.shutdown() self.iot.shutdown() # Transition to init mode on next state machine update self.mode = modes.INIT def run_error_mode(self) -> None: """Runs error mode. Shutsdown child threads, waits for new events and transitions.""" self.logger.info("Entered ERROR") # Shutsdown peripheral and controller threads self.shutdown_peripheral_threads() self.shutdown_controller_threads() # Loop forever while True: # Check for events self.check_events() # Check for transitions if self.new_transition(modes.ERROR): break # Update every 100ms time.sleep(0.1) ##### SUPPORT FUNCTIONS ############################################################ def update_state(self) -> None: """Updates stored state in database. If state does not exist, creates it.""" # TODO: Move this to state manager if not models.StateModel.objects.filter(pk=1).exists(): models.StateModel.objects.create( id=1, device=json.dumps(self.state.device), recipe=json.dumps(self.state.recipe), environment=json.dumps(self.state.environment), peripherals=json.dumps(self.state.peripherals), controllers=json.dumps(self.state.controllers), iot=json.dumps(self.state.iot), resource=json.dumps(self.state.resource), connect=json.dumps(self.state.network), upgrade=json.dumps(self.state.upgrade), ) else: models.StateModel.objects.filter(pk=1).update( device=json.dumps(self.state.device), recipe=json.dumps(self.state.recipe), environment=json.dumps(self.state.environment), peripherals=json.dumps(self.state.peripherals), controllers=json.dumps(self.state.controllers), iot=json.dumps(self.state.iot), resource=json.dumps(self.state.resource), connect=json.dumps(self.state.network), # TODO: migrate this upgrade=json.dumps(self.state.upgrade), ) def load_local_data_files(self) -> None: """ Loads local data files. """ self.logger.info("Loading local data files") # Load files with no verification dependencies first self.load_sensor_variables_file() self.load_actuator_variables_file() self.load_cultivars_file() self.load_cultivation_methods_file() # Load recipe files after sensor/actuator variables, cultivars, and # cultivation methods since verification depends on them self.load_recipe_files() # Load peripheral setup files after sensor/actuator variable since verification # depends on them self.load_peripheral_setup_files() # Load controller setup files after sensor/actuator variable since verification # depends on them self.load_controller_setup_files() # Load device config after peripheral setups since verification # depends on them self.load_device_config_files() def load_sensor_variables_file(self) -> None: """ Loads sensor variables file into database after removing all existing entries. """ self.logger.debug("Loading sensor variables file") # Load sensor variables and schema sensor_variables = json.load(open(SENSOR_VARIABLES_PATH)) sensor_variables_schema = json.load(open(SENSOR_VARIABLES_SCHEMA_PATH)) # Validate sensor variables with schema jsonschema.validate(sensor_variables, sensor_variables_schema) # Delete sensor variables tables models.SensorVariableModel.objects.all().delete() # Create sensor variables table for sensor_variable in sensor_variables: models.SensorVariableModel.objects.create( json=json.dumps(sensor_variable)) def load_actuator_variables_file(self) -> None: """ Loads actuator variables file into database after removing all existing entries. """ self.logger.debug("Loading actuator variables file") # Load actuator variables and schema actuator_variables = json.load(open(ACTUATOR_VARIABLES_PATH)) actuator_variables_schema = json.load( open(ACTUATOR_VARIABLES_SCHEMA_PATH)) # Validate actuator variables with schema jsonschema.validate(actuator_variables, actuator_variables_schema) # Delete actuator variables tables models.ActuatorVariableModel.objects.all().delete() # Create actuator variables table for actuator_variable in actuator_variables: models.ActuatorVariableModel.objects.create( json=json.dumps(actuator_variable)) def load_cultivars_file(self) -> None: """ Loads cultivars file into database after removing all existing entries.""" self.logger.debug("Loading cultivars file") # Load cultivars and schema cultivars = json.load(open(CULTIVARS_PATH)) cultivars_schema = json.load(open(CULTIVARS_SCHEMA_PATH)) # Validate cultivars with schema jsonschema.validate(cultivars, cultivars_schema) # Delete cultivars tables models.CultivarModel.objects.all().delete() # Create cultivars table for cultivar in cultivars: models.CultivarModel.objects.create(json=json.dumps(cultivar)) def load_cultivation_methods_file(self) -> None: """ Loads cultivation methods file into database after removing all existing entries. """ self.logger.debug("Loading cultivation methods file") # Load cultivation methods and schema cultivation_methods = json.load(open(CULTIVATION_METHODS_PATH)) cultivation_methods_schema = json.load( open(CULTIVATION_METHODS_SCHEMA_PATH)) # Validate cultivation methods with schema jsonschema.validate(cultivation_methods, cultivation_methods_schema) # Delete cultivation methods tables models.CultivationMethodModel.objects.all().delete() # Create cultivation methods table for cultivation_method in cultivation_methods: models.CultivationMethodModel.objects.create( json=json.dumps(cultivation_method)) def load_recipe_files(self) -> None: """Loads recipe files into database via recipe manager create or update function.""" self.logger.debug("Loading recipe files") # Get recipes for filepath in glob.glob(RECIPES_PATH): self.logger.debug("Loading recipe file: {}".format(filepath)) with open(filepath, "r") as f: json_ = f.read().replace("\n", "") message, code = self.recipe.create_or_update_recipe(json_) if code != 200: filename = filepath.split("/")[-1] error = "Unable to load {} -> {}".format(filename, message) self.logger.error(error) def load_peripheral_setup_files(self) -> None: """Loads peripheral setup files from codebase into database by creating new entries after deleting existing entries. Verification depends on sensor and actuator variables.""" self.logger.info("Loading peripheral setup files") # Get peripheral setups peripheral_setups = [] for filepath in glob.glob(PERIPHERAL_SETUP_FILES_PATH): self.logger.debug( "Loading peripheral setup file: {}".format(filepath)) peripheral_setups.append(json.load(open(filepath))) # Get get peripheral setup schema # TODO: Finish schema peripheral_setup_schema = json.load(open(PERIPHERAL_SETUP_SCHEMA_PATH)) # Validate peripheral setups with schema for peripheral_setup in peripheral_setups: jsonschema.validate(peripheral_setup, peripheral_setup_schema) # Delete all peripheral setup entries from database models.PeripheralSetupModel.objects.all().delete() # TODO: Validate peripheral setup variables with database variables # Create peripheral setup entries in database for peripheral_setup in peripheral_setups: models.PeripheralSetupModel.objects.create( json=json.dumps(peripheral_setup)) def load_controller_setup_files(self) -> None: """Loads controller setup files from codebase into database by creating new entries after deleting existing entries. Verification depends on sensor and actuator variables.""" self.logger.info("Loading controller setup files") # Get controller setups controller_setups = [] for filepath in glob.glob(CONTROLLER_SETUP_FILES_PATH): self.logger.debug( "Loading controller setup file: {}".format(filepath)) controller_setups.append(json.load(open(filepath))) # Get get controller setup schema controller_setup_schema = json.load(open(CONTROLLER_SETUP_SCHEMA_PATH)) # Validate peripheral setups with schema for controller_setup in controller_setups: jsonschema.validate(controller_setup, controller_setup_schema) # Delete all peripheral setup entries from database models.ControllerSetupModel.objects.all().delete() # TODO: Validate controller setup variables with database variables # Create peripheral setup entries in database for controller_setup in controller_setups: models.ControllerSetupModel.objects.create( json=json.dumps(controller_setup)) def load_device_config_files(self) -> None: """Loads device config files from codebase into database by creating new entries after deleting existing entries. Verification depends on peripheral setups. """ self.logger.info("Loading device config files") # Get devices device_configs = [] for filepath in glob.glob(DEVICE_CONFIG_FILES_PATH): self.logger.debug( "Loading device config file: {}".format(filepath)) device_configs.append(json.load(open(filepath))) # Get get device config schema # TODO: Finish schema (see optional objects) device_config_schema = json.load(open(DEVICE_CONFIG_SCHEMA_PATH)) # Validate device configs with schema for device_config in device_configs: jsonschema.validate(device_config, device_config_schema) # TODO: Validate device config with peripherals # TODO: Validate device config with varibles # Delete all device config entries from database models.DeviceConfigModel.objects.all().delete() # Create device config entry if new or update existing for device_config in device_configs: models.DeviceConfigModel.objects.create( json=json.dumps(device_config)) def load_database_stored_state(self) -> None: """ Loads stored state from database if it exists. """ self.logger.info("Loading database stored state") # Get stored state from database if not models.StateModel.objects.filter(pk=1).exists(): self.logger.info("No stored state in database") self.config_uuid = None return stored_state = models.StateModel.objects.filter(pk=1).first() # Load device state stored_device_state = json.loads(stored_state.device) # Load recipe state stored_recipe_state = json.loads(stored_state.recipe) self.recipe.recipe_uuid = stored_recipe_state["recipe_uuid"] self.recipe.recipe_name = stored_recipe_state["recipe_name"] self.recipe.duration_minutes = stored_recipe_state["duration_minutes"] self.recipe.start_timestamp_minutes = stored_recipe_state[ "start_timestamp_minutes"] self.recipe.last_update_minute = stored_recipe_state[ "last_update_minute"] self.recipe.stored_mode = stored_recipe_state["mode"] # Load peripherals state stored_peripherals_state = json.loads(stored_state.peripherals) for peripheral_name in stored_peripherals_state: self.state.peripherals[peripheral_name] = {} if "stored" in stored_peripherals_state[peripheral_name]: stored = stored_peripherals_state[peripheral_name]["stored"] self.state.peripherals[peripheral_name]["stored"] = stored # Load controllers state stored_controllers_state = json.loads(stored_state.controllers) for controller_name in stored_controllers_state: self.state.controllers[controller_name] = {} if "stored" in stored_controllers_state[controller_name]: stored = stored_controllers_state[controller_name]["stored"] self.state.controllers[controller_name]["stored"] = stored # Load iot state stored_iot_state = json.loads(stored_state.iot) self.state.iot["stored"] = stored_iot_state.get("stored", {}) def store_environment(self) -> None: """ Stores current environment state in environment table. """ models.EnvironmentModel.objects.create(state=self.state.environment) def create_peripherals(self) -> None: """ Creates peripheral managers. """ self.logger.info("Creating peripheral managers") # Verify peripherals are configured if self.config_dict.get("peripherals") == None: self.logger.info("No peripherals configured") return # Set var type mux_simulator: Optional[MuxSimulator] # Inintilize simulation parameters if os.environ.get("SIMULATE") == "true": simulate = True mux_simulator = MuxSimulator() else: simulate = False mux_simulator = None # Create thread locks i2c_lock = threading.RLock() # Create peripheral managers self.peripherals = {} peripheral_config_dicts = self.config_dict.get("peripherals", {}) for peripheral_config_dict in peripheral_config_dicts: self.logger.debug("Creating {}".format( peripheral_config_dict["name"])) # Get peripheral setup dict peripheral_uuid = peripheral_config_dict["uuid"] peripheral_setup_dict = self.get_peripheral_setup_dict( peripheral_uuid) self.logger.debug("UUID {}".format(peripheral_uuid)) # Verify valid peripheral config dict if peripheral_setup_dict == {}: self.logger.critical( "Invalid peripheral uuid in device " "config. Validator should have caught this.") continue # Get peripheral module and class name module_name = ("device.peripherals.modules." + peripheral_setup_dict["module_name"]) class_name = peripheral_setup_dict["class_name"] # Import peripheral library module_instance = __import__(module_name, fromlist=[class_name]) class_instance = getattr(module_instance, class_name) # Create peripheral manager peripheral_name = peripheral_config_dict["name"] peripheral = class_instance( name=peripheral_name, state=self.state, config=peripheral_config_dict, simulate=simulate, i2c_lock=i2c_lock, mux_simulator=mux_simulator, ) self.peripherals[peripheral_name] = peripheral def get_peripheral_setup_dict(self, uuid: str) -> Dict[str, Any]: """Gets peripheral setup dict for uuid in peripheral setup table.""" if not models.PeripheralSetupModel.objects.filter(uuid=uuid).exists(): return {} else: json_ = models.PeripheralSetupModel.objects.get(uuid=uuid).json peripheral_setup_dict = json.loads(json_) return peripheral_setup_dict # type: ignore def get_controller_setup_dict(self, uuid: str) -> Dict[str, Any]: """Gets controller setup dict for uuid in peripheral setup table.""" if not models.ControllerSetupModel.objects.filter(uuid=uuid).exists(): return {} else: json_ = models.ControllerSetupModel.objects.get(uuid=uuid).json controller_setup_dict = json.loads(json_) return controller_setup_dict # type: ignore def spawn_peripherals(self) -> None: """ Spawns peripherals. """ if self.peripherals == {}: self.logger.info("No peripheral threads to spawn") else: self.logger.info("Spawning peripherals") for name, manager in self.peripherals.items(): manager.spawn() def create_controllers(self) -> None: """ Creates controller managers. """ self.logger.info("Creating controller managers") # Verify controllers are configured if self.config_dict.get("controllers") == None: self.logger.info("No controllers configured") return # Create controller managers self.controllers = {} controller_config_dicts = self.config_dict.get("controllers", {}) for controller_config_dict in controller_config_dicts: self.logger.debug("Creating {}".format( controller_config_dict["name"])) # Get controller setup dict controller_uuid = controller_config_dict["uuid"] controller_setup_dict = self.get_controller_setup_dict( controller_uuid) # Verify valid controller config dict if controller_setup_dict == None: self.logger.critical( "Invalid controller uuid in device " "config. Validator should have caught this.") continue # Get controller module and class name module_name = ("device.controllers.modules." + controller_setup_dict["module_name"]) class_name = controller_setup_dict["class_name"] # Import controller library module_instance = __import__(module_name, fromlist=[class_name]) class_instance = getattr(module_instance, class_name) # Create controller manager controller_name = controller_config_dict["name"] controller_manager = class_instance(controller_name, self.state, controller_config_dict) self.controllers[controller_name] = controller_manager def spawn_controllers(self) -> None: """ Spawns controllers. """ if self.controllers == {}: self.logger.info("No controller threads to spawn") else: self.logger.info("Spawning controllers") for name, manager in self.controllers.items(): self.logger.debug("Spawning {}".format(name)) manager.spawn() def all_managers_initialized(self) -> bool: """Checks if all managers have initialized.""" if self.recipe.mode == modes.INIT: return False elif not self.all_peripherals_initialized(): return False elif not self.all_controllers_initialized(): return False return True def all_peripherals_initialized(self) -> bool: """Checks if all peripherals have initialized.""" for name, manager in self.peripherals.items(): if manager.mode == modes.INIT: return False return True def all_controllers_initialized(self) -> bool: """Checks if all controllers have initialized.""" for name, manager in self.controllers.items(): if manager.mode == modes.INIT: return False return True def shutdown_peripheral_threads(self) -> None: """Shutsdown all peripheral threads.""" for name, manager in self.peripherals.items(): manager.shutdown() def shutdown_controller_threads(self) -> None: """Shutsdown all controller threads.""" for name, manager in self.controllers.items(): manager.shutdown() def all_peripherals_shutdown(self) -> bool: """Check if all peripherals are shutdown.""" for name, manager in self.peripherals.items(): if manager.thread.is_alive(): return False return True def all_controllers_shutdown(self) -> bool: """Check if all controllers are shutdown.""" for name, manager in self.controllers.items(): if manager.thread.is_alive(): return False return True ##### EVENT FUNCTIONS ############################################################## def check_events(self) -> None: """Checks for a new event. Only processes one event per call, even if there are multiple in the queue. Events are processed first-in-first-out (FIFO).""" # Check for new events if self.event_queue.empty(): return # Get request request = self.event_queue.get() self.logger.debug("Received new request: {}".format(request)) # Get request parameters try: type_ = request["type"] except KeyError as e: message = "Invalid request parameters: {}".format(e) self.logger.exception(message) return # Execute request if type_ == events.RESET: self._reset() # Defined in parent class elif type_ == events.SHUTDOWN: self._shutdown() # Defined in parent class elif type_ == events.LOAD_DEVICE_CONFIG: self._load_device_config(request) else: self.logger.error( "Invalid event request type in queue: {}".format(type_)) def load_device_config(self, uuid: str) -> Tuple[str, int]: """Pre-processes load device config event request.""" self.logger.debug("Pre-processing load device config request") # Get filename of corresponding uuid filename = None for filepath in glob.glob(DEVICE_CONFIG_FILES_PATH): self.logger.debug(filepath) device_config = json.load(open(filepath)) if device_config["uuid"] == uuid: filename = filepath.split("/")[-1].replace(".json", "") # Verify valid config uuid if filename == None: message = "Invalid config uuid, corresponding filepath not found" self.logger.debug(message) return message, 400 # Check valid mode transition if enabled if not self.valid_transition(self.mode, modes.LOAD): message = "Unable to load device config from {} mode".format( self.mode) self.logger.debug(message) return message, 400 # Add load device config event request to event queue request = {"type": events.LOAD_DEVICE_CONFIG, "filename": filename} self.event_queue.put(request) # Successfully added load device config request to event queue message = "Loading config" return message, 200 def _load_device_config(self, request: Dict[str, Any]) -> None: """Processes load device config event request.""" self.logger.debug("Processing load device config request") # Get request parameters filename = request.get("filename") # Write config filename to device config path with open(DEVICE_CONFIG_PATH, "w") as f: f.write(str(filename) + "\n") # Transition to init mode on next state machine update self.mode = modes.LOAD
# Import standard python modules import subprocess, os, re # Import device utilities from device.utilities.logger import Logger from django.conf import settings # Initialize file paths # DEVICE_CONFIG_PATH = "data/config/device.txt" # DATA_PATH = os.getenv("STORAGE_LOCATION", "data") DEVICE_CONFIG_PATH = settings.DATA_PATH + "/config/device.txt" # Initialize logger logger = Logger("SystemUtility", "system") logger.debug("Initializing utility") def device_config_name() -> str: """Gets device config name from file.""" logger.debug("Getting device config name") # Get device config name if os.path.exists(DEVICE_CONFIG_PATH): with open(DEVICE_CONFIG_PATH) as f: device_config_name = f.readline().strip() else: device_config_name = "unspecified" # Successfully got device config name logger.debug("Device config name: {}".format(device_config_name))
class USBCameraDriver: """Driver for a usb camera.""" def __init__( self, name: str, vendor_id: int, product_id: int, resolution: str, simulate: bool = False, usb_mux_comms: Optional[Dict[str, Any]] = None, usb_mux_channel: Optional[int] = None, i2c_lock: Optional[threading.Lock] = None, mux_simulator: Optional[MuxSimulator] = None, ) -> None: """Initializes USB camera camera.""" # Initialize parameters self.name = name self.vendor_id = vendor_id self.product_id = product_id self.resolution = resolution self.simulate = simulate # Initialize logger self.logger = Logger(name="Driver({})".format(name), dunder_name=__name__) # Check if simulating if simulate: self.logger.info("Simulating driver") self.directory = "device/peripherals/modules/usb_camera/tests/images/" else: self.directory = IMAGE_DIR # Check directory exists else create it if not os.path.exists(self.directory): os.makedirs(self.directory) # Check if using usb mux if usb_mux_comms == None or usb_mux_channel == None: self.dac5578 = None return # Get optional i2c parameters mux = usb_mux_comms.get("mux", None) # type: ignore if mux != None: mux = int(mux, 16) # Using usb mux, initialize driver try: self.dac5578 = DAC5578Driver( name=name, i2c_lock=i2c_lock, # type: ignore bus=usb_mux_comms.get("bus", None), # type: ignore address=int(usb_mux_comms.get("address", None), 16), # type: ignore mux=mux, channel=usb_mux_comms.get("channel", None), # type: ignore simulate=simulate, mux_simulator=mux_simulator, ) self.usb_mux_channel = usb_mux_channel except I2CError as e: raise InitError(logger=self.logger) from e def list_cameras(self, vendor_id: Optional[int] = None, product_id: Optional[int] = None) -> List[str]: """Returns list of cameras that match the provided vendor id and product id.""" # List all cameras cameras = glob.glob("/dev/video*") # Check if filtering by product and vendor id if vendor_id == None and product_id == None: return cameras # Check for product and vendor id matches matches = [] for camera in cameras: if usb_device_matches(camera, vendor_id, product_id): matches.append(camera) return matches def get_camera(self) -> str: """Gets camera path.""" # Get camera paths that match vendor and product ID cameras = self.list_cameras(self.vendor_id, self.product_id) # Check only one active camera if len(cameras) < 1: message = "no active cameras" raise GetCameraError(message=message, logger=self.logger) elif len(cameras) > 1: message = "too many active cameras" raise GetCameraError(message=message, logger=self.logger) # Successfuly got one camera return cameras[0] def enable_camera(self, retry: bool = True) -> None: """Enables camera by setting dac output high.""" self.logger.debug("Enabling camera") # Turn on usb mux channel try: self.dac5578.set_high(channel=self.usb_mux_channel, retry=retry) # type: ignore except DriverError as e: raise EnableCameraError(logger=self.logger) from e # Wait for camera to initialize time.sleep(5) def disable_camera(self, retry: bool = True) -> None: """Disables camera by setting dac output low.""" self.logger.debug("Disabling camera") # Turn off usb mux channel try: self.dac5578.set_low(channel=self.usb_mux_channel, retry=retry) # type: ignore except DriverError as e: raise DisableCameraError(logger=self.logger) from e # Wait for camera to power down start_time = time.time() while True: # 5 second timeout # Look for camera try: camera = self.get_camera() # Check if camera powered down if camera == None: self.logger.debug("Camera powered down") return # TODO: Handle specific exceptions except Exception as e: raise DisableCameraError(logger=self.logger) from e # Check for timeout if time.time() - start_time > 5: # 5 second timeout message = "timed out" raise DisableCameraError(message=message, logger=self.logger) # Update every 100ms time.sleep(0.1) def capture(self, retry: bool = True) -> None: """Manages usb mux and captures an image.""" # Check if not using usb mux if self.dac5578 == None: return self.capture_image() # TODO: Lock camera threads # Manage 'mux' and capture image try: self.enable_camera() self.capture_image() self.disable_camera() except DriverError as e: raise CaptureError(logger=self.logger) from e # TODO: Unlock camera threads def capture_image(self) -> None: """Captures an image.""" # Name image according to ISO8601 timestr = datetime.datetime.utcnow().strftime("%Y-%m-%d-T%H:%M:%SZ") filename = timestr + "_" + self.name + ".png" # Specify filepaths image_path = self.directory + filename base_path = "device/peripherals/modules/usb_camera" dummy_path = "{}/dummy.png".format(base_path) active_path = "{}/active.jpg".format(base_path) # Check if simulated if self.simulate: self.logger.info( "Simulating capture, saving simulation image to: {}".format( image_path)) command = "cp device/peripherals/modules/usb_camera/tests/simulation_image.png {}".format( image_path) os.system(command) return # Camera not simulated, get camera path try: camera = self.get_camera() except GetCameraError as e: raise CaptureImageError(logger=self.logger) from e # Capture image try: # Take 3 low res images to clear out buffer self.logger.debug("Capturing dummy images") command = "fswebcam -d {} -r '320x240' ".format(camera, dummy_path) for i in range(3): os.system(command) # Try taking up to 3 real images self.logger.debug("Capturing active image") command = "fswebcam -d {} -r {} --png 9 -F 10 --no-banner --save {}".format( camera, self.resolution, active_path) valid_image = False for i in range(3): os.system(command) size = os.path.getsize(active_path) # Check if image meets minimum size constraint # TODO: Check lighting conditions (if box is dark, images are small) if size > 160000: # 160kB valid_image = True break # Check if active image is valid, if so copy to images/ directory if not valid_image: self.logger.warning("Unable to capture a valid image") else: self.logger.info( "Captured image, saved to {}".format(image_path)) os.rename(active_path, image_path) except Exception as e: raise CaptureImageError(logger=self.logger) from e
class I2C(object): """I2C communication device. Can communicate with device directly or via an I2C mux.""" def __init__( self, name: str, i2c_lock: threading.RLock, address: int, bus: Optional[int] = None, mux: Optional[int] = None, channel: Optional[int] = None, mux_simulator: Optional[MuxSimulator] = None, PeripheralSimulator: Optional[PeripheralSimulator] = None, verify_device: bool = True, ) -> None: # Initialize passed in parameters self.name = name self.i2c_lock = i2c_lock self.bus = bus self.address = address self.mux = mux self.channel = channel # Initialize logger logname = "I2C({})".format(self.name) self.logger = Logger(logname, "i2c") self.logger.debug("Initializing communication") # Verify mux config if self.mux != None and self.channel == None: raise InitError( "Mux requires channel value to be set") from ValueError # Initialize io if PeripheralSimulator != None: self.logger.debug("Using simulated io stream") self.io = PeripheralSimulator( # type: ignore name, bus, address, mux, channel, mux_simulator) else: self.logger.debug("Using device io stream") with self.i2c_lock: self.io = DeviceIO(name, bus) # Verify mux exists if self.mux != None: self.verify_mux() # Verify device exists if verify_device: self.verify_device() # Successfully initialized! self.logger.debug("Initialization successful") def verify_mux(self) -> None: """Verifies mux exists by trying to set it to a channel.""" try: self.logger.debug("Verifying mux exists") byte = self.set_mux(self.mux, self.channel, retry=True) except MuxError as e: message = "Unable to verify mux exists" raise InitError(message, logger=self.logger) from e def verify_device(self) -> None: """Verifies device exists by trying to read a byte from it.""" try: self.logger.debug("Verifying device exists") byte = self.read(1, retry=True) except ReadError as e: message = "Unable to verify device exists, read error" raise InitError(message, logger=self.logger) from e except MuxError as e: message = "Unable to verify device exists, mux error" raise InitError(message, logger=self.logger) from e @retry((WriteError, MuxError), tries=5, delay=0.2, backoff=3) def write(self, bytes_: bytes, retry: bool = True, disable_mux: bool = False) -> None: """Writes byte list to device. Converts byte list to byte array then sends bytes. Returns error message.""" with self.i2c_lock: self.manage_mux("write bytes", disable_mux) self.logger.debug("Writing bytes: {}".format(byte_str(bytes_))) self.io.write(self.address, bytes_) @retry((ReadError, MuxError), tries=5, delay=0.2, backoff=3) def read(self, num_bytes: int, retry: bool = True, disable_mux: bool = False) -> bytes: """Reads num bytes from device. Returns byte array.""" with self.i2c_lock: self.manage_mux("read bytes", disable_mux) self.logger.debug("Reading {} bytes".format(num_bytes)) bytes_ = bytes(self.io.read(self.address, num_bytes)) self.logger.debug("Read bytes: {}".format(byte_str(bytes_))) return bytes_ @retry((ReadError, MuxError), tries=5, delay=0.2, backoff=3) def read_register(self, register: int, retry: bool = True, disable_mux: bool = False) -> int: """Reads byte stored in register at address.""" with self.i2c_lock: self.manage_mux("read register", disable_mux) self.logger.debug("Reading register: 0x{:02X}".format(register)) return int(self.io.read_register(self.address, register)) @retry((WriteError, MuxError), tries=5, delay=0.2, backoff=3) def write_register(self, register: int, value: int, retry: bool = True, disable_mux: bool = False) -> None: with self.i2c_lock: self.manage_mux("write register", disable_mux) message = "Writing register: 0x{:02X}, value: 0x{:02X}".format( register, value) self.logger.debug(message) self.io.write_register(self.address, register, value) @retry(MuxError, tries=5, delay=0.2, backoff=3) def set_mux(self, mux: int, channel: int, retry: bool = True) -> None: """Sets mux to channel""" with self.i2c_lock: channel_byte = 0x01 << channel self.logger.debug( "Setting mux 0x{:02X} to channel {}, writing: [0x{:02X}]". format(mux, channel, channel_byte)) try: self.io.write(mux, bytes([channel_byte])) except WriteError as e: raise MuxError("Unable to set mux", logger=self.logger) from e def manage_mux(self, message: str, disable_mux: bool) -> None: """Sets mux if enabled.""" if disable_mux: return elif self.mux != None: self.logger.debug("Managing mux to {}".format(message)) self.set_mux(self.mux, self.channel, retry=False)
def __init__( self, name: str, vendor_id: int, product_id: int, resolution: str, simulate: bool = False, usb_mux_comms: Optional[Dict[str, Any]] = None, usb_mux_channel: Optional[int] = None, i2c_lock: Optional[threading.Lock] = None, mux_simulator: Optional[MuxSimulator] = None, ) -> None: """Initializes USB camera camera.""" # Initialize parameters self.name = name self.vendor_id = vendor_id self.product_id = product_id self.resolution = resolution self.simulate = simulate # Initialize logger self.logger = Logger(name="Driver({})".format(name), dunder_name=__name__) # Check if simulating if simulate: self.logger.info("Simulating driver") self.directory = "device/peripherals/modules/usb_camera/tests/images/" else: self.directory = IMAGE_DIR # Check directory exists else create it if not os.path.exists(self.directory): os.makedirs(self.directory) # Check if using usb mux if usb_mux_comms == None or usb_mux_channel == None: self.dac5578 = None return # Get optional i2c parameters mux = usb_mux_comms.get("mux", None) # type: ignore if mux != None: mux = int(mux, 16) # Using usb mux, initialize driver try: self.dac5578 = DAC5578Driver( name=name, i2c_lock=i2c_lock, # type: ignore bus=usb_mux_comms.get("bus", None), # type: ignore address=int(usb_mux_comms.get("address", None), 16), # type: ignore mux=mux, channel=usb_mux_comms.get("channel", None), # type: ignore simulate=simulate, mux_simulator=mux_simulator, ) self.usb_mux_channel = usb_mux_channel except I2CError as e: raise InitError(logger=self.logger) from e
class AtlasDriver: """Parent class for atlas drivers.""" def __init__( self, name: str, i2c_lock: threading.RLock, bus: int, address: int, mux: Optional[int] = None, channel: Optional[int] = None, simulate: bool = False, mux_simulator: Optional[MuxSimulator] = None, Simulator: Optional[PeripheralSimulator] = None, ) -> None: """ Initializes atlas driver. """ # Initialize parameters self.simulate = simulate # Initialize logger logname = "Driver({})".format(name) self.logger = Logger(logname, "peripherals") # Initialize I2C try: self.i2c = I2C( name=name, i2c_lock=i2c_lock, bus=bus, address=address, mux=mux, channel=channel, mux_simulator=mux_simulator, PeripheralSimulator=Simulator, ) except I2CError as e: raise exceptions.InitError(logger=self.logger) def setup(self, retry: bool = True) -> None: """Setsup sensor.""" self.logger.debug("Setting up sensor") try: self.enable_led() info = self.read_info() if info.firmware_version > 1.94: self.enable_protocol_lock() except Exception as e: raise exceptions.SetupError(logger=self.logger) from e def process_command( self, command_string: str, process_seconds: float, num_bytes: int = 31, retry: bool = True, read_response: bool = True, ) -> Optional[str]: """Sends command string to device, waits for processing seconds, then tries to read num response bytes with optional retry if device returns a `still processing` response code. Read retry is enabled by default. Returns response string on success or raises exception on error.""" self.logger.debug("Processing command: {}".format(command_string)) try: # Send command to device byte_array = bytearray(command_string + "\00", "utf8") self.i2c.write(bytes(byte_array), retry=retry) # Check if reading response if read_response: return self.read_response(process_seconds, num_bytes, retry=retry) # Otherwise return none return None except Exception as e: raise exceptions.ProcessCommandError(logger=self.logger) from e def read_response(self, process_seconds: float, num_bytes: int, retry: bool = True) -> str: """Reads response from from device. Waits processing seconds then tries to read num response bytes with optional retry. Returns response string on success or raises exception on error.""" # Give device time to process self.logger.debug("Waiting for {} seconds".format(process_seconds)) time.sleep(process_seconds) # Read device dataSet try: self.logger.debug("Reading response") data = self.i2c.read(num_bytes) except Exception as e: raise exceptions.ReadResponseError(logger=self.logger) from e # Format response code response_code = int(data[0]) # Check for invalid syntax if response_code == 2: message = "invalid command string syntax" raise exceptions.ReadResponseError(message=message, logger=self.logger) # Check if still processing elif response_code == 254: # Try to read one more time if retry enabled if retry == True: self.logger.debug("Sensor still processing, retrying read") return self.read_response(process_seconds, num_bytes, retry=False) else: message = "insufficient processing time" raise exceptions.ReadResponseError(message, logger=self.logger) # Check if device has no data to send elif response_code == 255: # Try to read one more time if retry enabled if retry == True: self.logger.warning( "Sensor reported no data to read, retrying read") return self.read_response(process_seconds, num_bytes, retry=False) else: message = "insufficient processing time" raise exceptions.ReadResponseError(message=message, logger=self.logger) # Invalid response code elif response_code != 1: message = "invalid response code" raise exceptions.ReadResponseError(message=message, logger=self.logger) # Successfully read response response_message = str(data[1:].decode("utf-8").strip("\x00")) self.logger.debug("Response:`{}`".format(response_message)) return response_message def read_info(self, retry: bool = True) -> Info: """Read sensor info register containing sensor type and firmware version. e.g. EC, 2.0.""" self.logger.debug("Reading info register") # Send command try: response = self.process_command("i", process_seconds=0.3, retry=retry) except Exception as e: raise exceptions.ReadInfoError(logger=self.logger) from e # Parse response _, sensor_type, firmware_version = response.split(",") # type: ignore firmware_version = float(firmware_version) # Store firmware version self.firmware_version = firmware_version # Create info dataclass info = Info(sensor_type=sensor_type.lower(), firmware_version=firmware_version) # Successfully read info self.logger.debug(str(info)) return info def read_status(self, retry: bool = True) -> Status: """Reads status from device.""" self.logger.debug("Reading status register") try: response = self.process_command("Status", process_seconds=0.3, retry=retry) except Exception as e: raise exceptions.ReadStatusError(logger=self.logger) from e # Parse response message command, code, voltage = response.split(",") # type: ignore # Break out restart code if code == "P": prev_restart_reason = "Powered off" self.logger.debug("Device previous restart due to powered off") elif code == "S": prev_restart_reason = "Software reset" self.logger.debug("Device previous restart due to software reset") elif code == "B": prev_restart_reason = "Browned out" self.logger.critical("Device browned out on previous restart") elif code == "W": prev_restart_reason = "Watchdog" self.logger.debug("Device previous restart due to watchdog") elif code == "U": self.prev_restart_reason = "Unknown" self.logger.warning("Device previous restart due to unknown") # Build status data class status = Status(prev_restart_reason=prev_restart_reason, voltage=float(voltage)) # Successfully read status self.logger.debug(str(status)) return status def enable_protocol_lock(self, retry: bool = True) -> None: """Enables protocol lock.""" self.logger.debug("Enabling protocol lock") try: self.process_command("Plock,1", process_seconds=0.9, retry=retry) except Exception as e: raise exceptions.EnableProtocolLockError(logger=self.logger) from e def disable_protocol_lock(self, retry: bool = True) -> None: """Disables protocol lock.""" self.logger.debug("Disabling protocol lock") try: self.process_command("Plock,0", process_seconds=0.9, retry=retry) except Exception as e: raise exceptions.DisableProtocolLockError( logger=self.logger) from e def enable_led(self, retry: bool = True) -> None: """Enables led.""" self.logger.debug("Enabling led") try: self.process_command("L,1", process_seconds=1.8, retry=retry) except Exception as e: raise exceptions.EnableLEDError(logger=self.logger) from e def disable_led(self, retry: bool = True) -> None: """Disables led.""" self.logger.debug("Disabling led") try: self.process_command("L,0", process_seconds=1.8, retry=retry) except Exception as e: raise exceptions.DisableLEDError(logger=self.logger) from e def enable_sleep_mode(self, retry: bool = True) -> None: """Enables sleep mode, sensor will wake up by sending any command to it.""" self.logger.debug("Enabling sleep mode") # Send command try: self.process_command("Sleep", process_seconds=0.3, read_response=False, retry=retry) except Exception as e: raise exceptions.EnableSleepModeError(logger=self.logger) from e def set_compensation_temperature(self, temperature: float, retry: bool = True) -> None: """Sets compensation temperature.""" self.logger.debug("Setting compensation temperature") try: command = "T,{}".format(temperature) self.process_command(command, process_seconds=0.3, retry=retry) except Exception as e: raise exceptions.SetCompensationTemperatureError( logger=self.logger) from e def calibrate_low(self, value: float, retry: bool = True) -> None: """Takes a low point calibration reading.""" self.logger.debug("Taking low point calibration reading") try: command = "Cal,low,{}".format(value) self.process_command(command, process_seconds=0.9, retry=retry) except Exception as e: raise exceptions.TakeLowPointCalibrationError( logger=self.logger) from e def calibrate_mid(self, value: float, retry: bool = True) -> None: """Takes a mid point calibration reading.""" self.logger.debug("Taking mid point calibration reading") try: command = "Cal,mid,{}".format(value) self.process_command(command, process_seconds=0.9, retry=retry) except Exception as e: raise exceptions.TakeMidPointCalibrationError( logger=self.logger) from e def calibrate_high(self, value: float, retry: bool = True) -> None: """Takes a high point calibration reading.""" self.logger.debug("Taking high point calibration reading") try: command = "Cal,high,{}".format(value) self.process_command(command, process_seconds=0.9, retry=retry) except Exception as e: raise exceptions.TakeHighPointCalibrationError( logger=self.logger) from e def clear_calibrations(self, retry: bool = True) -> None: """Clears calibration readings.""" self.logger.debug("Clearing calibration readings") try: self.process_command("Cal,clear", process_seconds=0.9, retry=retry) except Exception as e: raise exceptions.ClearCalibrationError(logger=self.logger) from e def factory_reset(self, retry: bool = True) -> None: """Resets sensor to factory config.""" self.logger.debug("Performing factory reset") try: self.process_command("Factory", process_seconds=0.3, read_response=False, retry=retry) except Exception as e: raise exceptions.FactoryResetError(logger=self.logger) from e
# Import device utilities from device.utilities.logger import Logger # Import driver elements from device.peripherals.modules.bacnet import exceptions # Conditionally import the bacpypes wrapper class, or use the simulator. # The brain that runs on PFCs doesn't have or need BACnet communications, # only the LGHC (running on linux) does. try: from device.peripherals.modules.bacnet import bnet_wrapper as BACNET except Exception as e: l = Logger("\n\nBACNet.driver", __name__) l.critical(e) from device.peripherals.modules.bacnet import bnet_simulator as BACNET class BacnetDriver: """Driver for BACNet communications to HVAC.""" # -------------------------------------------------------------------------- def __init__(self, name: str, simulate: bool = False, ini_file: str = None, config_file: str = None, debug: bool = False) -> None: """Initializes bacpypes.""" self.logger = Logger(name + ".BACNet", __name__)
class DAC5578Driver: """Driver for DAC5578 digital to analog converter.""" def __init__( self, name: str, i2c_lock: threading.RLock, bus: int, address: int, mux: Optional[int] = None, channel: Optional[int] = None, simulate: bool = False, mux_simulator: Optional[MuxSimulator] = None, ) -> None: """Initializes DAC5578.""" # Initialize logger logname = "DAC5578-({})".format(name) self.logger = Logger(logname, __name__) # Check if simulating if simulate: self.logger.info("Simulating driver") Simulator = DAC5578Simulator else: Simulator = None # Initialize I2C try: self.i2c = I2C( name="DAC5578-{}".format(name), i2c_lock=i2c_lock, bus=bus, address=address, mux=mux, channel=channel, mux_simulator=mux_simulator, PeripheralSimulator=Simulator, ) except I2CError as e: raise exceptions.InitError(logger=self.logger) from e def write_output(self, channel: int, percent: int, retry: bool = True, disable_mux: bool = False) -> None: """Sets output value to channel.""" message = "Writing output on channel {} to: {:.02F}%".format( channel, percent) self.logger.debug(message) # Check valid channel range if channel < 0 or channel > 7: message = "channel out of range, must be within 0-7" raise exceptions.WriteOutputError(message=message, logger=self.logger) # Check valid value range if percent < 0 or percent > 100: message = "output percent out of range, must be within 0-100" raise exceptions.WriteOutputError(message=message, logger=self.logger) # Convert output percent to byte, ensure 100% is byte 255 if percent == 100: byte = 255 else: byte = int(percent * 2.55) # Send set output command to dac self.logger.debug("Writing to dac: ch={}, byte={}".format( channel, byte)) try: self.i2c.write(bytes([0x30 + channel, byte, 0x00]), disable_mux=disable_mux) except I2CError as e: raise exceptions.WriteOutputError(logger=self.logger) from e def write_outputs(self, outputs: dict, retry: bool = True) -> None: """Sets output channels to output percents. Only sets mux once. Keeps thread locked since relies on mux not changing.""" self.logger.debug("Writing outputs: {}".format(outputs)) # Check output dict is not empty if len(outputs) < 1: message = "output dict must not be empty" raise exceptions.WriteOutputsError(message=message, logger=self.logger) if len(outputs) > 8: print("outputs len = {}".format(len(outputs))) message = "output dict must not contain more than 8 entries" raise exceptions.WriteOutputsError(message=message, logger=self.logger) # Run through each output for channel, percent in outputs.items(): message = "Writing output for ch {}: {}%".format(channel, percent) self.logger.debug(message) try: self.write_output(channel, percent, retry=retry) except exceptions.WriteOutputError as e: raise exceptions.WriteOutputsError(logger=self.logger) from e def read_power_register(self, retry: bool = True) -> Optional[Dict[int, bool]]: """Reads power register.""" self.logger.debug("Reading power register") # Read register try: self.i2c.write([0x40], retry=retry) bytes_ = self.i2c.read(2, retry=retry) except I2CError as e: raise exceptions.ReadPowerRegisterError(logger=self.logger) from e # Parse response bytes msb = bytes_[0] lsb = bytes_[1] powered_channels = { 0: not bool(bitwise.get_bit_from_byte(4, msb)), 1: not bool(bitwise.get_bit_from_byte(3, msb)), 2: not bool(bitwise.get_bit_from_byte(2, msb)), 3: not bool(bitwise.get_bit_from_byte(1, msb)), 4: not bool(bitwise.get_bit_from_byte(0, msb)), 5: not bool(bitwise.get_bit_from_byte(7, lsb)), 6: not bool(bitwise.get_bit_from_byte(6, lsb)), 7: not bool(bitwise.get_bit_from_byte(5, lsb)), } return powered_channels def set_high(self, channel: Optional[int] = None, retry: bool = True) -> None: """Sets channel high, sets all channels high if no channel is specified.""" if channel != None: self.logger.debug("Setting channel {} high".format(channel)) try: self.write_output(channel, 100, retry=retry) # type: ignore except exceptions.WriteOutputError as e: raise exceptions.SetHighError(logger=self.logger) from e else: self.logger.debug("Setting all channels high") outputs = { 0: 100, 1: 100, 2: 100, 3: 100, 4: 100, 5: 100, 6: 100, 7: 100 } try: self.write_outputs(outputs, retry=retry) except exceptions.WriteOutputsError as e: raise exceptions.SetHighError(logger=self.logger) from e def set_low(self, channel: Optional[int] = None, retry: bool = True) -> None: """Sets channel low, sets all channels low if no channel is specified.""" if channel != None: self.logger.debug("Setting channel {} low".format(channel)) try: self.write_output(channel, 100, retry=retry) # type: ignore except exceptions.WriteOutputError as e: raise exceptions.SetLowError(logger=self.logger) from e else: self.logger.debug("Setting all channels low") outputs = {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0} try: self.write_outputs(outputs, retry=retry) except exceptions.WriteOutputsError as e: raise exceptions.SetLowError(logger=self.logger) from e
# Import device utilities from device.utilities.logger import Logger from device.utilities import network # Initialize file paths REGISTRATION_DATA_DIR = "data/registration/" DEVICE_ID_PATH = REGISTRATION_DATA_DIR + "device_id.bash" ROOTS_PATH = REGISTRATION_DATA_DIR + "roots.pem" RSA_CERT_PATH = REGISTRATION_DATA_DIR + "rsa_cert.pem" RSA_PRIVATE_PATH = REGISTRATION_DATA_DIR + "rsa_private.pem" VERIFICATION_CODE_PATH = REGISTRATION_DATA_DIR + "verification_code.txt" REGISTER_SCRIPT_PATH = "scripts/one_time_key_creation_and_iot_device_registration.sh" # Initialize logger logger = Logger("IotRegistrationUtility", "iot") def is_registered() -> bool: """Checks if device is registered by checking local files.""" logger.debug("Checking if device is registered") if (os.path.exists(DEVICE_ID_PATH) and os.path.exists(ROOTS_PATH) and os.path.exists(RSA_CERT_PATH) and os.path.exists(RSA_PRIVATE_PATH)): return True else: return False def device_id() -> str: """Gets device id string from local file. TODO: Handle exeptions."""
class BacnetDriver: """Driver for BACNet communications to HVAC.""" # -------------------------------------------------------------------------- def __init__(self, name: str, simulate: bool = False, ini_file: str = None, config_file: str = None, debug: bool = False) -> None: """Initializes bacpypes.""" self.logger = Logger(name + ".BACNet", __name__) if ini_file is None or config_file is None: raise exceptions.InitError(message="Missing file args", logger=self.logger) try: self.logger.info("driver init") self.bnet = BACNET.Bnet(self.logger, ini_file, config_file, debug) except Exception as e: raise exceptions.InitError(logger=self.logger) from e # -------------------------------------------------------------------------- def setup(self) -> None: self.bnet.setup() # -------------------------------------------------------------------------- def reset(self) -> None: self.bnet.reset() # -------------------------------------------------------------------------- # This peripheral thread has been killed by the periph. manager. dead. def shutdown(self) -> None: self.logger.info("shutdown") # -------------------------------------------------------------------------- def ping(self) -> None: self.bnet.ping() # -------------------------------------------------------------------------- def set_test_voltage(self, voltage: float) -> None: if voltage is None or voltage < 0.0 or voltage > 100.0: self.logger.error(f"Test voltage {voltage} out of range (0-100%)") return self.bnet.set_test_voltage(voltage) # -------------------------------------------------------------------------- def set_air_temp(self, tempC: float) -> None: if tempC is None or tempC < -100.0 or tempC > 200.0: self.logger.error(f"Air Temperature Celsius {tempC} out of range") return self.bnet.set_air_temp(tempC) # -------------------------------------------------------------------------- def set_air_RH(self, RH: float) -> None: if RH is None or RH < 0.0 or RH > 100.0: self.logger.error(f"Relative Humidity {RH} out of range") return self.bnet.set_air_RH(RH) # -------------------------------------------------------------------------- def get_air_temp(self) -> float: return self.bnet.get_air_temp() # -------------------------------------------------------------------------- def get_air_RH(self) -> float: return self.bnet.get_air_RH()