Browse Source

Merge branch 'master' of git.starbeamrainbowlabs.com:sbrl/Msc-Summer-Project

master
Starbeamrainbowlabs 3 years ago
parent
commit
1fbd864af8
  1. 3
      .gitignore
  2. 15
      .gitmodules
  3. 7
      README.md
  4. 50
      build
  5. 195
      iot/TTNTest/TTNTest.ino
  6. 15
      iot/TTNTest/config.custom.h.example
  7. 256
      iot/TTNTestABPNew/TTNTestABPNew.ino
  8. 15
      iot/TTNTestABPNew/config.custom.h.example
  9. 159
      iot/TTNTestOTAA/TTNTestOTAA.ino
  10. 26
      iot/TTNTestOTAA/config.custom.h.example
  11. 1
      iot/libraries/Arduino-MemoryFree
  12. 1
      iot/libraries/SdFat
  13. 1
      iot/libraries/TinyGPS
  14. 2
      iot/libraries/arduino-lmic
  15. 87
      iot/main/gps.cpp
  16. 27
      iot/main/gps.h
  17. 85
      iot/main/main.ino
  18. 20
      iot/main/peripheral.cpp
  19. 24
      iot/main/peripheral.h
  20. 15
      iot/main/power.cpp
  21. 9
      iot/main/power.h
  22. 26
      iot/main/settings.custom.cpp.example
  23. 14
      iot/main/settings.custom.h
  24. 51
      iot/main/settings.h
  25. 57
      iot/main/storage.cpp
  26. 8
      iot/main/storage.h
  27. 150
      iot/main/transmission.cpp
  28. 13
      iot/main/transmission.h

3
.gitignore

