From 63a2d32accb3f78f7f3f328e0b0964a4f1277c4f Mon Sep 17 00:00:00 2001 From: Matthias Prinke <83612361+matthias-bs@users.noreply.github.com> Date: Mon, 29 Jul 2024 20:21:03 +0200 Subject: [PATCH] Added PowerFeather specific features (#91) * Added PowerFeather specific status information to LoRaWAN node status message * Added PowerFeather specific configuration parameters to BresserWeatherSensorLWCfg.h and LoadNodeCfg.h/.cpp --- BresserWeatherSensorLW.ino | 35 +++++++++---- BresserWeatherSensorLWCfg.h | 18 ++++++- BresserWeatherSensorLWCmd.cpp | 95 +++++++++++++++++++++++++++++++++++ README.md | 13 +++-- data/node_config.json | 8 ++- scripts/uplink_formatter.js | 59 ++++++++++++++++++---- src/LoadNodeCfg.cpp | 28 +++++++++-- src/LoadNodeCfg.h | 11 +++- 8 files changed, 235 insertions(+), 32 deletions(-) diff --git a/BresserWeatherSensorLW.ino b/BresserWeatherSensorLW.ino index c03620b..4ac6a2e 100644 --- a/BresserWeatherSensorLW.ino +++ b/BresserWeatherSensorLW.ino @@ -97,6 +97,7 @@ // Moved decodeDownlink() & sendCfgUplink() to BresserWeatherSensorLWCmd.cpp/.h // 20240725 Added reading of hardware/deployment specific configuration node_config.json // from LittleFS (optional) +// 20240729 PowerFeather: Enabled battery temperature measurement, added specific configuration // // ToDo: // - @@ -225,6 +226,17 @@ ESP32Time rtc; /// Application layer AppLayer appLayer(&rtc, &rtcLastClockSync); +#if defined(ARDUINO_ESP32S3_POWERFEATHER) +struct sPowerFeatherCfg PowerFeatherCfg = { + .battery_capacity = BATTERY_CAPACITY_MAH, + .supply_maintain_voltage = PF_SUPPLY_MAINTAIN_VOLTAGE, + .temperature_measurement = PF_TEMPERATURE_MEASUREMENT, + .battery_fuel_gauge = PF_BATTERY_FUEL_GAUGE +}; +#else +struct sPowerFeatherCfg PowerFeatherCfg = {0}; +#endif + #if defined(ESP32) /*! * \brief Print wakeup reason (ESP32 only) @@ -479,11 +491,11 @@ void setup() uint16_t battery_low = BATTERY_LOW; uint16_t battery_discharge_lim = BATTERY_DISCHARGE_LIM; uint16_t battery_charge_lim = BATTERY_CHARGE_LIM; -#if defined(BATTERY_CAPACITY_MAH) - uint16_t battery_capacity_mah = BATTERY_CAPACITY_MAH; -#else - uint16_t battery_capacity_mah = 0; -#endif +// #if defined(BATTERY_CAPACITY_MAH) + // uint16_t battery_capacity_mah = BATTERY_CAPACITY_MAH; +// #else + // uint16_t battery_capacity_mah = 0; +// #endif loadNodeCfg( timeZoneInfo, @@ -491,13 +503,18 @@ void setup() battery_low, battery_discharge_lim, battery_charge_lim, - battery_capacity_mah); + PowerFeatherCfg); #if defined(ARDUINO_ESP32S3_POWERFEATHER) delay(2000); - Board.init(battery_capacity_mah); // Note: Battery capacity / type has to be set for voltage measurement - Board.enable3V3(true); // Power supply for FeatherWing - Board.enableVSQT(true); // Power supply for battery management chip (voltage measurement) + // Note: Battery capacity / type has to be set for voltage measurement + Board.init(PowerFeatherCfg.battery_capacity); + Board.enable3V3(true); // Power supply for FeatherWing + Board.enableVSQT(true); // Power supply for battery management chip (voltage measurement) + Board.enableBatteryTempSense(PowerFeatherCfg.temperature_measurement); // Enable battery temperature measurement + Board.enableBatteryFuelGauge(PowerFeatherCfg.battery_fuel_gauge); // Enable battery fuel gauge + Board.setSupplyMaintainVoltage(PowerFeatherCfg.supply_maintain_voltage); // Set supply maintain voltage + Board.enableBatteryCharging(true); // Enable battery charging #endif #if defined(ARDUINO_ARCH_RP2040) diff --git a/BresserWeatherSensorLWCfg.h b/BresserWeatherSensorLWCfg.h index cb4e158..159cbe7 100644 --- a/BresserWeatherSensorLWCfg.h +++ b/BresserWeatherSensorLWCfg.h @@ -62,6 +62,7 @@ // 20240722 Added LW_STATUS_INTERVAL, // renamed STATUS_INTERVAL to APP_STATUS_INTERVAL // 20240726 Renamed BATTERY_DISCHARGE_LIMIT/BATTERY_CHARGE_LIMIT +// 20240729 Added PowerFeather specific configuration // // Note: // Depending on board package file date, some defines are written either @@ -96,6 +97,16 @@ #define DFROBOT_COVER_LORA #endif +// PowerFeather specific configuration +struct sPowerFeatherCfg { + + uint16_t battery_capacity; /// Battery capacity in mAh + uint16_t supply_maintain_voltage; /// Supply voltage to maintain in mV + bool temperature_measurement; /// Enable temperature measurement + bool battery_fuel_gauge; /// Enable battery fuel gauge +}; + + // Uplink message payload size // The maximum allowed for all data rates is 51 bytes. const uint8_t PAYLOAD_SIZE = 51; @@ -205,8 +216,11 @@ const uint8_t MAX_DOWNLINK_SIZE = 51; // On-board VB #define PIN_ADC_IN A0 #elif defined(ARDUINO_ESP32S3_POWERFEATHER) -// Set battery capacity in mAh -#define BATTERY_CAPACITY_MAH 0 +// See https://docs.powerfeather.dev +#define BATTERY_CAPACITY_MAH 2200 // battery capacity in mAh +#define PF_TEMPERATURE_MEASUREMENT true // enable/diable temperature measurement +#define PF_SUPPLY_MAINTAIN_VOLTAGE 5500 // ~maximum power point (MPP) voltage if using a solar panel +#define PF_BATTERY_FUEL_GAUGE true // enable/disable battery fuel gauge #if BATTERY_CAPACITY_MAH == 0 #pragma message("Battery capacity set to 0 - battery voltage measurement disabled.") #endif diff --git a/BresserWeatherSensorLWCmd.cpp b/BresserWeatherSensorLWCmd.cpp index 0ec9281..e38ffe9 100644 --- a/BresserWeatherSensorLWCmd.cpp +++ b/BresserWeatherSensorLWCmd.cpp @@ -35,6 +35,7 @@ // History: // // 20240723 Extracted from BresserWeatherSensorLW.ino +// 20240729 Added PowerFeather specific status information // // ToDo: // - @@ -48,6 +49,10 @@ #include #include "src/AppLayer.h" +#if defined(ARDUINO_ESP32S3_POWERFEATHER) +#include +using namespace PowerFeather; +#endif /* * From config.h @@ -186,6 +191,96 @@ void sendCfgUplink(uint8_t uplinkReq, uint32_t uplinkInterval) log_d("Device Status: U_batt=%u mV, longSleep=%u", getBatteryVoltage(), status); encoder.writeUint16(getBatteryVoltage()); encoder.writeUint8(status); + #if defined(ARDUINO_ESP32S3_POWERFEATHER) + Result res; + uint16_t voltage; + int16_t current; + uint8_t battery_soc; + uint8_t battery_soh; + uint16_t battery_cycles; + float battery_temp; + int time_left; + + res = Board.getSupplyVoltage(voltage); + if (res == Result::Ok) + { + encoder.writeUint16(voltage); + } + else + { + encoder.writeUint16(INV_UINT16); + } + + res = Board.getSupplyCurrent(current); + if (res == Result::Ok) + { + encoder.writeUint16(current + 0x8000); + } + else + { + encoder.writeUint16(INV_UINT16); + } + + res = Board.getBatteryCurrent(current); + if (res == Result::Ok) + { + encoder.writeUint16(current + 0x8000); + } + else + { + encoder.writeUint16(INV_UINT16); + } + + res = Board.getBatteryCharge(battery_soc); + if (res == Result::Ok) + { + encoder.writeUint8(battery_soc); + } + else + { + encoder.writeUint8(INV_UINT8); + } + + res = Board.getBatteryHealth(battery_soh); + if (res == Result::Ok) + { + encoder.writeUint8(battery_soh); + } + else + { + encoder.writeUint8(INV_UINT8); + } + + res = Board.getBatteryCycles(battery_cycles); + if (res == Result::Ok) + { + encoder.writeUint16(battery_cycles); + } + else + { + encoder.writeUint16(INV_UINT16); + } + + res = Board.getBatteryTimeLeft(time_left); + if (res == Result::Ok) + { + encoder.writeUint32(time_left + 0x80000000); + } + else + { + encoder.writeUint32(INV_UINT32); + } + + res = Board.getBatteryTemperature(battery_temp); + if (res == Result::Ok) + { + encoder.writeTemperature(battery_temp); + } + else + { + encoder.writeTemperature(INV_TEMP); + } + #endif } else { diff --git a/README.md b/README.md index 8cc06fc..208f3aa 100644 --- a/README.md +++ b/README.md @@ -313,9 +313,12 @@ File | `SLEEP_INTERVAL`
`SLEEP_INTERVAL_LONG`
`LW_STATUS_INTERVAL`
`APP_STATUS_INTERVAL`
`WEATHERSENSOR_TIMEOUT` | Timing parameters | X | X | | | `en_decoders` | Enabled sensor decoders (saves CPU cycles / energy) | | X | | | `BATTERY_WEAK`
`BATTERY_LOW`
`BATTERY_DISCHARGE_LIM`
`BATTERY_CHARGE_LIM` | Battery voltage levels in mV | X | | X | -| `BATTERY_CAPACITY_MAH` /
`battery_capacity` | Battery capacity | X | | X | | see header file | ADC's input pins, dividers and oversampling | X | | | - +| — | PowerFeather specific configuration | | | | +| `BATTERY_CAPACITY_MAH` /
`powerfeather/battery_capacity` | see [https://docs.powerfeather.dev](https://docs.powerfeather.dev) | X | | X | +| `PF_TEMPERATURE_MEASUREMENT` /
`powerfeather/temperature_measurement` | see [https://docs.powerfeather.dev](https://docs.powerfeather.dev) | X | | X | +| `PF_BATTERY_FUEL_GAUGE` /
`powerfeather/battery_fuel_gauge` | see [https://docs.powerfeather.dev](https://docs.powerfeather.dev) | X | | X | +| `PF_SUPPLY_MAINTAIN_VOLTAGE` /
`powerfeather/supply_maintain_voltage` | see [https://docs.powerfeather.dev](https://docs.powerfeather.dev) | X | | X | ### Enabling Debug Output @@ -682,7 +685,11 @@ The following parameters are available: | battery_low | Voltage threshold in mV for deep-discharge protection
(power off) | `3200` | | battery_discharge_lim | Discharging voltage limit in mV
for battery level estimation | `3200` | | battery_charge_lim | Charging voltage limit in mV
for battery level estimation | `4200` | -| battery_capacity | Battery capacity in mAh
(curently only used by ESP32S3 PowerFeather) | `0` | +| powerfeather/ | PowerFeather specific (see [https://docs.powerfeather.dev](https://docs.powerfeather.dev)) | | +|   battery_capacity | Battery capacity in mAh
(`0`: no battery connected) | `0` | +|   supply_maintain_voltage | see [PowerFeather Docs: `setSupplyMaintainVoltage()`](https://docs.powerfeather.dev/sdk/api/mainboard#result-setsupplymaintainvoltageuint16_t-voltage) | `5500` | +|   temperature_measurement | see [PowerFeather Docs: `setSupplyMaintainVoltage(enableBatteryTempSense)`](https://docs.powerfeather.dev/sdk/api/mainboard#result-enablebatterytempsensebool-enable) | `true` | +|   battery_fuel_gauge | see [PowerFeather Docs: `enableBatteryFuelGauge()`](https://docs.powerfeather.dev/sdk/api/mainboard#result-enablebatteryfuelgaugebool-enable) | `true` | Modify the example [data/node_config.json](data/node_config.json) as required and install it to the board's Flash memory using [earlephilhower/arduino-littlefs-upload](https://github.com/earlephilhower/arduino-littlefs-upload). diff --git a/data/node_config.json b/data/node_config.json index 8121aba..9666cff 100644 --- a/data/node_config.json +++ b/data/node_config.json @@ -4,5 +4,9 @@ "battery_low": 3201, "battery_discharge_lim": 3201, "battery_charge_lim": 4201, - "battery_capacity": 2201 -} + "powerfeather": { + "battery_capacity": 2201, + "temperature_measurement": true, + "battery_fuel_gauge": true + } +} \ No newline at end of file diff --git a/scripts/uplink_formatter.js b/scripts/uplink_formatter.js index e54bb52..26a7131 100644 --- a/scripts/uplink_formatter.js +++ b/scripts/uplink_formatter.js @@ -144,6 +144,7 @@ // 20240716 Added CMD_SCAN_SENSORS // 20240722 Added CMD_SET_LW_STATUS_INTERVAL, modified CMD_GET_LW_CONFIG, // renamed CMD_SET_STATUS_INTERVAL to CMD_SET_APP_STATUS_INTERVAL +// 20240729 Added PowerFeather specific status information // // ToDo: // - @@ -159,6 +160,9 @@ function decoder(bytes, port) { // Compatibility mode: create "status" as in BresserweatherSensorTTN const COMPATIBILITY_MODE = false; + // Enable PowerFeather specific information in LoRaWAN Node Status message + const POWERFEATHER = false; + const CMD_GET_DATETIME = 0x20; const CMD_GET_LW_CONFIG = 0x36; const CMD_GET_LW_STATUS = 0x38; @@ -335,6 +339,30 @@ function decoder(bytes, port) { }; uint32BE.BYTES = 4; + var int16 = function (bytes) { + if (bytes.length !== int16.BYTES) { + throw new Error('int must have exactly 2 bytes'); + } + let res = bytesToInt(bytes); + if (SKIP_INVALID_SIGNALS && res === 0xFFFF) { + return NaN; + } + return res - 0x8000; + }; + int16.BYTES = 2; + + var int32 = function (bytes) { + if (bytes.length !== int32.BYTES) { + throw new Error('int must have exactly 4 bytes'); + } + let res = bytesToInt(bytes); + if (SKIP_INVALID_SIGNALS && res === 0xFFFF) { + return NaN; + } + return res - 0x80000000; + }; + int32.BYTES = 4; + function byte2hex(byte) { return ('0' + byte.toString(16)).slice(-2); } @@ -603,6 +631,8 @@ function decoder(bytes, port) { uint8: uint8, uint16: uint16, uint32: uint32, + int16: int16, + int32: int32, uint16BE: uint16BE, uint32BE: uint32BE, mac48: mac48, @@ -732,14 +762,25 @@ function decoder(bytes, port) { ] ); } else if (port === CMD_GET_LW_STATUS) { - return decode( - port, - bytes, - [uint16, uint8 - ], - ['ubatt_mv', 'long_sleep' - ] - ); + if (POWERFEATHER) { + return decode( + port, + bytes, + [uint16, uint8, uint16, int16, int16, uint8, uint8, uint16, int32, temperature + ], + ['ubatt_mv', 'long_sleep', 'usupply_mv', 'isupply_ma', 'ibatt_ma', 'soc', 'soh', 'batt_cycles', 'Tbatt_min', 'batt_temp_c' + ] + ); + } else { + return decode( + port, + bytes, + [uint16, uint8 + ], + ['ubatt_mv', 'long_sleep' + ] + ); + } } else if (port === CMD_GET_WS_TIMEOUT) { return decode( port, @@ -811,7 +852,7 @@ function decoder(bytes, port) { ] ); } else if (port === CMD_SCAN_SENSORS) { - return {'found_sensors': found_sensors(bytes)}; + return { 'found_sensors': found_sensors(bytes) }; } else if (port === CMD_GET_SENSORS_STAT) { return decode( diff --git a/src/LoadNodeCfg.cpp b/src/LoadNodeCfg.cpp index 65cea89..88644e0 100644 --- a/src/LoadNodeCfg.cpp +++ b/src/LoadNodeCfg.cpp @@ -35,6 +35,7 @@ // History: // // 20240725 Created +// 20240729 Added PowerFeather specific configuration // // ToDo: // - @@ -50,9 +51,9 @@ void loadNodeCfg( uint16_t &batt_low, uint16_t &batt_discharge_lim, uint16_t &batt_charge_lim, - uint16_t &batt_capacity) + struct sPowerFeatherCfg &powerFeatherCfg) { - uint16_t foo = 42; + if (!LittleFS.begin( #if defined(ESP32) @@ -94,8 +95,21 @@ void loadNodeCfg( batt_discharge_lim = doc["battery_discharge_lim"]; if (doc.containsKey("battery_charge_lim")) batt_charge_lim = doc["battery_charge_lim"]; - if (doc.containsKey("battery_capacity")) - batt_capacity = doc["battery_capacity"]; + if (doc.containsKey("powerfeather")) { + JsonObject pf = doc["powerfeather"]; + if (pf.containsKey("battery_capacity")) { + powerFeatherCfg.battery_capacity = pf["battery_capacity"]; + } + if (pf.containsKey("supply_maintain_voltage")) { + powerFeatherCfg.supply_maintain_voltage = pf["supply_maintain_voltage"]; + } + if (pf.containsKey("temperature_measurement")) { + powerFeatherCfg.temperature_measurement = pf["temperature_measurement"]; + } + if (pf.containsKey("battery_fuel_gauge")) { + powerFeatherCfg.battery_fuel_gauge = pf["battery_fuel_gauge"]; + } + } } // deserializeJson o.k. } // file read o.k. file.close(); @@ -106,5 +120,9 @@ void loadNodeCfg( log_d("Battery low: %4d mV", batt_low); log_d("Battery discharge limit: %4d mV", batt_discharge_lim); log_d("Battery charge limit: %4d mV", batt_charge_lim); - log_d("Battery capacity: %4d mAh", batt_capacity); + log_d("PowerFeather"); + log_d(" Battery capacity: %4d mAh", powerFeatherCfg.battery_capacity); + log_d(" Supply maintain voltage: %4d mV", powerFeatherCfg.supply_maintain_voltage); + log_d(" Temperature measurement: %s", powerFeatherCfg.temperature_measurement ? "true" : "false"); + log_d(" Battery fuel gauge: %s", powerFeatherCfg.battery_fuel_gauge ? "true" : "false"); } // loadNodeCfg() \ No newline at end of file diff --git a/src/LoadNodeCfg.h b/src/LoadNodeCfg.h index dd6c392..ba22086 100644 --- a/src/LoadNodeCfg.h +++ b/src/LoadNodeCfg.h @@ -35,6 +35,7 @@ // History: // // 20240725 Created +// 20240729 Added PowerFeather specific configuration // // ToDo: // - @@ -44,6 +45,7 @@ #include #include #include +#include "../BresserWeatherSensorLWCfg.h" #include "logging.h" /*! @@ -59,7 +61,12 @@ * "battery_low": 3400, * "battery_discharge_lim": 3200, * "battery_charge_lim": 4200, - * "battery_capacity": 2200 + * "powerfeather": { + * "battery_capacity":, 2200 + * "supply_maintain_voltage": 5500, + * "temperature_measurement": true, + * "battery_fuel_gauge": true + * } * } */ void loadNodeCfg( @@ -68,4 +75,4 @@ void loadNodeCfg( uint16_t &batt_low, uint16_t &batt_discharge_lim, uint16_t &batt_charge_lim, - uint16_t &batt_capacity); \ No newline at end of file + struct sPowerFeatherCfg &powerFeatherCfg); \ No newline at end of file