#include "M5Core2.h" #include #include "M5_LoRaWAN.h" #include #include #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; ideparture - 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 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); }