diff --git a/main/devices/Device.hpp b/main/devices/Device.hpp index 2b44ae4d..d01bbe9e 100644 --- a/main/devices/Device.hpp +++ b/main/devices/Device.hpp @@ -239,7 +239,7 @@ class ConsoleProvider : public LogConsumer { : logRecords(logRecords) , recordedLevel(recordedLevel) { Serial.begin(115200); - Serial1.begin(115200, SERIAL_8N1, pins::RXD0, pins::TXD0); + Serial1.begin(115200, SERIAL_8N1, pins::RXD0->getGpio(), pins::TXD0->getGpio()); #if Serial != Serial0 Serial0.begin(115200); #endif diff --git a/main/devices/DeviceDefinition.hpp b/main/devices/DeviceDefinition.hpp index a12cad4a..bcd9d312 100644 --- a/main/devices/DeviceDefinition.hpp +++ b/main/devices/DeviceDefinition.hpp @@ -26,6 +26,7 @@ #include #include #include +#include using namespace farmhub::kernel; using namespace farmhub::kernel::drivers; @@ -64,7 +65,7 @@ class DeviceConfiguration : public ConfigurationSection { template class DeviceDefinition { public: - DeviceDefinition(gpio_num_t statusPin, gpio_num_t bootPin) + DeviceDefinition(PinPtr statusPin, InternalPinPtr bootPin) : statusLed("status", statusPin) , bootPin(bootPin) { } @@ -78,6 +79,7 @@ class DeviceDefinition { peripheralManager.registerFactory(electricFenceMonitorFactory); peripheralManager.registerFactory(bh1750Factory); peripheralManager.registerFactory(tsl2591Factory); + peripheralManager.registerFactory(xl9535Factory); registerDeviceSpecificPeripheralFactories(peripheralManager); } @@ -99,7 +101,7 @@ class DeviceDefinition { LedDriver statusLed; PcntManager pcnt; PwmManager pwm; - const gpio_num_t bootPin; + const InternalPinPtr bootPin; private: ConfigurationFile configFile { FileSystem::get(), "/device-config.json" }; @@ -121,6 +123,8 @@ class DeviceDefinition { farmhub::peripherals::light_sensor::Bh1750Factory bh1750Factory; farmhub::peripherals::light_sensor::Tsl2591Factory tsl2591Factory; + + farmhub::peripherals::multiplexer::Xl9535Factory xl9535Factory; }; } // namespace farmhub::devices diff --git a/main/devices/Pin.hpp b/main/devices/Pin.hpp deleted file mode 100644 index d1cd435e..00000000 --- a/main/devices/Pin.hpp +++ /dev/null @@ -1,83 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -#include - -namespace farmhub::devices { - -class Pin { -public: - static gpio_num_t registerPin(const String& name, gpio_num_t pin) { - GPIO_NUMBERS[name] = pin; - GPIO_NAMES[pin] = name; - return pin; - } - - static gpio_num_t numberOf(const String& name) { - auto it = GPIO_NUMBERS.find(name); - if (it != GPIO_NUMBERS.end()) { - return it->second; - } - return GPIO_NUM_MAX; - } - - static String nameOf(gpio_num_t pin) { - auto it = GPIO_NAMES.find(pin); - if (it == GPIO_NAMES.end()) { - String name = "GPIO_NUM_" + String(pin); - registerPin(name, pin); - return name; - } else { - return it->second; - } - } - - static bool isRegistered(gpio_num_t pin) { - return GPIO_NAMES.find(pin) != GPIO_NAMES.end(); - } - -private: - bool useName; - - static std::map GPIO_NAMES; - static std::map GPIO_NUMBERS; -}; - -std::map Pin::GPIO_NAMES; -std::map Pin::GPIO_NUMBERS; - -} // namespace farmhub::devices - -namespace ArduinoJson { - -using farmhub::devices::Pin; - -template <> -struct Converter { - static void toJson(const gpio_num_t& src, JsonVariant dst) { - if (Pin::isRegistered(src)) { - dst.set(Pin::nameOf(src)); - } else { - dst.set(static_cast(src)); - } - } - - static gpio_num_t fromJson(JsonVariantConst src) { - if (src.is()) { - return Pin::numberOf(src.as()); - } else { - return static_cast(src.as()); - } - } - - static bool checkJson(JsonVariantConst src) { - return src.is() || src.is(); - } -}; - -} // namespace ArduinoJson diff --git a/main/devices/UglyDucklingMk4.hpp b/main/devices/UglyDucklingMk4.hpp index 6dfa15de..dd3487d6 100644 --- a/main/devices/UglyDucklingMk4.hpp +++ b/main/devices/UglyDucklingMk4.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -15,7 +16,6 @@ #include #include -#include using namespace farmhub::kernel; using namespace farmhub::peripherals::chicken_door; @@ -34,33 +34,33 @@ class Mk4Config }; namespace pins { -static gpio_num_t BOOT = Pin::registerPin("BOOT", GPIO_NUM_0); -static gpio_num_t STATUS = Pin::registerPin("STATUS", GPIO_NUM_26); - -static gpio_num_t SOIL_MOISTURE = Pin::registerPin("SOIL_MOISTURE", GPIO_NUM_6); -static gpio_num_t SOIL_TEMP = Pin::registerPin("SOIL_TEMP", GPIO_NUM_7); - -static gpio_num_t VALVE_EN = Pin::registerPin("VALVE_EN", GPIO_NUM_10); -static gpio_num_t VALVE_PH = Pin::registerPin("VALVE_PH", GPIO_NUM_11); -static gpio_num_t VALVE_FAULT = Pin::registerPin("VALVE_FAULT", GPIO_NUM_12); -static gpio_num_t VALVE_SLEEP = Pin::registerPin("VALVE_SLEEP", GPIO_NUM_13); -static gpio_num_t VALVE_MODE1 = Pin::registerPin("VALVE_MODE1", GPIO_NUM_14); -static gpio_num_t VALVE_MODE2 = Pin::registerPin("VALVE_MODE2", GPIO_NUM_15); -static gpio_num_t VALVE_CURRENT = Pin::registerPin("VALVE_CURRENT", GPIO_NUM_16); -static gpio_num_t FLOW = Pin::registerPin("FLOW", GPIO_NUM_17); - -static gpio_num_t SDA = Pin::registerPin("SDA", GPIO_NUM_8); -static gpio_num_t SCL = Pin::registerPin("SCL", GPIO_NUM_9); -static gpio_num_t RXD0 = Pin::registerPin("RXD0", GPIO_NUM_44); -static gpio_num_t TXD0 = Pin::registerPin("TXD0", GPIO_NUM_43); +static InternalPinPtr BOOT = InternalPin::registerPin("BOOT", GPIO_NUM_0); +static InternalPinPtr STATUS = InternalPin::registerPin("STATUS", GPIO_NUM_26); + +static InternalPinPtr SOIL_MOISTURE = InternalPin::registerPin("SOIL_MOISTURE", GPIO_NUM_6); +static InternalPinPtr SOIL_TEMP = InternalPin::registerPin("SOIL_TEMP", GPIO_NUM_7); + +static InternalPinPtr VALVE_EN = InternalPin::registerPin("VALVE_EN", GPIO_NUM_10); +static InternalPinPtr VALVE_PH = InternalPin::registerPin("VALVE_PH", GPIO_NUM_11); +static InternalPinPtr VALVE_FAULT = InternalPin::registerPin("VALVE_FAULT", GPIO_NUM_12); +static InternalPinPtr VALVE_SLEEP = InternalPin::registerPin("VALVE_SLEEP", GPIO_NUM_13); +static InternalPinPtr VALVE_MODE1 = InternalPin::registerPin("VALVE_MODE1", GPIO_NUM_14); +static InternalPinPtr VALVE_MODE2 = InternalPin::registerPin("VALVE_MODE2", GPIO_NUM_15); +static InternalPinPtr VALVE_CURRENT = InternalPin::registerPin("VALVE_CURRENT", GPIO_NUM_16); +static InternalPinPtr FLOW = InternalPin::registerPin("FLOW", GPIO_NUM_17); + +static InternalPinPtr SDA = InternalPin::registerPin("SDA", GPIO_NUM_8); +static InternalPinPtr SCL = InternalPin::registerPin("SCL", GPIO_NUM_9); +static InternalPinPtr RXD0 = InternalPin::registerPin("RXD0", GPIO_NUM_44); +static InternalPinPtr TXD0 = InternalPin::registerPin("TXD0", GPIO_NUM_43); } // namespace pins class UglyDucklingMk4 : public DeviceDefinition { public: UglyDucklingMk4() : DeviceDefinition( - pins::STATUS, - pins::BOOT) { + pins::STATUS, + pins::BOOT) { } void registerDeviceSpecificPeripheralFactories(PeripheralManager& peripheralManager) override { diff --git a/main/devices/UglyDucklingMk5.hpp b/main/devices/UglyDucklingMk5.hpp index 24b16292..6ef9d5df 100644 --- a/main/devices/UglyDucklingMk5.hpp +++ b/main/devices/UglyDucklingMk5.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -13,7 +14,6 @@ #include #include -#include using namespace farmhub::kernel; using namespace farmhub::peripherals::chicken_door; @@ -33,54 +33,54 @@ class Mk5Config }; namespace pins { -static gpio_num_t BOOT = Pin::registerPin("BOOT", GPIO_NUM_0); -static gpio_num_t BATTERY = Pin::registerPin("BATTERY", GPIO_NUM_1); -static gpio_num_t STATUS = Pin::registerPin("STATUS", GPIO_NUM_2); -static gpio_num_t AIPROPI = Pin::registerPin("AIPROPI", GPIO_NUM_4); - -static gpio_num_t IOA1 = Pin::registerPin("A1", GPIO_NUM_5); -static gpio_num_t IOA2 = Pin::registerPin("A2", GPIO_NUM_6); -static gpio_num_t BIPROPI = Pin::registerPin("BIPROPI", GPIO_NUM_7); -static gpio_num_t IOB1 = Pin::registerPin("B1", GPIO_NUM_15); -static gpio_num_t AIN1 = Pin::registerPin("AIN1", GPIO_NUM_16); -static gpio_num_t AIN2 = Pin::registerPin("AIN2", GPIO_NUM_17); -static gpio_num_t BIN1 = Pin::registerPin("BIN1", GPIO_NUM_18); -static gpio_num_t BIN2 = Pin::registerPin("BIN2", GPIO_NUM_8); - -static gpio_num_t DMINUS = Pin::registerPin("D-", GPIO_NUM_19); -static gpio_num_t DPLUS = Pin::registerPin("D+", GPIO_NUM_20); - -static gpio_num_t IOB2 = Pin::registerPin("B2", GPIO_NUM_9); - -static gpio_num_t NSLEEP = Pin::registerPin("NSLEEP", GPIO_NUM_10); -static gpio_num_t NFault = Pin::registerPin("NFault", GPIO_NUM_11); -static gpio_num_t IOC4 = Pin::registerPin("C4", GPIO_NUM_12); -static gpio_num_t IOC3 = Pin::registerPin("C3", GPIO_NUM_13); -static gpio_num_t IOC2 = Pin::registerPin("C2", GPIO_NUM_14); -static gpio_num_t IOC1 = Pin::registerPin("C1", GPIO_NUM_21); -static gpio_num_t IOD4 = Pin::registerPin("D4", GPIO_NUM_47); -static gpio_num_t IOD3 = Pin::registerPin("D3", GPIO_NUM_48); - -static gpio_num_t SDA = Pin::registerPin("SDA", GPIO_NUM_35); -static gpio_num_t SCL = Pin::registerPin("SCL", GPIO_NUM_36); - -static gpio_num_t IOD1 = Pin::registerPin("D1", GPIO_NUM_37); -static gpio_num_t IOD2 = Pin::registerPin("D2", GPIO_NUM_38); - -static gpio_num_t TCK = Pin::registerPin("TCK", GPIO_NUM_39); -static gpio_num_t TDO = Pin::registerPin("TDO", GPIO_NUM_40); -static gpio_num_t TDI = Pin::registerPin("TDI", GPIO_NUM_41); -static gpio_num_t TMS = Pin::registerPin("TMS", GPIO_NUM_42); -static gpio_num_t RXD0 = Pin::registerPin("RXD0", GPIO_NUM_44); -static gpio_num_t TXD0 = Pin::registerPin("TXD0", GPIO_NUM_43); +static InternalPinPtr BOOT = InternalPin::registerPin("BOOT", GPIO_NUM_0); +static InternalPinPtr BATTERY = InternalPin::registerPin("BATTERY", GPIO_NUM_1); +static InternalPinPtr STATUS = InternalPin::registerPin("STATUS", GPIO_NUM_2); +static InternalPinPtr AIPROPI = InternalPin::registerPin("AIPROPI", GPIO_NUM_4); + +static InternalPinPtr IOA1 = InternalPin::registerPin("A1", GPIO_NUM_5); +static InternalPinPtr IOA2 = InternalPin::registerPin("A2", GPIO_NUM_6); +static InternalPinPtr BIPROPI = InternalPin::registerPin("BIPROPI", GPIO_NUM_7); +static InternalPinPtr IOB1 = InternalPin::registerPin("B1", GPIO_NUM_15); +static InternalPinPtr AIN1 = InternalPin::registerPin("AIN1", GPIO_NUM_16); +static InternalPinPtr AIN2 = InternalPin::registerPin("AIN2", GPIO_NUM_17); +static InternalPinPtr BIN1 = InternalPin::registerPin("BIN1", GPIO_NUM_18); +static InternalPinPtr BIN2 = InternalPin::registerPin("BIN2", GPIO_NUM_8); + +static InternalPinPtr DMINUS = InternalPin::registerPin("D-", GPIO_NUM_19); +static InternalPinPtr DPLUS = InternalPin::registerPin("D+", GPIO_NUM_20); + +static InternalPinPtr IOB2 = InternalPin::registerPin("B2", GPIO_NUM_9); + +static InternalPinPtr NSLEEP = InternalPin::registerPin("NSLEEP", GPIO_NUM_10); +static InternalPinPtr NFault = InternalPin::registerPin("NFault", GPIO_NUM_11); +static InternalPinPtr IOC4 = InternalPin::registerPin("C4", GPIO_NUM_12); +static InternalPinPtr IOC3 = InternalPin::registerPin("C3", GPIO_NUM_13); +static InternalPinPtr IOC2 = InternalPin::registerPin("C2", GPIO_NUM_14); +static InternalPinPtr IOC1 = InternalPin::registerPin("C1", GPIO_NUM_21); +static InternalPinPtr IOD4 = InternalPin::registerPin("D4", GPIO_NUM_47); +static InternalPinPtr IOD3 = InternalPin::registerPin("D3", GPIO_NUM_48); + +static InternalPinPtr SDA = InternalPin::registerPin("SDA", GPIO_NUM_35); +static InternalPinPtr SCL = InternalPin::registerPin("SCL", GPIO_NUM_36); + +static InternalPinPtr IOD1 = InternalPin::registerPin("D1", GPIO_NUM_37); +static InternalPinPtr IOD2 = InternalPin::registerPin("D2", GPIO_NUM_38); + +static InternalPinPtr TCK = InternalPin::registerPin("TCK", GPIO_NUM_39); +static InternalPinPtr TDO = InternalPin::registerPin("TDO", GPIO_NUM_40); +static InternalPinPtr TDI = InternalPin::registerPin("TDI", GPIO_NUM_41); +static InternalPinPtr TMS = InternalPin::registerPin("TMS", GPIO_NUM_42); +static InternalPinPtr RXD0 = InternalPin::registerPin("RXD0", GPIO_NUM_44); +static InternalPinPtr TXD0 = InternalPin::registerPin("TXD0", GPIO_NUM_43); } // namespace pins class UglyDucklingMk5 : public DeviceDefinition { public: UglyDucklingMk5() : DeviceDefinition( - pins::STATUS, - pins::BOOT) { + pins::STATUS, + pins::BOOT) { } void registerDeviceSpecificPeripheralFactories(PeripheralManager& peripheralManager) override { diff --git a/main/devices/UglyDucklingMk6.hpp b/main/devices/UglyDucklingMk6.hpp index 559272fd..16e7fa37 100644 --- a/main/devices/UglyDucklingMk6.hpp +++ b/main/devices/UglyDucklingMk6.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -16,7 +17,6 @@ #include #include -#include using namespace farmhub::kernel; using namespace farmhub::peripherals::chicken_door; @@ -27,47 +27,47 @@ using namespace farmhub::peripherals::valve; namespace farmhub::devices { namespace pins { -static gpio_num_t BOOT = Pin::registerPin("BOOT", GPIO_NUM_0); -static gpio_num_t BATTERY = Pin::registerPin("BATTERY", GPIO_NUM_1); -static gpio_num_t STATUS = Pin::registerPin("STATUS", GPIO_NUM_2); -static gpio_num_t STATUS2 = Pin::registerPin("STATUS2", GPIO_NUM_4); - -static gpio_num_t IOB1 = Pin::registerPin("B1", GPIO_NUM_5); -static gpio_num_t IOA1 = Pin::registerPin("A1", GPIO_NUM_6); -static gpio_num_t DIPROPI = Pin::registerPin("DIPROPI", GPIO_NUM_7); -static gpio_num_t IOA2 = Pin::registerPin("A2", GPIO_NUM_15); -static gpio_num_t AIN1 = Pin::registerPin("AIN1", GPIO_NUM_16); -static gpio_num_t AIN2 = Pin::registerPin("AIN2", GPIO_NUM_17); -static gpio_num_t BIN2 = Pin::registerPin("BIN2", GPIO_NUM_18); -static gpio_num_t BIN1 = Pin::registerPin("BIN1", GPIO_NUM_8); - -static gpio_num_t DMINUS = Pin::registerPin("D-", GPIO_NUM_19); -static gpio_num_t DPLUS = Pin::registerPin("D+", GPIO_NUM_20); - -static gpio_num_t LEDA_RED = Pin::registerPin("LEDA_RED", GPIO_NUM_46); -static gpio_num_t LEDA_GREEN = Pin::registerPin("LEDA_GREEN", GPIO_NUM_9); - -static gpio_num_t NFault = Pin::registerPin("NFault", GPIO_NUM_11); -static gpio_num_t BTN1 = Pin::registerPin("BTN1", GPIO_NUM_12); -static gpio_num_t BTN2 = Pin::registerPin("BTN2", GPIO_NUM_13); -static gpio_num_t IOC4 = Pin::registerPin("C4", GPIO_NUM_14); -static gpio_num_t IOC3 = Pin::registerPin("C3", GPIO_NUM_21); -static gpio_num_t IOC2 = Pin::registerPin("C2", GPIO_NUM_47); -static gpio_num_t IOC1 = Pin::registerPin("C1", GPIO_NUM_48); -static gpio_num_t IOB2 = Pin::registerPin("B2", GPIO_NUM_45); - -static gpio_num_t SDA = Pin::registerPin("SDA", GPIO_NUM_35); -static gpio_num_t SCL = Pin::registerPin("SCL", GPIO_NUM_36); - -static gpio_num_t LEDB_GREEN = Pin::registerPin("LEDB_GREEN", GPIO_NUM_37); -static gpio_num_t LEDB_RED = Pin::registerPin("LEDB_RED", GPIO_NUM_38); - -static gpio_num_t TCK = Pin::registerPin("TCK", GPIO_NUM_39); -static gpio_num_t TDO = Pin::registerPin("TDO", GPIO_NUM_40); -static gpio_num_t TDI = Pin::registerPin("TDI", GPIO_NUM_41); -static gpio_num_t TMS = Pin::registerPin("TMS", GPIO_NUM_42); -static gpio_num_t RXD0 = Pin::registerPin("RXD0", GPIO_NUM_44); -static gpio_num_t TXD0 = Pin::registerPin("TXD0", GPIO_NUM_43); +static InternalPinPtr BOOT = InternalPin::registerPin("BOOT", GPIO_NUM_0); +static InternalPinPtr BATTERY = InternalPin::registerPin("BATTERY", GPIO_NUM_1); +static InternalPinPtr STATUS = InternalPin::registerPin("STATUS", GPIO_NUM_2); +static InternalPinPtr STATUS2 = InternalPin::registerPin("STATUS2", GPIO_NUM_4); + +static InternalPinPtr IOB1 = InternalPin::registerPin("B1", GPIO_NUM_5); +static InternalPinPtr IOA1 = InternalPin::registerPin("A1", GPIO_NUM_6); +static InternalPinPtr DIPROPI = InternalPin::registerPin("DIPROPI", GPIO_NUM_7); +static InternalPinPtr IOA2 = InternalPin::registerPin("A2", GPIO_NUM_15); +static InternalPinPtr AIN1 = InternalPin::registerPin("AIN1", GPIO_NUM_16); +static InternalPinPtr AIN2 = InternalPin::registerPin("AIN2", GPIO_NUM_17); +static InternalPinPtr BIN2 = InternalPin::registerPin("BIN2", GPIO_NUM_18); +static InternalPinPtr BIN1 = InternalPin::registerPin("BIN1", GPIO_NUM_8); + +static InternalPinPtr DMINUS = InternalPin::registerPin("D-", GPIO_NUM_19); +static InternalPinPtr DPLUS = InternalPin::registerPin("D+", GPIO_NUM_20); + +static InternalPinPtr LEDA_RED = InternalPin::registerPin("LEDA_RED", GPIO_NUM_46); +static InternalPinPtr LEDA_GREEN = InternalPin::registerPin("LEDA_GREEN", GPIO_NUM_9); + +static InternalPinPtr NFault = InternalPin::registerPin("NFault", GPIO_NUM_11); +static InternalPinPtr BTN1 = InternalPin::registerPin("BTN1", GPIO_NUM_12); +static InternalPinPtr BTN2 = InternalPin::registerPin("BTN2", GPIO_NUM_13); +static InternalPinPtr IOC4 = InternalPin::registerPin("C4", GPIO_NUM_14); +static InternalPinPtr IOC3 = InternalPin::registerPin("C3", GPIO_NUM_21); +static InternalPinPtr IOC2 = InternalPin::registerPin("C2", GPIO_NUM_47); +static InternalPinPtr IOC1 = InternalPin::registerPin("C1", GPIO_NUM_48); +static InternalPinPtr IOB2 = InternalPin::registerPin("B2", GPIO_NUM_45); + +static InternalPinPtr SDA = InternalPin::registerPin("SDA", GPIO_NUM_35); +static InternalPinPtr SCL = InternalPin::registerPin("SCL", GPIO_NUM_36); + +static InternalPinPtr LEDB_GREEN = InternalPin::registerPin("LEDB_GREEN", GPIO_NUM_37); +static InternalPinPtr LEDB_RED = InternalPin::registerPin("LEDB_RED", GPIO_NUM_38); + +static InternalPinPtr TCK = InternalPin::registerPin("TCK", GPIO_NUM_39); +static InternalPinPtr TDO = InternalPin::registerPin("TDO", GPIO_NUM_40); +static InternalPinPtr TDI = InternalPin::registerPin("TDI", GPIO_NUM_41); +static InternalPinPtr TMS = InternalPin::registerPin("TMS", GPIO_NUM_42); +static InternalPinPtr RXD0 = InternalPin::registerPin("RXD0", GPIO_NUM_44); +static InternalPinPtr TXD0 = InternalPin::registerPin("TXD0", GPIO_NUM_43); } // namespace pins class Mk6Config @@ -81,7 +81,7 @@ class Mk6Config * @brief The built-in motor driver's nSLEEP pin can be manually set by a jumper, * but can be connected to a GPIO pin, too. Defaults to C2. */ - Property motorNSleepPin { this, "motorNSleepPin", pins::IOC2 }; + Property motorNSleepPin { this, "motorNSleepPin", pins::IOC2 }; }; class UglyDucklingMk6 : public DeviceDefinition { @@ -92,8 +92,8 @@ class UglyDucklingMk6 : public DeviceDefinition { pins::BOOT) { // Switch off strapping pin // TODO: Add a LED driver instead - pinMode(pins::LEDA_RED, OUTPUT); - digitalWrite(pins::LEDA_RED, HIGH); + pins::LEDA_RED->pinMode(OUTPUT); + pins::LEDA_RED->digitalWrite(HIGH); } virtual std::shared_ptr createBatteryDriver(I2CManager& i2c) override { diff --git a/main/devices/UglyDucklingMk7.hpp b/main/devices/UglyDucklingMk7.hpp index e418b775..ad693ad5 100644 --- a/main/devices/UglyDucklingMk7.hpp +++ b/main/devices/UglyDucklingMk7.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -14,7 +15,6 @@ #include #include -#include using namespace farmhub::kernel; using namespace farmhub::peripherals::chicken_door; @@ -25,53 +25,53 @@ using namespace farmhub::peripherals::valve; namespace farmhub::devices { namespace pins { -static gpio_num_t BOOT = Pin::registerPin("BOOT", GPIO_NUM_0); +static InternalPinPtr BOOT = InternalPin::registerPin("BOOT", GPIO_NUM_0); -static gpio_num_t IOA2 = Pin::registerPin("A2", GPIO_NUM_1); -static gpio_num_t IOA1 = Pin::registerPin("A1", GPIO_NUM_2); -static gpio_num_t IOA3 = Pin::registerPin("A3", GPIO_NUM_3); -static gpio_num_t IOB3 = Pin::registerPin("B3", GPIO_NUM_4); -static gpio_num_t IOB1 = Pin::registerPin("B1", GPIO_NUM_5); -static gpio_num_t IOB2 = Pin::registerPin("B2", GPIO_NUM_6); +static InternalPinPtr IOA2 = InternalPin::registerPin("A2", GPIO_NUM_1); +static InternalPinPtr IOA1 = InternalPin::registerPin("A1", GPIO_NUM_2); +static InternalPinPtr IOA3 = InternalPin::registerPin("A3", GPIO_NUM_3); +static InternalPinPtr IOB3 = InternalPin::registerPin("B3", GPIO_NUM_4); +static InternalPinPtr IOB1 = InternalPin::registerPin("B1", GPIO_NUM_5); +static InternalPinPtr IOB2 = InternalPin::registerPin("B2", GPIO_NUM_6); // GPIO_NUM_7 is NC -static gpio_num_t BAT_GPIO = Pin::registerPin("BAT_GPIO", GPIO_NUM_8); +static InternalPinPtr BAT_GPIO = InternalPin::registerPin("BAT_GPIO", GPIO_NUM_8); -static gpio_num_t FSPIHD = Pin::registerPin("FSPIHD", GPIO_NUM_9); -static gpio_num_t FSPICS0 = Pin::registerPin("FSPICS0", GPIO_NUM_10); -static gpio_num_t FSPID = Pin::registerPin("FSPID", GPIO_NUM_11); -static gpio_num_t FSPICLK = Pin::registerPin("FSPICLK", GPIO_NUM_12); -static gpio_num_t FSPIQ = Pin::registerPin("FSPIQ", GPIO_NUM_13); -static gpio_num_t FSPIWP = Pin::registerPin("FSPIWP", GPIO_NUM_14); +static InternalPinPtr FSPIHD = InternalPin::registerPin("FSPIHD", GPIO_NUM_9); +static InternalPinPtr FSPICS0 = InternalPin::registerPin("FSPICS0", GPIO_NUM_10); +static InternalPinPtr FSPID = InternalPin::registerPin("FSPID", GPIO_NUM_11); +static InternalPinPtr FSPICLK = InternalPin::registerPin("FSPICLK", GPIO_NUM_12); +static InternalPinPtr FSPIQ = InternalPin::registerPin("FSPIQ", GPIO_NUM_13); +static InternalPinPtr FSPIWP = InternalPin::registerPin("FSPIWP", GPIO_NUM_14); -static gpio_num_t STATUS = Pin::registerPin("STATUS", GPIO_NUM_15); -static gpio_num_t LOADEN = Pin::registerPin("LOADEN", GPIO_NUM_16); +static InternalPinPtr STATUS = InternalPin::registerPin("STATUS", GPIO_NUM_15); +static InternalPinPtr LOADEN = InternalPin::registerPin("LOADEN", GPIO_NUM_16); -static gpio_num_t SCL = Pin::registerPin("SCL", GPIO_NUM_17); -static gpio_num_t SDA = Pin::registerPin("SDA", GPIO_NUM_18); +static InternalPinPtr SCL = InternalPin::registerPin("SCL", GPIO_NUM_17); +static InternalPinPtr SDA = InternalPin::registerPin("SDA", GPIO_NUM_18); -static gpio_num_t DMINUS = Pin::registerPin("D-", GPIO_NUM_19); -static gpio_num_t DPLUS = Pin::registerPin("D+", GPIO_NUM_20); +static InternalPinPtr DMINUS = InternalPin::registerPin("D-", GPIO_NUM_19); +static InternalPinPtr DPLUS = InternalPin::registerPin("D+", GPIO_NUM_20); -static gpio_num_t IOX1 = Pin::registerPin("X1", GPIO_NUM_21); +static InternalPinPtr IOX1 = InternalPin::registerPin("X1", GPIO_NUM_21); // GPIO_NUM_22 to GPIO_NUM_36 are NC -static gpio_num_t DBIN1 = Pin::registerPin("DBIN1", GPIO_NUM_37); -static gpio_num_t DBIN2 = Pin::registerPin("DBIN2", GPIO_NUM_38); -static gpio_num_t DAIN2 = Pin::registerPin("DAIN2", GPIO_NUM_39); -static gpio_num_t DAIN1 = Pin::registerPin("DAIN1", GPIO_NUM_40); -static gpio_num_t DNFault = Pin::registerPin("DNFault", GPIO_NUM_41); +static InternalPinPtr DBIN1 = InternalPin::registerPin("DBIN1", GPIO_NUM_37); +static InternalPinPtr DBIN2 = InternalPin::registerPin("DBIN2", GPIO_NUM_38); +static InternalPinPtr DAIN2 = InternalPin::registerPin("DAIN2", GPIO_NUM_39); +static InternalPinPtr DAIN1 = InternalPin::registerPin("DAIN1", GPIO_NUM_40); +static InternalPinPtr DNFault = InternalPin::registerPin("DNFault", GPIO_NUM_41); // GPIO_NUM_42 is NC -static gpio_num_t TXD0 = Pin::registerPin("TXD0", GPIO_NUM_43); -static gpio_num_t RXD0 = Pin::registerPin("RXD0", GPIO_NUM_44); -static gpio_num_t IOX2 = Pin::registerPin("X2", GPIO_NUM_45); -static gpio_num_t STATUS2 = Pin::registerPin("STATUS2", GPIO_NUM_46); -static gpio_num_t IOB4 = Pin::registerPin("B4", GPIO_NUM_47); -static gpio_num_t IOA4 = Pin::registerPin("A4", GPIO_NUM_48); +static InternalPinPtr TXD0 = InternalPin::registerPin("TXD0", GPIO_NUM_43); +static InternalPinPtr RXD0 = InternalPin::registerPin("RXD0", GPIO_NUM_44); +static InternalPinPtr IOX2 = InternalPin::registerPin("X2", GPIO_NUM_45); +static InternalPinPtr STATUS2 = InternalPin::registerPin("STATUS2", GPIO_NUM_46); +static InternalPinPtr IOB4 = InternalPin::registerPin("B4", GPIO_NUM_47); +static InternalPinPtr IOA4 = InternalPin::registerPin("A4", GPIO_NUM_48); } // namespace pins class Mk7Config diff --git a/main/kernel/I2CManager.hpp b/main/kernel/I2CManager.hpp index 6234820d..b336cb75 100644 --- a/main/kernel/I2CManager.hpp +++ b/main/kernel/I2CManager.hpp @@ -2,27 +2,136 @@ #include #include +#include #include #include #include #include +#include namespace farmhub::kernel { -using GpioPair = std::pair; -using TwoWireMap = std::map; +using std::shared_ptr; + +using farmhub::kernel::PinPtr; + +using GpioPair = std::pair; struct I2CConfig { public: uint8_t address; - gpio_num_t sda; - gpio_num_t scl; + InternalPinPtr sda; + InternalPinPtr scl; String toString() const { - return String("I2C address: 0x") + String(address, HEX) + ", SDA: " + String(sda) + ", SCL: " + String(scl); + return String("I2C address: 0x") + String(address, HEX) + ", SDA: " + sda->getName() + ", SCL: " + scl->getName(); + } +}; + +class I2CBus { +public: + I2CBus(TwoWire& wire) + : wire(wire) { + } + + TwoWire& wire; + Mutex mutex; +}; + +class I2CTransmission; + +class I2CDevice { +public: + I2CDevice(const String& name, I2CBus& bus, uint8_t address) + : name(name) + , bus(bus) + , address(address) { + } + +private: + const String name; + I2CBus& bus; + const uint8_t address; + + friend class I2CTransmission; +}; + +class I2CTransmission { +public: + I2CTransmission(shared_ptr device) + : device(device) + , lock(device->bus.mutex) { + wire().beginTransmission(device->address); + } + + ~I2CTransmission() { + auto result = wire().endTransmission(); + if (result != 0) { + Log.error("Communication unsuccessful with I2C device %s at address 0x%02x, result: %d", + device->name.c_str(), device->address, result); + } + } + + size_t requestFrom(size_t len, bool stopBit = true) { + Log.trace("Requesting %d bytes from I2C device %s at address 0x%02x", + len, device->name.c_str(), device->address); + auto count = wire().requestFrom(device->address, len, stopBit); + Log.trace("Received %d bytes from I2C device %s at address 0x%02x", + count, device->name.c_str(), device->address); + return count; } + + size_t write(uint8_t data) { + Log.trace("Writing 0x%02x to I2C device %s at address 0x%02x", + data, device->name.c_str(), device->address); + auto count = wire().write(data); + Log.trace("Wrote %d bytes to I2C device %s at address 0x%02x", + count, device->name.c_str(), device->address); + return count; + } + + size_t write(const uint8_t* data, size_t quantity) { + Log.trace("Writing %d bytes to I2C device %s at address 0x%02x", + quantity, device->name.c_str(), device->address); + auto count = wire().write(data, quantity); + Log.trace("Wrote %d bytes to I2C device %s at address 0x%02x", + count, device->name.c_str(), device->address); + return count; + } + + int available() { + return wire().available(); + } + + int read() { + auto value = wire().read(); + Log.trace("Read 0x%02x from I2C device %s at address 0x%02x", + value, device->name.c_str(), device->address); + return value; + } + + int peek() { + auto value = wire().peek(); + Log.trace("Peeked 0x%02x from I2C device %s at address 0x%02x", + value, device->name.c_str(), device->address); + return value; + } + + void flush() { + Log.trace("Flushing I2C device %s at address 0x%02x", + device->name.c_str(), device->address); + wire().flush(); + } + +private: + inline TwoWire& wire() const { + return device->bus.wire; + } + + shared_ptr device; + Lock lock; }; class I2CManager { @@ -31,31 +140,51 @@ class I2CManager { return getWireFor(config.sda, config.scl); } - TwoWire& getWireFor(gpio_num_t sda, gpio_num_t scl) { + TwoWire& getWireFor(InternalPinPtr sda, InternalPinPtr scl) { + return getBusFor(sda, scl).wire; + } + + shared_ptr createDevice(const String& name, const I2CConfig& config) { + return createDevice(name, config.sda, config.scl, config.address); + } + + shared_ptr createDevice(const String& name, InternalPinPtr sda, InternalPinPtr scl, uint8_t address) { + auto device = std::make_shared(name, getBusFor(sda, scl), address); + Log.info("Created I2C device %s at address 0x%02x", + name.c_str(), address); + // Test if communication is possible + I2CTransmission tx(device); + return device; + } + +private: + I2CBus& getBusFor(InternalPinPtr sda, InternalPinPtr scl) { GpioPair key = std::make_pair(sda, scl); - auto it = wireMap.find(key); - if (it != wireMap.end()) { - Log.trace("Reusing already registered I2C bus for SDA: %d, SCL: %d", sda, scl); + auto it = busMap.find(key); + if (it != busMap.end()) { + Log.trace("Reusing already registered I2C bus for SDA: %s, SCL: %s", + sda->getName().c_str(), scl->getName().c_str()); return *(it->second); } else { - Log.trace("Creating new I2C bus for SDA: %d, SCL: %d", sda, scl); + Log.debug("Creating new I2C bus for SDA: %s, SCL: %s", + sda->getName().c_str(), scl->getName().c_str()); if (nextBus >= 2) { throw std::runtime_error("Maximum number of I2C buses reached"); } TwoWire* wire = new TwoWire(nextBus++); - if (!wire->begin(sda, scl)) { + if (!wire->begin(sda->getGpio(), scl->getGpio())) { throw std::runtime_error( - String("Failed to initialize I2C bus for SDA: " + String(sda) + ", SCL: " + String(scl)).c_str()); + String("Failed to initialize I2C bus for SDA: " + sda->getName() + ", SCL: " + scl->getName()).c_str()); } - wireMap[key] = wire; - return *wire; + I2CBus* bus = new I2CBus(*wire); + busMap[key] = bus; + return *bus; } } -private: uint8_t nextBus = 0; - TwoWireMap wireMap; + std::map busMap; }; } // namespace farmhub::kernel diff --git a/main/kernel/PcntManager.hpp b/main/kernel/PcntManager.hpp index 89b65710..7157164d 100644 --- a/main/kernel/PcntManager.hpp +++ b/main/kernel/PcntManager.hpp @@ -15,13 +15,13 @@ namespace farmhub::kernel { // TODO Limit number of channels available struct PcntUnit { - PcntUnit(pcnt_unit_handle_t unit, gpio_num_t pin) + PcntUnit(pcnt_unit_handle_t unit, InternalPinPtr pin) : unit(unit) , pin(pin) { } PcntUnit() - : PcntUnit(nullptr, GPIO_NUM_MAX) { + : PcntUnit(nullptr, nullptr) { } PcntUnit(const PcntUnit& other) @@ -48,18 +48,18 @@ struct PcntUnit { return count; } - gpio_num_t getPin() const { + PinPtr getPin() const { return pin; } private: pcnt_unit_handle_t unit; - gpio_num_t pin; + InternalPinPtr pin; }; class PcntManager { public: - PcntUnit registerUnit(gpio_num_t pin) { + PcntUnit registerUnit(InternalPinPtr pin) { pcnt_unit_config_t unitConfig = { .low_limit = std::numeric_limits::min(), .high_limit = std::numeric_limits::max(), @@ -75,7 +75,7 @@ class PcntManager { ESP_ERROR_CHECK(pcnt_unit_set_glitch_filter(unit, &filterConfig)); pcnt_chan_config_t channelConfig = { - .edge_gpio_num = pin, + .edge_gpio_num = pin->getGpio(), .level_gpio_num = -1, .flags = {}, }; @@ -87,8 +87,8 @@ class PcntManager { ESP_ERROR_CHECK(pcnt_unit_clear_count(unit)); ESP_ERROR_CHECK(pcnt_unit_start(unit)); - Log.debug("Registered PCNT unit on pin %d", - pin); + Log.debug("Registered PCNT unit on pin %s", + pin->getName().c_str()); return PcntUnit(unit, pin); } }; diff --git a/main/kernel/Pin.hpp b/main/kernel/Pin.hpp new file mode 100644 index 00000000..86fb8198 --- /dev/null +++ b/main/kernel/Pin.hpp @@ -0,0 +1,185 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include + +namespace farmhub::kernel { + +class Pin; +using PinPtr = std::shared_ptr; + +class InternalPin; +using InternalPinPtr = std::shared_ptr; + +/** + * @brief A GPIO pin abstraction that allows digital reads and writes. + * + * @details This can be implemented by internal pins (GPIO pins of the MCU) or external pins provided by external peripherals. + */ +class Pin { +public: + static PinPtr byName(const String& name) { + auto it = BY_NAME.find(name); + if (it != BY_NAME.end()) { + return it->second; + } + throw std::runtime_error(String("Unknown pin: " + name).c_str()); + } + + virtual void pinMode(uint8_t mode) const = 0; + + virtual void digitalWrite(uint8_t val) const = 0; + + virtual int digitalRead() const = 0; + + inline const String& getName() const { + return name; + } + + static void registerPin(const String& name, PinPtr pin) { + BY_NAME[name] = pin; + } + +protected: + Pin(const String& name) + : name(name) { + } + +protected: + const String name; + + static std::map BY_NAME; +}; + +std::map Pin::BY_NAME; + +/** + * @brief An internal GPIO pin of the MCU. These pins can do analog reads as well, and can expose the GPIO number. + */ +class InternalPin : public Pin { +public: + static InternalPinPtr registerPin(const String& name, gpio_num_t gpio) { + auto pin = std::make_shared(name, gpio); + INTERNAL_BY_GPIO[gpio] = pin; + INTERNAL_BY_NAME[name] = pin; + Pin::registerPin(name, pin); + return pin; + } + + static InternalPinPtr byName(const String& name) { + auto it = INTERNAL_BY_NAME.find(name); + if (it != INTERNAL_BY_NAME.end()) { + return it->second; + } + throw std::runtime_error(String("Unknown internal pin: " + name).c_str()); + } + + static InternalPinPtr byGpio(gpio_num_t pin) { + auto it = INTERNAL_BY_GPIO.find(pin); + if (it == INTERNAL_BY_GPIO.end()) { + String name = "GPIO_NUM_" + String(pin); + return registerPin(name, pin); + } else { + return it->second; + } + } + + InternalPin(const String& name, gpio_num_t gpio) + : Pin(name) + , gpio(gpio) { + } + + inline void pinMode(uint8_t mode) const override { + ::pinMode(gpio, mode); + } + + inline void digitalWrite(uint8_t val) const override { + ::digitalWrite(gpio, val); + } + + inline int digitalRead() const override { + return ::digitalRead(gpio); + } + + inline uint16_t analogRead() const { + return ::analogRead(gpio); + } + + inline gpio_num_t getGpio() const { + return gpio; + } + +private: + const gpio_num_t gpio; + static std::map INTERNAL_BY_NAME; + static std::map INTERNAL_BY_GPIO; +}; + +std::map InternalPin::INTERNAL_BY_NAME; +std::map InternalPin::INTERNAL_BY_GPIO; + +} // namespace farmhub::kernel + +namespace ArduinoJson { + +using farmhub::kernel::InternalPin; +using farmhub::kernel::InternalPinPtr; +using farmhub::kernel::Pin; +using farmhub::kernel::PinPtr; + +template <> +struct Converter { + static void toJson(const PinPtr& src, JsonVariant dst) { + if (src == nullptr) { + dst.set(nullptr); + } else { + dst.set(src->getName()); + } + } + + static PinPtr fromJson(JsonVariantConst src) { + if (src.is()) { + return Pin::byName(src.as()); + } else { + throw std::runtime_error(String("Invalid pin name: " + src.as()).c_str()); + } + } + + static bool checkJson(JsonVariantConst src) { + return src.is(); + } +}; + +template <> +struct Converter { + static void toJson(const InternalPinPtr& src, JsonVariant dst) { + if (src == nullptr) { + dst.set(nullptr); + } else if (src->getName().startsWith("GPIO_NUM_")) { + dst.set(static_cast(src->getGpio())); + } else { + dst.set(src->getName()); + } + } + + static InternalPinPtr fromJson(JsonVariantConst src) { + if (src.is()) { + return InternalPin::byName(src.as()); + } else { + return InternalPin::byGpio(static_cast(src.as())); + } + } + + static bool checkJson(JsonVariantConst src) { + return src.is() || src.is(); + } +}; + +} // namespace ArduinoJson diff --git a/main/kernel/PwmManager.hpp b/main/kernel/PwmManager.hpp index 9ab6c70d..b70c94e5 100644 --- a/main/kernel/PwmManager.hpp +++ b/main/kernel/PwmManager.hpp @@ -11,7 +11,7 @@ namespace farmhub::kernel { // TODO Limit number of channels available struct PwmPin { - PwmPin(gpio_num_t pin, uint32_t freq, uint8_t resolutionBits) + PwmPin(InternalPinPtr pin, uint32_t freq, uint8_t resolutionBits) : pin(pin) , freq(freq) , resolutionBits(resolutionBits) { @@ -26,20 +26,20 @@ struct PwmPin { } void write(uint32_t value) const { - ledcWrite(pin, value); + ledcWrite(pin->getGpio(), value); } - const gpio_num_t pin; + const InternalPinPtr pin; const uint32_t freq; const uint8_t resolutionBits; }; class PwmManager { public: - PwmPin registerPin(gpio_num_t pin, uint32_t freq, uint8_t resolutionBits = 8) { - ledcAttach(pin, freq, resolutionBits); - Log.debug("Registered PWM channel on pin %d with freq %ld and resolution %d", - pin, freq, resolutionBits); + PwmPin registerPin(InternalPinPtr pin, uint32_t freq, uint8_t resolutionBits = 8) { + ledcAttach(pin->getGpio(), freq, resolutionBits); + Log.debug("Registered PWM channel on pin %s with freq %ld and resolution %d", + pin->getName().c_str(), freq, resolutionBits); return PwmPin(pin, freq, resolutionBits); } }; diff --git a/main/kernel/drivers/BatteryDriver.hpp b/main/kernel/drivers/BatteryDriver.hpp index 78fec16a..a111a179 100644 --- a/main/kernel/drivers/BatteryDriver.hpp +++ b/main/kernel/drivers/BatteryDriver.hpp @@ -3,8 +3,11 @@ #include #include +#include #include +using farmhub::kernel::PinPtr; + namespace farmhub::kernel::drivers { class BatteryDriver : public TelemetryProvider { @@ -20,22 +23,22 @@ class BatteryDriver : public TelemetryProvider { class AnalogBatteryDriver : public BatteryDriver { public: - AnalogBatteryDriver(gpio_num_t pin, float voltageDividerRatio) + AnalogBatteryDriver(InternalPinPtr pin, float voltageDividerRatio) : pin(pin) , voltageDividerRatio(voltageDividerRatio) { - Log.info("Initializing analog battery driver on pin %d", - pin); + Log.info("Initializing analog battery driver on pin %s", + pin->getName().c_str()); - pinMode(pin, INPUT); + pin->pinMode(INPUT); } float getVoltage() { - auto batteryLevel = analogRead(pin); + auto batteryLevel = pin->analogRead(); return batteryLevel * 3.3 / 4096 * voltageDividerRatio; } private: - const gpio_num_t pin; + const InternalPinPtr pin; const float voltageDividerRatio; }; diff --git a/main/kernel/drivers/Bq27220Driver.hpp b/main/kernel/drivers/Bq27220Driver.hpp index 34843f81..961633f0 100644 --- a/main/kernel/drivers/Bq27220Driver.hpp +++ b/main/kernel/drivers/Bq27220Driver.hpp @@ -12,18 +12,10 @@ namespace farmhub::kernel::drivers { class Bq27220Driver : public BatteryDriver { public: - Bq27220Driver(I2CManager& i2c, gpio_num_t sda, gpio_num_t scl, const uint8_t address = 0x55) - : wire(i2c.getWireFor(sda, scl)) - , address(address) { - Log.info("Initializing BQ27220 driver on SDA %d, SCL %d", - sda, scl); - - wire.beginTransmission(address); - if (wire.endTransmission() != 0) { - // TODO Throw an actual exception? - Log.error("BQ27220 not found at address 0x%02x", address); - return; - } + Bq27220Driver(I2CManager& i2c, InternalPinPtr sda, InternalPinPtr scl, const uint8_t address = 0x55) + : device(i2c.createDevice("battery:bq27220", sda, scl, address)) { + Log.info("Initializing BQ27220 driver on SDA %s, SCL %s", + sda->getName().c_str(), scl->getName().c_str()); auto deviceType = readControlWord(0x0001); if (deviceType != 0x0220) { @@ -61,34 +53,28 @@ class Bq27220Driver : public BatteryDriver { private: bool readFrom(uint8_t reg, uint8_t* buffer, size_t length) { - wire.beginTransmission(address); - wire.write(reg); - auto txResult = wire.endTransmission(); - if (txResult != 0) { - Log.error("Failed to write to 0x%02x: %d", reg, txResult); - return false; + { + I2CTransmission tx(device); + tx.write(reg); } - - auto rxResult = wire.requestFrom(address, (uint8_t) length); - if (rxResult != length) { - Log.error("Failed to read from 0x%02x: %d", reg, rxResult); - return false; - } - for (size_t i = 0; i < length; i++) { - buffer[i] = wire.read(); - // Log.trace("Read 0x%02x from 0x%02x", buffer[i], reg); + { + I2CTransmission tx(device); + auto rxResult = tx.requestFrom((uint8_t) length); + if (rxResult != length) { + Log.error("Failed to read from 0x%02x: %d", reg, rxResult); + return false; + } + for (size_t i = 0; i < length; i++) { + buffer[i] = tx.read(); + } } return true; } - bool writeTo(uint8_t reg, const uint8_t* buffer, size_t length) { - wire.beginTransmission(address); - wire.write(reg); - for (size_t i = 0; i < length; i++) { - // Log.trace("Writing 0x%02x to 0x%02x", buffer[i], reg); - wire.write(buffer[i]); - } - return wire.endTransmission() == 0; + void writeTo(uint8_t reg, const uint8_t* buffer, size_t length) { + I2CTransmission tx(device); + tx.write(reg); + tx.write(buffer, length); } uint8_t readByte(uint8_t reg) { @@ -107,9 +93,9 @@ class Bq27220Driver : public BatteryDriver { return static_cast(readWord(reg)); } - bool writeWord(uint8_t reg, uint16_t value) { + void writeWord(uint8_t reg, uint16_t value) { uint16_t buffer = value; - return writeTo(reg, reinterpret_cast(&buffer), 2); + writeTo(reg, reinterpret_cast(&buffer), 2); } uint16_t readControlWord(uint16_t subcommand) { @@ -117,8 +103,7 @@ class Bq27220Driver : public BatteryDriver { return readByte(0x40) | (readByte(0x41) << 8); } - TwoWire& wire; - const uint8_t address; + shared_ptr device; }; } // namespace farmhub::kernel::drivers diff --git a/main/kernel/drivers/Drv8801Driver.hpp b/main/kernel/drivers/Drv8801Driver.hpp index f78d015b..8f0c0404 100644 --- a/main/kernel/drivers/Drv8801Driver.hpp +++ b/main/kernel/drivers/Drv8801Driver.hpp @@ -28,32 +28,38 @@ class Drv8801Driver // Note: on Ugly Duckling MK5, the DRV8874's PMODE is wired to 3.3V, so it's locked in PWM mode Drv8801Driver( PwmManager& pwm, - gpio_num_t enablePin, - gpio_num_t phasePin, - gpio_num_t mode1Pin, - gpio_num_t mode2Pin, - gpio_num_t currentPin, - gpio_num_t faultPin, - gpio_num_t sleepPin) + PinPtr enablePin, + InternalPinPtr phasePin, + PinPtr mode1Pin, + PinPtr mode2Pin, + PinPtr currentPin, + PinPtr faultPin, + PinPtr sleepPin) : enablePin(enablePin) , phaseChannel(pwm.registerPin(phasePin, PWM_FREQ, PWM_RESOLUTION)) , currentPin(currentPin) , faultPin(faultPin) , sleepPin(sleepPin) { - Log.info("Initializing DRV8801 on pins enable = %d, phase = %d, fault = %d, sleep = %d, mode1 = %d, mode2 = %d, current = %d", - enablePin, phasePin, faultPin, sleepPin, mode1Pin, mode2Pin, currentPin); - - pinMode(enablePin, OUTPUT); - pinMode(mode1Pin, OUTPUT); - pinMode(mode2Pin, OUTPUT); - pinMode(sleepPin, OUTPUT); - pinMode(faultPin, INPUT); - pinMode(currentPin, INPUT); + Log.info("Initializing DRV8801 on pins enable = %s, phase = %s, fault = %s, sleep = %s, mode1 = %s, mode2 = %s, current = %s", + enablePin->getName().c_str(), + phasePin->getName().c_str(), + faultPin->getName().c_str(), + sleepPin->getName().c_str(), + mode1Pin->getName().c_str(), + mode2Pin->getName().c_str(), + currentPin->getName().c_str()); + + enablePin->pinMode(OUTPUT); + mode1Pin->pinMode(OUTPUT); + mode2Pin->pinMode(OUTPUT); + sleepPin->pinMode(OUTPUT); + faultPin->pinMode(INPUT); + currentPin->pinMode(INPUT); // TODO Allow using the DRV8801 in other modes - digitalWrite(mode1Pin, HIGH); - digitalWrite(mode2Pin, HIGH); + mode1Pin->digitalWrite(HIGH); + mode2Pin->digitalWrite(HIGH); sleep(); } @@ -62,11 +68,11 @@ class Drv8801Driver if (duty == 0) { Log.debug("Stopping"); sleep(); - digitalWrite(enablePin, LOW); + enablePin->digitalWrite(LOW); return; } wakeUp(); - digitalWrite(enablePin, HIGH); + enablePin->digitalWrite(HIGH); int direction = (phase == MotorPhase::FORWARD ? 1 : -1); int dutyValue = phaseChannel.maxValue() / 2 + direction * (int) (phaseChannel.maxValue() / 2 * duty); @@ -78,12 +84,12 @@ class Drv8801Driver } void sleep() { - digitalWrite(sleepPin, LOW); + sleepPin->digitalWrite(LOW); sleeping = true; } void wakeUp() { - digitalWrite(sleepPin, HIGH); + sleepPin->digitalWrite(HIGH); sleeping = false; } @@ -92,11 +98,11 @@ class Drv8801Driver } private: - const gpio_num_t enablePin; + const PinPtr enablePin; const PwmPin phaseChannel; - const gpio_num_t currentPin; - const gpio_num_t faultPin; - const gpio_num_t sleepPin; + const PinPtr currentPin; + const PinPtr faultPin; + const PinPtr sleepPin; std::atomic sleeping { false }; }; diff --git a/main/kernel/drivers/Drv8833Driver.hpp b/main/kernel/drivers/Drv8833Driver.hpp index 0e0798b7..03469285 100644 --- a/main/kernel/drivers/Drv8833Driver.hpp +++ b/main/kernel/drivers/Drv8833Driver.hpp @@ -23,22 +23,29 @@ class Drv8833Driver { // Note: on Ugly Duckling MK5, the DRV8874's PMODE is wired to 3.3V, so it's locked in PWM mode Drv8833Driver( PwmManager& pwm, - gpio_num_t ain1Pin, - gpio_num_t ain2Pin, - gpio_num_t bin1Pin, - gpio_num_t bin2Pin, - gpio_num_t faultPin, - gpio_num_t sleepPin) - : motorA(this, pwm, ain1Pin, ain2Pin, sleepPin != GPIO_NUM_NC) - , motorB(this, pwm, bin1Pin, bin2Pin, sleepPin != GPIO_NUM_NC) + InternalPinPtr ain1Pin, + InternalPinPtr ain2Pin, + InternalPinPtr bin1Pin, + InternalPinPtr bin2Pin, + PinPtr faultPin, + PinPtr sleepPin) + : motorA(this, pwm, ain1Pin, ain2Pin, sleepPin != nullptr) + , motorB(this, pwm, bin1Pin, bin2Pin, sleepPin != nullptr) , faultPin(faultPin) , sleepPin(sleepPin) { - Log.info("Initializing DRV8833 on pins ain1 = %d, ain2 = %d, bin1 = %d, bin2 = %d, fault = %d, sleep = %d", - ain1Pin, ain2Pin, bin1Pin, bin2Pin, faultPin, sleepPin); + Log.info("Initializing DRV8833 on pins ain1 = %s, ain2 = %s, bin1 = %s, bin2 = %s, fault = %s, sleep = %s", + ain1Pin->getName().c_str(), + ain2Pin->getName().c_str(), + bin1Pin->getName().c_str(), + bin2Pin->getName().c_str(), + faultPin->getName().c_str(), + sleepPin->getName().c_str()); - pinMode(sleepPin, OUTPUT); - pinMode(faultPin, INPUT); + if (sleepPin != nullptr) { + sleepPin->pinMode(OUTPUT); + } + faultPin->pinMode(INPUT); updateSleepState(); } @@ -61,22 +68,21 @@ class Drv8833Driver { Drv8833MotorDriver( Drv8833Driver* driver, PwmManager& pwm, - gpio_num_t in1Pin, - gpio_num_t in2Pin, + InternalPinPtr in1Pin, + InternalPinPtr in2Pin, bool canSleep) : driver(driver) , in1Channel(pwm.registerPin(in1Pin, PWM_FREQ, PWM_RESOLUTION)) , in2Channel(pwm.registerPin(in2Pin, PWM_FREQ, PWM_RESOLUTION)) - , canSleep(canSleep) , sleeping(canSleep) { } void drive(MotorPhase phase, double duty = 1) override { int dutyValue = static_cast((in1Channel.maxValue() + in1Channel.maxValue() * duty) / 2); - Log.debug("Driving motor %s on pins %d/%d at %d%% (duty = %d)", + Log.debug("Driving motor %s on pins %s/%s at %d%% (duty = %d)", phase == MotorPhase::FORWARD ? "forward" : "reverse", - in1Channel.pin, - in2Channel.pin, + in1Channel.pin->getName().c_str(), + in2Channel.pin->getName().c_str(), (int) (duty * 100), dutyValue); @@ -116,7 +122,6 @@ class Drv8833Driver { Drv8833Driver* const driver; const PwmPin in1Channel; const PwmPin in2Channel; - const bool canSleep; bool sleeping; }; @@ -126,15 +131,15 @@ class Drv8833Driver { } void setSleepState(bool sleep) { - if (sleepPin != GPIO_NUM_NC) { - digitalWrite(sleepPin, sleep ? LOW : HIGH); + if (sleepPin != nullptr) { + sleepPin->digitalWrite(sleep ? LOW : HIGH); } } Drv8833MotorDriver motorA; Drv8833MotorDriver motorB; - const gpio_num_t faultPin; - const gpio_num_t sleepPin; + const PinPtr faultPin; + const PinPtr sleepPin; std::atomic sleeping { false }; }; diff --git a/main/kernel/drivers/Drv8874Driver.hpp b/main/kernel/drivers/Drv8874Driver.hpp index 79433660..ec3e17dc 100644 --- a/main/kernel/drivers/Drv8874Driver.hpp +++ b/main/kernel/drivers/Drv8874Driver.hpp @@ -28,23 +28,27 @@ class Drv8874Driver // Note: on Ugly Duckling MK5, the DRV8874's PMODE is wired to 3.3V, so it's locked in PWM mode Drv8874Driver( PwmManager& pwm, - gpio_num_t in1Pin, - gpio_num_t in2Pin, - gpio_num_t currentPin, - gpio_num_t faultPin, - gpio_num_t sleepPin) + InternalPinPtr in1Pin, + InternalPinPtr in2Pin, + PinPtr currentPin, + PinPtr faultPin, + PinPtr sleepPin) : in1Channel(pwm.registerPin(in1Pin, PWM_FREQ, PWM_RESOLUTION)) , in2Channel(pwm.registerPin(in2Pin, PWM_FREQ, PWM_RESOLUTION)) , currentPin(currentPin) , faultPin(faultPin) , sleepPin(sleepPin) { - Log.info("Initializing DRV8874 on pins in1 = %d, in2 = %d, fault = %d, sleep = %d, current = %d", - in1Pin, in2Pin, faultPin, sleepPin, currentPin); + Log.info("Initializing DRV8874 on pins in1 = %s, in2 = %s, fault = %s, sleep = %s, current = %s", + in1Pin->getName().c_str(), + in2Pin->getName().c_str(), + faultPin->getName().c_str(), + sleepPin->getName().c_str(), + currentPin->getName().c_str()); - pinMode(sleepPin, OUTPUT); - pinMode(faultPin, INPUT); - pinMode(currentPin, INPUT); + sleepPin->pinMode(OUTPUT); + faultPin->pinMode(INPUT); + currentPin->pinMode(INPUT); sleep(); } @@ -75,12 +79,12 @@ class Drv8874Driver } void sleep() { - digitalWrite(sleepPin, LOW); + sleepPin->digitalWrite(LOW); sleeping = true; } void wakeUp() { - digitalWrite(sleepPin, HIGH); + sleepPin->digitalWrite(HIGH); sleeping = false; } @@ -91,9 +95,9 @@ class Drv8874Driver private: const PwmPin in1Channel; const PwmPin in2Channel; - const gpio_num_t currentPin; - const gpio_num_t faultPin; - const gpio_num_t sleepPin; + const PinPtr currentPin; + const PinPtr faultPin; + const PinPtr sleepPin; std::atomic sleeping { false }; }; diff --git a/main/kernel/drivers/LedDriver.hpp b/main/kernel/drivers/LedDriver.hpp index efcece5e..18c8046a 100644 --- a/main/kernel/drivers/LedDriver.hpp +++ b/main/kernel/drivers/LedDriver.hpp @@ -6,24 +6,27 @@ #include #include +#include #include using namespace std::chrono; +using farmhub::kernel::PinPtr; + namespace farmhub::kernel::drivers { class LedDriver { public: typedef std::list BlinkPattern; - LedDriver(const String& name, gpio_num_t pin) + LedDriver(const String& name, PinPtr pin) : pin(pin) , patternQueue(name, 1) , pattern({ -milliseconds::max() }) { - Log.info("Initializing LED driver on pin %d", - pin); + Log.info("Initializing LED driver on pin %s", + pin->getName().c_str()); - pinMode(pin, OUTPUT); + pin->pinMode(OUTPUT); Task::loop(name, 2048, [this](Task& task) { if (currentPattern.empty()) { currentPattern = pattern; @@ -77,10 +80,10 @@ class LedDriver { void setLedState(bool state) { this->ledState = state; - digitalWrite(pin, state); + pin->digitalWrite(state); } - const gpio_num_t pin; + const PinPtr pin; Queue patternQueue; BlinkPattern pattern; std::atomic ledState; diff --git a/main/kernel/drivers/MqttDriver.hpp b/main/kernel/drivers/MqttDriver.hpp index 59560a15..4414fbd1 100644 --- a/main/kernel/drivers/MqttDriver.hpp +++ b/main/kernel/drivers/MqttDriver.hpp @@ -400,8 +400,8 @@ class MqttDriver { } else { Log.debug("MQTT: server: %s:%d, client ID is '%s', using TLS", hostname.c_str(), mqttServer.port, clientId.c_str()); - Log.debug("Server cert: %s", serverCert.c_str()); - Log.debug("Client cert: %s", clientCert.c_str()); + Log.trace("Server cert: %s", serverCert.c_str()); + Log.trace("Client cert: %s", clientCert.c_str()); wifiClientSecure.setCACert(serverCert.c_str()); wifiClientSecure.setCertificate(clientCert.c_str()); wifiClientSecure.setPrivateKey(clientKey.c_str()); @@ -490,7 +490,7 @@ class MqttDriver { // Actually subscribe to the given topic bool registerSubscriptionWithMqtt(const Subscription& subscription) { - Log.debug("MQTT: Subscribing to topic '%s' (qos = %d)", + Log.trace("MQTT: Subscribing to topic '%s' (qos = %d)", subscription.topic.c_str(), static_cast(subscription.qos)); bool success = mqttClient.subscribe(subscription.topic, static_cast(subscription.qos), [](const String& payload, const size_t size) { // Global handler will take care of putting the received message on the incoming queue diff --git a/main/kernel/drivers/SwitchManager.hpp b/main/kernel/drivers/SwitchManager.hpp index 1880d795..55c10e0f 100644 --- a/main/kernel/drivers/SwitchManager.hpp +++ b/main/kernel/drivers/SwitchManager.hpp @@ -10,10 +10,13 @@ #include #include +#include #include using namespace std::chrono; +using farmhub::kernel::PinPtr; + namespace farmhub::kernel::drivers { enum class SwitchMode { @@ -24,7 +27,7 @@ enum class SwitchMode { class Switch { public: virtual const String& getName() const = 0; - virtual gpio_num_t getPin() const = 0; + virtual InternalPinPtr getPin() const = 0; virtual bool isEngaged() const = 0; }; @@ -52,22 +55,22 @@ class SwitchManager { typedef std::function SwitchEngagementHandler; typedef std::function SwitchReleaseHandler; - const Switch& onEngaged(const String& name, gpio_num_t pin, SwitchMode mode, SwitchEngagementHandler engagementHandler) { + const Switch& onEngaged(const String& name, InternalPinPtr pin, SwitchMode mode, SwitchEngagementHandler engagementHandler) { return registerHandler( name, pin, mode, engagementHandler, [](const Switch&, milliseconds) {}); } - const Switch& onReleased(const String& name, gpio_num_t pin, SwitchMode mode, SwitchReleaseHandler releaseHandler) { + const Switch& onReleased(const String& name, InternalPinPtr pin, SwitchMode mode, SwitchReleaseHandler releaseHandler) { return registerHandler( name, pin, mode, [](const Switch&) {}, releaseHandler); } - const Switch& registerHandler(const String& name, gpio_num_t pin, SwitchMode mode, SwitchEngagementHandler engagementHandler, SwitchReleaseHandler releaseHandler) { - Log.info("Registering switch %s on pin %d, mode %s", - name.c_str(), pin, mode == SwitchMode::PullUp ? "pull-up" : "pull-down"); + const Switch& registerHandler(const String& name, InternalPinPtr pin, SwitchMode mode, SwitchEngagementHandler engagementHandler, SwitchReleaseHandler releaseHandler) { + Log.info("Registering switch %s on pin %s, mode %s", + name.c_str(), pin->getName().c_str(), mode == SwitchMode::PullUp ? "pull-up" : "pull-down"); // Configure PIN_INPUT as input - pinMode(pin, mode == SwitchMode::PullUp ? INPUT_PULLUP : INPUT_PULLDOWN); + pin->pinMode(mode == SwitchMode::PullUp ? INPUT_PULLUP : INPUT_PULLDOWN); // gpio_set_direction(pin, GPIO_MODE_INPUT); // gpio_set_pull_mode(pin, mode == SwitchMode::PullUp ? GPIO_PULLUP_ONLY : GPIO_PULLDOWN_ONLY); @@ -81,8 +84,8 @@ class SwitchManager { // Install GPIO ISR gpio_install_isr_service(0); - gpio_isr_handler_add(pin, handleSwitchInterrupt, switchState); - gpio_set_intr_type(pin, GPIO_INTR_ANYEDGE); + gpio_isr_handler_add(pin->getGpio(), handleSwitchInterrupt, switchState); + gpio_set_intr_type(pin->getGpio(), GPIO_INTR_ANYEDGE); return *switchState; } @@ -94,17 +97,17 @@ class SwitchManager { return name; } - gpio_num_t getPin() const override { + InternalPinPtr getPin() const override { return pin; } bool isEngaged() const override { - return digitalRead(pin) == (mode == SwitchMode::PullUp ? LOW : HIGH); + return pin->digitalRead() == (mode == SwitchMode::PullUp ? LOW : HIGH); } private: String name; - gpio_num_t pin; + InternalPinPtr pin; SwitchMode mode; SwitchEngagementHandler engagementHandler; @@ -133,7 +136,7 @@ class SwitchManager { // ISR handler for GPIO interrupt static void IRAM_ATTR handleSwitchInterrupt(void* arg) { SwitchManager::SwitchState* state = static_cast(arg); - bool engaged = digitalRead(state->pin) == (state->mode == SwitchMode::PullUp ? LOW : HIGH); + bool engaged = state->pin->digitalRead() == (state->mode == SwitchMode::PullUp ? LOW : HIGH); state->manager->queueSwitchStateChange(state, engaged); } diff --git a/main/peripherals/I2CConfig.hpp b/main/peripherals/I2CConfig.hpp index 519a0c8b..90fc3b0f 100644 --- a/main/peripherals/I2CConfig.hpp +++ b/main/peripherals/I2CConfig.hpp @@ -19,18 +19,18 @@ class I2CDeviceConfig // but JSON doesn't support 0x notation, so we // take it as a string instead Property address { this, "address" }; - Property sda { this, "sda", GPIO_NUM_NC }; - Property scl { this, "scl", GPIO_NUM_NC }; + Property sda { this, "sda" }; + Property scl { this, "scl" }; - I2CConfig parse(uint8_t defaultAddress = 0xFF, gpio_num_t defaultSda = GPIO_NUM_NC, gpio_num_t defaultScl = GPIO_NUM_NC) const { + I2CConfig parse(uint8_t defaultAddress = 0xFF, InternalPinPtr defaultSda = nullptr, InternalPinPtr defaultScl = nullptr) const { return { address.get().isEmpty() ? defaultAddress : (uint8_t) strtol(address.get().c_str(), nullptr, 0), - sda.get() == GPIO_NUM_NC + sda.get() == nullptr ? defaultSda : sda.get(), - scl.get() == GPIO_NUM_NC + scl.get() == nullptr ? defaultScl : scl.get() }; diff --git a/main/peripherals/Motorized.hpp b/main/peripherals/Motorized.hpp index 6af9d08e..78a6aee7 100644 --- a/main/peripherals/Motorized.hpp +++ b/main/peripherals/Motorized.hpp @@ -20,7 +20,6 @@ class Motorized { : motors(motors) { } -protected: PwmMotorDriver& findMotor(const String& motorName) { // If there's only one motor and no name is specified, use it if (motorName.isEmpty() && motors.size() == 1) { diff --git a/main/peripherals/Peripheral.hpp b/main/peripherals/Peripheral.hpp index a23b1afc..e10bce04 100644 --- a/main/peripherals/Peripheral.hpp +++ b/main/peripherals/Peripheral.hpp @@ -197,9 +197,6 @@ class PeripheralManager } String name = deviceConfig.name.get(); - if (name.isEmpty()) { - name = "default"; - } String type = deviceConfig.type.get(); try { Lock lock(stateMutex); @@ -249,7 +246,7 @@ class PeripheralManager private: class PeripheralDeviceConfiguration : public ConfigurationSection { public: - Property name { this, "name" }; + Property name { this, "name", "default" }; Property type { this, "type" }; Property params { this, "params" }; }; diff --git a/main/peripherals/SinglePinDeviceConfig.hpp b/main/peripherals/SinglePinDeviceConfig.hpp index f9b50bc7..b84d83f0 100644 --- a/main/peripherals/SinglePinDeviceConfig.hpp +++ b/main/peripherals/SinglePinDeviceConfig.hpp @@ -14,7 +14,7 @@ namespace farmhub::peripherals { class SinglePinDeviceConfig : public ConfigurationSection { public: - Property pin { this, "pin", GPIO_NUM_NC }; + Property pin { this, "pin" }; }; } // namespace farmhub::peripherals::environment diff --git a/main/peripherals/chicken_door/ChickenDoor.hpp b/main/peripherals/chicken_door/ChickenDoor.hpp index 7b06a32c..7bec698a 100644 --- a/main/peripherals/chicken_door/ChickenDoor.hpp +++ b/main/peripherals/chicken_door/ChickenDoor.hpp @@ -70,8 +70,8 @@ class ChickenDoorDeviceConfig : public ConfigurationSection { public: Property motor { this, "motor" }; - Property openPin { this, "openPin", GPIO_NUM_NC }; - Property closedPin { this, "closedPin", GPIO_NUM_NC }; + Property openPin { this, "openPin" }; + Property closedPin { this, "closedPin" }; Property movementTimeout { this, "movementTimeout", seconds(60) }; NamedConfigurationEntry lightSensor { this, "lightSensor" }; @@ -95,8 +95,8 @@ class ChickenDoorComponent SwitchManager& switches, PwmMotorDriver& motor, TLightSensorComponent& lightSensor, - gpio_num_t openPin, - gpio_num_t closedPin, + InternalPinPtr openPin, + InternalPinPtr closedPin, ticks movementTimeout, std::function publishTelemetry) : Component(name, mqttRoot) @@ -122,8 +122,8 @@ class ChickenDoorComponent // TODO Make this configurable { - Log.info("Initializing chicken door %s, open switch %d, close switch %d", - name.c_str(), openSwitch.getPin(), closedSwitch.getPin()); + Log.info("Initializing chicken door %s, open switch %s, close switch %s", + name.c_str(), openSwitch.getPin()->getName().c_str(), closedSwitch.getPin()->getName().c_str()); motor.stop(); diff --git a/main/peripherals/environment/Ds18B20SoilSensor.hpp b/main/peripherals/environment/Ds18B20SoilSensor.hpp index 4210cde2..35fa9bcb 100644 --- a/main/peripherals/environment/Ds18B20SoilSensor.hpp +++ b/main/peripherals/environment/Ds18B20SoilSensor.hpp @@ -32,13 +32,13 @@ class Ds18B20SoilSensorComponent Ds18B20SoilSensorComponent( const String& name, shared_ptr mqttRoot, - gpio_num_t pin) + InternalPinPtr pin) : Component(name, mqttRoot) { - Log.info("Initializing DS18B20 soil temperature sensor on pin %d", - pin); + Log.info("Initializing DS18B20 soil temperature sensor on pin %s", + pin->getName().c_str()); - oneWire.begin(pin); + oneWire.begin(pin->getGpio()); // locate devices on the bus Log.trace("Locating devices..."); @@ -90,7 +90,7 @@ class Ds18B20SoilSensorComponent class Ds18B20SoilSensor : public Peripheral { public: - Ds18B20SoilSensor(const String& name, shared_ptr mqttRoot, gpio_num_t pin) + Ds18B20SoilSensor(const String& name, shared_ptr mqttRoot, InternalPinPtr pin) : Peripheral(name, mqttRoot) , sensor(name, mqttRoot, pin) { } diff --git a/main/peripherals/environment/Environment.hpp b/main/peripherals/environment/Environment.hpp index 92c0a36a..0ff596d0 100644 --- a/main/peripherals/environment/Environment.hpp +++ b/main/peripherals/environment/Environment.hpp @@ -51,7 +51,7 @@ class I2CEnvironmentFactory } unique_ptr> createPeripheral(const String& name, const I2CDeviceConfig& deviceConfig, shared_ptr mqttRoot, PeripheralServices& services) override { - auto i2cConfig = deviceConfig.parse(defaultAddress, GPIO_NUM_NC, GPIO_NUM_NC); + auto i2cConfig = deviceConfig.parse(defaultAddress); Log.info("Creating %s sensor %s with %s", sensorType.c_str(), name.c_str(), i2cConfig.toString().c_str()); return make_unique>(name, sensorType, mqttRoot, services.i2c, i2cConfig); diff --git a/main/peripherals/environment/SoilMoistureSensor.hpp b/main/peripherals/environment/SoilMoistureSensor.hpp index af313742..4bb7fe11 100644 --- a/main/peripherals/environment/SoilMoistureSensor.hpp +++ b/main/peripherals/environment/SoilMoistureSensor.hpp @@ -19,7 +19,7 @@ namespace farmhub::peripherals::environment { class SoilMoistureSensorDeviceConfig : public ConfigurationSection { public: - Property pin { this, "pin", GPIO_NUM_NC }; + Property pin { this, "pin" }; // These values need calibrating for each sensor Property air { this, "air", 3000 }; Property water { this, "water", 1000 }; @@ -38,14 +38,14 @@ class SoilMoistureSensorComponent , waterValue(config.water.get()) , pin(config.pin.get()) { - Log.info("Initializing soil moisture sensor on pin %d; air value: %d; water value: %d", - pin, airValue, waterValue); + Log.info("Initializing soil moisture sensor on pin %s; air value: %d; water value: %d", + pin->getName().c_str(), airValue, waterValue); - pinMode(pin, INPUT); + pin->pinMode(INPUT); } void populateTelemetry(JsonObject& json) override { - uint16_t soilMoistureValue = analogRead(pin); + uint16_t soilMoistureValue = pin->analogRead(); Log.trace("Soil moisture value: %d", soilMoistureValue); @@ -60,7 +60,7 @@ class SoilMoistureSensorComponent private: const int airValue; const int waterValue; - gpio_num_t pin; + InternalPinPtr pin; }; class SoilMoistureSensor diff --git a/main/peripherals/fence/ElectricFenceMonitor.hpp b/main/peripherals/fence/ElectricFenceMonitor.hpp index 8cf932b3..ce408454 100644 --- a/main/peripherals/fence/ElectricFenceMonitor.hpp +++ b/main/peripherals/fence/ElectricFenceMonitor.hpp @@ -20,7 +20,7 @@ using namespace farmhub::peripherals; namespace farmhub::peripherals::fence { struct FencePinConfig { - gpio_num_t pin; + InternalPinPtr pin; uint16_t voltage; }; @@ -56,7 +56,7 @@ class ElectricFenceMonitorComponent for (auto& pinConfig : config.pins.get()) { if (pinsDescription.length() > 0) pinsDescription += ", "; - pinsDescription += String(pinConfig.pin) + "=" + String(pinConfig.voltage) + "V"; + pinsDescription += pinConfig.pin->getName() + "=" + String(pinConfig.voltage) + "V"; } Log.info("Initializing electric fence with pins %s", pinsDescription.c_str()); @@ -74,8 +74,8 @@ class ElectricFenceMonitorComponent if (count > 0) { lastVoltage = max(pin.voltage, lastVoltage); - Log.trace("Counted %d pulses on pin %d (voltage: %dV)", - count, pin.pcntUnit.getPin(), pin.voltage); + Log.trace("Counted %d pulses on pin %s (voltage: %dV)", + count, pin.pcntUnit.getPin()->getName().c_str(), pin.voltage); } } { diff --git a/main/peripherals/flow_control/FlowControl.hpp b/main/peripherals/flow_control/FlowControl.hpp index 303b24f2..cc8de60c 100644 --- a/main/peripherals/flow_control/FlowControl.hpp +++ b/main/peripherals/flow_control/FlowControl.hpp @@ -34,13 +34,12 @@ class FlowControl : public Peripheral { shared_ptr mqttRoot, PcntManager& pcnt, SleepManager& sleepManager, - PwmMotorDriver& controller, ValveControlStrategy& strategy, - gpio_num_t pin, + InternalPinPtr pin, double qFactor, milliseconds measurementFrequency) : Peripheral(name, mqttRoot) - , valve(name, sleepManager, controller, strategy, mqttRoot, [this]() { + , valve(name, sleepManager, strategy, mqttRoot, [this]() { publishTelemetry(); }) , flowMeter(name, mqttRoot, pcnt, pin, qFactor, measurementFrequency) { @@ -88,26 +87,15 @@ class FlowControlFactory } unique_ptr> createPeripheral(const String& name, const FlowControlDeviceConfig& deviceConfig, shared_ptr mqttRoot, PeripheralServices& services) override { - const ValveDeviceConfig& valveConfig = deviceConfig.valve.get(); - const FlowMeterDeviceConfig& flowMeterConfig = deviceConfig.flowMeter.get(); - - PwmMotorDriver& targetMotor = findMotor(valveConfig.motor.get()); - ValveControlStrategy* strategy; - try { - strategy = createValveControlStrategy( - valveConfig.strategy.get(), - valveConfig.switchDuration.get(), - valveConfig.holdDuty.get() / 100.0); - } catch (const std::exception& e) { - throw PeripheralCreationException("failed to create strategy: " + String(e.what())); - } + auto strategy = deviceConfig.valve.get().createValveControlStrategy(this); + + auto flowMeterConfig = deviceConfig.flowMeter.get(); return make_unique( name, mqttRoot, services.pcntManager, services.sleepManager, - targetMotor, *strategy, flowMeterConfig.pin.get(), diff --git a/main/peripherals/flow_meter/FlowMeter.hpp b/main/peripherals/flow_meter/FlowMeter.hpp index f2202df1..bc56125f 100644 --- a/main/peripherals/flow_meter/FlowMeter.hpp +++ b/main/peripherals/flow_meter/FlowMeter.hpp @@ -19,7 +19,7 @@ namespace farmhub::peripherals::flow_meter { class FlowMeter : public Peripheral { public: - FlowMeter(const String& name, shared_ptr mqttRoot, PcntManager& pcnt, gpio_num_t pin, double qFactor, milliseconds measurementFrequency) + FlowMeter(const String& name, shared_ptr mqttRoot, PcntManager& pcnt, InternalPinPtr pin, double qFactor, milliseconds measurementFrequency) : Peripheral(name, mqttRoot) , flowMeter(name, mqttRoot, pcnt, pin, qFactor, measurementFrequency) { } diff --git a/main/peripherals/flow_meter/FlowMeterComponent.hpp b/main/peripherals/flow_meter/FlowMeterComponent.hpp index ce1f4a9d..96b8ec16 100644 --- a/main/peripherals/flow_meter/FlowMeterComponent.hpp +++ b/main/peripherals/flow_meter/FlowMeterComponent.hpp @@ -27,13 +27,14 @@ class FlowMeterComponent const String& name, shared_ptr mqttRoot, PcntManager& pcnt, - gpio_num_t pin, + InternalPinPtr pin, double qFactor, milliseconds measurementFrequency) : Component(name, mqttRoot) , qFactor(qFactor) { - Log.info("Initializing flow meter on pin %d with Q = %.2f", pin, qFactor); + Log.info("Initializing flow meter on pin %s with Q = %.2f", + pin->getName().c_str(), qFactor); pcntUnit = pcnt.registerUnit(pin); diff --git a/main/peripherals/flow_meter/FlowMeterConfig.hpp b/main/peripherals/flow_meter/FlowMeterConfig.hpp index 75f66169..b2752e23 100644 --- a/main/peripherals/flow_meter/FlowMeterConfig.hpp +++ b/main/peripherals/flow_meter/FlowMeterConfig.hpp @@ -13,7 +13,7 @@ namespace farmhub::peripherals::flow_meter { class FlowMeterDeviceConfig : public ConfigurationSection { public: - Property pin { this, "pin", GPIO_NUM_NC }; + Property pin { this, "pin" }; Property qFactor { this, "qFactor", 5.0 }; Property measurementFrequency { this, "measurementFrequency", 1s }; }; diff --git a/main/peripherals/multiplexer/Xl9535.hpp b/main/peripherals/multiplexer/Xl9535.hpp new file mode 100644 index 00000000..a177f5ab --- /dev/null +++ b/main/peripherals/multiplexer/Xl9535.hpp @@ -0,0 +1,155 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace farmhub::peripherals::multiplexer { + +class Xl9535DeviceConfig + : public I2CDeviceConfig { +}; + +class Xl9535Component + : public Component { + +public: + Xl9535Component( + const String& name, + shared_ptr mqttRoot, + I2CManager& i2c, + I2CConfig config) + : Component(name, mqttRoot) + , device(i2c.createDevice(name, config)) { + Log.info("Initializing XL9535 multiplexer with %s", + config.toString().c_str()); + } + + void pinMode(uint8_t pin, uint8_t mode) { + if (mode == OUTPUT) { + direction &= ~(1 << pin); + } else { + direction |= 1 << pin; + } + if (pin < 8) { + updateDirection1(); + } else { + updateDirection2(); + } + } + + void digitalWrite(uint8_t pin, uint8_t val) { + if (val == HIGH) { + output |= 1 << pin; + } else { + output &= ~(1 << pin); + } + if (pin < 8) { + updateOutput1(); + } else { + updateOutput2(); + } + } + + int digitalRead(uint8_t pin) { + I2CTransmission tx(device); + tx.write(pin < 8 ? 0x00 : 0x01); + tx.requestFrom(1); + uint8_t data = tx.read(); + return (data >> (pin % 8)) & 1; + } + +private: + void updateDirection1() { + I2CTransmission tx(device); + tx.write(0x06); + tx.write(direction & 0xFF); + } + + void updateDirection2() { + I2CTransmission tx(device); + tx.write(0x07); + tx.write(direction >> 8); + } + + void updateOutput1() { + I2CTransmission tx(device); + tx.write(0x02); + tx.write(output & 0xFF); + } + + void updateOutput2() { + I2CTransmission tx(device); + tx.write(0x03); + tx.write(output >> 8); + } + + shared_ptr device; + + uint16_t polarity = 0; + uint16_t direction = 0xFFFF; + uint16_t output = 0; +}; + +class Xl9535Pin : public Pin { +public: + Xl9535Pin(const String& name, Xl9535Component& mpx, uint8_t pin) + : Pin(name) + , mpx(mpx) + , pin(pin) { + } + + void pinMode(uint8_t mode) const override { + mpx.pinMode(pin, mode); + } + + void digitalWrite(uint8_t val) const override { + mpx.digitalWrite(pin, val); + } + + int digitalRead() const override { + return mpx.digitalRead(pin); + } + +private: + Xl9535Component& mpx; + const uint8_t pin; +}; + +class Xl9535 + : public Peripheral { +public: + Xl9535(const String& name, shared_ptr mqttRoot, I2CManager& i2c, I2CConfig config) + : Peripheral(name, mqttRoot) + , component(name, mqttRoot, i2c, config) { + + // Create a pin for each bit in the pins mask + for (int i = 0; i < 16; i++) { + String pinName = name + ":" + String(i); + Log.trace("Registering external pin %s", + pinName.c_str()); + auto pin = std::make_shared(pinName, component, i); + Pin::registerPin(pinName, pin); + } + } + +private: + Xl9535Component component; +}; + +class Xl9535Factory + : public PeripheralFactory { +public: + Xl9535Factory() + : PeripheralFactory("multiplexer:xl9535") { + } + + unique_ptr> createPeripheral(const String& name, const Xl9535DeviceConfig& deviceConfig, shared_ptr mqttRoot, PeripheralServices& services) override { + return make_unique(name, mqttRoot, services.i2c, deviceConfig.parse()); + } +}; + +} // namespace farmhub::peripherals::multiplexer diff --git a/main/peripherals/valve/Valve.hpp b/main/peripherals/valve/Valve.hpp index f319c63f..157a7054 100644 --- a/main/peripherals/valve/Valve.hpp +++ b/main/peripherals/valve/Valve.hpp @@ -36,11 +36,10 @@ class Valve Valve( const String& name, SleepManager& sleepManager, - PwmMotorDriver& controller, ValveControlStrategy& strategy, shared_ptr mqttRoot) : Peripheral(name, mqttRoot) - , valve(name, sleepManager, controller, strategy, mqttRoot, [this]() { + , valve(name, sleepManager, strategy, mqttRoot, [this]() { publishTelemetry(); }) { } @@ -73,17 +72,8 @@ class ValveFactory } unique_ptr> createPeripheral(const String& name, const ValveDeviceConfig& deviceConfig, shared_ptr mqttRoot, PeripheralServices& services) override { - PwmMotorDriver& targetMotor = findMotor(deviceConfig.motor.get()); - ValveControlStrategy* strategy; - try { - strategy = createValveControlStrategy( - deviceConfig.strategy.get(), - deviceConfig.switchDuration.get(), - deviceConfig.holdDuty.get() / 100.0); - } catch (const std::exception& e) { - throw PeripheralCreationException("failed to create strategy: " + String(e.what())); - } - return make_unique(name, services.sleepManager, targetMotor, *strategy, mqttRoot); + auto strategy = deviceConfig.createValveControlStrategy(this); + return make_unique(name, services.sleepManager, *strategy, mqttRoot); } }; diff --git a/main/peripherals/valve/ValveComponent.hpp b/main/peripherals/valve/ValveComponent.hpp index 8a1b0b6a..d45e6828 100644 --- a/main/peripherals/valve/ValveComponent.hpp +++ b/main/peripherals/valve/ValveComponent.hpp @@ -35,30 +35,42 @@ namespace farmhub::peripherals::valve { class ValveControlStrategy { public: - virtual void open(PwmMotorDriver& controller) = 0; - virtual void close(PwmMotorDriver& controller) = 0; + virtual void open() = 0; + virtual void close() = 0; virtual ValveState getDefaultState() const = 0; virtual String describe() const = 0; }; -class HoldingValveControlStrategy +class MotorValveControlStrategy : public ValveControlStrategy { +public: + MotorValveControlStrategy(PwmMotorDriver& controller) + : controller(controller) { + } + +protected: + PwmMotorDriver& controller; +}; + +class HoldingMotorValveControlStrategy + : public MotorValveControlStrategy { public: - HoldingValveControlStrategy(milliseconds switchDuration, double holdDuty) - : switchDuration(switchDuration) + HoldingMotorValveControlStrategy(PwmMotorDriver& controller, milliseconds switchDuration, double holdDuty) + : MotorValveControlStrategy(controller) + , switchDuration(switchDuration) , holdDuty(holdDuty) { } protected: - void driveAndHold(PwmMotorDriver& controller, ValveState targetState) { + void driveAndHold(ValveState targetState) { switch (targetState) { case ValveState::OPEN: - driveAndHold(controller, MotorPhase::FORWARD); + driveAndHold(MotorPhase::FORWARD); break; case ValveState::CLOSED: - driveAndHold(controller, MotorPhase::REVERSE); + driveAndHold(MotorPhase::REVERSE); break; default: // Ignore @@ -70,25 +82,25 @@ class HoldingValveControlStrategy const double holdDuty; private: - void driveAndHold(PwmMotorDriver& controller, MotorPhase phase) { + void driveAndHold(MotorPhase phase) { controller.drive(phase, 1.0); delay(switchDuration.count()); controller.drive(phase, holdDuty); } }; -class NormallyClosedValveControlStrategy - : public HoldingValveControlStrategy { +class NormallyClosedMotorValveControlStrategy + : public HoldingMotorValveControlStrategy { public: - NormallyClosedValveControlStrategy(milliseconds switchDuration, double holdDuty) - : HoldingValveControlStrategy(switchDuration, holdDuty) { + NormallyClosedMotorValveControlStrategy(PwmMotorDriver& controller, milliseconds switchDuration, double holdDuty) + : HoldingMotorValveControlStrategy(controller, switchDuration, holdDuty) { } - void open(PwmMotorDriver& controller) override { - driveAndHold(controller, ValveState::OPEN); + void open() override { + driveAndHold(ValveState::OPEN); } - void close(PwmMotorDriver& controller) override { + void close() override { controller.stop(); } @@ -101,19 +113,19 @@ class NormallyClosedValveControlStrategy } }; -class NormallyOpenValveControlStrategy - : public HoldingValveControlStrategy { +class NormallyOpenMotorValveControlStrategy + : public HoldingMotorValveControlStrategy { public: - NormallyOpenValveControlStrategy(milliseconds switchDuration, double holdDuty) - : HoldingValveControlStrategy(switchDuration, holdDuty) { + NormallyOpenMotorValveControlStrategy(PwmMotorDriver& controller, milliseconds switchDuration, double holdDuty) + : HoldingMotorValveControlStrategy(controller, switchDuration, holdDuty) { } - void open(PwmMotorDriver& controller) override { + void open() override { controller.stop(); } - void close(PwmMotorDriver& controller) override { - driveAndHold(controller, ValveState::CLOSED); + void close() override { + driveAndHold(ValveState::CLOSED); } ValveState getDefaultState() const override { @@ -125,21 +137,22 @@ class NormallyOpenValveControlStrategy } }; -class LatchingValveControlStrategy - : public ValveControlStrategy { +class LatchingMotorValveControlStrategy + : public MotorValveControlStrategy { public: - LatchingValveControlStrategy(milliseconds switchDuration, double switchDuty = 1.0) - : switchDuration(switchDuration) + LatchingMotorValveControlStrategy(PwmMotorDriver& controller, milliseconds switchDuration, double switchDuty = 1.0) + : MotorValveControlStrategy(controller) + , switchDuration(switchDuration) , switchDuty(switchDuty) { } - void open(PwmMotorDriver& controller) override { + void open() override { controller.drive(MotorPhase::FORWARD, switchDuty); delay(switchDuration.count()); controller.stop(); } - void close(PwmMotorDriver& controller) override { + void close() override { controller.drive(MotorPhase::REVERSE, switchDuty); delay(switchDuration.count()); controller.stop(); @@ -158,18 +171,44 @@ class LatchingValveControlStrategy const double switchDuty; }; +class LatchingPinValveControlStrategy + : public ValveControlStrategy { +public: + LatchingPinValveControlStrategy(PinPtr pin) + : pin(pin) { + pin->pinMode(OUTPUT); + } + + void open() override { + pin->digitalWrite(HIGH); + } + + void close() override { + pin->digitalWrite(LOW); + } + + ValveState getDefaultState() const override { + return ValveState::NONE; + } + + String describe() const override { + return "latching with pin " + pin->getName(); + } + +private: + PinPtr pin; +}; + class ValveComponent : public Component { public: ValveComponent( const String& name, SleepManager& sleepManager, - PwmMotorDriver& controller, ValveControlStrategy& strategy, shared_ptr mqttRoot, std::function publishTelemetry) : Component(name, mqttRoot) , sleepManager(sleepManager) - , controller(controller) , nvs(name) , strategy(strategy) , publishTelemetry(publishTelemetry) { @@ -177,41 +216,33 @@ class ValveComponent : public Component { Log.info("Creating valve '%s' with strategy %s", name.c_str(), strategy.describe().c_str()); - controller.stop(); - - // Rewrite this to a switch statement - if (strategy.getDefaultState() == ValveState::OPEN) { - state = ValveState::OPEN; - } else if (strategy.getDefaultState() == ValveState::CLOSED) { - state = ValveState::CLOSED; - } - + ValveState initState; switch (strategy.getDefaultState()) { case ValveState::OPEN: Log.info("Assuming valve '%s' is open by default", name.c_str()); - state = ValveState::OPEN; + initState = ValveState::OPEN; break; case ValveState::CLOSED: Log.info("Assuming valve '%s' is closed by default", name.c_str()); - state = ValveState::CLOSED; + initState = ValveState::CLOSED; break; default: // Try to load from NVS ValveState lastStoredState; if (nvs.get("state", lastStoredState)) { - state = lastStoredState; + initState = lastStoredState; Log.info("Restored state for valve '%s' from NVS: %d", name.c_str(), static_cast(state)); } else { - Log.info("No stored state for valve '%s'", + initState = ValveState::CLOSED; + Log.info("No stored state for valve '%s', defaulting to closed", name.c_str()); } break; } - - // TODO Restore stored state? + doTransitionTo(initState); mqttRoot->registerCommand("override", [this](const JsonObject& request, JsonObject& response) { ValveState targetState = request["state"].as(); @@ -313,14 +344,14 @@ class ValveComponent : public Component { void open() { Log.info("Opening valve '%s'", name.c_str()); KeepAwake keepAwake(sleepManager); - strategy.open(controller); + strategy.open(); setState(ValveState::OPEN); } void close() { Log.info("Closing valve '%s'", name.c_str()); KeepAwake keepAwake(sleepManager); - strategy.close(controller); + strategy.close(); setState(ValveState::CLOSED); } @@ -329,7 +360,15 @@ class ValveComponent : public Component { if (this->state == state) { return; } + doTransitionTo(state); + mqttRoot->publish("events/state", [=](JsonObject& json) { + json["state"] = state; + }); + publishTelemetry(); + } + + void doTransitionTo(ValveState state) { switch (state) { case ValveState::OPEN: open(); @@ -341,10 +380,6 @@ class ValveComponent : public Component { // Ignore break; } - mqttRoot->publish("events/state", [=](JsonObject& json) { - json["state"] = state; - }); - publishTelemetry(); } void setState(ValveState state) { @@ -356,7 +391,6 @@ class ValveComponent : public Component { } SleepManager& sleepManager; - PwmMotorDriver& controller; NvsStore nvs; ValveControlStrategy& strategy; std::function publishTelemetry; diff --git a/main/peripherals/valve/ValveConfig.hpp b/main/peripherals/valve/ValveConfig.hpp index 504749ab..d5e5c6bc 100644 --- a/main/peripherals/valve/ValveConfig.hpp +++ b/main/peripherals/valve/ValveConfig.hpp @@ -24,19 +24,6 @@ enum class ValveControlStrategyType { Latching }; -static ValveControlStrategy* createValveControlStrategy(ValveControlStrategyType strategy, milliseconds switchDuration, double holdDuty) { - switch (strategy) { - case ValveControlStrategyType::NormallyOpen: - return new NormallyOpenValveControlStrategy(switchDuration, holdDuty); - case ValveControlStrategyType::NormallyClosed: - return new NormallyClosedValveControlStrategy(switchDuration, holdDuty); - case ValveControlStrategyType::Latching: - return new LatchingValveControlStrategy(switchDuration, holdDuty); - default: - throw std::runtime_error("Unknown strategy"); - } -} - class ValveConfig : public ConfigurationSection { public: @@ -50,10 +37,64 @@ class ValveDeviceConfig : strategy(this, "strategy", defaultStrategy) { } + /** + * @brief The pin to use to control the valve. + * + * @details This can be an internal or an external pin. When specified, the motor is ignored. + */ + Property pin { this, "pin" }; + + /** + * @brief The name of the motor service to use to control the valve. + * + * @details When the pin is specified, this is ignored. + */ Property motor { this, "motor" }; + + /** + * @brief The strategy to use to control the motorized valve. + * + * @details Ignored when the pin is specified. + */ Property strategy; + + /** + * @brief Duty to use to hold the motorized valve in place. + * + * @details This is a percentage from 0 to 100, default is 100%. This is ignored for latching strategies and when the pin is specified. + */ Property holdDuty { this, "holdDuty", 100 }; // This is a percentage + + /** + * @brief Duration to keep the motor running to switch the motorized valve. + * + * @details This is in milliseconds, default is 500ms. This is ignored when the pin is specified. + */ Property switchDuration { this, "switchDuration", 500ms }; + + ValveControlStrategy* createValveControlStrategy(Motorized* motorOwner) const { + PinPtr pin = this->pin.get(); + if (pin != nullptr) { + return new LatchingPinValveControlStrategy(pin); + } + + PwmMotorDriver& motor = motorOwner->findMotor(this->motor.get()); + ValveControlStrategy* strategy; + + auto switchDuration = this->switchDuration.get(); + auto holdDuty = this->holdDuty.get() / 100.0; + + switch (this->strategy.get()) { + case ValveControlStrategyType::NormallyOpen: + return new NormallyOpenMotorValveControlStrategy(motor, switchDuration, holdDuty); + case ValveControlStrategyType::NormallyClosed: + return new NormallyClosedMotorValveControlStrategy(motor, switchDuration, holdDuty); + case ValveControlStrategyType::Latching: + return new LatchingMotorValveControlStrategy(motor, switchDuration, holdDuty); + default: + throw PeripheralCreationException("unknown strategy"); + } + } }; // JSON: ValveControlStrategyType