325 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			325 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include <lmic.h>
 | |
| #include <hal/hal.h>
 | |
| #include <TinyGPS++.h>
 | |
| 
 | |
| #include <AltSoftSerial.h>
 | |
| 
 | |
| // LoRaWAN NwkSKey, network session key
 | |
| static const PROGMEM u1_t NWKSKEY[16] = { 0x52, 0x92, 0xC0, 0x72, 0x2D, 0x3C, 0x55, 0x5E, 0xE4, 0xB9, 0x9E, 0x9B, 0x88, 0x66, 0x47, 0xF1 };
 | |
| 
 | |
| // LoRaWAN AppSKey, application session key
 | |
| static const u1_t PROGMEM APPSKEY[16] = { 0xC4, 0x30, 0xEF, 0x56, 0x4F, 0x6D, 0xA2, 0x56, 0x1F, 0x15, 0x2F, 0xB8, 0x62, 0xC7, 0xCA, 0xC2 };
 | |
| 
 | |
| void os_getArtEui (u1_t* buf) { }
 | |
| void os_getDevEui (u1_t* buf) { }
 | |
| void os_getDevKey (u1_t* buf) { }
 | |
| 
 | |
| static osjob_t sendjob;
 | |
| // Chirpstack keys
 | |
| // static const u1_t PROGMEM NWKSKEY[16] = {0x5b,0xe6,0x8b,0xb7,0xaa,0x4f,0x01,0x85,0x54,0x72,0xd9,0x6f,0xd8,0xba,0xbc,0x99};
 | |
| // static const u1_t PROGMEM APPSKEY[16] = {0xee,0x9a,0x94,0x96,0x9d,0x59,0xfb,0xc2,0x7a,0xe6,0x07,0xe1,0x6e,0x04,0x37,0x5b};
 | |
| // static const u4_t DEVADDR = 0x005d96f5;
 | |
| 
 | |
| static const u4_t DEVADDR = 0x260212B6;
 | |
| 
 | |
| // void printf(char *str) {
 | |
| //    Serial.println(str);
 | |
| // }
 | |
| 
 | |
| void debug_function(char *str) {
 | |
|   Serial.println(str);
 | |
| }
 | |
| 
 | |
| 
 | |
| // Schedule TX every this many seconds (might become longer due to duty
 | |
| // cycle limitations).
 | |
| const unsigned TX_INTERVAL = 5;
 | |
| 
 | |
| // Pin mapping for Adafruit Feather M0 LoRa
 | |
| const lmic_pinmap lmic_pins = {
 | |
|   .nss = 10,
 | |
|   .rxtx = LMIC_UNUSED_PIN,
 | |
|   .rst = 9,
 | |
|   .dio = {2, 3, LMIC_UNUSED_PIN},
 | |
| };
 | |
| 
 | |
| /****************************************************
 | |
|  * Arduino drivers
 | |
|  * - LoRaWAN
 | |
|  * - GPS
 | |
|  ***************************************************/
 | |
| TinyGPSPlus gps;
 | |
| 
 | |
| 
 | |
| uint8_t general_int;
 | |
| volatile uint8_t n_poly;
 | |
| #define isr general_int
 | |
| #define timeout general_int
 | |
| /****************************************************
 | |
|  * 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*2+5];
 | |
| float * const polyy = polyx + N_POLY_MAX;
 | |
| 
 | |
| /****************************************************
 | |
|  * Check a pair of coordinates against two lists
 | |
|  * of vertices
 | |
|  * - https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html
 | |
|  ***************************************************/
 | |
| const int pnpoly
 | |
| (const uint8_t nvert, const float *vertx, const float *verty, const float testx, const float testy)
 | |
