546 lines
No EOL
14 KiB
C++
546 lines
No EOL
14 KiB
C++
#include <Arduino.h>
|
|
|
|
#include <main.h>
|
|
|
|
SX1278 radio = new Module(LORA_SS, LORA_DIO0, LORA_RST);
|
|
POCSAGTransmitter transmitter;
|
|
|
|
cfg_t globcfg;
|
|
bool mqtt_enable = false;
|
|
bool wifi_hotspot = false;
|
|
bool old_tx_state = false;
|
|
char *timeString = new char[33]{};
|
|
//
|
|
//
|
|
//
|
|
void setup()
|
|
{
|
|
setup_leds();
|
|
|
|
delay(2e2);
|
|
Serial.begin(115200);
|
|
|
|
#ifdef SMART_POC_DISPLAY
|
|
display_setup();
|
|
#endif
|
|
setup_storage(&globcfg);
|
|
#ifdef SMART_POC_DISPLAY
|
|
display_update_state("loaded cfg");
|
|
#endif
|
|
// memdebug
|
|
Serial.print("Free Heap Size after reading Config:");
|
|
Serial.println(ESP.getFreeHeap());
|
|
|
|
Serial.println("starting up modem");
|
|
float bitrate = globcfg.tx_baud / 1000.0;
|
|
int state = radio.beginFSK(globcfg.tx_freq, bitrate, globcfg.tx_dev);
|
|
if (state == 0) {
|
|
Serial.println(F("success!"));
|
|
} else {
|
|
Serial.print(F("failed, code "));
|
|
Serial.println(state);
|
|
#ifdef SMART_POC_DISPLAY
|
|
display_update_state("modem fail");
|
|
#endif
|
|
// TODO: add solution to this
|
|
while (true) {
|
|
digitalWrite(PIN_LED, HIGH);
|
|
delay(850);
|
|
digitalWrite(PIN_LED, LOW);
|
|
delay(250);
|
|
}
|
|
}
|
|
//
|
|
radio.setOutputPower(globcfg.tx_power);
|
|
Serial.println("initialized modem");
|
|
#ifdef SMART_POC_DISPLAY
|
|
display_update_state("init modem");
|
|
#endif
|
|
transmitter.begin(&radio);
|
|
Serial.println("initialized transmitter");
|
|
#ifdef SMART_POC_DISPLAY
|
|
display_update_state("init pocsag-stack");
|
|
#endif
|
|
//
|
|
setup_network();
|
|
webserver_setup(&globcfg);
|
|
setup_mqtt();
|
|
setup_broadcasts();
|
|
// memdebug
|
|
Serial.print("Free Heap Size after setup:");
|
|
Serial.println(ESP.getFreeHeap());
|
|
// leds
|
|
digitalWrite(PIN_LED, LOW);
|
|
}
|
|
//
|
|
// Network: WiFi 1+2 or Hotspot-Mode
|
|
//
|
|
void setup_network()
|
|
{
|
|
WiFi.mode(WIFI_STA);
|
|
WiFi.setHostname(globcfg.device_name);
|
|
|
|
unsigned long connectTimeout = 10e3;
|
|
uint8_t connectResult;
|
|
bool wifi1valid = strlen(globcfg.wifi1_ssid) > 2;
|
|
bool wifi2valid = strlen(globcfg.wifi2_ssid) > 2;
|
|
//
|
|
if (wifi1valid)
|
|
{
|
|
#ifdef SMART_POC_DISPLAY
|
|
display_update_state("trying wifi 1");
|
|
#endif
|
|
Serial.println("connecting to WiFi 1");
|
|
WiFi.begin(globcfg.wifi1_ssid, globcfg.wifi1_pass);
|
|
while ((WiFi.status() != WL_CONNECTED) && (millis() < 10000))
|
|
{
|
|
delay(10);
|
|
}
|
|
connectResult = WiFi.status();
|
|
Serial.printf("result=%d\n", connectResult);
|
|
if (connectResult == WL_CONNECTED)
|
|
{
|
|
Serial.println(WiFi.localIP());
|
|
return;
|
|
}
|
|
// while (WiFi.status() != WL_CONNECTED)
|
|
// {
|
|
// delay(500);
|
|
// Serial.print(".");
|
|
// }
|
|
}
|
|
if (wifi2valid)
|
|
{
|
|
#ifdef SMART_POC_DISPLAY
|
|
display_update_state("trying wifi 2");
|
|
#endif
|
|
Serial.println("connecting to WiFi 2");
|
|
WiFi.begin(globcfg.wifi2_ssid, globcfg.wifi2_pass);
|
|
while ((WiFi.status() != WL_CONNECTED) && (millis() < 22000))
|
|
{
|
|
delay(10);
|
|
}
|
|
connectResult = WiFi.status();
|
|
Serial.printf("result=%d\n", connectResult);
|
|
if (connectResult == WL_CONNECTED)
|
|
{
|
|
Serial.println(WiFi.localIP());
|
|
return;
|
|
}
|
|
}
|
|
//
|
|
#ifdef SMART_POC_DISPLAY
|
|
display_update_state("init hotspot mode");
|
|
#endif
|
|
Serial.println("wifi failed to connect, going AP");
|
|
WiFi.mode(WIFI_AP);
|
|
WiFi.softAP(globcfg.ap_ssid, globcfg.ap_pass);
|
|
wifi_hotspot = true;
|
|
}
|
|
//
|
|
// MQTT
|
|
//
|
|
WiFiClient wifiMqttClient;
|
|
PubSubClient mqttClient(wifiMqttClient);
|
|
String clear_topic, transmit_topic, state_topic;
|
|
void setup_mqtt()
|
|
{
|
|
if (wifi_hotspot)
|
|
{
|
|
Serial.println("hotspot prevents mqtt");
|
|
return;
|
|
} // dont
|
|
mqtt_enable = strlen(globcfg.mqtt_host) > 5;
|
|
if (!mqtt_enable)
|
|
{
|
|
Serial.println("MQTT not wanted, exiting setup");
|
|
return;
|
|
} // if we should not do this
|
|
mqttClient.setServer(globcfg.mqtt_host, globcfg.mqtt_port);
|
|
#ifdef SMART_POC_DISPLAY
|
|
display_update_state("connecting to MQTT");
|
|
#endif
|
|
Serial.printf("connecting to mqtt %s\n", globcfg.mqtt_host);
|
|
if (mqttClient.connect(globcfg.mqtt_user, globcfg.mqtt_user, globcfg.mqtt_pass))
|
|
{
|
|
// succ
|
|
mqttClient.setCallback(mqtt_callback);
|
|
//
|
|
transmit_topic = String(globcfg.mqtt_topic);
|
|
transmit_topic.concat("/transmit");
|
|
mqttClient.subscribe(transmit_topic.c_str());
|
|
|
|
clear_topic = String(globcfg.mqtt_topic);
|
|
clear_topic.concat("/clear");
|
|
mqttClient.subscribe(clear_topic.c_str());
|
|
|
|
state_topic = String(globcfg.mqtt_topic);
|
|
state_topic.concat("/state");
|
|
mqttClient.publish(state_topic.c_str(), "false");
|
|
//
|
|
Serial.println("connected to mqtt");
|
|
}
|
|
else
|
|
{
|
|
Serial.printf("failed to connect to mqtt, state= %d\n", mqttClient.state());
|
|
}
|
|
}
|
|
void mqtt_callback(char *topic, byte *payload, unsigned int length)
|
|
{
|
|
JsonDocument doc;
|
|
deserializeJson(doc, payload);
|
|
if (clear_topic.equals(topic))
|
|
{
|
|
txControllerBatchClear();
|
|
return;
|
|
}
|
|
Serial.println("rx mqtt");
|
|
|
|
if (doc["ric"].is<int>() && doc["msg"].is<String>())
|
|
{
|
|
String text = doc["msg"].as<String>();
|
|
text = txHandleUmlauts(text);
|
|
int addr = doc["ric"].as<int>();
|
|
int func = 3;
|
|
if (doc["fun"].is<int>())
|
|
{
|
|
func = doc["fun"].as<int>();
|
|
}
|
|
char *messageText = new char[text.length() + 1]{};
|
|
text.toCharArray(messageText, text.length() + 1);
|
|
txControllerInsert((uint32_t)addr, (uint8_t)func, messageText);
|
|
delete []messageText;
|
|
// if (transmitter.isActive() == false)
|
|
// {
|
|
// if (mqtt_enable)
|
|
// {
|
|
// mqttClient.publish(state_topic.c_str(), "true");
|
|
// }
|
|
// }
|
|
txControllerBatchStart();
|
|
}
|
|
}
|
|
void mqtt_loop(bool tx_state) {
|
|
mqttClient.loop();
|
|
if (old_tx_state != tx_state) {
|
|
if (tx_state)
|
|
mqttClient.publish(state_topic.c_str(), "true");
|
|
else
|
|
mqttClient.publish(state_topic.c_str(), "false");
|
|
}
|
|
}
|
|
//
|
|
// LEDs
|
|
//
|
|
void setup_leds() {
|
|
pinMode(PIN_LED, OUTPUT);
|
|
digitalWrite(PIN_LED, HIGH);
|
|
}
|
|
void leds_loop(bool tx_state) {
|
|
if (old_tx_state != tx_state)
|
|
digitalWrite(PIN_LED, tx_state);
|
|
}
|
|
//
|
|
//
|
|
//
|
|
void serlog_loop(bool tx_state) {
|
|
if (tx_state != old_tx_state)
|
|
if (tx_state)
|
|
Serial.println("Transmitter Active");
|
|
else
|
|
Serial.println("Transmitter Standby");
|
|
}
|
|
//
|
|
//
|
|
//
|
|
//
|
|
void loop()
|
|
{
|
|
bool tx_state = transmitter.isActive();
|
|
#ifdef SMART_POC_DISPLAY
|
|
display_loop(tx_state, &transmitter, &globcfg, timeString);
|
|
#endif
|
|
if (old_tx_state == true && tx_state == false && globcfg.tx_empty_queue)
|
|
txControllerBatchClear();
|
|
//
|
|
serlog_loop(tx_state);
|
|
leds_loop(tx_state);
|
|
if (mqtt_enable)
|
|
mqtt_loop(tx_state);
|
|
|
|
broadcast_loop();
|
|
heap_loop();
|
|
old_tx_state = tx_state;
|
|
}
|
|
//
|
|
// TX Stack
|
|
//
|
|
struct UmlautMap {
|
|
char utf8;
|
|
char replacement;
|
|
};
|
|
UmlautMap umlautMapGermany[] = {
|
|
{0xDC, 0x5D}, {0xC4, 0x5B}, {0xD6, 0x5C},
|
|
{0xFC, 0x7D}, {0xE4, 0x7B}, {0xF6, 0x7C},
|
|
{0xDF, 0x7E}
|
|
};
|
|
UmlautMap umlautMapIntl[] = {
|
|
{0xDC, 0x56}, {0xC4, 0x41}, {0xD6, 0x4F},
|
|
{0xFC, 0x75}, {0xE4, 0x61}, {0xF6, 0x6F},
|
|
{0xDF, 0x73}
|
|
};
|
|
#ifdef SMART_POC_DEBUG_UMLAUT_HEX
|
|
void printHex(const char* text) {
|
|
while (*text) {
|
|
if ((uint8_t)*text < 0x10) {
|
|
Serial.print("0"); // Ensure two-digit hex format
|
|
}
|
|
Serial.print((uint8_t)*text, HEX);
|
|
Serial.print(" ");
|
|
text++;
|
|
}
|
|
Serial.println();
|
|
}
|
|
#endif
|
|
void replaceStringWithChar(String &input, const String &find, char replace) {
|
|
int index = input.indexOf(find); // Find the first occurrence
|
|
while (index != -1) {
|
|
input = input.substring(0, index) + replace + input.substring(index + find.length());
|
|
index = input.indexOf(find, index + 1); // Find next occurrence
|
|
}
|
|
}
|
|
|
|
String txHandleUmlauts(String input) {
|
|
bool useGermanMap = globcfg.pocsag_german;
|
|
UmlautMap* map = useGermanMap ? umlautMapGermany : umlautMapIntl;
|
|
int mapSize = useGermanMap ? sizeof(umlautMapGermany) / sizeof(UmlautMap)
|
|
: sizeof(umlautMapIntl) / sizeof(UmlautMap);
|
|
for (int i = 0; i < mapSize; i++) {
|
|
input.replace(map[i].utf8, map[i].replacement);
|
|
}
|
|
if (useGermanMap) {
|
|
replaceStringWithChar(input, "Ä", '[');
|
|
replaceStringWithChar(input, "Ö", '\\');
|
|
replaceStringWithChar(input, "Ü", ']');
|
|
replaceStringWithChar(input, "ä", '{');
|
|
replaceStringWithChar(input, "ö", '|');
|
|
replaceStringWithChar(input, "ü", '}');
|
|
replaceStringWithChar(input, "ß", '~');
|
|
} else {
|
|
replaceStringWithChar(input, "Ä", 'A');
|
|
replaceStringWithChar(input, "Ö", 'O');
|
|
replaceStringWithChar(input, "Ü", 'U');
|
|
replaceStringWithChar(input, "ä", 'a');
|
|
replaceStringWithChar(input, "ö", 'o');
|
|
replaceStringWithChar(input, "ü", 'u');
|
|
replaceStringWithChar(input, "ß", 's');
|
|
}
|
|
return input;
|
|
}
|
|
void txControllerInsert(uint32_t ric, uint8_t functionBit, char *text)
|
|
{
|
|
transmitter.queuePage(ric, functionBit, text);
|
|
}
|
|
void txControllerInsert(uint32_t ric, uint8_t functionBit, bool numeric, char *text)
|
|
{
|
|
transmitter.queuePage(ric, functionBit, numeric, text);
|
|
}
|
|
void txControllerBatchStart()
|
|
{
|
|
transmitter.transmitBatch();
|
|
}
|
|
void txControllerBatchClear()
|
|
{
|
|
transmitter.clearQueue();
|
|
}
|
|
int txControllerBatchQueueCount()
|
|
{
|
|
return transmitter.getQueueCount();
|
|
}
|
|
//
|
|
// DWD + MoWaS + Time + Idle
|
|
//
|
|
WiFiClient wifiHttpClient;
|
|
#ifdef SMART_POC_MODULE_DWD
|
|
DWDClient dwdClient(&wifiHttpClient);
|
|
#endif
|
|
#ifdef SMART_POC_MODULE_DWD_WX
|
|
DWDWXClient dwdWXClient(&wifiHttpClient);
|
|
#endif
|
|
#ifdef SMART_POC_MODULE_MOWAS
|
|
MoWaSClient mowasClient(&wifiHttpClient);
|
|
#endif
|
|
|
|
Timezone utcTime;
|
|
Timezone localTime;
|
|
bool time_beacon_first = true;
|
|
unsigned long lastTimeBeaconTime = 0;
|
|
|
|
String currentMessage;
|
|
String formattedTime;
|
|
char *idleBeaconText = new char[1]{};
|
|
bool idle_beacon_first = true;
|
|
unsigned long lastIdleBeaconTime = 0;
|
|
//
|
|
void setup_broadcasts()
|
|
{
|
|
if (wifi_hotspot) {
|
|
mqtt_enable = false;
|
|
globcfg.dwd_enable = false;
|
|
globcfg.mowas_enable = false;
|
|
globcfg.time_enable = false;
|
|
}
|
|
#ifdef SMART_POC_MODULE_DWD
|
|
// DWD
|
|
if (globcfg.dwd_interval < 1)
|
|
globcfg.dwd_enable = false;
|
|
if (globcfg.dwd_enable)
|
|
dwdClient.begin(globcfg.dwd_interval, globcfg.dwd_region);
|
|
#endif
|
|
#ifdef SMART_POC_MODULE_DWD_WX
|
|
// DWD
|
|
if (globcfg.dwdwx_enable)
|
|
dwdWXClient.begin(60*6, globcfg.dwdwx_lat, globcfg.dwdwx_lng);
|
|
#endif
|
|
#ifdef SMART_POC_MODULE_MOWAS
|
|
// MoWaS
|
|
if (globcfg.mowas_interval < 1)
|
|
globcfg.mowas_enable = false;
|
|
if (globcfg.mowas_enable)
|
|
mowasClient.begin(globcfg.mowas_interval, globcfg.mowas_region);
|
|
#endif
|
|
// Time
|
|
if (globcfg.time_enable) {
|
|
waitForSync(30e3);
|
|
localTime.setLocation(globcfg.time_zone);
|
|
}
|
|
}
|
|
void broadcast_loop()
|
|
{
|
|
#ifdef SMART_POC_MODULE_DWD
|
|
if (globcfg.dwd_enable)
|
|
{
|
|
dwdClient.loop();
|
|
if (dwdClient.isDirty())
|
|
{
|
|
currentMessage = txHandleUmlauts(dwdClient.currentMessage);
|
|
#ifdef SMART_POC_DEBUG_UMLAUT_HEX
|
|
Serial.print("Hex: ");
|
|
printHex(currentMessage.c_str());
|
|
#endif
|
|
char *messageText = new char[currentMessage.length() + 1]{};
|
|
currentMessage.toCharArray(messageText, currentMessage.length() + 1);
|
|
Serial.println(F("[DWD] Transmitting Warning"));
|
|
Serial.println(messageText);
|
|
transmitter.queuePage(globcfg.broadcast_ric, globcfg.dwd_fun + 0, messageText);
|
|
delete []messageText;
|
|
dwdClient.currentMessage.clear();
|
|
currentMessage.clear();
|
|
transmitter.transmitBatch();
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef SMART_POC_MODULE_DWD_WX
|
|
if (globcfg.dwdwx_enable)
|
|
{
|
|
dwdWXClient.loop();
|
|
if (dwdWXClient.isDirty())
|
|
{
|
|
currentMessage = txHandleUmlauts(dwdWXClient.currentMessage);
|
|
#ifdef SMART_POC_DEBUG_UMLAUT_HEX
|
|
Serial.print("Hex: ");
|
|
printHex(currentMessage.c_str());
|
|
#endif
|
|
char *messageText = new char[currentMessage.length() + 1]{};
|
|
currentMessage.toCharArray(messageText, currentMessage.length() + 1);
|
|
Serial.println(F("[DWD-WX] Transmitting"));
|
|
transmitter.queuePage(globcfg.broadcast_ric, globcfg.dwdwx_fun + 0, messageText);
|
|
delete []messageText;
|
|
dwdWXClient.currentMessage.clear();
|
|
currentMessage.clear();
|
|
transmitter.transmitBatch();
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef SMART_POC_MODULE_MOWAS
|
|
if (globcfg.mowas_enable)
|
|
{
|
|
mowasClient.loop();
|
|
if (mowasClient.isDirty())
|
|
{
|
|
currentMessage = txHandleUmlauts(mowasClient.currentMessage);
|
|
#ifdef SMART_POC_DEBUG_UMLAUT_HEX
|
|
Serial.print("Hex: ");
|
|
printHex(currentMessage.c_str());
|
|
#endif
|
|
char *messageText = new char[currentMessage.length() + 1]{};
|
|
currentMessage.toCharArray(messageText, currentMessage.length() + 1);
|
|
Serial.println(F("[MoWaS] Transmitting Warning"));
|
|
Serial.println(messageText);
|
|
transmitter.queuePage(globcfg.broadcast_ric, globcfg.mowas_fun + 0, messageText);
|
|
delete []messageText;
|
|
currentMessage.clear();
|
|
transmitter.transmitBatch();
|
|
}
|
|
}
|
|
#endif
|
|
if (globcfg.time_enable) {
|
|
events();
|
|
|
|
// re-use this var ;)
|
|
formattedTime = utcTime.dateTime("Y-m-d H:i:s");
|
|
formattedTime.toCharArray(timeString, formattedTime.length() + 1);
|
|
|
|
if (time_beacon_first || millis() - lastTimeBeaconTime >= globcfg.time_interval * 60e3) {
|
|
lastTimeBeaconTime = millis();
|
|
time_beacon_first = false;
|
|
switch (globcfg.time_mode) {
|
|
case 0: // TPL-Eng SubRIC 1(B) @ Service OTA
|
|
formattedTime = "***1***" + utcTime.dateTime("Hidmy");
|
|
break;
|
|
case 1: // TPL-Ger
|
|
formattedTime = "#ZEIT=" + localTime.dateTime("Hidmy") + "#ZEIT=" + localTime.dateTime("Hidmy");
|
|
break;
|
|
case 2: // Shitphone
|
|
formattedTime = ";TIME=" + localTime.dateTime("Hidmy") + ";TIME=" + localTime.dateTime("Hidmy");
|
|
break;
|
|
}
|
|
char *messageText = new char[formattedTime.length() + 1]{};
|
|
formattedTime.toCharArray(messageText, formattedTime.length() + 1);
|
|
Serial.println(F("[Time] Transmitting Time"));
|
|
transmitter.queuePage(globcfg.time_ric, globcfg.time_fun + 0, messageText);
|
|
delete []messageText;
|
|
formattedTime.clear();
|
|
transmitter.transmitBatch();
|
|
}
|
|
}
|
|
if (globcfg.idle_enable) {
|
|
if (idle_beacon_first || millis() - lastIdleBeaconTime >= globcfg.idle_interval * 60e3) {
|
|
lastIdleBeaconTime = millis();
|
|
idle_beacon_first = false;
|
|
Serial.println(F("[Idle] Transmitting Beacon"));
|
|
transmitter.queuePage(0, 0, idleBeaconText);
|
|
transmitter.transmitBatch();
|
|
}
|
|
}
|
|
}
|
|
//
|
|
void broadcastTriggerTimeBeacon() {
|
|
time_beacon_first = true;
|
|
}
|
|
//
|
|
// Heap Management Logs
|
|
//
|
|
unsigned long lastReportTime = 0;
|
|
const unsigned long reportInterval = 60000;
|
|
void heap_loop()
|
|
{
|
|
if (millis() - lastReportTime >= reportInterval)
|
|
{
|
|
lastReportTime = millis();
|
|
Serial.print(F("Free Heap Size: "));
|
|
Serial.print(ESP.getFreeHeap());
|
|
Serial.print(F("Free min Heap Size: "));
|
|
Serial.println(ESP.getMinFreeHeap());
|
|
}
|
|
} |