diff --git a/config.json b/config.json index 55198ba97..70ba10415 100755 --- a/config.json +++ b/config.json @@ -1 +1 @@ -{"exportVersion": "1.0.0", "exportedBy": "tester", "exportedAt": "2024-10-28T18:58:23.976Z", "exportedFromDevice": {"board": "metroesp32s3", "firmwareVersion": "1.0.0-beta.93", "referenceVoltage": 2.6, "totalGPIOPins": 11, "totalAnalogPins": 6, "rtc": "DS3231", "statusLEDBrightness": 0.1}, "components": [{"componentAPI": "analogio", "name": "Analog Pin", "pinName": "A0", "type": "analog_pin", "mode": "ANALOG", "direction": "INPUT", "sampleMode": "TIMER", "analogReadMode": "PIN_VALUE", "period": 30}, {"componentAPI": "ds18x20", "name": "DS18B20: Temperature Sensor (\u00b0F)", "sensorTypeCount": 2, "sensorType1": "object-temp-fahrenheit", "sensorType2": "object-temp", "pinName": "D4", "sensorResolution": 12, "period": 30}], "checksum": 236} \ No newline at end of file +{"exportVersion": "1.0.0", "exportedBy": "tester", "exportedAt": "2024-10-28T18:58:23.976Z", "exportedFromDevice": {"board": "metroesp32s3", "firmwareVersion": "1.0.0-beta.93", "referenceVoltage": 2.6, "totalGPIOPins": 11, "totalAnalogPins": 6, "rtc": "DS3231", "statusLEDBrightness": 0.1}, "components": [{"componentAPI": "analogin", "name": "Analog Pin", "pinName": "A0", "type": "analog_pin", "mode": "ANALOG", "direction": "INPUT", "sampleMode": "TIMER", "analogReadMode": "PIN_VALUE", "period": 30}, {"componentAPI": "ds18x20", "name": "DS18B20: Temperature Sensor (\u00b0F)", "sensorTypeCount": 2, "sensorType1": "object-temp-fahrenheit", "sensorType2": "object-temp", "pinName": "D4", "sensorResolution": 12, "period": 30}], "checksum": 235} \ No newline at end of file diff --git a/library.properties b/library.properties index 5d6b7ed89..a2b32c19e 100644 --- a/library.properties +++ b/library.properties @@ -7,4 +7,4 @@ paragraph=Arduino application for Adafruit.io WipperSnapper category=Communication url=https://github.com/adafruit/Adafruit_Wippersnapper_Arduino architectures=* -depends=SdFat - Adafruit Fork, Adafruit SPIFlash, Adafruit NeoPixel, ArduinoJson, Adafruit DotStar, Adafruit HDC302x, Adafruit INA219, Adafruit LTR329 and LTR303, Adafruit LTR390 Library, Adafruit MCP3421, Adafruit NAU7802 Library, Adafruit SleepyDog Library, Adafruit TMP117, Adafruit TinyUSB Library, Adafruit AHTX0, Adafruit BME280 Library, Adafruit BMP280 Library, Adafruit BMP3XX Library, Adafruit DPS310, Adafruit DS248x, Adafruit SCD30, Adafruit SGP30 Sensor, Adafruit SGP40 Sensor, Sensirion I2C SCD4x, Sensirion I2C SEN5X, Sensirion I2C SEN66, arduino-sht, Adafruit Si7021 Library, Adafruit MQTT Library, Adafruit MS8607, Adafruit MCP9808 Library, Adafruit MCP9600 Library, Adafruit MPL115A2, Adafruit MPRLS Library, Adafruit TSL2591 Library, Adafruit_VL53L0X, Adafruit VL53L1X, STM32duino VL53L4CD, STM32duino VL53L4CX, Adafruit_VL6180X, Adafruit PM25 AQI Sensor, Adafruit VCNL4020 Library, Adafruit VCNL4040, Adafruit VCNL4200 Library, Adafruit VEML7700 Library, Adafruit LC709203F, Adafruit LPS2X, Adafruit LPS28, Adafruit LPS35HW, Adafruit seesaw Library, Adafruit BME680 Library, Adafruit MAX1704X, Adafruit ADT7410 Library, Adafruit HTS221, Adafruit HTU21DF Library, Adafruit HTU31D Library, Adafruit PCT2075, hp_BH1750, ENS160 - Adafruit Fork, Adafruit BusIO, Adafruit Unified Sensor, Sensirion Core, Adafruit GFX Library, RTClib, StreamUtils, Adafruit SHT4x Library, Adafruit LED Backpack Library, Adafruit LiquidCrystal, Adafruit uBlox +depends=SdFat - Adafruit Fork, Adafruit SPIFlash, Adafruit NeoPixel, ArduinoJson, Adafruit DotStar, Adafruit HDC302x, Adafruit INA219, Adafruit LTR329 and LTR303, Adafruit LTR390 Library, Adafruit MCP3421, Adafruit NAU7802 Library, Adafruit SleepyDog Library, Adafruit TMP117, Adafruit TinyUSB Library, Adafruit AHTX0, Adafruit BME280 Library, Adafruit BMP280 Library, Adafruit BMP3XX Library, Adafruit DPS310, Adafruit DS248x, Adafruit SCD30, Adafruit SGP30 Sensor, Adafruit SGP40 Sensor, Sensirion I2C SCD4x, Sensirion I2C SEN5X, Sensirion I2C SEN66, arduino-sht, Adafruit Si7021 Library, Adafruit MQTT Library, Adafruit MS8607, Adafruit MCP9808 Library, Adafruit MCP9600 Library, Adafruit MPL115A2, Adafruit MPRLS Library, Adafruit TSL2591 Library, Adafruit_VL53L0X, Adafruit VL53L1X, STM32duino VL53L4CD, STM32duino VL53L4CX, Adafruit_VL6180X, Adafruit PM25 AQI Sensor, Adafruit VCNL4020 Library, Adafruit VCNL4040, Adafruit VCNL4200 Library, Adafruit VEML7700 Library, Adafruit LC709203F, Adafruit LPS2X, Adafruit LPS28, Adafruit LPS35HW, Adafruit seesaw Library, Adafruit BME680 Library, Adafruit MAX1704X, Adafruit ADT7410 Library, Adafruit HTS221, Adafruit HTU21DF Library, Adafruit HTU31D Library, Adafruit PCT2075, hp_BH1750, ENS160 - Adafruit Fork, Adafruit BusIO, Adafruit Unified Sensor, Sensirion Core, Adafruit GFX Library, RTClib, StreamUtils, Adafruit SHT4x Library, Adafruit LED Backpack Library, Adafruit LiquidCrystal, Adafruit uBlox, Adafruit AW9523, Adafruit MCP23017 Arduino Library, Adafruit TCA8418, Adafruit ADS1X15, Adafruit PCF8574 diff --git a/platformio.ini b/platformio.ini index 6dfef63c4..560e80ec8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -94,6 +94,13 @@ lib_deps = https://github.com/Sensirion/arduino-i2c-scd4x.git adafruit/Adafruit GPS Library https://github.com/adafruit/Adafruit_uBlox.git + https://github.com/adafruit/Adafruit_PCF8574.git + https://github.com/adafruit/Adafruit_AW9523.git + https://github.com/adafruit/Adafruit-MCP23017-Arduino-Library.git + https://github.com/adafruit/Adafruit_TCA8418.git + https://github.com/adafruit/Adafruit_Seesaw.git + https://github.com/adafruit/Adafruit_ADS1X15.git + https://github.com/adafruit/Adafruit_NAU7802.git ; Common build environment for ESP32 platform [common:esp32] diff --git a/src/components/analogIO/controller.cpp b/src/components/analogIO/controller.cpp deleted file mode 100644 index 194a1889b..000000000 --- a/src/components/analogIO/controller.cpp +++ /dev/null @@ -1,306 +0,0 @@ -/*! - * @file src/components/analogIO/controller.cpp - * - * Controller for the analogio.proto API - * - * Adafruit invests time and resources providing this open source code, - * please support Adafruit and open-source hardware by purchasing - * products from Adafruit! - * - * Copyright (c) Brent Rubell 2024-2025 for Adafruit Industries. - * - * BSD license, all text here must be included in any redistribution. - * - */ -#include "controller.h" - -/*! - @brief AnalogIO controller constructor -*/ -AnalogIOController::AnalogIOController() { - _analogio_hardware = new AnalogIOHardware(); - _analogio_model = new AnalogIOModel(); -} - -/*! - @brief AnalogIO controller destructor -*/ -AnalogIOController::~AnalogIOController() { - delete _analogio_hardware; - delete _analogio_model; -} - -/*! - @brief Set the reference voltage for the analog pins - @param voltage - The reference voltage. -*/ -void AnalogIOController::SetRefVoltage(float voltage) { - // To set the reference voltage, we require a call into the hardware - _analogio_hardware->SetReferenceVoltage(voltage); -} - -/*! - @brief Allocate memory for the total number of analog pins. - @param total_pins - The hardware's total number of analog pins. -*/ -void AnalogIOController::SetTotalAnalogPins(uint8_t total_pins) { - _analogio_pins.reserve(total_pins); -} - -/*! - @brief Routes messages using the analogio.proto API to the - appropriate controller functions. - @param stream - The nanopb input stream. - @return True if the message was successfully routed, False otherwise. -*/ -bool AnalogIOController::Router(pb_istream_t *stream) { - // Attempt to decode the AnalogIO B2D envelope - ws_analogio_B2D b2d = ws_analogio_B2D_init_zero; - if (!ws_pb_decode(stream, ws_analogio_B2D_fields, &b2d)) { - WS_DEBUG_PRINTLN( - "[analogio] ERROR: Unable to decode AnalogIO B2D envelope"); - return false; - } - - // Route based on payload type - bool res = false; - switch (b2d.which_payload) { - case ws_analogio_B2D_add_tag: - res = Handle_AnalogIOAdd(&b2d.payload.add); - break; - case ws_analogio_B2D_remove_tag: - res = Handle_AnalogIORemove(&b2d.payload.remove); - break; - default: - WS_DEBUG_PRINTLN("[analogio] WARNING: Unsupported AnalogIO payload"); - res = false; - break; - } - - return res; -} - -/*! - @brief Handles an AnalogIOAdd message from the broker and adds a - new analog pin to the controller. - @param msg - The AnalogIOAdd message. - @return True if the pin was successfully added, False otherwise. -*/ -bool AnalogIOController::Handle_AnalogIOAdd(ws_analogio_Add *msg) { - WS_DEBUG_PRINTLN("[analogio] Handle_AnalogIOAdd MESSAGE..."); - // Get the pin name - uint8_t pin_name = atoi(msg->pin_name + 1); - - // Create a new analogioPin object - analogioPin new_pin = { - .name = pin_name, - .period = (ulong)(msg->period * 1000.0f), - .prv_period = - 0, // Initialize previous period to 0 to force immediate read - .read_mode = msg->read_mode, - .did_read_send = false}; - - // Initialize the pin and add it to the vector - _analogio_hardware->InitPin(pin_name); - _analogio_pins.push_back(new_pin); - - // Print out the pin's details - WS_DEBUG_PRINTLN("[analogio] Added new pin:"); - WS_DEBUG_PRINT("Pin Name: "); - WS_DEBUG_PRINTLNVAR(new_pin.name); - WS_DEBUG_PRINT("Period: "); - WS_DEBUG_PRINTLNVAR(new_pin.period); - WS_DEBUG_PRINT("Read Mode: "); - WS_DEBUG_PRINTLNVAR(new_pin.read_mode); - - return true; -} - -/*! - @brief Handles an AnalogIORemove message from the broker and removes - the requested analog pin from the controller. - @param msg - The AnalogIORemove message. - @return True if the pin was successfully removed, False otherwise. -*/ -bool AnalogIOController::Handle_AnalogIORemove(ws_analogio_Remove *msg) { - // Get the pin name - uint8_t pin_name = atoi(msg->pin_name + 1); - - bool removed = false; - for (size_t i = 0; i < _analogio_pins.size(); i++) { - if (_analogio_pins[i].name == pin_name) { - _analogio_hardware->DeinitPin(pin_name); - _analogio_pins.erase(_analogio_pins.begin() + i); - removed = true; - break; - } - } - - if (!removed) { - WS_DEBUG_PRINTLN("[analogio] ERROR: Unable to find requested pin!"); - return false; - } - - WS_DEBUG_PRINT("[analogio] Removed pin: "); - WS_DEBUG_PRINTLNVAR(msg->pin_name); - return true; -} - -/*! - @brief Checks if a pin's periodic timer has expired. - @param pin - The requested pin to check. - @param cur_time - The current time (called from millis()). - @return True if the pin's period has expired, False otherwise. -*/ -bool AnalogIOController::IsPinTimerExpired(analogioPin *pin, ulong cur_time) { - return cur_time - pin->prv_period > pin->period; -} - -/*! - @brief Encodes and publishes an AnalogIOEvent message to the broker. - @param pin - The pin to encode and publish. - @param value - The pin's value. - @param read_type - The type of read to perform on the pin. - @return True if the message was successfully encoded and published. -*/ -bool AnalogIOController::EncodePublishPinEvent(uint8_t pin, float value, - ws_sensor_Type read_type) { - char c_pin_name[12]; - sprintf(c_pin_name, "A%d", pin); - - if (read_type == ws_sensor_Type_T_RAW) { - if (!_analogio_model->EncodeAnalogIOEventRaw(c_pin_name, value)) { - WS_DEBUG_PRINTLN("ERROR: Unable to encode AnalogIO raw adc message!"); - return false; - } - } else if (read_type == ws_sensor_Type_T_VOLTAGE) { - if (!_analogio_model->EncodeAnalogIOEventVoltage(c_pin_name, value)) { - WS_DEBUG_PRINTLN("ERROR: Unable to encode AnalogIO voltage message!"); - return false; - } - } else { - WS_DEBUG_PRINTLN("ERROR: Invalid read type for AnalogIOEvent message!"); - return false; - } - - // Publish the AnalogIO message to the broker - WS_DEBUG_PRINT("Publishing AnalogIOEvent..."); - if (!Ws.PublishD2b(ws_signal_DeviceToBroker_analogio_tag, - _analogio_model->GetAnalogIOD2B())) { - WS_DEBUG_PRINTLN("ERROR: Unable to publish analogio voltage event message, " - "moving onto the next pin!"); - return false; - } - WS_DEBUG_PRINTLN("Published!"); - - return true; -} - -/*! - @brief Encodes and publishes an AnalogIO message to the broker or SD - card, depending on the device's mode. - @param pin - The requested pin. - @param value - The pin's value (raw ADC or voltage). - @param sensor_type - The sensor type (ws_sensor_Type_T_RAW or ws_sensor_Type_T_VOLTAGE). - @return True if the message was successfully encoded and published, - otherwise False. -*/ -bool AnalogIOController::EncodePublishPin(uint8_t pin, float value, - ws_sensor_Type sensor_type) { - if (!Ws._sdCardV2->isModeOffline()) { - return EncodePublishPinEvent(pin, value, sensor_type); - } - return Ws._sdCardV2->LogGPIOSensorEventToSD(pin, value, sensor_type); -} - -/*! - @brief Update/polling loop for the AnalogIO controller. - @param force - If true, forces a read on all pins regardless of period. -*/ -void AnalogIOController::update(bool force) { - // Bail-out if the vector is empty - if (_analogio_pins.empty()) - return; - - // Process analog input pins - size_t num_pins = _analogio_pins.size(); - - for (size_t i = 0; i < num_pins; i++) { - // Create a pin object for this iteration - analogioPin &pin = _analogio_pins[i]; - - // (force only) - Was pin previously read and sent? - if (pin.did_read_send && force) - continue; - - // Go to the next pin if the period hasn't expired yet or if we're not - // forcing a read across all pins - ulong cur_time = millis(); - if (!IsPinTimerExpired(&pin, cur_time) && !force) - continue; - - // Pins timer has expired, lets read the pin - if (pin.read_mode == ws_sensor_Type_T_RAW) { - // Read the pin's raw value - uint16_t value = _analogio_hardware->GetPinValue(pin.name); - // Encode and publish it to the broker - if (!EncodePublishPin(pin.name, (float)value, ws_sensor_Type_T_RAW)) { - WS_DEBUG_PRINTLN("[analogio] ERROR: Unable to record pin value!"); - pin.did_read_send = false; - continue; - } - pin.did_read_send = true; - } else if (pin.read_mode == ws_sensor_Type_T_VOLTAGE) { - // Convert the raw value into voltage - float pin_value = _analogio_hardware->GetPinVoltage(pin.name); - // Encode and publish the voltage value to the broker - if (!EncodePublishPin(pin.name, pin_value, ws_sensor_Type_T_VOLTAGE)) { - WS_DEBUG_PRINTLN("[analogio] ERROR: Unable to record pin voltage!"); - pin.did_read_send = false; - continue; - } - pin.did_read_send = true; - } else { - WS_DEBUG_PRINTLN("[analogio] ERROR: Invalid read mode for analog pin!"); - pin.did_read_send = false; - continue; - } - pin.prv_period = cur_time; // Reset the pin's period timer - } -} - -/*! - @brief Checks if all analog pins have been read and their values sent. - @return True if all pins have been read and sent, False otherwise. -*/ -bool AnalogIOController::UpdateComplete() { - for (size_t i = 0; i < _analogio_pins.size(); i++) { - if (!_analogio_pins[i].did_read_send) { - return false; - } - } - return true; -} - -/*! - @brief Resets all analog pins' did_read_send flags to false. -*/ -void AnalogIOController::ResetFlags() { - for (size_t i = 0; i < _analogio_pins.size(); i++) { - _analogio_pins[i].did_read_send = false; - } -} \ No newline at end of file diff --git a/src/components/analogIO/controller.h b/src/components/analogIO/controller.h deleted file mode 100644 index 36c31603b..000000000 --- a/src/components/analogIO/controller.h +++ /dev/null @@ -1,68 +0,0 @@ -/*! - * @file src/components/analogIO/controller.h - * - * Controller for the AnalogIO API - * - * Adafruit invests time and resources providing this open source code, - * please support Adafruit and open-source hardware by purchasing - * products from Adafruit! - * - * Copyright (c) Brent Rubell 2025 for Adafruit Industries. - * - * BSD license, all text here must be included in any redistribution. - * - */ -#ifndef WS_ANALOGIO_CONTROLLER_H -#define WS_ANALOGIO_CONTROLLER_H -#include "hardware.h" -#include "model.h" -#include "wippersnapper.h" - -class wippersnapper; ///< Forward declaration -class AnalogIOModel; ///< Forward declaration -class AnalogIOHardware; ///< Forward declaration - -/** - * @struct analogioPin - * @brief Represents a device's analogio pin. - */ -struct analogioPin { - uint8_t name; ///< The pin's name. - ulong period; ///< The pin's period, in milliseconds. - ulong prv_period; ///< The pin's previous period, in milliseconds. - ws_sensor_Type read_mode; ///< Type of analog read to perform - bool - did_read_send; ///< True if the last read was sent to IO, False otherwise. -}; - -/*! - @brief Routes messages using the analogio.proto API to the - appropriate hardware and model classes, controls and tracks - the state of the hardware's digital I/O pins. -*/ -class AnalogIOController { -public: - AnalogIOController(); - ~AnalogIOController(); - // Routing - bool Router(pb_istream_t *stream); - bool Handle_AnalogIOAdd(ws_analogio_Add *msg); - bool Handle_AnalogIORemove(ws_analogio_Remove *msg); - void update(bool force = false); - bool UpdateComplete(); - void ResetFlags(); - - void SetTotalAnalogPins(uint8_t total_pins); - void SetRefVoltage(float voltage); - -private: - bool IsPinTimerExpired(analogioPin *pin, ulong cur_time); - bool EncodePublishPinEvent(uint8_t pin, float value, - ws_sensor_Type read_type); - bool EncodePublishPin(uint8_t pin, float value, ws_sensor_Type sensor_type); - AnalogIOModel *_analogio_model; ///< AnalogIO model - AnalogIOHardware *_analogio_hardware; ///< AnalogIO hardware - std::vector _analogio_pins; ///< Vector of analogio pins -}; -extern wippersnapper Ws; ///< Wippersnapper V2 instance -#endif // WS_ANALOGIO_CONTROLLER_H \ No newline at end of file diff --git a/src/components/analogIO/hardware.cpp b/src/components/analogIO/hardware.cpp deleted file mode 100644 index 69b4273bb..000000000 --- a/src/components/analogIO/hardware.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/*! - * @file src/components/analogIO/hardware.cpp - * - * Hardware interface for the analogio.proto API - * - * Adafruit invests time and resources providing this open source code, - * please support Adafruit and open-source hardware by purchasing - * products from Adafruit! - * - * Copyright (c) Brent Rubell 2024 for Adafruit Industries. - * - * BSD license, all text here must be included in any redistribution. - * - */ -#include "hardware.h" - -/*! - @brief AnalogIO hardware constructor -*/ -AnalogIOHardware::AnalogIOHardware() { - SetNativeADCResolution(); // Configure the device's native ADC resolution - SetResolution( - DEFAULT_ADC_RESOLUTION); // Wippersnapper's default resolution is 16-bit - SetReferenceVoltage( - DEFAULT_REF_VOLTAGE); // Wippersnapper's default ref voltage is 3.3V -} - -/*! - @brief AnalogIO hardware destructor -*/ -AnalogIOHardware::~AnalogIOHardware() {} - -/*! - @brief Initializes an analog input pin. - @param pin - The requested pin. -*/ -void AnalogIOHardware::InitPin(uint8_t pin) { pinMode(pin, INPUT); } - -/*! - @brief Deinitializes an analog input pin and frees it for - other uses. - @param pin - The requested pin. -*/ -void AnalogIOHardware::DeinitPin(uint8_t pin) { - pinMode(pin, INPUT); // set to a hi-z floating pin -} - -/*! - @brief Sets the hardware's reference voltage for reading analog pins - @param voltage - The reference voltage. -*/ -void AnalogIOHardware::SetReferenceVoltage(float voltage) { - _ref_voltage = voltage; -} - -/*! - @brief Configures the hardware's native ADC resolution. -*/ -void AnalogIOHardware::SetNativeADCResolution() { -#if defined(ARDUINO_ARCH_SAMD) - _native_adc_resolution = 12; -#elif defined(ARDUINO_ARCH_ESP32) -#if defined(CONFIG_IDF_TARGET_ESP32S3) - // In arduino-esp32, the ESP32-S3 ADC uses a 13-bit resolution - _native_adc_resolution = 13; -#else - // The ESP32, ESP32-S2, ESP32-C3 ADCs uses 12-bit resolution - _native_adc_resolution = 12; -#endif -#elif defined(ARDUINO_ARCH_RP2040) - _native_adc_resolution = 10; -#else - _native_adc_resolution = 10; -#endif -#ifndef ARDUINO_ARCH_ESP8266 - // Set the resolution (in bits) of the hardware's ADC - analogReadResolution(_native_adc_resolution); -#endif // ARDUINO_ARCH_ESP8266 -} - -/*! - @brief Sets the hardware ADC's resolution. - @param resolution - The requested resolution, in bits. -*/ -void AnalogIOHardware::SetResolution(uint8_t resolution) { - _desired_adc_resolution = resolution; - // Calculate (or re-calculate) the scale factor whenever we set the desired - // read resolution - CalculateScaleFactor(); -} - -/*! - @brief Calculates a factor used by the hardware for scaling - the ADC's resolution. -*/ -void AnalogIOHardware::CalculateScaleFactor() { - _max_scale_resolution_desired = - pow(2, 16); // TODO: Change to _desired_adc_resolution - _max_scale_resolution_native = - pow(2, 13); // TODO: Change to _native_adc_resolution -} - -/*! - @brief Gets the "raw" value of an analog pin from the ADC. - @param pin - The requested pin. - @return The pin's value. -*/ -uint16_t AnalogIOHardware::GetPinValue(uint8_t pin) { - return (analogRead(pin) * _max_scale_resolution_desired) / - _max_scale_resolution_native; -} - -/*! - @brief Gets the voltage of an analog pin. - @param pin - The requested pin. - @return The pin's voltage. -*/ -float AnalogIOHardware::GetPinVoltage(uint8_t pin) { -#ifdef ARDUINO_ARCH_ESP32 - return analogReadMilliVolts(pin) / 1000.0; -#else - return (GetPinValue(pin) * _ref_voltage) / 65536; -#endif // ARDUINO_ARCH_ESP32 -} \ No newline at end of file diff --git a/src/components/analogIO/hardware.h b/src/components/analogIO/hardware.h deleted file mode 100644 index 4e90c2266..000000000 --- a/src/components/analogIO/hardware.h +++ /dev/null @@ -1,49 +0,0 @@ -/*! - * @file src/components/analogIO/hardware.h - * - * Hardware implementation for the analogio.proto message. - * - * Adafruit invests time and resources providing this open source code, - * please support Adafruit and open-source hardware by purchasing - * products from Adafruit! - * - * Copyright (c) Brent Rubell 2024 for Adafruit Industries. - * - * BSD license, all text here must be included in any redistribution. - * - */ -#ifndef WS_ANALOGIO_HARDWARE_H -#define WS_ANALOGIO_HARDWARE_H -#include "wippersnapper.h" - -#define DEFAULT_ADC_RESOLUTION 16 ///< Default ADC resolution, in bits -#define DEFAULT_REF_VOLTAGE 3.3 ///< Default reference voltage, in volts - -/*! - @brief Interface for interacting with hardware's analog i/o pin API. -*/ -class AnalogIOHardware { -public: - AnalogIOHardware(); - ~AnalogIOHardware(); - void SetNativeADCResolution(); - void SetResolution(uint8_t resolution); - void SetReferenceVoltage(float voltage); - void CalculateScaleFactor(); - // Arduino/Wiring API - void InitPin(uint8_t pin); - void DeinitPin(uint8_t pin); - uint16_t GetPinValue(uint8_t pin); - float GetPinVoltage(uint8_t pin); - -private: - uint8_t _native_adc_resolution; ///< Hardware's native ADC resolution. - uint8_t _desired_adc_resolution; ///< Desired (final) ADC resolution. - int _max_scale_resolution_desired; ///< Maximum scale resolution desired. - int _max_scale_resolution_native; ///< Maximum scale resolution native. - - bool _is_adc_resolution_scaled; ///< True if the ADC's resolution is scaled, - ///< False otherwise. - float _ref_voltage; ///< Reference voltage for reading analog pins. -}; -#endif // WS_ANALOGIO_HARDWARE_H \ No newline at end of file diff --git a/src/components/analogIO/model.cpp b/src/components/analogIO/model.cpp deleted file mode 100644 index f9d2f7ce5..000000000 --- a/src/components/analogIO/model.cpp +++ /dev/null @@ -1,157 +0,0 @@ -/*! - * @file src/components/analogIO/model.cpp - * - * Interfaces for the analogio.proto API - * - * Adafruit invests time and resources providing this open source code, - * please support Adafruit and open-source hardware by purchasing - * products from Adafruit! - * - * Copyright (c) Brent Rubell 2024 for Adafruit Industries. - * - * BSD license, all text here must be included in any redistribution. - * - */ -#include "model.h" - -/*! - @brief AnalogIOModel constructor -*/ -AnalogIOModel::AnalogIOModel() { - memset(&_msg_AnalogioAdd, 0, sizeof(_msg_AnalogioAdd)); - memset(&_msg_AnalogioRemove, 0, sizeof(_msg_AnalogioRemove)); - memset(&_msg_AnalogioEvent, 0, sizeof(_msg_AnalogioEvent)); - // no-op -} - -/*! - @brief AnalogIOModel destructor -*/ -AnalogIOModel::~AnalogIOModel() { - memset(&_msg_AnalogioAdd, 0, sizeof(_msg_AnalogioAdd)); - memset(&_msg_AnalogioRemove, 0, sizeof(_msg_AnalogioRemove)); - memset(&_msg_AnalogioEvent, 0, sizeof(_msg_AnalogioEvent)); -} - -/*! - @brief Decodes an AnalogIOAdd message from a stream into an - AnalogIOAdd message struct. - @param stream - The pb_istream_t stream to decode. - @return True if successful, False otherwise. -*/ -bool AnalogIOModel::DecodeAnalogIOAdd(pb_istream_t *stream) { - // Zero-out the AnalogIOAdd message struct. to ensure we don't have any old - // data - memset(&_msg_AnalogioAdd, 0, sizeof(_msg_AnalogioAdd)); - // Decode the stream into a AnalogIOAdd message - return pb_decode(stream, ws_analogio_Add_fields, &_msg_AnalogioAdd); -} - -/*! - @brief Gets an AnalogIOAdd message struct. - @return Pointer to an AnalogIOAdd message struct. -*/ -ws_analogio_Add *AnalogIOModel::GetAnalogIOAddMsg() { - return &_msg_AnalogioAdd; -} - -/*! - @brief Decodes an AnalogIORemove message from a stream into an - AnalogIORemove message struct. - @param stream - The pb_istream_t stream to decode. - @return True if successful, False otherwise. -*/ -bool AnalogIOModel::DecodeAnalogIORemove(pb_istream_t *stream) { - // Zero-out the AnalogIORemove message struct. to ensure we don't have any old - // data - memset(&_msg_AnalogioRemove, 0, sizeof(_msg_AnalogioRemove)); - // Decode the stream into a AnalogIORemove message - return pb_decode(stream, ws_analogio_Remove_fields, &_msg_AnalogioRemove); -} - -/*! - @brief Gets an AnalogIORemove message struct. - @return Pointer to an AnalogIORemove message struct. -*/ -ws_analogio_Remove *AnalogIOModel::GetAnalogIORemoveMsg() { - return &_msg_AnalogioRemove; -} - -/*! - @brief Gets an AnalogIOEvent message struct. - @return Pointer to an AnalogIOEvent message struct. -*/ -ws_analogio_Event *AnalogIOModel::GetAnalogIOEvent() { - return &_msg_AnalogioEvent; -} - -/*! - @brief Gets an AnalogIO DeviceToBroker message struct. - @return Pointer to an AnalogIO D2B message struct. -*/ -ws_analogio_D2B *AnalogIOModel::GetAnalogIOD2B() { return &_msg_AnalogioD2B; } - -/*! - @brief Encodes an AnalogIOEvent message. - @param pin_name - The requested pin's name. - @param pin_value - The value of the pin. - @param read_type - The type of sensor event to encode. - @return True if successful, False otherwise. -*/ -bool AnalogIOModel::EncodeAnalogIOEvent(char *pin_name, float pin_value, - ws_sensor_Type read_type) { - // Initialize the AnalogIOEvent message to default values - memset(&_msg_AnalogioEvent, 0, sizeof(_msg_AnalogioEvent)); - // Fill the AnalogIOEvent message's fields - strncpy(_msg_AnalogioEvent.pin_name, pin_name, - sizeof(_msg_AnalogioEvent.pin_name)); - _msg_AnalogioEvent.has_value = true; - _msg_AnalogioEvent.value.type = read_type; - _msg_AnalogioEvent.value.which_value = ws_sensor_Event_float_value_tag; - _msg_AnalogioEvent.value.value.float_value = pin_value; - - // Wrap the event in the D2B envelope - memset(&_msg_AnalogioD2B, 0, sizeof(_msg_AnalogioD2B)); - _msg_AnalogioD2B.which_payload = ws_analogio_D2B_event_tag; - _msg_AnalogioD2B.payload.event = _msg_AnalogioEvent; - - return true; -} - -/*! - @brief Encodes an AnalogIOEvent message with a raw pin value. - @param pin_name - The requested pin's name. - @param pin_value - The value of the pin. - @return True if successful, False otherwise. -*/ -bool AnalogIOModel::EncodeAnalogIOEventRaw(char *pin_name, float pin_value) { - WS_DEBUG_PRINT("[analogio] Pin: "); - WS_DEBUG_PRINTVAR(pin_name); - WS_DEBUG_PRINT(" | Raw Value: "); - WS_DEBUG_PRINTLNVAR(pin_value); - return EncodeAnalogIOEvent(pin_name, pin_value, ws_sensor_Type_T_RAW); -} - -/*! - @brief Encodes an AnalogIOEvent message with a voltage pin value. - @param pin_name - The requested pin's name. - @param pin_value - The value of the pin. - @return True if successful, False otherwise. -*/ -bool AnalogIOModel::EncodeAnalogIOEventVoltage(char *pin_name, - float pin_value) { - WS_DEBUG_PRINT("[analogio] Pin: "); - WS_DEBUG_PRINTVAR(pin_name); - WS_DEBUG_PRINT(" | Voltage: "); - WS_DEBUG_PRINTLNVAR(pin_value); - return EncodeAnalogIOEvent(pin_name, pin_value, ws_sensor_Type_T_VOLTAGE); -} \ No newline at end of file diff --git a/src/components/analogIO/model.h b/src/components/analogIO/model.h deleted file mode 100644 index db872e6a4..000000000 --- a/src/components/analogIO/model.h +++ /dev/null @@ -1,47 +0,0 @@ -/*! - * @file src/components/analogIO/model.h - * - * Model interface for the analogio.proto message. - * - * Adafruit invests time and resources providing this open source code, - * please support Adafruit and open-source hardware by purchasing - * products from Adafruit! - * - * Copyright (c) Brent Rubell 2024 for Adafruit Industries. - * - * BSD license, all text here must be included in any redistribution. - * - */ -#ifndef WS_ANALOGIO_MODEL_H -#define WS_ANALOGIO_MODEL_H -#include "wippersnapper.h" - -/*! - @brief Provides an interface for creating, encoding, and parsing - messages from analogio.proto. -*/ -class AnalogIOModel { -public: - AnalogIOModel(); - ~AnalogIOModel(); - // AnalogIOAdd - bool DecodeAnalogIOAdd(pb_istream_t *stream); - ws_analogio_Add *GetAnalogIOAddMsg(); - // AnalogIORemove - bool DecodeAnalogIORemove(pb_istream_t *stream); - ws_analogio_Remove *GetAnalogIORemoveMsg(); - // AnalogIOEvent - bool EncodeAnalogIOEvent(char *pin_name, float pin_value, - ws_sensor_Type read_type); - bool EncodeAnalogIOEventVoltage(char *pin_name, float pin_value); - bool EncodeAnalogIOEventRaw(char *pin_name, float pin_value); - ws_analogio_Event *GetAnalogIOEvent(); - ws_analogio_D2B *GetAnalogIOD2B(); - -private: - ws_analogio_Add _msg_AnalogioAdd; ///< AnalogIOAdd message - ws_analogio_Remove _msg_AnalogioRemove; ///< AnalogIORemove message - ws_analogio_Event _msg_AnalogioEvent; ///< AnalogIOEvent message - ws_analogio_D2B _msg_AnalogioD2B; ///< AnalogIO DeviceToBroker wrapper -}; -#endif // WS_DIGITALIO_MODEL_H \ No newline at end of file diff --git a/src/components/analogIn/controller.cpp b/src/components/analogIn/controller.cpp new file mode 100644 index 000000000..6d608fa07 --- /dev/null +++ b/src/components/analogIn/controller.cpp @@ -0,0 +1,333 @@ +/*! + * @file src/components/analogIn/controller.cpp + * + * Controller for the analogin.proto API + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024-2026 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#include "controller.h" +#include "../expander/controller.h" +#include "hardware.h" + +/*! + @brief AnalogIn controller constructor +*/ +AnalogInController::AnalogInController() { + _analogin_model = new AnalogInModel(); + _mcu_vref = DEFAULT_MCU_VREF; +} + +/*! + @brief AnalogIn controller destructor +*/ +AnalogInController::~AnalogInController() { + for (size_t i = 0; i < _pins.size(); i++) + delete _pins[i]; + delete _analogin_model; +} + +/*! + @brief Set the reference voltage for the analog pins + @param voltage + The reference voltage. +*/ +void AnalogInController::SetRefVoltage(float voltage) { _mcu_vref = voltage; } + +/*! + @brief Allocate memory for the total number of analog pins. + @param max_analog_pins + The hardware's maximum number of analog pins. +*/ +void AnalogInController::SetMaxAnalogPins(uint8_t max_analog_pins) { + _pins.reserve(max_analog_pins); +} + +/*! + @brief Routes messages using the analogin.proto API to the + appropriate controller functions. + @param stream + The nanopb input stream. + @return True if the message was successfully routed, False otherwise. +*/ +bool AnalogInController::Router(pb_istream_t *stream) { + // Attempt to decode the AnalogIn B2D envelope + ws_analogin_B2D b2d = ws_analogin_B2D_init_zero; + if (!ws_pb_decode(stream, ws_analogin_B2D_fields, &b2d)) { + WS_DEBUG_PRINTLN( + "[analogin] ERROR: Unable to decode AnalogIn B2D envelope"); + return false; + } + + // Route based on payload type + bool res = false; + switch (b2d.which_payload) { + case ws_analogin_B2D_add_tag: + res = Handle_AnalogInAdd(&b2d.payload.add); + break; + case ws_analogin_B2D_remove_tag: + res = Handle_AnalogInRemove(&b2d.payload.remove); + break; + default: + WS_DEBUG_PRINTLN("[analogin] WARNING: Unsupported AnalogIn payload"); + res = false; + break; + } + + return res; +} + +/*! + @brief Removes a pin from the vector by pin number. + Deletes the pin object (destructor deinits hardware). + @param pin_num + The pin number to remove. + @return True if the pin was found and removed. +*/ +bool AnalogInController::RemovePin(uint8_t pin_num, + ExpanderHardware *expander) { + for (size_t i = 0; i < _pins.size(); i++) { + if (_pins[i]->GetPinNum() == pin_num && + _pins[i]->GetExpanderDriver() == expander) { + delete _pins[i]; + _pins.erase(_pins.begin() + i); + return true; + } + } + return false; +} + +/*! + @brief Get a pointer to an analog pin by pin number. + @param pin_num + The pin's number. + @return Pointer to the analog pin, or nullptr if not found. +*/ +AnalogInHardware *AnalogInController::GetPin(uint8_t pin_num, + ExpanderHardware *expander) { + for (size_t i = 0; i < _pins.size(); i++) { + if (_pins[i]->GetPinNum() == pin_num && + _pins[i]->GetExpanderDriver() == expander) + return _pins[i]; + } + return nullptr; +} + +/*! + @brief Handles an AnalogInAdd message from the broker and adds a + new analog pin to the controller. + @param msg + The AnalogInAdd message. + @return True if the pin was successfully added, False otherwise. +*/ +bool AnalogInController::Handle_AnalogInAdd(ws_analogin_Add *msg) { + WS_DEBUG_PRINTLN("[analogin] Handle_AnalogInAdd MESSAGE..."); + uint8_t pin_num = 0; + if (!ExpanderHardware::ParsePinNum(msg->pin_name, pin_num)) { + WS_DEBUG_PRINTLN("[analogin] ERROR: Malformed expander pin name!"); + return false; + } + + // Validate the read mode + if (msg->read_mode != ws_sensor_Type_T_RAW && + msg->read_mode != ws_sensor_Type_T_VOLTAGE) { + WS_DEBUG_PRINTLN("[analogin] ERROR: Invalid read mode in message!"); + return false; + } + // Validate the sample mode + if (msg->sample_mode != ws_analogin_SampleMode_SM_TIMER && + msg->sample_mode != ws_analogin_SampleMode_SM_EVENT) { + WS_DEBUG_PRINTLN("[analogin] ERROR: Invalid sample mode in message!"); + return false; + } + + // Resolve the expander driver if this is an expander pin + ExpanderHardware *expander_drv = nullptr; + if (strncmp(msg->pin_name, "EXP_", 4) == 0) { + uint8_t i2c_addr = (uint8_t)strtoul(msg->pin_name + 4, nullptr, 16); + expander_drv = Ws._expander_controller->GetDriver(i2c_addr); + if (!expander_drv) { + WS_DEBUG_PRINTLN("[analogin] ERROR: Expander not found for address!"); + return false; + } + } + + // If pin is being updated, remove the existing pin first + RemovePin(pin_num, expander_drv); + + // Create a new analog input pin + AnalogInHardware *new_pin = new AnalogInHardware( + pin_num, msg->read_mode, msg->sample_mode, (ulong)(msg->period * 1000.0f), + _mcu_vref, expander_drv, msg->gain); + + // Add the pin to the controller's list + _pins.push_back(new_pin); + + // Print out the pin's details + WS_DEBUG_PRINTLN("[analogin] Added new pin:"); + WS_DEBUG_PRINT("Pin Name: "); + WS_DEBUG_PRINTLNVAR(new_pin->GetPinNum()); + WS_DEBUG_PRINT("Period: "); + WS_DEBUG_PRINTLNVAR(msg->period * 1000.0f); + WS_DEBUG_PRINT("Read Mode: "); + ws_sensor_Type pin_read_mode = new_pin->GetReadMode(); + WS_DEBUG_PRINTLNVAR(pin_read_mode); + + return true; +} + +/*! + @brief Handles an AnalogInRemove message from the broker and removes + the requested analog pin from the controller. + @param msg + The AnalogInRemove message. + @return True if the pin was successfully removed, False otherwise. +*/ +bool AnalogInController::Handle_AnalogInRemove(ws_analogin_Remove *msg) { + uint8_t pin_num = 0; + if (!ExpanderHardware::ParsePinNum(msg->pin_name, pin_num)) { + WS_DEBUG_PRINTLN("[analogin] ERROR: Malformed expander pin name!"); + return false; + } + + // Resolve the expander driver if this is an expander pin + ExpanderHardware *expander_drv = nullptr; + if (strncmp(msg->pin_name, "EXP_", 4) == 0) { + uint8_t i2c_addr = (uint8_t)strtoul(msg->pin_name + 4, nullptr, 16); + expander_drv = Ws._expander_controller->GetDriver(i2c_addr); + if (!expander_drv) { + WS_DEBUG_PRINTLN("[analogin] ERROR: Expander not found for address!"); + return false; + } + } + + if (!RemovePin(pin_num, expander_drv)) { + WS_DEBUG_PRINTLN("[analogin] ERROR: Unable to find requested pin!"); + return false; + } + + WS_DEBUG_PRINT("[analogin] Removed pin: "); + WS_DEBUG_PRINTLNVAR(msg->pin_name); + return true; +} + +/*! + @brief Encodes and publishes an AnalogInEvent message to the broker + or logs to SD card if offline. + @param pin + Pointer to the analog pin hardware object. + @return True if the message was successfully recorded. +*/ +bool AnalogInController::EncodePublishPinEvent(AnalogInHardware *pin) { + uint8_t pin_num = pin->GetPinNum(); + float value = pin->GetValue(); + ws_sensor_Type read_type = pin->GetReadMode(); + + if (Ws._sdCardV2->isModeOffline()) { + return Ws._sdCardV2->LogGPIOSensorEventToSD(pin_num, value, read_type); + } + + // Format pin name: expander pins use "EXP_0xNN_P", native pins use "AN" + char c_pin_name[20]; + ExpanderHardware *expander = pin->GetExpanderDriver(); + if (expander != nullptr) { + ExpanderHardware::FormatPinName(c_pin_name, sizeof(c_pin_name), + expander->getAddress(), pin_num); + } else { + snprintf(c_pin_name, sizeof(c_pin_name), "A%d", pin_num); + } + + if (read_type == ws_sensor_Type_T_RAW) { + if (!_analogin_model->EncodeAnalogInEventRaw(c_pin_name, value)) { + WS_DEBUG_PRINTLN("ERROR: Unable to encode AnalogIn raw adc message!"); + return false; + } + } else if (read_type == ws_sensor_Type_T_VOLTAGE) { + if (!_analogin_model->EncodeAnalogInEventVoltage(c_pin_name, value)) { + WS_DEBUG_PRINTLN("ERROR: Unable to encode AnalogIn voltage message!"); + return false; + } + } else { + WS_DEBUG_PRINTLN("ERROR: Invalid read type for AnalogInEvent message!"); + return false; + } + + // Publish the AnalogIn message to the broker + WS_DEBUG_PRINT("Publishing AnalogInEvent..."); + if (!Ws.PublishD2b(ws_signal_DeviceToBroker_analogin_tag, + _analogin_model->GetAnalogInD2B())) { + WS_DEBUG_PRINTLN("ERROR: Unable to publish analogin voltage event message, " + "moving onto the next pin!"); + return false; + } + WS_DEBUG_PRINTLN("Published!"); + + return true; +} + +/*! + @brief Update/polling loop for the AnalogIn controller. + @param force + If true, forces a read on all pins regardless of period. +*/ +void AnalogInController::update(bool force) { + // Bail-out if the vector is empty + if (_pins.empty()) + return; + + for (size_t i = 0; i < _pins.size(); i++) { + AnalogInHardware *pin = _pins[i]; + + // Is the pin ready for a new reading? + if (!force) { + bool ready; + if (pin->GetSampleMode() == ws_analogin_SampleMode_SM_EVENT) { + ready = pin->CheckEvent(); + } else { + ready = pin->CheckTimer(); + } + if (!ready) + continue; + } else { + // Sleep wake - force a new reading + if (pin->DidReadSend()) + continue; + pin->ReadValue(); + } + + if (!EncodePublishPinEvent(pin)) { + WS_DEBUG_PRINTLN("[analogin] ERROR: Unable to record pin value!"); + pin->ResetSendFlag(); + continue; + } + pin->MarkSent(); + } +} + +/*! + @brief Checks if all analog pins have been read and their values sent. + @return True if all pins have been read and sent, False otherwise. +*/ +bool AnalogInController::UpdateComplete() { + for (size_t i = 0; i < _pins.size(); i++) { + if (!_pins[i]->DidReadSend()) { + return false; + } + } + return true; +} + +/*! + @brief Resets all analog pins' did_read_send flags to false. +*/ +void AnalogInController::ResetFlags() { + for (size_t i = 0; i < _pins.size(); i++) { + _pins[i]->ResetSendFlag(); + } +} diff --git a/src/components/analogIn/controller.h b/src/components/analogIn/controller.h new file mode 100644 index 000000000..93028ea39 --- /dev/null +++ b/src/components/analogIn/controller.h @@ -0,0 +1,57 @@ +/*! + * @file src/components/analogIn/controller.h + * + * Controller for the AnalogIn API + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2025-2026 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_ANALOGIN_CONTROLLER_H +#define WS_ANALOGIN_CONTROLLER_H +#include "model.h" +#include "wippersnapper.h" + +class wippersnapper; ///< Forward declaration +class AnalogInModel; ///< Forward declaration +class AnalogInHardware; ///< Forward declaration +class ExpanderHardware; ///< Forward declaration + +/*! + @brief Routes messages using the analogin.proto API to the + appropriate hardware and model classes, controls and tracks + the state of the hardware's analog input pins. +*/ +class AnalogInController { +public: + AnalogInController(); + ~AnalogInController(); + // Routing + bool Router(pb_istream_t *stream); + bool Handle_AnalogInAdd(ws_analogin_Add *msg); + bool Handle_AnalogInRemove(ws_analogin_Remove *msg); + void update(bool force = false); + + // Called by CheckinModel + void SetMaxAnalogPins(uint8_t max_analog_pins); + void SetRefVoltage(float voltage); + + // Sleep Mode + bool UpdateComplete(); + void ResetFlags(); + +private: + bool EncodePublishPinEvent(AnalogInHardware *pin); + bool RemovePin(uint8_t pin_num, ExpanderHardware *expander); + AnalogInHardware *GetPin(uint8_t pin_num, ExpanderHardware *expander); + std::vector _pins; ///< Vector of analog pin objects + AnalogInModel *_analogin_model; ///< AnalogIn model + float _mcu_vref; ///< Reference voltage passed to each new pin +}; +extern wippersnapper Ws; ///< Wippersnapper V2 instance +#endif // WS_ANALOGIN_CONTROLLER_H diff --git a/src/components/analogIn/hardware.cpp b/src/components/analogIn/hardware.cpp new file mode 100644 index 000000000..0af39698a --- /dev/null +++ b/src/components/analogIn/hardware.cpp @@ -0,0 +1,250 @@ +/*! + * @file src/components/analogIn/hardware.cpp + * + * Hardware driver for the analogin.proto API + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024-2026 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#include "hardware.h" + +/*! + @brief AnalogInHardware constructor + @param pin_name The pin number. + @param read_mode The type of analog read (RAW or VOLTAGE). + @param sample_mode The pin's sample mode (TIMER or EVENT). + @param period The pin's period in milliseconds. + @param ref_voltage The reference voltage for analog reads. + @param expander_drv Pointer to expander driver, or nullptr. + @param gain An optional gain setting for the ADC. +*/ +AnalogInHardware::AnalogInHardware(uint8_t pin_name, ws_sensor_Type read_mode, + ws_analogin_SampleMode sample_mode, + ulong period, float ref_voltage, + ExpanderHardware *expander_drv, + ws_analogin_Gain gain) + : _name(pin_name), _read_mode(read_mode), _sample_mode(sample_mode), + _period(period), _prv_time(0), _did_read_send(false), _value_raw(0), + _value_voltage(0.0f), _prv_value_raw(0), _native_adc_resolution(0), + _desired_adc_resolution(0), _max_scale_resolution_desired(0), + _max_scale_resolution_native(0), _mcu_vref(ref_voltage), + _expander_drv(expander_drv), _gain(gain) { + if (_expander_drv != nullptr) { + _native_adc_resolution = _expander_drv->getAdcResolution(); + } else { + SetNativeADCResolution(); + } + SetResolution(DEFAULT_ADC_RESOLUTION); + InitPin(); +} + +/*! + @brief AnalogInHardware destructor. Resets pin to floating INPUT state. +*/ +AnalogInHardware::~AnalogInHardware() { DeinitPin(); } + +/*! + @brief Initializes an analog input pin. +*/ +void AnalogInHardware::InitPin() { + if (_expander_drv != nullptr) { + _expander_drv->pinMode(_name, INPUT); + if (_gain != ws_analogin_Gain_G_UNSPECIFIED) { + _expander_drv->setGain(_gain); + } + } else { + pinMode(_name, INPUT); + } +} + +/*! + @brief Deinitializes an analog input pin and frees it for + other uses. +*/ +void AnalogInHardware::DeinitPin() { + if (_expander_drv != nullptr) { + _expander_drv->pinMode(_name, INPUT); + } else { + pinMode(_name, INPUT); // set to a hi-z floating pin + } +} + +/*! + @brief Configures the hardware's native ADC resolution. +*/ +void AnalogInHardware::SetNativeADCResolution() { +#if defined(ARDUINO_ARCH_SAMD) + _native_adc_resolution = 12; +#elif defined(ARDUINO_ARCH_ESP32) +#if defined(CONFIG_IDF_TARGET_ESP32S3) + // In arduino-esp32, the ESP32-S3 ADC uses a 13-bit resolution + _native_adc_resolution = 13; +#else + // The ESP32, ESP32-S2, ESP32-C3 ADCs uses 12-bit resolution + _native_adc_resolution = 12; +#endif +#elif defined(ARDUINO_ARCH_RP2040) + _native_adc_resolution = 10; +#else + _native_adc_resolution = 10; +#endif +#ifndef ARDUINO_ARCH_ESP8266 + // Set the resolution (in bits) of the hardware's ADC + analogReadResolution(_native_adc_resolution); +#endif // ARDUINO_ARCH_ESP8266 +} + +/*! + @brief Sets the hardware ADC's resolution. + @param resolution + The requested resolution, in bits. +*/ +void AnalogInHardware::SetResolution(uint8_t resolution) { + _desired_adc_resolution = resolution; + // Calculate (or re-calculate) the scale factor whenever we set the desired + // read resolution + CalculateScaleFactor(); +} + +/*! + @brief Calculates a factor used by the hardware for scaling + the ADC's resolution. +*/ +void AnalogInHardware::CalculateScaleFactor() { + _max_scale_resolution_desired = pow(2, _desired_adc_resolution); + _max_scale_resolution_native = pow(2, _native_adc_resolution); +} + +/*! + @brief Reads the raw ADC value from the pin, scaled to the + desired resolution. + @return The raw ADC value. +*/ +uint16_t AnalogInHardware::ReadRawValue() { + if (_expander_drv != nullptr) { + _value_raw = + (_expander_drv->analogRead(_name) * _max_scale_resolution_desired) / + _max_scale_resolution_native; + } else { + _value_raw = (analogRead(_name) * _max_scale_resolution_desired) / + _max_scale_resolution_native; + } + return _value_raw; +} + +/*! + @brief Reads the voltage from the analog pin. + @return The pin's voltage. +*/ +float AnalogInHardware::ReadVoltage() { + if (_expander_drv != nullptr) { + _value_voltage = + (ReadRawValue() * _mcu_vref) / _max_scale_resolution_desired; + return _value_voltage; + } +#ifdef ARDUINO_ARCH_ESP32 + _value_voltage = analogReadMilliVolts(_name) / 1000.0; +#else + _value_voltage = (ReadRawValue() * _mcu_vref) / MAX_DESIRED_SCALE_RESOLUTION; +#endif // ARDUINO_ARCH_ESP32 + return _value_voltage; +} + +/*! + @brief Checks if the pin's raw value has changed since last check. + @return True if the value changed. +*/ +bool AnalogInHardware::CheckEvent() { + ReadValue(); + + if (_value_raw == _prv_value_raw) + return false; + _prv_value_raw = _value_raw; + return true; +} + +/*! + @brief Reads the pin according to its read_mode. + @return The pin's value as a float (raw cast or voltage). +*/ +float AnalogInHardware::ReadValue() { + if (_read_mode == ws_sensor_Type_T_RAW) { + return (float)ReadRawValue(); + } + return ReadVoltage(); +} + +/*! + @brief Checks if the pin's timer has expired and reads the value. + @return True if the timer expired. +*/ +bool AnalogInHardware::CheckTimer() { + ulong cur_time = millis(); + if (cur_time - _prv_time <= _period) + return false; + // Timer expired, read the pin + ReadValue(); + _prv_time = cur_time; + return true; +} + +/*! + @brief Gets the pin's number. + @return The pin's number. +*/ +uint8_t AnalogInHardware::GetPinNum() const { return _name; } + +/*! + @brief Gets the pin's read mode. + @return The pin's read mode (RAW or VOLTAGE). +*/ +ws_sensor_Type AnalogInHardware::GetReadMode() const { return _read_mode; } + +/*! + @brief Gets the pin's sample mode. + @return The pin's sample mode (TIMER or EVENT). +*/ +ws_analogin_SampleMode AnalogInHardware::GetSampleMode() const { + return _sample_mode; +} + +/*! + @brief Gets the last read value according to read_mode. + @return The last value as a float (raw cast or voltage). +*/ +float AnalogInHardware::GetValue() const { + if (_read_mode == ws_sensor_Type_T_RAW) { + return (float)_value_raw; + } + return _value_voltage; +} + +/*! + @brief Gets the expander driver, or nullptr for native pins. + @return Pointer to the expander driver, or nullptr. +*/ +ExpanderHardware *AnalogInHardware::GetExpanderDriver() const { + return _expander_drv; +} + +/*! + @brief Gets whether the last read was sent to IO. + @return True if the last read was sent successfully. +*/ +bool AnalogInHardware::DidReadSend() const { return _did_read_send; } + +/*! + @brief Marks that the current pin value has been sent to IO. +*/ +void AnalogInHardware::MarkSent() { _did_read_send = true; } + +/*! + @brief Resets the pin's did_read_send flag to false. +*/ +void AnalogInHardware::ResetSendFlag() { _did_read_send = false; } diff --git a/src/components/analogIn/hardware.h b/src/components/analogIn/hardware.h new file mode 100644 index 000000000..10b873e75 --- /dev/null +++ b/src/components/analogIn/hardware.h @@ -0,0 +1,78 @@ +/*! + * @file src/components/analogIn/hardware.h + * + * Hardware implementation for the analogin.proto message. + * Each instance represents a single analog input pin and + * carries its own ADC configuration as instance members. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024-2026 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_ANALOGIN_HARDWARE_H +#define WS_ANALOGIN_HARDWARE_H +#include "wippersnapper.h" + +#define DEFAULT_ADC_RESOLUTION 16 ///< Default ADC resolution, in bits +#define DEFAULT_MCU_VREF 3.3 ///< Default reference voltage, in volts +#define MAX_DESIRED_SCALE_RESOLUTION \ + 65536 ///< Maximum desired scale resolution, in bits + +class ExpanderHardware; + +/*! + @brief Represents a single analog input pin and provides + hardware-level operations for reading and polling + its state. Each instance carries its own ADC + configuration. +*/ +class AnalogInHardware { +public: + AnalogInHardware(uint8_t pin_name, ws_sensor_Type read_mode, + ws_analogin_SampleMode sample_mode, ulong period, + float ref_voltage, ExpanderHardware *expander_drv, + ws_analogin_Gain gain); + ~AnalogInHardware(); + float ReadValue(); + bool CheckEvent(); + bool CheckTimer(); + uint8_t GetPinNum() const; + ws_sensor_Type GetReadMode() const; + ws_analogin_SampleMode GetSampleMode() const; + float GetValue() const; + ExpanderHardware *GetExpanderDriver() const; + bool DidReadSend() const; + void MarkSent(); + void ResetSendFlag(); + +private: + uint16_t ReadRawValue(); + float ReadVoltage(); + void InitPin(); + void DeinitPin(); + void SetNativeADCResolution(); + void SetResolution(uint8_t resolution); + void CalculateScaleFactor(); + uint8_t _name; ///< The pin's number. + ws_sensor_Type _read_mode; ///< Type of analog read (RAW or VOLTAGE) + ws_analogin_SampleMode _sample_mode; ///< Sample mode (TIMER or EVENT) + ulong _period; ///< The pin's period, in milliseconds. + ulong _prv_time; ///< Last read timestamp. + bool _did_read_send; ///< True if the last read was sent to IO. + uint16_t _value_raw; ///< Last raw ADC reading. + float _value_voltage; ///< Last voltage reading. + uint16_t _prv_value_raw; ///< Previous raw value for event detection. + uint8_t _native_adc_resolution; ///< Hardware's native ADC resolution. + uint8_t _desired_adc_resolution; ///< Desired (final) ADC resolution. + int _max_scale_resolution_desired; ///< Maximum scale resolution desired. + int _max_scale_resolution_native; ///< Maximum scale resolution native. + float _mcu_vref; ///< Reference voltage for reading analog pins. + ExpanderHardware *_expander_drv; ///< Pointer to expander driver, or nullptr. + ws_analogin_Gain _gain; ///< ADC gain setting for expander pins. +}; +#endif // WS_ANALOGIN_HARDWARE_H diff --git a/src/components/analogIn/model.cpp b/src/components/analogIn/model.cpp new file mode 100644 index 000000000..75e225521 --- /dev/null +++ b/src/components/analogIn/model.cpp @@ -0,0 +1,158 @@ +/*! + * @file src/components/analogIn/model.cpp + * + * Interfaces for the analogin.proto API + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#include "model.h" + +/*! + @brief AnalogInModel constructor +*/ +AnalogInModel::AnalogInModel() { + memset(&_msg_AnalogInAdd, 0, sizeof(_msg_AnalogInAdd)); + memset(&_msg_AnalogInRemove, 0, sizeof(_msg_AnalogInRemove)); + memset(&_msg_AnalogInEvent, 0, sizeof(_msg_AnalogInEvent)); + // no-op +} + +/*! + @brief AnalogInModel destructor +*/ +AnalogInModel::~AnalogInModel() { + memset(&_msg_AnalogInAdd, 0, sizeof(_msg_AnalogInAdd)); + memset(&_msg_AnalogInRemove, 0, sizeof(_msg_AnalogInRemove)); + memset(&_msg_AnalogInEvent, 0, sizeof(_msg_AnalogInEvent)); +} + +/*! + @brief Decodes an AnalogInAdd message from a stream into an + AnalogInAdd message struct. + @param stream + The pb_istream_t stream to decode. + @return True if successful, False otherwise. +*/ +bool AnalogInModel::DecodeAnalogInAdd(pb_istream_t *stream) { + // Zero-out the AnalogInAdd message struct. to ensure we don't have any old + // data + memset(&_msg_AnalogInAdd, 0, sizeof(_msg_AnalogInAdd)); + // Decode the stream into a AnalogInAdd message + return pb_decode(stream, ws_analogin_Add_fields, &_msg_AnalogInAdd); +} + +/*! + @brief Gets an AnalogInAdd message struct. + @return Pointer to an AnalogInAdd message struct. +*/ +ws_analogin_Add *AnalogInModel::GetAnalogInAddMsg() { + return &_msg_AnalogInAdd; +} + +/*! + @brief Decodes an AnalogInRemove message from a stream into an + AnalogInRemove message struct. + @param stream + The pb_istream_t stream to decode. + @return True if successful, False otherwise. +*/ +bool AnalogInModel::DecodeAnalogInRemove(pb_istream_t *stream) { + // Zero-out the AnalogInRemove message struct. to ensure we don't have any old + // data + memset(&_msg_AnalogInRemove, 0, sizeof(_msg_AnalogInRemove)); + // Decode the stream into a AnalogInRemove message + return pb_decode(stream, ws_analogin_Remove_fields, &_msg_AnalogInRemove); +} + +/*! + @brief Gets an AnalogInRemove message struct. + @return Pointer to an AnalogInRemove message struct. +*/ +ws_analogin_Remove *AnalogInModel::GetAnalogInRemoveMsg() { + return &_msg_AnalogInRemove; +} + +/*! + @brief Gets an AnalogInEvent message struct. + @return Pointer to an AnalogInEvent message struct. +*/ +ws_analogin_Event *AnalogInModel::GetAnalogInEvent() { + return &_msg_AnalogInEvent; +} + +/*! + @brief Gets an AnalogIn DeviceToBroker message struct. + @return Pointer to an AnalogIn D2B message struct. +*/ +ws_analogin_D2B *AnalogInModel::GetAnalogInD2B() { return &_msg_AnalogInD2B; } + +/*! + @brief Encodes an AnalogInEvent message. + @param pin_name + The pin's name string (e.g. "A0" or "EXP_0x48_0"). + @param pin_value + The value of the pin. + @param read_type + The type of sensor event to encode. + @return True if successful, False otherwise. +*/ +bool AnalogInModel::EncodeAnalogInEvent(const char *pin_name, float pin_value, + ws_sensor_Type read_type) { + // Initialize the AnalogInEvent message to default values + memset(&_msg_AnalogInEvent, 0, sizeof(_msg_AnalogInEvent)); + // Fill the AnalogInEvent message's fields + strncpy(_msg_AnalogInEvent.pin_name, pin_name, + sizeof(_msg_AnalogInEvent.pin_name)); + _msg_AnalogInEvent.has_value = true; + _msg_AnalogInEvent.value.type = read_type; + _msg_AnalogInEvent.value.which_value = ws_sensor_Event_float_value_tag; + _msg_AnalogInEvent.value.value.float_value = pin_value; + + // Wrap the event in the D2B envelope + memset(&_msg_AnalogInD2B, 0, sizeof(_msg_AnalogInD2B)); + _msg_AnalogInD2B.which_payload = ws_analogin_D2B_event_tag; + _msg_AnalogInD2B.payload.event = _msg_AnalogInEvent; + + return true; +} + +/*! + @brief Encodes an AnalogInEvent message with a raw pin value. + @param pin_name + The pin's name string. + @param pin_value + The value of the pin. + @return True if successful, False otherwise. +*/ +bool AnalogInModel::EncodeAnalogInEventRaw(const char *pin_name, + float pin_value) { + WS_DEBUG_PRINT("[analogin] Pin: "); + WS_DEBUG_PRINTVAR(pin_name); + WS_DEBUG_PRINT(" | Raw Value: "); + WS_DEBUG_PRINTLNVAR(pin_value); + return EncodeAnalogInEvent(pin_name, pin_value, ws_sensor_Type_T_RAW); +} + +/*! + @brief Encodes an AnalogInEvent message with a voltage pin value. + @param pin_name + The pin's name string. + @param pin_value + The value of the pin. + @return True if successful, False otherwise. +*/ +bool AnalogInModel::EncodeAnalogInEventVoltage(const char *pin_name, + float pin_value) { + WS_DEBUG_PRINT("[analogin] Pin: "); + WS_DEBUG_PRINTVAR(pin_name); + WS_DEBUG_PRINT(" | Voltage: "); + WS_DEBUG_PRINTLNVAR(pin_value); + return EncodeAnalogInEvent(pin_name, pin_value, ws_sensor_Type_T_VOLTAGE); +} diff --git a/src/components/analogIn/model.h b/src/components/analogIn/model.h new file mode 100644 index 000000000..b9d131246 --- /dev/null +++ b/src/components/analogIn/model.h @@ -0,0 +1,47 @@ +/*! + * @file src/components/analogIn/model.h + * + * Model interface for the analogin.proto message. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_ANALOGIN_MODEL_H +#define WS_ANALOGIN_MODEL_H +#include "wippersnapper.h" + +/*! + @brief Provides an interface for creating, encoding, and parsing + messages from analogin.proto. +*/ +class AnalogInModel { +public: + AnalogInModel(); + ~AnalogInModel(); + // AnalogInAdd + bool DecodeAnalogInAdd(pb_istream_t *stream); + ws_analogin_Add *GetAnalogInAddMsg(); + // AnalogInRemove + bool DecodeAnalogInRemove(pb_istream_t *stream); + ws_analogin_Remove *GetAnalogInRemoveMsg(); + // AnalogInEvent + bool EncodeAnalogInEvent(const char *pin_name, float pin_value, + ws_sensor_Type read_type); + bool EncodeAnalogInEventVoltage(const char *pin_name, float pin_value); + bool EncodeAnalogInEventRaw(const char *pin_name, float pin_value); + ws_analogin_Event *GetAnalogInEvent(); + ws_analogin_D2B *GetAnalogInD2B(); + +private: + ws_analogin_Add _msg_AnalogInAdd; ///< AnalogInAdd message + ws_analogin_Remove _msg_AnalogInRemove; ///< AnalogInRemove message + ws_analogin_Event _msg_AnalogInEvent; ///< AnalogInEvent message + ws_analogin_D2B _msg_AnalogInD2B; ///< AnalogIn DeviceToBroker wrapper +}; +#endif // WS_ANALOGIN_MODEL_H diff --git a/src/components/checkin/model.cpp b/src/components/checkin/model.cpp index 89ab22208..a249e6fb7 100644 --- a/src/components/checkin/model.cpp +++ b/src/components/checkin/model.cpp @@ -122,9 +122,9 @@ bool CheckinModel::ProcessResponse(pb_istream_t *stream) { void CheckinModel::ConfigureControllers() { Ws.digital_io_controller->SetMaxDigitalPins( _CheckinB2D.payload.response.total_gpio_pins); - Ws.analogio_controller->SetRefVoltage( + Ws.analogin_controller->SetRefVoltage( _CheckinB2D.payload.response.reference_voltage); - Ws.analogio_controller->SetTotalAnalogPins( + Ws.analogin_controller->SetMaxAnalogPins( _CheckinB2D.payload.response.total_analog_pins); } @@ -151,8 +151,8 @@ bool CheckinModel::cbSetupResponse(pb_istream_t *stream, response->component_adds.digitalio_adds.funcs.decode = &cbDigitalIOAdds; response->component_adds.digitalio_adds.arg = model; - response->component_adds.analogio_adds.funcs.decode = &cbAnalogIOAdds; - response->component_adds.analogio_adds.arg = model; + response->component_adds.analogin_adds.funcs.decode = &cbAnalogInAdds; + response->component_adds.analogin_adds.arg = model; response->component_adds.servo_adds.funcs.decode = &cbServoAdds; response->component_adds.servo_adds.arg = model; @@ -195,20 +195,20 @@ bool CheckinModel::cbDigitalIOAdds(pb_istream_t *stream, } /*! - @brief Callback for decoding AnalogIO Add messages. + @brief Callback for decoding AnalogIn Add messages. @param stream Incoming data stream from buffer. @param field Protobuf message's tag type. @param arg Optional arguments from decoder calling function. @returns True if decoded and executed successfully, False otherwise. */ -bool CheckinModel::cbAnalogIOAdds(pb_istream_t *stream, const pb_field_t *field, +bool CheckinModel::cbAnalogInAdds(pb_istream_t *stream, const pb_field_t *field, void **arg) { - ws_analogio_Add add_msg = ws_analogio_Add_init_zero; - if (!pb_decode(stream, ws_analogio_Add_fields, &add_msg)) { - WS_DEBUG_PRINTLN("[checkin] ERROR: Failed to decode analogio add"); + ws_analogin_Add add_msg = ws_analogin_Add_init_zero; + if (!pb_decode(stream, ws_analogin_Add_fields, &add_msg)) { + WS_DEBUG_PRINTLN("[checkin] ERROR: Failed to decode analogin add"); return false; } - return Ws.analogio_controller->Handle_AnalogIOAdd(&add_msg); + return Ws.analogin_controller->Handle_AnalogInAdd(&add_msg); } /*! @@ -336,7 +336,7 @@ ws_sleep_SleepConfig *CheckinModel::GetSleepConfig() { @brief Configures sleep controller based on checkin response. */ void CheckinModel::configureSleep() { - #if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_RP2350) +#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_RP2350) // Process sleep configuration if present if (IsSleepEnabled()) { ws_sleep_SleepConfig *sleep_cfg = GetSleepConfig(); @@ -345,7 +345,7 @@ void CheckinModel::configureSleep() { Ws._sleep_controller->handleSleepConfig(sleep_cfg, true); } } - #endif +#endif } /*! diff --git a/src/components/checkin/model.h b/src/components/checkin/model.h index 0a23b46c8..d34f1062c 100644 --- a/src/components/checkin/model.h +++ b/src/components/checkin/model.h @@ -39,7 +39,7 @@ class CheckinModel { void **arg); //< Pre-decode callback to set up component_adds static bool cbDigitalIOAdds(pb_istream_t *stream, const pb_field_t *field, void **arg); - static bool cbAnalogIOAdds(pb_istream_t *stream, const pb_field_t *field, + static bool cbAnalogInAdds(pb_istream_t *stream, const pb_field_t *field, void **arg); static bool cbServoAdds(pb_istream_t *stream, const pb_field_t *field, void **arg); diff --git a/src/components/digitalIO/controller.cpp b/src/components/digitalIO/controller.cpp index d69f548cc..2252224a4 100644 --- a/src/components/digitalIO/controller.cpp +++ b/src/components/digitalIO/controller.cpp @@ -7,27 +7,31 @@ * please support Adafruit and open-source hardware by purchasing * products from Adafruit! * - * Copyright (c) Brent Rubell 2025 for Adafruit Industries. + * Copyright (c) Brent Rubell 2026 for Adafruit Industries. * * BSD license, all text here must be included in any redistribution. * */ #include "controller.h" +#include "../expander/controller.h" +#include "hardware.h" /*! @brief DigitalIOController constructor */ DigitalIOController::DigitalIOController() { _dio_model = new DigitalIOModel(); - _dio_hardware = new DigitalIOHardware(); } /*! @brief DigitalIOController destructor */ DigitalIOController::~DigitalIOController() { + for (size_t i = 0; i < _pins_input.size(); i++) + delete _pins_input[i]; + for (size_t i = 0; i < _pins_output.size(); i++) + delete _pins_output[i]; delete _dio_model; - delete _dio_hardware; } /*! @@ -76,6 +80,34 @@ bool DigitalIOController::Router(pb_istream_t *stream) { return res; } +/*! + @brief Removes a pin from both vectors by pin number. + Deletes the pin object (destructor deinits hardware). + @param pin_num + The pin number to remove. + @return True if the pin was found and removed. +*/ +bool DigitalIOController::RemovePin(uint8_t pin_num, + ExpanderHardware *expander) { + for (size_t i = 0; i < _pins_output.size(); i++) { + if (_pins_output[i]->GetPinNum() == pin_num && + _pins_output[i]->GetExpanderDriver() == expander) { + delete _pins_output[i]; + _pins_output.erase(_pins_output.begin() + i); + return true; + } + } + for (size_t i = 0; i < _pins_input.size(); i++) { + if (_pins_input[i]->GetPinNum() == pin_num && + _pins_input[i]->GetExpanderDriver() == expander) { + delete _pins_input[i]; + _pins_input.erase(_pins_input.begin() + i); + return true; + } + } + return false; +} + /*! @brief Adds a digital pin to the controller @param msg @@ -84,61 +116,67 @@ bool DigitalIOController::Router(pb_istream_t *stream) { */ bool DigitalIOController::Handle_DigitalIO_Add(ws_digitalio_Add *msg) { WS_DEBUG_PRINTLN("[dio] Handle_DigitalIO_Add MESSAGE..."); - // Strip the D/A prefix off the pin name and convert to a uint8_t pin number - uint8_t pin_name = atoi(msg->pin_name + 1); + uint8_t pin_num = 0; - // Check if the provided pin is also the status LED pin - if (_dio_hardware->IsStatusLEDPin(pin_name)) - ReleaseStatusPixel(); - - // Deinit the pin if it's already in use - if (GetPinIdx(pin_name) != -1) - _dio_hardware->deinit(pin_name); - - // Attempt to configure the pin - if (!_dio_hardware->ConfigurePin(pin_name, msg->gpio_direction)) { + // Validate all fields of the digital pin add message before adding the pin + if (msg->gpio_direction == ws_digitalio_Direction_D_UNSPECIFIED) { + WS_DEBUG_PRINTLN("[dio] ERROR: Invalid GPIO direction specified!"); + return false; + } + if (msg->sample_mode == ws_digitalio_SampleMode_SM_UNSPECIFIED) { + WS_DEBUG_PRINTLN("[dio] ERROR: Invalid sample mode specified!"); + return false; + } + if (msg->sample_mode == ws_digitalio_SampleMode_SM_TIMER && + msg->period <= 0) { WS_DEBUG_PRINTLN( - "[dio] ERROR: Pin provided an invalid protobuf direction!"); + "[dio] ERROR: Invalid period specified for timer sample mode!"); return false; } + if (!ExpanderHardware::ParsePinNum(msg->pin_name, pin_num)) { + WS_DEBUG_PRINTLN("[dio] ERROR: Malformed expander pin name!"); + return false; + } + + // Resolve the expander driver if this is an expander pin + ExpanderHardware *expander_drv = nullptr; + if (strncmp(msg->pin_name, "EXP_", 4) == 0) { + uint8_t i2c_addr = (uint8_t)strtoul(msg->pin_name + 4, nullptr, 16); + expander_drv = Ws._expander_controller->GetDriver(i2c_addr); + if (!expander_drv) { + WS_DEBUG_PRINTLN("[dio] ERROR: Expander not found for address!"); + return false; + } + } + + // Remove existing pin if re-adding (destructor deinits hardware) + RemovePin(pin_num, expander_drv); + // Get the initial value from the write message (if present) bool initial_value = false; if (msg->has_write && msg->write.has_value) { initial_value = msg->write.value.value.bool_value; } - // Create the digital pin and add it to the vector - DigitalIOPin new_pin = {.pin_name = pin_name, - .pin_direction = msg->gpio_direction, - .sample_mode = msg->sample_mode, - .pin_value = initial_value, - .prv_pin_value = initial_value, - .pin_period = (ulong)(msg->period * 1000.0f), - .prv_pin_time = - 0, // Set to 0 so timer pins trigger immediately - .did_read_send = false}; + // Initialize a new digital pin instance + DigitalIOHardware *new_pin = new DigitalIOHardware( + pin_num, msg->gpio_direction, msg->sample_mode, initial_value, + (ulong)(msg->period * 1000.0f), expander_drv); // Add the pin to the controller's list of pins if (msg->gpio_direction == ws_digitalio_Direction_D_INPUT || msg->gpio_direction == ws_digitalio_Direction_D_INPUT_PULL_UP) { _pins_input.push_back(new_pin); } else if (msg->gpio_direction == ws_digitalio_Direction_D_OUTPUT) { - // Write the initial value to the output pin - _dio_hardware->SetValue(pin_name, initial_value); _pins_output.push_back(new_pin); } - // Print out the pin's details WS_DEBUG_PRINTLN("[dio] Added new pin:"); WS_DEBUG_PRINT("Pin Name: "); - WS_DEBUG_PRINTLNVAR(new_pin.pin_name); - WS_DEBUG_PRINT("Period: "); - WS_DEBUG_PRINTLNVAR(new_pin.pin_period); - WS_DEBUG_PRINT("Sample Mode: "); - WS_DEBUG_PRINTLNVAR(new_pin.sample_mode); + WS_DEBUG_PRINTLNVAR(new_pin->GetPinNum()); WS_DEBUG_PRINT("Direction: "); - WS_DEBUG_PRINTLNVAR(new_pin.pin_direction); + WS_DEBUG_PRINTLNVAR(new_pin->GetDirection()); return true; } @@ -150,59 +188,52 @@ bool DigitalIOController::Handle_DigitalIO_Add(ws_digitalio_Add *msg) { @return True if the digital pin was successfully removed, False otherwise. */ bool DigitalIOController::Handle_DigitalIO_Remove(ws_digitalio_Remove *msg) { - bool did_remove = false; - uint8_t pin_name = atoi(msg->pin_name + 1); - - for (size_t i = 0; i < _pins_output.size(); i++) { - if (_pins_output[i].pin_name == pin_name) { - _dio_hardware->deinit(pin_name); - _pins_output.erase(_pins_output.begin() + i); - did_remove = true; - break; - } + uint8_t pin_num = 0; + if (!ExpanderHardware::ParsePinNum(msg->pin_name, pin_num)) { + WS_DEBUG_PRINTLN("[dio] ERROR: Malformed expander pin name!"); + return false; } - if (!did_remove) { - for (size_t i = 0; i < _pins_input.size(); i++) { - if (_pins_input[i].pin_name == pin_name) { - _dio_hardware->deinit(pin_name); - _pins_input.erase(_pins_input.begin() + i); - did_remove = true; - break; - } + // Resolve the expander driver if this is an expander pin + ExpanderHardware *expander_drv = nullptr; + if (strncmp(msg->pin_name, "EXP_", 4) == 0) { + uint8_t i2c_addr = (uint8_t)strtoul(msg->pin_name + 4, nullptr, 16); + expander_drv = Ws._expander_controller->GetDriver(i2c_addr); + if (!expander_drv) { + WS_DEBUG_PRINTLN("[dio] ERROR: Expander not found for address!"); + return false; } } - if (!did_remove) { + + if (!RemovePin(pin_num, expander_drv)) { WS_DEBUG_PRINTLN("[dio] ERROR: Unable to find requested pin!"); return false; } WS_DEBUG_PRINT("[dio] Pin removed: "); - WS_DEBUG_PRINTLNVAR(pin_name); + WS_DEBUG_PRINTLNVAR(pin_num); return true; } /*! - @brief Get the index of a digital output pin - @param pin_name - The pin's name. - @return The index of the digital output pin. + @brief Get a pointer to a digital pin by pin number + @param pin_num + The pin's number. + @return Pointer to the digital pin, or nullptr if not found. */ -int DigitalIOController::GetPinIdx(uint8_t pin_name) { - // Search through output pins first - for (int i = 0; i < _pins_output.size(); i++) { - if (_pins_output[i].pin_name == pin_name) { - return i; - } +DigitalIOHardware *DigitalIOController::GetPin(uint8_t pin_num, + ExpanderHardware *expander) { + for (size_t i = 0; i < _pins_output.size(); i++) { + if (_pins_output[i]->GetPinNum() == pin_num && + _pins_output[i]->GetExpanderDriver() == expander) + return _pins_output[i]; } - - // Search through input pins next - for (int i = 0; i < _pins_input.size(); i++) { - if (_pins_input[i].pin_name == pin_name) { - return i; - } + for (size_t i = 0; i < _pins_input.size(); i++) { + if (_pins_input[i]->GetPinNum() == pin_num && + _pins_input[i]->GetExpanderDriver() == expander) + return _pins_input[i]; } - return -1; // Pin not found + return nullptr; } /*! @@ -212,16 +243,30 @@ int DigitalIOController::GetPinIdx(uint8_t pin_name) { @return True if the digital pin was successfully written. */ bool DigitalIOController::Handle_DigitalIO_Write(ws_digitalio_Write *msg) { - // Get the digital pin - int pin_idx = GetPinIdx(atoi(msg->pin_name + 1)); - // Check if the pin was found and is a valid digital output pin - if (pin_idx == -1) { + uint8_t pin_num = 0; + if (!ExpanderHardware::ParsePinNum(msg->pin_name, pin_num)) { + WS_DEBUG_PRINTLN("[dio] ERROR: Malformed expander pin name!"); + return false; + } + + // Resolve the expander driver if this is an expander pin + ExpanderHardware *expander_drv = nullptr; + if (strncmp(msg->pin_name, "EXP_", 4) == 0) { + uint8_t i2c_addr = (uint8_t)strtoul(msg->pin_name + 4, nullptr, 16); + expander_drv = Ws._expander_controller->GetDriver(i2c_addr); + if (!expander_drv) { + WS_DEBUG_PRINTLN("[dio] ERROR: Expander not found for address!"); + return false; + } + } + + DigitalIOHardware *pin = GetPin(pin_num, expander_drv); + if (!pin) { WS_DEBUG_PRINTLN("[dio] ERROR: Unable to find the requested output pin!"); return false; } - // Ensure pin_idx exists within pins_output vector - if (pin_idx >= _pins_output.size()) { + if (pin->GetDirection() != ws_digitalio_Direction_D_OUTPUT) { WS_DEBUG_PRINTLN("[dio] ERROR: Requested pin is not a digital output pin!"); return false; } @@ -235,105 +280,43 @@ bool DigitalIOController::Handle_DigitalIO_Write(ws_digitalio_Write *msg) { WS_DEBUG_PRINT("[dio] Writing: "); WS_DEBUG_PRINTVAR(msg->value.value.bool_value); WS_DEBUG_PRINT(" to Pin "); - WS_DEBUG_PRINTLNVAR(_pins_output[pin_idx].pin_name); - - // Is the pin already set to this value? If so, we don't need to write it - // again - if (_pins_output[pin_idx].pin_value == msg->value.value.bool_value) - return true; - - // Write the value - _dio_hardware->SetValue(_pins_output[pin_idx].pin_name, - msg->value.value.bool_value); - - // Update the pin's value - _pins_output[pin_idx].pin_value = msg->value.value.bool_value; - return true; -} - -/*! - @brief Check if a pin's timer has expired - @param pin - The pin to check. - @param cur_time - The current time. - @return True if the pin's timer has expired. -*/ -bool DigitalIOController::IsPinTimerExpired(DigitalIOPin *pin, ulong cur_time) { - return cur_time - pin->prv_pin_time > pin->pin_period; -} - -/*! - @brief Check if a pin's timer has expired - @param pin - The pin to check. - @return True if the pin's timer has expired. -*/ -bool DigitalIOController::CheckTimerPin(DigitalIOPin *pin) { - if (!pin) - return false; - - ulong cur_time = millis(); - // Bail out if the pin's timer has not expired - if (!IsPinTimerExpired(pin, cur_time)) - return false; - - // Fill in the pin's current time and value - pin->prv_pin_time = cur_time; - pin->pin_value = _dio_hardware->GetValue(pin->pin_name); - - return true; -} - -/*! - @brief Check if a pin's value has changed - @param pin - The pin to check. - @return True if the pin's value has changed. -*/ -bool DigitalIOController::CheckEventPin(DigitalIOPin *pin) { - if (!pin) - return false; - // Get the pin's current value - pin->pin_value = _dio_hardware->GetValue(pin->pin_name); - - // Bail out if the pin value hasn't changed - if (pin->pin_value == pin->prv_pin_value) - return false; - - // Update the pin's previous value to the current value - pin->prv_pin_value = pin->pin_value; + WS_DEBUG_PRINTLNVAR(pin->GetPinNum()); + pin->Write(msg->value.value.bool_value); return true; } /*! @brief Encode and publish a pin event - @param pin_name - The pin's name. - @param pin_value - The pin's value. + @param pin + Pointer to the digital pin hardware object. @return True if the pin event was successfully encoded and published. */ -bool DigitalIOController::EncodePublishPinEvent(uint8_t pin_name, - bool pin_value) { - // Prefix pin_name with "D" to match the expected pin name format - char c_pin_name[12]; - sprintf(c_pin_name, "D%d", pin_name); +bool DigitalIOController::EncodePublishPinEvent(DigitalIOHardware *pin) { + uint8_t pin_num = pin->GetPinNum(); + bool pin_value = pin->GetPinValue(); + + // Format pin name: expander pins use "EXP_0xNN_P", native pins use "DN" + char c_pin_name[20]; + ExpanderHardware *expander = pin->GetExpanderDriver(); + if (expander != nullptr) { + ExpanderHardware::FormatPinName(c_pin_name, sizeof(c_pin_name), + expander->getAddress(), pin_num); + } else { + snprintf(c_pin_name, sizeof(c_pin_name), "D%d", pin_num); + } - // If we are in ONLINE mode, publish the event to the broker if (!Ws._sdCardV2->isModeOffline()) { WS_DEBUG_PRINT("[dio] Publish Event: "); WS_DEBUG_PRINTVAR(c_pin_name); WS_DEBUG_PRINT(" | value: "); WS_DEBUG_PRINTLNVAR(pin_value); - // Encode the DigitalIOEvent message + if (!_dio_model->EncodeDigitalIOEvent(c_pin_name, pin_value)) { WS_DEBUG_PRINTLN("ERROR: Unable to encode DigitalIOEvent message!"); return false; } - // Publish the DigitalIOEvent message to the broker if (!Ws.PublishD2b(ws_signal_DeviceToBroker_digitalio_tag, _dio_model->GetDigitalIOD2B())) { WS_DEBUG_PRINTLN("[dio] ERROR: Unable to publish event message, " @@ -342,8 +325,7 @@ bool DigitalIOController::EncodePublishPinEvent(uint8_t pin_name, } WS_DEBUG_PRINTLN("[dio] Published!") } else { - // let's log the event to the SD card - if (!Ws._sdCardV2->LogGPIOSensorEventToSD(pin_name, pin_value, + if (!Ws._sdCardV2->LogGPIOSensorEventToSD(pin_num, pin_value, ws_sensor_Type_T_BOOLEAN)) return false; } @@ -358,70 +340,54 @@ bool DigitalIOController::EncodePublishPinEvent(uint8_t pin_name, If true, forces a read on all pins regardless of timers/events. */ void DigitalIOController::update(bool force) { - // Bail out if we have no digital input pins to poll if (_pins_input.empty()) return; - // Check the input pins for events or timer expirations - const size_t num_input_pins = _pins_input.size(); - for (size_t i = 0; i < num_input_pins; i++) { - // Create a pin object for this iteration - DigitalIOPin &pin = _pins_input[i]; - - // (force only) - Was pin previously read and sent? - if (pin.did_read_send && force) - continue; + for (size_t i = 0; i < _pins_input.size(); i++) { + DigitalIOHardware *pin = _pins_input[i]; - // Skip normal checks if we're forcing a read - if (!force) { - switch (pin.sample_mode) { - case ws_digitalio_SampleMode_SM_EVENT: - // Check if the pin value has changed - if (!CheckEventPin(&pin)) - continue; // No change in pin value detected, move onto the next pin - break; - case ws_digitalio_SampleMode_SM_TIMER: - // Check if the timer has expired - if (!CheckTimerPin(&pin)) - continue; // Timer has not expired yet, move onto the next pin - break; - default: + if (force) { + if (pin->DidReadSend()) continue; - } + pin->ReadValue(); } else { - // Force read the pin value - pin.pin_value = _dio_hardware->GetValue(pin.pin_name); + bool changed; + if (pin->GetSampleMode() == ws_digitalio_SampleMode_SM_EVENT) + changed = pin->CheckEvent(); + else + changed = pin->CheckTimer(); + if (!changed) + continue; } - // Encode and publish the event - if (!EncodePublishPinEvent(pin.pin_name, pin.pin_value)) { + if (!EncodePublishPinEvent(pin)) { WS_DEBUG_PRINTLN("[dio] ERROR: Unable to record pin value!"); - pin.did_read_send = false; + pin->ResetSendFlag(); continue; } - pin.did_read_send = true; + pin->MarkSent(); } } /*! - @brief Checks if all digital input pins have been read and their values - sent. + @brief SLEEP MODE: Checks if all digital input pins have been read and + their values sent. @return True if all pins have been read and sent, False otherwise. */ bool DigitalIOController::UpdateComplete() { for (size_t i = 0; i < _pins_input.size(); i++) { - if (!_pins_input[i].did_read_send) { + if (!_pins_input[i]->DidReadSend()) return false; - } } return true; } /*! - @brief Resets all digital input pins' did_read_send flags to false. + @brief SLEEP MODE: Resets all digital input pins' did_read_send flags to + false. */ void DigitalIOController::ResetFlags() { for (size_t i = 0; i < _pins_input.size(); i++) { - _pins_input[i].did_read_send = false; + _pins_input[i]->ResetSendFlag(); } -} \ No newline at end of file +} diff --git a/src/components/digitalIO/controller.h b/src/components/digitalIO/controller.h index 73f24d5b3..8d9c4bf82 100644 --- a/src/components/digitalIO/controller.h +++ b/src/components/digitalIO/controller.h @@ -7,37 +7,20 @@ * please support Adafruit and open-source hardware by purchasing * products from Adafruit! * - * Copyright (c) Brent Rubell 2025 for Adafruit Industries. + * Copyright (c) Brent Rubell 2026 for Adafruit Industries. * * BSD license, all text here must be included in any redistribution. * */ #ifndef WS_DIGITALIO_CONTROLLER_H #define WS_DIGITALIO_CONTROLLER_H -#include "hardware.h" #include "model.h" #include "wippersnapper.h" class wippersnapper; - -/** - * @struct DigitalIOPin - * @brief This struct represents a digital I/O pin. - */ -struct DigitalIOPin { - uint8_t pin_name; ///< The pin's name. - ws_digitalio_Direction pin_direction; ///< The pin's direction. - ws_digitalio_SampleMode sample_mode; ///< The pin's sample mode. - bool pin_value; ///< The pin's value. - bool prv_pin_value; ///< The pin's previous value. - ulong pin_period; ///< The pin's period. - ulong prv_pin_time; ///< The pin's previous time. - bool - did_read_send; ///< True if the last read was sent to IO, False otherwise. -}; - -class DigitalIOModel; // Forward declaration -class DigitalIOHardware; // Forward declaration +class DigitalIOModel; +class DigitalIOHardware; +class ExpanderHardware; /*! @brief Routes messages using the digitalio.proto API to the @@ -53,23 +36,17 @@ class DigitalIOController { bool Handle_DigitalIO_Remove(ws_digitalio_Remove *msg); bool Handle_DigitalIO_Write(ws_digitalio_Write *msg); void update(bool force = false); - // For sleep cycles bool UpdateComplete(); void ResetFlags(); - - // Called once per-run, during CheckinResponse processing void SetMaxDigitalPins(uint8_t max_digital_pins); private: - bool EncodePublishPinEvent(uint8_t pin_name, bool pin_value); - bool CheckEventPin(DigitalIOPin *pin); - bool CheckTimerPin(DigitalIOPin *pin); - bool IsPinTimerExpired(DigitalIOPin *pin, ulong cur_time); - int GetPinIdx(uint8_t pin_name); - std::vector _pins_input; - std::vector _pins_output; + bool EncodePublishPinEvent(DigitalIOHardware *pin); + bool RemovePin(uint8_t pin_num, ExpanderHardware *expander); + DigitalIOHardware *GetPin(uint8_t pin_num, ExpanderHardware *expander); + std::vector _pins_input; + std::vector _pins_output; DigitalIOModel *_dio_model; - DigitalIOHardware *_dio_hardware; }; extern wippersnapper Ws; ///< Wippersnapper V2 instance #endif // WS_DIGITALIO_CONTROLLER_H \ No newline at end of file diff --git a/src/components/digitalIO/hardware.cpp b/src/components/digitalIO/hardware.cpp index 4f6d1ac28..29a70cd2a 100644 --- a/src/components/digitalIO/hardware.cpp +++ b/src/components/digitalIO/hardware.cpp @@ -7,7 +7,7 @@ * please support Adafruit and open-source hardware by purchasing * products from Adafruit! * - * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * Copyright (c) Brent Rubell 2026 for Adafruit Industries. * * BSD license, all text here must be included in any redistribution. * @@ -16,93 +16,212 @@ /*! @brief DigitalIOHardware constructor + @param pin_name The pin number. + @param direction The pin's direction. + @param sample_mode The pin's sample mode. + @param initial_value The pin's initial value. + @param period The pin's period in milliseconds. + @param expander_drv Pointer to expander driver, or nullptr. */ -DigitalIOHardware::DigitalIOHardware() {} +DigitalIOHardware::DigitalIOHardware(uint8_t pin_name, + ws_digitalio_Direction direction, + ws_digitalio_SampleMode sample_mode, + bool initial_value, ulong period, + ExpanderHardware *expander_drv) + : _name(pin_name), _direction(direction), _sample_mode(sample_mode), + _value(initial_value), _prv_value(initial_value), _period(period), + _prv_time(0), _did_read_send(false), _expander_drv(expander_drv) { + SetMode(); +} /*! - @brief DigitalIOHardware destructor + @brief DigitalIOHardware destructor. Resets pin to floating INPUT state. */ -DigitalIOHardware::~DigitalIOHardware() {} +DigitalIOHardware::~DigitalIOHardware() { + bool has_expander = (_expander_drv != nullptr); + + if (!has_expander) { + digitalWrite(_name, LOW); + pinMode(_name, INPUT); + } else { + _expander_drv->digitalWrite(_name, LOW); + _expander_drv->pinMode(_name, INPUT); + } + + if (IsStatusLEDPin()) + initStatusLED(); +} /*! - @brief Configures a digital pin. - @param name - The pin's name. - @param direction - The pin's direction. - @return True if the pin was successfully configured. False otherwise. + @brief Configures the pin's mode based on its direction. + @return True if the pin was successfully configured. */ -bool DigitalIOHardware::ConfigurePin(uint8_t name, - ws_digitalio_Direction direction) { - // Configure an output pin - if (direction == ws_digitalio_Direction_D_OUTPUT) { - // Set pin mode to OUTPUT - pinMode(name, OUTPUT); -// Initialize pin value to LOW +bool DigitalIOHardware::SetMode() { + if (IsStatusLEDPin()) + ReleaseStatusPixel(); + + bool has_expander = (_expander_drv != nullptr); + + if (_direction == ws_digitalio_Direction_D_OUTPUT) { + if (!has_expander) { + pinMode(_name, OUTPUT); + digitalWrite(_name, _value ? HIGH : LOW); #if defined(ARDUINO_ESP8266_ADAFRUIT_HUZZAH) - // The Adafruit Feather ESP8266's built-in LED is reverse wired so setting - // the pin LOW will turn the LED on. - digitalWrite(name, !0); -#else - pinMode(name, OUTPUT); - digitalWrite(name, LOW); // initialize LOW + if (!_value) + digitalWrite(_name, !0); #endif - } else if (direction == ws_digitalio_Direction_D_INPUT) { - pinMode(name, INPUT); - } else if (direction == ws_digitalio_Direction_D_INPUT_PULL_UP) { - pinMode(name, INPUT_PULLUP); + } else { + _expander_drv->pinMode(_name, OUTPUT); + _expander_drv->digitalWrite(_name, _value ? HIGH : LOW); + } + } else if (_direction == ws_digitalio_Direction_D_INPUT) { + if (!has_expander) { + pinMode(_name, INPUT); + } else { + _expander_drv->pinMode(_name, INPUT); + } + } else if (_direction == ws_digitalio_Direction_D_INPUT_PULL_UP) { + if (!has_expander) { + pinMode(_name, INPUT_PULLUP); + } else { + _expander_drv->pinMode(_name, INPUT_PULLUP); + } } else { - return false; // Invalid pin configuration + return false; } return true; } /*! - @brief Deinitializes a digital pin. - @param pin_name - The digital pin to deinitialize. + @brief Writes a value to the pin and updates internal state. + @param value The value to write. */ -void DigitalIOHardware::deinit(uint8_t pin_name) { - // Turn off pin output and reset mode to hi-z floating state - digitalWrite(pin_name, LOW); - pinMode(pin_name, INPUT); - // Prior to using this pin as a DIO, - // was this a status LED pin? - if (IsStatusLEDPin(pin_name)) { - initStatusLED(); // it was! re-init status led +void DigitalIOHardware::Write(bool value) { + if (_value == value) + return; + + bool has_expander = (_expander_drv != nullptr); + + if (!has_expander) { + digitalWrite(_name, value ? HIGH : LOW); + } else { + _expander_drv->digitalWrite(_name, value ? HIGH : LOW); } + _value = value; +} + +/*! + @brief Reads the pin's current value from hardware and updates + internal state. + @return The pin's current value. +*/ +bool DigitalIOHardware::ReadValue() { + bool has_expander = (_expander_drv != nullptr); + + if (!has_expander) { + _value = digitalRead(_name); + } else { + _value = _expander_drv->digitalRead(_name); + } + return _value; +} + +/*! + @brief Checks if the pin's value has changed since last check. + @return True if the value changed. +*/ +bool DigitalIOHardware::CheckEvent() { + ReadValue(); + + // Has the value changed since the last time we checked? + if (_value == _prv_value) + return false; + _prv_value = _value; + return true; +} + +/*! + @brief Checks if the pin's timer has expired and reads the value. + @return True if the timer expired. +*/ +bool DigitalIOHardware::CheckTimer() { + ulong cur_time = millis(); + if (!IsPinTimerExpired(cur_time)) + return false; + + _prv_time = cur_time; + ReadValue(); + return true; +} + +/*! + @brief Checks if the pin's timer has expired. + @param cur_time The current time in milliseconds. + @return True if the timer has expired. +*/ +bool DigitalIOHardware::IsPinTimerExpired(ulong cur_time) { + return cur_time - _prv_time > _period; } /*! - @brief Sets a digital pin's value. - @param pin_name - The pin's name. - @param pin_value - The pin's value. + @brief Gets the pin's number. + @return The pin's number. */ -void DigitalIOHardware::SetValue(uint8_t pin_name, bool pin_value) { - digitalWrite(pin_name, pin_value ? HIGH : LOW); +uint8_t DigitalIOHardware::GetPinNum() const { return _name; } + +/*! + @brief Gets the pin's current value. + @return The pin's current value. +*/ +bool DigitalIOHardware::GetPinValue() const { return _value; } + +/*! + @brief Gets the pin's direction. + @return The pin's direction. +*/ +ws_digitalio_Direction DigitalIOHardware::GetDirection() const { + return _direction; } /*! - @brief Gets a digital pin's value. - @param pin_name - The pin's name. - @return The pin's value. + @brief Gets the pin's sample mode. + @return The pin's sample mode. */ -bool DigitalIOHardware::GetValue(uint8_t pin_name) { - return digitalRead(pin_name); +ws_digitalio_SampleMode DigitalIOHardware::GetSampleMode() const { + return _sample_mode; } /*! - @brief Checks if a pin is the status LED pin. - @param pin_name - The pin's name. - @return True if the pin is the status LED pin. + @brief Gets the expander driver, or nullptr for native pins. + @return Pointer to the expander driver, or nullptr. */ -bool DigitalIOHardware::IsStatusLEDPin(uint8_t pin_name) { +ExpanderHardware *DigitalIOHardware::GetExpanderDriver() const { + return _expander_drv; +} + +/*! + @brief Gets whether the last read was sent to IO. + @returns True if the last read was sent successfully, False otherwise. +*/ +bool DigitalIOHardware::DidReadSend() const { return _did_read_send; } + +/*! + @brief Marks that the current pin value has been sent to IO. +*/ +void DigitalIOHardware::MarkSent() { _did_read_send = true; } + +/*! + @brief Resets the pin's did_read_send flag to false. +*/ +void DigitalIOHardware::ResetSendFlag() { _did_read_send = false; } + +/*! + @brief Checks if this pin is the status LED pin. + @return True if this pin is the status LED pin. +*/ +bool DigitalIOHardware::IsStatusLEDPin() const { #ifdef STATUS_LED_PIN - return pin_name == STATUS_LED_PIN; + return _name == STATUS_LED_PIN; #endif return false; -} \ No newline at end of file +} diff --git a/src/components/digitalIO/hardware.h b/src/components/digitalIO/hardware.h index 1b6d0072e..8f457aa89 100644 --- a/src/components/digitalIO/hardware.h +++ b/src/components/digitalIO/hardware.h @@ -7,7 +7,7 @@ * please support Adafruit and open-source hardware by purchasing * products from Adafruit! * - * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * Copyright (c) Brent Rubell 2026 for Adafruit Industries. * * BSD license, all text here must be included in any redistribution. * @@ -16,19 +16,53 @@ #define WS_DIGITALIO_HARDWARE_H #include "wippersnapper.h" +class ExpanderHardware; + /*! - @brief Interface for interacting with hardware's digital I/O pin API. + @brief Represents a single digital I/O pin and provides + hardware-level operations for reading, writing, + and polling its state. */ class DigitalIOHardware { public: - DigitalIOHardware(); + DigitalIOHardware(uint8_t pin_name, ws_digitalio_Direction direction, + ws_digitalio_SampleMode sample_mode, bool initial_value, + ulong period, ExpanderHardware *expander_drv); ~DigitalIOHardware(); - bool ConfigurePin(uint8_t name, ws_digitalio_Direction direction); - void SetValue(uint8_t pin_name, bool pin_value); - bool GetValue(uint8_t pin_name); - void deinit(uint8_t pin_name); - bool IsStatusLEDPin(uint8_t pin_name); + + // Pin operations + void Write(bool value); + bool ReadValue(); + + // Polling + bool CheckEvent(); + bool CheckTimer(); + + // Getters + uint8_t GetPinNum() const; + bool GetPinValue() const; + ws_digitalio_Direction GetDirection() const; + ws_digitalio_SampleMode GetSampleMode() const; + ExpanderHardware *GetExpanderDriver() const; + + // Sleep cycle support + bool DidReadSend() const; + void MarkSent(); + void ResetSendFlag(); private: + bool SetMode(); + bool IsPinTimerExpired(ulong cur_time); + bool IsStatusLEDPin() const; + + uint8_t _name; + ws_digitalio_Direction _direction; + ws_digitalio_SampleMode _sample_mode; + bool _value; + bool _prv_value; + ulong _period; + ulong _prv_time; + bool _did_read_send; + ExpanderHardware *_expander_drv; }; #endif // WS_DIGITALIO_HARDWARE_H \ No newline at end of file diff --git a/src/components/error/controller.cpp b/src/components/error/controller.cpp index 90d8d14bf..934687c56 100644 --- a/src/components/error/controller.cpp +++ b/src/components/error/controller.cpp @@ -165,14 +165,14 @@ bool ErrorController::PublishError(pb_size_t which_component_type, /*! Helper Functions */ /*! - @brief Publishes an AnalogIO error message to the broker. + @brief Publishes an AnalogIn error message to the broker. @param error_msg The error message to publish. @param pin_name The name of the pin that caused the error. @return True if the message was successfully published, False otherwise. */ -bool ErrorController::PublishAnalogIO(const char *error_msg, +bool ErrorController::PublishAnalogIn(const char *error_msg, const char *pin_name) { if (!error_msg || !pin_name) return false; @@ -187,7 +187,7 @@ bool ErrorController::PublishAnalogIO(const char *error_msg, pin_callback.arg = (void *)pin_name; // Pass to PublishError - return PublishError(ws_signal_DeviceToBroker_analogio_tag, + return PublishError(ws_signal_DeviceToBroker_analogin_tag, ws_error_ErrorD2B_pin_tag, pin_callback, error_msg_callback); } diff --git a/src/components/error/controller.h b/src/components/error/controller.h index 62abad2b9..d6843d859 100644 --- a/src/components/error/controller.h +++ b/src/components/error/controller.h @@ -29,7 +29,7 @@ class ErrorController { ErrorController(); ~ErrorController(); bool Router(pb_istream_t *stream); - bool PublishAnalogIO(const char *error_msg, const char *pin_name); + bool PublishAnalogIn(const char *error_msg, const char *pin_name); bool PublishDigitalIO(const char *error_msg, const char *pin_name); bool PublishDS18x20(const char *error_msg, const char *pin_name); bool PublishPixels(const char *error_msg, const char *pin_name); diff --git a/src/components/expander/controller.cpp b/src/components/expander/controller.cpp new file mode 100644 index 000000000..25deee4bc --- /dev/null +++ b/src/components/expander/controller.cpp @@ -0,0 +1,214 @@ +/*! + * @file src/components/expander/controller.cpp + * + * Controller for WipperSnapper's expander component, bridges between the + * expander.proto API, the model, and the hardware layer. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2026 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#include "controller.h" + +ExpanderController::ExpanderController() { _model = new ExpanderModel(); } + +ExpanderController::~ExpanderController() { + if (_model) { + delete _model; + _model = nullptr; + } + for (ExpanderHardware *drv : _expanders) { + delete drv; + } + _expanders.clear(); +} + +/*! + * @brief Routes messages using the expander.proto API to the + * appropriate controller functions. + * @param stream The nanopb input stream. + * @return True if the message was successfully routed, False otherwise. + */ +bool ExpanderController::Router(pb_istream_t *stream) { + ws_expander_B2D b2d = ws_expander_B2D_init_zero; + if (!ws_pb_decode(stream, ws_expander_B2D_fields, &b2d)) { + WS_DEBUG_PRINTLN( + "[expander] ERROR: Unable to decode expander B2D envelope"); + return false; + } + + bool res = false; + switch (b2d.which_payload) { + case ws_expander_B2D_add_tag: + res = Handle_Add(&b2d.payload.add); + break; + case ws_expander_B2D_remove_tag: + res = Handle_Remove(&b2d.payload.remove); + break; + default: + WS_DEBUG_PRINTLN("[expander] WARNING: Unsupported expander payload"); + res = false; + break; + } + return res; +} + +/*! + * @brief Adds an I2C expander hardware instance to the controller. + * @param device_name Name of the expander device (e.g., "mcp23017"). + * @param i2c_addr I2C address of the expander. + * @param wire Pointer to the TwoWire instance for I2C communication. + * @return True if the expander was added successfully, false otherwise. + */ +bool ExpanderController::AddExpander(const char *device_name, uint8_t i2c_addr, + TwoWire *wire) { + // Create the appropriate driver for the expander + ExpanderHardware *drv = nullptr; + if (strcmp(device_name, "mcp23017") == 0) { + drv = new ExpanderMCP23X17(); + } else if (strcmp(device_name, "mcp23008") == 0) { + drv = new ExpanderMCP23X08(); + } else if (strcmp(device_name, "aw9523") == 0) { + drv = new ExpanderAW9523(); + } else if (strcmp(device_name, "pcf8574") == 0) { + drv = new ExpanderPCF8574(); + } else if (strcmp(device_name, "pcf8575") == 0) { + drv = new ExpanderPCF8575(); + } else if (strcmp(device_name, "tca8418") == 0) { + drv = new ExpanderTCA8418(); + } else if (strcmp(device_name, "seesaw") == 0) { + drv = new ExpanderSeesaw(); + } else if (strcmp(device_name, "ads1015") == 0) { + drv = new ExpanderADS1015(); + } else if (strcmp(device_name, "ads1115") == 0) { + drv = new ExpanderADS1115(); + } else { + WS_DEBUG_PRINTLN("[expander] ERROR: Unsupported expander device type!"); + return false; + } + + if (drv == nullptr) { + WS_DEBUG_PRINTLN("[expander] ERROR: Unsupported expander device!"); + return false; + } + + if (!drv->begin(i2c_addr, wire)) { + WS_DEBUG_PRINTLN("[expander] ERROR: Failed to initialize expander!"); + delete drv; + return false; + } + + _expanders.push_back(drv); + WS_DEBUG_PRINTLN("[expander] Expander hardware added successfully!"); + return true; +} + +/*! + * @brief Finds an expander by its I2C address. + * @param addr The I2C address to search for. + * @return Pointer to the ExpanderHardware, or nullptr if not found. + */ +ExpanderHardware *ExpanderController::GetDriver(uint8_t addr) { + for (ExpanderHardware *drv : _expanders) { + if (drv->getAddress() == addr) + return drv; + } + return nullptr; +} + +/*! + * @brief Handles an expander Add message. + * @param msg The Add message. + * @return True if the expander was added successfully, False otherwise. + */ +bool ExpanderController::Handle_Add(ws_expander_Add *msg) { + if (!msg->has_cfg) { + WS_DEBUG_PRINTLN("[expander] ERROR: No configuration provided!"); + return false; + } + + ws_i2c_DeviceDescriptor desc = msg->cfg.device_description; + uint8_t addr = (uint8_t)desc.device_address; + + // Check if this expander has already been added + if (GetDriver(addr) != nullptr) { + WS_DEBUG_PRINTLN("[expander] ERROR: Expander exists at this address!"); + return false; + } + + // Get or create the I2C bus for the expander + TwoWire *wire = + Ws._i2c_controller->GetOrCreateI2cBus(desc.pin_scl, desc.pin_sda); + if (wire == nullptr) { + WS_DEBUG_PRINTLN("[expander] ERROR: Failed to get/create I2C bus!"); + return false; + } + + // Attempt to initialize the expander + bool did_add = AddExpander(msg->cfg.device_name, addr, wire); + + // Build and publish the Added response + ws_i2c_DeviceAddedOrReplaced response; + memset(&response, 0, sizeof(response)); + response.has_device_description = true; + response.device_description = desc; + response.bus_status = ws_i2c_BusStatus_BS_SUCCESS; + response.device_status = did_add ? ws_i2c_DeviceStatus_DS_SUCCESS + : ws_i2c_DeviceStatus_DS_FAIL_INIT; + + if (!Ws.PublishD2b(ws_signal_DeviceToBroker_expander_tag, + _model->GetAddedD2B(response))) { + WS_DEBUG_PRINTLN("[expander] ERROR: Failed to publish Added response!"); + return false; + } + return did_add; +} + +/*! + * @brief Handles an expander Remove message. + * @param msg The Remove message. + * @return True if the expander was removed successfully, False otherwise. + */ +bool ExpanderController::Handle_Remove(ws_expander_Remove *msg) { + if (!msg->has_cfg) { + WS_DEBUG_PRINTLN("[expander] ERROR: No I2C config provided in Remove!"); + return false; + } + + ws_i2c_DeviceDescriptor desc = msg->cfg.device_description; + uint8_t addr = (uint8_t)desc.device_address; + bool did_remove = false; + + // Find and remove the expander by address + for (size_t i = 0; i < _expanders.size(); i++) { + if (_expanders[i]->getAddress() == addr) { + delete _expanders[i]; + _expanders.erase(_expanders.begin() + i); + did_remove = true; + break; + } + } + + if (!did_remove) { + WS_DEBUG_PRINTLN("[expander] WARNING: Expander not found for removal!"); + } + + // Build and publish the Removed response + ws_i2c_DeviceRemoved response; + memset(&response, 0, sizeof(response)); + response.has_device_description = true; + response.device_description = desc; + response.did_remove = did_remove; + + if (!Ws.PublishD2b(ws_signal_DeviceToBroker_expander_tag, + _model->GetRemovedD2B(response))) { + WS_DEBUG_PRINTLN("[expander] ERROR: Failed to publish Removed response!"); + return false; + } + return did_remove; +} diff --git a/src/components/expander/controller.h b/src/components/expander/controller.h new file mode 100644 index 000000000..6c9942ec2 --- /dev/null +++ b/src/components/expander/controller.h @@ -0,0 +1,53 @@ +/*! + * @file src/components/expander/controller.h + * + * Controller for WipperSnapper's expander component, bridges between the + * expander.proto API, the model, and the hardware layer. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2026 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_EXPANDER_CONTROLLER_H +#define WS_EXPANDER_CONTROLLER_H +#include "drivers/drv_ads1015.h" +#include "drivers/drv_ads1115.h" +#include "drivers/drv_aw9523.h" +#include "drivers/drv_mcp23x08.h" +#include "drivers/drv_mcp23x17.h" +#include "drivers/drv_pcf8574.h" +#include "drivers/drv_pcf8575.h" +#include "drivers/drv_seesaw.h" +#include "drivers/drv_tca8418.h" +#include "hardware.h" +#include "model.h" +#include "wippersnapper.h" + +class wippersnapper; ///< Forward declaration +class ExpanderModel; ///< Forward declaration +class ExpanderHardware; ///< Forward declaration + +/*! + @brief Routes messages between the expander.proto API and the hardware. +*/ +class ExpanderController { +public: + ExpanderController(); + ~ExpanderController(); + bool Router(pb_istream_t *stream); + bool Handle_Add(ws_expander_Add *msg); + bool Handle_Remove(ws_expander_Remove *msg); + ExpanderHardware *GetDriver(uint8_t addr); + +private: + bool AddExpander(const char *device_name, uint8_t i2c_addr, TwoWire *wire); + ExpanderModel *_model; ///< Expander model instance + std::vector _expanders; ///< Expander hardware instances +}; +extern wippersnapper Ws; ///< Wippersnapper V2 instance +#endif // WS_EXPANDER_CONTROLLER_H diff --git a/src/components/expander/drivers/drv_ads1015.h b/src/components/expander/drivers/drv_ads1015.h new file mode 100644 index 000000000..49f7da195 --- /dev/null +++ b/src/components/expander/drivers/drv_ads1015.h @@ -0,0 +1,100 @@ +/*! + * @file src/components/expander/drivers/drv_ads1015.h + * + * Header-only expander driver for the ADS1015 4-channel, 12-bit, ADC. + * Wraps the Adafruit_ADS1X15 library. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2026 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_EXPANDER_DRV_ADS1015_H +#define WS_EXPANDER_DRV_ADS1015_H +#include "../hardware.h" +#include + +#define ADS1015_RESOLUTION 12 ///< ADC resolution, in bits + +/*! + @brief Expander driver for the ADS1015 4-channel, 12-bit ADC. +*/ +class ExpanderADS1015 : public ExpanderHardware { +public: + /*! + * @brief Constructor for the ADS1015 expander driver. + */ + ExpanderADS1015() {} + + /*! + * @brief Destructor for the ADS1015 expander driver. + */ + ~ExpanderADS1015() {} + + uint8_t getAdcResolution() override { return ADS1015_RESOLUTION; } + + /*! + * @brief Initializes the ADS1015 driver with the given I2C address and bus. + * @param i2c_addr The I2C address of the expander. + * @param wire The TwoWire I2C bus to use for communication. + * @return True if initialization was successful, False otherwise. + */ + bool begin(uint8_t i2c_addr, TwoWire *wire) override { + _i2c_addr = i2c_addr; + return _ads.begin(i2c_addr, wire); + } + + /*! + * @brief Reads the raw ADC value from the specified pin. + * @param pin The pin number to read from (0-3 for ADS1015). + * @return The raw ADC value read from the pin. + */ + uint16_t analogRead(uint8_t pin) override { + return _ads.readADC_SingleEnded(pin); + } + + /*! + * @brief Sets the gain for the ADC readings. + * @param gain The gain setting from ws_analogin_Gain. + * Values: 1=1x, 2=2x, 3=4x, 4=8x, 5=16x, 9=2/3x. + * Unsupported gains (32x, 64x, 128x) fall back to 1x. + * @return True if the gain was successfully set, False otherwise. + */ + bool setGain(uint8_t gain) override { + adsGain_t ads_gain; + switch (gain) { + case 1: // G_1X + ads_gain = GAIN_ONE; + break; + case 2: // G_2X + ads_gain = GAIN_TWO; + break; + case 3: // G_4X + ads_gain = GAIN_FOUR; + break; + case 4: // G_8X + ads_gain = GAIN_EIGHT; + break; + case 5: // G_16X + ads_gain = GAIN_SIXTEEN; + break; + case 9: // G_2_3X + ads_gain = GAIN_TWOTHIRDS; + break; + default: + ads_gain = GAIN_ONE; // default to 1x gain + break; + } + _ads.setGain(ads_gain); + return true; + } + +private: + Adafruit_ADS1015 _ads; ///< Adafruit ADS1015 driver instance +}; + +#endif // WS_EXPANDER_DRV_ADS1015_H diff --git a/src/components/expander/drivers/drv_ads1115.h b/src/components/expander/drivers/drv_ads1115.h new file mode 100644 index 000000000..80fc3a8a3 --- /dev/null +++ b/src/components/expander/drivers/drv_ads1115.h @@ -0,0 +1,100 @@ +/*! + * @file src/components/expander/drivers/drv_ads1115.h + * + * Header-only expander driver for the ADS1115 4-channel, 16-bit, ADC. + * Wraps the Adafruit_ADS1X15 library. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2026 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_EXPANDER_DRV_ADS1115_H +#define WS_EXPANDER_DRV_ADS1115_H +#include "../hardware.h" +#include + +#define ADS1115_RESOLUTION 16 ///< ADC resolution, in bits + +/*! + @brief Expander driver for the ADS1115 4-channel, 16-bit ADC. +*/ +class ExpanderADS1115 : public ExpanderHardware { +public: + /*! + * @brief Constructor for the ADS1115 expander driver. + */ + ExpanderADS1115() {} + + /*! + * @brief Destructor for the ADS1115 expander driver. + */ + ~ExpanderADS1115() {} + + uint8_t getAdcResolution() override { return ADS1115_RESOLUTION; } + + /*! + * @brief Initializes the ADS1115 driver with the given I2C address and bus. + * @param i2c_addr The I2C address of the expander. + * @param wire The TwoWire I2C bus to use for communication. + * @return True if initialization was successful, False otherwise. + */ + bool begin(uint8_t i2c_addr, TwoWire *wire) override { + _i2c_addr = i2c_addr; + return _ads.begin(i2c_addr, wire); + } + + /*! + * @brief Reads the raw ADC value from the specified pin. + * @param pin The pin number to read from (0-3 for ADS1115). + * @return The raw ADC value read from the pin. + */ + uint16_t analogRead(uint8_t pin) override { + return _ads.readADC_SingleEnded(pin); + } + + /*! + * @brief Sets the gain for the ADC readings. + * @param gain The gain setting from ws_analogin_Gain. + * Values: 1=1x, 2=2x, 3=4x, 4=8x, 5=16x, 9=2/3x. + * Unsupported gains (32x, 64x, 128x) fall back to 1x. + * @return True if the gain was successfully set, False otherwise. + */ + bool setGain(uint8_t gain) override { + adsGain_t ads_gain; + switch (gain) { + case 1: // G_1X + ads_gain = GAIN_ONE; + break; + case 2: // G_2X + ads_gain = GAIN_TWO; + break; + case 3: // G_4X + ads_gain = GAIN_FOUR; + break; + case 4: // G_8X + ads_gain = GAIN_EIGHT; + break; + case 5: // G_16X + ads_gain = GAIN_SIXTEEN; + break; + case 9: // G_2_3X + ads_gain = GAIN_TWOTHIRDS; + break; + default: + ads_gain = GAIN_ONE; // default to 1x gain + break; + } + _ads.setGain(ads_gain); + return true; + } + +private: + Adafruit_ADS1115 _ads; ///< Adafruit ADS1115 driver instance +}; + +#endif // WS_EXPANDER_DRV_ADS1115_H diff --git a/src/components/expander/drivers/drv_aw9523.h b/src/components/expander/drivers/drv_aw9523.h new file mode 100644 index 000000000..267209802 --- /dev/null +++ b/src/components/expander/drivers/drv_aw9523.h @@ -0,0 +1,74 @@ +/*! + * @file src/components/expander/drivers/drv_aw9523.h + * + * Header-only expander driver for the AW9523 16-pin I/O expander. + * Wraps the Adafruit_AW9523 library. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2026 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_EXPANDER_DRV_AW9523_H +#define WS_EXPANDER_DRV_AW9523_H +#include "../hardware.h" +#include + +/*! + @brief Expander driver for the AW9523 (16 GPIO pins, I2C). +*/ +class ExpanderAW9523 : public ExpanderHardware { +public: + /*! + * @brief Constructor for the AW9523 expander driver. + */ + ExpanderAW9523() {} + + /*! + * @brief Destructor for the AW9523 expander driver. + */ + ~ExpanderAW9523() {} + + /*! + * @brief Initializes the AW9523 driver with the given I2C address and bus. + * @param i2c_addr The I2C address of the expander. + * @param wire The TwoWire I2C bus to use for communication. + * @return True if initialization was successful, False otherwise. + */ + bool begin(uint8_t i2c_addr, TwoWire *wire) override { + _i2c_addr = i2c_addr; + return _aw.begin(i2c_addr, wire); + } + + /*! + * @brief Sets the mode of a specific pin on the AW9523. + * @param pin The pin number to configure. + * @param mode The mode to set (INPUT, OUTPUT, etc.). + */ + void pinMode(uint8_t pin, uint8_t mode) override { _aw.pinMode(pin, mode); } + + /*! + * @brief Writes a digital value to a specific pin on the AW9523. + * @param pin The pin number to write to. + * @param value The value to write (HIGH or LOW). + */ + void digitalWrite(uint8_t pin, uint8_t value) override { + _aw.digitalWrite(pin, value); + } + + /*! + * @brief Reads a digital value from a specific pin on the AW9523. + * @param pin The pin number to read from. + * @return The digital value read (HIGH or LOW). + */ + uint8_t digitalRead(uint8_t pin) override { return _aw.digitalRead(pin); } + +private: + Adafruit_AW9523 _aw; ///< Adafruit AW9523 driver instance +}; + +#endif // WS_EXPANDER_DRV_AW9523_H diff --git a/src/components/expander/drivers/drv_mcp23x08.h b/src/components/expander/drivers/drv_mcp23x08.h new file mode 100644 index 000000000..ef135d134 --- /dev/null +++ b/src/components/expander/drivers/drv_mcp23x08.h @@ -0,0 +1,74 @@ +/*! + * @file src/components/expander/drivers/drv_mcp23x08.h + * + * Header-only expander driver for the MCP23X08 8-pin I/O expander. + * Wraps the Adafruit_MCP23X08 library. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2026 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_EXPANDER_DRV_MCP23X08_H +#define WS_EXPANDER_DRV_MCP23X08_H +#include "../hardware.h" +#include + +/*! + @brief Expander driver for the MCP23X08 (8 GPIO pins, I2C). +*/ +class ExpanderMCP23X08 : public ExpanderHardware { +public: + /*! + * @brief Constructor for the MCP23X08 expander driver. + */ + ExpanderMCP23X08() {} + + /*! + * @brief Destructor for the MCP23X08 expander driver. + */ + ~ExpanderMCP23X08() {} + + /*! + * @brief Initializes the MCP23X08 driver with the given I2C address and bus. + * @param i2c_addr The I2C address of the expander. + * @param wire The TwoWire I2C bus to use for communication. + * @return True if initialization was successful, False otherwise. + */ + bool begin(uint8_t i2c_addr, TwoWire *wire) override { + _i2c_addr = i2c_addr; + return _mcp.begin_I2C(i2c_addr, wire); + } + + /*! + * @brief Sets the mode of a specific pin on the MCP23X08. + * @param pin The pin number to configure. + * @param mode The mode to set (INPUT, OUTPUT, etc.). + */ + void pinMode(uint8_t pin, uint8_t mode) override { _mcp.pinMode(pin, mode); } + + /*! + * @brief Writes a digital value to a specific pin on the MCP23X08. + * @param pin The pin number to write to. + * @param value The value to write (HIGH or LOW). + */ + void digitalWrite(uint8_t pin, uint8_t value) override { + _mcp.digitalWrite(pin, value); + } + + /*! + * @brief Reads a digital value from a specific pin on the MCP23X08. + * @param pin The pin number to read from. + * @return The digital value read (HIGH or LOW). + */ + uint8_t digitalRead(uint8_t pin) override { return _mcp.digitalRead(pin); } + +private: + Adafruit_MCP23X08 _mcp; ///< Adafruit MCP23X08 driver instance +}; + +#endif // WS_EXPANDER_DRV_MCP23X08_H diff --git a/src/components/expander/drivers/drv_mcp23x17.h b/src/components/expander/drivers/drv_mcp23x17.h new file mode 100644 index 000000000..bc8b8e154 --- /dev/null +++ b/src/components/expander/drivers/drv_mcp23x17.h @@ -0,0 +1,74 @@ +/*! + * @file src/components/expander/drivers/drv_mcp23x17.h + * + * Header-only expander driver for the MCP23X17 16-pin I/O expander. + * Wraps the Adafruit_MCP23X17 library. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2026 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_EXPANDER_DRV_MCP23X17_H +#define WS_EXPANDER_DRV_MCP23X17_H +#include "../hardware.h" +#include + +/*! + @brief Expander driver for the MCP23X17 (16 GPIO pins, I2C). +*/ +class ExpanderMCP23X17 : public ExpanderHardware { +public: + /*! + * @brief Constructor for the MCP23X17 expander driver. + */ + ExpanderMCP23X17() {} + + /*! + * @brief Destructor for the MCP23X17 expander driver. + */ + ~ExpanderMCP23X17() {} + + /*! + * @brief Initializes the MCP23X17 driver with the given I2C address and bus. + * @param i2c_addr The I2C address of the expander. + * @param wire The TwoWire I2C bus to use for communication. + * @return True if initialization was successful, False otherwise. + */ + bool begin(uint8_t i2c_addr, TwoWire *wire) override { + _i2c_addr = i2c_addr; + return _mcp.begin_I2C(i2c_addr, wire); + } + + /*! + * @brief Sets the mode of a specific pin on the MCP23X17. + * @param pin The pin number to configure. + * @param mode The mode to set (INPUT, OUTPUT, etc.). + */ + void pinMode(uint8_t pin, uint8_t mode) override { _mcp.pinMode(pin, mode); } + + /*! + * @brief Writes a digital value to a specific pin on the MCP23X17. + * @param pin The pin number to write to. + * @param value The value to write (HIGH or LOW). + */ + void digitalWrite(uint8_t pin, uint8_t value) override { + _mcp.digitalWrite(pin, value); + } + + /*! + * @brief Reads a digital value from a specific pin on the MCP23X17. + * @param pin The pin number to read from. + * @return The digital value read (HIGH or LOW). + */ + uint8_t digitalRead(uint8_t pin) override { return _mcp.digitalRead(pin); } + +private: + Adafruit_MCP23X17 _mcp; ///< Adafruit MCP23X17 driver instance +}; + +#endif // WS_EXPANDER_DRV_MCP23X17_H diff --git a/src/components/expander/drivers/drv_pcf8574.h b/src/components/expander/drivers/drv_pcf8574.h new file mode 100644 index 000000000..23212323c --- /dev/null +++ b/src/components/expander/drivers/drv_pcf8574.h @@ -0,0 +1,74 @@ +/*! + * @file src/components/expander/drivers/drv_pcf8574.h + * + * Header-only expander driver for the PCF8574 8-pin I/O expander. + * Wraps the Adafruit_PCF8574 library. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2026 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_EXPANDER_DRV_PCF8574_H +#define WS_EXPANDER_DRV_PCF8574_H +#include "../hardware.h" +#include + +/*! + @brief Expander driver for the PCF8574 (8 GPIO pins, I2C). +*/ +class ExpanderPCF8574 : public ExpanderHardware { +public: + /*! + * @brief Constructor for the PCF8574 expander driver. + */ + ExpanderPCF8574() {} + + /*! + * @brief Destructor for the PCF8574 expander driver. + */ + ~ExpanderPCF8574() {} + + /*! + * @brief Initializes the PCF8574 driver with the given I2C address and bus. + * @param i2c_addr The I2C address of the expander. + * @param wire The TwoWire I2C bus to use for communication. + * @return True if initialization was successful, False otherwise. + */ + bool begin(uint8_t i2c_addr, TwoWire *wire) override { + _i2c_addr = i2c_addr; + return _pcf.begin(i2c_addr, wire); + } + + /*! + * @brief Sets the mode of a specific pin on the PCF8574. + * @param pin The pin number to configure. + * @param mode The mode to set (INPUT, OUTPUT, etc.). + */ + void pinMode(uint8_t pin, uint8_t mode) override { _pcf.pinMode(pin, mode); } + + /*! + * @brief Writes a digital value to a specific pin on the PCF8574. + * @param pin The pin number to write to. + * @param value The value to write (HIGH or LOW). + */ + void digitalWrite(uint8_t pin, uint8_t value) override { + _pcf.digitalWrite(pin, value); + } + + /*! + * @brief Reads a digital value from a specific pin on the PCF8574. + * @param pin The pin number to read from. + * @return The digital value read (HIGH or LOW). + */ + uint8_t digitalRead(uint8_t pin) override { return _pcf.digitalRead(pin); } + +private: + Adafruit_PCF8574 _pcf; ///< Adafruit PCF8574 driver instance +}; + +#endif // WS_EXPANDER_DRV_PCF8574_H diff --git a/src/components/expander/drivers/drv_pcf8575.h b/src/components/expander/drivers/drv_pcf8575.h new file mode 100644 index 000000000..8af21ad8e --- /dev/null +++ b/src/components/expander/drivers/drv_pcf8575.h @@ -0,0 +1,74 @@ +/*! + * @file src/components/expander/drivers/drv_pcf8575.h + * + * Header-only expander driver for the PCF8575 16-pin I/O expander. + * Wraps the Adafruit_PCF8575 library. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2026 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_EXPANDER_DRV_PCF8575_H +#define WS_EXPANDER_DRV_PCF8575_H +#include "../hardware.h" +#include + +/*! + @brief Expander driver for the PCF8575 (16 GPIO pins, I2C). +*/ +class ExpanderPCF8575 : public ExpanderHardware { +public: + /*! + * @brief Constructor for the PCF8575 expander driver. + */ + ExpanderPCF8575() {} + + /*! + * @brief Destructor for the PCF8575 expander driver. + */ + ~ExpanderPCF8575() {} + + /*! + * @brief Initializes the PCF8575 driver with the given I2C address and bus. + * @param i2c_addr The I2C address of the expander. + * @param wire The TwoWire I2C bus to use for communication. + * @return True if initialization was successful, False otherwise. + */ + bool begin(uint8_t i2c_addr, TwoWire *wire) override { + _i2c_addr = i2c_addr; + return _pcf.begin(i2c_addr, wire); + } + + /*! + * @brief Sets the mode of a specific pin on the PCF8575. + * @param pin The pin number to configure. + * @param mode The mode to set (INPUT, OUTPUT, etc.). + */ + void pinMode(uint8_t pin, uint8_t mode) override { _pcf.pinMode(pin, mode); } + + /*! + * @brief Writes a digital value to a specific pin on the PCF8575. + * @param pin The pin number to write to. + * @param value The value to write (HIGH or LOW). + */ + void digitalWrite(uint8_t pin, uint8_t value) override { + _pcf.digitalWrite(pin, value); + } + + /*! + * @brief Reads a digital value from a specific pin on the PCF8575. + * @param pin The pin number to read from. + * @return The digital value read (HIGH or LOW). + */ + uint8_t digitalRead(uint8_t pin) override { return _pcf.digitalRead(pin); } + +private: + Adafruit_PCF8575 _pcf; ///< Adafruit PCF8575 driver instance +}; + +#endif // WS_EXPANDER_DRV_PCF8575_H diff --git a/src/components/expander/drivers/drv_seesaw.h b/src/components/expander/drivers/drv_seesaw.h new file mode 100644 index 000000000..b722843b7 --- /dev/null +++ b/src/components/expander/drivers/drv_seesaw.h @@ -0,0 +1,101 @@ +/*! + * @file src/components/expander/drivers/drv_seesaw.h + * + * Header-only expander driver for the Adafruit Seesaw expander. + * Wraps the Adafruit_Seesaw library. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2026 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_EXPANDER_DRV_SEESAW_H +#define WS_EXPANDER_DRV_SEESAW_H +#include "../hardware.h" +#include + +#define DEFAULT_SEESAW_ADC_RESOLUTION \ + 10 ///< Default ADC resolution for the Seesaw, in bits + +/*! + @brief Expander driver for the Adafruit Seesaw (various GPIO pins, I2C). +*/ +class ExpanderSeesaw : public ExpanderHardware { +public: + /*! + * @brief Constructor for the Seesaw expander driver. + */ + ExpanderSeesaw() { _resolution = DEFAULT_SEESAW_ADC_RESOLUTION; } + + /*! + * @brief Destructor for the Seesaw expander driver. + */ + ~ExpanderSeesaw() {} + + /*! + * @brief Initializes the Seesaw driver with the given I2C address and bus. + * @param i2c_addr The I2C address of the expander. + * @param wire The TwoWire I2C bus to use for communication. + * @return True if initialization was successful, False otherwise. + */ + bool begin(uint8_t i2c_addr, TwoWire *wire) override { + _i2c_addr = i2c_addr; + _ss = Adafruit_seesaw(wire); + return _ss.begin(i2c_addr); + } + + /*! + * @brief Sets the mode of a specific pin on the Seesaw. + * @param pin The pin number to configure. + * @param mode The mode to set (INPUT, OUTPUT, etc.). + */ + void pinMode(uint8_t pin, uint8_t mode) override { _ss.pinMode(pin, mode); } + + /*! + * @brief Writes a digital value to a specific pin on the Seesaw. + * @param pin The pin number to write to. + * @param value The value to write (HIGH or LOW). + */ + void digitalWrite(uint8_t pin, uint8_t value) override { + _ss.digitalWrite(pin, value); + } + + /*! + * @brief Reads a digital value from a specific pin on the TCA8418. + * @param pin The pin number to read from. + * @return The digital value read (HIGH or LOW). + */ + uint8_t digitalRead(uint8_t pin) override { return _ss.digitalRead(pin); } + + /*! + * @brief Gets the ADC raw value for a given pin/ADC channel. + * @param pin GPIO pin to read analog value + * @return Analog raw value (non-calibrated). + */ + uint16_t analogRead(uint8_t pin) override { return _ss.analogRead(pin); } + + /*! + * @brief Gets the resolution of the seesaw ADC. + * @return ADC resolution, in bits. + */ + uint8_t getAdcResolution() override { return _resolution; } + + /*! + * @brief Writes an analog value (PWM waveform) to a pin on the Seesaw. + * @param pin The pin number to write to. + * @param value The duty cycle value to write. + */ + void analogWrite(uint8_t pin, uint16_t value) override { + _ss.analogWrite(pin, value); + } + +private: + Adafruit_seesaw _ss; ///< Adafruit Seesaw driver instance + uint8_t _resolution; +}; + +#endif // WS_EXPANDER_DRV_SEESAW_H diff --git a/src/components/expander/drivers/drv_tca8418.h b/src/components/expander/drivers/drv_tca8418.h new file mode 100644 index 000000000..da8059982 --- /dev/null +++ b/src/components/expander/drivers/drv_tca8418.h @@ -0,0 +1,74 @@ +/*! + * @file src/components/expander/drivers/drv_tca8418.h + * + * Header-only expander driver for the TCA8418 16-pin I/O expander. + * Wraps the Adafruit_TCA8418 library. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2026 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_EXPANDER_DRV_TCA8418_H +#define WS_EXPANDER_DRV_TCA8418_H +#include "../hardware.h" +#include + +/*! + @brief Expander driver for the TCA8418 (16 GPIO pins, I2C). +*/ +class ExpanderTCA8418 : public ExpanderHardware { +public: + /*! + * @brief Constructor for the TCA8418 expander driver. + */ + ExpanderTCA8418() {} + + /*! + * @brief Destructor for the TCA8418 expander driver. + */ + ~ExpanderTCA8418() {} + + /*! + * @brief Initializes the TCA8418 driver with the given I2C address and bus. + * @param i2c_addr The I2C address of the expander. + * @param wire The TwoWire I2C bus to use for communication. + * @return True if initialization was successful, False otherwise. + */ + bool begin(uint8_t i2c_addr, TwoWire *wire) override { + _i2c_addr = i2c_addr; + return _tca.begin(i2c_addr, wire); + } + + /*! + * @brief Sets the mode of a specific pin on the TCA8418. + * @param pin The pin number to configure. + * @param mode The mode to set (INPUT, OUTPUT, etc.). + */ + void pinMode(uint8_t pin, uint8_t mode) override { _tca.pinMode(pin, mode); } + + /*! + * @brief Writes a digital value to a specific pin on the TCA8418. + * @param pin The pin number to write to. + * @param value The value to write (HIGH or LOW). + */ + void digitalWrite(uint8_t pin, uint8_t value) override { + _tca.digitalWrite(pin, value); + } + + /*! + * @brief Reads a digital value from a specific pin on the TCA8418. + * @param pin The pin number to read from. + * @return The digital value read (HIGH or LOW). + */ + uint8_t digitalRead(uint8_t pin) override { return _tca.digitalRead(pin); } + +private: + Adafruit_TCA8418 _tca; ///< Adafruit TCA8418 driver instance +}; + +#endif // WS_EXPANDER_DRV_TCA8418_H diff --git a/src/components/expander/hardware.h b/src/components/expander/hardware.h new file mode 100644 index 000000000..4f5e50560 --- /dev/null +++ b/src/components/expander/hardware.h @@ -0,0 +1,108 @@ +/*! + * @file src/components/expander/hardware.h + * + * Base class hardware abstraction for WipperSnapper's I/O expander + * component. Mirrors the Arduino/Wiring GPIO API so that digitalIO + * can treat expander pins the same as native pins. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2026 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_EXPANDER_HARDWARE_H +#define WS_EXPANDER_HARDWARE_H +#include +#include + +/*! + @brief Base class for I/O expander hardware drivers. + Provides a virtual Arduino/Wiring-style GPIO interface + so higher layers don't need to know the chip. +*/ +class ExpanderHardware { +public: + virtual ~ExpanderHardware() {} + + /*! @brief Initializes the expander hardware. + @param i2c_addr The I2C address of the expander. + @param wire Pointer to the TwoWire I2C bus instance. + @return True if initialization succeeded, false otherwise. */ + virtual bool begin(uint8_t i2c_addr, TwoWire *wire) = 0; + + /*! @brief Returns the I2C address of the expander. + @return The I2C address. */ + uint8_t getAddress() const { return _i2c_addr; } + + /*! @brief Parses pin number from a pin name string. Handles both + native ("A0", "D5") and expander ("EXP_0x48_0") formats. + @param pin_name The pin name string. + @param pin_num Output: the parsed pin number. + @return True if parsed successfully, false if malformed. */ + static bool ParsePinNum(const char *pin_name, uint8_t &pin_num) { + if (strncmp(pin_name, "EXP_", 4) == 0) { + const char *pin_str = strchr(pin_name + 4, '_'); + if (!pin_str) + return false; + pin_num = atoi(pin_str + 1); + } else { + pin_num = atoi(pin_name + 1); + } + return true; + } + + /*! @brief Formats an expander pin name into a buffer. + Inverse of ParsePinNum — builds "EXP_0xNN_P" from address and + pin. + @param buf Output buffer (must be >= 16 bytes). + @param buf_size Size of the output buffer. + @param addr The I2C address of the expander. + @param pin_num The pin number on the expander. */ + static void FormatPinName(char *buf, size_t buf_size, uint8_t addr, + uint8_t pin_num) { + snprintf(buf, buf_size, "EXP_0x%02x_%d", addr, pin_num); + } + + /*! @brief Sets the mode of a pin on the expander. + @param pin The pin number. + @param mode The mode (INPUT, OUTPUT, etc.). */ + virtual void pinMode(uint8_t pin, uint8_t mode) {}; + + /*! @brief Writes a digital value to a pin on the expander. + @param pin The pin number. + @param value HIGH or LOW. */ + virtual void digitalWrite(uint8_t pin, uint8_t value) {}; + + /*! @brief Reads the digital value of a pin on the expander. + @param pin The pin number. + @return HIGH or LOW. */ + virtual uint8_t digitalRead(uint8_t pin) { return 0; } + + /*! @brief Reads the analog value of a pin on the expander. + @param pin The pin number. + @return The raw ADC value. */ + virtual uint16_t analogRead(uint8_t pin) { return 0; } + + /*! @brief Returns the ADC resolution of the expander in bits. + @return The ADC resolution. */ + virtual uint8_t getAdcResolution() { return 0; } + + /*! @brief Sets the ADC gain of the expander. + @param gain The gain value. + @return True if the gain was set successfully. */ + virtual bool setGain(uint8_t gain) { return false; } + + /*! @brief Writes an analog (PWM) value to a pin on the expander. + @param pin The pin number. + @param value The PWM value. */ + virtual void analogWrite(uint8_t pin, uint16_t value) {}; + +protected: + uint8_t _i2c_addr = 0; ///< I2C address of the expander +}; + +#endif // WS_EXPANDER_HARDWARE_H diff --git a/src/components/expander/model.cpp b/src/components/expander/model.cpp new file mode 100644 index 000000000..9ddd5d926 --- /dev/null +++ b/src/components/expander/model.cpp @@ -0,0 +1,47 @@ +/*! + * @file src/components/expander/model.cpp + * + * Model interface for the expander.proto message. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2026 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#include "model.h" + +ExpanderModel::ExpanderModel() {} + +ExpanderModel::~ExpanderModel() {} + +/*! + * @brief Wraps an Added response in a D2B envelope for publishing. + * @param response The I2C DeviceAddedOrReplaced response. + * @returns Pointer to the ws_expander_D2B message. + */ +ws_expander_D2B * +ExpanderModel::GetAddedD2B(const ws_i2c_DeviceAddedOrReplaced &response) { + memset(&_msg_d2b, 0, sizeof(_msg_d2b)); + _msg_d2b.which_payload = ws_expander_D2B_added_tag; + _msg_d2b.payload.added.has_response = true; + _msg_d2b.payload.added.response = response; + return &_msg_d2b; +} + +/*! + * @brief Wraps a Removed response in a D2B envelope for publishing. + * @param response The I2C DeviceRemoved response. + * @returns Pointer to the ws_expander_D2B message. + */ +ws_expander_D2B * +ExpanderModel::GetRemovedD2B(const ws_i2c_DeviceRemoved &response) { + memset(&_msg_d2b, 0, sizeof(_msg_d2b)); + _msg_d2b.which_payload = ws_expander_D2B_removed_tag; + _msg_d2b.payload.removed.has_response = true; + _msg_d2b.payload.removed.response = response; + return &_msg_d2b; +} diff --git a/src/components/expander/model.h b/src/components/expander/model.h new file mode 100644 index 000000000..1c1bf1b7f --- /dev/null +++ b/src/components/expander/model.h @@ -0,0 +1,34 @@ +/*! + * @file src/components/expander/model.h + * + * Model interface for the expander.proto message. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2026 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_EXPANDER_MODEL_H +#define WS_EXPANDER_MODEL_H +#include "wippersnapper.h" +#include + +/*! + @brief Provides an interface for creating and encoding + D2B messages from expander.proto. +*/ +class ExpanderModel { +public: + ExpanderModel(); + ~ExpanderModel(); + ws_expander_D2B *GetAddedD2B(const ws_i2c_DeviceAddedOrReplaced &response); + ws_expander_D2B *GetRemovedD2B(const ws_i2c_DeviceRemoved &response); + +private: + ws_expander_D2B _msg_d2b; ///< D2B envelope for publishing +}; +#endif // WS_EXPANDER_MODEL_H diff --git a/src/components/gps/controller.cpp b/src/components/gps/controller.cpp index cfd5ce63e..7550f63ef 100644 --- a/src/components/gps/controller.cpp +++ b/src/components/gps/controller.cpp @@ -95,7 +95,8 @@ bool GPSController::Handle_GpsDeviceAddOrReplace( // I2C transport path WS_DEBUG_PRINTLN("[gps] Configuring GPS via I2C transport..."); ws_i2c_DeviceDescriptor desc = msg->add_i2c.device_description; - TwoWire *wire = Ws._i2c_controller->GetOrCreateI2cBus(desc.pin_scl, desc.pin_sda); + TwoWire *wire = + Ws._i2c_controller->GetOrCreateI2cBus(desc.pin_scl, desc.pin_sda); if (wire == nullptr) return false; did_add = AddGPS(wire, desc.device_address, &msg->config); diff --git a/src/components/gps/controller.h b/src/components/gps/controller.h index 1a77da7b1..0b2004280 100644 --- a/src/components/gps/controller.h +++ b/src/components/gps/controller.h @@ -15,10 +15,10 @@ */ #ifndef WS_GPS_CONTROLLER_H #define WS_GPS_CONTROLLER_H +#include "components/uart/hardware.h" #include "hardware.h" #include "model.h" #include "wippersnapper.h" -#include "components/uart/hardware.h" #include class wippersnapper; ///< Forward declaration diff --git a/src/components/gps/model.h b/src/components/gps/model.h index 14f279e55..e5a6a5c9c 100644 --- a/src/components/gps/model.h +++ b/src/components/gps/model.h @@ -51,6 +51,6 @@ class GPSModel { private: ws_gps_Config _msg_gps_config; ///< GPS configuration message ws_gps_Event _msg_gps_event; ///< GPS event message - ws_gps_D2B _msg_gps_d2b; ///< GPS D2B envelope for publishing + ws_gps_D2B _msg_gps_d2b; ///< GPS D2B envelope for publishing }; #endif // WS_GPS_MODEL_H \ No newline at end of file diff --git a/src/components/i2c/controller.cpp b/src/components/i2c/controller.cpp index a37295ebd..ca8a8f118 100644 --- a/src/components/i2c/controller.cpp +++ b/src/components/i2c/controller.cpp @@ -378,9 +378,7 @@ drvBase *CreateI2cSensorDrv(const char *driver_name, TwoWire *i2c, /*! @brief I2cController constructor */ -I2cController::I2cController() { - _i2c_model = new I2cModel(); -} +I2cController::I2cController() { _i2c_model = new I2cModel(); } /*! @brief I2cController destructor @@ -390,7 +388,6 @@ I2cController::~I2cController() { delete _i2c_model; } - /*! @brief Routes messages using the i2c.proto API to the appropriate controller functions. @@ -483,8 +480,7 @@ bool I2cController::IsBusStatusOK(I2cHardware *bus) { successfully, False otherwise. */ bool I2cController::publishDeviceAddedOrReplaced( - const ws_i2c_DeviceDescriptor &device_descriptor, - I2cHardware *bus, + const ws_i2c_DeviceDescriptor &device_descriptor, I2cHardware *bus, const ws_i2c_DeviceStatus &device_status) { // If we're in offline mode, don't publish out to IO if (Ws._sdCardV2->isModeOffline()) @@ -641,7 +637,8 @@ ws_i2c_DeviceStatus I2cController::InitMux(I2cHardware *bus, const char *name, @returns Pointer to the I2cHardware bus, or nullptr if initialization failed. */ -I2cHardware *I2cController::findOrCreateBus(uint32_t pin_scl, uint32_t pin_sda) { +I2cHardware *I2cController::findOrCreateBus(uint32_t pin_scl, + uint32_t pin_sda) { // Search existing buses for (I2cHardware *bus : _i2c_buses) { if (bus == nullptr) @@ -742,7 +739,6 @@ bool I2cController::Handle_I2cBusScan(ws_i2c_Scan *msg) { return true; } - /*! @brief Implements handling for a I2cDeviceAddOrReplace message @param msg @@ -759,17 +755,21 @@ bool I2cController::Handle_I2cDeviceAddOrReplace( strcpy(device_name, msg->device_name); ws_i2c_DeviceDescriptor device_descriptor = msg->device_description; - // Should we use the MUX for this device? We determine this based on if the mux_address field is set in the descriptor + // Should we use the MUX for this device? We determine this based on if the + // mux_address field is set in the descriptor bool use_mux = (device_descriptor.mux_address != 0x00); // Attempt to remove any existing driver at this address (or mux_channel). RemoveDriver(device_descriptor.device_address, device_descriptor.mux_channel); // Attempt to find or create the I2C bus specified by the device descriptor - I2cHardware *hw_bus = findOrCreateBus(device_descriptor.pin_scl, device_descriptor.pin_sda); + I2cHardware *hw_bus = + findOrCreateBus(device_descriptor.pin_scl, device_descriptor.pin_sda); if (hw_bus == nullptr) { - WS_DEBUG_PRINTLN("[i2c] ERROR: Failed to find or create I2C bus specified by device descriptor!"); - publishDeviceAddedOrReplaced(device_descriptor, nullptr, ws_i2c_DeviceStatus_DS_FAIL_INIT); + WS_DEBUG_PRINTLN("[i2c] ERROR: Failed to find or create I2C bus specified " + "by device descriptor!"); + publishDeviceAddedOrReplaced(device_descriptor, nullptr, + ws_i2c_DeviceStatus_DS_FAIL_INIT); return false; } @@ -781,7 +781,8 @@ bool I2cController::Handle_I2cDeviceAddOrReplace( false); // doesn't return, halts } // Publish back out to IO with error status from bus status check - if (!publishDeviceAddedOrReplaced(device_descriptor, hw_bus, device_status)) { + if (!publishDeviceAddedOrReplaced(device_descriptor, hw_bus, + device_status)) { WS_DEBUG_PRINTLN("[i2c] ERROR: Unable to publish message to IO!"); return false; } @@ -792,40 +793,48 @@ bool I2cController::Handle_I2cDeviceAddOrReplace( TwoWire *wire = hw_bus->GetBus(); // Check we are trying to add a new MUX - if so, initialize and return - ws_i2c_DeviceStatus mux_status = InitMux(hw_bus, device_name, device_descriptor.mux_address); + ws_i2c_DeviceStatus mux_status = + InitMux(hw_bus, device_name, device_descriptor.mux_address); if (mux_status == ws_i2c_DeviceStatus_DS_SUCCESS) { - // MUX initialized successfully, publish and back out since we don't need to create a driver for the MUX itself + // MUX initialized successfully, publish and back out since we don't need to + // create a driver for the MUX itself publishDeviceAddedOrReplaced(device_descriptor, hw_bus, mux_status); return true; } else if (mux_status != ws_i2c_DeviceStatus_DS_UNSPECIFIED) { - // MUX init failed, publish and back out since we can't use the MUX bus if it failed to initialize + // MUX init failed, publish and back out since we can't use the MUX bus if + // it failed to initialize publishDeviceAddedOrReplaced(device_descriptor, hw_bus, mux_status); Ws.haltErrorV2("[i2c] Failed to initialize MUX driver!", WS_LED_STATUS_ERROR_RUNTIME, false); } - // Check if we need to configure the MUX channel for this device - if so, configure it before creating the driver + // Check if we need to configure the MUX channel for this device - if so, + // configure it before creating the driver if (use_mux) { if (hw_bus->HasMux()) { - hw_bus->ClearMuxChannel(); // TODO: We may be able to remove this, test against IO first! + hw_bus->ClearMuxChannel(); // TODO: We may be able to remove this, test + // against IO first! hw_bus->SelectMuxChannel(device_descriptor.mux_channel); } else { - WS_DEBUG_PRINTLN("[i2c] ERROR: Expected a MUX on this bus but none found!"); - publishDeviceAddedOrReplaced(device_descriptor, hw_bus, ws_i2c_DeviceStatus_DS_FAIL_UNSUPPORTED_SENSOR); + WS_DEBUG_PRINTLN( + "[i2c] ERROR: Expected a MUX on this bus but none found!"); + publishDeviceAddedOrReplaced( + device_descriptor, hw_bus, + ws_i2c_DeviceStatus_DS_FAIL_UNSUPPORTED_SENSOR); return false; } } - // Attempt to initialize a new sensor driver WS_DEBUG_PRINTLN("[i2c] Creating sensor driver..."); - drvBase *drv = CreateI2cSensorDrv(device_name, wire, - device_descriptor.device_address, - device_descriptor.mux_channel, device_status); + drvBase *drv = + CreateI2cSensorDrv(device_name, wire, device_descriptor.device_address, + device_descriptor.mux_channel, device_status); if (drv == nullptr) { WS_DEBUG_PRINTLN("[i2c] ERROR: I2C driver failed to initialize!"); - publishDeviceAddedOrReplaced(device_descriptor, hw_bus, ws_i2c_DeviceStatus_DS_FAIL_INIT); + publishDeviceAddedOrReplaced(device_descriptor, hw_bus, + ws_i2c_DeviceStatus_DS_FAIL_INIT); return false; } @@ -846,7 +855,8 @@ bool I2cController::Handle_I2cDeviceAddOrReplace( // Attempt to communicate with the driver if (!drv->begin()) { WS_DEBUG_PRINTLN("[i2c] ERROR: Failed to initialize I2C driver!"); - publishDeviceAddedOrReplaced(device_descriptor, hw_bus, ws_i2c_DeviceStatus_DS_FAIL_INIT); + publishDeviceAddedOrReplaced(device_descriptor, hw_bus, + ws_i2c_DeviceStatus_DS_FAIL_INIT); delete drv; return false; } @@ -855,20 +865,22 @@ bool I2cController::Handle_I2cDeviceAddOrReplace( WS_DEBUG_PRINTLNVAR(device_name); _i2c_drivers.push_back(drv); - // If we're using a MUX, lets clear the channel for any subsequent bus operations that may not involve the MUX + // If we're using a MUX, lets clear the channel for any subsequent bus + // operations that may not involve the MUX if (use_mux) { hw_bus->ClearMuxChannel(); } // Publish success status back out to IO - WS_DEBUG_PRINT("[i2c] Successfully added/replaced device, publishing status to IO..."); - publishDeviceAddedOrReplaced(device_descriptor, hw_bus, ws_i2c_DeviceStatus_DS_SUCCESS); + WS_DEBUG_PRINT( + "[i2c] Successfully added/replaced device, publishing status to IO..."); + publishDeviceAddedOrReplaced(device_descriptor, hw_bus, + ws_i2c_DeviceStatus_DS_SUCCESS); WS_DEBUG_PRINTLN("OK!"); return true; } - /*! @brief Handles polling, reading, and logger for i2c devices attached to the I2C controller. @@ -981,7 +993,6 @@ void I2cController::ResetFlags() { } } - /*! @brief Finds or creates an I2C bus by SCL/SDA pins, validates it, and returns the underlying TwoWire instance. @@ -1029,9 +1040,7 @@ TwoWire *I2cController::GetI2cBus(uint32_t pin_scl, uint32_t pin_sda) { @brief Returns the number of I2C buses. @returns The number of I2C buses. */ -size_t I2cController::GetI2cBusCount() { - return _i2c_buses.size(); -} +size_t I2cController::GetI2cBusCount() { return _i2c_buses.size(); } /*! @brief Returns a pointer to the I2C bus by index. diff --git a/src/components/i2c/controller.h b/src/components/i2c/controller.h index 3d4cc960d..2264e2bbc 100644 --- a/src/components/i2c/controller.h +++ b/src/components/i2c/controller.h @@ -71,9 +71,9 @@ #include "drivers/drvVncl4020.h" #include "drivers/drvVncl4040.h" -class wippersnapper; ///< Forward declaration -class I2cModel; ///< Forward declaration -class I2cHardware; ///< Forward declaration +class wippersnapper; ///< Forward declaration +class I2cModel; ///< Forward declaration +class I2cHardware; ///< Forward declaration /*! @brief Routes messages using the i2c.proto API to the @@ -92,14 +92,15 @@ class I2cController { bool Handle_I2cBusScan(ws_i2c_Scan *msg); bool Handle_I2cDeviceRemove(ws_i2c_DeviceRemove *msg); // Publishing // - bool publishDeviceAddedOrReplaced( - const ws_i2c_DeviceDescriptor &device_descriptor, - I2cHardware *bus, - const ws_i2c_DeviceStatus &device_status); + bool + publishDeviceAddedOrReplaced(const ws_i2c_DeviceDescriptor &device_descriptor, + I2cHardware *bus, + const ws_i2c_DeviceStatus &device_status); bool publishScan(); // Helpers // TwoWire *GetOrCreateI2cBus(uint32_t pin_scl, uint32_t pin_sda); - ws_i2c_DeviceStatus InitMux(I2cHardware *bus, const char *name, uint32_t address); + ws_i2c_DeviceStatus InitMux(I2cHardware *bus, const char *name, + uint32_t address); bool RemoveDriver(uint32_t address, uint32_t mux_channel = WS_I2C_MUX_CHANNEL_ANY); size_t GetI2cBusCount(); @@ -109,9 +110,10 @@ class I2cController { I2cHardware *findOrCreateBus(uint32_t pin_scl, uint32_t pin_sda); bool IsBusStatusOK(I2cHardware *bus); TwoWire *GetI2cBus(uint32_t pin_scl, uint32_t pin_sda); - I2cModel *_i2c_model = nullptr; ///< Pointer to an I2C model object - std::vector _i2c_buses; ///< Vector of ptrs to I2C hardware buses - std::vector _i2c_drivers; ///< Vector of ptrs to I2C drivers + I2cModel *_i2c_model = nullptr; ///< Pointer to an I2C model object + std::vector + _i2c_buses; ///< Vector of ptrs to I2C hardware buses + std::vector _i2c_drivers; ///< Vector of ptrs to I2C drivers }; extern wippersnapper Ws; ///< Wippersnapper V2 instance #endif // WS_I2C_CONTROLLER_H \ No newline at end of file diff --git a/src/components/i2c/hardware.cpp b/src/components/i2c/hardware.cpp index 32d07ffab..3333165e3 100644 --- a/src/components/i2c/hardware.cpp +++ b/src/components/i2c/hardware.cpp @@ -16,6 +16,9 @@ /*! @brief Default I2C bus hardware class constructor + @param sda The SDA pin number. + @param scl The SCL pin number. + @param instance The I2C bus instance index. */ I2cHardware::I2cHardware(uint32_t sda, uint32_t scl, uint8_t instance) { _bus_status = ws_i2c_BusStatus_BS_UNSPECIFIED; @@ -66,7 +69,8 @@ void I2cHardware::TogglePowerPin() { /*! @brief Attempts to initialize the I2C bus @returns True if the bus was successfully initialized, False otherwise. - NOTE: If False, the bus' status can be retrieved with GetBusStatus() to provide the failure reason. + NOTE: If False, the bus' status can be retrieved with GetBusStatus() to + provide the failure reason. */ bool I2cHardware::begin() { // Some development boards define a pin that controls power @@ -107,7 +111,8 @@ bool I2cHardware::begin() { _bus->begin(_sda, _scl); _bus->setClock(50000); #elif defined(ARDUINO_ARCH_RP2040) - // arduino-pico uses two global instances for TwoWire, select based on instance number + // arduino-pico uses two global instances for TwoWire, select based on + // instance number if (_instance == 0) { _bus = &Wire; } else if (_instance == 1) { diff --git a/src/components/i2c/hardware.h b/src/components/i2c/hardware.h index f56487d6d..50e749be8 100644 --- a/src/components/i2c/hardware.h +++ b/src/components/i2c/hardware.h @@ -35,7 +35,11 @@ class I2cHardware { bool begin(); bool ScanBus(ws_i2c_Scanned *scan_results); TwoWire *GetBus(); + /*! @brief Returns the SDA pin number. + @return The SDA pin number. */ uint8_t getSDA() { return _sda; } + /*! @brief Returns the SCL pin number. + @return The SCL pin number. */ uint8_t getSCL() { return _scl; } ws_i2c_BusStatus GetBusStatus(); void TogglePowerPin(); @@ -48,12 +52,13 @@ class I2cHardware { bool ScanMux(ws_i2c_Scanned *scan_results); private: - TwoWire *_bus = nullptr; ///< I2C bus instance - ws_i2c_BusStatus _bus_status; ///< I2C bus status - uint8_t _sda; ///< SDA pin - uint8_t _scl; ///< SCL pin - uint8_t _instance; ///< I2C bus instance number (for hardware with multiple I2C buses) - bool _has_mux; ///< Is a MUX present on the bus? + TwoWire *_bus = nullptr; ///< I2C bus instance + ws_i2c_BusStatus _bus_status; ///< I2C bus status + uint8_t _sda; ///< SDA pin + uint8_t _scl; ///< SCL pin + uint8_t _instance; ///< I2C bus instance number (for hardware with multiple + ///< I2C buses) + bool _has_mux; ///< Is a MUX present on the bus? uint32_t _mux_address_register; ///< I2C address for the MUX int _mux_max_channels; ///< Maximum possible number of MUX channels }; diff --git a/src/components/i2c/model.cpp b/src/components/i2c/model.cpp index 78e082812..23fa9ec4e 100644 --- a/src/components/i2c/model.cpp +++ b/src/components/i2c/model.cpp @@ -274,7 +274,6 @@ ws_i2c_DeviceAddOrReplace *I2cModel::GetI2cDeviceAddOrReplaceMsg() { return &_msg_i2c_device_add_replace; } - /*! @brief Encodes a I2cDeviceAddedOrReplaced message. @param device_descriptor diff --git a/src/components/i2c/model.h b/src/components/i2c/model.h index 32ae3d79e..f16d691ac 100644 --- a/src/components/i2c/model.h +++ b/src/components/i2c/model.h @@ -19,7 +19,8 @@ #include #include -#define MAX_DEVICE_EVENTS 16 ///< Maximum number of SensorEvents within I2cDeviceEvent +#define MAX_DEVICE_EVENTS \ + 16 ///< Maximum number of SensorEvents within I2cDeviceEvent #define MAX_I2C_SCAN_DEVICES 16 ///< Maximum number of devices found on the bus /*! @@ -34,6 +35,9 @@ class I2cModel { bool DecodeI2cDeviceAddReplace(pb_istream_t *stream); bool DecodeI2cDeviceRemove(pb_istream_t *stream); bool DecodeI2cBusScan(pb_istream_t *stream); + /*! @brief Decodes an I2C device output write message from a stream. + @param stream Pointer to the nanopb input stream. + @return True if decoding succeeded, false otherwise. */ bool DecodeI2cDeviceOutputWrite(pb_istream_t *stream); // Encoders bool encodeMsgI2cDeviceAddedorReplaced( @@ -43,6 +47,8 @@ class I2cModel { // Getters ws_i2c_DeviceRemove *GetI2cDeviceRemoveMsg(); ws_i2c_DeviceAddOrReplace *GetI2cDeviceAddOrReplaceMsg(); + /*! @brief Returns a pointer to the I2C output add message. + @return Pointer to the ws_i2c_output_Add message. */ ws_i2c_output_Add *GetI2cOutputAddMsg(); ws_i2c_DeviceAddedOrReplaced *GetMsgI2cDeviceAddedOrReplaced(); ws_i2c_DeviceEvent *GetI2cDeviceEvent(); @@ -58,8 +64,7 @@ class I2cModel { ws_i2c_D2B *GetI2cD2B(); // DeviceEvent Message API void ClearI2cDeviceEvent(); - void SetI2cDeviceEventDeviceDescripton(uint32_t pin_scl, - uint32_t pin_sda, + void SetI2cDeviceEventDeviceDescripton(uint32_t pin_scl, uint32_t pin_sda, uint32_t addr_device, uint32_t addr_mux, uint32_t mux_channel); @@ -86,10 +91,20 @@ class I2cOutputModel { I2cOutputModel(); ~I2cOutputModel(); // Decoders + /*! @brief Decodes a LED backpack write message from a stream. + @param stream Pointer to the nanopb input stream. + @return True if decoding succeeded, false otherwise. */ bool DecodeLedBackpackWrite(pb_istream_t *stream); + /*! @brief Decodes a character LCD write message from a stream. + @param stream Pointer to the nanopb input stream. + @return True if decoding succeeded, false otherwise. */ bool DecodeCharLCDWrite(pb_istream_t *stream); // Getters + /*! @brief Returns a pointer to the LED backpack write message. + @return Pointer to the ws_i2c_output_LedBackpackWrite message. */ ws_i2c_output_LedBackpackWrite *GetLedBackpackWriteMsg(); + /*! @brief Returns a pointer to the character LCD write message. + @return Pointer to the ws_i2c_output_CharLCDWrite message. */ ws_i2c_output_CharLCDWrite *GetCharLCDWriteMsg(); private: diff --git a/src/components/pwm/controller.cpp b/src/components/pwm/controller.cpp index 3c176434b..e0419564c 100644 --- a/src/components/pwm/controller.cpp +++ b/src/components/pwm/controller.cpp @@ -7,25 +7,27 @@ * please support Adafruit and open-source hardware by purchasing * products from Adafruit! * - * Copyright (c) Brent Rubell 2025 for Adafruit Industries. + * Copyright (c) Brent Rubell 2025-2026 for Adafruit Industries. * * BSD license, all text here must be included in any redistribution. * */ #include "controller.h" +#include "../expander/controller.h" /*! @brief Ctor for PWMController. */ -PWMController::PWMController() { - _pwm_model = new PWMModel(); - _active_pwm_pins = 0; -} +PWMController::PWMController() { _pwm_model = new PWMModel(); } /*! @brief Dtor for PWMController. */ -PWMController::~PWMController() { delete _pwm_model; } +PWMController::~PWMController() { + for (size_t i = 0; i < _pins.size(); i++) + delete _pins[i]; + delete _pwm_model; +} /*! @brief Routes messages using the pwm.proto API to the @@ -69,32 +71,54 @@ bool PWMController::Router(pb_istream_t *stream) { @return True if the message was handled successfully, false otherwise. */ bool PWMController::Handle_PWM_Add(ws_pwm_Add *msg) { - bool did_attach; - uint8_t pin = atoi(msg->pin + 1); - _pwm_hardware[_active_pwm_pins] = new PWMHardware(); + uint8_t pin = 0; + if (!ExpanderHardware::ParsePinNum(msg->pin, pin)) { + WS_DEBUG_PRINTLN("[pwm] ERROR: Malformed expander pin name!"); + return false; + } - WS_DEBUG_PRINT("[pwm] Attaching pin: "); - WS_DEBUG_PRINTVAR(msg->pin); - did_attach = _pwm_hardware[_active_pwm_pins]->AttachPin( - pin, (uint32_t)msg->frequency, (uint32_t)msg->resolution); + // Resolve the expander driver if this is an expander pin + ExpanderHardware *expander_drv = nullptr; + if (strncmp(msg->pin, "EXP_", 4) == 0) { + uint8_t i2c_addr = (uint8_t)strtoul(msg->pin + 4, nullptr, 16); + expander_drv = Ws._expander_controller->GetDriver(i2c_addr); + if (!expander_drv) { + WS_DEBUG_PRINTLN("[pwm] ERROR: Expander not found for address!"); + return false; + } + } + + // If pin already exists, remove it before re-adding (for updates) + if (GetPin(pin, expander_drv) != nullptr) { + RemovePin(pin, expander_drv); + } + + PWMHardware *new_pin = new PWMHardware(); + bool did_attach = new_pin->attach(pin, (uint32_t)msg->frequency, + (uint32_t)msg->resolution, expander_drv); if (!did_attach) { WS_DEBUG_PRINTLN("[pwm] Failed to attach pin!"); - delete _pwm_hardware[_active_pwm_pins]; + delete new_pin; } else { - _active_pwm_pins++; + _pins.push_back(new_pin); } - // Publish PixelsAdded message to the broker + // Publish PWMAdded message to the broker if (!_pwm_model->EncodePWMAdded(msg->pin, did_attach)) { WS_DEBUG_PRINTLN("[pwm]: Failed to encode PWMAdded message!"); + RemovePin(pin, expander_drv); return false; } + if (!Ws.PublishD2b(ws_signal_DeviceToBroker_pwm_tag, _pwm_model->GetPWMAddedMsg())) { WS_DEBUG_PRINTLN("[PWM]: Unable to publish PWMAdded message!"); + RemovePin(pin, expander_drv); return false; } - WS_DEBUG_PRINTLN("...attached!"); + + WS_DEBUG_PRINT("[pwm] Attached pin: "); + WS_DEBUG_PRINTVAR(msg->pin); return true; } @@ -104,50 +128,63 @@ bool PWMController::Handle_PWM_Add(ws_pwm_Add *msg) { @return True if the message was handled successfully, false otherwise. */ bool PWMController::Handle_PWM_Remove(ws_pwm_Remove *msg) { - uint8_t pin = atoi(msg->pin + 1); - int pin_idx = GetPWMHardwareIdx(pin); - if (pin_idx == -1) { - WS_DEBUG_PRINTLN("[pwm] Error: pin not found!"); + uint8_t pin = 0; + if (!ExpanderHardware::ParsePinNum(msg->pin, pin)) { + WS_DEBUG_PRINTLN("[pwm] ERROR: Malformed expander pin name!"); return false; } - // Detach and free the pin for other uses - WS_DEBUG_PRINT("[pwm] Detaching pin: "); - WS_DEBUG_PRINTVAR(msg->pin); - if (_pwm_hardware[pin_idx] != nullptr) { - bool detach_result = _pwm_hardware[pin_idx]->DetachPin(); - if (!detach_result) { - WS_DEBUG_PRINTLN("[pwm] Error: Failed to detach pin."); + // Resolve the expander driver if this is an expander pin + ExpanderHardware *expander_drv = nullptr; + if (strncmp(msg->pin, "EXP_", 4) == 0) { + uint8_t i2c_addr = (uint8_t)strtoul(msg->pin + 4, nullptr, 16); + expander_drv = Ws._expander_controller->GetDriver(i2c_addr); + if (!expander_drv) { + WS_DEBUG_PRINTLN("[pwm] ERROR: Expander not found for address!"); + return false; } - delete _pwm_hardware[pin_idx]; - _pwm_hardware[pin_idx] = nullptr; - } else { - WS_DEBUG_PRINTLN("[pwm] Error: Pin not attached!"); } - // Reorganize _active_pwm_pins - _active_pwm_pins--; - for (int i = pin_idx; i < _active_pwm_pins; i++) { - _pwm_hardware[i] = _pwm_hardware[i + 1]; + if (!RemovePin(pin, expander_drv)) { + WS_DEBUG_PRINTLN("[pwm] Error: pin not found!"); + return false; } - _pwm_hardware[_active_pwm_pins] = nullptr; - WS_DEBUG_PRINTLN("...detached!"); + + WS_DEBUG_PRINT("[pwm] Removed pin: "); + WS_DEBUG_PRINTVAR(msg->pin); return true; } /*! - @brief Returns the index of the PWM hardware object that corresponds - to the given pin. - @param pin The pin number to search for. - @return The index of the PWM hardware object, or -1 if not found. + @brief Removes a pin from the vector by pin number. + Detaches and deletes the pin object, freeing the hardware resource. + @param pin The pin number to remove. + @return True if the pin was found and removed, False otherwise. */ -int PWMController::GetPWMHardwareIdx(uint8_t pin) { - for (int i = 0; i < _active_pwm_pins; i++) { - if (_pwm_hardware[i]->GetPin() == pin) { - return i; +bool PWMController::RemovePin(uint8_t pin, ExpanderHardware *expander) { + for (size_t i = 0; i < _pins.size(); i++) { + if (_pins[i]->GetPin() == pin && + _pins[i]->GetExpanderDriver() == expander) { + _pins[i]->detach(); + delete _pins[i]; + _pins.erase(_pins.begin() + i); + return true; } } - return -1; + return false; +} + +/*! + @brief Get a pointer to a PWM pin by pin number. + @param pin The pin number to search for. + @return Pointer to the PWM hardware object, or nullptr if not found. +*/ +PWMHardware *PWMController::GetPin(uint8_t pin, ExpanderHardware *expander) { + for (size_t i = 0; i < _pins.size(); i++) { + if (_pins[i]->GetPin() == pin && _pins[i]->GetExpanderDriver() == expander) + return _pins[i]; + } + return nullptr; } /*! @@ -156,37 +193,31 @@ int PWMController::GetPWMHardwareIdx(uint8_t pin) { @return True if the message was handled successfully, false otherwise. */ bool PWMController::Handle_PWM_Write(ws_pwm_Write *msg) { - uint8_t pin = atoi(msg->pin + 1); - int pin_idx = GetPWMHardwareIdx(pin); - if (pin_idx == -1) { - WS_DEBUG_PRINTLN("[pwm] Error: pin not found!"); + uint8_t pin = 0; + if (!ExpanderHardware::ParsePinNum(msg->pin, pin)) { + WS_DEBUG_PRINTLN("[pwm] ERROR: Malformed expander pin name!"); return false; } - // Check which payload type we have - if (msg->which_payload == ws_pwm_Write_duty_cycle_tag) { - // Write the duty cycle to the pin - if (!_pwm_hardware[pin_idx]->WriteDutyCycle(msg->payload.duty_cycle)) { - WS_DEBUG_PRINTLN("[pwm] Error: Failed to write duty cycle!"); + // Resolve the expander driver if this is an expander pin + ExpanderHardware *expander_drv = nullptr; + if (strncmp(msg->pin, "EXP_", 4) == 0) { + uint8_t i2c_addr = (uint8_t)strtoul(msg->pin + 4, nullptr, 16); + expander_drv = Ws._expander_controller->GetDriver(i2c_addr); + if (!expander_drv) { + WS_DEBUG_PRINTLN("[pwm] ERROR: Expander not found for address!"); return false; } - WS_DEBUG_PRINT("[pwm] Wrote duty cycle: "); - WS_DEBUG_PRINTVAR(msg->payload.duty_cycle); - WS_DEBUG_PRINT(" to pin: "); - WS_DEBUG_PRINTLNVAR(msg->pin); - } else if (msg->which_payload == ws_pwm_Write_frequency_tag) { - // Write the frequency to the pin - if (_pwm_hardware[pin_idx]->WriteTone(msg->payload.frequency) != - msg->payload.frequency) { - WS_DEBUG_PRINTLN("[pwm] Error: Failed to write frequency!"); - return false; - } - WS_DEBUG_PRINT("[pwm] Wrote frequency: "); - WS_DEBUG_PRINTVAR(msg->payload.frequency); - WS_DEBUG_PRINT(" to pin: "); - WS_DEBUG_PRINTLNVAR(msg->pin); - } else { - WS_DEBUG_PRINTLN("[pwm] Error: Invalid payload type!"); + } + + PWMHardware *hw = GetPin(pin, expander_drv); + if (hw == nullptr) { + WS_DEBUG_PRINTLN("[pwm] Error: pin not found!"); + return false; + } + + if (!hw->write(msg)) { + WS_DEBUG_PRINTLN("[pwm] Error: Failed to write to pin!"); return false; } diff --git a/src/components/pwm/controller.h b/src/components/pwm/controller.h index 8d84d0701..2f5db5f50 100644 --- a/src/components/pwm/controller.h +++ b/src/components/pwm/controller.h @@ -7,7 +7,7 @@ * please support Adafruit and open-source hardware by purchasing * products from Adafruit! * - * Copyright (c) Brent Rubell 2025 for Adafruit Industries. + * Copyright (c) Brent Rubell 2025-2026 for Adafruit Industries. * * BSD license, all text here must be included in any redistribution. * @@ -17,7 +17,7 @@ #include "hardware.h" #include "model.h" #include "wippersnapper.h" -#define MAX_PWM_PINS 25 ///< Maximum number of PWM pins supported +#include class wippersnapper; // Forward declaration class PWMModel; // Forward declaration @@ -36,12 +36,12 @@ class PWMController { bool Handle_PWM_Add(ws_pwm_Add *msg); bool Handle_PWM_Write(ws_pwm_Write *msg); bool Handle_PWM_Remove(ws_pwm_Remove *msg); - int GetPWMHardwareIdx(uint8_t pin); private: + bool RemovePin(uint8_t pin, ExpanderHardware *expander); + PWMHardware *GetPin(uint8_t pin, ExpanderHardware *expander); PWMModel *_pwm_model; - PWMHardware *_pwm_hardware[MAX_PWM_PINS] = {nullptr}; - int _active_pwm_pins; + std::vector _pins; }; extern wippersnapper Ws; ///< Wippersnapper V2 instance #endif // WS_PWM_CONTROLLER_H \ No newline at end of file diff --git a/src/components/pwm/hardware.cpp b/src/components/pwm/hardware.cpp index 01030292e..0e92c8491 100644 --- a/src/components/pwm/hardware.cpp +++ b/src/components/pwm/hardware.cpp @@ -7,7 +7,7 @@ * please support Adafruit and open-source hardware by purchasing * products from Adafruit! * - * Copyright (c) Brent Rubell 2025 for Adafruit Industries. + * Copyright (c) Brent Rubell 2025-2026 for Adafruit Industries. * * BSD license, all text here must be included in any redistribution. * @@ -24,7 +24,7 @@ PWMHardware::PWMHardware() { _is_attached = false; } */ PWMHardware::~PWMHardware() { if (_is_attached) - DetachPin(); + detach(); } /*! @@ -32,15 +32,25 @@ PWMHardware::~PWMHardware() { @param pin The pin to attach @param frequency The frequency of the PWM signal @param resolution The resolution of the PWM signal + @param expander_drv Pointer to an expander driver, or nullptr for + native pins. @return true if the pin was successfully attached, false otherwise */ -bool PWMHardware::AttachPin(uint8_t pin, uint32_t frequency, - uint32_t resolution) { +bool PWMHardware::attach(uint8_t pin, uint32_t frequency, uint32_t resolution, + ExpanderHardware *expander_drv) { + bool has_expander = (expander_drv != nullptr); + + // Attach the pin + if (!has_expander) { #ifdef ARDUINO_ARCH_ESP32 - _is_attached = ledcAttach(pin, frequency, resolution); + _is_attached = ledcAttach(pin, frequency, resolution); #else - _is_attached = true; + _is_attached = true; #endif + } else { + _is_attached = true; + _expander_drv = expander_drv; + } if (_is_attached) { _pin = pin; @@ -56,35 +66,59 @@ bool PWMHardware::AttachPin(uint8_t pin, uint32_t frequency, @brief Detaches a PWM pin and frees it for use. @return true if the PWM pin was successfully detached, false otherwise. */ -bool PWMHardware::DetachPin() { - if (!_is_attached) { - WS_DEBUG_PRINTLN("[pwm] Pin not attached!"); +bool PWMHardware::detach() { + if (!_is_attached) return false; - } + bool did_detach = false; + bool has_expander = (_expander_drv != nullptr); + + // Detach the pin + if (!has_expander) { #ifdef ARDUINO_ARCH_ESP32 - did_detach = ledcDetach(_pin); + did_detach = ledcDetach(_pin); #else - digitalWrite(_pin, LOW); // "Disable" the pin's output - did_detach = true; + digitalWrite(_pin, LOW); + did_detach = true; #endif + } else { + _expander_drv->analogWrite(_pin, 0); + did_detach = true; + } _is_attached = false; // always mark as false, for tracking return did_detach; } +/*! + @brief Dispatches a PWM write message to the appropriate method. + @param msg The PWM write message containing the payload type and value. + @return true if the write was successful, false otherwise. +*/ +bool PWMHardware::write(ws_pwm_Write *msg) { + if (msg->which_payload == ws_pwm_Write_duty_cycle_tag) { + return writeDutyCycle(msg->payload.duty_cycle); + } else if (msg->which_payload == ws_pwm_Write_frequency_tag) { + return writeTone(msg->payload.frequency) == msg->payload.frequency; + } + WS_DEBUG_PRINTLN("[pwm] Error: Invalid payload type!"); + return false; +} + /*! @brief Writes a duty cycle to a PWM pin with a fixed frequency of 5kHz and 8-bit resolution. @param duty The desired duty cycle to write to the pin. @return true if the duty cycle was successfully written, false otherwise */ -bool PWMHardware::WriteDutyCycle(uint32_t duty) { - if (!_is_attached) { - WS_DEBUG_PRINTLN("[pwm] Pin not attached!"); +bool PWMHardware::writeDutyCycle(uint32_t duty) { + if (!_is_attached) return false; - } + bool did_write = false; + bool has_expander = (_expander_drv != nullptr); + +// Handle inversion for certain boards (e.g. Adafruit Feather ESP8266) #if defined(ARDUINO_ESP8266_ADAFRUIT_HUZZAH) && defined(STATUS_LED_PIN) // Adafruit Feather ESP8266's analogWrite() gets inverted since the builtin // LED is reverse-wired @@ -93,14 +127,19 @@ bool PWMHardware::WriteDutyCycle(uint32_t duty) { _duty_cycle = duty; #endif + if (!has_expander) { #ifdef ARDUINO_ARCH_ESP32 - did_write = analogWrite(_duty_cycle); + did_write = analogWrite(_duty_cycle); #else - analogWrite(_pin, duty); - did_write = true; + analogWrite(_pin, duty); + did_write = true; #endif + } else { + _expander_drv->analogWrite(_pin, (uint16_t)duty); + did_write = true; + } - return true; + return did_write; } /*! @@ -108,12 +147,16 @@ bool PWMHardware::WriteDutyCycle(uint32_t duty) { @param freq The desired frequency to write to the pin. @return The frequency that was written to the pin. */ -uint32_t PWMHardware::WriteTone(uint32_t freq) { +uint32_t PWMHardware::writeTone(uint32_t freq) { if (!_is_attached) { WS_DEBUG_PRINTLN("[pwm] Pin not attached!"); return false; } + // NOTE: Tone generation is not supported on expander pins + if (_expander_drv != nullptr) + return 0; + uint32_t rc = 0; #ifdef ARDUINO_ARCH_ESP32 rc = ledcWriteTone(_pin, freq); @@ -131,6 +174,13 @@ uint32_t PWMHardware::WriteTone(uint32_t freq) { */ uint8_t PWMHardware::GetPin() { return _pin; } +/*! + @brief Returns a pointer to the expander driver, or nullptr for native + pins. + @return Pointer to the ExpanderHardware driver, or nullptr. +*/ +ExpanderHardware *PWMHardware::GetExpanderDriver() { return _expander_drv; } + // LEDC API Wrappers #ifdef ARDUINO_ARCH_ESP32 /*! diff --git a/src/components/pwm/hardware.h b/src/components/pwm/hardware.h index 98656aed8..2978b27a1 100644 --- a/src/components/pwm/hardware.h +++ b/src/components/pwm/hardware.h @@ -7,7 +7,7 @@ * please support Adafruit and open-source hardware by purchasing * products from Adafruit! * - * Copyright (c) Brent Rubell 2025 for Adafruit Industries. + * Copyright (c) Brent Rubell 2025-2026 for Adafruit Industries. * * BSD license, all text here must be included in any redistribution. * @@ -20,6 +20,8 @@ #include "esp_err.h" #endif +class ExpanderHardware; + /*! @brief Interface for interacting with hardware's PWM API. */ @@ -27,21 +29,25 @@ class PWMHardware { public: PWMHardware(); ~PWMHardware(); - bool AttachPin(uint8_t pin, uint32_t frequency, uint32_t resolution); - bool DetachPin(); - bool WriteDutyCycle(uint32_t duty); - uint32_t WriteTone(uint32_t freq); + bool attach(uint8_t pin, uint32_t frequency, uint32_t resolution, + ExpanderHardware *expander_drv); + bool detach(); + bool write(ws_pwm_Write *msg); uint8_t GetPin(); + ExpanderHardware *GetExpanderDriver(); // Abstractions for LEDC API #ifdef ARDUINO_ARCH_ESP32 bool analogWrite(uint32_t value); #endif private: + bool writeDutyCycle(uint32_t duty); + uint32_t writeTone(uint32_t freq); bool _is_attached; uint8_t _pin; uint32_t _frequency; uint32_t _resolution; uint32_t _duty_cycle; + ExpanderHardware *_expander_drv = nullptr; }; #endif // WS_PWM_HARDWARE_H diff --git a/src/components/sleep/controller.cpp b/src/components/sleep/controller.cpp index 04f321e24..bb4f0dad1 100644 --- a/src/components/sleep/controller.cpp +++ b/src/components/sleep/controller.cpp @@ -140,11 +140,11 @@ void SleepController::WakeFromLightSleep() { // If already enumerated, additional class driver begin() e.g msc, hid, midi // won't take effect until re-enumeration // TODO: This doesn't compile on ESP32-C3, do we need it? -/* if (TinyUSBDevice.mounted()) { - TinyUSBDevice.detach(); - delay(10); - TinyUSBDevice.attach(); - } */ + /* if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } */ // Refresh the cached sleep wakeup cause from hardware _sleep_hardware->GetSleepWakeupCause(); diff --git a/src/components/sleep/hardware.cpp b/src/components/sleep/hardware.cpp index e32af8b46..71bef4785 100644 --- a/src/components/sleep/hardware.cpp +++ b/src/components/sleep/hardware.cpp @@ -397,7 +397,6 @@ void SleepHardware::DisableExternalComponents() { #endif } - /*! @brief Retrieves the current sleep cycle count. @return The number of sleep cycles completed. diff --git a/src/components/sleep/model.cpp b/src/components/sleep/model.cpp index 13c91d8c0..1c579f089 100644 --- a/src/components/sleep/model.cpp +++ b/src/components/sleep/model.cpp @@ -127,8 +127,10 @@ void SleepModel::SetSleepEnterExt0(bool lock, const char *mode, // Configure pin-specific fields _msg_sleep_enter.which_config = ws_sleep_SleepConfig_ext0_tag; - strncpy(_msg_sleep_enter.config.ext0.pin_name, pin_name, sizeof(_msg_sleep_enter.config.ext0.pin_name) - 1); - _msg_sleep_enter.config.ext0.pin_name[sizeof(_msg_sleep_enter.config.ext0.pin_name) - 1] = '\0'; + strncpy(_msg_sleep_enter.config.ext0.pin_name, pin_name, + sizeof(_msg_sleep_enter.config.ext0.pin_name) - 1); + _msg_sleep_enter.config.ext0 + .pin_name[sizeof(_msg_sleep_enter.config.ext0.pin_name) - 1] = '\0'; _msg_sleep_enter.config.ext0.level = pin_level; _msg_sleep_enter.config.ext0.pull = pin_pull; } diff --git a/src/components/sleep/model.h b/src/components/sleep/model.h index aa7493ed6..ebb92cc7f 100644 --- a/src/components/sleep/model.h +++ b/src/components/sleep/model.h @@ -39,7 +39,7 @@ class SleepModel { private: void ConvertSleepMode(const char *mode_str, ws_sleep_SleepMode &mode); ws_sleep_Goodnight _msg_sleep_goodnight; ///< Goodnight message object - ws_sleep_SleepConfig _msg_sleep_enter; ///< Enter message object + ws_sleep_SleepConfig _msg_sleep_enter; ///< Enter message object uint32_t _run_duration = 60; ///< Duration to run before sleeping, in seconds }; diff --git a/src/components/uart/controller.cpp b/src/components/uart/controller.cpp index d6ea06948..50ac00938 100644 --- a/src/components/uart/controller.cpp +++ b/src/components/uart/controller.cpp @@ -129,7 +129,8 @@ bool UARTController::Handle_UartAdd(ws_uart_Add *msg) { delete uart_hardware; // cleanup return false; case ws_uart_DeviceType_DT_GPS: - WS_DEBUG_PRINTLN("[uart] GPS is now handled by the GPS controller directly!"); + WS_DEBUG_PRINTLN( + "[uart] GPS is now handled by the GPS controller directly!"); delete uart_hardware; // cleanup return false; case ws_uart_DeviceType_DT_PM25AQI: diff --git a/src/protos/analogin.pb.c b/src/protos/analogin.pb.c new file mode 100644 index 000000000..f7d39bf0c --- /dev/null +++ b/src/protos/analogin.pb.c @@ -0,0 +1,26 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.8 at Wed May 13 17:20:24 2026. */ + +#include "analogin.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(ws_analogin_B2D, ws_analogin_B2D, AUTO) + + +PB_BIND(ws_analogin_D2B, ws_analogin_D2B, AUTO) + + +PB_BIND(ws_analogin_Add, ws_analogin_Add, AUTO) + + +PB_BIND(ws_analogin_Remove, ws_analogin_Remove, AUTO) + + +PB_BIND(ws_analogin_Event, ws_analogin_Event, AUTO) + + + + + diff --git a/src/protos/analogin.pb.h b/src/protos/analogin.pb.h new file mode 100644 index 000000000..ffa7da2cc --- /dev/null +++ b/src/protos/analogin.pb.h @@ -0,0 +1,197 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.8 at Wed May 13 17:20:24 2026. */ + +#ifndef PB_WS_ANALOGIN_ANALOGIN_PB_H_INCLUDED +#define PB_WS_ANALOGIN_ANALOGIN_PB_H_INCLUDED +#include +#include "sensor.pb.h" + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Enum definitions */ +/* * + SampleMode specifies the pin's sample mode. */ +typedef enum _ws_analogin_SampleMode { + ws_analogin_SampleMode_SM_UNSPECIFIED = 0, /* * Invalid Sample Mode from Broker. */ + ws_analogin_SampleMode_SM_TIMER = 1, /* * Periodically sample the pin's value. */ + ws_analogin_SampleMode_SM_EVENT = 2 /* * Sample the pin's value when an event occurs. */ +} ws_analogin_SampleMode; + +/* * + Gain specifies the pin's ADC gain setting. */ +typedef enum _ws_analogin_Gain { + ws_analogin_Gain_G_UNSPECIFIED = 0, /* * Use platform default gain. */ + ws_analogin_Gain_G_1X = 1, /* * No gain, or unity gain. */ + ws_analogin_Gain_G_2X = 2, /* * Gain of 2x */ + ws_analogin_Gain_G_4X = 3, /* * Gain of 4x */ + ws_analogin_Gain_G_8X = 4, /* * Gain of 8x */ + ws_analogin_Gain_G_16X = 5, /* * Gain of 16x */ + ws_analogin_Gain_G_32X = 6, /* * Gain of 32x */ + ws_analogin_Gain_G_64X = 7, /* * Gain of 64x */ + ws_analogin_Gain_G_128X = 8, /* * Gain of 128x */ + ws_analogin_Gain_G_2_3X = 9 /* * Gain of 2/3x (Utilized by ADS1x15) */ +} ws_analogin_Gain; + +/* Struct definitions */ +/* * + Add adds an analog input pin to the device. */ +typedef struct _ws_analogin_Add { + char pin_name[64]; /* * Name of the pin. */ + float period; /* * Time between reads, in seconds. */ + ws_sensor_Type read_mode; /* * Desired read mode for the pin. */ + ws_analogin_SampleMode sample_mode; /* * Desired sample mode for the pin. */ + float ref_voltage; /* * Reference voltage for the pin, in volts. */ + ws_analogin_Gain gain; /* * Optional ADC gain setting. */ +} ws_analogin_Add; + +/* * + Remove removes an analog input pin from the device. */ +typedef struct _ws_analogin_Remove { + char pin_name[64]; /* * Name of the pin. */ +} ws_analogin_Remove; + +/* * + BrokerToDevice message envelope */ +typedef struct _ws_analogin_B2D { + pb_size_t which_payload; + union { + ws_analogin_Add add; + ws_analogin_Remove remove; + } payload; +} ws_analogin_B2D; + +/* * + Event contains a value, sent when an analog input pin is read. */ +typedef struct _ws_analogin_Event { + char pin_name[64]; /* * Name of the pin. */ + bool has_value; + ws_sensor_Event value; /* * Reading(s) from an analog input pin. */ +} ws_analogin_Event; + +/* * + DeviceToBroker message envelope */ +typedef struct _ws_analogin_D2B { + pb_size_t which_payload; + union { + ws_analogin_Event event; + } payload; +} ws_analogin_D2B; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Helper constants for enums */ +#define _ws_analogin_SampleMode_MIN ws_analogin_SampleMode_SM_UNSPECIFIED +#define _ws_analogin_SampleMode_MAX ws_analogin_SampleMode_SM_EVENT +#define _ws_analogin_SampleMode_ARRAYSIZE ((ws_analogin_SampleMode)(ws_analogin_SampleMode_SM_EVENT+1)) + +#define _ws_analogin_Gain_MIN ws_analogin_Gain_G_UNSPECIFIED +#define _ws_analogin_Gain_MAX ws_analogin_Gain_G_2_3X +#define _ws_analogin_Gain_ARRAYSIZE ((ws_analogin_Gain)(ws_analogin_Gain_G_2_3X+1)) + + + +#define ws_analogin_Add_read_mode_ENUMTYPE ws_sensor_Type +#define ws_analogin_Add_sample_mode_ENUMTYPE ws_analogin_SampleMode +#define ws_analogin_Add_gain_ENUMTYPE ws_analogin_Gain + + + + +/* Initializer values for message structs */ +#define ws_analogin_B2D_init_default {0, {ws_analogin_Add_init_default}} +#define ws_analogin_D2B_init_default {0, {ws_analogin_Event_init_default}} +#define ws_analogin_Add_init_default {"", 0, _ws_sensor_Type_MIN, _ws_analogin_SampleMode_MIN, 0, _ws_analogin_Gain_MIN} +#define ws_analogin_Remove_init_default {""} +#define ws_analogin_Event_init_default {"", false, ws_sensor_Event_init_default} +#define ws_analogin_B2D_init_zero {0, {ws_analogin_Add_init_zero}} +#define ws_analogin_D2B_init_zero {0, {ws_analogin_Event_init_zero}} +#define ws_analogin_Add_init_zero {"", 0, _ws_sensor_Type_MIN, _ws_analogin_SampleMode_MIN, 0, _ws_analogin_Gain_MIN} +#define ws_analogin_Remove_init_zero {""} +#define ws_analogin_Event_init_zero {"", false, ws_sensor_Event_init_zero} + +/* Field tags (for use in manual encoding/decoding) */ +#define ws_analogin_Add_pin_name_tag 1 +#define ws_analogin_Add_period_tag 2 +#define ws_analogin_Add_read_mode_tag 3 +#define ws_analogin_Add_sample_mode_tag 4 +#define ws_analogin_Add_ref_voltage_tag 5 +#define ws_analogin_Add_gain_tag 6 +#define ws_analogin_Remove_pin_name_tag 1 +#define ws_analogin_B2D_add_tag 1 +#define ws_analogin_B2D_remove_tag 2 +#define ws_analogin_Event_pin_name_tag 1 +#define ws_analogin_Event_value_tag 2 +#define ws_analogin_D2B_event_tag 1 + +/* Struct field encoding specification for nanopb */ +#define ws_analogin_B2D_FIELDLIST(X, a) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,add,payload.add), 1) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,remove,payload.remove), 2) +#define ws_analogin_B2D_CALLBACK NULL +#define ws_analogin_B2D_DEFAULT NULL +#define ws_analogin_B2D_payload_add_MSGTYPE ws_analogin_Add +#define ws_analogin_B2D_payload_remove_MSGTYPE ws_analogin_Remove + +#define ws_analogin_D2B_FIELDLIST(X, a) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,event,payload.event), 1) +#define ws_analogin_D2B_CALLBACK NULL +#define ws_analogin_D2B_DEFAULT NULL +#define ws_analogin_D2B_payload_event_MSGTYPE ws_analogin_Event + +#define ws_analogin_Add_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, pin_name, 1) \ +X(a, STATIC, SINGULAR, FLOAT, period, 2) \ +X(a, STATIC, SINGULAR, UENUM, read_mode, 3) \ +X(a, STATIC, SINGULAR, UENUM, sample_mode, 4) \ +X(a, STATIC, SINGULAR, FLOAT, ref_voltage, 5) \ +X(a, STATIC, SINGULAR, UENUM, gain, 6) +#define ws_analogin_Add_CALLBACK NULL +#define ws_analogin_Add_DEFAULT NULL + +#define ws_analogin_Remove_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, pin_name, 1) +#define ws_analogin_Remove_CALLBACK NULL +#define ws_analogin_Remove_DEFAULT NULL + +#define ws_analogin_Event_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, pin_name, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, value, 2) +#define ws_analogin_Event_CALLBACK NULL +#define ws_analogin_Event_DEFAULT NULL +#define ws_analogin_Event_value_MSGTYPE ws_sensor_Event + +extern const pb_msgdesc_t ws_analogin_B2D_msg; +extern const pb_msgdesc_t ws_analogin_D2B_msg; +extern const pb_msgdesc_t ws_analogin_Add_msg; +extern const pb_msgdesc_t ws_analogin_Remove_msg; +extern const pb_msgdesc_t ws_analogin_Event_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define ws_analogin_B2D_fields &ws_analogin_B2D_msg +#define ws_analogin_D2B_fields &ws_analogin_D2B_msg +#define ws_analogin_Add_fields &ws_analogin_Add_msg +#define ws_analogin_Remove_fields &ws_analogin_Remove_msg +#define ws_analogin_Event_fields &ws_analogin_Event_msg + +/* Maximum encoded size of messages (where known) */ +#if defined(ws_sensor_Event_size) +#endif +#define WS_ANALOGIN_ANALOGIN_PB_H_MAX_SIZE ws_analogin_B2D_size +#define ws_analogin_Add_size 81 +#define ws_analogin_B2D_size 83 +#define ws_analogin_Remove_size 65 +#if defined(ws_sensor_Event_size) +#define ws_analogin_D2B_size (77 + ws_sensor_Event_size) +#define ws_analogin_Event_size (71 + ws_sensor_Event_size) +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/protos/analogio.pb.c b/src/protos/analogio.pb.c deleted file mode 100644 index ef6720589..000000000 --- a/src/protos/analogio.pb.c +++ /dev/null @@ -1,24 +0,0 @@ -/* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ - -#include "analogio.pb.h" -#if PB_PROTO_HEADER_VERSION != 40 -#error Regenerate this file with the current version of nanopb generator. -#endif - -PB_BIND(ws_analogio_B2D, ws_analogio_B2D, AUTO) - - -PB_BIND(ws_analogio_D2B, ws_analogio_D2B, AUTO) - - -PB_BIND(ws_analogio_Add, ws_analogio_Add, AUTO) - - -PB_BIND(ws_analogio_Remove, ws_analogio_Remove, AUTO) - - -PB_BIND(ws_analogio_Event, ws_analogio_Event, AUTO) - - - diff --git a/src/protos/analogio.pb.h b/src/protos/analogio.pb.h deleted file mode 100644 index 295e438e1..000000000 --- a/src/protos/analogio.pb.h +++ /dev/null @@ -1,146 +0,0 @@ -/* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ - -#ifndef PB_WS_ANALOGIO_ANALOGIO_PB_H_INCLUDED -#define PB_WS_ANALOGIO_ANALOGIO_PB_H_INCLUDED -#include -#include "sensor.pb.h" - -#if PB_PROTO_HEADER_VERSION != 40 -#error Regenerate this file with the current version of nanopb generator. -#endif - -/* Struct definitions */ -/* * - Add adds an analog pin to the device. */ -typedef struct _ws_analogio_Add { - char pin_name[64]; /* * Name of the pin. */ - float period; /* * Time between reads, in seconds. */ - ws_sensor_Type read_mode; /* * Desired read mode for the pin. */ -} ws_analogio_Add; - -/* * - Remove removes an analog pin from the device. */ -typedef struct _ws_analogio_Remove { - char pin_name[64]; /* * Name of the pin. */ -} ws_analogio_Remove; - -/* * - BrokerToDevice message envelope */ -typedef struct _ws_analogio_B2D { - pb_size_t which_payload; - union { - ws_analogio_Add add; - ws_analogio_Remove remove; - } payload; -} ws_analogio_B2D; - -/* * - Event is contains a value, sent when an analog pin is read. */ -typedef struct _ws_analogio_Event { - char pin_name[64]; /* * Name of the pin. */ - bool has_value; - ws_sensor_Event value; /* * Reading(s) from an analog pin. */ -} ws_analogio_Event; - -/* * - DeviceToBroker message envelope */ -typedef struct _ws_analogio_D2B { - pb_size_t which_payload; - union { - ws_analogio_Event event; - } payload; -} ws_analogio_D2B; - - -#ifdef __cplusplus -extern "C" { -#endif - -/* Initializer values for message structs */ -#define ws_analogio_B2D_init_default {0, {ws_analogio_Add_init_default}} -#define ws_analogio_D2B_init_default {0, {ws_analogio_Event_init_default}} -#define ws_analogio_Add_init_default {"", 0, _ws_sensor_Type_MIN} -#define ws_analogio_Remove_init_default {""} -#define ws_analogio_Event_init_default {"", false, ws_sensor_Event_init_default} -#define ws_analogio_B2D_init_zero {0, {ws_analogio_Add_init_zero}} -#define ws_analogio_D2B_init_zero {0, {ws_analogio_Event_init_zero}} -#define ws_analogio_Add_init_zero {"", 0, _ws_sensor_Type_MIN} -#define ws_analogio_Remove_init_zero {""} -#define ws_analogio_Event_init_zero {"", false, ws_sensor_Event_init_zero} - -/* Field tags (for use in manual encoding/decoding) */ -#define ws_analogio_Add_pin_name_tag 1 -#define ws_analogio_Add_period_tag 2 -#define ws_analogio_Add_read_mode_tag 3 -#define ws_analogio_Remove_pin_name_tag 1 -#define ws_analogio_B2D_add_tag 1 -#define ws_analogio_B2D_remove_tag 2 -#define ws_analogio_Event_pin_name_tag 1 -#define ws_analogio_Event_value_tag 2 -#define ws_analogio_D2B_event_tag 1 - -/* Struct field encoding specification for nanopb */ -#define ws_analogio_B2D_FIELDLIST(X, a) \ -X(a, STATIC, ONEOF, MESSAGE, (payload,add,payload.add), 1) \ -X(a, STATIC, ONEOF, MESSAGE, (payload,remove,payload.remove), 2) -#define ws_analogio_B2D_CALLBACK NULL -#define ws_analogio_B2D_DEFAULT NULL -#define ws_analogio_B2D_payload_add_MSGTYPE ws_analogio_Add -#define ws_analogio_B2D_payload_remove_MSGTYPE ws_analogio_Remove - -#define ws_analogio_D2B_FIELDLIST(X, a) \ -X(a, STATIC, ONEOF, MESSAGE, (payload,event,payload.event), 1) -#define ws_analogio_D2B_CALLBACK NULL -#define ws_analogio_D2B_DEFAULT NULL -#define ws_analogio_D2B_payload_event_MSGTYPE ws_analogio_Event - -#define ws_analogio_Add_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, STRING, pin_name, 1) \ -X(a, STATIC, SINGULAR, FLOAT, period, 2) \ -X(a, STATIC, SINGULAR, UENUM, read_mode, 3) -#define ws_analogio_Add_CALLBACK NULL -#define ws_analogio_Add_DEFAULT NULL - -#define ws_analogio_Remove_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, STRING, pin_name, 1) -#define ws_analogio_Remove_CALLBACK NULL -#define ws_analogio_Remove_DEFAULT NULL - -#define ws_analogio_Event_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, STRING, pin_name, 1) \ -X(a, STATIC, OPTIONAL, MESSAGE, value, 2) -#define ws_analogio_Event_CALLBACK NULL -#define ws_analogio_Event_DEFAULT NULL -#define ws_analogio_Event_value_MSGTYPE ws_sensor_Event - -extern const pb_msgdesc_t ws_analogio_B2D_msg; -extern const pb_msgdesc_t ws_analogio_D2B_msg; -extern const pb_msgdesc_t ws_analogio_Add_msg; -extern const pb_msgdesc_t ws_analogio_Remove_msg; -extern const pb_msgdesc_t ws_analogio_Event_msg; - -/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ -#define ws_analogio_B2D_fields &ws_analogio_B2D_msg -#define ws_analogio_D2B_fields &ws_analogio_D2B_msg -#define ws_analogio_Add_fields &ws_analogio_Add_msg -#define ws_analogio_Remove_fields &ws_analogio_Remove_msg -#define ws_analogio_Event_fields &ws_analogio_Event_msg - -/* Maximum encoded size of messages (where known) */ -#if defined(ws_sensor_Event_size) -#endif -#define ws_analogio_Add_size 72 -#define ws_analogio_B2D_size 74 -#define ws_analogio_Remove_size 65 -#if defined(ws_sensor_Event_size) -#define WS_ANALOGIO_ANALOGIO_PB_H_MAX_SIZE ws_analogio_D2B_size -#define ws_analogio_D2B_size (77 + ws_sensor_Event_size) -#define ws_analogio_Event_size (71 + ws_sensor_Event_size) -#endif - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif diff --git a/src/protos/checkin.pb.c b/src/protos/checkin.pb.c index 5bcc0c652..8e31a6728 100644 --- a/src/protos/checkin.pb.c +++ b/src/protos/checkin.pb.c @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Mon May 11 18:43:03 2026. */ #include "checkin.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/protos/checkin.pb.h b/src/protos/checkin.pb.h index 604a40c1d..d3d1d088e 100644 --- a/src/protos/checkin.pb.h +++ b/src/protos/checkin.pb.h @@ -1,10 +1,10 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Mon May 11 18:43:03 2026. */ #ifndef PB_WS_CHECKIN_CHECKIN_PB_H_INCLUDED #define PB_WS_CHECKIN_CHECKIN_PB_H_INCLUDED #include -#include "analogio.pb.h" +#include "analogin.pb.h" #include "digitalio.pb.h" #include "ds18x20.pb.h" #include "i2c.pb.h" @@ -40,7 +40,7 @@ typedef struct _ws_checkin_Request { Generic component add message, could contain any *Add message from component protos */ typedef struct _ws_checkin_ComponentAdds { pb_callback_t digitalio_adds; - pb_callback_t analogio_adds; + pb_callback_t analogin_adds; pb_callback_t servo_adds; pb_callback_t pwm_adds; pb_callback_t pixels_adds; @@ -127,7 +127,7 @@ extern "C" { #define ws_checkin_Request_hardware_uid_tag 1 #define ws_checkin_Request_firmware_version_tag 2 #define ws_checkin_ComponentAdds_digitalio_adds_tag 1 -#define ws_checkin_ComponentAdds_analogio_adds_tag 2 +#define ws_checkin_ComponentAdds_analogin_adds_tag 2 #define ws_checkin_ComponentAdds_servo_adds_tag 3 #define ws_checkin_ComponentAdds_pwm_adds_tag 4 #define ws_checkin_ComponentAdds_pixels_adds_tag 5 @@ -182,7 +182,7 @@ X(a, STATIC, OPTIONAL, MESSAGE, sleep_config, 7) #define ws_checkin_ComponentAdds_FIELDLIST(X, a) \ X(a, CALLBACK, REPEATED, MESSAGE, digitalio_adds, 1) \ -X(a, CALLBACK, REPEATED, MESSAGE, analogio_adds, 2) \ +X(a, CALLBACK, REPEATED, MESSAGE, analogin_adds, 2) \ X(a, CALLBACK, REPEATED, MESSAGE, servo_adds, 3) \ X(a, CALLBACK, REPEATED, MESSAGE, pwm_adds, 4) \ X(a, CALLBACK, REPEATED, MESSAGE, pixels_adds, 5) \ @@ -193,7 +193,7 @@ X(a, CALLBACK, REPEATED, MESSAGE, display_adds, 9) #define ws_checkin_ComponentAdds_CALLBACK pb_default_field_callback #define ws_checkin_ComponentAdds_DEFAULT NULL #define ws_checkin_ComponentAdds_digitalio_adds_MSGTYPE ws_digitalio_Add -#define ws_checkin_ComponentAdds_analogio_adds_MSGTYPE ws_analogio_Add +#define ws_checkin_ComponentAdds_analogin_adds_MSGTYPE ws_analogin_Add #define ws_checkin_ComponentAdds_servo_adds_MSGTYPE ws_servo_Add #define ws_checkin_ComponentAdds_pwm_adds_MSGTYPE ws_pwm_Add #define ws_checkin_ComponentAdds_pixels_adds_MSGTYPE ws_pixels_Add diff --git a/src/protos/digitalio.pb.c b/src/protos/digitalio.pb.c index 04754c610..38345a651 100644 --- a/src/protos/digitalio.pb.c +++ b/src/protos/digitalio.pb.c @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #include "digitalio.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/protos/digitalio.pb.h b/src/protos/digitalio.pb.h index f923c7f55..327d5e795 100644 --- a/src/protos/digitalio.pb.h +++ b/src/protos/digitalio.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #ifndef PB_WS_DIGITALIO_DIGITALIO_PB_H_INCLUDED #define PB_WS_DIGITALIO_DIGITALIO_PB_H_INCLUDED diff --git a/src/protos/display.pb.c b/src/protos/display.pb.c index 7845da6dc..24d2dac8f 100644 --- a/src/protos/display.pb.c +++ b/src/protos/display.pb.c @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #include "display.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/protos/display.pb.h b/src/protos/display.pb.h index 3ff0ed0bf..3280aa293 100644 --- a/src/protos/display.pb.h +++ b/src/protos/display.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #ifndef PB_WS_DISPLAY_DISPLAY_PB_H_INCLUDED #define PB_WS_DISPLAY_DISPLAY_PB_H_INCLUDED diff --git a/src/protos/ds18x20.pb.c b/src/protos/ds18x20.pb.c index fab4e346c..1ac4ae5a2 100644 --- a/src/protos/ds18x20.pb.c +++ b/src/protos/ds18x20.pb.c @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #include "ds18x20.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/protos/ds18x20.pb.h b/src/protos/ds18x20.pb.h index d1a323dd1..0e278c1e2 100644 --- a/src/protos/ds18x20.pb.h +++ b/src/protos/ds18x20.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #ifndef PB_WS_DS18X20_DS18X20_PB_H_INCLUDED #define PB_WS_DS18X20_DS18X20_PB_H_INCLUDED diff --git a/src/protos/error.pb.c b/src/protos/error.pb.c index d4b5f7d45..ac15c3abd 100644 --- a/src/protos/error.pb.c +++ b/src/protos/error.pb.c @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Mon May 11 18:43:03 2026. */ #include "error.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/protos/error.pb.h b/src/protos/error.pb.h index 58292ca95..b734019d6 100644 --- a/src/protos/error.pb.h +++ b/src/protos/error.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Mon May 11 18:43:03 2026. */ #ifndef PB_WS_ERROR_ERROR_PB_H_INCLUDED #define PB_WS_ERROR_ERROR_PB_H_INCLUDED @@ -18,7 +18,7 @@ typedef enum _ws_error_ComponentType { ws_error_ComponentType_COMPONENT_TYPE_UNSPECIFIED = 0, /* * Invalid Component Type. */ ws_error_ComponentType_COMPONENT_TYPE_DIGITALIO = 1, /* * Digital IO Component. */ - ws_error_ComponentType_COMPONENT_TYPE_ANALOGIO = 2, /* * Analog IO Component. */ + ws_error_ComponentType_COMPONENT_TYPE_ANALOGIN = 2, /* * Analog In Component. */ ws_error_ComponentType_COMPONENT_TYPE_PWM = 3, /* * PWM Component. */ ws_error_ComponentType_COMPONENT_TYPE_SERVO = 4, /* * Servo Component. */ ws_error_ComponentType_COMPONENT_TYPE_PIXELS = 5, /* * Pixels Component. */ diff --git a/src/protos/expander.pb.c b/src/protos/expander.pb.c new file mode 100644 index 000000000..9f4a9f37d --- /dev/null +++ b/src/protos/expander.pb.c @@ -0,0 +1,27 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ + +#include "expander.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(ws_expander_B2D, ws_expander_B2D, AUTO) + + +PB_BIND(ws_expander_D2B, ws_expander_D2B, AUTO) + + +PB_BIND(ws_expander_Add, ws_expander_Add, AUTO) + + +PB_BIND(ws_expander_Added, ws_expander_Added, AUTO) + + +PB_BIND(ws_expander_Remove, ws_expander_Remove, AUTO) + + +PB_BIND(ws_expander_Removed, ws_expander_Removed, AUTO) + + + diff --git a/src/protos/expander.pb.h b/src/protos/expander.pb.h new file mode 100644 index 000000000..608037a2a --- /dev/null +++ b/src/protos/expander.pb.h @@ -0,0 +1,161 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ + +#ifndef PB_WS_EXPANDER_EXPANDER_PB_H_INCLUDED +#define PB_WS_EXPANDER_EXPANDER_PB_H_INCLUDED +#include +#include "i2c.pb.h" +#include "digitalio.pb.h" + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Struct definitions */ +/* * + Add adds an expander to the hardware. */ +typedef struct _ws_expander_Add { + bool has_cfg; + ws_i2c_DeviceAddOrReplace cfg; /* * I2C configuration for the expander. */ +} ws_expander_Add; + +/* * + Added represents a message from the hardware indicating an expander was added. */ +typedef struct _ws_expander_Added { + bool has_response; + ws_i2c_DeviceAddedOrReplaced response; /* * I2C configuration response. */ +} ws_expander_Added; + +/* * + Remove removes an analog pin from the hardware. */ +typedef struct _ws_expander_Remove { + bool has_cfg; + ws_i2c_DeviceRemove cfg; /* * I2C configuration for the expander to remove. */ +} ws_expander_Remove; + +/* * + BrokerToDevice message envelope */ +typedef struct _ws_expander_B2D { + pb_size_t which_payload; + union { + ws_expander_Add add; + ws_expander_Remove remove; + } payload; +} ws_expander_B2D; + +/* * + Removed represents a message from the device indicating an expander was removed. */ +typedef struct _ws_expander_Removed { + bool has_response; + ws_i2c_DeviceRemoved response; /* * I2C configuration response. */ +} ws_expander_Removed; + +/* * +DeviceToBroker message envelope */ +typedef struct _ws_expander_D2B { + pb_size_t which_payload; + union { + ws_expander_Added added; + ws_expander_Removed removed; + } payload; +} ws_expander_D2B; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initializer values for message structs */ +#define ws_expander_B2D_init_default {0, {ws_expander_Add_init_default}} +#define ws_expander_D2B_init_default {0, {ws_expander_Added_init_default}} +#define ws_expander_Add_init_default {false, ws_i2c_DeviceAddOrReplace_init_default} +#define ws_expander_Added_init_default {false, ws_i2c_DeviceAddedOrReplaced_init_default} +#define ws_expander_Remove_init_default {false, ws_i2c_DeviceRemove_init_default} +#define ws_expander_Removed_init_default {false, ws_i2c_DeviceRemoved_init_default} +#define ws_expander_B2D_init_zero {0, {ws_expander_Add_init_zero}} +#define ws_expander_D2B_init_zero {0, {ws_expander_Added_init_zero}} +#define ws_expander_Add_init_zero {false, ws_i2c_DeviceAddOrReplace_init_zero} +#define ws_expander_Added_init_zero {false, ws_i2c_DeviceAddedOrReplaced_init_zero} +#define ws_expander_Remove_init_zero {false, ws_i2c_DeviceRemove_init_zero} +#define ws_expander_Removed_init_zero {false, ws_i2c_DeviceRemoved_init_zero} + +/* Field tags (for use in manual encoding/decoding) */ +#define ws_expander_Add_cfg_tag 1 +#define ws_expander_Added_response_tag 1 +#define ws_expander_Remove_cfg_tag 1 +#define ws_expander_B2D_add_tag 1 +#define ws_expander_B2D_remove_tag 2 +#define ws_expander_Removed_response_tag 1 +#define ws_expander_D2B_added_tag 1 +#define ws_expander_D2B_removed_tag 2 + +/* Struct field encoding specification for nanopb */ +#define ws_expander_B2D_FIELDLIST(X, a) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,add,payload.add), 1) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,remove,payload.remove), 2) +#define ws_expander_B2D_CALLBACK NULL +#define ws_expander_B2D_DEFAULT NULL +#define ws_expander_B2D_payload_add_MSGTYPE ws_expander_Add +#define ws_expander_B2D_payload_remove_MSGTYPE ws_expander_Remove + +#define ws_expander_D2B_FIELDLIST(X, a) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,added,payload.added), 1) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,removed,payload.removed), 2) +#define ws_expander_D2B_CALLBACK NULL +#define ws_expander_D2B_DEFAULT NULL +#define ws_expander_D2B_payload_added_MSGTYPE ws_expander_Added +#define ws_expander_D2B_payload_removed_MSGTYPE ws_expander_Removed + +#define ws_expander_Add_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, cfg, 1) +#define ws_expander_Add_CALLBACK NULL +#define ws_expander_Add_DEFAULT NULL +#define ws_expander_Add_cfg_MSGTYPE ws_i2c_DeviceAddOrReplace + +#define ws_expander_Added_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, response, 1) +#define ws_expander_Added_CALLBACK NULL +#define ws_expander_Added_DEFAULT NULL +#define ws_expander_Added_response_MSGTYPE ws_i2c_DeviceAddedOrReplaced + +#define ws_expander_Remove_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, cfg, 1) +#define ws_expander_Remove_CALLBACK NULL +#define ws_expander_Remove_DEFAULT NULL +#define ws_expander_Remove_cfg_MSGTYPE ws_i2c_DeviceRemove + +#define ws_expander_Removed_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, response, 1) +#define ws_expander_Removed_CALLBACK NULL +#define ws_expander_Removed_DEFAULT NULL +#define ws_expander_Removed_response_MSGTYPE ws_i2c_DeviceRemoved + +extern const pb_msgdesc_t ws_expander_B2D_msg; +extern const pb_msgdesc_t ws_expander_D2B_msg; +extern const pb_msgdesc_t ws_expander_Add_msg; +extern const pb_msgdesc_t ws_expander_Added_msg; +extern const pb_msgdesc_t ws_expander_Remove_msg; +extern const pb_msgdesc_t ws_expander_Removed_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define ws_expander_B2D_fields &ws_expander_B2D_msg +#define ws_expander_D2B_fields &ws_expander_D2B_msg +#define ws_expander_Add_fields &ws_expander_Add_msg +#define ws_expander_Added_fields &ws_expander_Added_msg +#define ws_expander_Remove_fields &ws_expander_Remove_msg +#define ws_expander_Removed_fields &ws_expander_Removed_msg + +/* Maximum encoded size of messages (where known) */ +#define WS_EXPANDER_EXPANDER_PB_H_MAX_SIZE ws_expander_B2D_size +#define ws_expander_Add_size 88 +#define ws_expander_Added_size 38 +#define ws_expander_B2D_size 90 +#define ws_expander_D2B_size 40 +#define ws_expander_Remove_size 34 +#define ws_expander_Removed_size 36 + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/protos/gps.pb.c b/src/protos/gps.pb.c index 8afb99abd..e785d266d 100644 --- a/src/protos/gps.pb.c +++ b/src/protos/gps.pb.c @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #include "gps.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/protos/gps.pb.h b/src/protos/gps.pb.h index 04003c2b5..c1bc1c7a1 100644 --- a/src/protos/gps.pb.h +++ b/src/protos/gps.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #ifndef PB_WS_GPS_GPS_PB_H_INCLUDED #define PB_WS_GPS_GPS_PB_H_INCLUDED diff --git a/src/protos/i2c.pb.c b/src/protos/i2c.pb.c index 4e0ead84f..0da905b7e 100644 --- a/src/protos/i2c.pb.c +++ b/src/protos/i2c.pb.c @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #include "i2c.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/protos/i2c.pb.h b/src/protos/i2c.pb.h index 125e92aa1..2ae9aed43 100644 --- a/src/protos/i2c.pb.h +++ b/src/protos/i2c.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #ifndef PB_WS_I2C_I2C_PB_H_INCLUDED #define PB_WS_I2C_I2C_PB_H_INCLUDED diff --git a/src/protos/pixels.pb.c b/src/protos/pixels.pb.c index fad1a2c09..aeee1bdcc 100644 --- a/src/protos/pixels.pb.c +++ b/src/protos/pixels.pb.c @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #include "pixels.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/protos/pixels.pb.h b/src/protos/pixels.pb.h index 73cd08d7b..b55e94a7e 100644 --- a/src/protos/pixels.pb.h +++ b/src/protos/pixels.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #ifndef PB_WS_PIXELS_PIXELS_PB_H_INCLUDED #define PB_WS_PIXELS_PIXELS_PB_H_INCLUDED diff --git a/src/protos/pwm.pb.c b/src/protos/pwm.pb.c index ff7b9d9d3..7b8b09278 100644 --- a/src/protos/pwm.pb.c +++ b/src/protos/pwm.pb.c @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #include "pwm.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/protos/pwm.pb.h b/src/protos/pwm.pb.h index 0f0fe0fa0..64e875600 100644 --- a/src/protos/pwm.pb.h +++ b/src/protos/pwm.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #ifndef PB_WS_PWM_PWM_PB_H_INCLUDED #define PB_WS_PWM_PWM_PB_H_INCLUDED diff --git a/src/protos/sensor.pb.c b/src/protos/sensor.pb.c index 710b505dc..cdcf3eece 100644 --- a/src/protos/sensor.pb.c +++ b/src/protos/sensor.pb.c @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #include "sensor.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/protos/sensor.pb.h b/src/protos/sensor.pb.h index 94f77c636..e11efbc48 100644 --- a/src/protos/sensor.pb.h +++ b/src/protos/sensor.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #ifndef PB_WS_SENSOR_SENSOR_PB_H_INCLUDED #define PB_WS_SENSOR_SENSOR_PB_H_INCLUDED diff --git a/src/protos/servo.pb.c b/src/protos/servo.pb.c index 02c11d9fd..b9e726025 100644 --- a/src/protos/servo.pb.c +++ b/src/protos/servo.pb.c @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #include "servo.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/protos/servo.pb.h b/src/protos/servo.pb.h index 00c060ab5..495373d10 100644 --- a/src/protos/servo.pb.h +++ b/src/protos/servo.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #ifndef PB_WS_SERVO_SERVO_PB_H_INCLUDED #define PB_WS_SERVO_SERVO_PB_H_INCLUDED diff --git a/src/protos/signal.pb.c b/src/protos/signal.pb.c index 2e174583e..ac70a2bde 100644 --- a/src/protos/signal.pb.c +++ b/src/protos/signal.pb.c @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Mon May 11 18:02:45 2026. */ #include "signal.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/protos/signal.pb.h b/src/protos/signal.pb.h index f7f29090e..c823c712e 100644 --- a/src/protos/signal.pb.h +++ b/src/protos/signal.pb.h @@ -1,12 +1,13 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Mon May 11 18:02:45 2026. */ #ifndef PB_WS_SIGNAL_SIGNAL_PB_H_INCLUDED #define PB_WS_SIGNAL_SIGNAL_PB_H_INCLUDED #include #include "error.pb.h" +#include "expander.pb.h" #include "checkin.pb.h" -#include "analogio.pb.h" +#include "analogin.pb.h" #include "digitalio.pb.h" #include "display.pb.h" #include "ds18x20.pb.h" @@ -38,7 +39,7 @@ typedef struct _ws_signal_BrokerToDevice { ws_sleep_B2D sleep; /* Component Interactions */ ws_digitalio_B2D digitalio; - ws_analogio_B2D analogio; + ws_analogin_B2D analogin; ws_servo_B2D servo; ws_pwm_B2D pwm; ws_pixels_B2D pixels; @@ -47,6 +48,7 @@ typedef struct _ws_signal_BrokerToDevice { ws_uart_B2D uart; ws_i2c_B2D i2c; ws_gps_B2D gps; + ws_expander_B2D expander; } payload; } ws_signal_BrokerToDevice; @@ -65,7 +67,7 @@ typedef struct _ws_signal_DeviceToBroker { ws_sleep_D2B sleep; /* Component Interactions */ ws_digitalio_D2B digitalio; - ws_analogio_D2B analogio; + ws_analogin_D2B analogin; ws_servo_D2B servo; ws_pwm_D2B pwm; ws_pixels_D2B pixels; @@ -74,6 +76,7 @@ typedef struct _ws_signal_DeviceToBroker { ws_uart_D2B uart; ws_i2c_D2B i2c; ws_gps_D2B gps; + ws_expander_D2B expander; } payload; } ws_signal_DeviceToBroker; @@ -93,7 +96,7 @@ extern "C" { #define ws_signal_BrokerToDevice_checkin_tag 20 #define ws_signal_BrokerToDevice_sleep_tag 21 #define ws_signal_BrokerToDevice_digitalio_tag 30 -#define ws_signal_BrokerToDevice_analogio_tag 31 +#define ws_signal_BrokerToDevice_analogin_tag 31 #define ws_signal_BrokerToDevice_servo_tag 32 #define ws_signal_BrokerToDevice_pwm_tag 33 #define ws_signal_BrokerToDevice_pixels_tag 34 @@ -102,11 +105,12 @@ extern "C" { #define ws_signal_BrokerToDevice_uart_tag 37 #define ws_signal_BrokerToDevice_i2c_tag 38 #define ws_signal_BrokerToDevice_gps_tag 39 +#define ws_signal_BrokerToDevice_expander_tag 40 #define ws_signal_DeviceToBroker_error_tag 10 #define ws_signal_DeviceToBroker_checkin_tag 20 #define ws_signal_DeviceToBroker_sleep_tag 21 #define ws_signal_DeviceToBroker_digitalio_tag 30 -#define ws_signal_DeviceToBroker_analogio_tag 31 +#define ws_signal_DeviceToBroker_analogin_tag 31 #define ws_signal_DeviceToBroker_servo_tag 32 #define ws_signal_DeviceToBroker_pwm_tag 33 #define ws_signal_DeviceToBroker_pixels_tag 34 @@ -115,6 +119,7 @@ extern "C" { #define ws_signal_DeviceToBroker_uart_tag 37 #define ws_signal_DeviceToBroker_i2c_tag 38 #define ws_signal_DeviceToBroker_gps_tag 39 +#define ws_signal_DeviceToBroker_expander_tag 40 /* Struct field encoding specification for nanopb */ #define ws_signal_BrokerToDevice_FIELDLIST(X, a) \ @@ -122,7 +127,7 @@ X(a, STATIC, ONEOF, MSG_W_CB, (payload,error,payload.error), 10) \ X(a, STATIC, ONEOF, MSG_W_CB, (payload,checkin,payload.checkin), 20) \ X(a, STATIC, ONEOF, MSG_W_CB, (payload,sleep,payload.sleep), 21) \ X(a, STATIC, ONEOF, MSG_W_CB, (payload,digitalio,payload.digitalio), 30) \ -X(a, STATIC, ONEOF, MSG_W_CB, (payload,analogio,payload.analogio), 31) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,analogin,payload.analogin), 31) \ X(a, STATIC, ONEOF, MSG_W_CB, (payload,servo,payload.servo), 32) \ X(a, STATIC, ONEOF, MSG_W_CB, (payload,pwm,payload.pwm), 33) \ X(a, STATIC, ONEOF, MSG_W_CB, (payload,pixels,payload.pixels), 34) \ @@ -130,14 +135,15 @@ X(a, STATIC, ONEOF, MSG_W_CB, (payload,ds18x20,payload.ds18x20), 35) \ X(a, STATIC, ONEOF, MSG_W_CB, (payload,display,payload.display), 36) \ X(a, STATIC, ONEOF, MSG_W_CB, (payload,uart,payload.uart), 37) \ X(a, STATIC, ONEOF, MSG_W_CB, (payload,i2c,payload.i2c), 38) \ -X(a, STATIC, ONEOF, MSG_W_CB, (payload,gps,payload.gps), 39) +X(a, STATIC, ONEOF, MSG_W_CB, (payload,gps,payload.gps), 39) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,expander,payload.expander), 40) #define ws_signal_BrokerToDevice_CALLBACK NULL #define ws_signal_BrokerToDevice_DEFAULT NULL #define ws_signal_BrokerToDevice_payload_error_MSGTYPE ws_error_ErrorB2D #define ws_signal_BrokerToDevice_payload_checkin_MSGTYPE ws_checkin_B2D #define ws_signal_BrokerToDevice_payload_sleep_MSGTYPE ws_sleep_B2D #define ws_signal_BrokerToDevice_payload_digitalio_MSGTYPE ws_digitalio_B2D -#define ws_signal_BrokerToDevice_payload_analogio_MSGTYPE ws_analogio_B2D +#define ws_signal_BrokerToDevice_payload_analogin_MSGTYPE ws_analogin_B2D #define ws_signal_BrokerToDevice_payload_servo_MSGTYPE ws_servo_B2D #define ws_signal_BrokerToDevice_payload_pwm_MSGTYPE ws_pwm_B2D #define ws_signal_BrokerToDevice_payload_pixels_MSGTYPE ws_pixels_B2D @@ -146,13 +152,14 @@ X(a, STATIC, ONEOF, MSG_W_CB, (payload,gps,payload.gps), 39) #define ws_signal_BrokerToDevice_payload_uart_MSGTYPE ws_uart_B2D #define ws_signal_BrokerToDevice_payload_i2c_MSGTYPE ws_i2c_B2D #define ws_signal_BrokerToDevice_payload_gps_MSGTYPE ws_gps_B2D +#define ws_signal_BrokerToDevice_payload_expander_MSGTYPE ws_expander_B2D #define ws_signal_DeviceToBroker_FIELDLIST(X, a) \ X(a, STATIC, ONEOF, MSG_W_CB, (payload,error,payload.error), 10) \ X(a, STATIC, ONEOF, MSG_W_CB, (payload,checkin,payload.checkin), 20) \ X(a, STATIC, ONEOF, MSG_W_CB, (payload,sleep,payload.sleep), 21) \ X(a, STATIC, ONEOF, MSG_W_CB, (payload,digitalio,payload.digitalio), 30) \ -X(a, STATIC, ONEOF, MSG_W_CB, (payload,analogio,payload.analogio), 31) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,analogin,payload.analogin), 31) \ X(a, STATIC, ONEOF, MSG_W_CB, (payload,servo,payload.servo), 32) \ X(a, STATIC, ONEOF, MSG_W_CB, (payload,pwm,payload.pwm), 33) \ X(a, STATIC, ONEOF, MSG_W_CB, (payload,pixels,payload.pixels), 34) \ @@ -160,14 +167,15 @@ X(a, STATIC, ONEOF, MSG_W_CB, (payload,ds18x20,payload.ds18x20), 35) \ X(a, STATIC, ONEOF, MSG_W_CB, (payload,display,payload.display), 36) \ X(a, STATIC, ONEOF, MSG_W_CB, (payload,uart,payload.uart), 37) \ X(a, STATIC, ONEOF, MSG_W_CB, (payload,i2c,payload.i2c), 38) \ -X(a, STATIC, ONEOF, MSG_W_CB, (payload,gps,payload.gps), 39) +X(a, STATIC, ONEOF, MSG_W_CB, (payload,gps,payload.gps), 39) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,expander,payload.expander), 40) #define ws_signal_DeviceToBroker_CALLBACK NULL #define ws_signal_DeviceToBroker_DEFAULT NULL #define ws_signal_DeviceToBroker_payload_error_MSGTYPE ws_error_ErrorD2B #define ws_signal_DeviceToBroker_payload_checkin_MSGTYPE ws_checkin_D2B #define ws_signal_DeviceToBroker_payload_sleep_MSGTYPE ws_sleep_D2B #define ws_signal_DeviceToBroker_payload_digitalio_MSGTYPE ws_digitalio_D2B -#define ws_signal_DeviceToBroker_payload_analogio_MSGTYPE ws_analogio_D2B +#define ws_signal_DeviceToBroker_payload_analogin_MSGTYPE ws_analogin_D2B #define ws_signal_DeviceToBroker_payload_servo_MSGTYPE ws_servo_D2B #define ws_signal_DeviceToBroker_payload_pwm_MSGTYPE ws_pwm_D2B #define ws_signal_DeviceToBroker_payload_pixels_MSGTYPE ws_pixels_D2B @@ -176,6 +184,7 @@ X(a, STATIC, ONEOF, MSG_W_CB, (payload,gps,payload.gps), 39) #define ws_signal_DeviceToBroker_payload_uart_MSGTYPE ws_uart_D2B #define ws_signal_DeviceToBroker_payload_i2c_MSGTYPE ws_i2c_D2B #define ws_signal_DeviceToBroker_payload_gps_MSGTYPE ws_gps_D2B +#define ws_signal_DeviceToBroker_payload_expander_MSGTYPE ws_expander_D2B extern const pb_msgdesc_t ws_signal_BrokerToDevice_msg; extern const pb_msgdesc_t ws_signal_DeviceToBroker_msg; @@ -185,17 +194,17 @@ extern const pb_msgdesc_t ws_signal_DeviceToBroker_msg; #define ws_signal_DeviceToBroker_fields &ws_signal_DeviceToBroker_msg /* Maximum encoded size of messages (where known) */ -#if defined(ws_checkin_B2D_size) && defined(ws_digitalio_B2D_size) && defined(ws_display_B2D_size) && defined(ws_uart_B2D_size) && defined(ws_gps_B2D_size) -union ws_signal_BrokerToDevice_payload_size_union {char f20[(7 + ws_checkin_B2D_size)]; char f30[(7 + ws_digitalio_B2D_size)]; char f36[(7 + ws_display_B2D_size)]; char f37[(7 + ws_uart_B2D_size)]; char f39[(7 + ws_gps_B2D_size)]; char f0[91];}; +#if defined(ws_checkin_B2D_size) && defined(ws_digitalio_B2D_size) && defined(ws_display_B2D_size) && defined(ws_uart_B2D_size) && defined(ws_i2c_B2D_size) && defined(ws_gps_B2D_size) +union ws_signal_BrokerToDevice_payload_size_union {char f20[(7 + ws_checkin_B2D_size)]; char f30[(7 + ws_digitalio_B2D_size)]; char f36[(7 + ws_display_B2D_size)]; char f37[(7 + ws_uart_B2D_size)]; char f38[(7 + ws_i2c_B2D_size)]; char f39[(7 + ws_gps_B2D_size)]; char f0[95];}; #endif -#if defined(ws_error_ErrorD2B_size) && defined(ws_digitalio_D2B_size) && defined(ws_analogio_D2B_size) && defined(ws_pixels_D2B_size) && defined(ws_ds18x20_D2B_size) && defined(ws_display_D2B_size) && defined(ws_uart_D2B_size) && defined(ws_i2c_D2B_size) && defined(ws_gps_D2B_size) -union ws_signal_DeviceToBroker_payload_size_union {char f10[(6 + ws_error_ErrorD2B_size)]; char f30[(7 + ws_digitalio_D2B_size)]; char f31[(7 + ws_analogio_D2B_size)]; char f34[(7 + ws_pixels_D2B_size)]; char f35[(7 + ws_ds18x20_D2B_size)]; char f36[(7 + ws_display_D2B_size)]; char f37[(7 + ws_uart_D2B_size)]; char f38[(7 + ws_i2c_D2B_size)]; char f39[(7 + ws_gps_D2B_size)]; char f0[96];}; +#if defined(ws_error_ErrorD2B_size) && defined(ws_digitalio_D2B_size) && defined(ws_analogin_D2B_size) && defined(ws_pixels_D2B_size) && defined(ws_ds18x20_D2B_size) && defined(ws_display_D2B_size) && defined(ws_uart_D2B_size) && defined(ws_i2c_D2B_size) && defined(ws_gps_D2B_size) +union ws_signal_DeviceToBroker_payload_size_union {char f10[(6 + ws_error_ErrorD2B_size)]; char f30[(7 + ws_digitalio_D2B_size)]; char f31[(7 + ws_analogin_D2B_size)]; char f34[(7 + ws_pixels_D2B_size)]; char f35[(7 + ws_ds18x20_D2B_size)]; char f36[(7 + ws_display_D2B_size)]; char f37[(7 + ws_uart_D2B_size)]; char f38[(7 + ws_i2c_D2B_size)]; char f39[(7 + ws_gps_D2B_size)]; char f0[96];}; #endif -#if defined(ws_checkin_B2D_size) && defined(ws_digitalio_B2D_size) && defined(ws_display_B2D_size) && defined(ws_uart_B2D_size) && defined(ws_gps_B2D_size) +#if defined(ws_checkin_B2D_size) && defined(ws_digitalio_B2D_size) && defined(ws_display_B2D_size) && defined(ws_uart_B2D_size) && defined(ws_i2c_B2D_size) && defined(ws_gps_B2D_size) #define WS_SIGNAL_SIGNAL_PB_H_MAX_SIZE ws_signal_BrokerToDevice_size #define ws_signal_BrokerToDevice_size (0 + sizeof(union ws_signal_BrokerToDevice_payload_size_union)) #endif -#if defined(ws_error_ErrorD2B_size) && defined(ws_digitalio_D2B_size) && defined(ws_analogio_D2B_size) && defined(ws_pixels_D2B_size) && defined(ws_ds18x20_D2B_size) && defined(ws_display_D2B_size) && defined(ws_uart_D2B_size) && defined(ws_i2c_D2B_size) && defined(ws_gps_D2B_size) +#if defined(ws_error_ErrorD2B_size) && defined(ws_digitalio_D2B_size) && defined(ws_analogin_D2B_size) && defined(ws_pixels_D2B_size) && defined(ws_ds18x20_D2B_size) && defined(ws_display_D2B_size) && defined(ws_uart_D2B_size) && defined(ws_i2c_D2B_size) && defined(ws_gps_D2B_size) #define ws_signal_DeviceToBroker_size (0 + sizeof(union ws_signal_DeviceToBroker_payload_size_union)) #endif diff --git a/src/protos/sleep.pb.c b/src/protos/sleep.pb.c index e3810c31a..cf205a53a 100644 --- a/src/protos/sleep.pb.c +++ b/src/protos/sleep.pb.c @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #include "sleep.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/protos/sleep.pb.h b/src/protos/sleep.pb.h index 35f175c09..8b9ebe337 100644 --- a/src/protos/sleep.pb.h +++ b/src/protos/sleep.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #ifndef PB_WS_SLEEP_SLEEP_PB_H_INCLUDED #define PB_WS_SLEEP_SLEEP_PB_H_INCLUDED diff --git a/src/protos/uart.pb.c b/src/protos/uart.pb.c index 853c45fbe..1baaa744d 100644 --- a/src/protos/uart.pb.c +++ b/src/protos/uart.pb.c @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #include "uart.pb.h" #if PB_PROTO_HEADER_VERSION != 40 diff --git a/src/protos/uart.pb.h b/src/protos/uart.pb.h index 1166c3781..c901b038e 100644 --- a/src/protos/uart.pb.h +++ b/src/protos/uart.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8 at Thu Apr 9 23:30:13 2026. */ +/* Generated by nanopb-0.4.8 at Wed Apr 22 16:43:09 2026. */ #ifndef PB_WS_UART_UART_PB_H_INCLUDED #define PB_WS_UART_UART_PB_H_INCLUDED diff --git a/src/provisioning/littlefs/WipperSnapper_LittleFS.cpp b/src/provisioning/littlefs/WipperSnapper_LittleFS.cpp index d6b4916ca..c1a910c71 100644 --- a/src/provisioning/littlefs/WipperSnapper_LittleFS.cpp +++ b/src/provisioning/littlefs/WipperSnapper_LittleFS.cpp @@ -88,7 +88,8 @@ void WipperSnapper_LittleFS::parseSecrets() { if (i >= 3) { WS_DEBUG_PRINT("WARNING: More than 3 networks in secrets.json, " "only the first 3 will be used. Not using "); - WS_DEBUG_PRINTLNVAR(altnetworks[i]["network_ssid"].as()); + WS_DEBUG_PRINTLNVAR( + altnetworks[i]["network_ssid"].as()); break; } convertFromJson(altnetworks[i], Ws._multiNetworksV2[i]); diff --git a/src/provisioning/sdcard/ws_sdcard.cpp b/src/provisioning/sdcard/ws_sdcard.cpp index b9909b45d..b337355b8 100644 --- a/src/provisioning/sdcard/ws_sdcard.cpp +++ b/src/provisioning/sdcard/ws_sdcard.cpp @@ -326,9 +326,9 @@ void ws_sdcard::CheckIn(const JsonObject &exported_from_device) { // Configure controllers Ws.digital_io_controller->SetMaxDigitalPins( exported_from_device["maxDigitalPins"] | 0); - Ws.analogio_controller->SetTotalAnalogPins( + Ws.analogin_controller->SetMaxAnalogPins( exported_from_device["maxAnalogPins"] | 0); - Ws.analogio_controller->SetRefVoltage(exported_from_device["refVoltage"] | + Ws.analogin_controller->SetRefVoltage(exported_from_device["refVoltage"] | 0.0f); // Since `secrets.json` is unused in offline mode, use the status LED // brightness from here instead @@ -510,38 +510,38 @@ bool ws_sdcard::ParseDigitalIOAdd(ws_digitalio_Add &msg_DigitalIOAdd, } /*! - @brief Parses an AnalogIOAdd message from the JSON configuration file. - @param msg_AnalogIOAdd - The AnalogIOAdd message to populate. + @brief Parses an AnalogInAdd message from the JSON configuration file. + @param msg_AnalogInAdd + The AnalogInAdd message to populate. @param pin The GPIO pin name. @param period The desired period to read the sensor, in seconds. @param mode The sensor read mode. - @returns True if the AnalogIOAdd message was successfully parsed, + @returns True if the AnalogInAdd message was successfully parsed, False otherwise. */ -bool ws_sdcard::ParseAnalogIOAdd(ws_analogio_Add &msg_AnalogIOAdd, +bool ws_sdcard::ParseAnalogInAdd(ws_analogin_Add &msg_AnalogInAdd, const char *pin, float period, const char *mode) { if (!ValidateJSONKey(pin, "[SD] Parsing Error: Analog pin name not found!")) return false; - strcpy(msg_AnalogIOAdd.pin_name, pin); + strcpy(msg_AnalogInAdd.pin_name, pin); if (period == 0.0) { WS_DEBUG_PRINTLN("[SD] Parsing Error: Analog pin period less than 1.0 " "seconds or not found!"); return false; } - msg_AnalogIOAdd.period = period; + msg_AnalogInAdd.period = period; if (!ValidateJSONKey(mode, "[SD] Parsing Error: Analog pin read mode not found!")) return false; - msg_AnalogIOAdd.read_mode = ParseSensorType(mode); - if (msg_AnalogIOAdd.read_mode == ws_sensor_Type_T_UNSPECIFIED) { + msg_AnalogInAdd.read_mode = ParseSensorType(mode); + if (msg_AnalogInAdd.read_mode == ws_sensor_Type_T_UNSPECIFIED) { WS_DEBUG_PRINT("[SD] Parsing Error: Unknown read mode found: "); WS_DEBUG_PRINTLNVAR(mode); return false; @@ -969,22 +969,22 @@ bool ws_sdcard::parseConfigFile() { msg_signal_b2d.which_payload = ws_signal_BrokerToDevice_digitalio_tag; msg_signal_b2d.payload.digitalio.payload.add = msg_DigitalIOAdd; msg_signal_b2d.payload.digitalio.which_payload = ws_digitalio_B2D_add_tag; - } else if (strcmp(component_api_type, "analogio") == 0) { - WS_DEBUG_PRINTLN("[SD] AnalogIO component found, decoding JSON to PB..."); - ws_analogio_Add msg_AnalogIOAdd = ws_analogio_Add_init_default; - if (!ParseAnalogIOAdd(msg_AnalogIOAdd, + } else if (strcmp(component_api_type, "analogin") == 0) { + WS_DEBUG_PRINTLN("[SD] AnalogIn component found, decoding JSON to PB..."); + ws_analogin_Add msg_AnalogInAdd = ws_analogin_Add_init_default; + if (!ParseAnalogInAdd(msg_AnalogInAdd, component["pinName"] | UNKNOWN_VALUE, component["period"] | 0.0, component["analogReadMode"] | UNKNOWN_VALUE)) { WS_DEBUG_PRINTLN( - "[SD] Runtime Error: Unable to parse AnalogIO Component, Pin: "); + "[SD] Runtime Error: Unable to parse AnalogIn Component, Pin: "); WS_DEBUG_PRINTLNVAR(component["pinName"] | UNKNOWN_VALUE); return false; } - msg_signal_b2d.which_payload = ws_signal_BrokerToDevice_analogio_tag; - msg_signal_b2d.payload.analogio.payload.add = msg_AnalogIOAdd; - msg_signal_b2d.payload.analogio.which_payload = ws_analogio_B2D_add_tag; + msg_signal_b2d.which_payload = ws_signal_BrokerToDevice_analogin_tag; + msg_signal_b2d.payload.analogin.payload.add = msg_AnalogInAdd; + msg_signal_b2d.payload.analogin.which_payload = ws_analogin_B2D_add_tag; } else if (strcmp(component_api_type, "ds18x20") == 0) { WS_DEBUG_PRINTLN("[SD] Ds18x20 component found, decoding JSON to PB..."); ws_ds18x20_Add msg_DS18X20Add = ws_ds18x20_Add_init_default; @@ -1390,7 +1390,7 @@ void ws_sdcard::waitForSerialConfig() { "}," "\"components\": [" "{" - "\"componentAPI\": \"analogio\"," + "\"componentAPI\": \"analogin\"," "\"name\": \"Analog Pin\"," "\"pinName\": \"D14\"," "\"type\": \"analog_pin\"," @@ -1402,7 +1402,7 @@ void ws_sdcard::waitForSerialConfig() { "\"isPin\": true" "}," "{" - "\"componentAPI\": \"analogio\"," + "\"componentAPI\": \"analogin\"," "\"name\": \"Analog Pin\"," "\"pinName\": \"D27\"," "\"type\": \"analog_pin\"," diff --git a/src/provisioning/sdcard/ws_sdcard.h b/src/provisioning/sdcard/ws_sdcard.h index 309f11c87..3f24db983 100644 --- a/src/provisioning/sdcard/ws_sdcard.h +++ b/src/provisioning/sdcard/ws_sdcard.h @@ -98,7 +98,7 @@ class ws_sdcard { bool ParseDigitalIOAdd(ws_digitalio_Add &msg_DigitalIOAdd, const char *pin, float period, bool value, const char *sample_mode, const char *direction, const char *pull); - bool ParseAnalogIOAdd(ws_analogio_Add &msg_AnalogIOAdd, const char *pin, + bool ParseAnalogInAdd(ws_analogin_Add &msg_AnalogInAdd, const char *pin, float period, const char *mode); bool ParseDS18X20Add(ws_ds18x20_Add &msg_DS18X20Add, const char *pin, int resolution, float period, int num_sensors, diff --git a/src/wippersnapper.cpp b/src/wippersnapper.cpp index 04491795d..329ac7650 100644 --- a/src/wippersnapper.cpp +++ b/src/wippersnapper.cpp @@ -40,12 +40,12 @@ wippersnapper Ws; */ wippersnapper::wippersnapper() : _mqttV2(nullptr), sensor_model(nullptr), error_controller(nullptr), - digital_io_controller(nullptr), analogio_controller(nullptr), - _ds18x20_controller(nullptr), _gps_controller(nullptr), - _i2c_controller(nullptr), _uart_controller(nullptr), - _pixels_controller(nullptr), _pwm_controller(nullptr), - _servo_controller(nullptr), _wdt(nullptr), _device_uidV2(nullptr), - _mqtt_client_id(nullptr) { + digital_io_controller(nullptr), analogin_controller(nullptr), + _ds18x20_controller(nullptr), _expander_controller(nullptr), + _gps_controller(nullptr), _i2c_controller(nullptr), + _uart_controller(nullptr), _pixels_controller(nullptr), + _pwm_controller(nullptr), _servo_controller(nullptr), _wdt(nullptr), + _device_uidV2(nullptr), _mqtt_client_id(nullptr) { // Initialize WDT wrapper _wdt = new ws_wdt(); @@ -54,8 +54,9 @@ wippersnapper::wippersnapper() // Initialize controller classes digital_io_controller = new DigitalIOController(); - analogio_controller = new AnalogIOController(); + analogin_controller = new AnalogInController(); _ds18x20_controller = new DS18X20Controller(); + _expander_controller = new ExpanderController(); _gps_controller = new GPSController(); _i2c_controller = new I2cController(); _uart_controller = new UARTController(); @@ -76,8 +77,9 @@ wippersnapper::~wippersnapper() { delete this->sensor_model; // delete this->error_controller; // TODO: Why is this commented out? delete this->digital_io_controller; - delete this->analogio_controller; + delete this->analogin_controller; delete this->_ds18x20_controller; + delete this->_expander_controller; delete this->_gps_controller; delete this->_i2c_controller; delete this->_uart_controller; @@ -108,6 +110,7 @@ void wippersnapper::_connect() { /*! @brief Disconnect Wippersnapper MQTT session and network. + @param wifi_off True to also power off the WiFi radio. */ void wippersnapper::_disconnect(bool wifi_off) { (void)wifi_off; // Avoid unused parameter warning for some network interfaces @@ -310,8 +313,8 @@ bool routeBrokerToDevice(pb_istream_t *stream, const pb_field_t *field, return handleCheckinResponse(stream); case ws_signal_BrokerToDevice_digitalio_tag: return Ws.digital_io_controller->Router(stream); - case ws_signal_BrokerToDevice_analogio_tag: - return Ws.analogio_controller->Router(stream); + case ws_signal_BrokerToDevice_analogin_tag: + return Ws.analogin_controller->Router(stream); case ws_signal_BrokerToDevice_pixels_tag: return Ws._pixels_controller->Router(stream); case ws_signal_BrokerToDevice_pwm_tag: @@ -324,6 +327,8 @@ bool routeBrokerToDevice(pb_istream_t *stream, const pb_field_t *field, return Ws._i2c_controller->Router(stream); case ws_signal_BrokerToDevice_uart_tag: return Ws._uart_controller->Router(stream); + case ws_signal_BrokerToDevice_expander_tag: + return Ws._expander_controller->Router(stream); case ws_signal_BrokerToDevice_gps_tag: return Ws._gps_controller->Router(stream); #if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_RP2350) @@ -738,9 +743,9 @@ bool wippersnapper::PublishD2b(pb_size_t which_payload, void *payload) { msg->which_payload = ws_signal_DeviceToBroker_digitalio_tag; msg->payload.digitalio = *(ws_digitalio_D2B *)payload; break; - case ws_signal_DeviceToBroker_analogio_tag: - msg->which_payload = ws_signal_DeviceToBroker_analogio_tag; - msg->payload.analogio = *(ws_analogio_D2B *)payload; + case ws_signal_DeviceToBroker_analogin_tag: + msg->which_payload = ws_signal_DeviceToBroker_analogin_tag; + msg->payload.analogin = *(ws_analogin_D2B *)payload; break; case ws_signal_DeviceToBroker_servo_tag: msg->which_payload = ws_signal_DeviceToBroker_servo_tag; @@ -766,6 +771,10 @@ bool wippersnapper::PublishD2b(pb_size_t which_payload, void *payload) { msg->which_payload = ws_signal_DeviceToBroker_i2c_tag; msg->payload.i2c = *(ws_i2c_D2B *)payload; break; + case ws_signal_DeviceToBroker_expander_tag: + msg->which_payload = ws_signal_DeviceToBroker_expander_tag; + msg->payload.expander = *(ws_expander_D2B *)payload; + break; case ws_signal_DeviceToBroker_gps_tag: msg->which_payload = ws_signal_DeviceToBroker_gps_tag; msg->payload.gps = *(ws_gps_D2B *)payload; @@ -1076,7 +1085,7 @@ void wippersnapper::loop() { Ws.digital_io_controller->update(); // Process all analog input events - Ws.analogio_controller->update(); + Ws.analogin_controller->update(); // Process all DS18x20 sensor events Ws._ds18x20_controller->update(); @@ -1124,8 +1133,8 @@ void wippersnapper::loopSleep() { all_controllers_complete = false; } - if (!Ws.analogio_controller->UpdateComplete()) { - Ws.analogio_controller->update(true); + if (!Ws.analogin_controller->UpdateComplete()) { + Ws.analogin_controller->update(true); all_controllers_complete = false; } @@ -1207,7 +1216,7 @@ void wippersnapper::loopSleep() { */ void wippersnapper::ResetAllControllerFlags() { Ws.digital_io_controller->ResetFlags(); - Ws.analogio_controller->ResetFlags(); + Ws.analogin_controller->ResetFlags(); Ws._ds18x20_controller->ResetFlags(); Ws._i2c_controller->ResetFlags(); Ws._uart_controller->ResetFlags(); diff --git a/src/wippersnapper.h b/src/wippersnapper.h index e39b46011..97a8f623f 100644 --- a/src/wippersnapper.h +++ b/src/wippersnapper.h @@ -36,8 +36,10 @@ #define WS_DEBUG_PRINT(x) \ { WS_PRINTER.print(F(x)); } /**< Print debug message to serial (Flash) */ #define WS_DEBUG_PRINTLN(x) \ - { WS_PRINTER.println(F(x)); } /**< Print debug message with newline (Flash) \ - */ + { \ + WS_PRINTER.println(F(x)); \ + } /**< Print debug message with newline (Flash) \ + */ #else // Other platforms: Standard variadic macros #define WS_DEBUG_PRINT(...) \ @@ -45,7 +47,8 @@ #define WS_DEBUG_PRINTLN(...) \ { \ WS_PRINTER.println(__VA_ARGS__); \ - } /**< Print debug message with newline */ + } /**< Print debug message with newline \ + */ #endif // Variable printing macros - use for non-string-literal arguments @@ -117,11 +120,12 @@ #endif // Components (API v2) -#include "components/analogIO/controller.h" +#include "components/analogIn/controller.h" #include "components/checkin/model.h" #include "components/digitalIO/controller.h" #include "components/ds18x20/controller.h" #include "components/error/controller.h" +#include "components/expander/controller.h" #include "components/gps/controller.h" #include "components/i2c/controller.h" #include "components/pixels/controller.h" @@ -163,9 +167,10 @@ class WipperSnapper_LittleFS; class ws_sdcard; class CheckinModel; class ErrorController; +class ExpanderController; class SensorModel; class DigitalIOController; -class AnalogIOController; +class AnalogInController; class DS18X20Controller; class GPSController; class I2cController; @@ -255,10 +260,12 @@ class wippersnapper { SensorModel *sensor_model = nullptr; ///< Instance of SensorModel class DigitalIOController *digital_io_controller = nullptr; ///< Instance of DigitalIO controller class - AnalogIOController *analogio_controller = - nullptr; ///< Instance of AnalogIO controller + AnalogInController *analogin_controller = + nullptr; ///< Instance of AnalogIn controller DS18X20Controller *_ds18x20_controller = - nullptr; ///< Instance of DS18X20 controller + nullptr; ///< Instance of DS18X20 controller + ExpanderController *_expander_controller = + nullptr; ///< Instance of Expander controller GPSController *_gps_controller = nullptr; ///< Instance of GPS controller I2cController *_i2c_controller = nullptr; ///< Instance of I2C controller PixelsController *_pixels_controller = diff --git a/tests/test_offline.py b/tests/test_offline.py index 6ecab7ed9..c25f95c2c 100644 --- a/tests/test_offline.py +++ b/tests/test_offline.py @@ -160,7 +160,7 @@ async def test_invalid_checksum(client): '{"exportVersion": "1.0.0", "exportedBy": "wokwi", "exportedAt": "2024-10-28T18:58:23.976Z", ' '"exportedFromDevice": {"board": "metroesp32s3", "firmwareVersion": "1.0.0-beta.93", ' '"referenceVoltage": 2.6, "totalGPIOPins": 11, "totalAnalogPins": 6}, ' - '"components": [{"componentAPI": "analogio", "name": "Analog Pin", "pinName": "D14", ' + '"components": [{"componentAPI": "analogin", "name": "Analog Pin", "pinName": "D14", ' '"type": "analog_pin", "mode": "ANALOG", "direction": "INPUT", "sampleMode": "TIMER", ' '"analogReadMode": "PIN_VALUE", "period": 5, "isPin": true}], "checksum": 5}\\n' ) @@ -176,9 +176,9 @@ async def test_valid_checksum(client): '{"exportVersion": "1.0.0", "exportedBy": "wokwi", "exportedAt": "2024-10-28T18:58:23.976Z", ' '"exportedFromDevice": {"board": "metroesp32s3", "firmwareVersion": "1.0.0-beta.93", ' '"referenceVoltage": 2.6, "totalGPIOPins": 11, "totalAnalogPins": 6}, ' - '"components": [{"componentAPI": "analogio", "name": "Analog Pin", "pinName": "D14", ' + '"components": [{"componentAPI": "analogin", "name": "Analog Pin", "pinName": "D14", ' '"type": "analog_pin", "mode": "ANALOG", "direction": "INPUT", "sampleMode": "TIMER", ' - '"analogReadMode": "raw", "period": 5, "isPin": true}], "checksum": 28}\\n' + '"analogReadMode": "raw", "period": 5, "isPin": true}], "checksum": 27}\\n' ) await wait_for_text("[SD] Successfully deserialized JSON config file!") @@ -230,14 +230,14 @@ async def test_analog_input(client): '{"exportVersion": "1.0.0", "exportedBy": "wokwi", "exportedAt": "2024-10-28T18:58:23.976Z", ' '"exportedFromDevice": {"board": "metroesp32s3", "firmwareVersion": "1.0.0-beta.93", ' '"referenceVoltage": 2.6, "totalGPIOPins": 11, "totalAnalogPins": 6}, ' - '"components": [{"componentAPI": "analogio", "name": "Analog Pin", "pinName": "D14", ' + '"components": [{"componentAPI": "analogin", "name": "Analog Pin", "pinName": "D14", ' '"type": "analog_pin", "mode": "ANALOG", "direction": "INPUT", "sampleMode": "TIMER", ' - '"analogReadMode": "raw", "period": 5, "isPin": true}], "checksum": 149}\\n' + '"analogReadMode": "raw", "period": 5, "isPin": true}], "checksum": 148}\\n' ) await client.serial_write("\n") # Wait for pin configuration - await wait_for_text("[analogio] Added new pin:") + await wait_for_text("[analogin] Added new pin:") await wait_for_text("Pin Name: 14") await wait_for_text("Period: 5000") await wait_for_text("Read Mode: 18")