diff --git a/esphome/.gitignore b/esphome/.gitignore new file mode 100644 index 0000000..1bfe7bd --- /dev/null +++ b/esphome/.gitignore @@ -0,0 +1,6 @@ +# Gitignore settings for ESPHome +# This is an example and may include too much for your use-case. +# You can modify this file to suit your needs. +/.esphome/ +/secrets.yaml +__pycache__/ diff --git a/esphome/components/smart_table/__init__.py b/esphome/components/smart_table/__init__.py new file mode 100644 index 0000000..f2c2f37 --- /dev/null +++ b/esphome/components/smart_table/__init__.py @@ -0,0 +1,27 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart, sensor +from esphome.const import CONF_ID + +smart_table_ns = cg.esphome_ns.namespace("smart_table") +SmartTable = smart_table_ns.class_("SmartTable", cg.Component) + +CONF_DESK_UART = "desk_uart" +CONF_CONTROLLER_UART = "controller_uart" +CONF_HEIGHT_SENSOR = "height_sensor" + +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(SmartTable), + cv.Required(CONF_DESK_UART): cv.use_id(uart.UARTComponent), + cv.Required(CONF_CONTROLLER_UART): cv.use_id(uart.UARTComponent), + cv.Required(CONF_HEIGHT_SENSOR): cv.use_id(sensor.Sensor), +}).extend(cv.COMPONENT_SCHEMA) + +async def to_code(config): + desk = await cg.get_variable(config[CONF_DESK_UART]) + controller = await cg.get_variable(config[CONF_CONTROLLER_UART]) + height_sensor = await cg.get_variable(config[CONF_HEIGHT_SENSOR]) + var = cg.new_Pvariable(config[CONF_ID], desk, controller, height_sensor) + await cg.register_component(var, config) + + \ No newline at end of file diff --git a/esphome/components/smart_table/smart_table.cpp b/esphome/components/smart_table/smart_table.cpp new file mode 100644 index 0000000..c6ff34d --- /dev/null +++ b/esphome/components/smart_table/smart_table.cpp @@ -0,0 +1,211 @@ +#include "smart_table.h" +#include "esphome/core/log.h" + +namespace smart_table +{ + + static const char *TAG = "smart_table"; + float last_height = -1.0; + // Global variable to track the desired height + static float target_height = -1.0; + static bool moving_to_target = false; + + // Store pointer to desk UART so press_button can use it + static esphome::uart::UARTComponent *desk_uart_ptr = nullptr; + + SmartTable::SmartTable(esphome::uart::UARTComponent *desk, + esphome::uart::UARTComponent *controller, + esphome::sensor::Sensor *height_sensor) + { + desk_ = desk; + controller_ = controller; + height_sensor_ = height_sensor; + + // Save globally for press_button() + desk_uart_ptr = desk_; + } + + void SmartTable::loop() + { + static uint8_t rx[6]; + static uint8_t idx = 0; + + // ================================================== + // Controller → Desk (stream only) + // ================================================== + while (controller_->available()) + { + uint8_t b; + controller_->read_byte(&b); + if (!moving_to_target) + { + desk_->write_byte(b); + } + // Optional: log raw data + // ESP_LOGD(TAG, "REMOTE → DESK : 0x%02X", b); + } + + // ================================================== + // Desk → Controller (stream + height decode) + // ================================================== + while (desk_->available()) + { + uint8_t b; + desk_->read_byte(&b); + controller_->write_byte(b); + + // Feed the 6-byte height frame parser + rx[idx++] = b; + if (idx == 6) + { + idx = 0; + + // Only decode valid frames: 0x98 0x98 + last 2 bytes equal + if ((rx[0] == 0x98 && rx[1] == 0x98) && (rx[4] == rx[5])) + { + uint8_t raw = rx[4]; + if (raw >= height_min && raw <= height_max) + { + // Step calculation like the remote + uint8_t height_step = raw - height_min + 1; + float height_in = base_height + step_size * (height_step - 1); + + if (height_sensor_ != nullptr && height_in != last_height) + { + // ESP_LOGI(TAG, "Desk Height: %.1f in (raw=0x%02X step=%u)", height_in, raw, height_step); + height_sensor_->publish_state(height_in); + last_height = height_in; // save new value + } + } + } + } + } + + smart_table::update_height_control(); + } + + // ================================================== + // Button helper (non-blocking, called from YAML) + // ================================================== + void press_button(uint8_t mask, uint32_t duration_ms) + { + if (desk_uart_ptr == nullptr) { + ESP_LOGE(TAG, "Desk UART not initialized"); + return; + } + + send_empty_byte(); + send_byte(mask); + + } + + void go_up(float distance_in) + { + if (last_height < 0) + { + ESP_LOGW(TAG, "Current height unknown, cannot go_up"); + return; + } + + float new_target = last_height + distance_in; + + ESP_LOGI(TAG, "Go UP by %.2f in (%.2f → %.2f)", + distance_in, last_height, new_target); + + go_to_height(new_target); + } + + void go_down(float distance_in) + { + if (last_height < 0) + { + ESP_LOGW(TAG, "Current height unknown, cannot go_down"); + return; + } + + float new_target = last_height - distance_in; + + ESP_LOGI(TAG, "Go DOWN by %.2f in (%.2f → %.2f)", + distance_in, last_height, new_target); + + go_to_height(new_target); + } + + void send_empty_byte() + { + if (desk_uart_ptr == nullptr) + return; + + // uint8_t release[5] = {0xD8, 0xD8, 0x66, 0x00, 0x00}; + // desk_uart_ptr->write_array(release, 5); + send_byte(0x00); + } + + // Call this to start moving + void go_to_height(float height) + { + if (last_height < 0) + { + ESP_LOGW(TAG, "Current height unknown, cannot go_to_height"); + return; + } + + ESP_LOGI(TAG, "Starting move from %.1f in to %.1f in", last_height, height); + send_empty_byte(); + target_height = height; + moving_to_target = true; + } + + // Call this from your SmartTable::loop() + void update_height_control() + { + if (!moving_to_target) + return; // nothing to do + + // ESP_LOGI("TAG","Working %f", last_height); + const float tolerance = 0.25f; + + if (fabs(last_height - target_height) <= tolerance) + { + ESP_LOGI(TAG, "Reached target height %.1f in", last_height); + // send_empty_byte(); + moving_to_target = false; // stop moving + return; + } + + // Move one small step each loop iteration + if (last_height < target_height) + { + byte_up(0); // 50 ms step + } + else + { + byte_down(0); + } + } + + void send_byte(uint8_t mask) + { + if (desk_uart_ptr == nullptr) + { + ESP_LOGE(TAG, "Desk UART not initialized"); + return; + } + uint8_t msg[5] = {0xD8, 0xD8, 0x66, mask, mask}; + + // ESP_LOGD(TAG, "TX -> DESK: %02X %02X %02X %02X %02X", msg[0], msg[1], msg[2], msg[3], msg[4]); + + desk_uart_ptr->write_array(msg, 5); + } + + void byte_up(uint32_t duration_ms) + { + send_byte(button_up); + } + + void byte_down(uint32_t duration_ms) + { + send_byte(button_down); + } + +} // namespace smart_table \ No newline at end of file diff --git a/esphome/components/smart_table/smart_table.h b/esphome/components/smart_table/smart_table.h new file mode 100644 index 0000000..e935c66 --- /dev/null +++ b/esphome/components/smart_table/smart_table.h @@ -0,0 +1,54 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" +#include "esphome/components/sensor/sensor.h" + +namespace smart_table { + +// Example: height calculation like autonomous_controller +static const float base_height = 29.5f; +static const float step_size = 0.4f; +static const uint8_t height_min = 0x4B; // minimum raw value +static const uint8_t height_max = 0x7B; // maximum raw value +using t_button = uint8_t; +t_button const button_down = 0x01; +t_button const button_up = 0x02; +t_button const button_1 = 0x04; +t_button const button_2 = 0x08; +t_button const button_3 = 0x10; +t_button const button_4 = 0x20; +t_button const button_M = 0x40; + + +class SmartTable : public esphome::Component { + public: + SmartTable(esphome::uart::UARTComponent *desk, + esphome::uart::UARTComponent *controller, + esphome::sensor::Sensor *height_sensor); + + void loop() override; + + // Move table up + // void go_up(uint32_t duration_ms = 10); + // // Move table down + // void go_down(uint32_t duration_ms = 10); + + protected: + esphome::uart::UARTComponent *desk_; + esphome::uart::UARTComponent *controller_; + esphome::sensor::Sensor *height_sensor_; + }; + + // Helper function used by YAML lambda + void press_button(uint8_t mask, uint32_t duration_ms); + void go_up(float distance_in); + void go_down(float distance_in); + void go_to_height(float height); + void update_height_control(); + void send_empty_byte(); + void byte_up(uint32_t duration_dist); + void byte_down(uint32_t duration_dist); + void send_byte(uint8_t mask); + +} // namespace smart_table \ No newline at end of file diff --git a/esphome/readme.md b/esphome/readme.md new file mode 100644 index 0000000..1a20c0a --- /dev/null +++ b/esphome/readme.md @@ -0,0 +1,16 @@ +# Using ESP32 WROOM DEV Module + +Here is the Pin configuration. + + - id: desk_uart + rx_pin: GPIO16 + tx_pin: GPIO17 + baud_rate: 9600 + + - id: controller_uart + rx_pin: GPIO12 + tx_pin: GPIO13 + baud_rate: 9600 + + + ![Screenshot ESPHOME](screenshot.png) \ No newline at end of file diff --git a/esphome/screenshot.png b/esphome/screenshot.png new file mode 100644 index 0000000..b42d59d Binary files /dev/null and b/esphome/screenshot.png differ diff --git a/esphome/smarttable.yaml b/esphome/smarttable.yaml new file mode 100644 index 0000000..bc812c1 --- /dev/null +++ b/esphome/smarttable.yaml @@ -0,0 +1,177 @@ +esphome: + name: $name + project: + name: autonomous.smarttable + version: dev + +substitutions: + name: smart-table + friendly_name: SmartTable + +esp32: + board: esp32dev + framework: + type: arduino + +captive_portal: + +# ============================= +# LOGGING +# ============================= +logger: + +# ============================= +# NETWORK +# ============================= +wifi: + ssid: "WIFI" + password: "Password" + +# ============================= +# HOME ASSISTANT API +# ============================= +api: + encryption: + key: somekey + +ota: + platform: esphome + +# ============================= +# WEB SERVER (ESP WEBPAGE) +# ============================= +web_server: + port: 80 + version: 3 + auth: + username: admin + password: admin + +# ============================= +# EXTERNAL COMPONENT +# ============================= +external_components: + - source: + type: local + path: components + +# ============================= +# UART CONFIG (MATCHES begin()) +# ============================= +uart: + - id: desk_uart + rx_pin: GPIO16 + tx_pin: GPIO17 + baud_rate: 9600 + + - id: controller_uart + rx_pin: GPIO12 + tx_pin: GPIO13 + baud_rate: 9600 + +# ============================= +# SMART TABLE COMPONENT +# ============================= +smart_table: + desk_uart: desk_uart + controller_uart: controller_uart + height_sensor: smart_table_height + +# ============================= +# HEIGHT SENSOR +# ============================= +sensor: + - platform: template + id: smart_table_height + name: "Smart Table Height" + unit_of_measurement: "in" + accuracy_decimals: 1 + # Expose to Home Assistant + icon: "mdi:arrow-up-down" + internal: false + +binary_sensor: + - platform: status + name: "${friendly_name} Status" + entity_category: diagnostic + +number: + - platform: template + id: desk_target_height + name: "Desk Target Height" + unit_of_measurement: "in" + icon: mdi:arrow-up-down + min_value: 29.5 + max_value: 48.4 + step: 1 + mode: slider + optimistic: true + set_action: + - lambda: smart_table::go_to_height(x); + +text_sensor: + - platform: version + hide_timestamp: true + name: "${friendly_name} ESPHome Version" + entity_category: diagnostic + - platform: wifi_info + ip_address: + name: "${friendly_name} IP Address" + icon: mdi:wifi + entity_category: diagnostic + ssid: + name: "${friendly_name} Connected SSID" + icon: mdi:wifi-strength-2 + entity_category: diagnostic + - platform: uptime + name: "${friendly_name} Uptime" + +# ============================= +# BUTTON CONTROLS (Expose to Home Assistant) +# ============================= +button: + - platform: template + id: desk_up + name: "Desk Up" + on_press: + - lambda: smart_table::go_up(3); + # - lambda: smart_table::press_button(0x02, 3000); + + - platform: template + id: desk_down + name: "Desk Down" + on_press: + - lambda: smart_table::go_down(3); + + - platform: template + id: preset_1 + name: "Preset 1" + on_press: + - lambda: smart_table::press_button(0x04, 20); + + - platform: template + id: preset_2 + name: "Preset 2" + on_press: + - lambda: smart_table::press_button(0x08, 20); + + - platform: template + id: preset_3 + name: "Preset 3" + on_press: + - lambda: smart_table::press_button(0x10, 20); + + - platform: template + id: preset_4 + name: "Preset 4" + on_press: + - lambda: smart_table::press_button(0x20, 20); + + - platform: template + id: memory + name: "Memory" + on_press: + - lambda: smart_table::press_button(0x40, 20); + - platform: restart + name: "${friendly_name} Restart" + entity_category: config \ No newline at end of file