#include "sdkconfig.h" // system includes #include #include // esp-idf includes #include #include #if defined(CONFIG_ESP_TASK_WDT_PANIC) || defined(CONFIG_ESP_TASK_WDT) #include #include #endif #ifdef CONFIG_APP_ROLLBACK_ENABLE #include #endif #include #include #include #include #include // Arduino includes #include #include // 3rdparty lib includes #include #include #include #include // local includes #include "espwifistack.h" #include "tickchrono.h" #include "wrappers/mqtt_client.h" using namespace std::chrono_literals; namespace { constexpr const char * const TAG = "MAIN"; constexpr const bool invertLamp = true; constexpr const bool invertSwitch = true; constexpr const gpio_num_t pins_lamp = GPIO_NUM_25; constexpr const gpio_num_t pins_switch = GPIO_NUM_35; constexpr const gpio_num_t pins_sda = GPIO_NUM_16; constexpr const gpio_num_t pins_scl = GPIO_NUM_17; constexpr const gpio_num_t pins_dht = GPIO_NUM_33; constexpr const std::string_view broker_url = "mqtt://192.168.0.2/"; constexpr const std::string_view topic_lamp_availability = "dahoam/wohnzimmer/deckenlicht1/available"; constexpr const std::string_view topic_lamp_status = "dahoam/wohnzimmer/deckenlicht1/status"; constexpr const std::string_view topic_lamp_set = "dahoam/wohnzimmer/deckenlicht1/set"; constexpr const std::string_view topic_switch_availability = "dahoam/wohnzimmer/schalter1/available"; constexpr const std::string_view topic_switch_status = "dahoam/wohnzimmer/schalter1/status"; constexpr const std::string_view topic_tsl2561_availability = "dahoam/wohnzimmer/tsl2561_1/available"; constexpr const std::string_view topic_tsl2561_lux = "dahoam/wohnzimmer/tsl2561_1/lux"; constexpr const std::string_view topic_bmp085_availability = "dahoam/wohnzimmer/bmp085_1/available"; constexpr const std::string_view topic_bmp085_pressure = "dahoam/wohnzimmer/bmp085_1/pressure"; constexpr const std::string_view topic_bmp085_temperature = "dahoam/wohnzimmer/bmp085_1/temperature"; constexpr const std::string_view topic_bmp085_altitude = "dahoam/wohnzimmer/bmp085_1/altitude"; constexpr const std::string_view topic_dht11_availability = "dahoam/wohnzimmer/dht11_1/available"; constexpr const std::string_view topic_dht11_temperature = "dahoam/wohnzimmer/dht11_1/temperature"; constexpr const std::string_view topic_dht11_humidity = "dahoam/wohnzimmer/dht11_1/humidity"; constexpr const auto availableTimeoutTime = 1min; constexpr const auto valueUpdateInterval = 15s; std::atomic mqttConnected; espcpputils::mqtt_client mqttClient; std::atomic lampState; std::atomic switchState; uint8_t switchDebounce{}; //espchrono::millis_clock::time_point lastLampToggle; //espchrono::millis_clock::time_point lastSwitchToggle; Adafruit_TSL2561_Unified tsl{TSL2561_ADDR_FLOAT, 12345}; struct TslValue { espchrono::millis_clock::time_point timestamp; float lux; }; std::optional lastTslValue; espchrono::millis_clock::time_point last_tsl2561_lux_pub; Adafruit_BMP085_Unified bmp{10085}; struct BmpValue { espchrono::millis_clock::time_point timestamp; float pressure; float temperature; float altitude; }; std::optional lastBmpValue; espchrono::millis_clock::time_point last_bmp085_pressure_pub; espchrono::millis_clock::time_point last_bmp085_temperature_pub; espchrono::millis_clock::time_point last_bmp085_altitude_pub; DHT dht(pins_dht, DHT11); struct DhtValue { espchrono::millis_clock::time_point timestamp; float temperature; float humidity; }; std::optional lastDhtValue; espchrono::millis_clock::time_point last_dht11_temperature_pub; espchrono::millis_clock::time_point last_dht11_humidity_pub; esp_err_t webserver_root_handler(httpd_req_t *req); esp_err_t webserver_on_handler(httpd_req_t *req); esp_err_t webserver_off_handler(httpd_req_t *req); esp_err_t webserver_toggle_handler(httpd_req_t *req); esp_err_t webserver_reboot_handler(httpd_req_t *req); void mqttEventHandler(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data); int mqttVerbosePub(std::string_view topic, std::string_view value, int qos, int retain); void verboseDigitalWrite(uint8_t pin, uint8_t val); void writeLamp(bool state); bool readSwitch(); } // namespace extern "C" void app_main() { #if defined(CONFIG_ESP_TASK_WDT_PANIC) || defined(CONFIG_ESP_TASK_WDT) { const auto taskHandle = xTaskGetCurrentTaskHandle(); if (!taskHandle) { ESP_LOGE(TAG, "could not get handle to current main task!"); } else if (const auto result = esp_task_wdt_add(taskHandle); result != ESP_OK) { ESP_LOGE(TAG, "could not add main task to watchdog: %s", esp_err_to_name(result)); } } #endif #ifdef CONFIG_APP_ROLLBACK_ENABLE esp_ota_img_states_t ota_state; if (const esp_partition_t * const running = esp_ota_get_running_partition()) { if (const auto result = esp_ota_get_state_partition(running, &ota_state); result == ESP_ERR_NOT_FOUND) ota_state = ESP_OTA_IMG_VALID; else if (result != ESP_OK) { ESP_LOGE(TAG, "esp_ota_get_state_partition() failed with %s", esp_err_to_name(result)); ota_state = ESP_OTA_IMG_UNDEFINED; } } else { ESP_LOGE(TAG, "esp_ota_get_running_partition() returned nullptr"); ota_state = ESP_OTA_IMG_UNDEFINED; } #endif { const auto result = nvs_flash_init(); ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "nvs_flash_init(): %s", esp_err_to_name(result)); //if (result != ESP_OK) // return result; } nvs_handle_t nvsHandle; { const auto result = nvs_open("deckenlampe", NVS_READWRITE, &nvsHandle); ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "nvs_open(): %s", esp_err_to_name(result)); //if (result != ESP_OK) // return result; } wifi_stack::config wifiConfig { .wifiEnabled = true, .hostname = "deckenlampe1", .wifis = std::array { wifi_stack::wifi_entry { .ssid = "ScheissAP", .key = "Passwort_123" }, wifi_stack::wifi_entry { .ssid = {}, .key = {} }, wifi_stack::wifi_entry { .ssid = {}, .key = {} }, wifi_stack::wifi_entry { .ssid = {}, .key = {} }, wifi_stack::wifi_entry { .ssid = {}, .key = {} }, wifi_stack::wifi_entry { .ssid = {}, .key = {} }, wifi_stack::wifi_entry { .ssid = {}, .key = {} }, wifi_stack::wifi_entry { .ssid = {}, .key = {} }, wifi_stack::wifi_entry { .ssid = {}, .key = {} }, wifi_stack::wifi_entry { .ssid = {}, .key = {} } }, .sta_ip = { .dhcpEnabled = true, // .staticIp = {}, // .staticGateway = {}, // .staticSubnet = {}, // .staticDns1 = {}, // .staticDns2 = {} }, .ap = { { .ssid = "deckenlampe1", .key = "Passwort_123" }, .channel = 1, .authmode = WIFI_AUTH_WPA2_PSK, .ssid_hidden = false, .max_connection = 4, .beacon_interval = 100, .ip{192, 168, 4, 1}, .subnet{255, 255, 255, 0} }, .min_rssi = -90 }; wifi_stack::init(wifiConfig); pinMode(pins_lamp, OUTPUT); writeLamp(lampState); pinMode(pins_switch, INPUT); httpd_handle_t httpdHandle{}; { httpd_config_t httpConfig HTTPD_DEFAULT_CONFIG(); const auto result = httpd_start(&httpdHandle, &httpConfig); ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "httpd_start(): %s", esp_err_to_name(result)); //if (result != ESP_OK) // return result; } { httpd_uri_t uri { .uri = "/", .method = HTTP_GET, .handler = webserver_root_handler, .user_ctx = NULL }; const auto result = httpd_register_uri_handler(httpdHandle, &uri); ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "httpd_register_uri_handler() for %s: %s", "/", esp_err_to_name(result)); //if (result != ESP_OK) // return result; } { httpd_uri_t uri { .uri = "/on", .method = HTTP_GET, .handler = webserver_on_handler, .user_ctx = NULL }; const auto result = httpd_register_uri_handler(httpdHandle, &uri); ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "httpd_register_uri_handler() for %s: %s", "/on", esp_err_to_name(result)); //if (result != ESP_OK) // return result; } { httpd_uri_t uri { .uri = "/off", .method = HTTP_GET, .handler = webserver_off_handler, .user_ctx = NULL }; const auto result = httpd_register_uri_handler(httpdHandle, &uri); ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "httpd_register_uri_handler() for %s: %s", "/off", esp_err_to_name(result)); //if (result != ESP_OK) // return result; } { httpd_uri_t uri { .uri = "/toggle", .method = HTTP_GET, .handler = webserver_toggle_handler, .user_ctx = NULL }; const auto result = httpd_register_uri_handler(httpdHandle, &uri); ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "httpd_register_uri_handler() for %s: %s", "/toggle", esp_err_to_name(result)); //if (result != ESP_OK) // return result; } { httpd_uri_t uri { .uri = "/reboot", .method = HTTP_GET, .handler = webserver_reboot_handler, .user_ctx = NULL }; const auto result = httpd_register_uri_handler(httpdHandle, &uri); ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "httpd_register_uri_handler() for %s: %s", "/reboot", esp_err_to_name(result)); //if (result != ESP_OK) // return result; } { const auto result = mdns_init(); ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "mdns_init(): %s", esp_err_to_name(result)); //if (result != ESP_OK) // return result; } { const auto result = mdns_hostname_set(wifiConfig.hostname.c_str()); ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "mdns_hostname_set(): %s", esp_err_to_name(result)); //if (result != ESP_OK) // return result; } { const auto result = mdns_instance_name_set(wifiConfig.hostname.c_str()); ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "mdns_instance_name_set(): %s", esp_err_to_name(result)); //if (result != ESP_OK) // return result; } { const auto result = mdns_service_add(NULL, "_http", "_tcp", 80, NULL, 0); ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "mdns_service_add(): %s", esp_err_to_name(result)); //if (result != ESP_OK) // return result; } { esp_mqtt_client_config_t mqtt_cfg = { .uri = broker_url.data(), }; mqttClient = espcpputils::mqtt_client{&mqtt_cfg}; if (!mqttClient) { ESP_LOGE(TAG, "error while initializing mqtt client!"); return; } else { { const auto result = mqttClient.register_event(MQTT_EVENT_ANY, mqttEventHandler, NULL); ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "esp_mqtt_client_register_event(): %s", esp_err_to_name(result)); //if (result != ESP_OK) // return result; } const auto result = mqttClient.start(); ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "esp_mqtt_client_start(): %s", esp_err_to_name(result)); //if (result != ESP_OK) // return result; } } espchrono::millis_clock::time_point lastValuesRead; { ESP_LOGI(TAG, "calling Wire.begin()..."); const auto result = Wire.begin(pins_sda, pins_scl); ESP_LOGI(TAG, "finished with %s", result ? "true" : "false"); } bool tslInitialized{}; { ESP_LOGI(TAG, "calling tsl.begin()..."); tslInitialized = tsl.begin(true); ESP_LOGI(TAG, "finished with %s", tslInitialized ? "true" : "false"); if (tslInitialized) { sensor_t sensor = tsl.getSensor(); ESP_LOGI(TAG, "------------------------------------"); ESP_LOGI(TAG, "Sensor: %s", sensor.name); ESP_LOGI(TAG, "Driver Ver: %i", sensor.version); ESP_LOGI(TAG, "Unique ID: %i", sensor.sensor_id); ESP_LOGI(TAG, "Max Value: %.1f lux", sensor.max_value); ESP_LOGI(TAG, "Min Value: %.1f lux", sensor.min_value); ESP_LOGI(TAG, "Resolution: %.1f lux", sensor.resolution); ESP_LOGI(TAG, "------------------------------------"); ESP_LOGI(TAG, ""); /* You can also manually set the gain or enable auto-gain support */ // tsl.setGain(TSL2561_GAIN_1X); /* No gain ... use in bright light to avoid sensor saturation */ // tsl.setGain(TSL2561_GAIN_16X); /* 16x gain ... use in low light to boost sensitivity */ tsl.enableAutoRange(true); /* Auto-gain ... switches automatically between 1x and 16x */ /* Changing the integration time gives you better sensor resolution (402ms = 16-bit data) */ tsl.setIntegrationTime(TSL2561_INTEGRATIONTIME_13MS); /* fast but low resolution */ // tsl.setIntegrationTime(TSL2561_INTEGRATIONTIME_101MS); /* medium resolution and speed */ // tsl.setIntegrationTime(TSL2561_INTEGRATIONTIME_402MS); /* 16-bit data but slowest conversions */ /* Update these values depending on what you've set above! */ ESP_LOGI(TAG, "------------------------------------"); ESP_LOGI(TAG, "Gain: Auto"); ESP_LOGI(TAG, "Timing: 13 ms"); ESP_LOGI(TAG, "------------------------------------"); } } bool bmpInitialized{}; { ESP_LOGI(TAG, "calling bmp.begin()..."); bmpInitialized = bmp.begin(); ESP_LOGI(TAG, "finished with %s", bmpInitialized ? "true" : "false"); if (bmpInitialized) { sensor_t sensor = bmp.getSensor(); ESP_LOGI(TAG, "------------------------------------"); ESP_LOGI(TAG, "Sensor: %s", sensor.name); ESP_LOGI(TAG, "Driver Ver: %i", sensor.version); ESP_LOGI(TAG, "Unique ID: %i", sensor.sensor_id); ESP_LOGI(TAG, "Max Value: %.1f hPa", sensor.max_value); ESP_LOGI(TAG, "Min Value: %.1f hPa", sensor.min_value); ESP_LOGI(TAG, "Resolution: %.1f hPa", sensor.resolution); ESP_LOGI(TAG, "------------------------------------"); ESP_LOGI(TAG, ""); } } bool dhtInitialized{}; { ESP_LOGI(TAG, "calling dht.begin()..."); dhtInitialized = dht.begin(); ESP_LOGI(TAG, "finished with %s", dhtInitialized ? "true" : "false"); } while (true) { wifi_stack::update(wifiConfig); // if (espchrono::ago(lastLampToggle) >= 10s) // { // lastLampToggle = espchrono::millis_clock::now(); // lampState = !lampState; // writeLamp(lampState); // if (mqttClient && mqttConnected) // mqttVerbosePub(topic_lamp_status, lampState ? "ON" : "OFF", 0, 1); // } // if (espchrono::ago(lastSwitchToggle) >= 30s) // { // lastSwitchToggle = espchrono::millis_clock::now(); // switchState = !switchState; // if (mqttClient && mqttConnected) // mqttVerbosePub(topic_switch_status, switchState ? "ON" : "OFF", 0, 1); // } if (espchrono::ago(lastValuesRead) >= 2s) { lastValuesRead = espchrono::millis_clock::now(); if (tslInitialized) { if (std::optional event = tsl.getEvent()) { /* Display the results (light is measured in lux) */ if (event->light) { TslValue tslValue { .timestamp = espchrono::millis_clock::now(), .lux = event->light }; ESP_LOGI(TAG, "read tsl: %.1f lux", tslValue.lux); if (mqttClient && mqttConnected) { if (!lastTslValue) mqttVerbosePub(topic_tsl2561_availability, "online", 0, 1); if (!lastTslValue || espchrono::ago(last_tsl2561_lux_pub) >= valueUpdateInterval) { if (mqttVerbosePub(topic_tsl2561_lux, fmt::format("{:.1f}", tslValue.lux), 0, 1) >= 0) last_tsl2561_lux_pub = espchrono::millis_clock::now(); } } lastTslValue = tslValue; } else { /* If event.light = 0 lux the sensor is probably saturated * and no reliable data could be generated! */ ESP_LOGW(TAG, "tsl sensor overload %f", event->light); goto tslOffline; } } else { ESP_LOGW(TAG, "tsl failed"); goto tslOffline; } } else { tslOffline: if (lastTslValue && espchrono::ago(lastTslValue->timestamp) >= availableTimeoutTime) { ESP_LOGW(TAG, "tsl timeouted"); if (mqttClient && mqttConnected) mqttVerbosePub(topic_tsl2561_availability, "offline", 0, 1); lastTslValue = std::nullopt; } } if (bmpInitialized) { if (std::optional values = bmp.getTemperatureAndPressure()) { if (values->temperature && values->pressure) { BmpValue bmpValue { .timestamp = espchrono::millis_clock::now(), .pressure = values->pressure, .temperature = values->temperature }; ESP_LOGI(TAG, "read bmp Pressure: %.1f hPa", bmpValue.pressure); ESP_LOGI(TAG, "read bmp Temperature: %.1f C", bmpValue.temperature); /* Then convert the atmospheric pressure, and SLP to altitude */ /* Update this next line with the current SLP for better results */ constexpr const float seaLevelPressure = SENSORS_PRESSURE_SEALEVELHPA; bmpValue.altitude = bmp.pressureToAltitude(seaLevelPressure, bmpValue.pressure); ESP_LOGI(TAG, "read bmp Altitude: %.1f m", bmpValue.altitude); if (mqttClient && mqttConnected) { if (!lastBmpValue) mqttVerbosePub(topic_bmp085_availability, "online", 0, 1); if (!lastBmpValue || espchrono::ago(last_bmp085_pressure_pub) >= valueUpdateInterval) { if (mqttVerbosePub(topic_bmp085_pressure, fmt::format("{:.1f}", bmpValue.pressure), 0, 1) >= 0) last_bmp085_pressure_pub = espchrono::millis_clock::now(); } if (!lastBmpValue || espchrono::ago(last_bmp085_temperature_pub) >= valueUpdateInterval) { if (mqttVerbosePub(topic_bmp085_temperature, fmt::format("{:.1f}", bmpValue.temperature), 0, 1) >= 0) last_bmp085_temperature_pub = espchrono::millis_clock::now(); } if (!lastBmpValue || espchrono::ago(last_bmp085_altitude_pub) >= valueUpdateInterval) { if (mqttVerbosePub(topic_bmp085_altitude, fmt::format("{:.1f}", bmpValue.altitude), 0, 1) >= 0) last_bmp085_altitude_pub = espchrono::millis_clock::now(); } } lastBmpValue = bmpValue; } else { ESP_LOGW(TAG, "bmp sensor error"); goto bmpOffline; } } else { ESP_LOGW(TAG, "bmp failed"); goto bmpOffline; } } else { bmpOffline: if (lastBmpValue && espchrono::ago(lastBmpValue->timestamp) >= availableTimeoutTime) { ESP_LOGW(TAG, "bmp timeouted"); if (mqttClient && mqttConnected) mqttVerbosePub(topic_bmp085_availability, "offline", 0, 1); lastBmpValue = std::nullopt; } } if (dhtInitialized) { if (const auto data = dht.read()) { DhtValue dhtValue { .timestamp = espchrono::millis_clock::now(), .temperature = dht.readTemperature(*data), .humidity = dht.readHumidity(*data) }; ESP_LOGI(TAG, "read dht temperature: %.1f C", dhtValue.temperature); ESP_LOGI(TAG, "read dht humidity: %.1f %%", dhtValue.humidity); if (mqttClient && mqttConnected) { if (!lastDhtValue) mqttVerbosePub(topic_dht11_availability, "online", 0, 1); if (!lastDhtValue || espchrono::ago(last_dht11_temperature_pub) >= valueUpdateInterval) { if (mqttVerbosePub(topic_dht11_temperature, fmt::format("{:.1f}", dhtValue.temperature), 0, 1) >= 0) last_dht11_temperature_pub = espchrono::millis_clock::now(); } if (!lastDhtValue || espchrono::ago(last_dht11_humidity_pub) >= valueUpdateInterval) { if (mqttVerbosePub(topic_dht11_humidity, fmt::format("{:.1f}", dhtValue.humidity), 0, 1) >= 0) last_dht11_humidity_pub = espchrono::millis_clock::now(); } } lastDhtValue = dhtValue; } else { ESP_LOGW(TAG, "dht failed"); goto dhtOffline; } } else { dhtOffline: if (lastDhtValue && espchrono::ago(lastDhtValue->timestamp) >= availableTimeoutTime) { ESP_LOGW(TAG, "dht timeouted"); if (mqttClient && mqttConnected) mqttVerbosePub(topic_dht11_availability, "offline", 0, 1); lastDhtValue = std::nullopt; } } } { const auto newState = readSwitch(); if (newState == switchState.load()) switchDebounce = 0; else { switchDebounce++; if (switchDebounce >= 10) { switchDebounce = 0; switchState = newState; if (mqttClient && mqttConnected) mqttVerbosePub(topic_switch_status, switchState ? "ON" : "OFF", 0, 1); lampState = !lampState; writeLamp(lampState); if (mqttClient && mqttConnected) mqttVerbosePub(topic_lamp_status, lampState ? "ON" : "OFF", 0, 1); } } } #if defined(CONFIG_ESP_TASK_WDT_PANIC) || defined(CONFIG_ESP_TASK_WDT) if (const auto result = esp_task_wdt_reset(); result != ESP_OK) ESP_LOGE(TAG, "esp_task_wdt_reset() failed with %s", esp_err_to_name(result)); #endif #ifdef CONFIG_APP_ROLLBACK_ENABLE if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) { const auto result = esp_ota_mark_app_valid_cancel_rollback(); ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "esp_ota_mark_app_valid_cancel_rollback() returned: %s", esp_err_to_name(result)); ota_state = ESP_OTA_IMG_VALID; } #endif vPortYield(); } } #define CALL_AND_EXIT_ON_ERROR(METHOD, ...) \ if (const auto result = METHOD(__VA_ARGS__); result != ESP_OK) \ { \ ESP_LOGE(TAG, "%s() failed with %s", #METHOD, esp_err_to_name(result)); \ return result; \ } #define CALL_AND_EXIT(METHOD, ...) \ if (const auto result = METHOD(__VA_ARGS__); result != ESP_OK) \ { \ ESP_LOGE(TAG, "%s() failed with %s", #METHOD, esp_err_to_name(result)); \ return result; \ } \ else \ return result; namespace { esp_err_t webserver_root_handler(httpd_req_t *req) { std::string body = "this is work in progress...
\n" "on
\n" "off
\n" "toggle
\n" "reboot
\n" "
\n"; body += fmt::format("Lamp: {}
\n", lampState ? "ON" : "OFF"); body += fmt::format("Switch: {}
\n", switchState ? "ON" : "OFF"); if (lastTslValue) { body += fmt::format("TSL2561 Brightness: {:.1f} lux
\n", lastTslValue->lux); } if (lastBmpValue) { body += fmt::format("BMP085 Pressure: {:.1f} lux
\n", lastBmpValue->pressure); body += fmt::format("BMP085 Temperature: {:.1f} C
\n", lastBmpValue->temperature); body += fmt::format("BMP085 Altitude: {:.1f} m
\n", lastBmpValue->altitude); } if (lastDhtValue) { body += fmt::format("DHT11 Temperature: {:.1f} C
\n", lastDhtValue->temperature); body += fmt::format("DHT11 Humidity: {:.1f} %
\n", lastDhtValue->humidity); } CALL_AND_EXIT_ON_ERROR(httpd_resp_set_type, req, "text/html") CALL_AND_EXIT_ON_ERROR(httpd_resp_send, req, body.data(), body.size()) return ESP_OK; } esp_err_t webserver_on_handler(httpd_req_t *req) { const bool state = (lampState = true); writeLamp(state); if (mqttClient && mqttConnected) mqttVerbosePub(topic_lamp_status, state ? "ON" : "OFF", 0, 1); std::string_view body{"ON called..."}; CALL_AND_EXIT_ON_ERROR(httpd_resp_set_type, req, "text/html") CALL_AND_EXIT_ON_ERROR(httpd_resp_send, req, body.data(), body.size()) return ESP_OK; } esp_err_t webserver_off_handler(httpd_req_t *req) { const bool state = (lampState = false); writeLamp(state); if (mqttClient && mqttConnected) mqttVerbosePub(topic_lamp_status, state ? "ON" : "OFF", 0, 1); std::string_view body{"OFF called..."}; CALL_AND_EXIT_ON_ERROR(httpd_resp_set_type, req, "text/html") CALL_AND_EXIT_ON_ERROR(httpd_resp_send, req, body.data(), body.size()) return ESP_OK; } esp_err_t webserver_toggle_handler(httpd_req_t *req) { const bool state = (lampState = !lampState); writeLamp(state); if (mqttClient && mqttConnected) mqttVerbosePub(topic_lamp_status, state ? "ON" : "OFF", 0, 1); std::string_view body{"TOGGLE called..."}; CALL_AND_EXIT_ON_ERROR(httpd_resp_set_type, req, "text/html") CALL_AND_EXIT_ON_ERROR(httpd_resp_send, req, body.data(), body.size()) return ESP_OK; } esp_err_t webserver_reboot_handler(httpd_req_t *req) { std::string_view body{"REBOOT called..."}; CALL_AND_EXIT_ON_ERROR(httpd_resp_set_type, req, "text/html") CALL_AND_EXIT_ON_ERROR(httpd_resp_send, req, body.data(), body.size()) esp_restart(); return ESP_OK; } void mqttEventHandler(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { CPP_UNUSED(event_handler_arg) const esp_mqtt_event_t *data = reinterpret_cast(event_data); switch (esp_mqtt_event_id_t(event_id)) { case MQTT_EVENT_ERROR: ESP_LOGE(TAG, "%s event_id=%s", event_base, "MQTT_EVENT_ERROR"); // ESP_LOGI(TAG, "MQTT_EVENT_ERROR"); // if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) // { // log_error_if_nonzero("reported from esp-tls", event->error_handle->esp_tls_last_esp_err); // log_error_if_nonzero("reported from tls stack", event->error_handle->esp_tls_stack_err); // log_error_if_nonzero("captured as transport's socket errno", event->error_handle->esp_transport_sock_errno); // ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno)); // } break; case MQTT_EVENT_CONNECTED: ESP_LOGI(TAG, "%s event_id=%s", event_base, "MQTT_EVENT_CONNECTED"); mqttConnected = true; mqttVerbosePub(topic_lamp_availability, "online", 0, 1); mqttVerbosePub(topic_lamp_status, lampState.load() ? "ON" : "OFF", 0, 1); mqttVerbosePub(topic_switch_availability, "online", 0, 1); mqttVerbosePub(topic_switch_status, switchState.load() ? "ON" : "OFF", 0, 1); mqttVerbosePub(topic_tsl2561_availability, lastTslValue ? "online" : "offline", 0, 1); if (lastTslValue) { if (mqttVerbosePub(topic_tsl2561_lux, fmt::format("{:.1f}", lastTslValue->lux), 0, 1) >= 0) last_tsl2561_lux_pub = espchrono::millis_clock::now(); } mqttVerbosePub(topic_bmp085_availability, lastBmpValue ? "online" : "offline", 0, 1); if (lastBmpValue) { if (mqttVerbosePub(topic_bmp085_pressure, fmt::format("{:.1f}", lastBmpValue->pressure), 0, 1) >= 0) last_bmp085_pressure_pub = espchrono::millis_clock::now(); if (mqttVerbosePub(topic_bmp085_temperature, fmt::format("{:.1f}", lastBmpValue->temperature), 0, 1) >= 0) last_bmp085_temperature_pub = espchrono::millis_clock::now(); if (mqttVerbosePub(topic_bmp085_altitude, fmt::format("{:.1f}", lastBmpValue->altitude), 0, 1) >= 0) last_bmp085_altitude_pub = espchrono::millis_clock::now(); } mqttVerbosePub(topic_dht11_availability, lastDhtValue ? "online" : "offline", 0, 1); if (lastDhtValue) { if (mqttVerbosePub(topic_dht11_temperature, fmt::format("{:.1f}", lastDhtValue->temperature), 0, 1) >= 0) last_dht11_temperature_pub = espchrono::millis_clock::now(); if (mqttVerbosePub(topic_dht11_humidity, fmt::format("{:.1f}", lastDhtValue->humidity), 0, 1) >= 0) last_dht11_humidity_pub = espchrono::millis_clock::now(); } mqttClient.subscribe(topic_lamp_set, 0); break; case MQTT_EVENT_DISCONNECTED: ESP_LOGI(TAG, "%s event_id=%s", event_base, "MQTT_EVENT_DISCONNECTED"); mqttConnected = false; break; case MQTT_EVENT_SUBSCRIBED: ESP_LOGI(TAG, "%s event_id=%s", event_base, "MQTT_EVENT_SUBSCRIBED"); break; case MQTT_EVENT_UNSUBSCRIBED: ESP_LOGI(TAG, "%s event_id=%s", event_base, "MQTT_EVENT_UNSUBSCRIBED"); break; case MQTT_EVENT_PUBLISHED: ESP_LOGI(TAG, "%s event_id=%s", event_base, "MQTT_EVENT_PUBLISHED"); break; case MQTT_EVENT_DATA: { std::string_view topic{data->topic, (size_t)data->topic_len}; std::string_view value{data->data, (size_t)data->data_len}; ESP_LOGI(TAG, "%s event_id=%s topic=%.*s data=%.*s", event_base, "MQTT_EVENT_DATA", topic.size(), topic.data(), value.size(), value.data()); if (topic == topic_lamp_set) { bool newState = (lampState = (value == "ON")); writeLamp(newState); if (mqttClient && mqttConnected) mqttVerbosePub(topic_lamp_status, newState ? "ON" : "OFF", 0, 1); } break; } case MQTT_EVENT_BEFORE_CONNECT: ESP_LOGI(TAG, "%s event_id=%s", event_base, "MQTT_EVENT_BEFORE_CONNECT"); break; case MQTT_EVENT_DELETED: ESP_LOGI(TAG, "%s event_id=%s", event_base, "MQTT_EVENT_DELETED"); break; default: ESP_LOGW(TAG, "%s unknown event_id %i", event_base, event_id); break; } } int mqttVerbosePub(std::string_view topic, std::string_view value, int qos, int retain) { ESP_LOGD(TAG, "topic=\"%.*s\" value=\"%.*s\"", topic.size(), topic.data(), value.size(), value.data()); const auto pending_msg_id = mqttClient.publish(topic, value, qos, retain); if (pending_msg_id < 0) ESP_LOGE(TAG, "topic=\"%.*s\" value=\"%.*s\" failed pending_msg_id=%i", topic.size(), topic.data(), value.size(), value.data(), pending_msg_id); else ESP_LOGI(TAG, "topic=\"%.*s\" value=\"%.*s\" succeeded pending_msg_id=%i", topic.size(), topic.data(), value.size(), value.data(), pending_msg_id); return pending_msg_id; } void verboseDigitalWrite(uint8_t pin, uint8_t val) { ESP_LOGI(TAG, "pin=%hhu val=%hhu", pin, val); digitalWrite(pin, val); } void writeLamp(bool state) { if (invertLamp) state = !state; verboseDigitalWrite(pins_lamp, state ? HIGH : LOW); } bool readSwitch() { bool state = digitalRead(pins_switch) == HIGH; if (invertSwitch) state = !state; return state; } } // namespace auto espchrono::local_clock::timezone() noexcept -> time_zone { return time_zone{1h, DayLightSavingMode::EuropeanSummerTime}; }