Skip to content

Commit

Permalink
Add loading of config data from node_config.json (#89)
Browse files Browse the repository at this point in the history
* Added reading of hardware/deployment specific configuration node_config.json from LittleFS
* Renamed BATTERY_DISCHARGE_LIMIT/BATTERY_CHARGE_LIMIT
* Updated README.md
  • Loading branch information
matthias-bs authored Jul 26, 2024
1 parent 12fbbde commit d2054c4
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 29 deletions.
54 changes: 36 additions & 18 deletions BresserWeatherSensorLW.ino
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@
// 20240722 Added periodic uplink of LoRaWAN node status messages
// 20240723 Moved loadSecrets() to LoadSecrets.cpp/.h
// Moved decodeDownlink() & sendCfgUplink() to BresserWeatherSensorLWCmd.cpp/.h
// 20240725 Added reading of hardware/deployment specific configuration node_config.json
// from LittleFS (optional)
//
// ToDo:
// -
Expand Down Expand Up @@ -172,13 +174,13 @@ using namespace PowerFeather;
#include "BresserWeatherSensorLWCfg.h"
#include "BresserWeatherSensorLWCmd.h"
#include "src/LoadSecrets.h"
#include "src/LoadNodeCfg.h"
#include "src/AppLayer.h"
#include "src/adc/adc.h"

// Time zone info
const char *TZ_INFO = TZINFO_STR;


// Variables which must retain their values after deep sleep
#if defined(ESP32)
// Stored in RTC RAM
Expand Down Expand Up @@ -245,7 +247,6 @@ void print_wakeup_reason()
}
#endif