@ -1,3 +1,6 @@
config.custom.h
settings.custom.cpp
Reports/*.pdf
# Created by https://www.gitignore.io/api/libreoffice

15
.gitmodules

@ -4,9 +4,18 @@
[submodule "iot/libraries/Entropy"]
path = iot/libraries/Entropy
url = https://github.com/taoyuan/Entropy.git
[submodule "iot/libraries/arduino-lmic"]
path = iot/libraries/arduino-lmic
url = https://github.com/mcci-catena/arduino-lmic.git
[submodule "iot/libraries/TinyGPSPlus"]
path = iot/libraries/TinyGPSPlus
url = https://github.com/mikalhart/TinyGPSPlus.git
[submodule "iot/libraries/arduino-lmic"]
path = iot/libraries/arduino-lmic
url = https://github.com/matthijskooijman/arduino-lmic.git
[submodule "iot/libraries/Arduino-MemoryFree"]
path = iot/libraries/Arduino-MemoryFree
url = https://github.com/mpflaga/Arduino-MemoryFree.git
[submodule "iot/libraries/SdFat"]
path = iot/libraries/SdFat
url = https://github.com/greiman/SdFat.git
[submodule "iot/libraries/TinyGPS"]
path = iot/libraries/TinyGPS
url = https://github.com/mikalhart/TinyGPS.git

7
README.md

@ -15,6 +15,13 @@
- [Project box](https://smile.amazon.co.uk/waterproof-plastic-Enclosure-Power-junction-Grey/dp/B01JHBHNMA/) - if we can't 3D print one
- [TPL-5111](https://www.adafruit.com/product/3573) - also available from Pimoroni
## Getting Started
Run `bash ./build setup` from the command line at the root of this repository.
### IoT Device
- Copy `settings.custom.cpp.example` to `settings.custom.cpp` and fill in the fields there
- Review `settings.h` to make sure it matches your setup
- Copy the folders in `iot/libraries` to your Arduino IDE libraries folder
## Useful Links
- Entropy extraction from the watchdog timer vs the internal clock: https://github.com/taoyuan/Entropy

50
build

@ -43,18 +43,60 @@ fi
###############################################################################
# Toggles commenting and uncommenting lines in a file that contain a specific
# substring. Checks for word boundaries either side of the substring.
# From https://stackoverflow.com/a/24901636/1460422
# $1 - Filename
# $2 - Search string
comment_toggle() {
filename="${1}";
search_string="${2}";
awk -v commentId='//' -v word="${search_string}" '
$0 ~ "(^|[[:punct:][:space:]])" word "($|[[:punct:][:space:]])" {
if (match($0, "^[[:space:]]*" commentId))
$0 = substr($0, RSTART + RLENGTH)
else
$0 = commentId $0
}
{ print }' "${filename}" > tmpfile.$$ && mv tmpfile.$$ "${filename}"
}
task_setup() {
task_begin "Setting up";
stage_begin "Setting up";
task_begin "Checking Environment";
check_command git true;
check_command awk true;
check_command pdflatex true;
check_command bibtex true;
task_end $?;
subtask_begin "Initialising submodules";
task_begin "Initialising submodules";
git submodule update --init;
subtask_end $?;
task_end $?;
task_begin "Preconfiguring libraries";
config_file_directory="./iot/libraries/arduino-lmic/src/lmic/";
config_file_name="config.h";
cd "${config_file_directory}";
git reset --hard;
# Disable OTAA
comment_toggle "${config_file_name}" "#define DISABLE_JOIN";
# Disable class b stuff
comment_toggle "${config_file_name}" "#define DISABLE_PING";
comment_toggle "${config_file_name}" "#define DISABLE_BEACONS";
# Disable other misc. stuff we're not likely to use
comment_toggle "${config_file_name}" "#define DISABLE_MCMD_DCAP_REQ"; # Duty cycle cap - won't work anyway 'cause we're shutting down in between
comment_toggle "${config_file_name}" "#define DISABLE_MCMD_DN2P_SET"; # Receiving stuff
# echo "#define DISABLE_JOIN" >>"${config_file_name}";
# echo "#define DISABLE_PING" >>"${config_file_name}";
# echo "#define DISABLE_BEACONS" >>"${config_file_name}";
cd -;
task_end $?;
task_end 0;
stage_end 0;
}
task_render() {

195
iot/TTNTest/TTNTest.ino

@ -0,0 +1,195 @@
/*******************************************************************************
* Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
*
* Permission is hereby granted, free of charge, to anyone
* obtaining a copy of this document and accompanying files,
* to do whatever they want with them without any restriction,
* including, but not limited to, copying, modification and redistribution.
* NO WARRANTY OF ANY KIND IS PROVIDED.
*
* This example sends a valid LoRaWAN packet with payload "Hello,
* world!", using frequency and encryption settings matching those of
* the (early prototype version of) The Things Network.
*
* Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in g1,
* 0.1% in g2).
*
* Change DEVADDR to a unique address!
* See http://thethingsnetwork.org/wiki/AddressSpace
*
* Do not forget to define the radio type correctly in config.h.
*
*******************************************************************************/
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include "config.custom.h"
// These callbacks are only used in over-the-air activation, so they are
// left empty here (we cannot leave them out completely unless
// DISABLE_JOIN is set in config.h, otherwise the linker will complain).
void os_getArtEui (u1_t* buf) { }
void os_getDevEui (u1_t* buf) { }
void os_getDevKey (u1_t* buf) { }
static uint8_t mydata[] = "Hello, world!";
static osjob_t sendjob;
// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 60;
// Pin mapping
const lmic_pinmap lmic_pins = {
.nss = 10,
.rxtx = LMIC_UNUSED_PIN,
.rst = 9,
.dio = {2, 6, 7},
};
void onEvent (ev_t ev) {
Serial.print(os_getTime());
Serial.print(": ");
switch(ev) {
case EV_SCAN_TIMEOUT:
Serial.println(F("EV_SCAN_TIMEOUT"));
break;
case EV_BEACON_FOUND:
Serial.println(F("EV_BEACON_FOUND"));
break;
case EV_BEACON_MISSED:
Serial.println(F("EV_BEACON_MISSED"));
break;
case EV_BEACON_TRACKED:
Serial.println(F("EV_BEACON_TRACKED"));
break;
case EV_JOINING:
Serial.println(F("EV_JOINING"));
break;
case EV_JOINED:
Serial.println(F("EV_JOINED"));
break;
case EV_RFU1:
Serial.println(F("EV_RFU1"));
break;
case EV_JOIN_FAILED:
Serial.println(F("EV_JOIN_FAILED"));
break;
case EV_REJOIN_FAILED:
Serial.println(F("EV_REJOIN_FAILED"));
break;
break;
case EV_TXCOMPLETE:
Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
if(LMIC.dataLen) {
// data received in rx slot after tx
Serial.print(F("Data Received: "));
Serial.write(LMIC.frame+LMIC.dataBeg, LMIC.dataLen);
Serial.println();
}
// Schedule next transmission
os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
break;
case EV_LOST_TSYNC:
Serial.println(F("EV_LOST_TSYNC"));
break;
case EV_RESET:
Serial.println(F("EV_RESET"));
break;
case EV_RXCOMPLETE:
// data received in ping slot
Serial.println(F("EV_RXCOMPLETE"));
break;
case EV_LINK_DEAD:
Serial.println(F("EV_LINK_DEAD"));
break;
case EV_LINK_ALIVE:
Serial.println(F("EV_LINK_ALIVE"));
break;
default:
Serial.println(F("Unknown event"));
break;
}
}
void do_send(osjob_t* j){
// Check if there is not a current TX/RX job running
if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {
// Prepare upstream data transmission at the next possible time.
LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0);
Serial.println(F("Packet queued"));
}
// Next TX is scheduled after TX_COMPLETE event.
}
void setup() {
Serial.begin(115200);
Serial.println(F("Starting"));
#ifdef VCC_ENABLE
// For Pinoccio Scout boards
pinMode(VCC_ENABLE, OUTPUT);
digitalWrite(VCC_ENABLE, HIGH);
delay(1000);
#endif
// LMIC init
os_init();
// Reset the MAC state. Session and pending data transfers will be discarded.
LMIC_reset();
// Set static session parameters. Instead of dynamically establishing a session
// by joining the network, precomputed session parameters are be provided.
#ifdef PROGMEM
// On AVR, these values are stored in flash and only copied to RAM
// once. Copy them to a temporary buffer here, LMIC_setSession will
// copy them into a buffer of its own again.
uint8_t appskey[sizeof(APPSKEY)];
uint8_t nwkskey[sizeof(NWKSKEY)];
memcpy_P(appskey, APPSKEY, sizeof(APPSKEY));
memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY));
LMIC_setSession (0x1, DEVADDR, nwkskey, appskey);
#else
// If not running an AVR with PROGMEM, just use the arrays directly
LMIC_setSession (0x1, DEVADDR, NWKSKEY, APPSKEY);
#endif
// Set up the channels used by the Things Network, which corresponds
// to the defaults of most gateways. Without this, only three base
// channels from the LoRaWAN specification are used, which certainly
// works, so it is good for debugging, but can overload those
// frequencies, so be sure to configure the full frequency range of
// your network here (unless your network autoconfigures them).
// Setting up channels should happen after LMIC_setSession, as that
// configures the minimal channel set.
LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI); // g-band
LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI); // g2-band
// TTN defines an additional channel at 869.525Mhz using SF9 for class B
// devices' ping slots. LMIC does not have an easy way to define set this
// frequency and support for class B is spotty and untested, so this
// frequency is not configured here.
// Disable link check validation
LMIC_setLinkCheckMode(0);
// Set data rate and transmit power (note: txpow seems to be ignored by the library)
LMIC_setDrTxpow(DR_SF7,14);
// Start job
do_send(&sendjob);
}
void loop() {
os_runloop_once();
}

15
iot/TTNTest/config.custom.h.example

@ -0,0 +1,15 @@
// Copy this file to have the name "config.custom.h", and fill in the 3 sections below
// LoRaWAN NwkSKey, network session key
// Big endian (i.e. msb)
static const PROGMEM u1_t NWKSKEY[16] = { .... };
// LoRaWAN AppSKey, application session key
// Big endian (i.e. msb)
static const u1_t PROGMEM APPSKEY[16] = { .... };
// LoRaWAN end-device address (DevAddr)
// See http://thethingsnetwork.org/wiki/AddressSpace
// The library converts the address to network byte order as needed.
// Big endian (i.e. msb)
static const u4_t DEVADDR = 0x....; // <-- Change this address for every node!

256
iot/TTNTestABPNew/TTNTestABPNew.ino

