matthias-bs / BresserWeatherSensorLW

Bresser 868 MHz Weather Sensor Radio Receiver based on ESP32/RP2040 and SX1262/SX1276 - sends data to a LoRaWAN Network
MIT License
10 stars 1 forks source link
arduino arduino-pico ble bluetooth bluetooth-low-energy chirpstack esp32 esp32-arduino lorawan lorawan-application rp2040 thethingsnetwork ttn-application weather-sensors weather-station

BresserWeatherSensorLW

CI GitHub release License: MIT

Bresser 868 MHz Weather Sensor Radio Receiver based on ESP32/RP2040 and SX1262/SX1276 — sends data to a LoRaWAN Network

Moreover, this project provides a base for a generic LoRaWAN device, which transmits sensor data, digital or analog input signals.

This was originally a remake of BresserWeatherSensorTTN based on RadioLib instead of MCCI Arduino LoRaWAN Library for LoRaWAN communication.

Important Notes

Features

Contents

LoRaWAN Uplink Messages

With the default configuration, the device will periodically send 3 different uplink messages. The LoRaWAN Node Status message and the Application Layer / Sensor Status message can be disabled by setting the corresponding interval to zero.

Sensor Data Message

LoRaWAN Node Status Message

The data types are implemented in lora-serialization and the [Payload Formatters] (#lorawan-payload-formatters). int16 and int32 are extensions in the payload formatter for signed integers (implemented as uint<16|32> + offset).

Application Layer / Sensor Status Massage

See Parameters for more details.

Supported Hardware

Status Setup Board (/ Revision) Define (Prefix: ARDUINO_) Radio Module Notes
:hourglass: LILYGO®TTGO-LORA32 V1 TTGO LoRa32-OLED /
TTGO LoRa32 V1 (No TFCard)
TTGO_LORA32_V1 SX1276 (HPD13A) -
:hourglass: LILYGO®TTGO-LORA32 V2 TTGO LoRa32-OLED /
TTGO LoRa32 V2
TTGO_LoRa32_V2 SX1276 (HPD13A) For LMIC only: Wire DIO1 to GPIO33
:white_check_mark: LILYGO®TTGO-LORA32 V2.1 TTGO LoRa32-OLED /
TTGO LoRa32 V2.1 (1.6.1)
TTGO_LoRa32_v21new SX1276 (HPD13A) -
:hourglass: Heltec Wireless Stick Heltec Wireless Stick HELTEC_WIRELESS_STICK SX1276 -
:hourglass: Heltec WiFi LoRa 32 V2 Heltec WiFi LoRa 32(V2) HELTEC_WIFI_LORA_32_V2 SX1276 -
:white_check_mark: Heltec WiFi LoRa 32 V3 Heltec WiFi LoRa 32(V3) HELTEC_WIFI_LORA_32_V3 SX1262 -
:white_check_mark: LoRaWAN_Node FireBeetle-ESP32 DFROBOT_FIREBEETLE_ESP32 & LORAWAN_NODE SX1276 (RFM95W) -
:white_check_mark: DFRobot FireBeetle ESP32 IoT Microcontroller with FireBeetle Cover LoRa Radio 868MHz FireBeetle-ESP32 DFROBOT_FIREBEETLE_ESP32 & DFROBOT_COVER_LORA SX1276 (LoRa1276) Wiring on the cover:
D2 to RESET
D3 to DIO0
D4 to CS
D5 to DIO1
:hourglass: Adafruit Feather ESP32S2 with Adafruit LoRa Radio FeatherWing Adafruit Feather ESP32-S2 FEATHER_ESP32S2 SX1276 (RFM95W) No Bluetooth available!
Wiring on the Featherwing:
E to IRQ
D to CS
C to RST
A to DI01
:white_check_mark: Thingpulse ePulse Feather with Adafruit LoRa Radio FeatherWing ThingPulse ePulse Feather THINGPULSE_EPULSE_FEATHER SX1276 (RFM95W) Wiring on the Featherwing:
E to IRQ
D to CS
C to RST
A to DI01
:white_check_mark: M5Stack Core2 with M5Stack Module LoRa868 M5Core2 M5STACK_CORE2 SX1276
(RA-01H)
Wiring on the LoRa868 Module:
DIO1 to GPIO35

"M5Unified" must be installed
M5.begin()is called to control power management
:white_check_mark: ESP32-S3 PowerFeather with Adafruit LoRa Radio FeatherWing ESP32-S3 PowerFeather ESP32S3_POWERFEATHER SX1276 (RFM95W) Wiring on the Featherwing:
E to IRQ
D to CS
C to RST
A to DI01

"PowerFeather-SDK" must be installed
Board.init(BATTERY_CAPACITY_MAH); is called to control power management
:white_check_mark: Adafruit Feather RP2040 with Adafruit LoRa Radio FeatherWing Adafruit Feather RP2040 ADAFRUIT_FEATHER_RP2040 SX1276 (RFM95W) No Bluetooth available!
Configuration: Choose an entry with "FS" in section Flash Size!
Wiring on the Featherwing:
E to IRQ
D to CS
C to RST
A to DI01

:hourglass: — confirmation pending

:white_check_mark: — confirmed

Predefined Board Configurations

[!NOTE] By using one of the boards listed in Supported Hardware and selecting this Board / Board Revision in the Arduino IDE, you get a working hardware configuration.

By selecting a Board and a Board Revision in the Arduino IDE, a define is passed to the preprocessor/compiler. A default configuration is assumed based on this define. If this is not what you need, you have to switch to Manual Configuration.

If you are not using the Arduino IDE, you can use the defines in Supported Hardware with your specific tool chain to get the same result.

If enabled in the Arduino IDE Preferences ("Verbose Output"), the preprosessor will provide some output regarding the selected configuration, e.g.

ARDUINO_ADAFRUIT_FEATHER_ESP32S2 defined; assuming RFM95W FeatherWing will be used
[...]
Radio chip: SX1276
Pin config: RST->0 , IRQ->5 , NSS->6 , GPIO->11

User-Defined Pinout and Radio Chip Configurations

Required Information

[!NOTE] Alternative pin names: SX1262: IRQ => DIO0, GPIO => BUSY SX1276: IRQ => DIO0, GPIO => DIO1

[!IMPORTANT] With the information above, the source code in both BresserWeatherSensorReceiver and BresserWeatherSensorLW has to be modified!

Board Identification

To find out which #define is set for identifying your board:

In the Arduino IDE —

The string which resembles your board name — without the preceding -D — is the wanted define (e.g. ARDUINO_FEATHER_ESP32).

This can be used by the C++ preprocessor to select board specific code, e.g.

#if defined(ARDUINO_FEATHER_ESP32)
  // Put Adafruit Feather ESP32 specific code here
#endif

BresserWeatherSensorReceiver Configuration

In WeatherSensorCfg.h:

BresserWeatherSensorLW

In config.h:

Provide Feedback

If your setup is working — congratulations! Be nice and provide your insights to the project to help others!

User-Defined Battery Voltage Measurement

[!WARNING] Exceeding the allowed supply voltage or analog digital converter (ADC) input voltage range or reversing the polarity will destroy your board!

Overview

While the battery voltage measurement is not crutial for operation, it is still important if the device is powered from a battery.

The battery voltage is used for:

[!CAUTION] The following section is meant as a general introduction. Actual implementations may vary. Consult you board's documentation for details!
The boards used in this project can be supplied by 5V via USB or by another supply voltage via a second power supply connector. Many have an integrated lithium-ion battery charger. A lithium-ion battery has a voltage range of ~2.4...4.2V. The usable voltage range for the board depends on the actual circuit. If a voltage regulator is used (and no voltage converter), the usable battery voltage range is ~3.3...4.2V.

The MCUs used in this project have an integrated ADC with an input voltage range of 0...3.3V. Therefore, the battery voltage has to be reduced by a voltage divider to provide a voltage range suitable for the ADC.

The ADC input circuitry may come in a few different flavors:

  1. A voltage divider is directly connected to the battery and to the ADC input
  2. Resistors for a voltage divider are present, but solder bridges are required to actually connect them
  3. A voltage divider is implemented, but an electronic switch has to be enabled for using it
  4. A voltage divider has to be implemented as external circuit

Last, but not least, some boards provide a separate battery monitoring chip.

Only the cases 1 and 2 will be covered here.

ADC Input Pin and Voltage Divider Ratio

Find the voltage divider and the ADC input pin used for battery voltage measurement (if available) in your board's circuit diagram.

In BresserWeatherSensorLWCfg.h:

The function getBatteryVoltage() in adc.cpp provides the battery voltage. Any board specific implementation should be placed there. getBatteryVoltage() returns 0 for any unknown board or a known board with out a default ADC input circuit to indicate that the battery voltage cannot be measured.

LoRaWAN Network Service Configuration

Create an account and set up a device configuration in your LoRaWAN network provider's web console, e.g. The Things Network.

[!IMPORTANT] Check the maximum permitted payload size and uplink/downlink rate according to your regional parameters and change the configuration if required! See Airtime calculator for LoRaWAN.

Software Build Configuration

Required Configuration

// The Device EUI & two keys can be generated on the TTN console

// Replace with your Device EUI
#define RADIOLIB_LORAWAN_DEV_EUI   0x---------------

// Replace with your App Key
#define RADIOLIB_LORAWAN_APP_KEY   0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--

// Put your Nwk Key here
#define RADIOLIB_LORAWAN_NWK_KEY   0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--

Optional Configuration

Header: BresserWeatherSensorLWCfg.h.

Downlink: see Remote Configuration Commands

File: see 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
SLEEP_INTERVAL_LONG
LW_STATUS_INTERVAL
APP_STATUS_INTERVAL
WEATHERSENSOR_TIMEOUT
Timing parameters X X
en_decoders Enabled sensor decoders
(disabling unused decoders saves CPU cycles / energy)
X
BATTERY_WEAK
BATTERY_LOW
BATTERY_DISCHARGE_LIM
BATTERY_CHARGE_LIM
Battery voltage levels in mV 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 X X
PF_TEMPERATURE_MEASUREMENT /
powerfeather/temperature_measurement
see https://docs.powerfeather.dev X X
PF_BATTERY_FUEL_GAUGE /
powerfeather/battery_fuel_gauge
see https://docs.powerfeather.dev X X
PF_SUPPLY_MAINTAIN_VOLTAGE /
powerfeather/supply_maintain_voltage
see https://docs.powerfeather.dev X X
PF_MAX_CHARGE_CURRENT_MAH /
powerfeather/max_charge_current
see https://docs.powerfeather.dev X X

Enabling Debug Output

Debug Output Configuration in Arduino IDE

Test Run

Watch your board's debug output in the serial console and the LoRaWAN communication in your network provider's web console.

LoRaWAN Payload Formatters

Upload Uplink Formatter and Downlink Formatter scripts in your LoRaWAN network service provider's web console to allow decoding / encoding of raw data to / from JSON format.

See The Things Network MQTT Integration and Payload Formatters and TS013-1.0.0 Payload Codec API for more details.

Encoding of Unavailable or Invalid Data

For various reasons, data can be (temporarily) unavailable or invalid, e.g. due to a sensor radio message reception failure, sensor initialization, or a low sensor battery. The sensor data uplink message has a fixed format (see Payload Configuration), therefore it is not possible to simply omit any data. The allowed payload size of a LoRaWAN frame is very small, therefore space should not be wasted by dedicated 'data valid' flags.

As a solution, unavailable/invalid data are encoded as special values — out of the normal range — in the data fields. E.g. a humidity value, which is encoded as 8-bit unsigned value with a range of 0 to 100 percent, is encoded as 255 (0xFF) to indicate invalid data. Those special values defined in BresserWeatherSensorLWCfg.h.

The Uplink Payload Formatter detects and skips this data, i.e. the JSON output string contains only valid data. This can be changed by setting SKIP_INVALID_SIGNALS = false.

The Things Network Payload Formatters Setup

Uplink Formatter

Decode uplink payload (a sequence of bytes) into JSON format, i.e. data structures which are readable/suitable for further processing.

In The Things Network Console:

  1. Go to "Payload formatters" -> "Uplink"
  2. Select "Formatter type": "Custom Javascript formatter"
  3. "Formatter code": Paste scripts/uplink_formatter.js
  4. Apply "Save changes"

TTN Uplink Formatter

[!NOTE] The actual payload depends on the options selected in the Arduino sketch (see BresserWeatherSensorsLW.cfg) — the decoder must be edited accordingly (add or remove data types and JSON identifiers). The configuration dependent part of the decoder can be created with a C++ preprocessor and the Python script generate_decoder.py.

Downlink Formatter

Encode downlink payload from JSON to a sequence of bytes.

In The Things Network Console:

  1. Go to "Payload formatters" -> "Downlink"
  2. Select "Formatter type": "Custom Javascript formatter"
  3. "Formatter code": Paste scripts/downlink_formatter.js
  4. Apply "Save changes"

MQTT Integration

The Things Network MQTT Integration

TTN provides an MQTT broker. How to receive and decode the payload with an MQTT client - see https://www.thethingsnetwork.org/forum/t/some-clarity-on-mqtt-topics/44226/2

V3 topic:

v3/<ttn app id><at symbol>ttn/devices/<ttn device id>/up

v3 message key field jsonpaths:

<ttn device id> = .end_device_ids.device_id
<ttn app id> = .end_device_ids.application_ids.application_id  // (not including the <at symbol>ttn in the topic)
<payload> = .uplink_message.frm_payload

JSON-Path with Uplink-Decoder (see scripts/uplink_formatter.js)

.uplink_message.decoded_payload.bytes.<variable>

ChirpStack and InfluxDB Integration

ChirpStack and InfluxDB Integration kindly provided by Davide D'Asaro.

Datacake Integration

Datacake / The Things Network Setup

YouTube Video: Get started for free with LoRaWaN on The Things Network and Datacake IoT Platform

Desktop Dashboard

Datacake_Dashboard_Desktop

Mobile Dashboard

Datacake_Dashboard_Mobile

Remote Configuration Commands / Status Requests via LoRaWAN

Many software parameters can be defined at compile time, i.e. in BresserWeatherSensorLWCfg.h. A few parameters can also be changed and queried at run time via LoRaWAN, either using raw data or using Javascript Uplink/Downlink Formatters.

Parameters

Parameter Description
Weather sensor receive timeout in seconds; 0...255
Sleep interval (regular) in seconds; 0...65535
Sleep interval (energy saving mode) in seconds; 0...65535
LoRaWAN node status message uplink interval in no. of uplink frames; 0...255; 0: disabled
Battery voltage in mV
0: regular sleep interval / 1: long sleep interval (depending on U_batt)
\<epoch> Unix epoch time, see https://www.epochconverter.com/ ( \<integer> / "0x....")
Raingauge reset flags; 0...15 (1: hourly / 2: daily / 4: weekly / 8: monthly) / "0x0"..."0xF"
Bresser sensor scan time in seconds; 0...255 (only for CMD_SCAN_SENSORS)
\<idX> Sensor ID
\<decoderX> Matching payload decoder
\<typeX> Sensor type
\<chX> Sensor channel
Sensor data flags
\<rssi> Sensor radio signal RSSI in dBm (sign inverted)
Real time clock source; 0x00: GPS / 0x01: RTC / 0x02: LORA / 0x03: unsynched / 0x04: set (source unknown)
Bresser sensor IDs include list; e.g. "0xDEADBEEF"; "0x00000000" => empty list => default values
Bresser sensor IDs include list; e.g. "0xDEADBEEF"; "0x00000000" => empty list => default values
Max. number of Bresser sensors per receive cycle; 1...8
Flags for getData(); see BresserWeatherSensorReceiver
Enabled sensor data decoders; see BresserWeatherSensorReceiver
BLE active scan; 1 (active scan) / 0 (passive scan)
BLE scan time in seconds; 0...255
BLE sensor MAC addresses; e.g. "DE:AD:BE:EF:12:23"
\<typeN> Bitmap for enabling Bresser sensors of type \<N>; each bit position corresponds to a channel,
e.g. bit 0 controls channel 0; unused bits can be used to select features
\<onewire> Bitmap for enabling 1-Wire sensors; each bit position corresponds to an index
\<analog> Bitmap for enabling analog input channels; each bit position corresponds to a channel
\<digital> Bitmap for enabling digital input channels in a broader sense — GPIO, SPI, I2C, UART, ...
Bitmap for Bresser sensor type \<N> battery status; each bit position corresponds to a channel
App Layer (sensor status) message uplink interval in no. of uplink frames; 0...255; 0: disabled
Bitmap for 1-Wire sensor status; each bit position corresponds to an index
Bitmap for analog input status; each bit position corresponds to a channel
Bitmap for digital input channel status
Bitmap for BLE sensor battery status

[!NOTE] See Payload Configuration for more details!

[!WARNING] Confirmed downlinks should not be used! (see here for an explanation.)

[!IMPORTANT] To set sensors_inc / sensors_exc to the compile time default configuration, set the first ID in CMD_SET_SENSORS_INC / CMD_SET_SENSORS_EXC to "0x00000000". To set the BLE sensor addresses to the compile time default configuration, set the first address in CMD_SET_BLE_ADDR to "00:00:00:00:00:00".

Default Parameter Values

Using Raw Data

Command Port Downlink Uplink
CMD_GET_DATETIME 0x20 (32) 0x00 epoch[31:24]
epoch[23:16]
epoch[15:8]
epoch[7:0]
rtc_source[7:0]
CMD_SET_DATETIME 0x21 (33) epoch[31:24]
epoch[23:16]
epoch[15:8]
epoch[7:0]
n.a.
CMD_SET_SLEEP_INTERVAL 0x31 (49) sleep_interval[15:8]
sleep_interval[7:0]
n.a.
CMD_SET_SLEEP_INTERVAL_LONG 0x33 (51) sleep_interval_long[15:8]
sleep_interval_long[7:0]
n.a.
CMD_SET_LW_STATUS_INTERVAL 0x35 (53) lw_status_interval[7:0] n.a.
CMD_GET_LW_CONFIG 0x36 (54) 0x00 sleep_interval[15:8]
sleep_interval[7:0]
sleep_interval_long[15:8]
sleep_interval_long[7:0]
CMD_GET_LW_STATUS 0x38 (56) 0x00 ubatt_mv[15:8]
ubatt_mv[7:0]
long_sleep[7:0]
CMD_GET_APP_STATUS_INTERVAL 0x40 (64) 0x00 app_status_interval[7:0]
CMD_SET_APP_STATUS_INTERVAL 0x41 (65) app_status_interval[7:0] n.a.
CMD_GET_SENSORS_STAT 0x42 (66) 0x00 type00_st[7:0]
type01_st[7:0]
...
type15_st[7:0]
onewire_st[15:8]
onewire_st[7:0]
analog_st[15:8]
analog_st[7:0]
digital_st[31:24]
digital_st[23:16]
digital_st[15:8]
digital_st[7:0]
ble_st[15:8]
ble_st[7:0]
CMD_GET_APP_PAYLOAD_CFG 0x46 (70) 0x00 type00[7:0]
type01[7:0]
...
type15[7:0]
onewire[15:8]
onewire[7:0]
analog[15:8]
analog[7:0]
digital[31:24]
digital[23:16]
digital[15:8]
digital[7:0]
CMD_SET_APP_PAYLOAD_CFG 0x47 (71) type00[7:0]
type01[7:0]
...
type15[7:0]
onewire[15:8]
onewire[7:0]
analog[15:8]
analog[7:0]
digital[31:24]
digital[23:16]
digital[15:8]
digital[7:0]
n.a.
CMD_GET_WS_TIMEOUT 0xC0 (192) 0x00 ws_timeout[7:0]
CMD_SET_WS_TIMEOUT 0xC1 (193) ws_timeout[7:0] n.a.
CMD_RESET_RAINGAUGE 0xC3 (195) flags[7:0] n.a.
CMD_SCAN_SENSORS 0xC4 (196) ws_scantime[7:0] id0[31:24]
id0[23:16]
id0[15:8]
id0[7:0]
decoder0[3:0]
type0[3:0]
ch0[7:0]
data_flags0[7:0]
rssi0[7:0]
...
CMD_GET_SENSORS_INC 0xC6 (198) 0x00 sensors_inc0[31:24]
sensors_inc0[23:15]
sensors_inc0[16:8]
sensors_inc0[7:0]
...
CMD_SET_SENSORS_INC 0xC7 (199) sensors_inc0[31:24]
sensors_inc0[23:16]
sensors_inc0[15:8]
sensors_inc0[7:0]
...
n.a.
CMD_GET_SENSORS_EXC 0xC8 (200) 0x00 sensors_exc0[31:24]
sensors_exc0[23:15]
sensors_exc0[16:8]
sensors_exc0[7:0]
...
CMD_SET_SENSORS_EXC 0xC9 (201) sensors_exc0[31:24]
sensors_exc0[23:16]
sensors_exc0[15:8]
sensors_exc0[7:0]
...
n.a.
CMD_GET_SENSORS_CFG 0xCA (202) 0x00 max_sensors[7:0]
rx_flags[7:0]
en_decoders<7:0>
CMD_SET_SENSORS_CFG 0xCB (203) max_sensors[7:0]
rx_flags[7:0]
en_decoders<7:0>
n.a.
CMD_GET_BLE_CONFIG 0xD0 (208) 0x00 ble_active[7:0]
ble_scantime[7:0]
CMD_SET_BLE_CONFIG 0xD1 (209) ble_active[7:0]
ble_scantime[7:0]
n.a.
CMD_GET_BLE_ADDR 0xD2 (210) 0x00 ble_addr0[47:40]
ble_addr0[39:32]
ble_addr0[31:24]
ble_addr0[23:16]
ble_addr0[15:8]
ble_addr0[7:0]
...
CMD_SET_BLE_ADDR 0xD3 (211) ble_addr0[47:40]
ble_addr0[39:32]
ble_addr0[31:24]
ble_addr0[23:16]
ble_addr0[15:8]
ble_addr0[7:0]
...
n.a.

The Things Network Examples

Example 1: Set SLEEP_INTERVAL to 360 seconds
  1. Set port for CMD_SET_SLEEP_INTERVAL to 49
  2. Convert interval to hex: 300 = 0x012C
  3. Set payload to 0x01 0x2C
  4. Send downlink via The Things Network Console

TTN Downlink as Hex

Example 2: Set Date/Time
  1. Set port for CMD_SET_DATETIME to 33
  2. Get epoch (e.g. from https://www.epochconverter.com/hex) (Example: 0x63B2BC32); add an offset (estimated) for time until received (Example: + 64 / 0x40 seconds => 0x63B2BC72)
  3. Set payload to 0x63 0xB2 0xBC 0x72
  4. Send downlink via The Things Network Console

Using the Javascript Uplink/Downlink Formatters

[!NOTE] The command ("cmd": ...) may be omitted if it can be derived from the given parameters.

Command Downlink Uplink
CMD_GET_DATETIME {"cmd": "CMD_GET_DATETIME"} {"epoch": \<epoch>}
CMD_SET_DATETIME {"epoch": \<epoch>} n.a.
CMD_SET_SLEEP_INTERVAL {"sleep_interval": } n.a.
CMD_SET_SLEEP_INTERVAL_LONG {"sleep_interval_long": } n.a.
CMD_SET_LW_STATUS_INTERVAL {"lw_status_interval": } n.a.
CMD_GET_LW_CONFIG {"cmd": "CMD_GET_LW_CONFIG"} {"sleep_interval": , "sleep_interval_long": , "lw_status_interval": }
CMD_GET_LW_STATUS {"cmd": "CMD_GET_LW_STATUS"} {"ubatt_mv": , "long_sleep": }
CMD_GET_APP_STATUS_INTERVAL {"cmd": "CMD_GET_APP_STATUS_INTERVAL"} {"app_status_interval": }
CMD_SET_APP_STATUS_INTERVAL {"app_status_interval": } n.a.
CMD_GET_SENSORS_STAT {"cmd": "CMD_GET_SENSORS_STAT"} "sensor_status": {"ble": , "bresser": [, ..., ]}
CMD_GET_APP_PAYLOAD_CFG {"cmd": "CMD_GET_APP_PAYLOAD_CFG"} {"bresser": [\<type0>, \<type1>, ..., \<type15>], "onewire": \<onewire>, "analog": \<analog>, "digital": \<digital>}
CMD_SET_APP_PAYLOAD_CFG {"bresser": [\<type0>, \<type1>, ..., \<type15>], "onewire": \<onewire>, "analog": \<analog>, "digital": \<digital>} n.a.
CMD_GET_WS_TIMEOUT {"cmd": "CMD_GET_WS_TIMEOUT"} {"ws_timeout": }
CMD_SET_WS_TIMEOUT {"ws_timeout": } n.a.
CMD_RESET_RAINGAUGE {"reset_flags": } n.a.
CMD_SCAN_SENSORS {"ws_scantime": } {"found_sensors": [{"id": \<id0>, "decoder": \<decoder0>, "type": \<type0>, "ch": \<ch0>, "data_flags": , "rssi": \<rssi0>}, ...]}
CMD_GET_SENSORS_INC {"cmd": "CMD_GET_SENSORS_INC"} {"sensors_inc": [, ..., ]}
CMD_SET_SENSORS_INC {"sensors_inc": [, ..., ]} n.a.
CMD_GET_SENSORS_EXC {"cmd": "CMD_GET_SENSORS_EXC"} {"sensors_exc": [, ..., ]}
CMD_SET_SENSORS_EXC {"sensors_exc": [, ..., ]} n.a.
CMD_GET_SENSORS_CFG {"cmd": "CMD_GET_SENSORS_CFG"} {"max_sensors": , "rx_flags": , "en_decoders": }
CMD_SET_SENSORS_CFG {"max_sensors": , "rx_flags": , "en_decoders": } n.a.
CMD_GET_BLE_CONFIG {"cmd": "CMD_GET_BLE_CONFIG"} {"ble_active": , "ble_scantime": }
CMD_SET_BLE_CONFIG {"ble_active": , "ble_scantime": } n.a.
CMD_GET_BLE_ADDR {"cmd": "CMD_GET_BLE_ADDR"} {"ble_addr": [, ..., ]}
CMD_SET_BLE_ADDR {"ble_addr": [, ..., ]} n.a.

The Things Network Examples

Example 1: Set SLEEP_INTERVAL to 360 seconds
  1. Build payload as JSON string: {"sleep_interval": 360} — the correct port is selected automatically
  2. Send downlink via The Things Network Console

TTN Downlink as JSON

Example 2: Set Date/Time
  1. Get epoch (e.g. from https://www.epochconverter.com) (Example: 1692729833); add an offset (estimated) for time until received (Example: + 64 seconds => 1692729897)
  2. Build payload as JSON string: {"epoch": 1692729897}
  3. Send downlink via The Things Network Console

Scanning for Sensors

[!NOTE] The command CMD_SCAN_SENSORS allows to gather information about all sensors within range.

The differences between regular sensor reception and CMD_SCAN_SENSORS are:

The number of sensors which can be reported is limited by the LoRaWAN uplink payload size, e.g. with a limit of 51 bytes, a maximum of 6 sensors can be reported. Scanning can be repeated to get another sample (due to a more or less random relation between sensor's start of transmission and start of scan process).

Example uplink (response to {"ws_scantime": 180}):

"found_sensors": [
            {
              "ch": 0,
              "decoder": "6-in-1",
              "flags": "0x0c",
              "id": "0x792882a2",
              "rssi": -100,
              "type": "Weather Sensor"
            },
            {
              "ch": 1,
              "decoder": "6-in-1",
              "flags": "0x00",
              "id": "0x22400873",
              "rssi": -52,
              "type": "Pool / Spa Thermometer"
            },
            {
              "ch": 0,
              "decoder": "6-in-1",
              "flags": "0x1f",
              "id": "0x39582376",
              "rssi": -78,
              "type": "Weather Sensor"
            },
            {
              "ch": 0,
              "decoder": "Lightning",
              "flags": "0x00",
              "id": "0x0000eefb",
              "rssi": -107,
              "type": "Lightning Sensor"
            },
            {
              "ch": 1,
              "decoder": "6-in-1",
              "flags": "0x00",
              "id": "0x67566300",
              "rssi": -96,
              "type": "Soil Temperature and Moisture Sensor"
            },
            {
              "ch": 3,
              "decoder": "Leakage",
              "flags": "0x00",
              "id": "0x28966796",
              "rssi": -106,
              "type": "Water Leakage Sensor"
            }
          ]

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 as required and install it to the board's Flash memory using earlephilhower/arduino-littlefs-upload.

[!WARNING] Only very basic validation of the file secrets.json is implemented — 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
see Time Zone Abbreviations
"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
(long sleep interval)
3500
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
powerfeather/ PowerFeather specific (see https://docs.powerfeather.dev)
  battery_capacity Battery capacity in mAh
(0: no battery connected)
see PowerFeather Docs: init()
0
  supply_maintain_voltage see PowerFeather Docs: setSupplyMaintainVoltage()
0: not set
0
  max_charge_current see PowerFeather Docs: setBatteryChargingMaxCurrent() 50
  temperature_measurement see PowerFeather Docs: enableBatteryTempSense() true
  battery_fuel_gauge see PowerFeather Docs: enableBatteryFuelGauge() true

Modify the example data/node_config.json as required and install it to the board's Flash memory using earlephilhower/arduino-littlefs-upload.

[!WARNING] No validation of the file node_config.json is implemented — check the debug output.

Payload Configuration

Default Configuration

The default payload configuration is as follows:

Sensor Signal Unit Type Bytes
Bresser Sensors
Weather Temperature °C temperature 2
Weather Humidity % uint8 1
Weather Rain Gauge mm rawfloat 4
Weather Wind Speed (Gusts) m/s uint16fp1 2
Weather Wind Speed (Avg) m/s uint16fp1 2
Weather Wind Direction ° uint16fp1 2
Weather UV Index - uint8fp1 1
Weather Post-processed: Hourly Rain mm rawfloat 4
Weather Post-processed: Daily Rain mm rawfloat 4
Weather Post-processed: Weekly Rain mm rawfloat 4
Weather Post-processed: Monthly Rain mm rawfloat 4
Temperature/Humidity Temperature °C temperature 2
Temperature/Humidity Humidity % uint8 1
Soil Moisture/Temperature Temperature °C temperature 2
Soil Moisture/Temperature Moisture % uint8 1
Lightning Post-processed: Event timestamp epoch unixtime 4
Lightning Post-processed: No. of events - uint16 2
Lightning Post-processed: Storm distance km uint8 1
1-Wire Sensors
Temperature Temperature °C temperature 2
Analog Interface
Ch 00 Battery voltage mV uint16 2
Digital Interface
— none —
BLE Sensors
Temperature/Humidity Temperature °C temperature 2
Temperature/Humidity Humidity % uint8 1

The data types are implemented in lora-serialization and the Payload Formatters. uint16fp1 and uint8fp1 are extensions in the payload formatter for fixed-point numbers with 1 decimal.

The default sensor data uplink configuration is defined in https://github.com/matthias-bs/BresserWeatherSensorLW/blob/cb918c6f17e2ef4f6e3f01d1cbb4b6a2c4e21089/BresserWeatherSensorLWCfg.h#L313 as a set of byte values, which are used in https://github.com/matthias-bs/BresserWeatherSensorLW/blob/cb918c6f17e2ef4f6e3f01d1cbb4b6a2c4e21089/src/AppLayer.h#L71 to define the array appPayloadCfgDef[APP_PAYLOAD_CFG_SIZE]. This array is used as a large bitmap, where each byte represents a specific sensor or interface and each bit corresponds to a channel or feature.

Config Helper

Changing the configuration by setting bitmaps is not really comfortable. Therefore the Config Helper has been created.

config_helper_20240725

Config Helper

In the Config Helper, you select the desired sensors/interfaces and the used channels/features and generate

  1. A bitmap to change the default payload configuration in the C++ source code
  2. A JSON string to configure the node via LoRaWAN downlink with the command CMD_SET_APP_PAYLOAD_CFG
  3. A JSON string to configure the Uplink Payload Formatter

[!NOTE] You do not have to modify the source code if you apply the configuration via LoRaWAN downlink!

Customizing the Application Layer

By replacing the Application Layer with your own code, you can use this project as a starting point for your own purpose.

Use extras/customization/AppLayerMinimal.h and extras/customization/AppLayerMinimal.cpp as a template.

AppLayer Programming Interface

Constructor

In BresserWeatherSensorLW.ino, the appLayer object is created:

/// Application layer
AppLayer appLayer(&rtc, &rtcLastClockSync);

The following constructor must be implemented by the AppLayer class:

/*!
 * \brief Constructor
 *
 * \param rtc Real time clock object
 * \param clocksync Timestamp of last clock synchronization
 */
AppLayer(ESP32Time *rtc, time_t *clocksync);

begin()

appLayer.begin() is called in BresserWeatherSensorLW.ino: setup() shortly after getting the RTC time. It can be used for any initialization which cannot be done in the constructor. A typical use case would be initialization of sensors which need a certain time to 'warm up' or acquire data. Other sensors/circuits should be started at the latest possible stage to save energy.

/*!
 * \brief AppLayer initialization
 */
void begin(void);

getPayloadStage1() and getPayloadStage2()

Both functions provide the sensor data as uplink message payload to the LoRaWAN network layer. The parameter port can be used to distinguish between different kinds of messages.

Using the LoraEncoder object from lora-serialization allows to encode common C++ data types as a sequence of bytes for transmission via LoRaWAN. Since the maximum permitted message payload size is very limited, the encoding must use as few bytes as possible.

getPayload

/*!
 * \brief Prepare / get payload at startup
 *
 * Use this if
 * - A sensor needs some time for warm-up or data acquisition
 * - The data acquisition has to be done directly after startup
 * - The radio transceiver is used for sensor communication
 *   before starting LoRaWAN activities
 *
 * \param port LoRaWAN port
 * \param encoder uplink encoder object
 */
void getPayloadStage1(uint8_t &port, LoraEncoder &encoder);

/*!
 * \brief Get payload before uplink
 *
 * Use this if
 * - The radio transceiver is NOT used for sensor communication
 * - The sensor preparation has been started in stage1
 * - The data aquistion has to be done immediately before uplink
 *
 * \param port LoRaWAN port
 * \param encoder uplink encoder object
 */
void getPayloadStage2(uint8_t &port, LoraEncoder &encoder);

decodeDownlink()

If node.sendReceive() provided a downlink message, the LoRaWAN network layer tries to decode it. If this fails — because according to port, it is not directed at the network layer — the message is passed to the ApplicationLayer via appLayer.decodeDownlink().

/*!
 * \brief Decode app layer specific downlink messages
 *
 * \param port downlink message port
 * \param payload downlink message payload
 * \param size payload size in bytes
 *
 * \returns config uplink request or 0
 */
uint8_t decodeDownlink(uint8_t port, uint8_t *payload, size_t size);

getConfigPayload()

A non-zero return value of decodeDownlink() triggers execution of getConfigPayload(). getConfigPayload() passes the uplink message payload and the required uplink port to the LoRaWAN network layer.

/*!
 * \brief Get configuration data for uplink
 *
 * Get the configuration data requested in a downlink command and
 * prepare it as payload in an uplink response.
 *
 * \param cmd command
 * \param port uplink port
 * \param encoder uplink data encoder object
 */
void getConfigPayload(uint8_t cmd, uint8_t &port, LoraEncoder &encoder);

getAppStatusUplinkInterval()

If implemented, status messages originating from the AppLayer can be sent as uplink periodically. The return value of getAppStatusUplinkInterval() is used by the LoRaWAN network layer to decide when such a message is due.

/*!
 * \brief Get sensor status message uplink interval
 *
 * \returns status uplink interval in frame counts (0: disabled)
 */
uint8_t getAppStatusUplinkInterval(void);

Implementation

Class Diagram

Class Diagram

Doxygen Generated Source Code Documentation

https://matthias-bs.github.io/BresserWeatherSensorWL/index.html

References

Based on

Legal

This project is in no way affiliated with, authorized, maintained, sponsored or endorsed by Bresser GmbH or any of its affiliates or subsidiaries.