diff --git a/components/vigilant_engine/include/i2c.h b/components/vigilant_engine/include/i2c.h index 93c6ab8..a46d319 100644 --- a/components/vigilant_engine/include/i2c.h +++ b/components/vigilant_engine/include/i2c.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "esp_err.h" #include "vigilant_i2c_device.h" @@ -13,6 +15,7 @@ esp_err_t i2c_remove_device(VigilantI2CDevice *device); esp_err_t i2c_set_reg8(VigilantI2CDevice *device, uint8_t reg, uint8_t value); esp_err_t i2c_read_reg8(VigilantI2CDevice *device, uint8_t reg, uint8_t *value); esp_err_t i2c_whoami_check(VigilantI2CDevice *device); +esp_err_t i2c_get_detected_devices(uint8_t *addresses, size_t max_addresses, size_t *count); void i2c_deinit(void); #ifdef __cplusplus diff --git a/components/vigilant_engine/include/vigilant.h b/components/vigilant_engine/include/vigilant.h index e3804b8..c853d01 100644 --- a/components/vigilant_engine/include/vigilant.h +++ b/components/vigilant_engine/include/vigilant.h @@ -28,8 +28,20 @@ typedef struct { char ip_ap[16]; // current AP IPv4 (or "0.0.0.0") } VigilantInfo; +typedef struct { + bool enabled; + uint8_t sda_io; + uint8_t scl_io; + uint32_t frequency_hz; + uint8_t added_device_count; + VigilantI2CDevice added_devices[8]; // fixed-size array for simplicity; adjust as needed + uint8_t detected_device_count; + uint8_t detected_devices[16]; // 7-bit addresses detected on the bus +} VigilantI2cInfo; + esp_err_t vigilant_init(VigilantConfig VgConfig); esp_err_t vigilant_get_info(VigilantInfo *info); +esp_err_t vigilant_get_i2cinfo(VigilantI2cInfo *info); esp_err_t vigilant_i2c_add_device(VigilantI2CDevice *device); esp_err_t vigilant_i2c_remove_device(VigilantI2CDevice *device); esp_err_t vigilant_i2c_set_reg8(VigilantI2CDevice *device, uint8_t reg, uint8_t value); diff --git a/components/vigilant_engine/src/http_server.c b/components/vigilant_engine/src/http_server.c index bc74390..245bf96 100644 --- a/components/vigilant_engine/src/http_server.c +++ b/components/vigilant_engine/src/http_server.c @@ -231,6 +231,119 @@ static const httpd_uri_t info_uri = { .user_ctx = NULL, }; +static esp_err_t i2cinfo_get_handler(httpd_req_t *req) +{ + VigilantI2cInfo info = {0}; + esp_err_t err = vigilant_get_i2cinfo(&info); + if (err != ESP_OK) { + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to fetch i2c info"); + return err; + } + + httpd_resp_set_type(req, "application/json"); + size_t payload_capacity = 256 // Calculates a size for the json object, for malloc later + + ((size_t)info.added_device_count * 160) // Added devices contain more information in the json than detected devices. + + ((size_t)info.detected_device_count * 96); + char *payload = malloc(payload_capacity); + if (!payload) { + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "No memory for i2c info"); + return ESP_ERR_NO_MEM; + } + + size_t offset = 0; + int written = snprintf( + payload + offset, + payload_capacity - offset, + "{\"enabled\":%s,\"sda_io\":%u,\"scl_io\":%u,\"frequency_hz\":%" PRIu32 ",\"added_device_count\":%u,\"detected_device_count\":%u,\"added_devices\":[", + info.enabled ? "true" : "false", + (unsigned int)info.sda_io, + (unsigned int)info.scl_io, + info.frequency_hz, + (unsigned int)info.added_device_count, + (unsigned int)info.detected_device_count + ); + + if (written < 0 || written >= (int)(payload_capacity - offset)) { + free(payload); + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Info too large"); + return ESP_FAIL; + } + offset += (size_t)written; + + for (uint8_t i = 0; i < info.added_device_count; ++i) { + const VigilantI2CDevice *device = &info.added_devices[i]; + written = snprintf( + payload + offset, + payload_capacity - offset, + "%s{\"name\":\"I2C Device 0x%02X\",\"address\":%u,\"address_hex\":\"0x%02X\",\"whoami_reg\":%u,\"whoami_reg_hex\":\"0x%02X\",\"expected_whoami\":%u,\"expected_whoami_hex\":\"0x%02X\"}", + i == 0 ? "" : ",", + (unsigned int)device->address, + (unsigned int)device->address, + (unsigned int)device->address, + (unsigned int)device->whoami_reg, + (unsigned int)device->whoami_reg, + (unsigned int)device->expected_whoami, + (unsigned int)device->expected_whoami + ); + + if (written < 0 || written >= (int)(payload_capacity - offset)) { + free(payload); + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Info too large"); + return ESP_FAIL; + } + offset += (size_t)written; + } + + written = snprintf(payload + offset, payload_capacity - offset, "],\"detected_devices\":["); + if (written < 0 || written >= (int)(payload_capacity - offset)) { + free(payload); + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Info too large"); + return ESP_FAIL; + } + offset += (size_t)written; + + for (uint8_t i = 0; i < info.detected_device_count; ++i) { + uint8_t address = info.detected_devices[i]; + written = snprintf( + payload + offset, + payload_capacity - offset, + "%s{\"name\":\"Detected I2C Device 0x%02X\",\"address\":%u,\"address_hex\":\"0x%02X\"}", + i == 0 ? "" : ",", + (unsigned int)address, + (unsigned int)address, + (unsigned int)address + ); + + if (written < 0 || written >= (int)(payload_capacity - offset)) { + free(payload); + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Info too large"); + return ESP_FAIL; + } + offset += (size_t)written; + } + + written = snprintf(payload + offset, payload_capacity - offset, "]}"); + if (written < 0 || written >= (int)(payload_capacity - offset)) { + free(payload); + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Info too large"); + return ESP_FAIL; + } + offset += (size_t)written; + + httpd_resp_send(req, payload, (ssize_t)offset); + free(payload); + return ESP_OK; +} + +static const httpd_uri_t i2cinfo_uri = { + .uri = "/i2cinfo", + .method = HTTP_GET, + .handler = i2cinfo_get_handler, + .user_ctx = NULL, +}; + + + esp_err_t http_404_error_handler(httpd_req_t *req, httpd_err_code_t err) { if (strcmp("/hello", req->uri) == 0) { @@ -294,6 +407,7 @@ static httpd_handle_t start_webserver_internal(void) httpd_register_uri_handler(server, &ctrl); httpd_register_uri_handler(server, &any); httpd_register_uri_handler(server, &info_uri); + httpd_register_uri_handler(server, &i2cinfo_uri); websocket_register_handlers(server); // OTA-Handler registrieren diff --git a/components/vigilant_engine/src/i2c.c b/components/vigilant_engine/src/i2c.c index 923d926..2048ef6 100644 --- a/components/vigilant_engine/src/i2c.c +++ b/components/vigilant_engine/src/i2c.c @@ -16,9 +16,96 @@ #define I2C_TIMEOUT_MS 50 static const char *TAG = "ve_i2c"; +static i2c_master_bus_handle_t s_i2c_bus = NULL; +static uint8_t s_detected_i2c_addresses[16] = {0}; +static size_t s_detected_i2c_count = 0; +static esp_err_t i2c_scan_devices(uint8_t *addresses, size_t max_addresses, size_t *count, bool log_results) +{ + if (!s_i2c_bus) { + return ESP_ERR_INVALID_STATE; + } + if (!count) { + return ESP_ERR_INVALID_ARG; + } -static i2c_master_bus_handle_t s_i2c_bus = NULL; + char line[128]; + size_t found = 0; + size_t stored = 0; + + if (log_results) { + ESP_LOGI(TAG, "I2C bus scan on SDA=%d SCL=%d", I2C_SDA_IO, I2C_SCL_IO); + + strcpy(line, " "); + for (int i = 0; i < 16; i++) { + char tmp[4]; + snprintf(tmp, sizeof(tmp), "%02X ", i); + strncat(line, tmp, sizeof(line) - strlen(line) - 1); + } + ESP_LOGI(TAG, "%s", line); + } + + for (int high = 0; high < 8; high++) { + if (log_results) { + snprintf(line, sizeof(line), "%02X: ", high << 4); + } + + for (int low = 0; low < 16; low++) { + uint8_t addr = (high << 4) | low; + char cell[4] = " "; + + if (addr >= 0x03 && addr <= 0x77) { + esp_err_t err = i2c_master_probe(s_i2c_bus, addr, I2C_TIMEOUT_MS); + if (err == ESP_OK) { + if (addresses && stored < max_addresses) { + addresses[stored++] = addr; + } + found++; + if (log_results) { + snprintf(cell, sizeof(cell), "%02X ", addr); + } + } else if (log_results) { + snprintf(cell, sizeof(cell), "-- "); + } + } + + if (log_results) { + strncat(line, cell, sizeof(line) - strlen(line) - 1); + } + } + + if (log_results) { + ESP_LOGI(TAG, "%s", line); + } + } + + if (log_results) { + ESP_LOGI(TAG, "Scan complete, found %u device(s)", (unsigned int)found); + if (stored < found) { + ESP_LOGW(TAG, "Detected device list truncated to %u entrie(s)", (unsigned int)stored); + } + } + + *count = stored; + return ESP_OK; +} + +static esp_err_t i2c_refresh_detected_devices(bool log_results) +{ + size_t detected_count = 0; + esp_err_t err = i2c_scan_devices( + s_detected_i2c_addresses, + sizeof(s_detected_i2c_addresses) / sizeof(s_detected_i2c_addresses[0]), + &detected_count, + log_results + ); + if (err != ESP_OK) { + return err; + } + + s_detected_i2c_count = detected_count; + return ESP_OK; +} esp_err_t i2c_init(void) { @@ -46,46 +133,11 @@ esp_err_t i2c_init(void) } ESP_LOGI(TAG, "Bus initialized successfully"); - ESP_LOGI(TAG, "I2C bus scan on SDA=%d SCL=%d", I2C_SDA_IO, I2C_SCL_IO); - - char line[128]; - int found = 0; - - strcpy(line, " "); - for (int i = 0; i < 16; i++) { - char tmp[4]; - snprintf(tmp, sizeof(tmp), "%02X ", i); - strncat(line, tmp, sizeof(line) - strlen(line) - 1); - } - ESP_LOGI(TAG, "%s", line); - - for (int high = 0; high < 8; high++) { - snprintf(line, sizeof(line), "%02X: ", high << 4); - - for (int low = 0; low < 16; low++) { - uint8_t addr = (high << 4) | low; - char cell[4]; - - if (addr < 0x03 || addr > 0x77) { - snprintf(cell, sizeof(cell), " "); - } else { - err = i2c_master_probe(s_i2c_bus, addr, I2C_TIMEOUT_MS); - if (err == ESP_OK) { - snprintf(cell, sizeof(cell), "%02X ", addr); - found++; - } else { - snprintf(cell, sizeof(cell), "-- "); - } - } - - strncat(line, cell, sizeof(line) - strlen(line) - 1); - } - - ESP_LOGI(TAG, "%s", line); + err = i2c_refresh_detected_devices(true); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Initial I2C scan failed: %s", esp_err_to_name(err)); } - ESP_LOGI(TAG, "Scan complete, found %d device(s)", found); - return ESP_OK; } @@ -193,6 +245,28 @@ esp_err_t i2c_whoami_check(VigilantI2CDevice *device) return ESP_OK; } +esp_err_t i2c_get_detected_devices(uint8_t *addresses, size_t max_addresses, size_t *count) +{ + if (!count) { + return ESP_ERR_INVALID_ARG; + } + if (!s_i2c_bus) { + return ESP_ERR_INVALID_STATE; + } + + size_t copied_count = s_detected_i2c_count; + if (copied_count > max_addresses) { + copied_count = max_addresses; + } + + if (addresses && copied_count > 0) { + memcpy(addresses, s_detected_i2c_addresses, copied_count * sizeof(s_detected_i2c_addresses[0])); + } + + *count = copied_count; + return ESP_OK; +} + void i2c_deinit(void) { if (s_i2c_bus) { @@ -203,5 +277,7 @@ void i2c_deinit(void) } s_i2c_bus = NULL; + memset(s_detected_i2c_addresses, 0, sizeof(s_detected_i2c_addresses)); + s_detected_i2c_count = 0; } } diff --git a/components/vigilant_engine/src/vigilant.c b/components/vigilant_engine/src/vigilant.c index 13ab994..9fa40ee 100644 --- a/components/vigilant_engine/src/vigilant.c +++ b/components/vigilant_engine/src/vigilant.c @@ -24,11 +24,18 @@ #include "websocket.h" static const char *TAG = "vigilant"; +#if CONFIG_VE_ENABLE_I2C +static const size_t MAX_I2C_INFO_DEVICES = 8; +#endif static esp_netif_t *s_netif_sta = NULL; static esp_netif_t *s_netif_ap = NULL; static VigilantConfig s_cfg = {0}; static TimerHandle_t sta_reconnect_timer; +#if CONFIG_VE_ENABLE_I2C +static VigilantI2CDevice s_i2c_devices[8] = {0}; +static size_t s_i2c_device_count = 0; +#endif static wifi_config_t sta_cfg = { .sta = { @@ -50,6 +57,61 @@ static wifi_config_t ap_cfg = { } }; +#if CONFIG_VE_ENABLE_I2C +static int registered_i2c_device_index(const VigilantI2CDevice *device) +{ + if (!device) { + return -1; + } + + for (size_t i = 0; i < s_i2c_device_count; ++i) { + if (s_i2c_devices[i].address == device->address && + s_i2c_devices[i].whoami_reg == device->whoami_reg && + s_i2c_devices[i].expected_whoami == device->expected_whoami) { + return (int)i; + } + } + + return -1; +} + +static void sync_registered_i2c_device(const VigilantI2CDevice *device) +{ + if (!device) { + return; + } + + int existing_index = registered_i2c_device_index(device); + if (existing_index >= 0) { + s_i2c_devices[existing_index] = *device; + return; + } + + if (s_i2c_device_count >= MAX_I2C_INFO_DEVICES) { + ESP_LOGW(TAG, "I2C device registry full; device 0x%02X will not be exposed via /i2cinfo", + (unsigned int)device->address); + return; + } + + s_i2c_devices[s_i2c_device_count++] = *device; +} + +static void remove_registered_i2c_device(const VigilantI2CDevice *device) +{ + int existing_index = registered_i2c_device_index(device); + if (existing_index < 0) { + return; + } + + for (size_t i = (size_t)existing_index + 1; i < s_i2c_device_count; ++i) { + s_i2c_devices[i - 1] = s_i2c_devices[i]; + } + + s_i2c_device_count--; + memset(&s_i2c_devices[s_i2c_device_count], 0, sizeof(s_i2c_devices[0])); +} +#endif + static void ap_cfg_fixup(wifi_config_t *cfg) { // Wenn password leer ist: auth auf OPEN setzen, sonst meckert IDF rum @@ -267,10 +329,48 @@ esp_err_t vigilant_get_info(VigilantInfo *info) return ESP_OK; } +esp_err_t vigilant_get_i2cinfo(VigilantI2cInfo *info) +{ + if (!info) { + return ESP_ERR_INVALID_ARG; + } + + memset(info, 0, sizeof(*info)); + +#if CONFIG_VE_ENABLE_I2C + info->enabled = true; + info->sda_io = CONFIG_VE_I2C_SDA_IO; + info->scl_io = CONFIG_VE_I2C_SCL_IO; + info->frequency_hz = CONFIG_VE_I2C_FREQ_HZ; + info->added_device_count = (uint8_t)s_i2c_device_count; + memcpy(info->added_devices, s_i2c_devices, sizeof(s_i2c_devices[0]) * s_i2c_device_count); + + size_t detected_count = 0; + esp_err_t detected_err = i2c_get_detected_devices( + info->detected_devices, + sizeof(info->detected_devices) / sizeof(info->detected_devices[0]), + &detected_count + ); + if (detected_err != ESP_OK) { + return detected_err; + } + + info->detected_device_count = (uint8_t)detected_count; +#else + info->enabled = false; +#endif + + return ESP_OK; +} + esp_err_t vigilant_i2c_add_device(VigilantI2CDevice *device) { #if CONFIG_VE_ENABLE_I2C - return i2c_add_device(device); + esp_err_t err = i2c_add_device(device); + if (err == ESP_OK) { + sync_registered_i2c_device(device); + } + return err; #else (void)device; return ESP_ERR_NOT_SUPPORTED; @@ -280,7 +380,11 @@ esp_err_t vigilant_i2c_add_device(VigilantI2CDevice *device) esp_err_t vigilant_i2c_remove_device(VigilantI2CDevice *device) { #if CONFIG_VE_ENABLE_I2C - return i2c_remove_device(device); + esp_err_t err = i2c_remove_device(device); + if (err == ESP_OK) { + remove_registered_i2c_device(device); + } + return err; #else (void)device; return ESP_ERR_NOT_SUPPORTED; diff --git a/main/main.c b/main/main.c index 7c6de5b..f5b3881 100644 --- a/main/main.c +++ b/main/main.c @@ -33,6 +33,5 @@ void app_main(void) uint8_t value = 0; ESP_ERROR_CHECK(vigilant_i2c_read_reg8(&device, 0x27, &value)); // Reading a register (e.g. control register) ESP_LOGI("app_main i2c test", "reg 0x27 value: 0x%02X", value); - ESP_ERROR_CHECK(vigilant_i2c_remove_device(&device)); // Removing a device } } diff --git a/vigilant-engine-frontend/src/shared/styles/theme.css b/vigilant-engine-frontend/src/shared/styles/theme.css index d97cb5d..fa73ff5 100644 --- a/vigilant-engine-frontend/src/shared/styles/theme.css +++ b/vigilant-engine-frontend/src/shared/styles/theme.css @@ -5,7 +5,7 @@ } body { - font-family: ui-monospace, monospace; + font-family: Inter, "Segoe UI", "Helvetica Neue", Arial, sans-serif; background: radial-gradient(circle at 20% 20%, rgba(96, 165, 250, 0.08), transparent 25%), radial-gradient(circle at 80% 0%, rgba(59, 130, 246, 0.06), transparent 22%), #0a0e14; @@ -13,11 +13,15 @@ body { min-height: 100vh; display: flex; align-items: stretch; - justify-content: center; padding: 16px; overflow: hidden; } +#app { + width: 100%; + min-height: calc(100vh - 32px); +} + @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } diff --git a/vigilant-engine-frontend/src/vigilant/VigilantApp.vue b/vigilant-engine-frontend/src/vigilant/VigilantApp.vue index 524c11b..378b2ae 100644 --- a/vigilant-engine-frontend/src/vigilant/VigilantApp.vue +++ b/vigilant-engine-frontend/src/vigilant/VigilantApp.vue @@ -11,15 +11,23 @@
{{ statusText }}
-
- -
-
-
+
+ + +
System Console
@@ -34,12 +42,134 @@

       
- +
+
+
+
Connected Devices
+
+ + + +
+ No connected devices reported. +
+
+ +
+
+
{{ activeConnectedDevice.name }}
+
+ + {{ activeConnectedDevice.state === "added" ? "Added" : "Detected" }} + + + {{ protocolLabel(activeConnectedDevice.protocol) }} + +
+
+ +
+
+
{{ detail.label }}
+
{{ detail.value }}
+
+
+
+ +
+
Device Details
+
+ No I2C devices are currently available from `/i2cinfo`. +
+
+
+ +
+
+
Device Settings
+ +
+
+ +
@@ -73,14 +203,56 @@