@ -0,0 +1,256 @@
/*******************************************************************************
* Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
* Copyright (c) 2018 Terry Moore, MCCI
*
* Permission is hereby granted, free of charge, to anyone
* obtaining a copy of this document and accompanying files,
* to do whatever they want with them without any restriction,
* including, but not limited to, copying, modification and redistribution.
* NO WARRANTY OF ANY KIND IS PROVIDED.
*
* This example sends a valid LoRaWAN packet with payload "Hello,
* world!", using frequency and encryption settings matching those of
* the The Things Network.
*
* This uses ABP (Activation-by-personalisation), where a DevAddr and
* Session keys are preconfigured (unlike OTAA, where a DevEUI and
* application key is configured, while the DevAddr and session keys are
* assigned/generated in the over-the-air-activation procedure).
*
* Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in
* g1, 0.1% in g2), but not the TTN fair usage policy (which is probably
* violated by this sketch when left running for longer)!
*
* To use this sketch, first register your application and device with
* the things network, to set or generate a DevAddr, NwkSKey and
* AppSKey. Each device should have their own unique values for these
* fields.
*
* Do not forget to define the radio type correctly in
* arduino-lmic/project_config/lmic_project_config.h or from your BOARDS.txt.
*
*******************************************************************************/
// References:
// [feather] adafruit-feather-m0-radio-with-lora-module.pdf
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include "config.custom.h"
// These callbacks are only used in over-the-air activation, so they are
// left empty here (we cannot leave them out completely unless
// DISABLE_JOIN is set in arduino-lmic/project_config/lmic_project_config.h,
// otherwise the linker will complain).
void os_getArtEui (u1_t* buf) { }
void os_getDevEui (u1_t* buf) { }
void os_getDevKey (u1_t* buf) { }
static uint8_t mydata[] = "Hello, world!";
static osjob_t sendjob;
// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 60;
// Pin mapping
const lmic_pinmap lmic_pins = {
.nss = 10,
.rxtx = LMIC_UNUSED_PIN,
.rst = 9,
.dio = {2, 6, 7},
};
void onEvent (ev_t ev) {
Serial.print(os_getTime());
Serial.print(": ");
switch(ev) {
case EV_SCAN_TIMEOUT:
Serial.println(F("EV_SCAN_TIMEOUT"));
break;
case EV_BEACON_FOUND:
Serial.println(F("EV_BEACON_FOUND"));
break;
case EV_BEACON_MISSED:
Serial.println(F("EV_BEACON_MISSED"));
break;
case EV_BEACON_TRACKED:
Serial.println(F("EV_BEACON_TRACKED"));
break;
case EV_JOINING:
Serial.println(F("EV_JOINING"));
break;
case EV_JOINED:
Serial.println(F("EV_JOINED"));
break;
/*
|| This event is defined but not used in the code. No
|| point in wasting codespace on it.
||
|| case EV_RFU1:
|| Serial.println(F("EV_RFU1"));
|| break;
*/
case EV_JOIN_FAILED:
Serial.println(F("EV_JOIN_FAILED"));
break;
case EV_REJOIN_FAILED:
Serial.println(F("EV_REJOIN_FAILED"));
break;
case EV_TXCOMPLETE:
Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
if (LMIC.txrxFlags & TXRX_ACK)
Serial.println(F("Received ack"));
if (LMIC.dataLen) {
Serial.println(F("Received "));
Serial.println(LMIC.dataLen);
Serial.println(F(" bytes of payload"));
}
// Schedule next transmission
os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
break;
case EV_LOST_TSYNC:
Serial.println(F("EV_LOST_TSYNC"));
break;
case EV_RESET:
Serial.println(F("EV_RESET"));
break;
case EV_RXCOMPLETE:
// data received in ping slot
Serial.println(F("EV_RXCOMPLETE"));
break;
case EV_LINK_DEAD:
Serial.println(F("EV_LINK_DEAD"));
break;
case EV_LINK_ALIVE:
Serial.println(F("EV_LINK_ALIVE"));
break;
/*
|| This event is defined but not used in the code. No
|| point in wasting codespace on it.
||
|| case EV_SCAN_FOUND:
|| Serial.println(F("EV_SCAN_FOUND"));
|| break;
*/
case EV_TXSTART:
Serial.println(F("EV_TXSTART"));
break;
default:
Serial.print(F("Unknown event: "));
Serial.println((unsigned) ev);
break;
}
}
void do_send(osjob_t* j){
// Check if there is not a current TX/RX job running
if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {
// Prepare upstream data transmission at the next possible time.
LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0);
Serial.println(F("Packet queued"));
}
// Next TX is scheduled after TX_COMPLETE event.
}
void setup() {
// pinMode(13, OUTPUT);
while (!Serial); // wait for Serial to be initialized
Serial.begin(115200);
delay(100); // per sample code on RF_95 test
Serial.println(F("Starting"));
// Activate the right SPI device
pinMode(10, OUTPUT);
pinMode(3, OUTPUT);
digitalWrite(10, LOW);
digitalWrite(3, HIGH);
#ifdef VCC_ENABLE
// For Pinoccio Scout boards
pinMode(VCC_ENABLE, OUTPUT);
digitalWrite(VCC_ENABLE, HIGH);
delay(1000);
#endif
// LMIC init
os_init();
// Reset the MAC state. Session and pending data transfers will be discarded.
LMIC_reset();
// Set static session parameters. Instead of dynamically establishing a session
// by joining the network, precomputed session parameters are be provided.
#ifdef PROGMEM
// On AVR, these values are stored in flash and only copied to RAM
// once. Copy them to a temporary buffer here, LMIC_setSession will
// copy them into a buffer of its own again.
uint8_t appskey[sizeof(APPSKEY)];
uint8_t nwkskey[sizeof(NWKSKEY)];
memcpy_P(appskey, APPSKEY, sizeof(APPSKEY));
memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY));
LMIC_setSession (0x13, DEVADDR, nwkskey, appskey);
#else
// If not running an AVR with PROGMEM, just use the arrays directly
LMIC_setSession (0x13, DEVADDR, NWKSKEY, APPSKEY);
#endif
#if defined(CFG_eu868)
// Set up the channels used by the Things Network, which corresponds
// to the defaults of most gateways. Without this, only three base
// channels from the LoRaWAN specification are used, which certainly
// works, so it is good for debugging, but can overload those
// frequencies, so be sure to configure the full frequency range of
// your network here (unless your network autoconfigures them).
// Setting up channels should happen after LMIC_setSession, as that
// configures the minimal channel set.
LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI); // g-band
LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI); // g2-band
// TTN defines an additional channel at 869.525Mhz using SF9 for class B
// devices' ping slots. LMIC does not have an easy way to define set this
// frequency and support for class B is spotty and untested, so this
// frequency is not configured here.
#elif defined(CFG_us915)
// NA-US channels 0-71 are configured automatically
// but only one group of 8 should (a subband) should be active
// TTN recommends the second sub band, 1 in a zero based count.
// https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json
LMIC_selectSubBand(1);
#endif
// Disable link check validation
LMIC_setLinkCheckMode(0);
// TTN uses SF9 for its RX2 window.
LMIC.dn2Dr = DR_SF9;
// Set data rate and transmit power for uplink
LMIC_setDrTxpow(DR_SF7,14);
// Start job
do_send(&sendjob);
}
void loop() {
unsigned long now;
now = millis();
if ((now & 512) != 0) {
digitalWrite(13, HIGH);
}
else {
digitalWrite(13, LOW);
}
os_runloop_once();
}

