diff --git a/multigeiger/display.cpp b/multigeiger/display.cpp index b9abe1e..944817b 100644 --- a/multigeiger/display.cpp +++ b/multigeiger/display.cpp @@ -87,9 +87,10 @@ static const char *status_chars[STATUS_MAX] = { ".t3T?", // ST_TTN_OFF, ST_TTN_IDLE, ST_TTN_ERROR, ST_TTN_SENDING, ST_TTN_INIT // group BlueTooth ".B4b?", // ST_BLE_OFF, ST_BLE_CONNECTED, ST_BLE_ERROR, ST_BLE_CONNECTABLE, ST_BLE_INIT + // group Telegram + ".g5G?", // ST_TELEGRAM_OFF, ST_TELEGRAM_IDLE, ST_TELEGRAM_ERROR, ST_TELEGRAM_SENDING, ST_TELEGRAM_INIT // group other ".", // ST_NODISPLAY - ".", // ST_NODISPLAY ".H7", // ST_NODISPLAY, ST_HV_OK, ST_HV_ERROR }; diff --git a/multigeiger/display.h b/multigeiger/display.h index 26c67be..9fc3eeb 100644 --- a/multigeiger/display.h +++ b/multigeiger/display.h @@ -51,7 +51,12 @@ void display_statusline(String txt); #define ST_BLE_CONNECTABLE 3 #define ST_BLE_INIT 4 -// status index 5 is still free +#define STATUS_TELEGRAM 5 +#define ST_TELEGRAM_OFF 0 +#define ST_TELEGRAM_IDLE 1 +#define ST_TELEGRAM_ERROR 2 +#define ST_TELEGRAM_SENDING 3 +#define ST_TELEGRAM_INIT 4 // status index 6 is still free diff --git a/multigeiger/multigeiger.ino b/multigeiger/multigeiger.ino index 8031f20..a3cdab2 100644 --- a/multigeiger/multigeiger.ino +++ b/multigeiger/multigeiger.ino @@ -37,7 +37,7 @@ // DIP switches static Switches switches; - +float accumulated_Count_Rate = 0.0, accumulated_Dose_Rate = 0.0; void setup() { bool isLoraBoard = init_hwtest(); @@ -107,14 +107,13 @@ int update_ble_status(void) { // currently no error detection } void publish(unsigned long current_ms, unsigned long current_counts, unsigned long gm_count_timestamp, unsigned long current_hv_pulses, - float temperature, float humidity, float pressure) { + bool have_thp, float temperature, float humidity, float pressure, int wifi_status) { static unsigned long last_timestamp = millis(); static unsigned long last_counts = 0; static unsigned long last_hv_pulses = 0; static unsigned long last_count_timestamp = 0; static unsigned int accumulated_GMC_counts = 0; static unsigned long accumulated_time = 0; - static float accumulated_Count_Rate = 0.0, accumulated_Dose_Rate = 0.0; if (((current_counts - last_counts) >= MINCOUNTS) || ((current_ms - last_timestamp) >= DISPLAYREFRESH)) { if ((gm_count_timestamp == 0) && (last_count_timestamp == 0)) { @@ -149,13 +148,21 @@ void publish(unsigned long current_ms, unsigned long current_counts, unsigned lo (showDisplay && switches.display_on)); // Sound local alarm? - if (soundLocalAlarm && GMC_factor_uSvph > 0) { - if (accumulated_Dose_Rate > localAlarmThreshold) { - log(WARNING, "Local alarm: Accumulated dose of %.3f µSv/h above threshold at %.3f µSv/h", accumulated_Dose_Rate, localAlarmThreshold); - alarm(); - } else if (Dose_Rate > (accumulated_Dose_Rate * localAlarmFactor)) { - log(WARNING, "Local alarm: Current dose of %.3f > %d x accumulated dose of %.3f µSv/h", Dose_Rate, localAlarmFactor, accumulated_Dose_Rate); - alarm(); + if ((soundLocalAlarm || sendLocalAlarmToMessenger) && GMC_factor_uSvph > 0) { + if ((accumulated_Dose_Rate > localAlarmThreshold) || (Dose_Rate > (accumulated_Dose_Rate * localAlarmFactor))) { + if (accumulated_Dose_Rate > localAlarmThreshold) { + log(WARNING, "Local alarm: Accumulated dose of %.3f µSv/h above threshold at %.3f µSv/h", accumulated_Dose_Rate, localAlarmThreshold); + } else { + log(WARNING, "Local alarm: Current dose of %.3f > %d x accumulated dose of %.3f µSv/h", Dose_Rate, localAlarmFactor, accumulated_Dose_Rate); + } + if (soundLocalAlarm) { + alarm(); + } + if (sendLocalAlarmToMessenger) { + transmit_userinfo(tubes[TUBE_TYPE].type, tubes[TUBE_TYPE].nbr, tubes[TUBE_TYPE].cps_to_uSvph, + (unsigned int)(Count_Rate * 60), (unsigned int)(accumulated_Count_Rate * 60), accumulated_Dose_Rate, + have_thp, temperature, humidity, pressure, wifi_status, true); + } } } @@ -236,8 +243,12 @@ void transmit(unsigned long current_ms, unsigned long current_counts, unsigned l log(DEBUG, "Measured GM: cpm= %d HV=%d", current_cpm, hv_pulses); - transmit_data(tubes[TUBE_TYPE].type, tubes[TUBE_TYPE].nbr, dt, hv_pulses, counts, current_cpm, + transmit_data(tubes[TUBE_TYPE].type, tubes[TUBE_TYPE].nbr, + dt, hv_pulses, counts, current_cpm, have_thp, temperature, humidity, pressure, wifi_status); + transmit_userinfo(tubes[TUBE_TYPE].type, tubes[TUBE_TYPE].nbr, tubes[TUBE_TYPE].cps_to_uSvph, + current_cpm, accumulated_Count_Rate, accumulated_Dose_Rate, + have_thp, temperature, humidity, pressure, wifi_status, false); } } @@ -283,7 +294,7 @@ void loop() { // do any other periodic updates for uplinks poll_transmission(); - publish(current_ms, gm_counts, gm_count_timestamp, hv_pulses, temperature, humidity, pressure); + publish(current_ms, gm_counts, gm_count_timestamp, hv_pulses, have_thp, temperature, humidity, pressure, wifi_status); if (Serial_Print_Mode == Serial_One_Minute_Log) one_minute_log(current_ms, gm_counts); diff --git a/multigeiger/transmission.cpp b/multigeiger/transmission.cpp index fc278f6..7370572 100644 --- a/multigeiger/transmission.cpp +++ b/multigeiger/transmission.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "log.h" #include "display.h" @@ -41,7 +42,8 @@ typedef struct https_client { HTTPClient *hc; } HttpsClient; -static HttpsClient c_madavi, c_sensorc, c_customsrv; +static HttpsClient c_madavi, c_sensorc, c_customsrv, c_telegram; +UniversalTelegramBot *telegram_bot; void setup_transmission(const char *version, char *ssid, bool loraHardware) { chipID = String(ssid); @@ -69,9 +71,22 @@ void setup_transmission(const char *version, char *ssid, bool loraHardware) { c_customsrv.wc->setCACert(ca_certs); c_customsrv.hc = new HTTPClient; + if ((strlen(telegramBotToken) < 40) || (strlen(telegramChatId) < 7)) + sendDataToMessengerEvery = -1; + + c_telegram.wc = new WiFiClientSecure; + c_telegram.wc->setCACert(TELEGRAM_CERTIFICATE_ROOT); + c_telegram.hc = new HTTPClient; + + if (sendDataToMessengerEvery >= 0) { + log(DEBUG, "Starting Telegram Bot..."); + telegram_bot = new UniversalTelegramBot(telegramBotToken, *(c_telegram.wc)); + } + set_status(STATUS_SCOMM, sendToCommunity ? ST_SCOMM_INIT : ST_SCOMM_OFF); set_status(STATUS_MADAVI, sendToMadavi ? ST_MADAVI_INIT : ST_MADAVI_OFF); set_status(STATUS_TTN, sendToLora ? ST_TTN_INIT : ST_TTN_OFF); + set_status(STATUS_TELEGRAM, (sendDataToMessengerEvery >= 0) ? ST_TELEGRAM_INIT : ST_TELEGRAM_OFF); } void poll_transmission() { @@ -142,7 +157,7 @@ int send_http_geiger(HttpsClient *client, const char *host, unsigned int timedif int send_http_thp(HttpsClient *client, const char *host, float temperature, float humidity, float pressure, int xpin) { char body[1000]; prepare_http(client, host); - if(xpin != XPIN_NO_XPIN) { + if (xpin != XPIN_NO_XPIN) { client->hc->addHeader("X-PIN", String(xpin)); } const char *json_format = R"=====( @@ -257,7 +272,7 @@ void transmit_data(String tube_type, int tube_nbr, unsigned int dt, unsigned int log(INFO, "Sent to CUSTOMSRV, status: %s, http: %d %d", customsrv_ok ? "ok" : "error", rc1, rc2); #endif - if(sendToMadavi && (wifi_status == ST_WIFI_CONNECTED)) { + if (sendToMadavi && (wifi_status == ST_WIFI_CONNECTED)) { bool madavi_ok; log(INFO, "Sending to Madavi ..."); set_status(STATUS_MADAVI, ST_MADAVI_SENDING); @@ -271,7 +286,7 @@ void transmit_data(String tube_type, int tube_nbr, unsigned int dt, unsigned int display_status(); } - if(sendToCommunity && (wifi_status == ST_WIFI_CONNECTED)) { + if (sendToCommunity && (wifi_status == ST_WIFI_CONNECTED)) { bool scomm_ok; log(INFO, "Sending to sensor.community ..."); set_status(STATUS_SCOMM, ST_SCOMM_SENDING); @@ -285,7 +300,7 @@ void transmit_data(String tube_type, int tube_nbr, unsigned int dt, unsigned int display_status(); } - if(isLoraBoard && sendToLora && (strcmp(appeui, "") != 0)) { // send only, if we have LoRa credentials + if (isLoraBoard && sendToLora && (strcmp(appeui, "") != 0)) { // send only, if we have LoRa credentials bool ttn_ok; log(INFO, "Sending to TTN ..."); set_status(STATUS_TTN, ST_TTN_SENDING); @@ -296,5 +311,59 @@ void transmit_data(String tube_type, int tube_nbr, unsigned int dt, unsigned int set_status(STATUS_TTN, ttn_ok ? ST_TTN_IDLE : ST_TTN_ERROR); display_status(); } + +} + +void transmit_userinfo(String tube_type, int tube_nbr, float tube_factor, unsigned int cpm, unsigned int accu_cpm, float accu_rate, + int have_thp, float temperature, float humidity, float pressure, int wifi_status, bool alarm_status) { + + if (wifi_status != ST_WIFI_CONNECTED) + return; + + // if we don't have a valid Messenger config, do not send to Messenger + if (sendDataToMessengerEvery < 0) + return; + // in case the config has been changed in the meantime + if ((strlen(telegramBotToken) < 40) || (strlen(telegramChatId) < 7)) { + sendDataToMessengerEvery = -1; + return; + } + + static unsigned int transmitCounter = 0; + transmitCounter++; + if (transmitCounter == 27720) transmitCounter = 0; // 27720 % 1..12 == 0 + + char localEspId[16]; + char thp_text[60]; + + chipID.toCharArray(localEspId, sizeof(localEspId)); + if (have_thp) { + sprintf(thp_text, "\nBME data: %.1fC %.1f%% %.1fhPa", temperature, humidity, pressure/100); + } + + if (((sendDataToMessengerEvery > 0) && (transmitCounter % sendDataToMessengerEvery == 0)) + || ((sendDataToMessengerEvery >= 0) && alarm_status)) { + bool telegram_ok; + char message[120]; + log(INFO, "Sending to Telegram messenger ..."); + set_status(STATUS_TELEGRAM, ST_TELEGRAM_SENDING); + display_status(); + if (alarm_status) { + if (tube_nbr > 0) + sprintf(message, "--- MULTIGEIGER ALERT ! ---\n%s rate too high:\n%.2f nSv/h (accumulated: %.2f nSv/h)", localEspId, cpm*tube_factor*1000/60, accu_rate*1000); + else + sprintf(message, "--- MULTIGEIGER ALERT ! ---\n%s rate too high:\n%d (accumulated: %d)", localEspId, cpm, accu_cpm); + } else { + if (tube_nbr > 0) + sprintf(message, "MultiGeiger %s rates:\n%.2f nSv/h (accumulated: %.2f nSv/h)%s", localEspId, cpm*tube_factor*1000/60, accu_rate*1000, have_thp ? thp_text : ""); + else + sprintf(message, "MultiGeiger %s CPM:\n%d (accumulated: %d)%s", localEspId, cpm, accu_cpm, have_thp ? thp_text : ""); + } + telegram_ok = telegram_bot->sendMessage(telegramChatId, message, "HTML"); + log(INFO, "Sent to Telegram messenger, status: %s", telegram_ok ? "ok" : "error"); + set_status(STATUS_TELEGRAM, telegram_ok ? ST_TELEGRAM_IDLE : ST_TELEGRAM_ERROR); + display_status(); + } + } diff --git a/multigeiger/transmission.h b/multigeiger/transmission.h index 428d18d..185102d 100644 --- a/multigeiger/transmission.h +++ b/multigeiger/transmission.h @@ -17,6 +17,8 @@ void setup_transmission(const char *version, char *ssid, bool lora); void transmit_data(String tube_type, int tube_nbr, unsigned int dt, unsigned int hv_pulses, unsigned int gm_counts, unsigned int cpm, int have_thp, float temperature, float humidity, float pressure, int wifi_status); +void transmit_userinfo(String tube_type, int tube_nbr, float tube_factor, unsigned int cpm, unsigned int accu_cpm, float accu_rate, + int have_thp, float temperature, float humidity, float pressure, int wifi_status, bool alarm_status); // The Arduino LMIC wants to be polled from loop(). This takes care of that on LoRa boards. void poll_transmission(void); diff --git a/multigeiger/userdefines-example.h b/multigeiger/userdefines-example.h index def914b..d840d23 100644 --- a/multigeiger/userdefines-example.h +++ b/multigeiger/userdefines-example.h @@ -82,3 +82,25 @@ // If current dose rate rises to (default) 3 times the accumulated dose rate, i.e. 0.3 µSv/h, trigger the local alarm. // ! Requires a valid tube type to be set in order to calculate dose rate. #define LOCAL_ALARM_FACTOR 3 // current / accumulated dose rate + +// Send MultiGeiger info and alerts to Messaging services. Currently supported: +// - Telegram Messenger +// REQUIRES WIFI CONNECTION! +// Update via Messenger every N sensor.community messages (default 150 s / 2.5 min). +// Set to 0 to disable normal data transfer. +// 24 = 1/hour, 576 = 1/day, 4032 = 1/week, max: 27719 (~48 days) +#define SEND_DATA_TO_MESSENGER_EVERY 0 + +// Send local alerts via supported Messenger services. +// See above for more info on local alerts. +#define SEND_LOCAL_ALARM_TO_MESSENGER true + +///// TELEGRAM MESSENGER +// To communicate with the Telegram Messenger on your phone you need to create a bot. +// Starting point: https://core.telegram.org/bots +// You will get a Bot token, please provide this via Web Config. +// Form: "XXXXXXXXXX:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +// Important: Start your bot channel with the command "/start" and receive the Chat ID. +// In order to get MultiGeiger messages to your specific chat, please provide this Chat ID via Web Config. +// Form: "123456789" + diff --git a/multigeiger/webconf.cpp b/multigeiger/webconf.cpp index 1ca60ac..8a8b4ff 100644 --- a/multigeiger/webconf.cpp +++ b/multigeiger/webconf.cpp @@ -40,6 +40,13 @@ static bool isLoraBoard; float localAlarmThreshold = LOCAL_ALARM_THRESHOLD; int localAlarmFactor = (int)LOCAL_ALARM_FACTOR; +int sendDataToMessengerEvery = (int)SEND_DATA_TO_MESSENGER_EVERY; +bool sendLocalAlarmToMessenger = SEND_LOCAL_ALARM_TO_MESSENGER; +char telegramBotToken[50] = ""; // "XXXXXXXXXX:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +char telegramChatId[12] = ""; // "1234567890" + +char sendLocalAlarmToMessenger_c[CHECKBOX_LEN]; + iotwebconf::ParameterGroup grpMisc = iotwebconf::ParameterGroup("misc", "Misc. Settings"); iotwebconf::CheckboxParameter startSoundParam = iotwebconf::CheckboxParameter("Start sound", "startSound", playSound_c, CHECKBOX_LEN, playSound); iotwebconf::CheckboxParameter speakerTickParam = iotwebconf::CheckboxParameter("Speaker tick", "speakerTick", speakerTick_c, CHECKBOX_LEN, speakerTick); @@ -57,7 +64,7 @@ iotwebconf::TextParameter deveuiParam = iotwebconf::TextParameter("DEVEUI", "dev iotwebconf::TextParameter appeuiParam = iotwebconf::TextParameter("APPEUI", "appeui", appeui, 17); iotwebconf::TextParameter appkeyParam = iotwebconf::TextParameter("APPKEY", "appkey", appkey, 33); -iotwebconf::ParameterGroup grpAlarm = iotwebconf::ParameterGroup("alarm", "Local Alarm Setting"); +iotwebconf::ParameterGroup grpAlarm = iotwebconf::ParameterGroup("alarm", "Local Alarm Settings"); iotwebconf::CheckboxParameter soundLocalAlarmParam = iotwebconf::CheckboxParameter("Enable local alarm sound", "soundLocalAlarm", soundLocalAlarm_c, CHECKBOX_LEN, soundLocalAlarm); iotwebconf::FloatTParameter localAlarmThresholdParam = iotwebconf::Builder("localAlarmThreshold"). @@ -71,6 +78,18 @@ iotwebconf::IntTParameter localAlarmFactorParam = min(2).max(100). step(1).placeholder("2..100").build(); +iotwebconf::ParameterGroup grpMessenger = iotwebconf::ParameterGroup("messenger", "Messenger Settings"); +iotwebconf::CheckboxParameter sendLocalAlarmToMessengerParam = iotwebconf::CheckboxParameter("Send local alarm via Messenger", "sendLocalAlarmToMessenger", sendLocalAlarmToMessenger_c, CHECKBOX_LEN, sendLocalAlarmToMessenger); +iotwebconf::IntTParameter sendDataToMessengerEveryParam = + iotwebconf::Builder>("sendDataToMessengerEvery"). + label("Send data via Messenger every N x 2.5min\n(0=never,24=1/h,576=1/d,4032=1/week,max:27719)"). + defaultValue(sendDataToMessengerEvery). + min(0).max(27719). + step(1).placeholder("0..27719").build(); +iotwebconf::PasswordParameter telegramBotTokenParam = iotwebconf::PasswordParameter("Telegram Bot Token (Reboot required!)", "telegramBotToken", telegramBotToken, 50); +iotwebconf::PasswordParameter telegramChatIdParam = iotwebconf::PasswordParameter("Telegram Chat ID", "telegramChatId", telegramChatId, 12); + + // This only needs to be changed if the layout of the configuration is changed. // Appending new variables does not require a new version number here. // If this value is changed, ALL configuration variables must be re-entered, @@ -162,6 +181,8 @@ void loadConfigVariables(void) { soundLocalAlarm = soundLocalAlarmParam.isChecked(); localAlarmThreshold = localAlarmThresholdParam.value(); localAlarmFactor = localAlarmFactorParam.value(); + sendDataToMessengerEvery = sendDataToMessengerEveryParam.value(); + sendLocalAlarmToMessenger = sendLocalAlarmToMessengerParam.isChecked(); } void configSaved(void) { @@ -205,6 +226,11 @@ void setup_webconf(bool loraHardware) { grpAlarm.addItem(&localAlarmThresholdParam); grpAlarm.addItem(&localAlarmFactorParam); iotWebConf.addParameterGroup(&grpAlarm); + grpMessenger.addItem(&sendDataToMessengerEveryParam); + grpMessenger.addItem(&sendLocalAlarmToMessengerParam); + grpMessenger.addItem(&telegramBotTokenParam); + grpMessenger.addItem(&telegramChatIdParam); + iotWebConf.addParameterGroup(&grpMessenger); // if we don't have LoRa hardware, do not send to LoRa if (!isLoraBoard) diff --git a/multigeiger/webconf.h b/multigeiger/webconf.h index ad68aa3..d4feb6f 100644 --- a/multigeiger/webconf.h +++ b/multigeiger/webconf.h @@ -23,6 +23,11 @@ extern char appkey[]; extern float localAlarmThreshold; extern int localAlarmFactor; +extern int sendDataToMessengerEvery; +extern char telegramBotToken[50]; +extern char telegramChatId[15]; +extern bool sendLocalAlarmToMessenger; + extern char ssid[]; extern IotWebConf iotWebConf; diff --git a/platformio-example.ini b/platformio-example.ini index 765db24..068cbd8 100644 --- a/platformio-example.ini +++ b/platformio-example.ini @@ -31,3 +31,4 @@ lib_deps= IotWebConf@^3.1.0 MCCI LoRaWAN LMIC library h2zero/NimBLE-Arduino + witnessmenow/UniversalTelegramBot@^1.3.0