diff --git a/.gitmodules b/.gitmodules index 78d5b4a..f4d2fa0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,12 @@ [submodule "components/date"] path = components/date url = git@github.com:0xFEEDC0DE64/date.git +[submodule "components/fmt"] + path = components/fmt + url = git@github.com:0xFEEDC0DE64/fmt.git +[submodule "components/Adafruit_Sensor"] + path = components/Adafruit_Sensor + url = git@github.com:0xFEEDC0DE64/Adafruit_Sensor.git +[submodule "components/Adafruit_TSL2561"] + path = components/Adafruit_TSL2561 + url = git@github.com:0xFEEDC0DE64/Adafruit_TSL2561.git diff --git a/components/Adafruit_Sensor b/components/Adafruit_Sensor new file mode 160000 index 0000000..08c7010 --- /dev/null +++ b/components/Adafruit_Sensor @@ -0,0 +1 @@ +Subproject commit 08c70104ef9d756b775faa95f25337574324354d diff --git a/components/Adafruit_TSL2561 b/components/Adafruit_TSL2561 new file mode 160000 index 0000000..f31bf77 --- /dev/null +++ b/components/Adafruit_TSL2561 @@ -0,0 +1 @@ +Subproject commit f31bf7762e9ac970cb51d9eea890c40f5035d2f1 diff --git a/components/fmt b/components/fmt new file mode 160000 index 0000000..4b11c94 --- /dev/null +++ b/components/fmt @@ -0,0 +1 @@ +Subproject commit 4b11c94036d4c5012dc4cb3854f47052f6f49812 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index c310c5a..6d52278 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -10,7 +10,8 @@ set(sources set(dependencies freertos nvs_flash esp_http_server esp_https_ota mdns app_update esp_system mqtt - arduino-esp32 cpputils espchrono espcpputils espwifistack expected + arduino-esp32 cpputils date espchrono espcpputils espwifistack expected fmt + Adafruit_Sensor Adafruit_TSL2561 ) idf_component_register( diff --git a/main/main.cpp b/main/main.cpp index ee99f4c..88886f6 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -21,6 +21,11 @@ // Arduino includes #include +#include + +// 3rdparty lib includes +#include +#include // local includes #include "espwifistack.h" @@ -32,14 +37,34 @@ using namespace std::chrono_literals; namespace { constexpr const char * const TAG = "MAIN"; +constexpr const int pins_sda = 16; +constexpr const int pins_scl = 17; + +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/lichtschalter1/available"; +constexpr const std::string_view topic_switch_status = "dahoam/wohnzimmer/lichtschalter1/status"; + +constexpr const std::string_view topic_lux_availability = "dahoam/wohnzimmer/lux1/available"; +constexpr const std::string_view topic_lux_value = "dahoam/wohnzimmer/lux1/value"; + +std::atomic mqttConnected; espcpputils::mqtt_client mqttClient; -std::atomic lightState; +std::atomic lampState; std::atomic switchState; -//espchrono::millis_clock::time_point lastLightToggle; +//espchrono::millis_clock::time_point lastLampToggle; //espchrono::millis_clock::time_point lastSwitchToggle; +Adafruit_TSL2561_Unified tsl{TSL2561_ADDR_FLOAT, 12345}; + +bool tslInitialized; + +std::optional lastTslValue; + 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); @@ -47,6 +72,8 @@ 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); } // namespace extern "C" void app_main() @@ -273,10 +300,102 @@ extern "C" void app_main() } } + { + ESP_LOGI(TAG, "calling Wire.begin()..."); + const auto result = Wire.begin(pins_sda, pins_scl); + ESP_LOGI(TAG, "finished with %s", result ? "true" : "false"); + } + + { + 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: %f lux", sensor.max_value); + ESP_LOGI(TAG, "Min Value: %f lux", sensor.min_value); + ESP_LOGI(TAG, "Resolution: %f 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, "------------------------------------"); + } + } + while (true) { wifi_stack::update(wifiConfig); +// if (espchrono::ago(lastLampToggle) >= 10s) +// { +// lastLampToggle = espchrono::millis_clock::now(); +// lampState = !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 (tslInitialized) { + if (std::optional event = tsl.getEvent()) { + /* Display the results (light is measured in lux) */ + if (event->light) { + ESP_LOGW(TAG, "%f lux", event->light); + + if (mqttClient && mqttConnected) { + if (!lastTslValue) + mqttVerbosePub(topic_lux_availability, "online", 0, 1); + if (!lastTslValue || *lastTslValue != event->light) + mqttVerbosePub(topic_lux_value, std::to_string(event->light), 0, 1); + } + + lastTslValue = event->light; + } else { + /* If event.light = 0 lux the sensor is probably saturated + * and no reliable data could be generated! */ + ESP_LOGD(TAG, "Sensor overload %f", event->light); + } + } else { + ESP_LOGW(TAG, "tsl failed"); + if (lastTslValue) { + if (mqttClient && mqttConnected) + mqttVerbosePub(topic_lux_availability, "offline", 0, 1); + lastTslValue = std::nullopt; + } + } + } else if (lastTslValue) { + if (mqttClient && mqttConnected) + mqttVerbosePub(topic_lux_availability, "offline", 0, 1); + lastTslValue = std::nullopt; + } + #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)); @@ -291,39 +410,9 @@ extern "C" void app_main() } #endif -// if (espchrono::ago(lastLightToggle) >= 10s) -// { -// lastLightToggle = espchrono::millis_clock::now(); -// lightState = !lightState; - -// if (mqttClient) -// { -// std::string_view topic = "dahoam/wohnzimmer/deckenlicht1/status"; -// std::string_view value = lightState ? "ON" : "OFF"; - -// ESP_LOGI(TAG, "sending %.*s = %.*s", topic.size(), topic.data(), value.size(), value.data()); -// mqttClient.publish(topic, value, 0, 1); -// } -// } - -// if (espchrono::ago(lastSwitchToggle) >= 30s) -// { -// lastSwitchToggle = espchrono::millis_clock::now(); -// switchState = !switchState; - -// if (mqttClient) -// { -// std::string_view topic = "dahoam/wohnzimmer/lichtschalter1/status"; -// std::string_view value = switchState ? "ON" : "OFF"; - -// ESP_LOGI(TAG, "sending %.*s = %.*s", topic.size(), topic.data(), value.size(), value.data()); -// mqttClient.publish(topic, value, 0, 1); -// } -// } - vPortYield(); - vTaskDelay(std::chrono::ceil(100ms).count()); + vTaskDelay(std::chrono::ceil(2000ms).count()); } } @@ -346,11 +435,24 @@ extern "C" void app_main() namespace { esp_err_t webserver_root_handler(httpd_req_t *req) { - std::string_view body{"this is work in progress...
\n" - "on
\n" - "off
\n" - "toggle
\n" - "reboot"}; + std::string body = "this is work in progress...
\n" + "on
\n" + "off
\n" + "toggle
\n" + "reboot
\n" + "
\n" + "Lamp: "; + body += lampState ? "ON" : "OFF"; + body += "
\n" + "Switch: "; + body += switchState ? "ON" : "OFF"; + body += "
\n"; + if (lastTslValue) { + body += "Brightness: "; + body += std::to_string(*lastTslValue); + body += " lux
\n"; + } + 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()) @@ -359,15 +461,10 @@ esp_err_t webserver_root_handler(httpd_req_t *req) esp_err_t webserver_on_handler(httpd_req_t *req) { - const bool state = (lightState = true); + const bool state = (lampState = true); - if (mqttClient) { - std::string_view topic = "dahoam/wohnzimmer/deckenlicht1/status"; - std::string_view value = state ? "ON" : "OFF"; - - ESP_LOGI(TAG, "sending %.*s = %.*s", topic.size(), topic.data(), value.size(), value.data()); - mqttClient.publish(topic, value, 0, 1); - } + 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") @@ -378,15 +475,10 @@ esp_err_t webserver_on_handler(httpd_req_t *req) esp_err_t webserver_off_handler(httpd_req_t *req) { - const bool state = (lightState = false); + const bool state = (lampState = false); - if (mqttClient) { - std::string_view topic = "dahoam/wohnzimmer/deckenlicht1/status"; - std::string_view value = state ? "ON" : "OFF"; - - ESP_LOGI(TAG, "sending %.*s = %.*s", topic.size(), topic.data(), value.size(), value.data()); - mqttClient.publish(topic, value, 0, 1); - } + 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") @@ -397,16 +489,10 @@ esp_err_t webserver_off_handler(httpd_req_t *req) esp_err_t webserver_toggle_handler(httpd_req_t *req) { - const bool state = (lightState = !lightState); + const bool state = (lampState = !lampState); - if (mqttClient) - { - std::string_view topic = "dahoam/wohnzimmer/deckenlicht1/status"; - std::string_view value = state ? "ON" : "OFF"; - - ESP_LOGI(TAG, "sending %.*s = %.*s", topic.size(), topic.data(), value.size(), value.data()); - mqttClient.publish(topic, value, 0, 1); - } + 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") @@ -449,18 +535,30 @@ void mqttEventHandler(void *event_handler_arg, esp_event_base_t event_base, int3 case MQTT_EVENT_CONNECTED: ESP_LOGI(TAG, "%s event_id=%s", event_base, "MQTT_EVENT_CONNECTED"); - mqttClient.publish("dahoam/wohnzimmer/deckenlicht1/available", "online", 0, 0); - mqttClient.publish("dahoam/wohnzimmer/deckenlicht1/status", lightState.load() ? "ON" : "OFF", 0, 1); + mqttConnected = true; - mqttClient.publish("dahoam/wohnzimmer/lichtschalter1/available", "online", 0, 0); - mqttClient.publish("dahoam/wohnzimmer/lichtschalter1/status", switchState.load() ? "ON" : "OFF", 0, 1); + mqttVerbosePub(topic_lamp_availability, "online", 0, 1); + mqttVerbosePub(topic_lamp_status, lampState.load() ? "ON" : "OFF", 0, 1); - mqttClient.subscribe("dahoam/wohnzimmer/deckenlicht1/set", 0); + mqttVerbosePub(topic_switch_availability, "online", 0, 1); + mqttVerbosePub(topic_switch_status, switchState.load() ? "ON" : "OFF", 0, 1); + + if (lastTslValue) { + mqttVerbosePub(topic_lux_availability, "online", 0, 1); + mqttVerbosePub(topic_lux_value, std::to_string(*lastTslValue).c_str(), 0, 1); + } else { + mqttVerbosePub(topic_lux_availability, "offline", 0, 1); + } + + 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: @@ -481,17 +579,11 @@ void mqttEventHandler(void *event_handler_arg, esp_event_base_t event_base, int3 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 == "dahoam/wohnzimmer/deckenlicht1/set") + if (topic == topic_lamp_set) { - lightState = value == "ON"; - if (mqttClient) - { - topic = "dahoam/wohnzimmer/deckenlicht1/status"; - value = lightState ? "ON" : "OFF"; - - ESP_LOGI(TAG, "sending %.*s = %.*s", topic.size(), topic.data(), value.size(), value.data()); - mqttClient.publish(topic, value, 0, 1); - } + lampState = value == "ON"; + if (mqttClient && mqttConnected) + mqttVerbosePub(topic_lamp_status, lampState ? "ON" : "OFF", 0, 1); } break; @@ -509,4 +601,10 @@ void mqttEventHandler(void *event_handler_arg, esp_event_base_t event_base, int3 break; } } + +int mqttVerbosePub(std::string_view topic, std::string_view value, int qos, int retain) +{ + ESP_LOGI(TAG, "topic=\"%.*s\" value=\"%.*s\"", topic.size(), topic.data(), value.size(), value.data()); + return mqttClient.publish(topic, value, qos, retain); +} } // namespace