15
iot/TTNTestABPNew/config.custom.h.example

@ -0,0 +1,15 @@
// Copy this file to have the name "config.custom.h", and fill in the 3 sections below
// LoRaWAN NwkSKey, network session key
// Big endian (i.e. msb)
static const PROGMEM u1_t NWKSKEY[16] = { .... };
// LoRaWAN AppSKey, application session key
// Big endian (i.e. msb)
static const u1_t PROGMEM APPSKEY[16] = { .... };
// LoRaWAN end-device address (DevAddr)
// See http://thethingsnetwork.org/wiki/AddressSpace
// The library converts the address to network byte order as needed.
// Big endian (i.e. msb)
static const u4_t DEVADDR = 0x....; // <-- Change this address for every node!

159
iot/TTNTestOTAA/TTNTestOTAA.ino

@ -0,0 +1,159 @@
/*******************************************************************************
* Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
*
* Permission is hereby granted, free of charge, to anyone
* obtaining a copy of this document and accompanying files,
* to do whatever they want with them without any restriction,
* including, but not limited to, copying, modification and redistribution.
* NO WARRANTY OF ANY KIND IS PROVIDED.
*
* This example sends a valid LoRaWAN packet with payload "Hello,
* world!", using frequency and encryption settings matching those of
* the The Things Network.
*
* This uses OTAA (Over-the-air activation), where where a DevEUI and
* application key is configured, which are used in an over-the-air
* activation procedure where a DevAddr and session keys are
* assigned/generated for use with all further communication.
*
* Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in
* g1, 0.1% in g2), but not the TTN fair usage policy (which is probably
* violated by this sketch when left running for longer)!
* To use this sketch, first register your application and device with
* the things network, to set or generate an AppEUI, DevEUI and AppKey.
* Multiple devices can use the same AppEUI, but each device has its own
* DevEUI and AppKey.
*
* Do not forget to define the radio type correctly in config.h.
*
*******************************************************************************/
#define DISABLE_PING
#define DISABLE_BEACONS
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include "config.custom.h"
static uint8_t mydata[] = "Hello, world!";
static osjob_t sendjob;
// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 60;
void onEvent (ev_t ev) {
Serial.print(os_getTime());
Serial.print(": ");
switch(ev) {
case EV_SCAN_TIMEOUT:
Serial.println(F("EV_SCAN_TIMEOUT"));
break;
case EV_BEACON_FOUND:
Serial.println(F("EV_BEACON_FOUND"));
break;
case EV_BEACON_MISSED:
Serial.println(F("EV_BEACON_MISSED"));
break;
case EV_BEACON_TRACKED:
Serial.println(F("EV_BEACON_TRACKED"));
break;
case EV_JOINING:
Serial.println(F("EV_JOINING"));
break;
case EV_JOINED:
Serial.println(F("EV_JOINED"));
// Disable link check validation (automatically enabled
// during join, but not supported by TTN at this time).
LMIC_setLinkCheckMode(0);
break;
case EV_RFU1:
Serial.println(F("EV_RFU1"));
break;
case EV_JOIN_FAILED:
Serial.println(F("EV_JOIN_FAILED"));
break;
case EV_REJOIN_FAILED:
Serial.println(F("EV_REJOIN_FAILED"));
break;
break;
case EV_TXCOMPLETE:
Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
if (LMIC.txrxFlags & TXRX_ACK)
Serial.println(F("Received ack"));
if (LMIC.dataLen) {
Serial.println(F("Received "));
Serial.println(LMIC.dataLen);
Serial.println(F(" bytes of payload"));
}
// Schedule next transmission
os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
break;
case EV_LOST_TSYNC:
Serial.println(F("EV_LOST_TSYNC"));
break;
case EV_RESET:
Serial.println(F("EV_RESET"));
break;
case EV_RXCOMPLETE:
// data received in ping slot
Serial.println(F("EV_RXCOMPLETE"));
break;
case EV_LINK_DEAD:
Serial.println(F("EV_LINK_DEAD"));
break;
case EV_LINK_ALIVE:
Serial.println(F("EV_LINK_ALIVE"));
break;
default:
Serial.println(F("Unknown event"));
break;
}
}
void do_send(osjob_t* j){
// Check if there is not a current TX/RX job running
if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {
// Prepare upstream data transmission at the next possible time.
LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0);
Serial.println(F("Packet queued"));
}
// Next TX is scheduled after TX_COMPLETE event.
}
void setup() {
Serial.begin(115200);
Serial.println(F("Starting"));
// Activate the right SPI device
pinMode(10, OUTPUT);
pinMode(3, OUTPUT);
digitalWrite(10, LOW);
digitalWrite(3, HIGH);
#ifdef VCC_ENABLE
// For Pinoccio Scout boards
pinMode(VCC_ENABLE, OUTPUT);
digitalWrite(VCC_ENABLE, HIGH);
delay(1000);
#endif
// LMIC init
os_init();
// Reset the MAC state. Session and pending data transfers will be discarded.
LMIC_reset();
LMIC_setClockError(5 * MAX_CLOCK_ERROR / 100);
// Start job (sending automatically starts OTAA too)
do_send(&sendjob);
}
void loop() {
os_runloop_once();
}

26
iot/TTNTestOTAA/config.custom.h.example

