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

This commit is contained in:
Starbeamrainbowlabs 2019-07-01 15:05:03 +01:00
commit 1fbd864af8
28 changed files with 1206 additions and 130 deletions

3
.gitignore vendored
View File

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

15
.gitmodules vendored
View File

@ -4,9 +4,18 @@
[submodule "iot/libraries/Entropy"] [submodule "iot/libraries/Entropy"]
path = iot/libraries/Entropy path = iot/libraries/Entropy
url = https://github.com/taoyuan/Entropy.git 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"] [submodule "iot/libraries/TinyGPSPlus"]
path = iot/libraries/TinyGPSPlus path = iot/libraries/TinyGPSPlus
url = https://github.com/mikalhart/TinyGPSPlus.git 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

View File

@ -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 - [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 - [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 ## Useful Links
- Entropy extraction from the watchdog timer vs the internal clock: https://github.com/taoyuan/Entropy - Entropy extraction from the watchdog timer vs the internal clock: https://github.com/taoyuan/Entropy

52
build
View File

@ -43,18 +43,60 @@ fi
############################################################################### ###############################################################################
task_setup() { # Toggles commenting and uncommenting lines in a file that contain a specific
task_begin "Setting up"; # 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() {
stage_begin "Setting up";
task_begin "Checking Environment";
check_command git true; check_command git true;
check_command awk true;
check_command pdflatex true; check_command pdflatex true;
check_command bibtex true; check_command bibtex true;
task_end $?;
subtask_begin "Initialising submodules"; task_begin "Initialising submodules";
git submodule update --init; git submodule update --init;
subtask_end $?; task_end $?;
task_end 0; 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 $?;
stage_end 0;
} }
task_render() { task_render() {

195
iot/TTNTest/TTNTest.ino Normal file
View File

@ -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();
}

View File

@ -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!

View File

@ -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();
}

View File

@ -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!

View File

@ -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();
}

View File

@ -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},
};

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

1
iot/libraries/SdFat Submodule

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

1
iot/libraries/TinyGPS Submodule

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

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

View File

