270 lines
7.6 KiB
C++
270 lines
7.6 KiB
C++
#include <Adafruit_Sensor.h>
|
|
#include <ArduinoJson.h>
|
|
#include <BH1750.h>
|
|
#include <DHT.h>
|
|
#include <DHT_U.h>
|
|
#include <ESPAsyncWebServer.h>
|
|
#include <ESPDash.h>
|
|
#include <IotWebConf.h>
|
|
#include <AsyncMqttClient.h>
|
|
|
|
#define I2C_SDA 25
|
|
#define I2C_SCL 26
|
|
#define DHT12_PIN 16
|
|
#define BAT_ADC 33
|
|
#define SALT_PIN 34
|
|
#define SOIL_PIN 32
|
|
#define BOOT_PIN 0
|
|
#define POWER_CTRL 4
|
|
#define USER_BUTTON 35
|
|
|
|
BH1750 lightMeter;
|
|
DHT_Unified dht(DHT12_PIN, DHT11);
|
|
|
|
#define STRING_LEN 128
|
|
|
|
// Configuration specific key. The value should be modified if config structure was changed.
|
|
#define CONFIG_VERSION "hgr1"
|
|
|
|
// Initial name of the Thing. Used e.g. as SSID of the own Access Point.
|
|
const char thingName[] = "hiGrow";
|
|
|
|
// Initial password to connect to the Thing, when it creates an own Access Point.
|
|
const char wifiInitialApPassword[] = "test1234";
|
|
|
|
char mqttServerValue[STRING_LEN];
|
|
char mqttUserNameValue[STRING_LEN];
|
|
char mqttUserPasswordValue[STRING_LEN];
|
|
char mqttTopicValue[STRING_LEN];
|
|
IotWebConfSeparator separator1 = IotWebConfSeparator();
|
|
IotWebConfParameter mqttServerParam = IotWebConfParameter("MQTT server", "mqttServer", mqttServerValue, STRING_LEN);
|
|
IotWebConfParameter mqttUserNameParam = IotWebConfParameter("MQTT user", "mqttUser", mqttUserNameValue, STRING_LEN);
|
|
IotWebConfParameter mqttUserPasswordParam = IotWebConfParameter("MQTT password", "mqttPass", mqttUserPasswordValue, STRING_LEN, "password");
|
|
IotWebConfParameter mqttTopicParam = IotWebConfParameter("MQTT topic", "mqttTopic", mqttTopicValue, STRING_LEN);
|
|
|
|
DNSServer dnsServer;
|
|
AsyncWebServer server(80);
|
|
AsyncCallbackWebHandler* rootHandler;
|
|
IotWebConf iotWebConf(thingName, &dnsServer, &server, wifiInitialApPassword, CONFIG_VERSION);
|
|
AsyncMqttClient mqttClient;
|
|
|
|
uint16_t soil = 0;
|
|
uint32_t salt = 0;
|
|
float bat = 0;
|
|
float temperature = 0;
|
|
float humidity = 0;
|
|
float lux = 0;
|
|
|
|
bool needMqttReconnect = true;
|
|
|
|
/**
|
|
* Handle web requests to "/" path.
|
|
*/
|
|
void handleRoot(AsyncWebServerRequest* request)
|
|
{
|
|
// -- Let IotWebConf test and handle captive portal requests.
|
|
if (iotWebConf.handleCaptivePortal(request)) {
|
|
// -- Captive portal request were already served.
|
|
return;
|
|
}
|
|
String s = F("<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/>");
|
|
s += F("<title>IotWebConf 01 Minimal</title></head><body>Hello world!");
|
|
s += F("Go to <a href='config'>configure page</a> to change settings.");
|
|
s += F("</body></html>\n");
|
|
|
|
request->send(200, "text/html", s);
|
|
}
|
|
|
|
void handleValues(AsyncWebServerRequest* request)
|
|
{
|
|
StaticJsonDocument<200> doc;
|
|
doc[F("temperature")] = temperature;
|
|
doc[F("humidity")] = humidity;
|
|
doc[F("soil")] = soil;
|
|
doc[F("salt")] = salt;
|
|
doc[F("battery")] = bat;
|
|
doc[F("lux")] = lux;
|
|
|
|
String output;
|
|
serializeJson(doc, output);
|
|
|
|
request->send(200, "application/json", output);
|
|
}
|
|
|
|
void wifiConnected()
|
|
{
|
|
Serial.println("WiFi was connected.");
|
|
if (server.removeHandler(rootHandler)) {
|
|
Serial.println("Root handler disabled.");
|
|
}
|
|
ESPDash.init(server);
|
|
}
|
|
|
|
uint32_t readSalt()
|
|
{
|
|
uint8_t samples = 120;
|
|
uint32_t humi = 0;
|
|
uint16_t array[120];
|
|
|
|
for (int i = 0; i < samples; i++) {
|
|
array[i] = analogRead(SALT_PIN);
|
|
delay(2);
|
|
}
|
|
std::sort(array, array + samples);
|
|
for (int i = 0; i < samples; i++) {
|
|
if (i == 0 || i == samples - 1)
|
|
continue;
|
|
humi += array[i];
|
|
}
|
|
humi /= samples - 2;
|
|
return humi;
|
|
}
|
|
|
|
uint16_t readSoil()
|
|
{
|
|
uint16_t soil = analogRead(SOIL_PIN);
|
|
return map(soil, 0, 4095, 100, 0);
|
|
}
|
|
|
|
float readBattery()
|
|
{
|
|
int vref = 1100;
|
|
uint16_t volt = analogRead(BAT_ADC);
|
|
float battery_voltage = ((float)volt / 4095.0) * 2.0 * 3.3 * (vref);
|
|
return battery_voltage;
|
|
}
|
|
|
|
void configSaved()
|
|
{
|
|
Serial.println("Configuration was updated.");
|
|
needMqttReconnect = true;
|
|
}
|
|
|
|
boolean formValidator(AsyncWebServerRequest* request)
|
|
{
|
|
Serial.println("Validating form.");
|
|
boolean valid = true;
|
|
|
|
if (request->arg(mqttServerParam.getId()).length() > 0
|
|
&& request->arg(mqttTopicParam.getId()).length() == 0) {
|
|
mqttTopicParam.errorMessage = "Please provide at least 1 character!";
|
|
valid = false;
|
|
}
|
|
return valid;
|
|
}
|
|
|
|
void setup()
|
|
{
|
|
Serial.begin(115200);
|
|
Serial.println();
|
|
Serial.println("Starting up...");
|
|
|
|
// Init IotWebConf
|
|
iotWebConf.setWifiConnectionCallback(&wifiConnected);
|
|
iotWebConf.addParameter(&separator1);
|
|
iotWebConf.addParameter(&mqttServerParam);
|
|
iotWebConf.addParameter(&mqttUserNameParam);
|
|
iotWebConf.addParameter(&mqttUserPasswordParam);
|
|
iotWebConf.addParameter(&mqttTopicParam);
|
|
iotWebConf.setConfigSavedCallback(&configSaved);
|
|
iotWebConf.setFormValidator(&formValidator);
|
|
if (!iotWebConf.init()) {
|
|
mqttServerValue[0] = '\0';
|
|
mqttUserNameValue[0] = '\0';
|
|
mqttUserPasswordValue[0] = '\0';
|
|
}
|
|
|
|
// Init WebServer
|
|
rootHandler = new AsyncCallbackWebHandler();
|
|
rootHandler->setUri("/");
|
|
rootHandler->setMethod(HTTP_GET);
|
|
rootHandler->onRequest(handleRoot);
|
|
server.addHandler(rootHandler);
|
|
|
|
server.on("/config", HTTP_ANY, [](AsyncWebServerRequest* request) { iotWebConf.handleConfig(request); });
|
|
server.on("/values", HTTP_GET, handleValues);
|
|
server.onNotFound([](AsyncWebServerRequest* request) { iotWebConf.handleNotFound(request); });
|
|
server.begin();
|
|
|
|
// Init Dashboard
|
|
ESPDash.addTemperatureCard("temp", "Temperature/C", 0, 0);
|
|
ESPDash.addHumidityCard("hum", "Humidity/%", 0);
|
|
ESPDash.addNumberCard("lux", "BH1750/lx", 0);
|
|
ESPDash.addHumidityCard("soil", "Soil", 0);
|
|
ESPDash.addNumberCard("salt", "Salt", 0);
|
|
ESPDash.addNumberCard("batt", "Battery/mV", 0);
|
|
|
|
Wire.begin(I2C_SDA, I2C_SCL);
|
|
dht.begin();
|
|
|
|
//! Sensor power control pin , use deteced must set high
|
|
pinMode(POWER_CTRL, OUTPUT);
|
|
digitalWrite(POWER_CTRL, 1);
|
|
delay(1000);
|
|
|
|
Wire.begin();
|
|
|
|
if (lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE)) {
|
|
Serial.println(F("BH1750 Advanced begin"));
|
|
} else {
|
|
Serial.println(F("Error initialising BH1750"));
|
|
}
|
|
|
|
Serial.println("Ready.");
|
|
}
|
|
|
|
void loop()
|
|
{
|
|
static uint64_t timestamp;
|
|
iotWebConf.doLoop();
|
|
|
|
if (needMqttReconnect) {
|
|
Serial.println("Reconfigure & reconnect MqTT");
|
|
if (String(mqttServerValue) != "") {
|
|
mqttClient.setServer(mqttServerValue, 1883);
|
|
mqttClient.connect();
|
|
}
|
|
needMqttReconnect = false;
|
|
}
|
|
|
|
if (millis() - timestamp > 1000) {
|
|
timestamp = millis();
|
|
|
|
// Read brightness
|
|
lux = lightMeter.readLightLevel();
|
|
|
|
sensors_event_t event;
|
|
|
|
// Read temperature
|
|
dht.temperature().getEvent(&event);
|
|
temperature = event.temperature;
|
|
|
|
// Read humidity
|
|
dht.humidity().getEvent(&event);
|
|
humidity = event.relative_humidity;
|
|
|
|
// Read soil
|
|
soil = readSoil();
|
|
|
|
// Read salt
|
|
salt = readSalt();
|
|
|
|
// Read battery
|
|
bat = readBattery();
|
|
|
|
// Update Dashboard
|
|
if (!isnan(temperature)) {
|
|
ESPDash.updateTemperatureCard("temp", (int)temperature);
|
|
}
|
|
if (!isnan(humidity)) {
|
|
ESPDash.updateHumidityCard("hum", (int)humidity);
|
|
}
|
|
ESPDash.updateNumberCard("lux", (int)lux);
|
|
ESPDash.updateHumidityCard("soil", (int)soil);
|
|
ESPDash.updateNumberCard("salt", (int)salt);
|
|
ESPDash.updateNumberCard("batt", (int)bat);
|
|
|
|
Serial.printf("DHTT:%.2f H:%.2f Lux:%.2f Soil:%u Salt:%u Bat:%.2f\r\n",
|
|
temperature, humidity, lux, soil, salt, bat);
|
|
}
|
|
} |