408 lines
9.4 KiB
C++
408 lines
9.4 KiB
C++
#include "M5Core2.h"
|
|
#include <M5GFX.h>
|
|
#include "M5_LoRaWAN.h"
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include "Ubuntu_24px.h"
|
|
#include "config.h"
|
|
|
|
|
|
|
|
|
|
M5_LoRaWAN LoRaWAN;
|
|
bool connected = false;
|
|
int lastMessage = 0;
|
|
|
|
void delay(int d) {
|
|
vTaskDelay(d/portTICK_PERIOD_MS);
|
|
}
|
|
|
|
void setupLoRaWAN() {
|
|
|
|
connected = false;
|
|
LoRaWAN.Init(&Serial2, 13, 14);
|
|
Serial.println("Connecting...");
|
|
while (!LoRaWAN.checkDeviceConnect());
|
|
LoRaWAN.writeCMD("AT?\r\n");
|
|
delay(100);
|
|
Serial2.flush();
|
|
|
|
LoRaWAN.writeCMD("AT+ILOGLVL=0\r\n");
|
|
LoRaWAN.writeCMD("AT+CSAVE\r\n");
|
|
LoRaWAN.writeCMD("AT+IREBOOT=0\r\n");
|
|
delay(1000);
|
|
LoRaWAN.configOTTA(
|
|
DEVICE_EUI,
|
|
APP_EUI,
|
|
APP_KEY,
|
|
UPLOAD_DOWNLOAD_MODE
|
|
);
|
|
LoRaWAN.waitMsg(1000);
|
|
|
|
LoRaWAN.setClass("2");
|
|
LoRaWAN.writeCMD("AT+CWORKMODE=2\r\n");
|
|
|
|
// Transmit frequencies
|
|
// 868.1 - SF7BW125 to SF12BW125
|
|
// 868.3 - SF7BW125 to SF12BW125 and SF7BW250
|
|
// 868.5 - SF7BW125 to SF12BW125
|
|
// 867.1 - SF7BW125 to SF12BW125
|
|
// 867.3 - SF7BW125 to SF12BW125
|
|
// 867.5 - SF7BW125 to SF12BW125
|
|
// 867.7 - SF7BW125 to SF12BW125
|
|
// 867.9 - SF7BW125 to SF12BW125
|
|
// 868.8 - FSK
|
|
LoRaWAN.setFreqMask("0004");
|
|
|
|
// 869.525 - SF9BW125 (RX2) | 869525000
|
|
LoRaWAN.setRxWindow("869525000");
|
|
LoRaWAN.startJoin();
|
|
|
|
while(true) {
|
|
delay(10);
|
|
String recvMsg = LoRaWAN.waitMsg(1000);
|
|
recvMsg.trim();
|
|
if(recvMsg == "+CJOIN:OK") {
|
|
Serial.println("Connected!");
|
|
connected = true;
|
|
lastMessage = time(NULL);
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void setup() {
|
|
|
|
M5.begin();
|
|
M5.Lcd.begin();
|
|
M5.Lcd.setTextSize(1);
|
|
M5.Lcd.setFreeFont(&Ubuntu_24px);
|
|
M5.Lcd.setTextPadding(0);
|
|
|
|
M5.Lcd.drawString("Connecting...", 0, 0);
|
|
setupLoRaWAN();
|
|
M5.Lcd.drawString("Connected!", 0, 30);
|
|
delay(500);
|
|
M5.Lcd.clear();
|
|
|
|
xTaskCreatePinnedToCore(taskReceiving, "taskReceiving", 4096, NULL, 1, NULL, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper functions
|
|
|
|
String split(String data, char separator, int index) {
|
|
int found = 0;
|
|
int strIndex[] = { 0, -1 };
|
|
int maxIndex = data.length() - 1;
|
|
|
|
for (int i = 0; i <= maxIndex && found <= index; i++) {
|
|
if (data.charAt(i) == separator || i == maxIndex) {
|
|
found++;
|
|
strIndex[0] = strIndex[1] + 1;
|
|
strIndex[1] = (i == maxIndex) ? i+1 : i;
|
|
}
|
|
}
|
|
return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
|
|
}
|
|
|
|
bool stringInArray(String val, String* arr, int length){
|
|
for(int i = 0; i < length; i++){
|
|
if(arr[i] == val) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void unixToDate(unsigned long timestamp, char* buffer) {
|
|
time_t t = timestamp;
|
|
struct tm* timeStruct = localtime(&t);
|
|
sprintf(buffer, "%02d.%02d.%04d", timeStruct->tm_mday, timeStruct->tm_mon + 1, timeStruct->tm_year + 1900);
|
|
}
|
|
|
|
void unixToTime(unsigned long timestamp, char* buffer) {
|
|
time_t t = timestamp;
|
|
struct tm* timeStruct = localtime(&t);
|
|
sprintf(buffer, "%02d:%02d", timeStruct->tm_hour, timeStruct->tm_min);
|
|
}
|
|
|
|
|
|
|
|
|
|
// Departure stucture
|
|
|
|
struct departure {
|
|
int id;
|
|
int type;
|
|
String line;
|
|
String last_stop;
|
|
int departure;
|
|
};
|
|
|
|
|
|
|
|
|
|
// Global departure list
|
|
|
|
struct departure departures[50];
|
|
int departureIndex = 0;
|
|
|
|
|
|
|
|
|
|
// Global variables
|
|
|
|
time_t seconds;
|
|
int lastUpdate = 0;
|
|
int unixtime = 1;
|
|
|
|
|
|
|
|
|
|
// Recieve from LoraWAN
|
|
|
|
String decodeMsg(String hexEncoded) {
|
|
if ((hexEncoded.length() & 1) == 0) {
|
|
char buf[hexEncoded.length() + 1];
|
|
char tempbuf[((hexEncoded.length() + 1))];
|
|
hexEncoded.toCharArray(buf, hexEncoded.length() + 1);
|
|
int i = 0;
|
|
for (int loop = 2; loop < hexEncoded.length() + 1; loop += 2) {
|
|
String tmpstr = hexEncoded.substring(loop - 2, loop);
|
|
sprintf(&tempbuf[i], "%c", strtoul(tmpstr.c_str(), nullptr, 16));
|
|
i++;
|
|
}
|
|
return String(tempbuf);
|
|
} else {
|
|
return hexEncoded;
|
|
}
|
|
}
|
|
|
|
void receiveMsg(int wait) {
|
|
String recvMsg = LoRaWAN.waitMsg(wait);
|
|
recvMsg.trim();
|
|
if(recvMsg.length() != 0 && recvMsg.substring(0, 8) == "OK+RECV:") {
|
|
lastMessage = seconds;
|
|
String decodedMsg = decodeMsg(split(recvMsg, ',', 3));
|
|
Serial.println(decodedMsg);
|
|
if(decodedMsg.substring(0,5) == "CLEAR") {
|
|
clearDepartures();
|
|
} else
|
|
if(decodedMsg.substring(0,5) == "TIME|") {
|
|
unixtime = (decodedMsg.substring(5,30).toInt()) - seconds;
|
|
} else {
|
|
addDeparture(decodedMsg);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
// Global display variables
|
|
|
|
String displayedLines[7];
|
|
String displayedLastStops[7];
|
|
String displayedDepartureTimes[7];
|
|
|
|
String displayedTime;
|
|
String displayedDate;
|
|
|
|
// Display functions
|
|
|
|
void displayTimeDate() {
|
|
char date[20];
|
|
char time[20];
|
|
unsigned long timestamp = unixtime + seconds;
|
|
unixToDate(timestamp, date);
|
|
unixToTime(timestamp, time);
|
|
int lines = 7;
|
|
if(displayedDate != date) {
|
|
displayedDate = date;
|
|
M5.Lcd.setTextDatum(TL_DATUM);
|
|
M5.Lcd.setTextColor(TFT_WHITE, TFT_BLACK);
|
|
M5.Lcd.fillRect(0, lines*30, 160, 30, BLACK);
|
|
M5.Lcd.drawString(String(date), 0, lines*30+5);
|
|
}
|
|
if(displayedTime != time) {
|
|
displayedTime = time;
|
|
M5.Lcd.setTextDatum(TR_DATUM);
|
|
M5.Lcd.setTextColor(TFT_WHITE, TFT_BLACK);
|
|
M5.Lcd.fillRect(160, lines*30, 160, 30, BLACK);
|
|
M5.Lcd.drawString(String(time), 320, lines*30+5);
|
|
}
|
|
}
|
|
|
|
void displayLine(String line, int type, int y) {
|
|
if(displayedLines[y] == line) return;
|
|
displayedLines[y] = line;
|
|
uint16_t color = 0xFFFFFF;
|
|
if(type == 1) color = 0xFFEA;
|
|
if(type == 2) color = 0x6FF0;
|
|
if(type == 3) color = 0xFAAD;
|
|
M5.Lcd.setTextDatum(TL_DATUM);
|
|
M5.Lcd.setTextColor(color, TFT_BLACK);
|
|
M5.Lcd.fillRect(0, y*30, 40, 30, BLACK);
|
|
M5.Lcd.drawString(line, 0, y*30);
|
|
}
|
|
|
|
void displayLastStop(String lastStop, int y) {
|
|
if(displayedLastStops[y] == lastStop) return;
|
|
displayedLastStops[y] = lastStop;
|
|
M5.Lcd.setTextDatum(TL_DATUM);
|
|
M5.Lcd.setTextColor(TFT_WHITE, TFT_BLACK);
|
|
M5.Lcd.fillRect(40, y*30, 255, 30, BLACK);
|
|
M5.Lcd.drawString(lastStop, 40, y*30);
|
|
}
|
|
|
|
void displayDepartureTime(String departure, int y) {
|
|
if(displayedDepartureTimes[y] == departure) return;
|
|
displayedDepartureTimes[y] = departure;
|
|
M5.Lcd.setTextDatum(TR_DATUM);
|
|
M5.Lcd.setTextColor(TFT_WHITE, TFT_BLACK);
|
|
M5.Lcd.fillRect(290, y*30, 30, 30, BLACK);
|
|
M5.Lcd.drawString(departure, 320, y*30);
|
|
}
|
|
|
|
void displayDeparture(struct departure d, int y) {
|
|
|
|
displayLine(d.line, d.type, y);
|
|
displayLastStop(d.last_stop, y);
|
|
String departure = "<1";
|
|
if(d.departure >= 10) departure = String(d.departure/10);
|
|
displayDepartureTime(departure, y);
|
|
|
|
}
|
|
|
|
void displayDepartures() {
|
|
String displayed[50];
|
|
int lines = 7;
|
|
int displayedIndex = 0;
|
|
for(int i=0; i < departureIndex; i++) {
|
|
if(departures[i].departure < -3) removeDeparture(i);
|
|
departures[i].departure -= 1;
|
|
if(displayedIndex < lines) {
|
|
String lineLastStop = departures[i].line + departures[i].last_stop;
|
|
if(!stringInArray(lineLastStop, displayed, displayedIndex) && departures[i].departure < 999) {
|
|
displayed[displayedIndex] = lineLastStop;
|
|
displayDeparture(departures[i], displayedIndex);
|
|
displayedIndex++;
|
|
}
|
|
}
|
|
}
|
|
for(int i=displayedIndex; i<lines; i++) {
|
|
clearLine(i);
|
|
}
|
|
}
|
|
|
|
void clearLine(int y) {
|
|
displayedLines[y] = "";
|
|
displayedLastStops[y] = "";
|
|
displayedDepartureTimes[y] = "";
|
|
M5.Lcd.fillRect(0, y*30, 320, 30, BLACK);
|
|
}
|
|
|
|
void clearDisplayCache() {
|
|
for(int y=0; y<7; y++) {
|
|
displayedLines[y] = "";
|
|
displayedLastStops[y] = "";
|
|
displayedDepartureTimes[y] = "";
|
|
}
|
|
displayedTime = "";
|
|
displayedDate = "";
|
|
}
|
|
|
|
|
|
|
|
|
|
// Departure functions
|
|
|
|
struct departure parseDeparture(String s) {
|
|
struct departure d;
|
|
d.id = split(s, '|', 0).toInt();
|
|
d.type = split(s, '|', 1).toInt();
|
|
d.line = split(s, '|', 2);
|
|
d.last_stop = split(s, '|', 3);
|
|
d.departure = split(s, '|', 4).toInt();
|
|
return d;
|
|
}
|
|
|
|
int departureComparation(const void* a,const void* b) {
|
|
departure* x = (departure*) a;
|
|
departure* y = (departure*) b;
|
|
return x->departure - y->departure;
|
|
}
|
|
|
|
void sortDepartures() {
|
|
qsort(departures, departureIndex, sizeof(departure), departureComparation);
|
|
}
|
|
|
|
void removeDeparture(int pos) {
|
|
for (int i = pos; i < departureIndex-1; i++) {
|
|
departures[i] = departures[i+1];
|
|
}
|
|
departureIndex--;
|
|
}
|
|
|
|
void clearDepartures() {
|
|
memset(departures, 0, sizeof(departures));
|
|
departureIndex = 0;
|
|
memset(displayedLines, 0, sizeof(displayedLines));
|
|
memset(displayedLastStops, 0, sizeof(displayedLastStops));
|
|
memset(displayedDepartureTimes, 0, sizeof(displayedDepartureTimes));
|
|
}
|
|
|
|
void addDeparture(String s) {
|
|
struct departure d = parseDeparture(s);
|
|
for(int i=0; i<departureIndex; i++) {
|
|
if(departures[i].id == d.id) {
|
|
if(d.line != "") departures[i].line = d.line;
|
|
if(d.type != 0) departures[i].type = d.type;
|
|
if(d.last_stop != "") departures[i].last_stop = d.last_stop;
|
|
if(d.departure != 0) departures[i].departure = d.departure;
|
|
return;
|
|
}
|
|
}
|
|
if(d.line == "" || d.last_stop == "") return;
|
|
departures[departureIndex] = d;
|
|
departureIndex++;
|
|
}
|
|
|
|
|
|
|
|
|
|
// Main loop with time counting
|
|
|
|
void loop() {
|
|
seconds = time(NULL);
|
|
if(connected && seconds - lastMessage > INACTIVITY_TIME) {
|
|
xTaskCreate(taskReconnect, "taskReconnect", 4096, NULL, 1, NULL);
|
|
}
|
|
if(seconds / 6 > lastUpdate) {
|
|
lastUpdate++;
|
|
sortDepartures();
|
|
displayDepartures();
|
|
displayTimeDate();
|
|
}
|
|
delay(900);
|
|
}
|
|
|
|
// Task for receiving LoRaWAN messages
|
|
|
|
void taskReceiving(void* params) {
|
|
while(true) {
|
|
if(connected) receiveMsg(1);
|
|
else delay(1000);
|
|
}
|
|
}
|
|
|
|
// Task for reconnecting
|
|
|
|
void taskReconnect(void* params) {
|
|
setupLoRaWAN();
|
|
vTaskDelete(NULL);
|
|
}
|