This commit is contained in:
schinken 2022-03-15 16:29:17 +01:00
parent 8a47d2a644
commit 975ba1cd34
6 changed files with 144 additions and 307 deletions

View file

@ -8,27 +8,11 @@ Here's some code and schematic to connect your mightyohm.com geiger counter to t
## compile ## compile
The firmware can be built and flashed using the Arduino IDE. To compile this code, just clone or download the master branch as a zip and ramp up your arduino library. For this code to work, you need the SimpleTimer-library (http://playground.arduino.cc/Code/SimpleTimer#Download) and the ESP8266 added to your Arduino-IDE as a board (https://github.com/esp8266/Arduino, see install instructions)
For this, you will need to add ESP8266 support to it by [using the Boards Manager](https://github.com/esp8266/Arduino#installing-with-boards-manager).
Furthermore, you will also need to install the following libraries using the Library Manager:
* ArduinoJSON 6.10.1
* PubSubClient 2.8.0
* WiFiManager 0.15.0
## settings ## settings
Since this project is using the WifiManager library, the ESP8266 will open up a WiFi Access Point for its initial configuration You need to adjust the settings in the settings.h file.
or if it is unable to connect to the previously configured WiFi.
The library pretends that said WiFi AP requires a captive portal which triggers a notification on recent android phones.
Simply connect to the AP with your phone, tap the "Login required"-notification and you should be able to configure everything.
## usage
Since we're using the Home Assistant Autodiscovery feature, everything should just work™.
## license and credits ## license and credits

View file

@ -1,37 +0,0 @@
void saveConfig() {
DynamicJsonDocument json(512);
json["mqtt_server"] = mqtt_server;
json["username"] = username;
json["password"] = password;
File configFile = SPIFFS.open("/config.json", "w");
if (!configFile) {
return;
}
serializeJson(json, configFile);
configFile.close();
}
void loadConfig() {
if (SPIFFS.begin()) {
if (SPIFFS.exists("/config.json")) {
File configFile = SPIFFS.open("/config.json", "r");
if (configFile) {
const size_t size = configFile.size();
std::unique_ptr<char[]> buf(new char[size]);
configFile.readBytes(buf.get(), size);
DynamicJsonDocument json(512);
if (DeserializationError::Ok == deserializeJson(json, buf.get())) {
strcpy(mqtt_server, json["mqtt_server"]);
strcpy(username, json["username"]);
strcpy(password, json["password"]);
}
}
}
}
}

View file

@ -1,207 +1,153 @@
#include "wifi.h"
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <ArduinoJson.h>
#include <WiFiManager.h>
#include <PubSubClient.h> #include <PubSubClient.h>
#include <SoftwareSerial.h> #include <SoftwareSerial.h>
#include <ArduinoOTA.h>
#include <SimpleTimer.h>
#include "settings.h" #include "settings.h"
uint8_t mqttRetryCounter = 0;
WiFiManager wifiManager;
WiFiClient wifiClient; WiFiClient wifiClient;
PubSubClient mqttClient; PubSubClient mqttClient;
WiFiManagerParameter custom_mqtt_server("server", "mqtt server", mqtt_server, sizeof(mqtt_server));
WiFiManagerParameter custom_mqtt_user("user", "MQTT username", username, sizeof(username));
WiFiManagerParameter custom_mqtt_pass("pass", "MQTT password", password, sizeof(password));
unsigned long lastMqttConnectionAttempt = millis();
const long mqttConnectionInterval = 60000;
unsigned long statusPublishPreviousMillis = millis();
const long statusPublishInterval = 30000;
char identifier[24];
#define FIRMWARE_PREFIX "esp8266-geigercounter"
#define AVAILABILITY_ONLINE "online"
#define AVAILABILITY_OFFLINE "offline"
char MQTT_TOPIC_AVAILABILITY[128];
char MQTT_TOPIC_CPM[128];
char MQTT_TOPIC_USV[128];
char MQTT_TOPIC_AUTOCONF_CPM[128];
char MQTT_TOPIC_AUTOCONF_USV[128];
SoftwareSerial geigerCounterSerial(PIN_UART_RX, PIN_UART_TX); SoftwareSerial geigerCounterSerial(PIN_UART_RX, PIN_UART_TX);
SimpleTimer timer;
String serialInput = "";
char serialInputHelper[RECV_LINE_SIZE];
bool shouldSaveConfig = false; int lastCPM = 0, currentCPM = 0;
float lastuSv = 0, currentuSv = 0;
void saveConfigCallback () {
shouldSaveConfig = true;
}
void setup() { void setup() {
// power up wait
delay(3000); delay(3000);
Serial.begin(9600);
delay(2000);
Serial.println("\n");
Serial.println("Hello from esp8266-geigercounter");
Serial.printf("Core Version: %s\n", ESP.getCoreVersion().c_str());
Serial.printf("Boot Version: %u\n", ESP.getBootVersion());
Serial.printf("Boot Mode: %u\n", ESP.getBootMode());
Serial.printf("CPU Frequency: %u MHz\n", ESP.getCpuFreqMHz());
Serial.printf("Reset reason: %s\n", ESP.getResetReason().c_str());
geigerCounterSerial.begin(9600);
snprintf(identifier, sizeof(identifier), "GEIGERCTR-%X", ESP.getChipId());
snprintf(MQTT_TOPIC_AVAILABILITY, 127, "%s/%s/status", FIRMWARE_PREFIX, identifier);
snprintf(MQTT_TOPIC_CPM, 127, "%s/%s_cpm/state", FIRMWARE_PREFIX, identifier);
snprintf(MQTT_TOPIC_USV, 127, "%s/%s_usv/state", FIRMWARE_PREFIX, identifier);
snprintf(MQTT_TOPIC_AUTOCONF_CPM, 127, "homeassistant/sensor/%s/%s_cpm/config", FIRMWARE_PREFIX, identifier);
snprintf(MQTT_TOPIC_AUTOCONF_USV, 127, "homeassistant/sensor/%s/%s_usv/config", FIRMWARE_PREFIX, identifier);
WiFi.hostname(identifier);
loadConfig();
setupWifi();
mqttClient.setServer(mqtt_server, 1883);
mqttClient.setKeepAlive(10);
mqttClient.setBufferSize(2048);
Serial.print("Hostname: ");
Serial.println(identifier);
Serial.print("\nIP: ");
Serial.println(WiFi.localIP());
Serial.println("-- Current GPIO Configuration --");
Serial.print("PIN_UART_RX: ");
Serial.println(PIN_UART_RX);
//Disable blue LED
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
mqttReconnect();
}
void loop() {
mqttClient.loop();
handleUart();
if (statusPublishInterval <= (millis() - statusPublishPreviousMillis)) { Serial.begin(115200);
statusPublishPreviousMillis = millis(); geigerCounterSerial.begin(BAUD_GEIGERCOUNTER);
updateRadiationValues(); delay(10);
WiFi.hostname(WIFI_HOSTNAME);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
} }
if (!mqttClient.connected() && (mqttConnectionInterval <= (millis() - lastMqttConnectionAttempt)) ) {
lastMqttConnectionAttempt = millis();
mqttReconnect();
}
}
void setupWifi() {
wifiManager.setDebugOutput(false);
wifiManager.setSaveConfigCallback(saveConfigCallback);
wifiManager.addParameter(&custom_mqtt_server);
wifiManager.addParameter(&custom_mqtt_user);
wifiManager.addParameter(&custom_mqtt_pass);
WiFi.hostname(identifier);
wifiManager.autoConnect(identifier);
mqttClient.setClient(wifiClient); mqttClient.setClient(wifiClient);
mqttClient.setServer(MQTT_HOST, 1883);
ArduinoOTA.setHostname(WIFI_HOSTNAME);
ArduinoOTA.setPassword(OTA_PASSWORD);
ArduinoOTA.begin();
strcpy(mqtt_server, custom_mqtt_server.getValue()); timer.setInterval(UPDATE_INTERVAL_SECONDS * 1000L, updateRadiationValues);
strcpy(username, custom_mqtt_user.getValue()); }
strcpy(password, custom_mqtt_pass.getValue());
if (shouldSaveConfig) { void updateRadiationValues() {
saveConfig();
} else {
//For some reason, the read values get overwritten in this function char tmp[8];
//To combat this, we just reload the config
//This is most likely a logic error which could be fixed otherwise if (currentCPM != lastCPM) {
loadConfig(); String(currentCPM).toCharArray(tmp, 8);
Serial.print("Sending CPM");
Serial.println(tmp);
mqttClient.publish(MQTT_TOPIC_CPM, tmp, true);
} }
if (currentuSv != lastuSv) {
String(currentuSv).toCharArray(tmp, 8);
Serial.print("Sending uSv");
Serial.println(tmp);
mqttClient.publish(MQTT_TOPIC_USV, tmp, true);
}
lastCPM = currentCPM;
lastuSv = currentuSv;
} }
void resetWifiSettingsAndReboot() { void connectMqtt() {
wifiManager.resetSettings();
delay(3000);
ESP.restart();
}
void mqttReconnect() { while (!mqttClient.connected()) {
for (int attempt = 0; attempt < 3; ++attempt) { if (mqttClient.connect(WIFI_HOSTNAME, MQTT_TOPIC_LAST_WILL, 1, true, "disconnected")) {
if (mqttClient.connect(identifier, username, password, MQTT_TOPIC_AVAILABILITY, 1, true, AVAILABILITY_OFFLINE)) { mqttClient.publish(MQTT_TOPIC_LAST_WILL, "connected", true);
mqttClient.publish(MQTT_TOPIC_AVAILABILITY, AVAILABILITY_ONLINE, true);
Serial.println("Connected to MQTT Server");
publishAutoConfig();
break;
} else { } else {
Serial.println("Failed to connect to MQTT Server :("); delay(1000);
delay(5000);
} }
} }
} }
boolean isMqttConnected() { void parseReceivedLine(char* input) {
return mqttClient.connected();
char segment = 0;
char *token;
float uSv = 0;
float cpm = 0;
token = strtok(input, delimiter);
while (token != NULL) {
switch (segment) {
// This is just for validation
case IDX_CPS_KEY: if (strcmp(token, "CPS") != 0) return; break;
case IDX_CPM_KEY: if (strcmp(token, "CPM") != 0) return; break;
case IDX_uSv_KEY: if (strcmp(token, "uSv/hr") != 0) return; break;
case IDX_CPM:
Serial.printf("Current CPM: %s\n", token);
cpm = String(token).toInt();
break;
case IDX_uSv:
Serial.printf("Current uSv/hr: %s\n", token);
uSv = String(token).toFloat();
break;
}
if (segment > 7) {
// Invalid! There should be no more than 7 segments
return;
}
token = strtok(NULL, delimiter);
segment++;
}
currentuSv = uSv;
currentCPM = cpm;
} }
void publishAutoConfig() { void loop() {
char mqttPayload[2048];
DynamicJsonDocument device(256);
StaticJsonDocument<64> identifiersDoc;
JsonArray identifiers = identifiersDoc.to<JsonArray>();
identifiers.add(identifier); connectMqtt();
timer.run();
mqttClient.loop();
device["identifiers"] = identifiers; if (geigerCounterSerial.available()) {
device["manufacturer"] = "MightyOhm LLC"; char in = (char) geigerCounterSerial.read();
device["model"] = "Geiger Counter";
device["name"] = identifier;
device["sw_version"] = "0.0.1";
serialInput += in;
DynamicJsonDocument cpmSensorPayload(512); if (in == '\n') {
serialInput.toCharArray(serialInputHelper, RECV_LINE_SIZE);
parseReceivedLine(serialInputHelper);
cpmSensorPayload["device"] = device.as<JsonObject>(); serialInput = "";
cpmSensorPayload["availability_topic"] = MQTT_TOPIC_AVAILABILITY; }
cpmSensorPayload["state_topic"] = MQTT_TOPIC_CPM;
cpmSensorPayload["name"] = identifier + String(" CPM");
cpmSensorPayload["unit_of_measurement"] = "CPM";
cpmSensorPayload["unique_id"] = identifier + String("_cpm");
serializeJson(cpmSensorPayload, mqttPayload); // Just in case the buffer gets to big, start from scratch
mqttClient.publish(MQTT_TOPIC_AUTOCONF_CPM, mqttPayload, true); if (serialInput.length() > RECV_LINE_SIZE + 10) {
serialInput = "";
}
DynamicJsonDocument usvSensorPayload(512); Serial.write(in);
}
usvSensorPayload["device"] = device.as<JsonObject>(); ArduinoOTA.handle();
usvSensorPayload["availability_topic"] = MQTT_TOPIC_AVAILABILITY;
usvSensorPayload["state_topic"] = MQTT_TOPIC_USV;
usvSensorPayload["name"] = identifier + String(" uSv");
usvSensorPayload["unit_of_measurement"] = "µSv/h";
usvSensorPayload["unique_id"] = identifier + String("_uSv");
serializeJson(usvSensorPayload, mqttPayload);
mqttClient.publish(MQTT_TOPIC_AUTOCONF_USV, mqttPayload, true);
Serial.println("Published MQTT Autoconf");
} }