| {
 | |
|   uint8_t 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
 | |
|  * - takes current GPS coordinates
 | |
|  * - return 1 if in bounds
 | |
|  ***************************************************/
 | |
| const int check_bounds(const float x, const float y) {
 | |
|   return pnpoly(n_poly, polyx, polyy, x, y);
 | |
| }
 | |
| 
 | |
| /****************************************************
 | |
|  * Load coordinates from protobuff stream
 | |
|  * - currently a maximum of 10 coordinates
 | |
|  * - loading arrays in nanopb does not appear
 | |
|  *   to work.
 | |
|  ***************************************************/
 | |
| static volatile uint8_t data_available = 0;
 | |
| static volatile uint8_t done_sending = 0;
 | |
| inline void import_protobuf(const uint8_t *protobuffer, const uint8_t size) {
 | |
|   if(size != 122) {
 | |
|     Serial.println("nmd");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   isr = protobuffer[1];
 | |
| 
 | |
|   if(isr>N_POLY_MAX) isr = 0;
 | |
|   const uint8_t *ptr = protobuffer + 5;
 | |
|   for(uint8_t i=0;i<isr && i<N_POLY_MAX;i++) {
 | |
|     memcpy(&polyx[i], ptr + i*12, 4);
 | |
|     memcpy(&polyy[i], ptr + i*12+5, 4);
 | |
|   }
 | |
|   n_poly = isr;
 | |
|   data_available = 1;
 | |
| }
 | |
| void do_send(osjob_t* j);
 | |
| 
 | |
| void onEvent (ev_t ev) {
 | |
| 
 | |
|   if(ev == EV_TXCOMPLETE) {
 | |
|     // Serial.print(F("- 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);
 | |
| 
 | |
|     if (LMIC.dataLen) {
 | |
|       import_protobuf(LMIC.frame + LMIC.dataBeg, LMIC.dataLen);
 | |
|     }
 | |
|     done_sending = 1;
 | |
|   }
 | |
|   else if(ev == EV_TXSTART) {
 | |
|     // Serial.print(F("- EV_TXSTART"));
 | |
|   }
 | |
|   else {
 | |
|     Serial.print(F("- Unknown event: "));
 | |
|   }
 | |
| }
 | |
| 
 | |
| enum STATE_ {
 | |
|   START,
 | |
|   START_GPS,
 | |
|   WAIT_GPS,
 | |
|   START_LORA,
 | |
|   WAIT_LORA,
 | |
|   END
 | |
| };
 | |
| 
 | |
| volatile uint8_t state;
 | |
| volatile uint32_t since;
 | |
| void setup() {
 | |
|   Serial.begin(9600);
 | |
| 
 | |
|   Serial.println(F("Starting"));
 | |
|       softserial_init();
 | |
| 
 | |
|   // softserial_init();
 | |
|   // pinMode(LED_BUILTIN, OUTPUT);
 | |
| 
 | |
|   os_init();
 | |
|   LMIC_reset();
 | |
| 
 | |
|   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);
 | |
|   LMIC_selectSubBand(1);
 | |
| 
 | |
|   // 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);
 | |
| 
 | |
|   delay(1000);
 | |
| 
 | |
|   data_available = 0;
 | |
|   state = START_GPS;
 | |
| 
 | |
|   
 | |
|   do_send(&sendjob);
 | |
| }
 | |
| 
 | |
| inline void read_gps() {
 | |
|   uint8_t available = softserial_available();
 | |
|   while(available> 0) {
 | |
|     gps.encode(softserial_read());
 | |
|     available--;
 | |
|   }
 | |
| }
 | |
| 
 | |
| inline void on_start_gps() {
 | |
|   Serial.println("Starting gps");
 | |
|   state = WAIT_GPS;
 | |
| }
 | |
| 
 | |
| inline void on_wait_gps() {
 | |
|   Serial.println("Waiting for gps");
 | |
|   if(gps.location.isValid()) {
 | |
|     state = START_LORA;
 | |
|     Serial.println("end of gps");
 | |
|   }
 | |
| }
 | |
| 
 | |
| #define TYPE_STRING 0x0A
 | |
| #define TYPE_VARIANT 0x10
 | |
| #define PROTO_LEN 0x0A
 | |
| #define FIELD_ONE_FLOAT 0x0D
 | |
| #define FIELD_TWO_FLOAT 0x15
 | |
| #define FIELD_TWO_VARIANT 0x10
 | |
| 
 | |
| 
 | |
| const char oob[] = "OUT OF BOUNDS";
 | |
| const char inb[] = "IN BOUNDS";
 | |
| uint8_t buffer[] = {
 | |
|   TYPE_STRING,
 | |
|   PROTO_LEN,
 | |
|   FIELD_ONE_FLOAT, 0x00, 0x00, 0x48, 0x43,
 | |
|   FIELD_TWO_FLOAT, 0x00, 0x00, 0xc8, 0x42,
 | |
|   FIELD_TWO_VARIANT, 0, 0};
 | |
| 
 | |
| void on_start_lora() {
 | |
|   // Serial.println("Starting lora");
 | |
| }
 | |
| void send_lora() {
 | |
|   if (LMIC.opmode & OP_TXRXPEND) {
 | |
|     Serial.print(" -cns");
 | |
|     done_sending = 1;
 | |
|   } else {
 | |
|     const float latitude  = gps.location.lat();
 | |
|     const float longitude = gps.location.lng();
 | |
|     if(n_poly>0) {
 | |
|       const uint8_t out_of_bounds = 0;//!check_bounds(latitude, longitude);
 | |
|       if(out_of_bounds) {
 | |
|         Serial.println(oob);
 | |
|       } else {
 | |
|         Serial.println(inb);
 | |
|       }
 | |
|       // digitalWrite(LED_BUILTIN, out_of_bounds);
 | |
|       buffer[13] = out_of_bounds;
 | |
|     } else {
 | |
|       const uint8_t out_of_bounds = 0;
 | |
|       buffer[13] = out_of_bounds;
 | |
|     }
 | |
|     memcpy(buffer+3, (void*)&latitude, 4);
 | |
|     memcpy(buffer+8, (void*)&longitude, 4);
 | |
|     LMIC_setTxData2(1, buffer, sizeof(buffer)-1, 0);
 | |
|   }
 | |
|   since = millis();
 | |
|   
 | |
|   state = WAIT_LORA;
 | |
| }
 | |
| void do_send(osjob_t* j){
 | |
|   send_lora();
 | |
| }
 | |
| 
 | |
| void on_wait_lora() {
 | |
|   if(millis()- since > TX_INTERVAL*1000UL || done_sending) {
 | |
|     Serial.println(" -Lora Done Sending");
 | |
|     done_sending = 0;
 | |
|     
 | |
|     state = START_GPS;
 | |
|   }
 | |
| }
 | |
| 
 | |
| #define LOOP_LATENCY_MS 200L
 | |
| 
 | |
| uint32_t time_last = 0;
 | |
| void loop() {
 | |
|   uint32_t time = millis();
 | |
|   if((time - time_last) > LOOP_LATENCY_MS) {
 | |
|     time_last = time;
 | |
|     Serial.print(state);
 | |
|     Serial.print('.');
 | |
| 
 | |
|     if(state == START_GPS) {
 | |
|       // softserial_init();
 | |
|       on_start_gps();
 | |
|     }
 | |
|     else if(state == WAIT_GPS) {
 | |
|       on_wait_gps();
 | |
|     }
 | |
|     else if(state == START_LORA) {
 | |
|       // softserial_end();
 | |
|       on_start_lora();
 | |
|     }
 | |
|     else if(state == WAIT_LORA) {
 | |
|       on_wait_lora();
 | |
|     }
 | |
|   } else {
 | |
|     // Serial.print('.');
 | |
|   }
 | |
|   if(data_available) {
 | |
|     Serial.println("Data available");
 | |
|      for(uint8_t i=0;i<n_poly;i++) {
 | |
|        Serial.print('(');
 | |
|        Serial.print((int)(polyy[i]*100));
 | |
|        Serial.print(',');
 | |
|        Serial.print((int)(polyx[i]*100));
 | |
|        Serial.print(')');
 | |
|        Serial.print(',');
 | |
|      }
 | |
|     data_available = 0;
 | |
|   }
 | |
|   if(state == WAIT_GPS)
 | |
|     read_gps();
 | |
|   os_runloop_once();
 | |
|   // Serial.print(time);
 | |
|   // Serial.print(':');
 | |
|   // Serial.println(state);
 | |
|   // delay(100);
 | |
| }
 |