AAOffice_vent/AAOffice_vent.ino

590 lines
17 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
Управление сервоприводом MQTT
*/
#include <M5Stack.h>
#include <WiFiManager.h> // https://github.com/tzapu/WiFiManager
#include <Preferences.h> //Сохранение настроек хеша прошивки
#include <HTTPClient.h>
#include <ESP32httpUpdate.h> //Библиотека ОТА обновлений
//#include "M5Atom.h" //Библиотека атома для функции Led и Кнопки, можно упразднить и убрать
#include <WiFiUdp.h> //Udp клиент
#include <NTPClient.h> //NTP запрос времени
#include <TimeLib.h> //Внутреннее время
#include <Wire.h> //Библиотека дял I2C
#include <PubSubClient.h> //Mtqq
#include <ArduinoJson.h> //Упакова в JSon - удобная библиотека
#include <ESPmDNS.h>
#include <ESP32Servo.h>
//Наша кнопочка при нажатии на которую произойдет вызов wifi менеджера и перезагрузка в станцию
#define TRIGGER_PIN 39
unsigned int VersionSW = 1; //65536 Версия прошивки
Servo servoAir;
int posServo = 0;
int posServoLast = 0;
byte errorID = 0;
WiFiManager wm; // обьект менеджера
WiFiManagerParameter custom_field;
Preferences OTApreferences; //Обьект хранения настроек хеша прошивки
String JsonData;
StaticJsonDocument<200> doc, docResult;
WiFiClient espClient;
PubSubClient MqttClient(espClient);
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "0.ua.pool.ntp.org", 7200, 60000); //Собственно сервер времени смещение и частоат запроса, но он вручную
const char *spacePref = "ota-config";
const PROGMEM char *mqttHostName = "cctv.automation.art"; //Хостнейм брокера 192.168.89.210 cctv.automation.art:8889
unsigned int mqttPort = 8889; //Порт брокера 1883
const PROGMEM char *mqttLogin = NULL, //Логин пароль - необходимо сменить код при connect()
*mqttPass = NULL;
const char *mqttIPHost; //тут хранится IP хоста по хостнейму
unsigned long timingUpdate, timingReqSensor, timingSendMqtt, timingAjaxUpdate; //Таймеры для millis()
int PROGMEM nextM5Update = 450000; //каждые 7.5 минут запрос обновления с сервера
int PROGMEM nextReqSensor = 10000; //опрос датчиков раз в 10 секунд
int PROGMEM nextMqttSend = 3000;
String getMacAddress();
String macc = getMacAddress();
bool mqttSendFlag = false;
int reqCounter = 0;
long rssi = 0;
byte MapAngle, SetAngle, SpeedAngle = 10;
char bufTopic[140], bufWillTopic[150], bufAngleTopic[150];
//Настройки
void setup()
{
M5.begin(true, false, true);
M5.Power.begin();
M5.Lcd.clear(WHITE);
M5.Lcd.setTextSize(4);
M5.Lcd.setTextColor(BLACK);
pinMode(25, OUTPUT);
digitalWrite(25, LOW);
servoAir.attach(25);
String topicTemp = "aastudio/" + macc;
String willTopicTemp = topicTemp + "/status";
String angleTopicTemp = topicTemp + "/setangle";
Serial.println(topicTemp);
Serial.println(willTopicTemp);
Serial.println(angleTopicTemp);
topicTemp.toCharArray(bufTopic, topicTemp.length() + 1);
willTopicTemp.toCharArray(bufWillTopic, willTopicTemp.length() + 1);
angleTopicTemp.toCharArray(bufAngleTopic, angleTopicTemp.length() + 1);
WiFi.mode(WIFI_STA);
Serial.begin(115200);
//Serial.setDebugOutput(true);
delay(100);
Serial.println("\n Starting station");
pinMode(TRIGGER_PIN, INPUT);
//int customFieldLength = 40;
const char *custom_radio_str = "<br/><label for='customfieldid'>Custom Field Label</label><input type='radio' name='customfieldid' value='1' checked> One<br><input type='radio' name='customfieldid' value='2'> Two<br><input type='radio' name='customfieldid' value='3'> Three";
new (&custom_field) WiFiManagerParameter(custom_radio_str); // custom html input
wm.addParameter(&custom_field);
wm.setSaveParamsCallback(saveParamCallback);
std::vector<const char *> menu = {"wifi", "info", "param", "sep", "restart", "exit"};
wm.setMenu(menu);
wm.setClass("invert");
wm.setConfigPortalTimeout(90); // auto close configportal after n seconds
wm.setWiFiAutoReconnect(true);
bool res;
res = wm.autoConnect("AirAnglePortal", "12345678"); // Подключение к анонимной точке доступа
if (!res)
{
Serial.println("Failed to connect or hit timeout");
ESP.restart();
}
else
{
Serial.println("connected client - OK");
}
//Запрос IP сервера MQTT и установка сервера
setMqttServer();
reqNtpTime();
}
//Установка сервера и порта
void setMqttServer()
{
mdns_init();
IPAddress IpMqtt, ipaddr;
ipaddr = MDNS.queryHost(mqttHostName); // .local omitted
// Serial.println(ipaddr.toString());
if (ipaddr.toString() == "0.0.0.0")
{
Serial.println("Trying again to resolve mDNS");
int err = WiFi.hostByName(mqttHostName, IpMqtt);
if (err == 1)
{
delay(50);
setServCall(IpMqtt);
}
else
{
Serial.print("Error code hostByName(): ");
Serial.println(err);
}
}
else
{
setServCall(ipaddr);
}
}
void setServCall(IPAddress SetIpaddr)
{
MqttClient.setServer(SetIpaddr, mqttPort);
MqttClient.setCallback(callback);
}
void moveServo(int SetAngleTmp, int SpeedAngleTmp, int posServoTmp)
{
if ((SetAngleTmp >= 0 && SetAngleTmp <= 100) && (SpeedAngleTmp >= 0 && SpeedAngleTmp <= 50))
{
MapAngle = map(SetAngleTmp, 0, 100, 2, 178);
// 544 мкс — для 0° и 2400 мкс — для 180°.
if (posServoTmp < MapAngle)
{
for (int tok = posServoTmp; tok <= MapAngle; tok++)
{
servoAir.write(tok);
delay(SpeedAngleTmp);
}
}
else
{
for (int tok = posServoTmp; tok >= MapAngle; tok--)
{
servoAir.write(tok);
delay(SpeedAngleTmp);
}
}
//servoAir.write(SetAngle);
posServo = (int)MapAngle;
SpeedAngle = SpeedAngleTmp;
SetAngle = SetAngleTmp;
OTApreferences.begin(spacePref, false);
OTApreferences.putString("stateangle", String(MapAngle));
OTApreferences.putString("percent", String(SpeedAngle));
OTApreferences.putString("us", String(SetAngle));
Serial.print("OTApreferences.getString(stateangle): ");
Serial.println(OTApreferences.getString("stateangle"));
OTApreferences.end();
drawLcd();
}
}
void drawLcd()
{
M5.Lcd.clear(WHITE);
M5.Lcd.setCursor(20, 50);
M5.Lcd.setTextColor(BLACK);
M5.Lcd.print(posServo);
M5.Lcd.println("<");
M5.Lcd.setCursor(20, 120);
M5.Lcd.setTextColor(BLACK);
M5.Lcd.print(SetAngle);
M5.Lcd.println("%");
M5.Lcd.setCursor(150, 120);
M5.Lcd.setTextColor(BLACK);
M5.Lcd.print(SpeedAngle);
M5.Lcd.println("uS");
}
//Функция получения данных из MQTT если мы подпишемся на топики
void callback(char *topic, byte *payload, unsigned int length)
{
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
char jsonResult[250];
for (int i = 0; i < length; i++)
{
Serial.print((char)payload[i]);
jsonResult[i] = (char)payload[i];
}
DeserializationError error = deserializeJson(docResult, jsonResult);
if (error)
{
Serial.print(F("deserializeJson() failed: "));
//Serial.println(String(error));
return;
}
// {"percent":50,"speed":20}
//сделать проверку на байті
SetAngle = docResult["percent"];
SpeedAngle = docResult["speed"]; //0-100
// byte isTimer = docResult["timer"];
// byte isTimerAngle = docResult["timerangle"];
Serial.println(SetAngle);
moveServo(SetAngle, SpeedAngle, posServo);
}
// Serial.println(isTimer);
// Serial.println(isTimerAngle);
//Запрос времени NTP и установка локлаьного времени
void reqNtpTime()
{
timeClient.update();
setTime(timeClient.getEpochTime());
// Serial.println(timeClient.getEpochTime());
// Serial.println("<=ntp====now=>");
//time_t t = now();
// Serial.println(t);
}
//Нажатие кнопки для сброса
void checkButton()
{
disconnectMQTT();
wm.resetSettings();
ESP.restart();
}
void saveParamCallback()
{
Serial.println("[CALLBACK] saveParamCallback fired");
Serial.println("PARAM customfieldid = " + getParam("customfieldid"));
}
String getParam(String name)
{
String value;
if (wm.server->hasArg(name))
{
value = wm.server->arg(name);
}
return value;
}
//Обновление прошивки, происходит проверка и загрузка
//Делается Get запрос на хостинг проверяется хеш, если хеш
void OTAUpdate()
{
Serial.println("OTAUpdate()");
bool flagOTA = false;
String keyOTA;
String payload;
OTApreferences.begin(spacePref, false);
if (WiFi.status() == WL_CONNECTED)
{
HTTPClient http;
String serverPath = "http://meteosence.s-host.net/airqa/airquality.php?meteopas=1234567890&mac=" + macc + "&meteodata=gethash";
http.begin(serverPath.c_str());
int httpResponseCode = http.GET();
if (httpResponseCode > 0)
{
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
payload = http.getString();
Serial.println(payload);
if (payload != "errno" || payload != "errfi")
{
keyOTA = OTApreferences.getString("md5HashOTA");
if (keyOTA.length() <= 0)
{
OTApreferences.putString("md5HashOTA", "undifined");
}
keyOTA = OTApreferences.getString("md5HashOTA");
if (payload != keyOTA)
{
flagOTA = true;
OTApreferences.putString("md5HashOTA", payload);
Serial.println("flagOTA = true;");
}
}
else
{
Serial.println("Hosting return error HASH or error REQUEST");
}
}
else
{
Serial.print("Error code HTTP: ");
Serial.println(httpResponseCode);
}
// Free resources
http.end();
}
else
{
Serial.println("WiFi Disconnected");
}
if (flagOTA == true)
{
flagOTA = false;
Serial.println("flagOTA = false;");
t_httpUpdate_return ret = ESPhttpUpdate.update("http://meteosence.s-host.net/airqa/safeatoms.bin");
//После update ничего не происходит, такая вот особенность.
//Если все прошло хорошо, перезагрузка на новую прошивку
Serial.print("ret ");
Serial.println(ret);
switch (ret)
{
case HTTP_UPDATE_FAILED:
Serial.printf("HTTP_UPDATE_FAILD Error (%d): %s", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());
Serial.println(ESPhttpUpdate.getLastError());
Serial.println(ESPhttpUpdate.getLastErrorString().c_str());
Serial.println("HTTP_UPDATE_FAILD Error");
delay(3000);
ESP.restart();
break;
case HTTP_UPDATE_NO_UPDATES:
Serial.println("HTTP_UPDATE_NO_UPDATES");
delay(3000);
ESP.restart();
break;
case HTTP_UPDATE_OK:
Serial.println("HTTP_UPDATE_OK");
delay(3000);
ESP.restart();
break;
}
}
OTApreferences.end();
}
void ledset(char color, bool blink = false)
{
// M5.dis.setBrightness(120); //Половина яркости
switch (color)
{
case 'g':
// M5.dis.drawpix(0, 0xf00000); //Зеленый
break;
case 'r':
// M5.dis.drawpix(0, 0x00f000); //Красный
break;
case 'b':
// M5.dis.drawpix(0, 0x0000f0); //Синий
break;
case 'w':
// M5.dis.drawpix(0, 0x707070); //Белый
break;
default:
// M5.dis.clear();
break;
}
}
//Получение мак адреса
String getMacAddress()
{
uint8_t baseMac[6];
esp_read_mac(baseMac, ESP_MAC_WIFI_STA);
char baseMacChr[18] = {0};
sprintf(baseMacChr, "%02X%02X%02X%02X%02X%02X", baseMac[0], baseMac[1], baseMac[2], baseMac[3], baseMac[4], baseMac[5]);
return String(baseMacChr);
}
//Отправка данных по MQTT
void SendMqttReq()
{
Serial.println("Data in SendMqttReq()");
rssi = WiFi.RSSI();
// doc["mac"] = String(getMacAddress());
doc["swver"] = VersionSW;
doc["anglestate"] = String(servoAir.read());
doc["percent"] = SetAngle;
doc["rssi"] = rssi;
doc["lasterror"] = errorID;
doc["time"] = now();
char resultString[200];
String JsonData = "";
serializeJson(doc, JsonData);
Serial.println(JsonData); //Вывод JSON строки в консоль
JsonData.toCharArray(resultString, JsonData.length() + 1);
if (mqttSendFlag == true)
{
MqttClient.publish(bufTopic, resultString, true);
//Serial.println("SentToTopic - ok");
}
}
//Переподключение при петери связи с MQTT
//10 раз проверили и вернулись в общий цикл что бы вдруг что втянуть обновления
void reconnectMqtt()
{
//clientId += String(random(0xffff), HEX);
//clientId.c_str()
//MqttClient.connect(macc.c_str(), mqttLogin, mqttPass
byte circle = 0;
while (!MqttClient.connected())
{
Serial.println("MQTT reconnect...");
circle++;
if (circle == 2)
{
break;
}
const char *clientId = macc.c_str();
//macc += String(random(0xffff), HEX);
if (MqttClient.connect(clientId, bufWillTopic, 2, true, "OFFLINE")) //mqttLogin, mqttPass
{
MqttClient.publish(bufTopic, "CONNECT", true);
MqttClient.publish(bufWillTopic, "ONLINE", true);
MqttClient.subscribe(bufAngleTopic);
mqttSendFlag = true;
}
else
{
////Serial.print("MqttClient.state() = ");
////Serial.println(MqttClient.state());
/*
-4 : MQTT_CONNECTION_TIMEOUT - the server didn't respond within the keepalive time
-3 : MQTT_CONNECTION_LOST - the network connection was broken
-2 : MQTT_CONNECT_FAILED - the network connection failed
-1 : MQTT_DISCONNECTED - the client is disconnected cleanly
0 : MQTT_CONNECTED - the client is connected
1 : MQTT_CONNECT_BAD_PROTOCOL - the server doesn't support the requested version of MQTT
2 : MQTT_CONNECT_BAD_CLIENT_ID - the server rejected the client identifier
3 : MQTT_CONNECT_UNAVAILABLE - the server was unable to accept the connection
4 : MQTT_CONNECT_BAD_CREDENTIALS - the username/password were rejected
5 : MQTT_CONNECT_UNAUTHORIZED - the client was not authorized to connect
*/
mqttSendFlag = false;
delay(3000);
}
}
}
void loop()
{
//Проверка старта сервера
M5.update();
if (!MqttClient.connected())
{
reconnectMqtt();
}
if (M5.BtnA.wasPressed())
{
if (SetAngle >= 0 && SetAngle < 100)
{
SetAngle += 1;
moveServo(SetAngle, SpeedAngle, posServo);
}
}
if (M5.BtnB.wasPressed())
{
if (SetAngle > 0 && SetAngle <= 100)
{
SetAngle -= 1;
moveServo(SetAngle, SpeedAngle, posServo);
}
}
if (M5.BtnC.wasPressed())
{
SpeedAngle += 5;
if (SpeedAngle > 50)
{
SpeedAngle = 0;
}
drawLcd();
}
//Если не определен IP то и не будет отправки.
// 40 секунд и происходит сброс настроек WIFI
// if (M5.Btn.wasReleasefor(40000))
// {
// checkButton();
// }
//По таймеру запруск обновления прошивки
if (millis() - timingUpdate > nextM5Update)
{
reqNtpTime();
OTAUpdate();
Serial.print("OTAUpdate() ");
// Serial.println(millis());
timingUpdate = millis();
}
if (posServo != posServoLast)
{
SendMqttReq();
posServoLast = posServo;
}
MqttClient.loop();
}
int disconnectMQTT()
{
MqttClient.publish(bufTopic, "DISCONNECTED", true);
MqttClient.disconnect();
return 0;
}