View file

@ -1,81 +0,0 @@
const char* delimiter = ", ";
int lastCPM = 0, currentCPM = 0;
float lastuSv = 0, currentuSv = 0;
byte serialRxBuf[255];
void handleUart() {
if (geigerCounterSerial.available()) {
geigerCounterSerial.readBytesUntil('\n', serialRxBuf, 250);
parseReceivedLine((char*)serialRxBuf);
}
}
void parseReceivedLine(char* input) {
Serial.println(input);
char segment = 0;
char *token;
float uSv = 0;
float cpm = 0;
token = strtok(input, delimiter);
while (token != NULL) {
switch (segment) {
// This is just for validation
case IDX_CPS_KEY: if (strcmp(token, "CPS") != 0) return; break;
case IDX_CPM_KEY: if (strcmp(token, "CPM") != 0) return; break;
case IDX_uSv_KEY: if (strcmp(token, "uSv/hr") != 0) return; break;
case IDX_CPM:
//Serial.printf("\nCurrent CPM: %s\n", token);
cpm = String(token).toInt();
break;
case IDX_uSv:
//Serial.printf("Current uSv/hr: %s\n", token);
uSv = String(token).toFloat();
break;
}
if (segment > 7) {
// Invalid! There should be no more than 7 segments
return;
}
token = strtok(NULL, delimiter);
segment++;
}
currentuSv = uSv;
currentCPM = cpm;
}
void updateRadiationValues() {
char tmp[8];
if (currentCPM != lastCPM) {
String(currentCPM).toCharArray(tmp, 8);
Serial.print("Sending CPM: ");
Serial.println(tmp);
mqttClient.publish(MQTT_TOPIC_CPM, tmp, true);
}
if (currentuSv != lastuSv) {
String(currentuSv).toCharArray(tmp, 8);
Serial.print("Sending uSv: ");
Serial.println(tmp);
mqttClient.publish(MQTT_TOPIC_USV, tmp, true);
}
lastCPM = currentCPM;
lastuSv = currentuSv;
}

29
settings.h.example Normal file
View file

@ -0,0 +1,29 @@
#define IDX_CPS_KEY 0
#define IDX_CPM_KEY 2
#define IDX_uSv_KEY 4
#define IDX_CPM 3
#define IDX_uSv 5
#define IDX_MODE 6
#define RECV_LINE_SIZE 37
#define PIN_UART_RX 0 // 4
#define PIN_UART_TX 13 // UNUSED
#define UPDATE_INTERVAL_SECONDS 300L
#define BAUD_GEIGERCOUNTER 9600
const char* HOSTNAME = "ESP-GeigerCounter";
const char* WIFI_SSID = "----";
const char* WIFI_PASSWORD = "----";
const char* OTA_PASSWORD = "foobar";
#define MQTT_TOPIC_CPM "sensor/radiation/cpm"
#define MQTT_TOPIC_USV "sensor/radiation/uSv"
#define MQTT_TOPIC_LAST_WILL "state/sensor/geigercounter"
const char* mqttHost = "mqtt.core.bckspc.de";
const char* delimiter = ", ";

4
wifi.h
View file

@ -1,4 +0,0 @@
char mqtt_server[80] = "example.tld";
char username[24] = "";
char password[24] = "";