Files
Neptune_W01/src/main.cpp

613 lines
26 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
Created on: 01.01.2021
Счётчик воды и протечки WEB морда для ESP8266
1) Установить: http://wiki.amperka.ru/%D0%BF%D1%80%D0%BE%D0%B4%D1%83%D0%BA%D1%82%D1%8B:esp8266:esptool
Для 1MB флешь памяти, файловая система: http://wikihandbk.com/wiki/ESP8266:%D0%9F%D1%80%D0%BE%D1%88%D0%B8%D0%B2%D0%BA%D0%B8/Arduino/%D0%A0%D0%B0%D0%B1%D0%BE%D1%82%D0%B0_%D1%81_%D1%84%D0%B0%D0%B9%D0%BB%D0%BE%D0%B2%D0%BE%D0%B9_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%BE%D0%B9_%D0%B2_%D0%B0%D0%B4%D0%B4%D0%BE%D0%BD%D0%B5_ESP8266_%D0%B4%D0%BB%D1%8F_IDE_Arduino
На борту счётчика выход
2 входа для счётчика горячей и холодной (Подключено к STM8L051 геркон)
1 вход для датчика утечки (Подключено к STM8L051 200 ком резистор по моему)
1 кнопка для включения ESP8266 и перехода в настройки при длительном нажатии (10 секунд) а при коротком просто отправить данные по WIFI (Подключено к STM8L051 кнопка)
1 кнопочка для открытия кранов не смотря на утеку воды (для пожаробезопасности) (Подключено к STM8L051 кнопка)
Подробнее схемотехника на: https://easyeda.com/ru
Другая документация на O:\MyDocuments\projects\_Doc\Water_meter_observer
*/
//----------------------------------------------------------------------------------------------------
#include <Arduino.h>
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h> // Include the WebServer library
#include <DNSServer.h>
#include <ESP8266mDNS.h>
#include <Ticker.h> //Ticker Library
#include <LittleFS.h> //Прмер: https://github.com/lorol/ESPAsyncWebServer/blob/master/examples/ESP_AsyncFSBrowser/ESP_AsyncFSBrowser.ino
#include <PubSubClient.h> //Для отправки данных по MQTT см.: https://www.emqx.io/blog/esp8266-connects-to-the-public-mqtt-broker
#include "Bounce2.h"
#include "tools.h"
//----------------------------------------------------------------------------------------------------
#define PIN_LED 02 //Пин светодиода
Bounce bounce = Bounce(); //Для избавления от дребезга контактов
int blink=LOW; //Для мигания при настройке
int blinkMSec=0; //Для мигания при настройке
ESP8266WebServer server(80); // Set web server and port number to 80
WiFiClient wifiClient;
WiFiClientSecure wifiClientS;
IPAddress local_IP(192,168,1,1); //Для точки доступа
IPAddress gateway(192,168,1,1); //Для точки доступа
IPAddress subnet(255,255,255,0); //Для точки доступа
IPAddress DNS_IP(192,168,1,1);
int lastPS=0; //сколько приконекченных
// DNS server
const byte DNS_PORT = 53;
DNSServer dnsServer;
//Для входа и выхода из режима настройки WIFI
#define CNF_TIME 300; //Сколько секунд даётся для настройки оборудования (600=5минут)
int g_ticks = CNF_TIME; //Переменная для отсчёта секунд, как дойдёт до нуля то засыпаем
Ticker blinker; //Для подсчёта времени до входа в спящий режим (предполагаю просыпание по кнопке ресет)
String ssid="Node6";
String pass="isecretk";
PubSubClient mqttClient;
String mqtt_protocol = "tcp";
String mqtt_host = "observer.kz"; // Имя сервера MQTT
uint16_t mqtt_port = 1883; // Порт для подключения к серверу MQTT
String mqtt_fingerprint = ""; //Отпечаток сертификата
String mqtt_user = ""; // Логи для подключения к серверу MQTT
String mqtt_pass = ""; // Пароль для подключения к серверу MQTT
String mqtt_topic = "home/water/main"; /* 1=on, 0=off else toggle*/
#define LED_QOS 1 //0 - Без подтверждения 1 - С подтверждением 2 - С подтверждением и без возможности двойной отправки
int time5s=millis(); //Для реконекта каждые 5 секунд
String uuid=""; //Уникальный ID клиента (загружается из файловой системы)
String g_data; //Для накопления данных с последовательного порта
//----------------------------------------------------------------------------------------------------
int g_cold = -1; //Показание холодной воды
int g_hot = -1; //Показание горячей воды
int g_leak = -1; //Протечка 0 нет 1 есть
int g_volt = -1; //Вольт на акамуляторе
int g_tmpr = -100; //Температура на микроконтроллере
//----------------------------------------------------------------------------------------------------
bool configWebServer();
void setLampLight(int mode);
//----------------------------------------------------------------------------------------------------
//Load settings from file system to local variables
bool loadConfig()
{
File f = LittleFS.open("settings.txt", "r");
String line="";
do {
line=readLine(f);
if(line!=""){
if(BeforeFirst(line,'=')=="ssid") ssid=AfterFirst(line,'='); //Название сети
else if(BeforeFirst(line,'=')=="pass") pass=AfterFirst(line,'='); //Номер сети
else if(BeforeFirst(line,'=')=="mqtt_protocol") mqtt_protocol=AfterFirst(line,'=');
else if(BeforeFirst(line,'=')=="mqtt_host") mqtt_host=AfterFirst(line,'=');
else if(BeforeFirst(line,'=')=="mqtt_port") mqtt_port=AfterFirst(line,'=').toInt();
else if(BeforeFirst(line,'=')=="mqtt_fingerprint") mqtt_fingerprint=AfterFirst(line,'=');
else if(BeforeFirst(line,'=')=="mqtt_user") mqtt_user=AfterFirst(line,'=');
else if(BeforeFirst(line,'=')=="mqtt_pass") mqtt_pass=AfterFirst(line,'=');
else if(BeforeFirst(line,'=')=="mqtt_topic") mqtt_topic=AfterFirst(line,'=');
}
} while (line!="");
f.close();
Serial.println("SSID = "+ssid);
Serial.println("MQTT HOST = "+mqtt_host);
return true;
}
//----------------------------------------------------------------------------------------------------
//Save settings from local variables to file system
bool saveConfigs()
{
File f = LittleFS.open("settings.txt", "w");
if (!f) {
Serial.println("Count file open failed on update.");
} else {
f.println("ssid="+ssid);
f.println("pass="+pass);
f.println("mqtt_protocol="+mqtt_protocol);
f.println("mqtt_host="+mqtt_host);
f.println("mqtt_port="+String(mqtt_port));
f.println("mqtt_fingerprint="+mqtt_fingerprint);
f.println("mqtt_topic="+mqtt_topic);
f.println("mqtt_user="+mqtt_user);
f.println("mqtt_pass="+mqtt_pass);
f.close();
}
return true;
}
//----------------------------------------------------------------------------------------------------
//Запрос на получение данных с микроконтроллера
//Для дебага написал тестовую программу клторая эиетирует STM8L по последовательному порту: O:\MyDocuments\projects\Workspace_C++Builder\NeptuneW01
bool getMode(){
Serial.println(addCRC("#0;")); //Запрашиваю для какого режима работы включили WIFI
return true;
}
//----------------------------------------------------------------------------------------------------
// Запрашиваю данные с датчиков
bool callSensorsData(){
Serial.println(addCRC("#1;")); //Запрашиваем '1'=холодная
Serial.println(addCRC("#2;")); //Запрашиваем '2'=горячая
Serial.println(addCRC("#3;")); //Запрашиваем '3'=протечки
Serial.println(addCRC("#4;")); //Запрашиваем '4'=вольт на аккумуляторе
Serial.println(addCRC("#5;")); //Запрашиваем '5'=температура на процессоре
//Уровень WIFI сигнала берём на ESP8266 а не с STM8L051
return true;
}
//----------------------------------------------------------------------------------------------------
// Отправляю данные на STM8L для сохранения
bool saveToProc(){
//
return true;
}
//----------------------------------------------------------------------------------------------------
void handleNotFound() {
server.send(404, "text/plain", "FileNotFound");
}
//----------------------------------------------------------------------------------------------------
//Отправляю показания датчиков для режима настройки
void handleSensors() {
callSensorsData(); //Запрашиваю новые данные датчиков с микроконтроллера
String json;
json.reserve(500); //У меня дома около 500 символов было необходимо
//Видимые сети
json = "{\"networks\":[";
int n = WiFi.scanNetworks();
Serial.print(n);
Serial.println(" network(s) found");
for (int i = 0; i < n; i++)
{
json += "{\"name\":\"";
json += WiFi.SSID(i); //Название сети
json += "\",\"lock\":";
json += WiFi.encryptionType(i); //Шифрование
json += ",\"strength\":";
json += WiFi.RSSI(i); //Уровень сигнала
json += "}";
if(i!=n-1) json += ",";
}
json += "],";
//Количество тиков до перехода в спящий режим
json += "\"ticks\":"+String(g_ticks);
if(g_cold<0) json += ",\"cold\":null"; else json += ",\"cold\":"+String(g_cold);
if(g_hot<0) json += ",\"hot\":null"; else json += ",\"hot\":"+String(g_hot);
if(g_leak==1) json += ",\"leak\":true"; else if(g_leak==0) json += ",\"leak\":false"; else json += ",\"leak\":null";
if(g_volt<0) json += ",\"volt\":null"; else json += ",\"volt\":"+String(g_volt);
if(g_tmpr<=-100) json += ",\"tmpr\":null"; else json += ",\"tmpr\":"+String(g_tmpr); // Температура
json += "}";
server.send(200, "application/json", json);
}
//----------------------------------------------------------------------------------------------------
//Отправляем настройки сети (для режима настройки)
void handleData(){
String json;
json.reserve(128);
json = "{";
json += "\"ssid\":\""+ssid+"\"";
json += ",\"pass\":\""+pass+"\"";
json += ",\"uuid\":\""+uuid+"\"";
json += ",\"mqtt_protocol\":\""+mqtt_protocol+"\"";
json += ",\"mqtt_host\":\""+mqtt_host+"\"";
json += ",\"mqtt_port\":\""+String(mqtt_port)+"\"";
json += ",\"mqtt_fingerprint\":\""+mqtt_fingerprint+"\"";
json += ",\"mqtt_topic\":\""+mqtt_topic+"\"";
json += ",\"mqtt_user\":\""+mqtt_user+"\"";
json += ",\"mqtt_pass\":\""+mqtt_pass+"\"";
json += "}";
server.send(200, "application/json", json);
}
//=======================================================================
void handleSave(){
//Serial.println("+++Save+++");
if(server.args()>0){
for(int i=0;i<server.args();i++){
//Serial.println("Nane = "+server.argName(i));
//Serial.println(server.arg(i));
if(server.argName(i)=="ssid")
ssid=server.arg(i);
if(server.argName(i)=="pass")
pass=server.arg(i);
if(server.argName(i)=="cold" && server.arg(i)!=""){
g_cold=server.arg(i);
}
if(server.argName(i)=="hot" && server.arg(i)!=""){
g_hot=server.arg(i);
}
if(server.argName(i)=="mqtt_protocol")
mqtt_protocol=server.arg(i);
if(server.argName(i)=="mqtt_host")
mqtt_host=server.arg(i);
if(server.argName(i)=="mqtt_port")
mqtt_port=server.arg(i).toInt();
if(server.argName(i)=="mqtt_fingerprint")
mqtt_fingerprint=server.arg(i);
if(server.argName(i)=="mqtt_topic")
mqtt_topic=server.arg(i);
if(server.argName(i)=="mqtt_user")
mqtt_user=server.arg(i);
if(server.argName(i)=="mqtt_pass")
mqtt_pass=server.arg(i);
}
}
server.send(200, "text/html", "ok");
}
//=======================================================================
void handleMain() {
if(LittleFS.exists("/index.html")){
//Serial.println("File exists.");
File file = LittleFS.open("/index.html", "r");
if(file){
if(server.streamFile(file, "text/html")!= file.size()){
Serial.println("Sent less data than expected!"); //Отправленно меньше данных чем ожидалось
}
file.close();
}
}else{
Serial.println("File not exists!");
}
}
//----------------------------------------------------------------------------------------------------
//Подключиться к сохраненой точки доступа и поднять HTML сервер
bool connectToWIFIPoint(){
WiFi.mode(WIFI_STA); //WIFI_STA - Режим только клиента
WiFi.hostname("Water_meter_V01");
WiFi.setAutoReconnect(true);
WiFi.begin(ssid, pass);
/*Serial.print("Connecting to ");
int i = 60; //Чтобы больше минуты не ждал
while (WiFi.status() != WL_CONNECTED && i>0) { // Wait for the Wi-Fi to connect
delay(1000);
Serial.print(--i); Serial.print(' ');
}*/
return true;
}
//----------------------------------------------------------------------------------------------------
//Отключиться от точки доступа
bool disconnectFromWIFIPoint(){
WiFi.setAutoReconnect(false);
WiFi.disconnect(true);
return true;
}
//----------------------------------------------------------------------------------------------------
//Инициилизировать WIFI точку доступа для настройки выключателя а также поднять HTML сервер
bool createAP(){
//WiFi.mode(WIFI_AP_STA); //WIFI_AP_STA - Режим точки доступа и клиента WIFI_AP - Режим только точки доступа
WiFi.mode(WIFI_AP); //WIFI_AP - Режим только точки доступа
//Настройки точки доступа
if(WiFi.softAPConfig(local_IP, gateway, subnet)){
Serial.println("Ready WiFi.softAPConfig");
}
Serial.print("Setting soft-AP ... ");
boolean result = WiFi.softAP("NeptuneW01");
if(result == true)
{
Serial.println("Ready");
IPAddress IP = WiFi.softAPIP();
Serial.println(IP);
//Serial.println(WiFi.localIP());
//Настройте DNS-сервер, перенаправляющий все домены на apIP
dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
dnsServer.start(DNS_PORT, "*", DNS_IP);
configWebServer();
}
else
{
Serial.println("Failed!");
}
return true;
}
//----------------------------------------------------------------------------------------------------
//Отключить программную точку доступа
bool deleteAP(){
Serial.print("softAPdisconnect();");
dnsServer.stop();
bool result=WiFi.softAPdisconnect(true);
//Гашу светодиод
digitalWrite(LED_BUILTIN,HIGH); //LOW
return result;
}
//----------------------------------------------------------------------------------------------------
//Функция для подсчёта времени до перехода в спящий режим
void tick_1s()
{
g_ticks--;
if(g_ticks==0){ //Переходим в спящий режим
Serial.print("Deep sleep!");
Serial.print(addCRC("#d;!")); //Отсылаю команду на STM8 чтобы он на CHIP_PU подал низский сигнал для отключения питания
}
//Если в течении 2х минут не перешли в режим глубокого сна то переходим в обычный сон
if(g_ticks==-600){
Serial.println("Sleep!");
Serial.print(addCRC("#d;!")); //Повторно отсылаю команду на STM8 чтобы он на CHIP_PU подал низский сигнал для отключения питания
ESP.deepSleep(0);
}
}
//----------------------------------------------------------------------------------------------------
bool configWebServer(){
Serial.println("configWebServer()");
server.on("/sensors/", HTTP_GET, handleSensors);
server.on("/data/", HTTP_GET, handleData);
server.on("/save/", HTTP_POST, handleSave);
server.on("/", HTTP_GET, handleMain);
server.onNotFound(handleNotFound);
// Start server
server.begin();
return true;
}
//----------------------------------------------------------------------------------------------------
// подключаемся к MQTT серверу
bool connectToMQTT(){
Serial.println("mqtt_protocol = "+mqtt_protocol+" mqtt_host = "+mqtt_host+" mqtt_port = " + String(mqtt_port));
// Fingerprint of the broker CA
// openssl x509 -in m2mqtt_srv.crt -sha1 -noout -fingerprint
if(mqtt_protocol=="tcp"){
mqttClient.setClient(wifiClient);
}else{
mqttClient.setClient(wifiClientS);
wifiClientS.setFingerprint(mqtt_fingerprint.c_str());
}
//Присваиваю настройки
mqttClient.setServer(mqtt_host.c_str(),mqtt_port);
if (WiFi.status() == WL_CONNECTED) {
if(!mqttClient.connected()) {
if (mqttClient.connect(uuid.c_str() )) {
Serial.println("connected");
/*mqttClient.setCallback(callback);
if(mqttClient.subscribe(String(mqtt_topic+"/set").c_str(),LED_QOS)) //Подписываемся на топик
Serial.println(String("Subscribe on ")+mqtt_topic);
else
Serial.println("Error subscribe.");*/
} else {
Serial.print("failed, status code =");
Serial.print(mqttClient.state());
}
}
}
return true;
}
//----------------------------------------------------------------------------------------------------
void setup()
{
Serial.begin(115200);
Serial.println("");
Serial.println("Init led pin");
//Настраиваем светодиод на плате
pinMode(PIN_LED, OUTPUT);
digitalWrite(PIN_LED, HIGH); // Выключаю светодиод
//Инициализирую файловую систему
if(LittleFS.begin())
Serial.println("SPIF FS ready");
else
Serial.println("SPIF FS failed");
uuid=readUUID(); //Загружаю уникальный идентификатор клиента
loadConfig(); //Загружаю настройки в локальные переменные
blinker.attach(1, tick_1s); //Инициализирую таймер для контроля времени работы ESP8266
connectToWIFIPoint(); //Пытаюсь подключиться к настроенной точке доступа
/*
connectToMQTT(); //Пытаюсь опубликовать топик
*/
//Заранее инициализирую HTTP сервер чтобы не замарачиваться с логигой его работы...
configWebServer();
//Запрашиваю актуальную информацию по датчикам c STM8 микроконтролера по USART
getMode();
}
//----------------------------------------------------------------------------------------------------
bool first=true;
//=======================================================================
void loop(){
mqttClient.loop();
dnsServer.processNextRequest(); //DNS
server.handleClient(); //Обрабатываем запрос клиента (без этого не берётся IP адресс)
//Как приконнектились отображаю IP адрес
if(WiFi.status() == WL_CONNECTED){
if(first){ //Если приконектились или переконектились то отображаю IP адресс
Serial.println();
Serial.print("Local IP address:\t");
Serial.println(WiFi.localIP());
first=false;
}
}else{
first=true;
}
//если кто приконектился то выводим это в консоль
if(WiFi.softAPgetStationNum()!=lastPS)
{
lastPS=WiFi.softAPgetStationNum();
Serial.printf("Stations connected to soft-AP = %d\n", WiFi.softAPgetStationNum());
}
/*
//После запуска спрашиваем в каком режиме работать настройки или передачи показаний
if(millis()-time5s>30000){
getMode();
time5s=millis();
}
*/
/*
mqttClient.loop();
dnsServer.processNextRequest(); //DNS
server.handleClient(); //Обрабатываем запрос клиента (без этого не берётся IP адресс)
if(g_ticks>0){
//В режиме конфигурации каждые 500 миллисекунд меняем цвет зелёного светодиода
if(millis()-blinkMSec>500){
blink=!blink;
digitalWrite(LED_BUILTIN,blink);
blinkMSec=millis();
}
}else{
//Если в нормальном режиме функционирования
//Проверяю соединение с MQTT и если не соединён то пытаюсь подключиться каждые 10 секунд
if(millis()-time5s>10000){
if(!mqttClient.connected()) {
connectToMQTT();
}
time5s=millis();
}
}
*/
//Блок кода для общения с энергоэфективным микроконтроллером
int inByte = 0;
if (Serial.available() > 0) { //если есть доступные данные то обрабатываем их
//Serial.println("available = "+String(Serial.available()));
// считываем байты в строку до символа с символа '#' до символа '*'
for(int i=0;i<Serial.available();i++){
inByte = Serial.read();
if(inByte=='#') g_data=""; //Новая строка
g_data+=(char)inByte;
if(inByte=='*') break;
}
//Если первый и последний символ в строке равен нужным сиволам то это полный ответ c CRC суммой
if(g_data[0]=='#' && g_data[g_data.length()-1]=='*')
{
g_data[0]='@'; //Чтобы команда заново не выполнилась
Serial.print("Length = "+String(g_data.length())+" ansver = "+g_data);
g_data[0]='#';
Serial.println("");
if(checkCRC(g_data)) //Проверяю CRC принятых данных
{
String cmd=CutBeforeFirst(g_data,';',false); //Отделяю команду
if(cmd=="#0;"){ //'0'=проверка зачем разбудили WIFI
String value=CutBeforeFirst(g_data,'!',true); //Если 1 то отправлять данные на сервер если 0 то войти в режим настройки в качестве точки доступа
Serial.print("value mode = ");
Serial.println(value);
if(value=="1"){ //Если разбудили для того чтобы передать данные
Serial.println("Send data mode");
if(deleteAP()){
connectToWIFIPoint();
}
}
if(value=="0"){ //Если разбудили для того чтобы настроить оборудование
Serial.println("Config mode");
if(disconnectFromWIFIPoint()){
if(createAP()){
g_ticks = CNF_TIME;
}
}
}
}
if(cmd=="#1;"){ //'1'=холодная
String value=CutBeforeFirst(g_data,'!',true);
Serial.print("value cold = ");
Serial.println(value);
g_cold=value.toInt();
}
if(cmd=="#2;"){ //'2'=горячая
String value=CutBeforeFirst(g_data,'!',true);
Serial.print("value hot = ");
Serial.println(value);
g_hot=value.toInt();
}
if(cmd=="#3;"){ //'3'=протечки
String value=CutBeforeFirst(g_data,'!',true);
Serial.print("value leak = ");
Serial.println(value);
g_leak=value.toInt();
}
if(cmd=="#4;"){ //'4'=вольт на аккумуляторе
String value=CutBeforeFirst(g_data,'!',true);
Serial.print("value volt = ");
Serial.println(value);
g_volt=value.toInt();
}
if(cmd=="#5;"){ //'5'=температура
String value=CutBeforeFirst(g_data,'!',true);
Serial.print("value tmrt = ");
Serial.println(value);
g_tmpr=value.toInt();
}
}
g_data=""; //Обработали ответ
}
}
//Как все данные накопились отправляем их по MQTT на сервер
if(WiFi.status() == WL_CONNECTED && g_cold!=-1 && g_hot!=-1 && g_leak!=-1 && g_volt !=-1){
String data="{";
data+="\"cold\":"+String(g_cold)+","; // Горячая
data+="\"hot\":"+String(g_hot)+","; // Холодная
data+="\"leak\":"+String(g_leak)+","; // Протечки
data+="\"volt\":"+String(g_volt/100.0f); // Вольт на акумуляторе
if(g_tmpr>-100){
data+=",\"tmpr\":"+String(g_tmpr); // Температура
}
data+=",\"rssi\":"+String(WiFi.RSSI()); //Уровень WIFI сигнала
data+="}";
mqttClient.publish(mqtt_topic.c_str(), data.c_str(), true); //Отправляю данные в топик (С флагом RETAIN чтобы слушатели которые подключились поздно получили тек. состояние)
Serial.println(data);
//Как отправили данные устанавливаю время ожидания входа в сон на 5 секунд чтобы ESP8266 заснул
g_ticks=5;
//Очищаю данные
g_cold=-1;
g_hot=-1;
g_leak=-1;
g_volt=-1;
g_tmpr=-100;
}
}