collar/collar.cpp

335 lines
10 KiB
C++

// Hello LoRa - ABP TTN Packet Sender (Multi-Channel)
// Tutorial Link: https://learn.adafruit.com/the-things-network-for-feather/using-a-feather-32u4
//
// Adafruit invests time and resources providing this open source code.
// Please support Adafruit and open source hardware by purchasing
// products from Adafruit!
//
// Copyright 2015, 2016 Ideetron B.V.
//
// Modified by Brent Rubell for Adafruit Industries, 2018
/************************** Configuration ***********************************/
#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <TinyLoRa.h>
#include <SPI.h>
#include "Base64.h"
#include "gateway/message.pb.h"
#include "pb_common.h"
#include "pb.h"
#include "pb_encode.h"
#include "pb_decode.h"
#include "gateway/message.pb.h"
// Visit your thethingsnetwork.org device console
// to create an account, or if you need your session keys.
// Network Session Key (MSB)
uint8_t NwkSkey[16] = {0x52, 0x92, 0xC0, 0x72, 0x2D, 0x3C, 0x55, 0x5E, 0xE4, 0xB9, 0x9E, 0x9B, 0x88, 0x66, 0x47, 0xF1};
// Application Session Key (MSB)
uint8_t AppSkey[16] = {0xC4, 0x30, 0xEF, 0x56, 0x4F, 0x6D, 0xA2, 0x56, 0x1F, 0x15, 0x2F, 0xB8, 0x62, 0xC7, 0xCA, 0xC2};
// Device Address (MSB)
uint8_t DevAddr[4] = {0x26, 0x02, 0x12, 0xB6};
/************************** Example Begins Here ***********************************/
// Data Packet to Send to TTN
unsigned char loraData[Fenceless_CollarResponse_size+1] = {0};
// How many times data transfer should occur, in seconds
const unsigned int sendInterval = 3000;
/****************************************************
* Arduino drivers
* - LoRaWAN
* - GPS
* - Software Serial
***************************************************/
TinyLoRa lora = TinyLoRa(2, 10, 9);
TinyGPSPlus gps;
SoftwareSerial ss(6, 7);
/****************************************************
* Track each pair of X and Y coordinates
* - arrays are used by the pnpoly function
***************************************************/
const uint8_t N_POLY_MAX=10;
float polyx[N_POLY_MAX];
float polyy[N_POLY_MAX];
int n_poly=0;
/****************************************************
* Add a coordinate to the arrays
* - stores a total of N_POLY_MAX pairs
***************************************************/
int push_vert(float x, float y) {
if(n_poly>N_POLY_MAX)
return 0;
polyx[n_poly]=x;
polyy[n_poly]=y;
n_poly++;
return 1;
}
/****************************************************
* 'Clear' pairs of coordinates
***************************************************/
void clear_verts() {
n_poly=0;
}
/****************************************************
* Check a pair of coordinates against two lists
* of vertices
* - https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html
***************************************************/
int pnpoly(int nvert, float *vertx, float *verty, float testx, float testy)
{
int i, j, c = 0;
for (i = 0, j = nvert-1; i < nvert; j = i++) {
if ( ((verty[i]>testy) != (verty[j]>testy)) &&
(testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
c = !c;
}
return c;
}
/****************************************************
* Test a coordinate against all vertices
***************************************************/
int check_bounds(float x, float y) {
return pnpoly(n_poly, polyx, polyy, x, y);
}
/****************************************************
* nanopb callback
***************************************************/
bool Fenceless_Coordinates_callback(pb_istream_t *stream, const pb_field_iter_t *field, void **arg) {
Serial.println("Called");
while(stream->bytes_left) {
Fenceless_Coordinate m = Fenceless_Coordinate_init_zero;
if(!pb_decode(stream, Fenceless_Coordinate_fields, &m)) {
return false;
}
push_vert(m.x,m.y);
return true;
}
return false;
}
/****************************************************
* Load coordinates from protobuff stream
* - currently a maximum of 10 coordinates
* - loading arrays in nanopb does not appear
* to work.
***************************************************/
void import_protobuf(uint8_t *protobuffer, uint32_t size) {
Fenceless_Coordinates m;
pb_istream_t stream = pb_istream_from_buffer(protobuffer, size);
int status = pb_decode(&stream, Fenceless_Coordinates_fields, &m);
if(!status){
Serial.println("Failed to decode");
}
clear_verts();
switch(m.isr) {
case 10:
push_vert(m.coord9.x, m.coord9.y);
case 9:
push_vert(m.coord8.x, m.coord8.y);
case 8:
push_vert(m.coord7.x, m.coord7.y);
case 7:
push_vert(m.coord6.x, m.coord6.y);
case 6:
push_vert(m.coord5.x, m.coord5.y);
case 5:
push_vert(m.coord4.x, m.coord4.y);
case 4:
push_vert(m.coord3.x, m.coord3.y);
case 3:
push_vert(m.coord2.x, m.coord2.y);
push_vert(m.coord1.x, m.coord1.y);
push_vert(m.coord0.x, m.coord0.y);
}
}
/****************************************************
* Initialize values
***************************************************/
void setup()
{
// give GPS time to start up
delay(3000);
/****************************************************
* Configure USART
* - onboard serial to usb
* - software serial connected to GPS module
***************************************************/
Serial.begin(9600);
ss.begin(4800);
while (! Serial);
/****************************************************
* Configure USART
* - onboard serial to usb
* - software serial connected to GPS module
***************************************************/
pinMode(LED_BUILTIN, OUTPUT);
push_vert(44.55818, -123.28341);
push_vert(44.55818, -123.28332);
push_vert(44.558308, -123.28332);
push_vert(44.558308, -123.28341);
/****************************************************
* Configure LoRa
* - set to multi-channel
* - set datarate
* - start communication
* - set transmission power
***************************************************/
Serial.print("Starting LoRa...");
lora.setChannel(MULTI);
lora.setDatarate(SF12BW125); // SF7BW125
if(!lora.begin())
{
Serial.println("Failed");
Serial.println("Check your radio");
while(true);
}
lora.setPower(15); // 1
Serial.println("OK");
}
/****************************************************
* Read a byte from GPS over software serial
***************************************************/
int read_gps() {
int ret = 0;
while(ss.available()>0) {
gps.encode(ss.read());
ret = 1;
}
return ret;
}
/****************************************************
* Set cursor to beginning of line and clear it
***************************************************/
const int16_t PROGRESS_BAR_COUNT = 50;
const int16_t START_OF_LINE = 13;
void clear_line() {
Serial.write(START_OF_LINE);
for(int i=0;i<PROGRESS_BAR_COUNT;i++)
Serial.write(' ');
Serial.write(START_OF_LINE);
}
/****************************************************
* State variables
* - track events of main loop
***************************************************/
enum STATE_ {
START_GPS,
WAITING_GPS,
VERIFYING_GPS,
SENDING_LORA,
WAITING_LORA,
LORA_DONE
};
int state = START_GPS;
int loopCounter = 0;
int startTime = 0;
/****************************************************
* Main loop
* - feeds data to GPS module as it is made
* available
***************************************************/
void loop()
{
if(state == START_GPS) {
Serial.println("Waiting for GPS");
state = WAITING_GPS;
}
else if(state == WAITING_GPS) {
int got_data = read_gps();
/****************************************************
* loading bar animation
***************************************************/
if(got_data) {
if(loopCounter%100==0)
Serial.write('.');
if(loopCounter>PROGRESS_BAR_COUNT*100) {
clear_line();
loopCounter=0;
state = VERIFYING_GPS;
}
loopCounter++;
}
}
else if(state == VERIFYING_GPS) {
/****************************************************
* if no data has been received from the gps in 5 seconds
* then the GPS is probably not connected properly
***************************************************/
if (millis() > 5000 && gps.charsProcessed() < 10)
{
Serial.println(F("No GPS detected: check wiring."));
while(true);
}
/****************************************************
* only send to LoRaWAN if valid GPS coordinates are
* available
***************************************************/
if(gps.location.isValid())
state = SENDING_LORA;
else
state = WAITING_GPS;
}
else if(state == SENDING_LORA) {
/****************************************************
* encode device information into a buffer using
* protobuf
***************************************************/
Fenceless_CollarResponse coord;
coord.loc.x = gps.location.lat();
coord.loc.y = gps.location.lng();
coord.oob = check_bounds(coord.loc.x, coord.loc.y);
pb_ostream_t stream;
stream = pb_ostream_from_buffer(loraData, sizeof(loraData));
pb_encode(&stream, Fenceless_CollarResponse_fields, &coord);
/****************************************************
* send encoded buffer over LoRaWAN
***************************************************/
Serial.println("Sending LoRa Data...");
lora.sendData(loraData, stream.bytes_written, lora.frameCounter);
Serial.print("Frame Counter: ");
Serial.println(lora.frameCounter);
/****************************************************
* set reference time for LoRaWAN transmission delay
***************************************************/
startTime = millis();
state = WAITING_LORA;
}
else if(state == WAITING_LORA) {
/****************************************************
* don't block the GPS from reading here
***************************************************/
read_gps();
/****************************************************
* if enough seconds have been delayed then move to
* next state
***************************************************/
if(millis()/1000 - startTime >= sendInterval) {
state = LORA_DONE;
}
}
else if(state == LORA_DONE) {
state = VERIFYING_GPS;
}
}