@ -1,54 +1,93 @@
#include "gps.h" #include "gps.h"
#include "settings.h" #include "settings.h"
// The GPS message decoder #include <TinyGPS.h>
TinyGPSPlus gps;
// The serial connection to the GPS device
SoftwareSerial serial_gps(PIN_GPS_RX, PIN_GPS_TX);
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 // Initialise the software-based serial connection to the GPS device
serial_gps.begin(BAUD_GPS); serial_gps.begin(BAUD_GPS);
return serial_gps;
} }
void gps_fetch() { GPSLocation gps_fetch() {
Serial.print("[gps] Getting location: "); // The serial connection to the GPS module
uint32_t ms_last_update = millis(); 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! // We WILL discover our location - if it's the last thing we do!
while(true) { while(true) {
if(millis() > 5000 && gps.charsProcessed() < 10) { gps.stats(&chars, &sentences, &failed_checksum);
Serial.println(F("\n[error] Failed to initialise GPS device."));
if(millis() - start > 5000 && chars < 10) {
Serial.println(F("\n[error] GPS device init failed"));
while(true) { delay(1); } while(true) { delay(1); }
} }
// Make sure there's something to read // Make sure there's something to read
if(serial_gps.available() <= 0) { if(serial_gps.available() <= 0)
continue; continue;
}
// If it failed, go around again // If it failed, go around again
if(!gps.encode(serial_gps.read())) if(!gps.encode(serial_gps.read()))
continue; continue;
gps.f_get_position(&(result.lat), &(result.lng), &fix_age);
// If we haven't acquired a lock yet, go around again // 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(fix_age == TinyGPS::GPS_INVALID_AGE) {
if(!gps.location.isValid()) { // NOTE: It can take a rather long time to get a lock. Just wait patiently :-)
// NOTE: It can really take a rather long time to get a lock. Just wait patiently :-) if(millis() - ms_last_update > 500) {
if(millis() - ms_last_update > 500) Serial.print("."); 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(); ms_last_update = millis();
continue; continue;
} }
// Hooray! // Hooray!
Serial.println("ok"); Serial.println(F("ok"));
return;
// 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() { void gps_end(SoftwareSerial serial_gps) {
return gps; serial_gps.end();
}
void gps_end() {
Serial.println(F("[warning] Putting the GPS device to sleep isn't implemented yet."));
} }

View File

@ -2,27 +2,29 @@
#include <SoftwareSerial.h> #include <SoftwareSerial.h>
#include <TinyGPS++.h>
#include "settings.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. * Initialises the connection to the GPS device.
*/ */
void gps_begin(); SoftwareSerial gps_begin();
/** /**
* Fetches new data from the GPS module. * Fetches new data from the GPS module.
* May take a moment, as the GPS device needs time to acquire a satellite fix. * May take a moment, as the GPS device needs time to acquire a satellite fix.
*/ */
void gps_fetch(); GPSLocation gps_fetch();
/**
* Fetches the latest information from the GPS device.
* Call gps_fetch() first.
* @return TinyGPSLocation The current location.
*/
TinyGPSLocation gps_info();
/** /**
* Ends the connection to the GPS device and puts it to sleep in order to save * 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 * 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 * ~30s to reacquire a lock when it first starts up - hence why we put it to
* sleep instead. * 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);

View File

@ -1,47 +1,78 @@
#include <Arduino.h>
#include <TinyGPS++.h>
#include <SD.h>
#include "settings.h" #include "settings.h"
#include <Arduino.h>
#ifdef ENABLE_MEMORY_DIAGNOSTICS
// #include <MemoryFree.h>
#define DISPLAY_FREE_MEMORY() Serial.println(freeMemory(), DEC);
#else
#define DISPLAY_FREE_MEMORY() // noop
#endif
#include "random.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. // 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 "random.cpp"
#include "gps.h" #include "gps.h"
#include "peripheral.h"
#include "storage.h"
#include "power.h"
#include "transmission.h"
void setup() { void setup() {
Serial.begin(BAUD_PC); Serial.begin(BAUD_PC);
Serial.println("[main] Beginning collection sequence."); Serial.println(F("[main] Starting"));
random_begin(); random_begin();
DISPLAY_FREE_MEMORY();
gps_begin(); GPSLocation gps_data = gps_fetch();
TinyGPSPlus gps_data = gps_location(); DISPLAY_FREE_MEMORY();
gps_end();
Serial.print(F("[main] Location ")); Serial.print(gps_data.lat); Serial.print(F(", ")); Serial.println(gps_data.lng);
Serial.print("[main] Location: ("); Serial.print(loc.lat()); Serial.print(", "); Serial.print(loc.lng()); Serial.println(")");
uint32_t id = random_get(); uint32_t id = random_get();
Serial.print("[main] id: "); Serial.print(F("[main] Id "));
Serial.println(id); Serial.println(id);
store_init(); // Activate microSD card breakout board on the SPI bus
store_reading(id, gps_data.location);
char debug_message[64]; peripheral_register(PIN_SPI_CS_RFM95);
int chars = snprintf(debug_message, 64, "%d-%d-%d %d:%d:%d | %f %f", peripheral_register(PIN_SPI_CS_SD);
gps_data.date.year(),
gps_data.date.month(), peripheral_unsilence(PIN_SPI_CS_SD);
gps_data.date.day(),
gps_data.time.hour(), DISPLAY_FREE_MEMORY();
gps_data.time.minute(), store_reading(id, gps_data);
gps_data.time.second(), DISPLAY_FREE_MEMORY();
gps_data.location.lat(), #ifdef SD_DEBUG_ENABLED
gps_data.location.lng(), store_debug(gps_data.time, 19 - 1); // Don't print the null byte
); DISPLAY_FREE_MEMORY();
store_debug(debug_message, chars); #endif
store_close();
// ------------------------------------------------------------------------
// 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 power_off(); // Doesn't return
} }

20
iot/main/peripheral.cpp Normal file
View File

@ -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 Normal file
View File

@ -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);

View File

@ -2,19 +2,10 @@
#include "settings.h" #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() { void power_off() {
Serial.println(F("[power] Beginning shutdown sequence")); Serial.println(F("[power] Shutting down"));
Serial.println(F("[power] Switching GPS module to standby")); // Serial.println(F("[power] Switching GPS module to standby"));
power_gps_standby(); // power_gps_standby();
Serial.println(F("[power] Signalling TPL5111")); Serial.println(F("[power] Signalling TPL5111"));
pinMode(PIN_TPL_DONE, OUTPUT); pinMode(PIN_TPL_DONE, OUTPUT);

View File

@ -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 * Signals that our work is complete and that we can turn off now to the
* TPL5111. * TPL5111.

View File

@ -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},
};

View File

@ -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;

View File

@ -1,5 +1,18 @@
#pragma once #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 ////////////// ////////////// Main //////////////
////////////////////////////////// //////////////////////////////////
@ -7,23 +20,44 @@
// The speed at which we should talk over our main hardware serial connection. // The speed at which we should talk over our main hardware serial connection.
#define BAUD_PC 115200 #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 #define PIN_SPI_DATA 9
// The 'done' pin to pulse to signal to the TPL5111
#define PIN_TPL_DONE 8 #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 ////////////// ////////////// GPS //////////////
///////////////////////////////// /////////////////////////////////
// The *TX* gin of the GPS device. // 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 #define PIN_GPS_RX 5
// The *RX* pin on the GPS device. // 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. // The TX / RX here are according to *our* side, not the GPS device's side.
#define PIN_GPS_TX 4 #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. // 9600 is the correct speed for a NEO-6M.
#define BAUD_GPS 9600 #define BAUD_GPS 9600
@ -32,10 +66,13 @@
////////////////////////////////// //////////////////////////////////
// The chip select pin that activates the connection to the microSD card over SPI. // 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. // 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 // The filename on the microSD card to store debug information in
#define SD_FILENAME_DEBUG "debug.log" #define SD_FILENAME_DEBUG F("debug.log")

View File

@ -1,41 +1,48 @@
#include <Arduino.h> #include <Arduino.h>
#include <SD.h> #include <SdFat.h>
#include <TinyGPS++.h> // #include <ArduinoFiles.h>
#include "settings.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() { SdFat store_init() {
// NOTE: If this doesn't work, then we need to use pinMode() & specify the data pin here instead. SdFat card;
if(!SD.begin(PIN_SD_SPI_CHIP_SELECT)) { if(!card.begin(PIN_SPI_CS_SD)) {
Serial.println(F("Error: Failed to initialise connection to microSD card")); Serial.println("Error: MicroSD card init failed");
while(true) { delay(1); } // Pseudo-halt, but uses delay() to ensure we keep passing back control to allow easy re-programming 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. // 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. // 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); File handle = card.open(SD_FILENAME, O_APPEND | O_CREAT | O_WRONLY);
uint8_t places = 6;
handle.print(id); handle.printField(id, '\t');
handle.print("\t"); handle.printField(location.lat, '\t', places);
handle.printField(location.lng, '\n', places);
handle.print(location.lat()); handle.close(); // Syncs implicitly
handle.print("\t");
handle.print(location.lng());
handle.println();
handle.close();
} }
void store_debug(char* buffer, int size) { 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;
} }
*/

View File

@ -1,14 +1,14 @@
#pragma once #pragma once
#include <SPI.h> #include <SPI.h>
#include <SD.h> #include <SdFat.h>
#include <TinyGPS++.h> #include "gps.h"
void store_init(); 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_debug(char* buffer, int size);
void store_close(); // void store_end();

150
iot/main/transmission.cpp Normal file
View File

@ -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 Normal file
View File

@ -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);