# Derived work based on https://github.com/reaper7/M5Stack_BLE_client_Owon_B35T by reaper7. # AI (ChatGPT (GPT5.5), Qwen3.6) has been used to adopt the Arduino sketch to ESPHome. # Ported to M5Stack Core2 due to memory constraints. # Integrated atorch ble proxy functionality from https://github.com/syssi/esphome-atorch-dl24 by syssi. substitutions: name: "lab-ble-proxy" friendly_name: "Lab BLE Proxy" device_description: "M5Stack Core2 BLE client for OWON B35T/B35T+ multimeter and Atorch DL24 DC load" owon_mac_address: !secret owon_b35t_mac_address dl24_mac_address: !secret dl24_mac_address external_components_source: github://syssi/esphome-atorch-dl24@main atorch_project_version: "2.1.0" esphome: name: ${name} friendly_name: ${friendly_name} comment: ${device_description} min_version: 2024.6.0 includes: - lab-ble-proxy.h on_boot: priority: 850 then: - lambda: |- owon_b35t::core2_axp192_init(id(core2_i2c)); project: name: "custom.lab-ble-proxy-m5stack-core2" version: "1.0" esp32: board: m5stack-core2 flash_size: 16MB framework: type: esp-idf advanced: minimum_chip_revision: "3.1" sram1_as_iram: true psram: mode: quad speed: 80MHz external_components: - source: ${external_components_source} refresh: 0s logger: level: INFO api: encryption: key: !secret apikey ota: platform: esphome password: !secret ota wifi: ssid: "Voltage-legacy" password: !secret voltage_legacy_psk #use_address: ${name}.home power_save_mode: none fast_connect: on min_auth_mode: WPA2 ap: ssid: "Lab BLE Proxy Fallback Hotspot" password: !secret fallback_psk captive_portal: globals: - id: display_page type: int initial_value: "0" restore_value: no - id: atorch_connected type: bool initial_value: "false" restore_value: no interval: - interval: 10s then: - lambda: |- ESP_LOGI("mem", "heap free=%u min_free=%u internal_free=%u internal_largest=%u dma_free=%u dma_largest=%u psram_free=%u psram_largest=%u", static_cast(esp_get_free_heap_size()), static_cast(esp_get_minimum_free_heap_size()), static_cast(heap_caps_get_free_size(MALLOC_CAP_INTERNAL)), static_cast(heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL)), static_cast(heap_caps_get_free_size(MALLOC_CAP_DMA)), static_cast(heap_caps_get_largest_free_block(MALLOC_CAP_DMA)), static_cast(heap_caps_get_free_size(MALLOC_CAP_SPIRAM)), static_cast(heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM))); esp32_ble_tracker: scan_parameters: active: true continuous: true ble_client: - mac_address: ${owon_mac_address} id: owon_ble_client on_connect: then: - lambda: |- owon_meter.on_connect(); id(display_page) = 0; id(lcd).update(); on_disconnect: then: - lambda: |- owon_meter.on_disconnect(); if (id(display_page) == 0 && id(atorch_connected)) { id(display_page) = 1; } id(lcd).update(); - mac_address: ${dl24_mac_address} id: atorch_ble_client on_connect: then: - lambda: |- id(atorch_connected) = true; id(display_page) = 1; id(lcd).update(); on_disconnect: then: - lambda: |- id(atorch_connected) = false; if (id(display_page) == 1 && owon_meter.connected) { id(display_page) = 0; } id(lcd).update(); atorch_dl24: - id: atorch0 ble_client_id: atorch_ble_client check_crc: false throttle: 0s spi: clk_pin: GPIO18 mosi_pin: GPIO23 i2c: id: core2_i2c sda: GPIO21 scl: GPIO22 scan: true output: - platform: template type: float id: lcd_backlight write_action: - lambda: |- owon_b35t::core2_axp192_set_backlight(state); light: - platform: monochromatic output: lcd_backlight name: "${friendly_name} Backlight" id: backlight restore_mode: ALWAYS_ON font: - file: "fonts/Roboto-Regular.ttf" id: meter_font size: 15 glyphs: [ " ", "!", "%", "+", "-", ".", "/", ":", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "<", ">", "A", "B", "C", "D", "E", "F", "G", "H", "I", "L", "M", "N", "O", "P", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "k", "l", "m", "n", "o", "p", "r", "s", "t", "u", "v", "w", "y", "z", "°", "µ", "Ω", ] - file: "fonts/Roboto-Medium.ttf" id: atorch_value_font size: 22 glyphs: [ " ", "+", "-", ".", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "C", "V", "W", "h", "°", ] display: - platform: mipi_spi id: lcd model: M5CORE2 update_interval: 500ms lambda: |- owon_meter.render( it, id(meter_font), id(atorch_value_font), id(display_page), id(atorch_connected), id(atorch_voltage).has_state() ? id(atorch_voltage).state : NAN, id(atorch_current).has_state() ? id(atorch_current).state : NAN, id(atorch_power).has_state() ? id(atorch_power).state : NAN, id(atorch_capacity).has_state() ? id(atorch_capacity).state : NAN, id(atorch_temperature).has_state() ? id(atorch_temperature).state : NAN ); touchscreen: - platform: ft63x6 id: touch display: lcd binary_sensor: - platform: touchscreen touchscreen_id: touch id: btn_toggle_page x_min: 70 x_max: 250 y_min: 45 y_max: 195 on_press: then: - lambda: |- id(display_page) = 1 - id(display_page); id(lcd).update(); - platform: touchscreen id: button_a touchscreen_id: touch x_min: 34 x_max: 74 y_min: 212 y_max: 240 internal: true on_press: then: - lambda: |- owon_meter.previous_button(); - platform: touchscreen id: button_b touchscreen_id: touch x_min: 108 x_max: 208 y_min: 212 y_max: 240 internal: true on_click: - min_length: 50ms max_length: 1500ms then: - logger.log: level: INFO format: "OWON short press: %s" args: ["owon_meter.selected_button_name()"] - ble_client.ble_write: id: owon_ble_client service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb" characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb" value: !lambda |- std::vector data = {owon_meter.selected_button, 0x01}; return data; - min_length: 1500ms max_length: 5000ms then: - logger.log: level: INFO format: "OWON long press: %s" args: ["owon_meter.selected_button_name()"] - ble_client.ble_write: id: owon_ble_client service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb" characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb" value: !lambda |- uint8_t press_type = (owon_meter.selected_button == 1 || owon_meter.selected_button == 5) ? 0x01 : 0x00; std::vector data = {owon_meter.selected_button, press_type}; return data; - platform: touchscreen id: button_c touchscreen_id: touch x_min: 242 x_max: 282 y_min: 212 y_max: 240 internal: true on_press: then: - lambda: |- owon_meter.next_button(); - platform: template name: "${friendly_name} OWON Connected" lambda: |- return owon_meter.connected; - platform: template name: "${friendly_name} OWON Overload" lambda: |- return owon_meter.overload; - platform: template name: "${friendly_name} OWON Low Battery" lambda: |- return owon_meter.low_battery; - platform: template name: "${friendly_name} Atorch Connected" device_class: connectivity lambda: |- return id(atorch_connected); sensor: - platform: ble_client type: characteristic ble_client_id: owon_ble_client id: owon_notify_source internal: true service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb" characteristic_uuid: "0000fff4-0000-1000-8000-00805f9b34fb" notify: true update_interval: never lambda: |- owon_meter.handle_notify(x); return owon_meter.value(); - platform: wifi_signal name: "${friendly_name} WiFi Signal" update_interval: 60s - platform: atorch_dl24 atorch_dl24_id: atorch0 voltage: name: "${friendly_name} Atorch Voltage" id: atorch_voltage current: name: "${friendly_name} Atorch Current" id: atorch_current power: name: "${friendly_name} Atorch Power" id: atorch_power capacity: name: "${friendly_name} Atorch Capacity" id: atorch_capacity temperature: name: "${friendly_name} Atorch Temperature" id: atorch_temperature dim_backlight: name: "${friendly_name} Atorch Dim Backlight" id: atorch_dim_backlight text_sensor: - platform: template name: "${friendly_name} OWON Reading" update_interval: 1s lambda: |- return owon_meter.reading_text(); - platform: template name: "${friendly_name} OWON Unit" update_interval: 1s lambda: |- return std::string(owon_meter.scale()) + owon_meter.unit(); - platform: template name: "${friendly_name} OWON Mode" update_interval: 1s lambda: |- return owon_meter.mode_text(); button: - platform: atorch_dl24 atorch_dl24_id: atorch0 reset_energy: name: "${friendly_name} Atorch Reset Energy" reset_capacity: name: "${friendly_name} Atorch Reset Capacity" reset_runtime: name: "${friendly_name} Atorch Reset Runtime" reset_all: name: "${friendly_name} Atorch Reset All" usb_plus: name: "${friendly_name} Atorch Plus" usb_minus: name: "${friendly_name} Atorch Minus" setup: name: "${friendly_name} Atorch Setup" enter: name: "${friendly_name} Atorch Enter" - platform: template name: "OWON SELECT" id: owon_btn_select on_press: - ble_client.ble_write: id: owon_ble_client service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb" characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb" value: !lambda "return std::vector({1, 0x01});" - platform: template name: "OWON RANGE" id: owon_btn_range on_press: - ble_client.ble_write: id: owon_ble_client service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb" characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb" value: !lambda "return std::vector({2, 0x01});" - platform: template name: "OWON HOLD | LIGHT" id: owon_btn_hold on_press: - ble_client.ble_write: id: owon_ble_client service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb" characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb" value: !lambda "return std::vector({3, 0x01});" - platform: template name: "OWON RELATIVE | BT" id: owon_btn_rel on_press: - ble_client.ble_write: id: owon_ble_client service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb" characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb" value: !lambda "return std::vector({4, 0x01});" - platform: template name: "OWON HZ | DUTY" id: owon_btn_hz on_press: - ble_client.ble_write: id: owon_ble_client service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb" characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb" value: !lambda "return std::vector({5, 0x01});" - platform: template name: "OWON MAX | MIN" id: owon_btn_maxmin on_press: - ble_client.ble_write: id: owon_ble_client service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb" characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb" value: !lambda "return std::vector({6, 0x01});"