diff --git a/README.md b/README.md index 920f130..2f77305 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,15 @@ [![Build Status](https://travis-ci.org/grillbaer/esp32-geiger-counter.svg?branch=master)](https://travis-ci.org/grillbaer/esp32-geiger-counter) -# IoT Geiger Counter with ESP32, OLED Display and Thingspeak Channel +# IoT Geiger Counter with ESP32, OLED Display, Thingspeak Channel and MQTT - Measures radioactive gamma and beta radiation with quite good resolution at the typical low levels of the natural radiation (due to the big STS-6 tube) - Displays the current counts per minute (CPM), estimated dose equivalent rate in micro-Sievert per hour (µS/h) and 10 minutes history with 5 second resolution as bargraph ![Circuit Board](media/geiger-counter-pcb.jpg) - Low-power mode for use with batteries, OLED display and click sounds on, WiFi off -- WiFi mode for thingspeak data upload every minute, see https://thingspeak.com/channels/758223 +- WiFi mode for + - optional thingspeak data upload every minute, see https://thingspeak.com/channels/758223 [![https://thingspeak.com/channels/758223](media/thingspeak.png)](https://thingspeak.com/channels/758223) + - MQTT publishing to a broker every minute Feel free to use this project as a base for your own projects AT YOUR OWN RISK! @@ -25,10 +27,22 @@ Feel free to use this project as a base for your own projects AT YOUR OWN RISK! # Software -- [PlatformIO VSCode project](https://github.com/platformio/platformio-vscode-ide) using [Arduino](https://github.com/arduino/Arduino) library, [Espressif ESP-IDF](https://github.com/espressif/esp-idf) for sleep functions and [U8g2](https://github.com/olikraus/u8g2) for display output +- [PlatformIO VSCode project](https://github.com/platformio/platformio-vscode-ide) using [Arduino](https://github.com/arduino/Arduino) library, [Espressif ESP-IDF](https://github.com/espressif/esp-idf) for sleep functions, [U8g2](https://github.com/olikraus/u8g2) for display output and [Arduino-MQTT](https://github.com/256dpi/arduino-mqtt). - Low-power mode uses light sleep, a wake-up for each signal pulse change and a wake-up every 1000 ms to update pulse statistics and OLED. This results in about 90% sleep. Could be improved using deep sleep and ULP. However, light sleep is already quite good and much easier. - WiFi mode uses no sleep and simple interrupts for pulse counting. Pulse statistics and OLED are updated every 1000 ms, data is sent to thingspeak every 60 s. -- Credentials (WiFi SSID, password, thingspeak channel key) are declared in `credentials.h` and replaced by dummy values in `credentials.cpp` by default. Define your real secrets in a sibling file named `secret_credentials.h` (which is never committed!). It will automatically be included by `credentials.cpp` if it exists. +- Credentials, addresses and user for WiFi, thingspeak channel and mqtt broker are declared in `credentials.h` and replaced by dummy values in `credentials.cpp` by default. Define your real secrets in a sibling file named `secret_credentials.h` (do never committed!). It will automatically be included by `credentials.cpp` if it exists. You may copy `TEMPLATE_secret_credentials.h` as a starting point. + +# MQTT Message Format + +Published messages have the following JSON format: +```json +{ + "pulses": 69, + "cpm": 69, + "uSph": 0.08, + "secs": 60 +} +``` # Geiger Tube Pulse Forming diff --git a/include/credentials.h b/include/credentials.h index 863cd21..f8c67c9 100644 --- a/include/credentials.h +++ b/include/credentials.h @@ -5,4 +5,10 @@ extern const char *wifiSsid; extern const char *wifiPassword; extern const char *thingspeakApiKey; +extern const char *mqttHost; +extern int mqttPort; +extern const char *mqttUser; +extern const char *mqttPassword; +extern const char *mqttTopic; + #endif /* CREDENTIALS_H_ */ diff --git a/include/ingest.h b/include/ingest.h index f598e58..cc4a371 100644 --- a/include/ingest.h +++ b/include/ingest.h @@ -5,6 +5,7 @@ void initIngest(); void deinitIngest(); -void ingest(GeigerData &geigerData, uint16_t intervalSamples); +void ingestToThingspeak(GeigerData &geigerData, uint16_t intervalSamples); +void ingestToMqtt(GeigerData &geigerData, uint16_t intervalSamples); #endif /* INGEST_H_ */ diff --git a/platformio.ini b/platformio.ini index 1752423..5750141 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,7 +12,7 @@ platform = espressif32 board = lolin32 framework = arduino - monitor_speed = 115200 - -lib_deps = U8g2@2.26.1 \ No newline at end of file +lib_deps = + U8g2@2.26.1 + 256dpi/MQTT@^2.4.7 diff --git a/src/TEMPLATE_secret_credentials.h b/src/TEMPLATE_secret_credentials.h new file mode 100644 index 0000000..2f2f958 --- /dev/null +++ b/src/TEMPLATE_secret_credentials.h @@ -0,0 +1,25 @@ +#define _SECRET_CREDENTIALS + +// Copy this file to secret_credentials.h and adjust for +// your needs and environment. + +// !!! NEVER COMMIT YOUR SECRETS !!! + +const char *wifiSsid = "MyWiFi"; +const char *wifiPassword = "mypassword"; + + +// ThingsPeak Channel + +// set key to NULL or empty string to disable ThingsPeak ingest: +const char *thingspeakApiKey = "MYAPIKEY"; + + +// MQTT Broker + +// set host to NULL or empty string to disable MQTT publishing: +const char *mqttHost = "my.mqtt.server"; +int mqttPort = 1833; +const char *mqttUser = "user"; +const char *mqttPassword = "mypassword"; +const char *mqttTopic = "home/radioactivity"; diff --git a/src/credentials.cpp b/src/credentials.cpp index 1f63e3d..7e15988 100644 --- a/src/credentials.cpp +++ b/src/credentials.cpp @@ -10,6 +10,12 @@ // use dummy credentials to make it compilable without secrets const char *wifiSsid = "unset-wifi-ssid"; const char *wifiPassword = "unset-password"; -const char *thingspeakApiKey = "unset-thinkspeak-api-key"; +const char *thingspeakApiKey = ""; // empty to disable thinkspeak + +const char *mqttHost = ""; // empty to disable MQTT +int mqttPort = 1833; +const char *mqttUser = "user"; +const char *mqttPassword = "unset-password"; +const char *mqttTopic = "home/geiger/radioactivity"; #endif diff --git a/src/ingest.cpp b/src/ingest.cpp index 42d6f99..7590e35 100644 --- a/src/ingest.cpp +++ b/src/ingest.cpp @@ -1,4 +1,5 @@ #include +#include #include "GeigerData.h" @@ -6,10 +7,13 @@ const char *thingsPeakUrl = "api.thingspeak.com"; -bool connect() +WiFiClient mqttWifiClient; +MQTTClient mqttClient; + +bool connectWiFi() { - uint16_t retries = 3; - while (WiFi.status() != WL_CONNECTED && (--retries) > 0) + uint16_t retries = 10; + while (WiFi.status() != WL_CONNECTED && (retries--) > 0) { Serial.print("Trying to connect to "); Serial.print(wifiSsid); @@ -35,7 +39,7 @@ bool connect() return WiFi.status() == WL_CONNECTED; } -void disconnect() +void disconnectWiFi() { Serial.println("Disconnecting WiFi"); WiFi.disconnect(true, true); @@ -43,20 +47,23 @@ void disconnect() void initIngest() { - connect(); + connectWiFi(); } void deinitIngest() { if (WiFi.status() == WL_CONNECTED) { - disconnect(); + disconnectWiFi(); } } -void ingest(GeigerData &geigerData, uint16_t intervalSamples) +void ingestToThingspeak(GeigerData &geigerData, uint16_t intervalSamples) { - if (!connect()) + if (!connectWiFi()) + return; + + if (thingsPeakUrl == NULL || thingsPeakUrl[0] == 0) return; WiFiClient client; @@ -66,7 +73,7 @@ void ingest(GeigerData &geigerData, uint16_t intervalSamples) Serial.print(thingsPeakUrl); Serial.println(" failed"); // disconnect from WiFi to trigger fresh connect on next ingest - disconnect(); + disconnectWiFi(); } else { @@ -123,3 +130,58 @@ void ingest(GeigerData &geigerData, uint16_t intervalSamples) client.stop(); } } + +bool connectMqtt() +{ + if (!connectWiFi()) + return false; + + if (mqttHost == NULL || mqttHost[0] == 0) + return false; + + if (!mqttClient.connected()) + { + Serial.print("Connecting to MQTT host "); + Serial.print(mqttHost); + Serial.print(":"); + Serial.print(mqttPort); + Serial.print(" user "); + Serial.print(mqttUser); + mqttClient.begin(mqttHost, mqttPort, mqttWifiClient); + if (mqttClient.connect("esp32-geiger-counter", mqttUser, mqttPassword)) + { + Serial.println(" successful"); + } + else + { + Serial.println(" failed"); + // disconnect from WiFi to trigger fresh connect on next ingest + disconnectWiFi(); + return false; + } + } + + return true; +} + +void disconnectMqtt() +{ + mqttClient.disconnect(); +} + +void ingestToMqtt(GeigerData &geigerData, uint16_t intervalSamples) +{ + if (connectMqtt()) + { + const unsigned long pulses = geigerData.getPreviousPulses(1, + intervalSamples); + const unsigned long cpm = (unsigned long)(pulses / ((float)intervalSamples * geigerData.sampleSeconds / 60.) + 0.5); + const float uSph = geigerData.toMicroSievertPerHour(pulses, + intervalSamples); + char payload[129]; + sprintf(payload, "{\"pulses\":%lu, \"cpm\":%lu,\"uSph\":%.2f,\"secs\":%d}", + pulses, cpm, uSph, (int)intervalSamples); + + mqttClient.publish(mqttTopic, payload); + } +} diff --git a/src/main.cpp b/src/main.cpp index 6863042..1ab2cb7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -97,7 +97,8 @@ void loop() if (ingestCountdown <= 0) { ingestCountdown = ingestInterval; - ingest(geigerData, ingestInterval); + ingestToThingspeak(geigerData, ingestInterval); + ingestToMqtt(geigerData, ingestInterval); } } else