@ -0,0 +1,26 @@
// This EUI must be in little-endian format, so least-significant-byte
// first. When copying an EUI from ttnctl output, this means to reverse
// the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3,
// 0x70.
static const u1_t PROGMEM APPEUI[8]={ ..... };
void os_getArtEui (u1_t* buf) { memcpy_P(buf, APPEUI, 8);}
// This should also be in little endian format, see above.
static const u1_t PROGMEM DEVEUI[8]={ ...... };
void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8);}
// This key should be in big endian format (or, since it is not really a
// number but a block of memory, endianness does not really apply). In
// practice, a key taken from ttnctl can be copied as-is.
// The key shown here is the semtech default key.
static const u1_t PROGMEM APPKEY[16] = { ...... };
void os_getDevKey (u1_t* buf) { memcpy_P(buf, APPKEY, 16);}
const lmic_pinmap lmic_pins = {
.nss = 10,
.rxtx = LMIC_UNUSED_PIN,
.rst = 9,
.dio = {2, 6, 7},
};

1
iot/libraries/Arduino-MemoryFree

@ -0,0 +1 @@
Subproject commit aed5cc5e28f8f01afc41a625151c184a8e94e32e

1
iot/libraries/SdFat

@ -0,0 +1 @@
Subproject commit 3b79f38cb554809a45a0ccd6d9d6752b6ad948c2

1
iot/libraries/TinyGPS

@ -0,0 +1 @@
Subproject commit db4ef9c97af767e7345f5ccb277ac3bd1a2eb81f

2
iot/libraries/arduino-lmic

@ -1 +1 @@
Subproject commit 9f016150100afcde4d3def8abae234c6efc46e0c
Subproject commit ba1265d5d2f775177cdc7c82186724e4f0bdc3a8

87
iot/main/gps.cpp