/*!
* \brief Compute sleep duration
*
Expand All @@ -261,14 +262,14 @@ void print_wakeup_reason()
*
* \returns sleep duration in seconds
*/
uint32_t sleepDuration(void)
uint32_t sleepDuration(uint16_t battery_weak)
{
uint32_t sleep_interval = prefs.sleep_interval;
longSleep = false;

uint16_t voltage = getBatteryVoltage();
// Long sleep interval if battery is weak
if (voltage && voltage <= BATTERY_WEAK)
if (voltage && voltage <= battery_weak)
{
sleep_interval = prefs.sleep_interval_long;
longSleep = true;
Expand All @@ -289,7 +290,6 @@ uint32_t sleepDuration(void)
return sleep_interval;
}


#if defined(ESP32)
/*!
* \brief Enter sleep mode (ESP32 variant)
Expand Down Expand Up @@ -351,7 +351,6 @@ void gotoSleep(uint32_t seconds)
}
#endif


/// Print date and time (i.e. local time)
void printDateTime(void)
{
Expand All @@ -364,7 +363,6 @@ void printDateTime(void)
log_i("%s", tbuf);
}


/*!
* \brief Activate node by restoring session or otherwise joining the network
*
Expand Down Expand Up @@ -472,17 +470,36 @@ void setup()
M5.begin(cfg);
#endif

Serial.begin(115200);
delay(2000); // give time to switch to the serial monitor
log_i("Setup");

String timeZoneInfo(TZ_INFO);
uint16_t battery_weak = BATTERY_WEAK;
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

loadNodeCfg(
timeZoneInfo,
battery_weak,
battery_low,
battery_discharge_lim,
battery_charge_lim,
battery_capacity_mah);

#if defined(ARDUINO_ESP32S3_POWERFEATHER)
delay(2000);
Board.init(BATTERY_CAPACITY_MAH); // Note: Battery capacity / type has to be set for voltage measurement
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)
#endif

Serial.begin(115200);
delay(2000); // give time to switch to the serial monitor
log_i("Setup");

#if defined(ARDUINO_ARCH_RP2040)
// see pico-sdk/src/rp2_common/hardware_rtc/rtc.c
rtc_init();
Expand Down Expand Up @@ -519,7 +536,7 @@ void setup()
}

// Set time zone
setenv("TZ", TZ_INFO, 1);
setenv("TZ", timeZoneInfo.c_str(), 1);
printDateTime();

// Try to load LoRaWAN secrets from LittleFS file, if available
Expand All @@ -538,10 +555,10 @@ void setup()
preferences.end();

uint16_t voltage = getBatteryVoltage();
if (voltage && voltage <= BATTERY_LOW)
if (voltage && voltage <= battery_low)
{
log_i("Battery low!");
gotoSleep(sleepDuration());
gotoSleep(sleepDuration(battery_weak));
}

// build payload byte array (+ reserve to prevent overflow with configuration at run-time)
Expand Down Expand Up @@ -575,14 +592,14 @@ void setup()
{
battLevel = 255;
}
else if (voltage > BATTERY_CHARGE_LIMIT)
else if (voltage > battery_charge_lim)
{
battLevel = 0;
}
else
{
battLevel = static_cast<uint8_t>(
static_cast<float>(voltage - BATTERY_DISCHARGE_LIMIT) / static_cast<float>(BATTERY_CHARGE_LIMIT - BATTERY_DISCHARGE_LIMIT) * 255);
static_cast<float>(voltage - battery_discharge_lim) / static_cast<float>(battery_charge_lim - battery_discharge_lim) * 255);
battLevel = (battLevel == 0) ? 1 : battLevel;
battLevel = (battLevel == 255) ? 254 : battLevel;
}
Expand Down Expand Up @@ -614,6 +631,7 @@ void setup()
appStatusUplinkPending = true;
}

// Set lwStatusUplink flag if required
if (prefs.lw_stat_interval && (fCntUp % prefs.lw_stat_interval == 0))
{
lwStatusUplinkPending = true;
Expand Down Expand Up @@ -763,7 +781,7 @@ void setup()
memcpy(LWsession, persist, RADIOLIB_LORAWAN_SESSION_BUF_SIZE);

// wait until next uplink - observing legal & TTN Fair Use Policy constraints
gotoSleep(sleepDuration());
gotoSleep(sleepDuration(battery_weak));
}

// The ESP32 wakes from deep-sleep and starts from the very beginning.
Expand Down
5 changes: 3 additions & 2 deletions BresserWeatherSensorLWCfg.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
// 20240704 Moved MAX_DOWNLINK_SIZE from config.h, changed to 51
// 20240722 Added LW_STATUS_INTERVAL,
// renamed STATUS_INTERVAL to APP_STATUS_INTERVAL
// 20240726 Renamed BATTERY_DISCHARGE_LIMIT/BATTERY_CHARGE_LIMIT
//
// Note:
// Depending on board package file date, some defines are written either
Expand Down Expand Up @@ -110,8 +111,8 @@ const uint8_t MAX_DOWNLINK_SIZE = 51;
#define BATTERY_LOW 3200

// Battery voltage limits in mV (usable range for the device) for battery state calculation
#define BATTERY_DISCHARGE_LIMIT 3200
#define BATTERY_CHARGE_LIMIT 4200
#define BATTERY_DISCHARGE_LIM 3200
#define BATTERY_CHARGE_LIM 4200

// Minimum sleep interval (in seconds)
#define SLEEP_INTERVAL_MIN 60
Expand Down
58 changes: 49 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ This was originally a remake of [BresserWeatherSensorTTN](https://github.com/mat
* [Using the Javascript Uplink/Downlink Formatters](#using-the-javascript-uplinkdownlink-formatters)
* [Scanning for Sensors](#scanning-for-sensors)
* [Loading LoRaWAN Network Service Credentials from File](#loading-lorawan-network-service-credentials-from-file)
* [Loading LoRaWAN Node Configuration from File](#loading-lorawan-node-configuration-from-file)
* [Payload Configuration](#payload-configuration)
* [Default Configuration](#default-configuration)
* [Config Helper](#config-helper)
Expand Down Expand Up @@ -296,15 +297,26 @@ Create an account and set up a device configuration in your LoRaWAN network prov

### Optional Configuration

In [BresserWeatherSensorLWCfg.h](BresserWeatherSensorLWCfg.h):
Header
: [BresserWeatherSensorLWCfg.h](BresserWeatherSensorLWCfg.h).

Downlink
: see [Remote Configuration Commands](#remote-configuration-commands--status-requests-via-lorawan)

File
: see [Loading LoRaWAN Node Configuration from File](#loading-lorawan-node-configuration-from-file)

| Parameter | Description | Header | Downlink | File |
| ---------------------- | ---------------------------------------------------------- |:------:|:--------:|:----:|
| `TZ_INFO` / `timezone` | your time zone | X | | X |
| `KNOWN_BLE_ADDRESSES` | BLE Sensor MAC Addresses | X | X | |
| `SLEEP_INTERVAL`<br>`SLEEP_INTERVAL_LONG`<br>`LW_STATUS_INTERVAL`<br>`APP_STATUS_INTERVAL`<br>`WEATHERSENSOR_TIMEOUT` | Timing parameters | X | X | |
| `en_decoders` | Enabled sensor decoders (saves CPU cycles / energy) | | X | |
| `BATTERY_WEAK`<br>`BATTERY_LOW`<br>`BATTERY_DISCHARGE_LIM`<br>`BATTERY_CHARGE_LIM` | Battery voltage levels in mV | X | | X |
| `BATTERY_CAPACITY_MAH` /<br>`battery_capacity` | Battery capacity | X | | X |
| see header file | ADC's input pins, dividers and oversampling | X | | |


* Configure your time zone by editing `TZ_INFO`
* Disable sensor/interface features which you do not want to use
* Adjust battery voltage levels
* Configure the timing parameters if required
* If enabled, configure your ATC MiThermometer's / Theengs Decoder's BLE MAC Address by by editing `KNOWN_BLE_ADDRESSES`
* Configure the ADC's input pins, dividers and oversampling settings as needed
* Disable sensor decoders wich are not needed

### Enabling Debug Output

Expand Down Expand Up @@ -641,14 +653,42 @@ This allows the following actions:

## Loading LoRaWAN Network Service Credentials from File

```[!NOTE]
To simplify deployment of a larger number of devices, LoRaWAN credentials can be read from a JSON file. This allows to use the same source code and binary file for a fleet of devices.
```

If a valid file `secrets.json` exists on LittleFS, the settings defined at compile time (in `secrets.h`) are overridden.

Modify the example [data/secrets.json](data/secrets.json) as required and install it to the board's Flash memory using [earlephilhower/arduino-littlefs-upload](https://github.com/earlephilhower/arduino-littlefs-upload).

> [!WARNING]
> Only very basic validation of the file `secrets.json` is implemented.
> Only very basic validation of the file `secrets.json` is implemented &mdash; check the debug output.
## Loading LoRaWAN Node Configuration from File

```[!NOTE]
To simplify deployment of a larger number of devices, LoRaWAN node configuration parameters can be read from a JSON file. These parameters are used for hardware or deployment environment specific settings. This allows to use the same source code and binary file for a fleet of devices.
```

If a valid file `node_config.json` exists on LittleFS, the default settings defined at compile time (in `BresserWeatherSensorCfg.h`) are overridden.
If a parameter cannot be read from the file, its default value will be used.

The following parameters are available:

| Parameter | Description | Default Value |
| --------------------- | ----------------------------------------------------------------------- | -------------:|
| timezone | Time Zone<br>see [Time Zone Abbreviations](https://remotemonitoringsystems.ca/time-zone-abbreviations.php) | `"CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00"` |
| battery_weak | Voltage threshold in mV for power saving mode<br>(long sleep interval) | `3500` |
| battery_low | Voltage threshold in mV for deep-discharge protection<br>(power off) | `3200` |
| battery_discharge_lim | Discharging voltage limit in mV<br>for battery level estimation | `3200` |
| battery_charge_lim | Charging voltage limit in mV<br>for battery level estimation | `4200` |
| battery_capacity | Battery capacity in mAh<br>(curently only used by ESP32S3 PowerFeather) | `0` |


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).

> [!WARNING]
> No validation of the file `node_config.json` is implemented &mdash; check the debug output.
## Payload Configuration

Expand Down
8 changes: 8 additions & 0 deletions data/node_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"timezone": "CET-1CEST,M3.5.0,M10.5.0/3",
"battery_weak": 3301,
"battery_low": 3401,
"battery_discharge_lim": 3201,
"battery_charge_lim": 4201,
"battery_capacity": 2201
}
110 changes: 110 additions & 0 deletions src/LoadNodeCfg.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
///////////////////////////////////////////////////////////////////////////////
// LoadNodeCfg.cpp
//
// Load LoRaWAN node configuration 'node_config.json' from LittleFS, if available
//
// This configuration file is intended for hardware/deployment environment
// specific settings (e.g. battery voltage levels, timezone)
//
// created: 07/2024
//
//
// MIT License
//
// Copyright (c) 2024 Matthias Prinke
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
//
// History:
//
// 20240725 Created
//
// ToDo:
// -
//
///////////////////////////////////////////////////////////////////////////////

#include "LoadNodeCfg.h"

// Load LoRaWAN node configuration 'node_config.json' from LittleFS, if available
void loadNodeCfg(
String &tzinfo,
uint16_t &batt_weak,
uint16_t &batt_low,
uint16_t &batt_discharge_lim,
uint16_t &batt_charge_lim,
uint16_t &batt_capacity)
{
uint16_t foo = 42;

if (!LittleFS.begin(
#if defined(ESP32)
// Format the LittleFS partition on error; parameter only available for ESP32
true
#endif
))
{
log_d("Could not initialize LittleFS.");
}
else
{
File file = LittleFS.open("/node_config.json", "r");

if (!file)
{
log_i("File 'node_config.json' not found.");
}
else
{
log_d("Reading 'node_config.json'");
JsonDocument doc;

// Deserialize the JSON document
DeserializationError error = deserializeJson(doc, file);
if (error)
{
log_d("Failed to read JSON file, using defaults.");
}
else
{
if (doc.containsKey("timezone"))
tzinfo = doc["timezone"].as<String>();
if (doc.containsKey("battery_weak"))
batt_weak = doc["battery_weak"];
if (doc.containsKey("battery_low"))
batt_low = doc["battery_low"];
if (doc.containsKey("battery_discharge_lim"))
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"];
} // deserializeJson o.k.
} // file read o.k.
file.close();
} // LittleFS o.k.

log_d("Timezone: %s", tzinfo.c_str());
log_d("Battery weak: %4d mV", batt_weak);
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);
} // loadNodeCfg()
Loading

0 comments on commit d2054c4

Please sign in to comment.