├── .gitignore ├── banner.jpg ├── LoRaWAN-Downlink ├── arduino_secrets.h └── LoRaWAN-Downlink.ino ├── LoRaWAN-Message ├── arduino_secrets.h └── LoRaWAN-Message.ino ├── ReadSensors-LoRaWAN ├── arduino_secrets.h └── ReadSensors-LoRaWAN.ino ├── LoRaWAN-Channel-Mask-Join ├── arduino_secrets.h └── LoRaWAN-Channel-Mask-Join.ino ├── ReadAnalogSensor-LoRaWAN ├── arduino_secrets.h └── ReadAnalogSensor-LoRaWAN.ino ├── ReadSensors-LoRaWAN-LowPower ├── arduino_secrets.h └── ReadSensors-LoRaWAN-LowPower.ino ├── payload-formatter-string.js ├── LoRa-Dev-EUI └── LoRa-Dev-EUI.ino ├── ENVBlinkDemo └── ENVBlinkDemo.ino ├── payload-formatter-int.js ├── payload-formatter-float.js ├── Solutions ├── payload-formatter-float-rounded.js ├── payload-formatter-float-warning.js ├── ReadSensors-Tendency │ ├── payload-formatter-tendency.js │ └── ReadSensors-Tendency.ino ├── payload-formatter-float-daylight.js └── ReadSensors-Temperature-Average │ └── ReadSensors-Temperature-Average.ino ├── ReadSensors └── ReadSensors.ino ├── LoRaWAN-Print-Channel-Mask └── LoRaWAN-Print-Channel-Mask.ino └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | -------------------------------------------------------------------------------- /banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebromero/arduino-lorawan-intro/HEAD/banner.jpg -------------------------------------------------------------------------------- /LoRaWAN-Downlink/arduino_secrets.h: -------------------------------------------------------------------------------- 1 | #define APP_EUI "0000000000000000" 2 | #define APP_KEY "00000000000000000000000000000000" 3 | -------------------------------------------------------------------------------- /LoRaWAN-Message/arduino_secrets.h: -------------------------------------------------------------------------------- 1 | #define APP_EUI "0000000000000000" 2 | #define APP_KEY "00000000000000000000000000000000" 3 | -------------------------------------------------------------------------------- /ReadSensors-LoRaWAN/arduino_secrets.h: -------------------------------------------------------------------------------- 1 | #define APP_EUI "0000000000000000" 2 | #define APP_KEY "00000000000000000000000000000000" 3 | -------------------------------------------------------------------------------- /LoRaWAN-Channel-Mask-Join/arduino_secrets.h: -------------------------------------------------------------------------------- 1 | #define APP_EUI "0000000000000000" 2 | #define APP_KEY "00000000000000000000000000000000" 3 | -------------------------------------------------------------------------------- /ReadAnalogSensor-LoRaWAN/arduino_secrets.h: -------------------------------------------------------------------------------- 1 | #define APP_EUI "0000000000000000" 2 | #define APP_KEY "00000000000000000000000000000000" 3 | -------------------------------------------------------------------------------- /ReadSensors-LoRaWAN-LowPower/arduino_secrets.h: -------------------------------------------------------------------------------- 1 | #define APP_EUI "0000000000000000" 2 | #define APP_KEY "00000000000000000000000000000000" 3 | -------------------------------------------------------------------------------- /payload-formatter-string.js: -------------------------------------------------------------------------------- 1 | function decodeUplink(input) { 2 | var result = ""; 3 | for (var i = 0; i < input.bytes.length; i++) { 4 | result += String.fromCharCode(parseInt(input.bytes[i])); 5 | } 6 | 7 | return { 8 | data: { 9 | bytes: input.bytes, 10 | message: result 11 | }, 12 | warnings: [], 13 | errors: [] 14 | }; 15 | } -------------------------------------------------------------------------------- /LoRa-Dev-EUI/LoRa-Dev-EUI.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | LoRaModem modem; 4 | 5 | void setup() { 6 | Serial.begin(115200); 7 | while (!Serial); 8 | 9 | // change this to your regional band (eg. US915, AS923, ...) 10 | if (!modem.begin(EU868)) { 11 | Serial.println("Failed to start module"); 12 | while (1) {} 13 | }; 14 | 15 | Serial.print("Your module version is: "); 16 | Serial.println(modem.version()); 17 | Serial.print("Your device EUI is: "); 18 | Serial.println(modem.deviceEUI()); 19 | } 20 | 21 | void loop() {} 22 | -------------------------------------------------------------------------------- /ENVBlinkDemo/ENVBlinkDemo.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void setup() { 4 | Serial.begin(9600); 5 | while (!Serial); 6 | 7 | if (!ENV.begin()) { 8 | Serial.println("Failed to initialize MKR ENV shield!"); 9 | while (1); 10 | } 11 | 12 | pinMode(LED_BUILTIN, OUTPUT); 13 | } 14 | 15 | void loop() { 16 | digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level) 17 | delay(200); // wait for a second 18 | digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW 19 | delay(200); // wait for a second 20 | } 21 | -------------------------------------------------------------------------------- /payload-formatter-int.js: -------------------------------------------------------------------------------- 1 | function decodeUplink(input) { 2 | var sensorData = []; 3 | var intSize = 4; // Size of an int in bytes 4 | var buffer = new ArrayBuffer(input.bytes.length); 5 | var dataView = new DataView(buffer); 6 | input.bytes.forEach(function (value, index) { 7 | dataView.setUint8(index, value); 8 | }); 9 | 10 | // Convert the bytes back into int values 11 | for (var i = 0; i < input.bytes.length; i += intSize) { 12 | var intValue = dataView.getInt32(i, true); 13 | sensorData.push(intValue); 14 | } 15 | 16 | var data = { 17 | bytes: input.bytes, 18 | sensorValue: sensorData[0], 19 | }; 20 | 21 | return { 22 | data: data, 23 | warnings: [], 24 | errors: [] 25 | }; 26 | } -------------------------------------------------------------------------------- /payload-formatter-float.js: -------------------------------------------------------------------------------- 1 | /* 2 | Sample data: 3 | Temperature = 27.03 °C 4 | Humidity = 56.49 % 5 | Pressure = 97.51 kPa 6 | Illuminance = 40.00 lx 7 | */ 8 | 9 | function decodeUplink(input) { 10 | var sensorData = []; 11 | var floatSize = 4; // Size of a float in bytes 12 | var buffer = new ArrayBuffer(input.bytes.length); 13 | var dataView = new DataView(buffer); 14 | input.bytes.forEach(function (value, index) { 15 | dataView.setUint8(index, value); 16 | }); 17 | 18 | // Convert the bytes back into float values 19 | for (var i = 0; i < input.bytes.length; i += floatSize) { 20 | var floatValue = dataView.getFloat32(i, true); 21 | sensorData.push(floatValue); 22 | } 23 | 24 | var data = { 25 | bytes: input.bytes, 26 | temperature: sensorData[0] + " °C", 27 | humidity: sensorData[1] + " %", 28 | pressure: sensorData[2] + " kPa", 29 | illuminance: sensorData[3] + " lx" 30 | }; 31 | 32 | return { 33 | data: data, 34 | warnings: [], 35 | errors: [] 36 | }; 37 | } -------------------------------------------------------------------------------- /Solutions/payload-formatter-float-rounded.js: -------------------------------------------------------------------------------- 1 | function decodeUplink(input) { 2 | var sensorData = []; 3 | var floatSize = 4; // Size of a float in bytes 4 | var buffer = new ArrayBuffer(input.bytes.length); 5 | var dataView = new DataView(buffer); 6 | input.bytes.forEach(function (value, index) { 7 | dataView.setUint8(index, value); 8 | }); 9 | 10 | // Convert the bytes back into float values 11 | for (var i = 0; i < input.bytes.length; i += floatSize) { 12 | var floatValue = dataView.getFloat32(i, true); 13 | 14 | // The following line rounds the value to two digits 15 | // by multiplying it by 100, rounding the integer and 16 | // then dividing it again by hundred. 17 | var roundedValue = Math.round(floatValue * 100) / 100; 18 | sensorData.push(roundedValue); 19 | } 20 | 21 | var data = { 22 | bytes: input.bytes, 23 | temperature: sensorData[0] + " °C", 24 | humidity: sensorData[1] + " %", 25 | pressure: sensorData[2] + " kPa", 26 | illuminance: illuminance + " lx" 27 | }; 28 | 29 | return { 30 | data: data, 31 | warnings: [], 32 | errors: [] 33 | }; 34 | } -------------------------------------------------------------------------------- /Solutions/payload-formatter-float-warning.js: -------------------------------------------------------------------------------- 1 | /* 2 | Sample data: 3 | Temperature = 27.03 °C 4 | Humidity = 56.49 % 5 | Pressure = 97.51 kPa 6 | Illuminance = 40.00 lx 7 | */ 8 | 9 | function decodeUplink(input) { 10 | var sensorData = []; 11 | var floatSize = 4; // Size of a float in bytes 12 | var buffer = new ArrayBuffer(input.bytes.length); 13 | var dataView = new DataView(buffer); 14 | input.bytes.forEach(function (value, index) { 15 | dataView.setUint8(index, value); 16 | }); 17 | 18 | // Convert the bytes back into float values 19 | for (var i = 0; i < input.bytes.length; i += floatSize) { 20 | var floatValue = dataView.getFloat32(i, true); 21 | var roundedValue = Math.round(floatValue * 100) / 100; 22 | sensorData.push(roundedValue); 23 | } 24 | 25 | var data = { 26 | bytes: input.bytes, 27 | temperature: sensorData[0] + " °C", 28 | humidity: sensorData[1] + " %", 29 | pressure: sensorData[2] + " kPa", 30 | illuminance: sensorData[3] + " lx" 31 | }; 32 | 33 | var warnings = []; 34 | if (sensorData.temperature < -5) { 35 | warnings.push("It's freezing cold 🥶"); 36 | } 37 | 38 | return { 39 | data: data, 40 | warnings: warnings, 41 | errors: [] 42 | }; 43 | } -------------------------------------------------------------------------------- /LoRaWAN-Channel-Mask-Join/LoRaWAN-Channel-Mask-Join.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "arduino_secrets.h" 3 | 4 | LoRaModem modem; 5 | 6 | // Enter your sensitive data in the secret tab or arduino_secrets.h 7 | String appEui = APP_EUI; 8 | String appKey = APP_KEY; 9 | 10 | void setup() { 11 | // Initialize serial port at 9600 bauds 12 | Serial.begin(9600); 13 | while (!Serial); 14 | 15 | // Initialize LoRa module with the US915-928 region parameters 16 | if (!modem.begin(US915)) { 17 | Serial.println("Failed to start module"); 18 | while (1) {} 19 | }; 20 | 21 | // Device module version and EUI 22 | delay(5000); 23 | Serial.print("Your module version is: "); 24 | Serial.println(modem.version()); 25 | Serial.print("Your device EUI is: "); 26 | Serial.println(modem.deviceEUI()); 27 | 28 | // Enable US915-928 channels 29 | // LoRaWAN® Regional Parameters and TTN specification: channels 8 to 15 plus 65 30 | modem.sendMask("ff000001f000ffff00020000"); 31 | Serial.println(modem.getChannelMask()); 32 | modem.setADR(true); 33 | join(); 34 | } 35 | 36 | void join() { 37 | // Try to connect 38 | int connected = modem.joinOTAA(appEui, appKey); 39 | if (!connected) { 40 | Serial.println("Something went wrong, retrying..."); 41 | join(); 42 | } 43 | } 44 | 45 | void loop(){} 46 | -------------------------------------------------------------------------------- /ReadSensors/ReadSensors.ino: -------------------------------------------------------------------------------- 1 | /* 2 | MKR ENV Shield - Read Sensors 3 | 4 | This example reads the sensors on-board the MKR ENV shield 5 | and prints them to the Serial Monitor once a second. 6 | 7 | The circuit: 8 | - Arduino MKR board 9 | - Arduino MKR ENV Shield attached 10 | 11 | This example code is in the public domain. 12 | */ 13 | 14 | #include 15 | 16 | void setup() { 17 | Serial.begin(9600); 18 | while (!Serial); 19 | 20 | if (!ENV.begin()) { 21 | Serial.println("Failed to initialize MKR ENV shield!"); 22 | while (1); 23 | } 24 | } 25 | 26 | void loop() { 27 | // read all the sensor values 28 | float temperature = ENV.readTemperature(); 29 | float humidity = ENV.readHumidity(); 30 | float pressure = ENV.readPressure(); 31 | float illuminance = ENV.readIlluminance(); 32 | 33 | // print each of the sensor values 34 | Serial.print("Temperature = "); 35 | Serial.print(temperature); 36 | Serial.println(" °C"); 37 | 38 | Serial.print("Humidity = "); 39 | Serial.print(humidity); 40 | Serial.println(" %"); 41 | 42 | Serial.print("Pressure = "); 43 | Serial.print(pressure); 44 | Serial.println(" kPa"); 45 | 46 | Serial.print("Illuminance = "); 47 | Serial.print(illuminance); 48 | Serial.println(" lx"); 49 | 50 | // print an empty line 51 | Serial.println(); 52 | 53 | // wait 1 second to print again 54 | delay(1000); 55 | } 56 | -------------------------------------------------------------------------------- /LoRaWAN-Message/LoRaWAN-Message.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "arduino_secrets.h" 3 | 4 | LoRaModem modem; 5 | 6 | void setup() { 7 | Serial.begin(115200); 8 | while (!Serial); 9 | 10 | // change this to your regional band (eg. US915, AS923, ...) 11 | if (!modem.begin(EU868)) { 12 | Serial.println("Failed to start module"); 13 | while (1) {} 14 | }; 15 | 16 | Serial.print("Your module version is: "); 17 | Serial.println(modem.version()); 18 | 19 | if (modem.version() != ARDUINO_FW_VERSION) { 20 | Serial.println("Please make sure that the latest modem firmware is installed."); 21 | Serial.println("To update the firmware upload the 'MKRWANFWUpdate_standalone.ino' sketch."); 22 | } 23 | 24 | Serial.print("Your device EUI is: "); 25 | Serial.println(modem.deviceEUI()); 26 | 27 | Serial.println("Connecting..."); 28 | int connected = modem.joinOTAA(APP_EUI, APP_KEY); 29 | 30 | if (!connected) { 31 | Serial.println("Something went wrong; are you indoor? Move near a window and retry"); 32 | while (1) {} 33 | } 34 | 35 | delay(5000); 36 | 37 | Serial.println("Sending message..."); 38 | modem.setPort(3); 39 | modem.beginPacket(); 40 | modem.print("HeLoRA world!"); 41 | 42 | int error = modem.endPacket(true); 43 | 44 | if (error > 0) { 45 | Serial.println("Message sent correctly!"); 46 | } else { 47 | Serial.println("Error sending message :("); 48 | } 49 | } 50 | 51 | void loop() { 52 | } 53 | -------------------------------------------------------------------------------- /Solutions/ReadSensors-Tendency/payload-formatter-tendency.js: -------------------------------------------------------------------------------- 1 | function decodeUplink(input) { 2 | var sensorData = []; 3 | var floatSize = 4; // Size of a float in bytes 4 | var buffer = new ArrayBuffer(input.bytes.length); 5 | var dataView = new DataView(buffer); 6 | input.bytes.forEach(function (value, index) { 7 | dataView.setUint8(index, value); 8 | }); 9 | 10 | // Convert the bytes back into float values 11 | for (var i = 0; i < input.bytes.length; i += floatSize) { 12 | var floatValue = dataView.getFloat32(i, true); 13 | var roundedValue = Math.round(floatValue * 100) / 100; 14 | sensorData.push(roundedValue); 15 | } 16 | 17 | var pressureTendencyNumeric = sensorData[4]; 18 | var pressureTendency = "undefined"; 19 | 20 | // NOTE: Unit is kPa, so 0.1 corresponds to 1 hPa 21 | if (pressureTendencyNumeric > 0) { 22 | pressureTendency = "rising"; 23 | } else if(pressureTendencyNumeric < 0){ 24 | pressureTendency = "falling"; 25 | } 26 | 27 | var data = { 28 | bytes: input.bytes, 29 | temperature: sensorData[0] + " °C", 30 | humidity: sensorData[1] + " %", 31 | pressure: sensorData[2] + " kPa", 32 | illuminance: sensorData[3] + " lx", 33 | pressureTendencyNumeric: pressureTendencyNumeric, 34 | pressureTendency: pressureTendency 35 | }; 36 | 37 | return { 38 | data: data, 39 | warnings: [], 40 | errors: [] 41 | }; 42 | } -------------------------------------------------------------------------------- /Solutions/payload-formatter-float-daylight.js: -------------------------------------------------------------------------------- 1 | /* 2 | Sample data: 3 | Temperature = 27.03 °C 4 | Humidity = 56.49 % 5 | Pressure = 97.51 kPa 6 | Illuminance = 40.00 lx 7 | */ 8 | 9 | function decodeUplink(input) { 10 | var sensorData = []; 11 | var floatSize = 4; // Size of a float in bytes 12 | var buffer = new ArrayBuffer(input.bytes.length); 13 | var dataView = new DataView(buffer); 14 | input.bytes.forEach(function (value, index) { 15 | dataView.setUint8(index, value); 16 | }); 17 | 18 | // Convert the bytes back into float values 19 | for (var i = 0; i < input.bytes.length; i += floatSize) { 20 | var floatValue = dataView.getFloat32(i, true); 21 | var roundedValue = Math.round(floatValue * 100) / 100; 22 | sensorData.push(roundedValue); 23 | } 24 | 25 | var daylight = "undefined"; 26 | var illuminance = sensorData[3]; 27 | 28 | if (illuminance > 9) { 29 | daylight = "day"; 30 | } else if (illuminance > 4) { 31 | daylight = "twilight"; 32 | } else { 33 | daylight = "night"; 34 | } 35 | 36 | var data = { 37 | bytes: input.bytes, 38 | temperature: sensorData[0] + " °C", 39 | humidity: sensorData[1] + " %", 40 | pressure: sensorData[2] + " kPa", 41 | illuminance: illuminance + " lx", 42 | daylight: daylight 43 | }; 44 | 45 | var warnings = []; 46 | if (sensorData.temperature < -5) { 47 | warnings.push("It's freezing cold 🥶"); 48 | } 49 | 50 | return { 51 | data: data, 52 | warnings: warnings, 53 | errors: [] 54 | }; 55 | } -------------------------------------------------------------------------------- /ReadAnalogSensor-LoRaWAN/ReadAnalogSensor-LoRaWAN.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "arduino_secrets.h" 3 | 4 | #define ANALOG_SENSOR_PIN A2 5 | 6 | LoRaModem modem; 7 | int analogSensorValue; 8 | 9 | void setup() { 10 | Serial.begin(9600); 11 | while (!Serial); 12 | pinMode(ANALOG_SENSOR_PIN, INPUT); // Configures the analog pin as an input 13 | 14 | // change this to your regional band (eg. US915, AS923, ...) 15 | if (!modem.begin(EU868)) { 16 | Serial.println("Failed to start module"); 17 | while (1) {} 18 | }; 19 | 20 | connectToLoRaWAN(); 21 | } 22 | 23 | void connectToLoRaWAN(){ 24 | Serial.println("Connecting..."); 25 | int connected = modem.joinOTAA(APP_EUI, APP_KEY); 26 | 27 | if (!connected) { 28 | Serial.println("Something went wrong; are you indoor? Move near a window and retry"); 29 | while (1) {} 30 | } 31 | 32 | delay(5000); 33 | } 34 | 35 | void sendSensorValue(){ 36 | Serial.println("Sending message..."); 37 | modem.setPort(3); 38 | modem.beginPacket(); 39 | // Sends 4 bytes of data. Use the integer payload decoder on TTS 40 | modem.write(analogSensorValue); 41 | 42 | int error = modem.endPacket(true); 43 | 44 | if (error > 0) { 45 | Serial.println("Message sent correctly!"); 46 | } else { 47 | Serial.println("Error sending message :("); 48 | } 49 | 50 | Serial.println(); 51 | } 52 | 53 | void loop() { 54 | analogSensorValue = analogRead(ANALOG_SENSOR_PIN); 55 | 56 | Serial.print("Sensor value = "); 57 | Serial.println(analogSensorValue); 58 | Serial.println(); // print an empty line 59 | 60 | sendSensorValue(); 61 | delay(120000); // wait 120 seconds to print again 62 | } 63 | -------------------------------------------------------------------------------- /LoRaWAN-Print-Channel-Mask/LoRaWAN-Print-Channel-Mask.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | LoRaModem modem; 4 | 5 | void setup() { 6 | int status; 7 | 8 | // Initialize serial port at 9600 bauds 9 | Serial.begin(9600); 10 | while (!Serial); 11 | 12 | loraSetup(); 13 | 14 | Serial.println("--------------------------------"); 15 | Serial.println("- MKR WAN 1310 Channel Masking -"); 16 | Serial.println("--------------------------------"); 17 | 18 | // Print default channels configuration 19 | Serial.print("- Default mask: "); 20 | Serial.println(modem.getChannelMask()); 21 | 22 | // Disable all channels 23 | Serial.println("- Disabling all channels..."); 24 | for (unsigned int i = 0; i < 72; i++) { 25 | modem.disableChannel(i); 26 | } 27 | 28 | // Print current channels configuration 29 | Serial.print("- Current mask: "); 30 | Serial.println(modem.getChannelMask()); 31 | 32 | // Enable AU915-928 channels 33 | // LoRaWAN® Regional Parameters and TTN specification: channels 8 to 15 plus 65 34 | Serial.println("- Enabling channels 8 to 15 plus 65..."); 35 | for (unsigned int i = 8; i <= 15; i++) { 36 | modem.enableChannel(i); 37 | } 38 | 39 | modem.enableChannel(65); 40 | 41 | // Print current channels configuration 42 | Serial.print("- Current mask: "); 43 | Serial.println(modem.getChannelMask()); 44 | } 45 | 46 | void loraSetup() { 47 | // Initialize LoRa module with the US915-928 region parameters 48 | if (!modem.begin(US915)) { 49 | Serial.println("- Failed to start LoRa module!"); 50 | while (1); 51 | }; 52 | 53 | delay(5000); 54 | 55 | // Set poll interval to 30 seconds 56 | modem.minPollInterval(30); 57 | } 58 | 59 | void loop() { 60 | } 61 | -------------------------------------------------------------------------------- /LoRaWAN-Downlink/LoRaWAN-Downlink.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "arduino_secrets.h" 4 | 5 | LoRaModem modem; 6 | 7 | void setup() { 8 | Serial.begin(115200); 9 | while (!Serial); 10 | 11 | ENV.begin(); 12 | pinMode(LED_BUILTIN, OUTPUT); 13 | 14 | // change this to your regional band (eg. US915, AS923, ...) 15 | if (!modem.begin(EU868)) { 16 | Serial.println("Failed to start module"); 17 | while (1) {} 18 | }; 19 | 20 | Serial.print("Your module version is: "); 21 | Serial.println(modem.version()); 22 | 23 | if (modem.version() != ARDUINO_FW_VERSION) { 24 | Serial.println("Please make sure that the latest modem firmware is installed."); 25 | Serial.println("To update the firmware upload the 'MKRWANFWUpdate_standalone.ino' sketch."); 26 | } 27 | 28 | Serial.print("Your device EUI is: "); 29 | Serial.println(modem.deviceEUI()); 30 | 31 | Serial.println("Connecting..."); 32 | int connected = modem.joinOTAA(APP_EUI, APP_KEY); 33 | 34 | if (!connected) { 35 | Serial.println("Something went wrong; are you indoor? Move near a window and retry"); 36 | while (1) {} 37 | } 38 | 39 | modem.minPollInterval(60); // Default is 300s 40 | Serial.println("Waiting for messages..."); 41 | } 42 | 43 | void loop() { 44 | delay(60 * 1000); // Wait 60 secs before polling again 45 | 46 | modem.poll(); 47 | 48 | // On The Things Stack the RX1 window is 5s which is the earliest moment 49 | // to receive any downlink data. We add 1500ms to allow for the transmission 50 | // of the data to complete. 51 | delay(5000 + 1500); 52 | 53 | if (!modem.available()) { 54 | Serial.println("No downlink message received at this time."); 55 | return; 56 | } 57 | 58 | char dataBuffer[64]; 59 | int i = 0; 60 | while (modem.available()) { 61 | dataBuffer[i++] = (char)modem.read(); 62 | } 63 | 64 | Serial.print("Received: "); 65 | for (unsigned int j = 0; j < i; j++) { 66 | Serial.print(dataBuffer[j] >> 4, HEX); 67 | Serial.print(dataBuffer[j] & 0xF, HEX); 68 | Serial.print(" "); 69 | } 70 | Serial.println(); 71 | 72 | if(dataBuffer[0] == 1){ 73 | digitalWrite(LED_BUILTIN, HIGH); 74 | } else if(dataBuffer[0] == 0) { 75 | digitalWrite(LED_BUILTIN, LOW); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /Solutions/ReadSensors-Temperature-Average/ReadSensors-Temperature-Average.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define TEMPERATURE_READINGS_COUNT 70 // The amount of samples to take: 10 times a day for 7 days 4 | #define TEMPERATURE_READ_INTERVAL 24 * 7 * 60 * 60 * 1000 / TEMPERATURE_READINGS_COUNT // How often to check in 3 hours 5 | 6 | unsigned long lastSensorCheck = 0; // To keep track when was the last time to check 7 | float temperatureReadings[TEMPERATURE_READINGS_COUNT] = {}; 8 | int temperatureReadingIndex = -1; 9 | 10 | template float getAverage(float (&readings)[N]){ 11 | float sum = 0; 12 | 13 | int i; 14 | for (i = 0; i < N; ++i) { 15 | auto currentValue = readings[i]; 16 | if(isnan(currentValue)) break; 17 | sum += currentValue; 18 | } 19 | if(i == 0) return NAN; 20 | return sum / static_cast(i); 21 | } 22 | 23 | void setup() { 24 | Serial.begin(115200); 25 | while (!Serial); 26 | 27 | // Pre-populate the array with NAN values so that we can 28 | // determine which are invalid values when reading the buffer. 29 | auto amountOfReadings = sizeof(temperatureReadings) / sizeof(float); 30 | for (size_t i = 0; i < amountOfReadings; i++){ 31 | temperatureReadings[i] = NAN; 32 | } 33 | 34 | if (!ENV.begin()) { 35 | Serial.println("Failed to initialize MKR ENV shield!"); 36 | while (1); 37 | } 38 | } 39 | 40 | void maybeUpdateTemperature(float sensorValue){ 41 | // Determine if it's time to update the sensor reading 42 | if(millis() - lastSensorCheck < TEMPERATURE_READ_INTERVAL) return; 43 | 44 | temperatureReadingIndex = (temperatureReadingIndex + 1) % TEMPERATURE_READINGS_COUNT; 45 | temperatureReadings[temperatureReadingIndex] = sensorValue; 46 | Serial.print("Updating sensor value at index: "); 47 | Serial.println(temperatureReadingIndex); 48 | lastSensorCheck = millis(); 49 | } 50 | 51 | void loop() { 52 | float temperature = ENV.readTemperature(); 53 | maybeUpdateTemperature(temperature); 54 | float weeklyAverage = getAverage(temperatureReadings); 55 | 56 | // print each of the sensor values 57 | Serial.print("Temperature = "); 58 | Serial.print(temperature); 59 | Serial.println(" °C"); 60 | 61 | Serial.print("Weekly average = "); 62 | Serial.print(weeklyAverage); 63 | Serial.println(" °C"); 64 | 65 | // print an empty line 66 | Serial.println(); 67 | 68 | // TODO: Send values to LoRaWAN network 69 | 70 | // wait 120 seconds to print again 71 | delay(120000); 72 | } 73 | -------------------------------------------------------------------------------- /ReadSensors-LoRaWAN/ReadSensors-LoRaWAN.ino: -------------------------------------------------------------------------------- 1 | /* 2 | MKR ENV Shield - Read Sensors 3 | 4 | This example reads the sensors on-board the MKR ENV shield 5 | and prints them to the Serial Monitor once a second. 6 | 7 | The circuit: 8 | - Arduino MKR board 9 | - Arduino MKR ENV Shield attached 10 | 11 | This example code is in the public domain. 12 | */ 13 | 14 | #include 15 | #include 16 | #include "arduino_secrets.h" 17 | 18 | LoRaModem modem; 19 | 20 | float temperature; 21 | float humidity; 22 | float pressure; 23 | float illuminance; 24 | 25 | void setup() { 26 | Serial.begin(9600); 27 | while (!Serial); 28 | 29 | if (!ENV.begin()) { 30 | Serial.println("Failed to initialize MKR ENV shield!"); 31 | while (1); 32 | } 33 | 34 | // change this to your regional band (eg. US915, AS923, ...) 35 | if (!modem.begin(EU868)) { 36 | Serial.println("Failed to start module"); 37 | while (1) {} 38 | }; 39 | 40 | connectToLoRaWAN(); 41 | } 42 | 43 | void connectToLoRaWAN(){ 44 | Serial.println("Connecting..."); 45 | int connected = modem.joinOTAA(APP_EUI, APP_KEY); 46 | 47 | if (!connected) { 48 | Serial.println("Something went wrong; are you indoor? Move near a window and retry"); 49 | while (1) {} 50 | } 51 | 52 | delay(5000); 53 | } 54 | 55 | void sendSensorValues(){ 56 | Serial.println("Sending message..."); 57 | modem.setPort(3); 58 | modem.beginPacket(); 59 | modem.write(temperature); 60 | modem.write(humidity); 61 | modem.write(pressure); 62 | modem.write(illuminance); 63 | 64 | int error = modem.endPacket(true); 65 | 66 | if (error > 0) { 67 | Serial.println("Message sent correctly!"); 68 | } else { 69 | Serial.println("Error sending message :("); 70 | } 71 | 72 | Serial.println(); 73 | } 74 | 75 | void loop() { 76 | // read all the sensor values 77 | temperature = ENV.readTemperature(); 78 | humidity = ENV.readHumidity(); 79 | pressure = ENV.readPressure(); 80 | illuminance = ENV.readIlluminance(); 81 | 82 | // print each of the sensor values 83 | Serial.print("Temperature = "); 84 | Serial.print(temperature); 85 | Serial.println(" °C"); 86 | 87 | Serial.print("Humidity = "); 88 | Serial.print(humidity); 89 | Serial.println(" %"); 90 | 91 | Serial.print("Pressure = "); 92 | Serial.print(pressure); 93 | Serial.println(" kPa"); 94 | 95 | Serial.print("Illuminance = "); 96 | Serial.print(illuminance); 97 | Serial.println(" lx"); 98 | 99 | // print an empty line 100 | Serial.println(); 101 | 102 | sendSensorValues(); 103 | 104 | // wait 120 seconds to print again 105 | delay(120000); 106 | } 107 | -------------------------------------------------------------------------------- /ReadSensors-LoRaWAN-LowPower/ReadSensors-LoRaWAN-LowPower.ino: -------------------------------------------------------------------------------- 1 | /* 2 | MKR ENV Shield - Read Sensors 3 | 4 | This example reads the sensors on-board the MKR ENV shield 5 | and prints them to the Serial Monitor once a second. 6 | 7 | The circuit: 8 | - Arduino MKR board 9 | - Arduino MKR ENV Shield attached 10 | 11 | This example code is in the public domain. 12 | */ 13 | 14 | #include 15 | #include 16 | #include 17 | #include "arduino_secrets.h" 18 | 19 | LoRaModem modem; 20 | 21 | float temperature; 22 | float humidity; 23 | float pressure; 24 | float illuminance; 25 | 26 | void setup() { 27 | Serial.begin(9600); 28 | while (!Serial); 29 | 30 | if (!ENV.begin()) { 31 | Serial.println("Failed to initialize MKR ENV shield!"); 32 | while (1); 33 | } 34 | 35 | // change this to your regional band (eg. US915, AS923, ...) 36 | if (!modem.begin(EU868)) { 37 | Serial.println("Failed to start module"); 38 | while (1) {} 39 | }; 40 | 41 | connectToLoRaWAN(); 42 | } 43 | 44 | void connectToLoRaWAN(){ 45 | Serial.println("Connecting..."); 46 | int connected = modem.joinOTAA(APP_EUI, APP_KEY); 47 | 48 | if (!connected) { 49 | Serial.println("Something went wrong; are you indoor? Move near a window and retry"); 50 | while (1) {} 51 | } 52 | 53 | delay(5000); 54 | } 55 | 56 | void sendSensorValues(){ 57 | Serial.println("Sending message..."); 58 | modem.setPort(3); 59 | modem.beginPacket(); 60 | modem.write(temperature); 61 | modem.write(humidity); 62 | modem.write(pressure); 63 | modem.write(illuminance); 64 | 65 | int error = modem.endPacket(true); 66 | 67 | if (error > 0) { 68 | Serial.println("Message sent correctly!"); 69 | } else { 70 | Serial.println("Error sending message :("); 71 | } 72 | 73 | Serial.println(); 74 | } 75 | 76 | void loop() { 77 | // read all the sensor values 78 | temperature = ENV.readTemperature(); 79 | humidity = ENV.readHumidity(); 80 | pressure = ENV.readPressure(); 81 | illuminance = ENV.readIlluminance(); 82 | 83 | // print each of the sensor values 84 | Serial.print("Temperature = "); 85 | Serial.print(temperature); 86 | Serial.println(" °C"); 87 | 88 | Serial.print("Humidity = "); 89 | Serial.print(humidity); 90 | Serial.println(" %"); 91 | 92 | Serial.print("Pressure = "); 93 | Serial.print(pressure); 94 | Serial.println(" kPa"); 95 | 96 | Serial.print("Illuminance = "); 97 | Serial.print(illuminance); 98 | Serial.println(" lx"); 99 | 100 | // print an empty line 101 | Serial.println(); 102 | 103 | sendSensorValues(); 104 | 105 | // wait 120 seconds to print again 106 | //LowPower.idle(120000); 107 | LowPower.sleep(120000); 108 | //LowPower.deepSleep(120000); 109 | //LowPower.deepSleep(); // Wake up externally 110 | 111 | //SEE: https://www.arduino.cc/en/Reference/ArduinoLowPower 112 | } 113 | -------------------------------------------------------------------------------- /Solutions/ReadSensors-Tendency/ReadSensors-Tendency.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define PRESSURE_READINGS_COUNT 20 // The amount of samples to take 4 | #define PRESSURE_READ_INTERVAL 3 * 60 * 60 * 1000 / PRESSURE_READINGS_COUNT // How often to check in 3 hours 5 | 6 | unsigned long lastSensorCheck = 0; // To keep track when was the last time to check 7 | float pressureReadings[PRESSURE_READINGS_COUNT] = {}; 8 | int pressureReadingIndex = -1; 9 | 10 | float temperature; 11 | float humidity; 12 | float pressure; 13 | float illuminance; 14 | float pressureTendency; 15 | 16 | /** 17 | * Simply calculates the moving difference of the numbers in the array 18 | * and returns a positive or negative number indicating the trend. 19 | * It reads the ring buffer in two steps. First the values up to the current 20 | * index, then the values from the previous fill. 21 | * */ 22 | template 23 | float calculatePressureTrend(float (&readings)[N], int currentIndex){ 24 | float trend = 0; 25 | auto arraySize = N; 26 | 27 | // Loop over the newest values 28 | for(int i = 0; i < currentIndex; ++i){ 29 | auto currentValue = pressureReadings[i]; 30 | auto nextValue = pressureReadings[i+1]; 31 | // Skip if no readings are available 32 | if(isnan(currentValue) || isnan(nextValue)) break; 33 | auto difference = nextValue - currentValue; 34 | trend += difference; 35 | } 36 | 37 | // Loop over the previous values 38 | for (int i = currentIndex + 1; i < arraySize - 1; ++i) { 39 | auto currentValue = pressureReadings[i]; 40 | auto nextValue = pressureReadings[i+1]; 41 | // Skip if no readings are available 42 | if(isnan(currentValue) || isnan(nextValue)) break; 43 | auto difference = nextValue - currentValue; 44 | trend += difference; 45 | } 46 | 47 | return trend; 48 | } 49 | 50 | void setup() { 51 | Serial.begin(9600); 52 | while (!Serial); 53 | 54 | // Pre-populate the array with NAN values so that we can 55 | // determine which are invalid values when reading the buffer. 56 | auto amountOfReadings = sizeof(pressureReadings) / sizeof(float); 57 | for (size_t i = 0; i < amountOfReadings; i++){ 58 | pressureReadings[i] = NAN; 59 | } 60 | 61 | if (!ENV.begin()) { 62 | Serial.println("Failed to initialize MKR ENV shield!"); 63 | while (1); 64 | } 65 | } 66 | 67 | void maybeUpdatePressureTendency(float sensorValue){ 68 | // Determine if it's time to update the sensor reading 69 | if(millis() - lastSensorCheck < PRESSURE_READ_INTERVAL) return; 70 | 71 | pressureReadingIndex = (pressureReadingIndex + 1) % PRESSURE_READINGS_COUNT; 72 | pressureReadings[pressureReadingIndex] = sensorValue; 73 | Serial.print("Updating sensor value at index: "); 74 | Serial.println(pressureReadingIndex); 75 | lastSensorCheck = millis(); 76 | } 77 | 78 | void loop() { 79 | // read all the sensor values 80 | temperature = ENV.readTemperature(); 81 | humidity = ENV.readHumidity(); 82 | pressure = ENV.readPressure(); 83 | illuminance = ENV.readIlluminance(); 84 | maybeUpdatePressureTendency(pressure); 85 | pressureTendency = calculatePressureTrend(pressureReadings, pressureReadingIndex); 86 | 87 | // print each of the sensor values 88 | Serial.print("Temperature = "); 89 | Serial.print(temperature); 90 | Serial.println(" °C"); 91 | 92 | Serial.print("Humidity = "); 93 | Serial.print(humidity); 94 | Serial.println(" %"); 95 | 96 | Serial.print("Pressure = "); 97 | Serial.print(pressure); 98 | Serial.println(" kPa"); 99 | 100 | Serial.print("Illuminance = "); 101 | Serial.print(illuminance); 102 | Serial.println(" lx"); 103 | 104 | Serial.print("Pressure Tendency = "); 105 | Serial.print(pressureTendency); 106 | Serial.println(" kPa"); 107 | 108 | // print an empty line 109 | Serial.println(); 110 | 111 | // TODO: Send values to LoRaWAN network 112 | /* Keep the following order of variables when constructing the packet: 113 | 1. temperature 114 | 2. humidity 115 | 3. pressure 116 | 4. illuminance 117 | 5. pressureTendency 118 | */ 119 | 120 | /* 121 | If the air pressure drops by more than 1 to 2 hPA in one hour, 122 | then storms and violent winds are to be expected. 123 | This sketch could be easily adapted to also issue storm warnings. 124 | */ 125 | 126 | // wait 120 seconds to send again 127 | delay(120000); 128 | } 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](banner.jpg) 2 | 3 | # Arduino LoRaWAN Introduction 4 | This repository contains the example code for The Things Summer Academy workshop about LoRaWAN devices. 5 | 6 | # Provided Code 7 | The following list explains for each sketch / code what it's intended for. 8 | 9 | - **[ENVBlinkDemo](./ENVBlinkDemo)**: 10 | A blink Arduino sketch that works also when the MKR ENV shield is connected to the MKR WAN 1300 board. The regular blink sketch from the IDE doesn't work in this scenario because some additional configuration needs to be done. 11 | 12 | - **[ReadSensors](./ReadSensors)**: 13 | An Arduino sketch that shows how to read from the MKR ENV shield sensors. 14 | See: https://www.arduino.cc/en/Reference/ArduinoMKRENV 15 | 16 | - **[LoRa-Dev-EUI](./LoRa-Dev-EUI)**: 17 | An Arduino sketch that helps you to find out the device EUI which you will need to register your device with The Things Stack. 18 | 19 | - **[LoRaWAN-Print-Channel-Mask](./LoRaWAN-Print-Channel-Mask)**: 20 | An Arduino sketch that helps you to find out the channel mask for your desired channels to be enabled. This mask can then be used for joining a LoRaWAN network. 21 | 22 | - **[LoRaWAN-Channel-Mask-Join](./LoRaWAN-Channel-Mask-Join)**: 23 | An example Arduino sketch that shows you how to use channel masking to join The Things Stack. This may be required depending on where you're based. (E.g. US or AU). 24 | More info can be found here: 25 | - https://docs.arduino.cc/tutorials/mkr-wan-1310/lorawan-regional-parameters 26 | - https://www.thethingsnetwork.org/docs/lorawan/frequency-plans/ 27 | - https://www.thethingsnetwork.org/docs/lorawan/regional-parameters/ 28 | 29 | - **[LoRaWAN-Message](./LoRaWAN-Message)**: 30 | An Arduino sketch that shows how to send a simple string message to a LoRaWAN backend (e.g. The Things Stack). 31 | See: https://www.arduino.cc/en/Reference/MKRWAN 32 | 33 | - **[payload-formatter-string.js](./payload-formatter-string.js)**: 34 | A javascript snippet that shows an implementation of a Payload Decoder which converts a bytes stream back into a human readable ASCII string. 35 | See: https://www.thethingsindustries.com/docs/integrations/payload-formatters/javascript/ 36 | 37 | - **[ReadSensors-LoRaWAN](./ReadSensors-LoRaWAN)**: 38 | An Arduino sketch that shows how to read from the MKR ENV shield sensors and send those values as floats to a LoRaWAN backend. 39 | 40 | - **[ReadAnalogSensor-LoRaWAN](./ReadAnalogSensor-LoRaWAN)**: 41 | An Arduino sketch that shows how to read an analog sensors and send the value as an int to a LoRaWAN backend. 42 | 43 | - **[payload-formatter-float.js](./payload-formatter-float.js)**: 44 | A javascript snippet that shows an implementation of a Payload Decoder that unpacks float values from a byte stream and converts them into a javascript object. 45 | See: https://www.thethingsnetwork.org/docs/devices/bytes/ 46 | 47 | - **[LoRaWAN-Downlink](./LoRaWAN-Downlink)**: 48 | An Arduino sketch that shows you how to use downlink messages to control your Arduino board. In this sketch, a message with the data `01` turns the on-board LED on, a message with `00` turns it off. Keep in mind that downlink messages won't be received instantly. You may have to wait a few minutes until they are delivered to your device. 49 | 50 | - **[ReadSensors-LoRaWAN-LowPower](./ReadSensors-LoRaWAN-LowPower)**: 51 | An Arduino sketch that shows how to read from the MKR ENV shield sensors and send those values as floats to a LoRaWAN backend while using sleep modes to ensure maximum battery life. 52 | See: https://www.arduino.cc/en/Reference/ArduinoLowPower 53 | 54 | # Assignment 55 | 56 | Create your own LoRa powered environment sensor station! 57 | 58 | ## Warm up tasks 59 | 60 | 1. Create a TTS app and register your device 61 | 2. Create your own sketch that reads from the ENV shield sensors 62 | 3. Send the sensor values to TTS every 20 minutes. Put the board to sleep in the mean time. 63 | 4. Add a payload decoder to decode the sensor values 64 | 5. Round the float values to two digits in the payload decoder 65 | 66 | ## Tasks 67 | 6. Add a warning message in the payload decoder if the temperature is too high / too cold (e.g. -5 / above 34 celsius) 68 | 7. Define approximate illuminance thresholds for: day, twilight, night and add a “daylight” property with a value according to those thresholds (changes over the day) 69 | 8. Add a “pressureTendency” property to see if atmospheric pressure is rising / falling. Do so by calculating the tendency over the last 3 hours. (Hint: millis()) 70 | 9. Add the weekly average temperature as a “averageTemperature” property 71 | 72 | ## Bonus 73 | 1. Use a TTS downlink to maintain and synchronise a clock on the Arduino (manually). 74 | 2. Now that you know the time, replace the twilight value of the “daylight” property with either dusk or dawn depending on the time of the day. 75 | 3. Record the approximate sunrise / sunset times based on the daylight property and add them as “sunrise” / “sunset” properties (don’t change over the day) 76 | 4. Think of a way of enabling/disabling the 4 sensors by sending a downlink messages with just 1 byte. (Hint: Use bit-wise operators). If a sensor is disabled, the sensor value shall not be read/sent via LoRaWAN to save energy and bandwidth. Send the same byte in your uplink message to let the payload decoder know how to decode the message. e.g. if The pressure sensor is disabled, only 3 * 4 = 12 bytes are sent for the sensor values and the bytes of the 3rd sensor in the byte stream belong now to the illuminance sensor rather than the pressure sensor. 77 | 78 | ## Optional 79 | If you have a micro SD card, save the sensor data to the SD card to prevent data loss when power goes out. Upon boot, check for existing sensor data and send it to TTS. SEE: https://www.arduino.cc/en/reference/SD 80 | 81 | 82 | # Troubleshooting 83 | If you encounter any errors in the Arduino IDE, please refer to the Arduino help center: https://support.arduino.cc/hc/en-us 84 | 85 | # Relevant Links 86 | 87 | - https://docs.arduino.cc/foundations/ 88 | - https://www.arduino.cc/reference/en/ 89 | - https://docs.arduino.cc/hardware/mkr-env-shield 90 | - https://docs.arduino.cc/hardware/mkr-wan-1300 --------------------------------------------------------------------------------