@ -1,54 +1,93 @@
#include "gps.h"
#include "settings.h"
// The GPS message decoder
TinyGPSPlus gps;
// The serial connection to the GPS device
SoftwareSerial serial_gps(PIN_GPS_RX, PIN_GPS_TX);
#include <TinyGPS.h>
void gps_begin() {
SoftwareSerial gps_begin() {
// FUTURE: If this doesn't work as expected, we may want to find a more reliable SoftwareSerial library
SoftwareSerial serial_gps(PIN_GPS_RX, PIN_GPS_TX);
// Initialise the software-based serial connection to the GPS device
serial_gps.begin(BAUD_GPS);
return serial_gps;
}
void gps_fetch() {
Serial.print("[gps] Getting location: ");
uint32_t ms_last_update = millis();
GPSLocation gps_fetch() {
// The serial connection to the GPS module
SoftwareSerial serial_gps = gps_begin();
// The GPS message decoder
TinyGPS gps;
// The struct that will hold the result
GPSLocation result;
Serial.print(F("[gps] Working: "));
uint32_t start = millis(), ms_last_update = millis();
uint8_t ticks = 0;
unsigned long fix_age = TinyGPS::GPS_INVALID_AGE;
unsigned long chars;
unsigned short sentences, failed_checksum;
// We WILL discover our location - if it's the last thing we do!
while(true) {
if(millis() > 5000 && gps.charsProcessed() < 10) {
Serial.println(F("\n[error] Failed to initialise GPS device."));
gps.stats(&chars, &sentences, &failed_checksum);
if(millis() - start > 5000 && chars < 10) {
Serial.println(F("\n[error] GPS device init failed"));
while(true) { delay(1); }
}
// Make sure there's something to read
if(serial_gps.available() <= 0) {
if(serial_gps.available() <= 0)
continue;
}
// If it failed, go around again
if(!gps.encode(serial_gps.read()))
continue;
gps.f_get_position(&(result.lat), &(result.lng), &fix_age);
// If we haven't acquired a lock yet, go around again
// Also go around again if the location is the same hasn't yet been updated (GPS devices are strange)
if(!gps.location.isValid()) {
// NOTE: It can really take a rather long time to get a lock. Just wait patiently :-)
if(millis() - ms_last_update > 500) Serial.print(".");
if(fix_age == TinyGPS::GPS_INVALID_AGE) {
// NOTE: It can take a rather long time to get a lock. Just wait patiently :-)
if(millis() - ms_last_update > 500) {
ticks++;
Serial.print('.');
if(ticks > 80) {
Serial.println();
ticks = 0;
}
// Serial.println(gps.location.lat());
// Serial.println(gps.location.lng());
// Serial.println(gps.date.year());
// Serial.println(gps.time.hour());
// Serial.println(gps.time.minute());
}
ms_last_update = millis();
continue;
}
// Hooray!
Serial.println("ok");
return;
Serial.println(F("ok"));
// gps.f_get_position pushes the values into result directly
#ifdef SD_DEBUG_ENABLED
int year;
byte month, day, hour, minute, second, _hundredths;
gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &_hundredths, &fix_age);
snprintf(result.time, 19, "%04d-%02d-%02d %02d:%02d:%02d",
year, month, day,
hour, minute, second
);
#endif
return result;
}
gps_end(serial_gps);
}
TinyGPSPlus gps_info() {
return gps;
}
void gps_end() {
Serial.println(F("[warning] Putting the GPS device to sleep isn't implemented yet."));
void gps_end(SoftwareSerial serial_gps) {
serial_gps.end();
}

27
iot/main/gps.h

@ -2,27 +2,29 @@
#include <SoftwareSerial.h>
#include <TinyGPS++.h>
#include "settings.h"
// A lightweight struct to hold location information.
// TinyGPS++ actually uses a *ton* of RAM so we can't keep the instance around as a global variable
// FUTURE: Rewrite this to use TinyGPS's integer system instead?
struct GPSLocation {
float lat;
float lng;
#ifdef SD_DEBUG_ENABLED
char time[20];
#endif
};
/**
* Initialises the connection to the GPS device.
*/
void gps_begin();
SoftwareSerial gps_begin();
/**
* Fetches new data from the GPS module.
* May take a moment, as the GPS device needs time to acquire a satellite fix.
*/
void gps_fetch();
/**
* Fetches the latest information from the GPS device.
* Call gps_fetch() first.
* @return TinyGPSLocation The current location.
*/
TinyGPSLocation gps_info();
GPSLocation gps_fetch();
/**
* Ends the connection to the GPS device and puts it to sleep in order to save
@ -31,5 +33,6 @@ TinyGPSLocation gps_info();
* system and so doesn't get turned off after each measurement, as it takes
* ~30s to reacquire a lock when it first starts up - hence why we put it to
* sleep instead.
* TODO: Connect the gps module to the timed power rail instead, as its; got an onboard battery.
*/
void gps_end();
void gps_end(SoftwareSerial gps);

85
iot/main/main.ino

@ -1,47 +1,78 @@
#include "settings.h"
#include <Arduino.h>
#include <TinyGPS++.h>
#include <SD.h>
#ifdef ENABLE_MEMORY_DIAGNOSTICS
// #include <MemoryFree.h>
#define DISPLAY_FREE_MEMORY() Serial.println(freeMemory(), DEC);
#else
#define DISPLAY_FREE_MEMORY() // noop
#endif
#include "settings.h"
#include "random.h"
// BAD PRACTICE: For some extremely strange reason, the Arduino IDE doesn't pick up random.cpp like it does our other source files - so we've got to explicitly include it here. If we had control over the build process (which we don't), we've use a Makefile here that handled this better.
#include "random.cpp"
#include "gps.h"
#include "peripheral.h"
#include "storage.h"
#include "power.h"
#include "transmission.h"
void setup() {
Serial.begin(BAUD_PC);
Serial.println("[main] Beginning collection sequence.");
Serial.println(F("[main] Starting"));
random_begin();
gps_begin();
TinyGPSPlus gps_data = gps_location();
gps_end();
Serial.print("[main] Location: ("); Serial.print(loc.lat()); Serial.print(", "); Serial.print(loc.lng()); Serial.println(")");
DISPLAY_FREE_MEMORY();
GPSLocation gps_data = gps_fetch();
DISPLAY_FREE_MEMORY();
Serial.print(F("[main] Location ")); Serial.print(gps_data.lat); Serial.print(F(", ")); Serial.println(gps_data.lng);
uint32_t id = random_get();
Serial.print("[main] id: ");
Serial.print(F("[main] Id "));
Serial.println(id);
store_init();
store_reading(id, gps_data.location);
char debug_message[64];
int chars = snprintf(debug_message, 64, "%d-%d-%d %d:%d:%d | %f %f",
gps_data.date.year(),
gps_data.date.month(),
gps_data.date.day(),
gps_data.time.hour(),
gps_data.time.minute(),
gps_data.time.second(),
gps_data.location.lat(),
gps_data.location.lng(),
);
store_debug(debug_message, chars);
store_close();
// Activate microSD card breakout board on the SPI bus
peripheral_register(PIN_SPI_CS_RFM95);
peripheral_register(PIN_SPI_CS_SD);
peripheral_unsilence(PIN_SPI_CS_SD);
DISPLAY_FREE_MEMORY();
store_reading(id, gps_data);
DISPLAY_FREE_MEMORY();
#ifdef SD_DEBUG_ENABLED
store_debug(gps_data.time, 19 - 1); // Don't print the null byte
DISPLAY_FREE_MEMORY();
#endif
// ------------------------------------------------------------------------
// Activate the RFM95
peripheral_switch(PIN_SPI_CS_SD, PIN_SPI_CS_RFM95);
const uint8_t message_size = sizeof(uint32_t) + sizeof(float)*2;
uint8_t message[message_size];
const uint8_t* bytes_id = reinterpret_cast<uint8_t const *>(&id);
const uint8_t* bytes_lat = reinterpret_cast<uint8_t const *>(&(gps_data.lat));
const uint8_t* bytes_lng = reinterpret_cast<uint8_t const *>(&(gps_data.lat));
for(int i = 0; i < sizeof(uint32_t); i++) {
message[i] = bytes_id[i];
}
for(int i = 0; i < sizeof(float); i++) {
message[sizeof(uint32_t) + i] = bytes_lat[i];
message[sizeof(uint32_t) + sizeof(float) + i] = bytes_lng[i];
}
#ifdef ENABLE_RADIO
Serial.println(F("[main] Transmitting"));
transmit_init();
transmit_send(message, message_size);
#endif
power_off(); // Doesn't return
}

20
iot/main/peripheral.cpp

@ -0,0 +1,20 @@
#include <Arduino.h>
void peripheral_register(uint8_t pin_number) {
pinMode(OUTPUT, pin_number);
// Disable the device by default to avoid issues
digitalWrite(pin_number, HIGH);
}
void peripheral_unsilence(uint8_t pin_number) {
digitalWrite(pin_number, LOW);
}
void peripheral_silence(uint8_t pin_number) {
digitalWrite(pin_number, HIGH);
}
void peripheral_switch(uint8_t pin_number_old, uint8_t pin_number_new) {
digitalWrite(pin_number_old, HIGH);
digitalWrite(pin_number_new, LOW);
}

24
iot/main/peripheral.h

@ -0,0 +1,24 @@
#pragma once
/**
* Register a new SPI peripheral.
* @param pin_number The pin number of the device's chip select pin.
*/
void peripheral_register(uint8_t pin_number);
/**
* Allows the device with the given chip select pin to talk on the SPI bus.
* @param pin_number The pin number of the chip select pin of the device to allow to talk.
*/
void peripheral_unsilence(uint8_t pin_number);
/**
* Stops the device with the given chip select pin from talking on the SPI bus.
* @param pin_number The chip-select pin number of the device to stop.
*/
void peripheral_silence(uint8_t pin_number);
/**
* Switches the active device from one to another on the SPI bus.
* @param pin_number_old The chip-select pin number of the old device to switch out from.
* @param pin_number_new The chip-select pin number of the new device to switch in to.
*/
void peripheral_switch(uint8_t pin_number_old, uint8_t pin_number_new);

15
iot/main/power.cpp

@ -2,19 +2,10 @@
#include "settings.h"
void power_gps_wakeup() {
Serial.println("Warn: GPS wakeup isn't implemented yet.");
}
void power_gps_standby() {
Serial.println("Warn: GPS standby isn't implemented yet.");
}
void power_off() {
Serial.println(F("[power] Beginning shutdown sequence"));
Serial.println(F("[power] Switching GPS module to standby"));
power_gps_standby();
Serial.println(F("[power] Shutting down"));
// Serial.println(F("[power] Switching GPS module to standby"));
// power_gps_standby();
Serial.println(F("[power] Signalling TPL5111"));
pinMode(PIN_TPL_DONE, OUTPUT);

9
iot/main/power.h

@ -1,13 +1,4 @@
/**
* Wakes the GPS module up from standby.
*/
void power_gps_wakeup();
/**
* Puts the GPS module into standby.
*/
void power_gps_standby();
/**
* Signals that our work is complete and that we can turn off now to the
* TPL5111.

26
iot/main/settings.custom.cpp.example

@ -0,0 +1,26 @@
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include "settings.h"
// LoRaWAN NwkSKey, network session key
// This is the default Semtech key, which is used by the early prototype TTN
// network.
static const PROGMEM u1_t NWKSKEY[16] = { .... };
// LoRaWAN AppSKey, application session key
// This is the default Semtech key, which is used by the early prototype TTN
// network.
static const u1_t PROGMEM APPSKEY[16] = { ..... };
// LoRaWAN end-device address (DevAddr)
static const u4_t DEVADDR = 0x....; // <-- Change this address for every node!
const lmic_pinmap lmic_pins = {
.nss = PIN_SPI_CS_RFM95,
.rxtx = LMIC_UNUSED_PIN,
.rst = 9,
.dio = {2, 6, 7},
};

14
iot/main/settings.custom.h

@ -0,0 +1,14 @@
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
/**
* Don't edit this file - you want to make a copy of settings.custom.cpp, call
* it settings.cpp, and edit that instead.
*/
extern const PROGMEM u1_t NWKSKEY[16];
extern const u1_t PROGMEM APPSKEY[16];
extern const u4_t DEVADDR;
extern const lmic_pinmap lmic_pins;

51
iot/main/settings.h

@ -1,5 +1,18 @@
#pragma once
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include "settings.custom.h"
/*
* This is the main settings file - customise it to match your setup.
* Don't forget that you need to take a copy of settings.custom.cpp.example as
* settings.custom.cpp, and fill in the private keys the program needs to talk
* over LORaWAN.
*/
//////////////////////////////////
////////////// Main //////////////
//////////////////////////////////
@ -7,23 +20,44 @@
// The speed at which we should talk over our main hardware serial connection.
#define BAUD_PC 115200
// Multiple devices can use the same SPI data pin AFAIKT, but some libraries *cough* SD *cough* are too stupid to figure out which pin it is on their own.
// Multiple devices can use the same SPI data pin AFAIKT, but some libraries
// *cough* SD *cough* are too stupid to figure out which pin it is on their own.
#define PIN_SPI_DATA 9
// The 'done' pin to pulse to signal to the TPL5111
#define PIN_TPL_DONE 8
// Uncomment to print RAM diagnostics at regular intervals.
//#define ENABLE_MEMORY_DIAGNOSTICS
/////////////
/// RFM95 ///
/////////////
// Whether to actually enable LMIC-based transmission or not.
#define ENABLE_RADIO
// The SPI chip-select pin for the RFM 95
#define PIN_SPI_CS_RFM95 10
// Pin mapping - see settings.custom.cpp
/////////////////////////////////
////////////// GPS //////////////
/////////////////////////////////
// The *TX* gin of the GPS device.
// This is swapped because we receive the GPS device's message on our side on the RX pin, and the GPS device transmits messages on the TX.
// This is swapped because we receive the GPS device's message on our side on
// the RX pin, and the GPS device transmits messages on the TX.
#define PIN_GPS_RX 5
// The *RX* pin on the GPS device.
// This is swapped because where the GPs device is receiving, we aresending and vice versa.
// This is swapped because where the GPs device is receiving, we aresending and
// vice versa.
// The TX / RX here are according to *our* side, not the GPS device's side.
#define PIN_GPS_TX 4
// The speed at which we should talk to the GPS device. Some GPS devices require a certain speed in order to use certain commands, so it's important that you check the datasheets for the device you're using.
// The speed at which we should talk to the GPS device. Some GPS devices
// require a certain speed in order to use certain commands, so it's important
// that you check the datasheets for the device you're using.
// 9600 is the correct speed for a NEO-6M.
#define BAUD_GPS 9600
@ -32,10 +66,13 @@
//////////////////////////////////
// The chip select pin that activates the connection to the microSD card over SPI.
#define PIN_SD_SPI_CHIP_SELECT 3
#define PIN_SPI_CS_SD 3
// The filename on the microSD card to store data in.
#define SD_FILENAME "data.tsv"
#define SD_FILENAME F("data.tsv")
// Whether to write debug information to the filename below. This could compromise privacy, so it should be commented out for production.
// #define SD_DEBUG_ENABLED
// The filename on the microSD card to store debug information in
#define SD_FILENAME_DEBUG "debug.log"
#define SD_FILENAME_DEBUG F("debug.log")

57
iot/main/storage.cpp

@ -1,41 +1,48 @@
#include <Arduino.h>
#include <SD.h>
#include <TinyGPS++.h>
#include <SdFat.h>
// #include <ArduinoFiles.h>
#include "settings.h"
#include "gps.h"
File _debug_handle;
// FUTURE: We might be able to trim it down if we disable long filenames with #define
void store_init() {
// NOTE: If this doesn't work, then we need to use pinMode() & specify the data pin here instead.
if(!SD.begin(PIN_SD_SPI_CHIP_SELECT)) {
Serial.println(F("Error: Failed to initialise connection to microSD card"));
while(true) { delay(1); } // Pseudo-halt, but uses delay() to ensure we keep passing back control to allow easy re-programming
SdFat store_init() {
SdFat card;
if(!card.begin(PIN_SPI_CS_SD)) {
Serial.println("Error: MicroSD card init failed");
while(true) delay(100);
}
return card;
}
void store_reading(uint32_t id, TinyGPSLocation location) {
void store_reading(uint32_t id, GPSLocation location) {
// Port the rest of this to SdFat from SD
SdFat card = store_init();
// Open the file to write the data to. If it doesn't exist it will be created.
// Unlike elsewhere, FILE_WRITE opens the file with the cursor starting at the end, so it's basically actually equivalent to FILE_APPEND that we use in other environments. Confusing, I know.
File handle = SD.open(SD_FILENAME, FILE_WRITE);
handle.print(id);
handle.print("\t");
handle.print(location.lat());
handle.print("\t");
handle.print(location.lng());
handle.println();
handle.close();
File handle = card.open(SD_FILENAME, O_APPEND | O_CREAT | O_WRONLY);
uint8_t places = 6;
handle.printField(id, '\t');
handle.printField(location.lat, '\t', places);
handle.printField(location.lng, '\n', places);
handle.close(); // Syncs implicitly
}
void store_debug(char* buffer, int size) {
File handle = SD.open(SD_FILENAME_DEBUG, FILE_WRITE);
SdFat card = store_init();
File handle = card.open(SD_FILENAME_DEBUG, O_APPEND | O_CREAT | O_WRONLY);
handle.write(buffer, size);
handle.write('\n');
handle.close();
}
void store_close() {
SD.end();
/*
void store_end() {
// Apparently we'ree fine so long as we don't have any open file handles - there's no end() method on the SdFat class
// card->end();
delete card;
card = nullptr;
}
*/

8
iot/main/storage.h

@ -1,14 +1,14 @@
#pragma once
#include <SPI.h>
#include <SD.h>
#include <SdFat.h>
#include <TinyGPS++.h>
#include "gps.h"
void store_init();
void store_reading(uint32_t id, TinyGPSLocation location);
void store_reading(uint32_t id, GPSLocation location);
void store_debug(char* buffer, int size);
void store_close();
// void store_end();

150
iot/main/transmission.cpp

@ -0,0 +1,150 @@
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include "settings.h"
// Global static variable that's used to detect when LMIC has finished doing it's thing
bool is_sending_complete = false;
void transmit_init() {
Serial.println(F("[LMIC] Init"));
// LMIC init
os_init();
// Reset the MAC state. Session and pending data transfers will be discarded.
LMIC_reset();
// Set static session parameters. Instead of dynamically establishing a session
// by joining the network, precomputed session parameters are be provided.
#ifdef PROGMEM
// On AVR, these values are stored in flash and only copied to RAM
// once. Copy them to a temporary buffer here, LMIC_setSession will
// copy them into a buffer of its own again.
uint8_t appskey[sizeof(APPSKEY)];
uint8_t nwkskey[sizeof(NWKSKEY)];
memcpy_P(appskey, APPSKEY, sizeof(APPSKEY));
memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY));
LMIC_setSession (0x1, DEVADDR, nwkskey, appskey);
#else
// If not running an AVR with PROGMEM, just use the arrays directly
LMIC_setSession (0x1, DEVADDR, NWKSKEY, APPSKEY);
#endif
// Set up the channels used by the Things Network, which corresponds
// to the defaults of most gateways. Without this, only three base
// channels from the LoRaWAN specification are used, which certainly
// works, so it is good for debugging, but can overload those
// frequencies, so be sure to configure the full frequency range of
// your network here (unless your network autoconfigures them).
// Setting up channels should happen after LMIC_setSession, as that
// configures the minimal channel set.
LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI); // g-band
LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI); // g2-band
// TTN defines an additional channel at 869.525Mhz using SF9 for class B
// devices' ping slots. LMIC does not have an easy way to define set this
// frequency and support for class B is spotty and untested, so this
// frequency is not configured here.
// Disable link check validation
LMIC_setLinkCheckMode(0);
// Set data rate and transmit power (note: txpow seems to be ignored by the library)
LMIC_setDrTxpow(DR_SF7,14);
}
/**
* Sends a specified message via LoRaWAN.
* @param data The message to send.
* @param length The length of the given message.
*/
bool transmit_send(uint8_t* data, int length) {
// Check if there is not a current TX/RX job running
if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND: Job running, can't send"));
return false;
}
// Prepare upstream data transmission at the next possible time.
LMIC_setTxData2(1, data, length, 0);
Serial.println(F("Packet queued"));
// Run the LMIC loop, but only until it's finished sending the packet
while (!is_sending_complete) {
os_runloop_once();
}
// Reset it for next time (just in case)
is_sending_complete = false;
return true;
}
void onEvent (ev_t ev) {
Serial.print(millis());
Serial.print(" ");
switch(ev) {
case EV_SCAN_TIMEOUT:
Serial.println(F("EV_SCAN_TIMEOUT"));
break;
case EV_BEACON_FOUND:
Serial.println(F("EV_BEACON_FOUND"));
break;
case EV_BEACON_MISSED:
Serial.println(F("EV_BEACON_MISSED"));
break;
case EV_BEACON_TRACKED:
Serial.println(F("EV_BEACON_TRACKED"));
break;
/*case EV_JOINING:
Serial.println(F("EV_JOINING"));
break;
case EV_JOINED:
Serial.println(F("EV_JOINED"));
break;*/
case EV_RFU1:
Serial.println(F("EV_RFU1"));
break;
/*case EV_JOIN_FAILED:
Serial.println(F("EV_JOIN_FAILED"));
break;
case EV_REJOIN_FAILED:
Serial.println(F("EV_REJOIN_FAILED"));
break;*/
case EV_TXCOMPLETE:
Serial.println(F("EV_TXCOMPLETE (incl. RX window wait)"));
if(LMIC.dataLen) {
// data received in rx slot after tx
Serial.print(F("Received: "));
Serial.write(LMIC.frame+LMIC.dataBeg, LMIC.dataLen);
Serial.println();
}
// We're done!
is_sending_complete = true;
break;
case EV_LOST_TSYNC:
Serial.println(F("EV_LOST_TSYNC"));
break;
case EV_RESET:
Serial.println(F("EV_RESET"));
break;
case EV_RXCOMPLETE:
// data received in ping slot
Serial.println(F("EV_RXCOMPLETE"));
break;
case EV_LINK_DEAD:
Serial.println(F("EV_LINK_DEAD"));
break;
case EV_LINK_ALIVE:
Serial.println(F("EV_LINK_ALIVE"));
break;
default:
Serial.println(F("Unknown event"));
break;
}
}

13
iot/main/transmission.h

@ -0,0 +1,13 @@
#pragma once
/**
* Initialises the RFM95 LoRa radio.
*/
void transmit_init();
/**
* Sends a specified message via LoRaWAN.
* @param data The message to send.
* @param length The length of the given message.
*/
bool transmit_send(byte* data, int length);
Loading…
Cancel
Save