diff --git a/.gitignore b/.gitignore index 6f31401f7..fa4197745 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build/ .vscode/ +examples/lora/sensor-lorawan/radioConfig.h diff --git a/examples/lora/sensor-lorawan/CayenneLPP.cc b/examples/lora/sensor-lorawan/CayenneLPP.cc new file mode 100644 index 000000000..50e764ec8 --- /dev/null +++ b/examples/lora/sensor-lorawan/CayenneLPP.cc @@ -0,0 +1,219 @@ +/* + * The Cayenne Low Power Payload (LPP) provides a convenient and + * easy way to send data over LPWAN networks such as LoRaWAN. The Cayenne LPP + * is compliant with the payload size restriction, which can be lowered down to + * 11 bytes, and allows the device to send multiple sensor data at one time. + * + * CayenneLPP is also support by TTN: + * https://www.thethingsindustries.com/docs/integrations/payload-formatters/cayenne/ + * + * This code is direct from: https://github.com/myDevicesIoT/CayenneLPP + * + * GPL-3.0 license + * See https://github.com/myDevicesIoT/CayenneLPP for license details + */ + +#include "CayenneLPP.h" + +CayenneLPP::CayenneLPP(uint8_t size) : maxsize(size) { + buffer = (uint8_t*) malloc(size); + cursor = 0; +} + +CayenneLPP::~CayenneLPP(void) { + free(buffer); +} + +void CayenneLPP::reset(void) { + cursor = 0; +} + +uint8_t CayenneLPP::getSize(void) { + return cursor; +} + +uint8_t* CayenneLPP::getBuffer(void) { +// uint8_t[cursor] result; +// memcpy(result, buffer, cursor); +// return result; + return buffer; +} + +uint8_t CayenneLPP::copy(uint8_t* dst) { + memcpy(dst, buffer, cursor); + return cursor; +} + +uint8_t CayenneLPP::addDigitalInput(uint8_t channel, uint8_t value) { + if ((cursor + LPP_DIGITAL_INPUT_SIZE) > maxsize) { + return 0; + } + buffer[cursor++] = channel; + buffer[cursor++] = LPP_DIGITAL_INPUT; + buffer[cursor++] = value; + + return cursor; +} + +uint8_t CayenneLPP::addDigitalOutput(uint8_t channel, uint8_t value) { + if ((cursor + LPP_DIGITAL_OUTPUT_SIZE) > maxsize) { + return 0; + } + buffer[cursor++] = channel; + buffer[cursor++] = LPP_DIGITAL_OUTPUT; + buffer[cursor++] = value; + + return cursor; +} + +uint8_t CayenneLPP::addAnalogInput(uint8_t channel, float value) { + if ((cursor + LPP_ANALOG_INPUT_SIZE) > maxsize) { + return 0; + } + + int16_t val = value * 100; + buffer[cursor++] = channel; + buffer[cursor++] = LPP_ANALOG_INPUT; + buffer[cursor++] = val >> 8; + buffer[cursor++] = val; + + return cursor; +} + +uint8_t CayenneLPP::addAnalogOutput(uint8_t channel, float value) { + if ((cursor + LPP_ANALOG_OUTPUT_SIZE) > maxsize) { + return 0; + } + int16_t val = value * 100; + buffer[cursor++] = channel; + buffer[cursor++] = LPP_ANALOG_OUTPUT; + buffer[cursor++] = val >> 8; + buffer[cursor++] = val; + + return cursor; +} + +uint8_t CayenneLPP::addLuminosity(uint8_t channel, uint16_t lux) { + if ((cursor + LPP_LUMINOSITY_SIZE) > maxsize) { + return 0; + } + buffer[cursor++] = channel; + buffer[cursor++] = LPP_LUMINOSITY; + buffer[cursor++] = lux >> 8; + buffer[cursor++] = lux; + + return cursor; +} + +uint8_t CayenneLPP::addPresence(uint8_t channel, uint8_t value) { + if ((cursor + LPP_PRESENCE_SIZE) > maxsize) { + return 0; + } + buffer[cursor++] = channel; + buffer[cursor++] = LPP_PRESENCE; + buffer[cursor++] = value; + + return cursor; +} + +uint8_t CayenneLPP::addTemperature(uint8_t channel, float celsius) { + if ((cursor + LPP_TEMPERATURE_SIZE) > maxsize) { + return 0; + } + int16_t val = celsius * 10; + buffer[cursor++] = channel; + buffer[cursor++] = LPP_TEMPERATURE; + buffer[cursor++] = val >> 8; + buffer[cursor++] = val; + + return cursor; +} + +uint8_t CayenneLPP::addRelativeHumidity(uint8_t channel, float rh) { + if ((cursor + LPP_RELATIVE_HUMIDITY_SIZE) > maxsize) { + return 0; + } + buffer[cursor++] = channel; + buffer[cursor++] = LPP_RELATIVE_HUMIDITY; + buffer[cursor++] = rh * 2; + + return cursor; +} + +uint8_t CayenneLPP::addAccelerometer(uint8_t channel, float x, float y, float z) { + if ((cursor + LPP_ACCELEROMETER_SIZE) > maxsize) { + return 0; + } + int16_t vx = x * 1000; + int16_t vy = y * 1000; + int16_t vz = z * 1000; + + buffer[cursor++] = channel; + buffer[cursor++] = LPP_ACCELEROMETER; + buffer[cursor++] = vx >> 8; + buffer[cursor++] = vx; + buffer[cursor++] = vy >> 8; + buffer[cursor++] = vy; + buffer[cursor++] = vz >> 8; + buffer[cursor++] = vz; + + return cursor; +} + +uint8_t CayenneLPP::addBarometricPressure(uint8_t channel, float hpa) { + if ((cursor + LPP_BAROMETRIC_PRESSURE_SIZE) > maxsize) { + return 0; + } + int16_t val = hpa * 10; + + buffer[cursor++] = channel; + buffer[cursor++] = LPP_BAROMETRIC_PRESSURE; + buffer[cursor++] = val >> 8; + buffer[cursor++] = val; + + return cursor; +} + +uint8_t CayenneLPP::addGyrometer(uint8_t channel, float x, float y, float z) { + if ((cursor + LPP_GYROMETER_SIZE) > maxsize) { + return 0; + } + int16_t vx = x * 100; + int16_t vy = y * 100; + int16_t vz = z * 100; + + buffer[cursor++] = channel; + buffer[cursor++] = LPP_GYROMETER; + buffer[cursor++] = vx >> 8; + buffer[cursor++] = vx; + buffer[cursor++] = vy >> 8; + buffer[cursor++] = vy; + buffer[cursor++] = vz >> 8; + buffer[cursor++] = vz; + + return cursor; +} + +uint8_t CayenneLPP::addGPS(uint8_t channel, float latitude, float longitude, float meters) { + if ((cursor + LPP_GPS_SIZE) > maxsize) { + return 0; + } + int32_t lat = latitude * 10000; + int32_t lon = longitude * 10000; + int32_t alt = meters * 100; + + buffer[cursor++] = channel; + buffer[cursor++] = LPP_GPS; + + buffer[cursor++] = lat >> 16; + buffer[cursor++] = lat >> 8; + buffer[cursor++] = lat; + buffer[cursor++] = lon >> 16; + buffer[cursor++] = lon >> 8; + buffer[cursor++] = lon; + buffer[cursor++] = alt >> 16; + buffer[cursor++] = alt >> 8; + buffer[cursor++] = alt; + + return cursor; +} diff --git a/examples/lora/sensor-lorawan/CayenneLPP.h b/examples/lora/sensor-lorawan/CayenneLPP.h new file mode 100644 index 000000000..e7a48718c --- /dev/null +++ b/examples/lora/sensor-lorawan/CayenneLPP.h @@ -0,0 +1,86 @@ +/* + * The Cayenne Low Power Payload (LPP) provides a convenient and + * easy way to send data over LPWAN networks such as LoRaWAN. The Cayenne LPP + * is compliant with the payload size restriction, which can be lowered down to + * 11 bytes, and allows the device to send multiple sensor data at one time. + * + * CayenneLPP is also support by TTN: + * https://www.thethingsindustries.com/docs/integrations/payload-formatters/cayenne/ + * + * This code is direct from: https://github.com/myDevicesIoT/CayenneLPP + * + * GPL-3.0 license + * See https://github.com/myDevicesIoT/CayenneLPP for license details + */ + +#ifndef _CAYENNE_LPP_H_ +#define _CAYENNE_LPP_H_ + +#include +#include +#include + +#define LPP_DIGITAL_INPUT 0 // 1 byte +#define LPP_DIGITAL_OUTPUT 1 // 1 byte +#define LPP_ANALOG_INPUT 2 // 2 bytes, 0.01 signed +#define LPP_ANALOG_OUTPUT 3 // 2 bytes, 0.01 signed +#define LPP_LUMINOSITY 101 // 2 bytes, 1 lux unsigned +#define LPP_PRESENCE 102 // 1 byte, 1 +#define LPP_TEMPERATURE 103 // 2 bytes, 0.1°C signed +#define LPP_RELATIVE_HUMIDITY 104 // 1 byte, 0.5% unsigned +#define LPP_ACCELEROMETER 113 // 2 bytes per axis, 0.001G +#define LPP_BAROMETRIC_PRESSURE 115 // 2 bytes 0.1 hPa Unsigned +#define LPP_GYROMETER 134 // 2 bytes per axis, 0.01 °/s +#define LPP_GPS 136 // 3 byte lon/lat 0.0001 °, 3 bytes alt 0.01 meter + + +// Data ID + Data Type + Data Size +#define LPP_DIGITAL_INPUT_SIZE 3 // 1 byte +#define LPP_DIGITAL_OUTPUT_SIZE 3 // 1 byte +#define LPP_ANALOG_INPUT_SIZE 4 // 2 bytes, 0.01 signed +#define LPP_ANALOG_OUTPUT_SIZE 4 // 2 bytes, 0.01 signed +#define LPP_LUMINOSITY_SIZE 4 // 2 bytes, 1 lux unsigned +#define LPP_PRESENCE_SIZE 3 // 1 byte, 1 +#define LPP_TEMPERATURE_SIZE 4 // 2 bytes, 0.1°C signed +#define LPP_RELATIVE_HUMIDITY_SIZE 3 // 1 byte, 0.5% unsigned +#define LPP_ACCELEROMETER_SIZE 8 // 2 bytes per axis, 0.001G +#define LPP_BAROMETRIC_PRESSURE_SIZE 4 // 2 bytes 0.1 hPa Unsigned +#define LPP_GYROMETER_SIZE 8 // 2 bytes per axis, 0.01 °/s +#define LPP_GPS_SIZE 11 // 3 byte lon/lat 0.0001 °, 3 bytes alt 0.01 meter + + +class CayenneLPP { + public: + CayenneLPP(uint8_t size); + ~CayenneLPP(); + + void reset(void); + uint8_t getSize(void); + uint8_t* getBuffer(void); + uint8_t copy(uint8_t* buffer); + + uint8_t addDigitalInput(uint8_t channel, uint8_t value); + uint8_t addDigitalOutput(uint8_t channel, uint8_t value); + + uint8_t addAnalogInput(uint8_t channel, float value); + uint8_t addAnalogOutput(uint8_t channel, float value); + + uint8_t addLuminosity(uint8_t channel, uint16_t lux); + uint8_t addPresence(uint8_t channel, uint8_t value); + uint8_t addTemperature(uint8_t channel, float celsius); + uint8_t addRelativeHumidity(uint8_t channel, float rh); + uint8_t addAccelerometer(uint8_t channel, float x, float y, float z); + uint8_t addBarometricPressure(uint8_t channel, float hpa); + uint8_t addGyrometer(uint8_t channel, float x, float y, float z); + uint8_t addGPS(uint8_t channel, float latitude, float longitude, float meters); + + private: + uint8_t *buffer; + uint8_t maxsize; + uint8_t cursor; + + +}; + + +#endif diff --git a/examples/lora/sensor-lorawan/Makefile b/examples/lora/sensor-lorawan/Makefile new file mode 100644 index 000000000..54e3d90e2 --- /dev/null +++ b/examples/lora/sensor-lorawan/Makefile @@ -0,0 +1,36 @@ +# Makefile for user application + +# Specify this directory relative to the current application. +TOCK_USERLAND_BASE_DIR = ../../../ + +STACK_SIZE = 4096 + +# Which files to compile. +CXX_SRCS := $(wildcard *.cc) + +# Include the core RadioLib headers +override CPPFLAGS += -isystem $(TOCK_USERLAND_BASE_DIR)/libradio/RadioLib/src + +# Include the Tock specific headers +override CPPFLAGS += -isystem $(TOCK_USERLAND_BASE_DIR)/libradio/RadioLib/examples/NonArduino/Tock + +# Include the base of libtock-c to fix the libtock/ includes from RadioLib +override CPPFLAGS += -I$(TOCK_USERLAND_BASE_DIR)/ -DRADIOLIB_CLOCK_DRIFT_MS=9 + +ifneq ($(CI),) +override CPPFLAGS += "-DRADIO_CONFIG_CI=radioConfig_example.h" +endif + +# Use the libtock-c Make system +LIBS_cortex-m0 += $(TOCK_USERLAND_BASE_DIR)/libradio/RadioLib/build/cortex-m0/RadioLib.a +LIBS_cortex-m3 += $(TOCK_USERLAND_BASE_DIR)/libradio/RadioLib/build/cortex-m3/RadioLib.a +LIBS_cortex-m4 += $(TOCK_USERLAND_BASE_DIR)/libradio/RadioLib/build/cortex-m4/RadioLib.a +LIBS_cortex-m7 += $(TOCK_USERLAND_BASE_DIR)/libradio/RadioLib/build/cortex-m7/RadioLib.a + +LIBS_rv32imc += $(TOCK_USERLAND_BASE_DIR)/libradio/RadioLib/build/rv32imc/RadioLib.a +LIBS_rv32imac += $(TOCK_USERLAND_BASE_DIR)/libradio/RadioLib/build/rv32imac/RadioLib.a + +EXTERN_LIBS := $(TOCK_USERLAND_BASE_DIR)/libradio/RadioLib +include $(TOCK_USERLAND_BASE_DIR)/libradio/Makefile + +include $(TOCK_USERLAND_BASE_DIR)/AppMakefile.mk diff --git a/examples/lora/sensor-lorawan/README.md b/examples/lora/sensor-lorawan/README.md new file mode 100644 index 000000000..26e557652 --- /dev/null +++ b/examples/lora/sensor-lorawan/README.md @@ -0,0 +1,16 @@ +Sensor LoRaWAN Transmitter +========================== + +This example builds an application to transmit sensor data via LoRaWAN. + +See https://github.com/jgromes/RadioLib/blob/master/examples/LoRaWAN/LoRaWAN_Starter/notes.md +for notes on setting up the LoRaWAN device. + +The most important part is creating a radioConfig.h file with the secrets +from your LoRaWAN server and any country specific settings. There is an +existing radioConfig_example.h which can be used as a useful starting point. + +This has been tested against The Things Network. Before changing settings +make sure you consider regulatory duty cycles and TTN's Fair Usage Policy, +for example don't drop the delay in the loop, otherwise you will break the +TTN Fair Usage Policy. diff --git a/examples/lora/sensor-lorawan/main.cc b/examples/lora/sensor-lorawan/main.cc new file mode 100644 index 000000000..a74bfb18c --- /dev/null +++ b/examples/lora/sensor-lorawan/main.cc @@ -0,0 +1,110 @@ +/* + RadioLib Non-Arduino Tock Library LoRaWAN test application + + Licensed under the MIT or Apache License + + Copyright (c) 2023 Alistair Francis + */ + +// include the library +#include + +// include the hardware abstraction layer +#include "libtockHal.h" + +// Include some libtock-c helpers +#include +#include + +#include "CayenneLPP.h" + +// To get this working copy radioConfig_example.h to radioConfig.h +// and then modify it to match the LoRaWAN gateway settings. +#ifdef RADIO_CONFIG_CI +#include "radioConfig_example.h" +#else +#include "radioConfig.h" +#endif + +#define MAX_SIZE 10 + +// the entry point for the program +int main(void) { + CayenneLPP Payload(MAX_SIZE); + + printf("[SX1261] Initialising Radio ... \n"); + + // create a new instance of the HAL class + TockHal* hal = new TockHal(); + int state; + + // now we can create the radio module + // pinout corresponds to the SparkFun LoRa Thing Plus - expLoRaBLE + // NSS pin: 0 + // DIO1 pin: 2 + // NRST pin: 4 + // BUSY pin: 1 + SX1262 tock_module = new Module(hal, RADIO_NSS, RADIO_DIO_1, RADIO_RESET, RADIO_BUSY); + LoRaWANNode node(&tock_module, Region, subBand); + + // Setup the radio + // The settings here work for the SparkFun LoRa Thing Plus - expLoRaBLE + node.scanGuard = 30; + tock_module.XTAL = true; + state = tock_module.begin(915.0); + + if (state != RADIOLIB_ERR_NONE) { + printf("begin failed, code %d\r\n", state); + return 1; + } + + node.beginOTAA(joinEUI, devEUI, nwkKey, appKey); + + state = node.activateOTAA(); + + if (state != RADIOLIB_LORAWAN_NEW_SESSION) { + printf("activateOTAA failed, code %d\r\n", state); + return 1; + } + + printf("success!\r\n"); + + hal->detachInterrupt(RADIO_DIO_1); + hal->pinMode(RADIO_DIO_1, PIN_INPUT); + + int temp = 0; + int humi = 0; + + // loop forever + for ( ;;) { + Payload.reset(); + + // Read some sensor data from the board + libtocksync_temperature_read(&temp); + libtocksync_humidity_read(&humi); + + temp = 24; + humi = 60; + + Payload.addTemperature(0, temp); + Payload.addRelativeHumidity(0, humi); + + printf("[SX1261] Transmitting\r\n"); + + state = node.sendReceive(Payload.getBuffer(), Payload.getSize()); + + if (state == RADIOLIB_ERR_NONE) { + // the packet was successfully transmitted + printf("success!\r\n"); + } else { + printf("failed, code %d\r\n", state); + } + + printf("Waiting 60 minutes before transmitting again\r\n"); + for (int i = 0; i < 60; i++) { + hal->delay(60 * 1000); + } + } + + return 0; +} diff --git a/examples/lora/sensor-lorawan/radioConfig_example.h b/examples/lora/sensor-lorawan/radioConfig_example.h new file mode 100644 index 000000000..333137370 --- /dev/null +++ b/examples/lora/sensor-lorawan/radioConfig_example.h @@ -0,0 +1,17 @@ +/* + RadioLib Non-Arduino Tock Library LoRaWAN connection details + + Licensed under the MIT or Apache License + + Copyright (c) 2024 Alistair Francis + */ + +/* These need to be updated to use values from your LoRaWAN server */ +uint64_t joinEUI = 0x0000000000000000; +uint64_t devEUI = 0x0000000000000000; +uint8_t nwkKey[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +uint8_t appKey[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +// regional choices: EU868, US915, AU915, AS923, IN865, KR920, CN780, CN500 +const LoRaWANBand_t* Region = &AU915; +const uint8_t subBand = 2; diff --git a/libradio/RadioLib b/libradio/RadioLib index 954477b0c..fffb1fae9 160000 --- a/libradio/RadioLib +++ b/libradio/RadioLib @@ -1 +1 @@ -Subproject commit 954477b0c0589e6373692ce569ca117191667c30 +Subproject commit fffb1fae9f6c43997c7ee0993e68ec7cd80ebb17