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.
-
Helium Network is not supported (requires LoRaWAN v1.0.x)
-
This should not be the first Arduino sketch you are ever trying to flash to your board - try something simple first (e.g.
blink.ino
) to get familiar with the tools and workflow. -
If you are new to LoRaWAN
- Check out The Things Fundamentals on LoRaWAN
- Read the excellent article RadioLib LoRaWAN on TTN starter script
-
You currently need RadioLib v7.0.1
-
You need espressif/arduino-esp32 v3.0.X
-
Try and configure BresserWeatherSensorReceiver (examples/BresserWeatherSensorBasic) stand-alone before using it with BresserWeatherSensorLW
-
If you previously used BresserWeatherSensorTTN
- The default payload configuration is different
- LoRaWAN downlink commands (and responses) are different
-
While the software has quite many configuration options, most users won't need any or just a few. See Required Configuration for the bare minimum configuration (and maybe Default Parameter Values & Default Configuration).
- Single 868 MHz Radio Transceiver for both Sensor Data Reception and LoRaWAN Connection
- Compatible to LoRaWAN Specification 1.1.0 / RP001 Regional Parameters 1.1 revision A
- Tested with The Things Network and ChirpStack
- Supports multiple 868 MHz Sensors (e.g. Weather Sensor and Soil Moisture Sensor or Indoor Thermometer/Hygrometer)
- See BresserWeatherSensorReceiver for supported sensors
- Low Power Design (using ESP32 Deep Sleep Mode / RP2040 Sleep State)
- Fast LoRaWAN Joining after Deep Sleep (using ESP32 RTC RAM / RP2040 RAM)
- ATC MiThermometer Bluetooth Low Energy Thermometer/Hygrometer Integration (optional)
- Theengs Decoder Bluetooth Low Energy Sensors Integration (optional)
- OneWire Temperature Sensor Integration (optional)
- ESP32/RP2040 Analog Digital Converter Integration (optional)
- A02YYUW / DFRobot SEN0311 Ultrasonic Distance Sensor (30...4500mm) (optional)
- Remote Configuration via LoRaWAN Downlink
- Easy Sensor Data Uplink Payload Configuration
- Implementation with Separation between LoRaWAN Network Layer and Application Layer for easy Repurposing
- Loading of LoRaWAN Secrets from JSON File on LittleFS (optional)
- Loading of Hardware/Deployment specific Configuration Parameters from JSON file on LittleFS (optional)
- LoRaWAN Uplink Messages
- Supported Hardware
- LoRaWAN Network Service Configuration
- Software Build Configuration
- LoRaWAN Payload Formatters
- MQTT Integration
- Datacake Integration
- Remote Configuration Commands / Status Requests via LoRaWAN
- Scanning for Sensors
- Loading LoRaWAN Network Service Credentials from File
- Loading LoRaWAN Node Configuration from File
- Payload Configuration
- Customizing the Application Layer
- Implementation
- Doxygen Generated Source Code Documentation
- References
- Legal
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.
- Payload: see Default Configuration
- Port: 1
- Interval: ~
<sleep_interval>
/<sleep_interval_long>
(depending on battery voltage); see Default Parameter Values
-
Payload:
Signal description Unit Type Bytes ubatt_mv Battery Voltage mv uint16 2 long_sleep Flag: Long sleep time (energy saving) — uint8 1 PowerFeather specific usupply_mv Supply Voltage (VDC or USB input) mv uint16 2 isupply_ma Supply Current (VDC or USB input) mA int16 2 isupply_ma Battery Current mA int16 2 soc Battery State of Charge % uint8 1 soh Battery State of Health % uint8 1 battery_cycles Estimated Battery Cycles — uint16 2 batt_time_min Estimated time to charge/discharge min int32 4 batt_temp_c Battery Temperature °C temperature 2
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).
- Port:
CMD_GET_LW_STATUS
- Interval:
<lw_status_interval>
(uplink frames); see Default Parameter Values
- Payload: Bresser/BLE Sensor Battery Status (Bitmap)
- Port:
CMD_GET_SENSORS_STAT
- Interval:
<app_status_interval>
(uplink frames); see Default Parameter Values
See Parameters for more details.
Status | Setup | Board (/ Revision) | Define (Prefix: ARDUINO_) | Radio Module | Notes |
---|---|---|---|---|---|
⌛ | LILYGO®TTGO-LORA32 V1 | TTGO LoRa32-OLED / TTGO LoRa32 V1 (No TFCard) |
TTGO_LORA32_V1 | SX1276 (HPD13A) | - |
⌛ | LILYGO®TTGO-LORA32 V2 | TTGO LoRa32-OLED / TTGO LoRa32 V2 |
TTGO_LoRa32_V2 | SX1276 (HPD13A) | For LMIC only: Wire DIO1 to GPIO33 |
✅ | LILYGO®TTGO-LORA32 V2.1 | TTGO LoRa32-OLED / TTGO LoRa32 V2.1 (1.6.1) |
TTGO_LoRa32_v21new | SX1276 (HPD13A) | - |
⌛ | Heltec Wireless Stick | Heltec Wireless Stick | HELTEC_WIRELESS_STICK | SX1276 | - |
⌛ | Heltec WiFi LoRa 32 V2 | Heltec WiFi LoRa 32(V2) | HELTEC_WIFI_LORA_32_V2 | SX1276 | - |
✅ | Heltec WiFi LoRa 32 V3 | Heltec WiFi LoRa 32(V3) | HELTEC_WIFI_LORA_32_V3 | SX1262 | - |
✅ | LoRaWAN_Node | FireBeetle-ESP32 | DFROBOT_FIREBEETLE_ESP32 & LORAWAN_NODE | SX1276 (RFM95W) | - |
✅ | 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 |
⌛ | 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 |
✅ | 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 |
✅ | 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 |
✅ | 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 |
✅ | 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 |
⌛ — confirmation pending
✅ — confirmed
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
-
Check the board manufacturer's datasheet, pinout specifications and schematic.
-
Check the board's pin definitions file (
pins_arduino.h
) in the arduino-esp32 project -
Which LoRaWAN radio chip is used? SX1262 or SX1276?
-
Which pins are used for SPI (SCK, MISO and MOSI)?
-
On-board LoRaWAN radio chip:
- Which GPIO pins are connected to NSS (CSN), RST, IRQ and GPIO?
-
Separate LoRaWAN module:
- Which GPIO pins are available (i.e. otherwise unconnected) for NSS (CSN), RST, IRQ and GPIO?
- Connect the ESP32 board with the LoRaWAN module according to the selected GPIO pins.
- Connect the SPI and power supply pins as required.
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!
To find out which #define
is set for identifying your board:
In the Arduino IDE —
- In the
File
menu, openPreferences
and enable the checkbox "Show verbose output during compile" - In the
Tools
menu, select your board (and board variant, if available) - Compile any sketch (e.g.
Blink
from the Arduino examples) - Search for a string starting with
-DARDUINO_
in the output window (e.g.-DARDUINO_FEATHER_ESP32
)
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
In WeatherSensorCfg.h
:
- Select or create a code section which will actually be used by the C++ preprocessor (`#if defined(<YOUR_BOARD_DEFINE>) ...).
- Set the radio chip according to your hardware by (un-)commenting
USE_SX1262
orUSE_SX1276
. - Set the pin definitions
PIN_RECEIVER_CS
,PIN_RECEIVER_IRQ
,PIN_RECEIVER_GPIO
andPIN_RECEIVER_RST
according to your hardware. - Cross check in the compiler log messages if the desired settings are actually used.
In config.h
:
- Select or create a code section which will actually be used by the C++ preprocessor (`#if defined(<YOUR_BOARD_DEFINE>) ...).
- Set the radio chip according to your hardware defining
LORA_CHIP
. - Set the pin definitions
#define PIN_LORA_NSS
,PIN_LORA_RST
,PIN_LORA_IRQ
andPIN_LORA_GPIO
according to your hardware. - Cross check in the compiler log messages if the desired settings are actually used.
If your setup is working — congratulations! Be nice and provide your insights to the project to help others!
Warning
Exceeding the allowed supply voltage or analog digital converter (ADC) input voltage range or reversing the polarity will destroy your board!
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:
- Providing battery status to the LoRaWAN network server on request
- Battery deep-discharge protection and energy saving mode
- Monitoring battery status via uplink (e.g. for optimization of transmission interval)
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:
- A voltage divider is directly connected to the battery and to the ADC input
- Resistors for a voltage divider are present, but solder bridges are required to actually connect them
- A voltage divider is implemented, but an electronic switch has to be enabled for using it
- 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.
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
:
- Add a code section with your board definition (see Board Identification).
- Define
PIN_ADC_IN
with your board specific pin. - Define
UBATT_DIV
with your voltage divider ratio if it differs from the default value of0.5
.
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.
Create an account and set up a device configuration in your LoRaWAN network provider's web console, e.g. The Things Network.
- LoRaWAN v1.1
- Regional Parameters 1.1 Revision A
- Device class A
- Over the air activation (OTAA)
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.
- Install the Arduino ESP32 board package in the Arduino IDE
- Select your ESP32 board
- Install all libraries as listed in package.json — section 'dependencies' — via the Arduino IDE Library Manager
- Clone (or download and unpack) the latest (BresserWeatherSensorLW Release)
- Set your LoRaWAN Network Service credentials —
RADIOLIB_LORAWAN_DEV_EUI
,RADIOLIB_LORAWAN_NWK_KEY
andRADIOLIB_LORAWAN_APP_KEY
— in secrets.h:
// 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--
- Load the sketch BresserWeatherSensorLW.ino
- Compile and Upload
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 |
Debug Output Configuration in Arduino IDE
Watch your board's debug output in the serial console and the LoRaWAN communication in your network provider's web console.
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.
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
.
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:
- Go to "Payload formatters" -> "Uplink"
- Select "Formatter type": "Custom Javascript formatter"
- "Formatter code": Paste scripts/uplink_formatter.js
- Apply "Save changes"
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.
Encode downlink payload from JSON to a sequence of bytes.
In The Things Network Console:
- Go to "Payload formatters" -> "Downlink"
- Select "Formatter type": "Custom Javascript formatter"
- "Formatter code": Paste scripts/downlink_formatter.js
- Apply "Save changes"
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 kindly provided by Davide D'Asaro.
YouTube Video: Get started for free with LoRaWaN on The Things Network and Datacake IoT Platform
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.
Parameter | Description |
---|---|
<ws_timeout> | Weather sensor receive timeout in seconds; 0...255 |
<sleep_interval> | Sleep interval (regular) in seconds; 0...65535 |
<sleep_interval_long> | Sleep interval (energy saving mode) in seconds; 0...65535 |
<lw_status_interval> | LoRaWAN node status message uplink interval in no. of uplink frames; 0...255; 0: disabled |
<ubatt_mv> | Battery voltage in mV |
<long_sleep> | 0: regular sleep interval / 1: long sleep interval (depending on U_batt) |
<epoch> | Unix epoch time, see https://www.epochconverter.com/ ( <integer> / "0x....") |
<reset_flags> | Raingauge reset flags; 0...15 (1: hourly / 2: daily / 4: weekly / 8: monthly) / "0x0"..."0xF" |
<ws_scantime> | 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 |
<data_flagsX> | Sensor data flags |
<rssi> | Sensor radio signal RSSI in dBm (sign inverted) |
<rtc_source> | Real time clock source; 0x00: GPS / 0x01: RTC / 0x02: LORA / 0x03: unsynched / 0x04: set (source unknown) |
<sensors_incX> | Bresser sensor IDs include list; e.g. "0xDEADBEEF"; "0x00000000" => empty list => default values |
<sensors_excX> | Bresser sensor IDs include list; e.g. "0xDEADBEEF"; "0x00000000" => empty list => default values |
<max_sensors> | Max. number of Bresser sensors per receive cycle; 1...8 |
<rx_flags> | Flags for getData(); see BresserWeatherSensorReceiver |
<en_decoders> | Enabled sensor data decoders; see BresserWeatherSensorReceiver |
<ble_active> | BLE active scan; 1 (active scan) / 0 (passive scan) |
<ble_scantime> | BLE scan time in seconds; 0...255 |
<ble_addrX> | 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, ... |
<typeN_st> | Bitmap for Bresser sensor type <N> battery status; each bit position corresponds to a channel |
<app_status_interval> | App Layer (sensor status) message uplink interval in no. of uplink frames; 0...255; 0: disabled |
<onewire_st> | Bitmap for 1-Wire sensor status; each bit position corresponds to an index |
<analog_st> | Bitmap for analog input status; each bit position corresponds to a channel |
<digital_st> | Bitmap for digital input channel status |
<ble_st> | 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".
- Sleep interval (long):
SLEEP_INTERVAL
,SLEEP_INTERVAL_LONG
; see BresserWeatherSensorLWCfg.h LW_STATUS_INTERVAL
: see BresserWeatherSensorLWCfg.hAPP_STATUS_INTERVAL
: see BresserWeatherSensorLWCfg.h- BLE addresses and scan parameters:
BLE_SCAN_TIME
,BLE_SCAN_MODE
,KNOWN_BLE_ADDRESSES
; see BresserWeatherSensorLWCfg.h - Weather sensor receive timeout:
WEATHERSENSOR_TIMEOUT
; see BresserWeatherSensorLWCfg.h - Sensor IDs include/exclude list:
SENSOR_IDS_EXC
/SENSOR_IDS_INC
; see WeatherSensorCfg.h - Sensor data uplink payload configuration: see Payload Configuration
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. |
- Set port for CMD_SET_SLEEP_INTERVAL to 49
- Convert interval to hex: 300 = 0x012C
- Set payload to 0x01 0x2C
- Send downlink via The Things Network Console
- Set port for CMD_SET_DATETIME to 33
- 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)
- Set payload to 0x63 0xB2 0xBC 0x72
- Send downlink via The Things Network Console
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": <sleep_interval>} | n.a. |
CMD_SET_SLEEP_INTERVAL_LONG | {"sleep_interval_long": <sleep_interval_long>} | n.a. |
CMD_SET_LW_STATUS_INTERVAL | {"lw_status_interval": <lw_status_interval>} | n.a. |
CMD_GET_LW_CONFIG | {"cmd": "CMD_GET_LW_CONFIG"} | {"sleep_interval": <sleep_interval>, "sleep_interval_long": <sleep_interval_long>, "lw_status_interval": <lw_status_interval>} |
CMD_GET_LW_STATUS | {"cmd": "CMD_GET_LW_STATUS"} | {"ubatt_mv": <ubatt_mv>, "long_sleep": <long_sleep>} |
CMD_GET_APP_STATUS_INTERVAL | {"cmd": "CMD_GET_APP_STATUS_INTERVAL"} | {"app_status_interval": <app_status_interval>} |
CMD_SET_APP_STATUS_INTERVAL | {"app_status_interval": <app_status_interval>} | n.a. |
CMD_GET_SENSORS_STAT | {"cmd": "CMD_GET_SENSORS_STAT"} | "sensor_status": {"ble": <ble_stat>, "bresser": [<bresser0_st>, ..., <bresser15_st>]} |
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": <ws_timeout>} |
CMD_SET_WS_TIMEOUT | {"ws_timeout": <ws_timeout>} | n.a. |
CMD_RESET_RAINGAUGE | {"reset_flags": <reset_flags>} | n.a. |
CMD_SCAN_SENSORS | {"ws_scantime": <ws_scantime>} | {"found_sensors": [{"id": <id0>, "decoder": <decoder0>, "type": <type0>, "ch": <ch0>, "data_flags": <data_flags0>, "rssi": <rssi0>}, ...]} |
CMD_GET_SENSORS_INC | {"cmd": "CMD_GET_SENSORS_INC"} | {"sensors_inc": [<sensors_inc0>, ..., <sensors_incN>]} |
CMD_SET_SENSORS_INC | {"sensors_inc": [<sensors_inc0>, ..., <sensors_incN>]} | n.a. |
CMD_GET_SENSORS_EXC | {"cmd": "CMD_GET_SENSORS_EXC"} | {"sensors_exc": [<sensors_exc0>, ..., <sensors_excN>]} |
CMD_SET_SENSORS_EXC | {"sensors_exc": [<sensors_exc0>, ..., <sensors_excN>]} | n.a. |
CMD_GET_SENSORS_CFG | {"cmd": "CMD_GET_SENSORS_CFG"} | {"max_sensors": <max_sensors>, "rx_flags": <rx_flags>, "en_decoders": <en_decoders>} |
CMD_SET_SENSORS_CFG | {"max_sensors": <max_sensors>, "rx_flags": <rx_flags>, "en_decoders": <en_decoders>} | n.a. |
CMD_GET_BLE_CONFIG | {"cmd": "CMD_GET_BLE_CONFIG"} | {"ble_active": <ble_active>, "ble_scantime": <ble_scantime>} |
CMD_SET_BLE_CONFIG | {"ble_active": <ble_active>, "ble_scantime": <ble_scantime>} | n.a. |
CMD_GET_BLE_ADDR | {"cmd": "CMD_GET_BLE_ADDR"} | {"ble_addr": [<ble_addr0>, ..., <ble_addrN>]} |
CMD_SET_BLE_ADDR | {"ble_addr": [<ble_addr0>, ..., <ble_addrN>]} | n.a. |
- Build payload as JSON string:
{"sleep_interval": 360}
— the correct port is selected automatically - Send downlink via The Things Network Console
- Get epoch (e.g. from https://www.epochconverter.com) (Example: 1692729833); add an offset (estimated) for time until received (Example: + 64 seconds => 1692729897)
- Build payload as JSON string: {"epoch": 1692729897}
- Send downlink via The Things Network Console
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:
-
Scanning will run for
<ws_scantime>
seconds (as opposed tows_timeout
) -
rx_flags
is set toDATA_ALL_SLOTS | DATA_COMPLETE
implicitly, i.e.- As many sensors as possible will be received and
- For weather sensors using the 6-in-1 protocol, both message types have to be received
before scanning is finished. (Scanning is also stopped when
<ws_scantime>
has expired.) -
The sensor ID filters (include/exclude list) are disabled
-
Different information is provided in the uplink message
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:
rssi
: Improvement of receptiontype
&ch
: Identification of sensorstype
,ch
,flags
: Payload configuration (CMD_SET_APP_PAYLOAD_CFG
)id
: Configuration of reception filters (include or exclude list;CMD_SET_SENSORS_INC
/CMD_SET_SENSORS_EXC
)decoder
: Optimization of execution time by disabling unused decoders (CMD_SET_SENSORS_CFG
)- No. of available sensors: Optimization of execution time by adjusting
max_sensors
(CMD_SET_SENSORS_CFG
)
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.
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.
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
as a set of byte values, which are used inBresserWeatherSensorLW/src/AppLayer.h
Line 71 in cb918c6
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.
Changing the configuration by setting bitmaps is not really comfortable. Therefore the Config Helper has been created.
In the Config Helper, you select the desired sensors/interfaces and the used channels/features and generate
- A bitmap to change the default payload configuration in the C++ source code
- A JSON string to configure the node via LoRaWAN downlink with the command CMD_SET_APP_PAYLOAD_CFG
- 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!
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.
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);
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);
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);
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);
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);
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);
https://matthias-bs.github.io/BresserWeatherSensorWL/index.html
Based on
- BresserWeatherSensorReceiver by Matthias Prinke
- RadioLib by Jan Gromeš
- Lora-Serialization by Joscha Feth
- ESP32Time by Felix Biego
- OneWireNg by Piotr Stolarz
- DallasTemperature / Arduino-Temperature-Control-Library by Miles Burton
- NimBLE-Arduino by h2zero
- Theengs Decoder by Theengs Project
- DistanceSensor_A02YYUW by Pablo Portela
- Preferences by Volodymyr Shymanskyy
This project is in no way affiliated with, authorized, maintained, sponsored or endorsed by Bresser GmbH or any of its affiliates or subsidiaries.