Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ff8b5cafe | |||
| 7e08c4a8b9 | |||
| 855f013095 | |||
| a68b527c48 | |||
| a3bd9ff213 | |||
| 24fc4bf9f4 | |||
| 0bfc2eff34 | |||
| 8f18a043af |
+1
-1
@@ -1 +1 @@
|
|||||||
2026.4.4
|
2026.5.4
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 26 KiB |
@@ -13,3 +13,4 @@ home-assistant.log*
|
|||||||
zigbee.db*
|
zigbee.db*
|
||||||
*__pycache__*
|
*__pycache__*
|
||||||
.ha_run.lock
|
.ha_run.lock
|
||||||
|
.cache
|
||||||
|
|||||||
+23
-130
@@ -139,10 +139,6 @@
|
|||||||
trigger: time
|
trigger: time
|
||||||
conditions: []
|
conditions: []
|
||||||
actions:
|
actions:
|
||||||
- action: notify.mobile_app_apollo
|
|
||||||
data:
|
|
||||||
message: Lodere es, Lustknabe.
|
|
||||||
title: 420!
|
|
||||||
- data:
|
- data:
|
||||||
title: 420!
|
title: 420!
|
||||||
message: Lodere es, Lustknabe.
|
message: Lodere es, Lustknabe.
|
||||||
@@ -183,6 +179,10 @@
|
|||||||
topic: awtrix_desk/notify
|
topic: awtrix_desk/notify
|
||||||
payload: '{"text": "420, lodere es, Lustknabe!"}'
|
payload: '{"text": "420, lodere es, Lustknabe!"}'
|
||||||
action: mqtt.publish
|
action: mqtt.publish
|
||||||
|
- action: notify.mobile_app_apollo
|
||||||
|
data:
|
||||||
|
message: Lodere es, Lustknabe.
|
||||||
|
title: 420!
|
||||||
mode: single
|
mode: single
|
||||||
- id: '1623911524804'
|
- id: '1623911524804'
|
||||||
alias: TV Anti-Reflexion (undo)
|
alias: TV Anti-Reflexion (undo)
|
||||||
@@ -227,28 +227,28 @@
|
|||||||
action: light.turn_on
|
action: light.turn_on
|
||||||
- data:
|
- data:
|
||||||
cache: true
|
cache: true
|
||||||
media_player_entity_id: media_player.home_assistant_voice_0a7c6b
|
media_player_entity_id: media_player.home_assistant_voice_0a7c6b_media_player
|
||||||
message: Ab ins Bett, Schlafenszeit.
|
message: Ab ins Bett, Schlafenszeit.
|
||||||
target:
|
target:
|
||||||
entity_id: tts.piper
|
entity_id: tts.piper
|
||||||
action: tts.speak
|
|
||||||
enabled: true
|
enabled: true
|
||||||
|
action: tts.speak
|
||||||
- data:
|
- data:
|
||||||
cache: true
|
cache: true
|
||||||
media_player_entity_id: media_player.home_assistant_voice_09c0e7_media_player_2
|
media_player_entity_id: media_player.home_assistant_voice_09c0e7_media_player
|
||||||
message: Ab ins Bett, Schlafenszeit.
|
message: Ab ins Bett, Schlafenszeit.
|
||||||
target:
|
target:
|
||||||
entity_id: tts.piper
|
entity_id: tts.piper
|
||||||
action: tts.speak
|
|
||||||
enabled: true
|
enabled: true
|
||||||
|
action: tts.speak
|
||||||
- data:
|
- data:
|
||||||
cache: true
|
cache: true
|
||||||
media_player_entity_id: media_player.m5stack_atom_echo
|
media_player_entity_id: media_player.m5stack_atom_echo
|
||||||
message: Ab ins Bett, Schlafenszeit.
|
message: Ab ins Bett, Schlafenszeit.
|
||||||
target:
|
target:
|
||||||
entity_id: tts.piper
|
entity_id: tts.piper
|
||||||
action: tts.speak
|
|
||||||
enabled: true
|
enabled: true
|
||||||
|
action: tts.speak
|
||||||
mode: single
|
mode: single
|
||||||
- id: '1623954512941'
|
- id: '1623954512941'
|
||||||
alias: Licht im Schlafzimmer zum Aufwachen einschalten
|
alias: Licht im Schlafzimmer zum Aufwachen einschalten
|
||||||
@@ -278,8 +278,10 @@
|
|||||||
alias: 'Anruf: Beim Klingeln grün blinken'
|
alias: 'Anruf: Beim Klingeln grün blinken'
|
||||||
description: ''
|
description: ''
|
||||||
triggers:
|
triggers:
|
||||||
- entity_id: sensor.fritz_box_7490_call_monitor_dem_commander1024_seine_cloud
|
- entity_id:
|
||||||
to: ringing
|
- sensor.fritz_box_7690_anrufmonitor_dem_commander1024_seine_cloud
|
||||||
|
to:
|
||||||
|
- ringing
|
||||||
trigger: state
|
trigger: state
|
||||||
- entity_id:
|
- entity_id:
|
||||||
- sensor.le2123_phone_state_2
|
- sensor.le2123_phone_state_2
|
||||||
@@ -301,8 +303,8 @@
|
|||||||
- 0
|
- 0
|
||||||
target:
|
target:
|
||||||
entity_id:
|
entity_id:
|
||||||
- light.awtrix_desk_indicator_1
|
- light.awtrix_b8658c_indicator_1
|
||||||
- light.awtrix_kitchen_indicator_1
|
- light.awtrix_528bd4_indicator_1
|
||||||
action: light.turn_on
|
action: light.turn_on
|
||||||
- data:
|
- data:
|
||||||
scene_id: wled_last_state
|
scene_id: wled_last_state
|
||||||
@@ -335,16 +337,18 @@
|
|||||||
- data: {}
|
- data: {}
|
||||||
target:
|
target:
|
||||||
entity_id:
|
entity_id:
|
||||||
- light.awtrix_desk_indicator_1
|
- light.awtrix_b8658c_indicator_1
|
||||||
- light.awtrix_kitchen_indicator_1
|
- light.awtrix_528bd4_indicator_1
|
||||||
action: light.turn_off
|
action: light.turn_off
|
||||||
mode: single
|
mode: single
|
||||||
- id: '1625481640348'
|
- id: '1625481640348'
|
||||||
alias: 'Anruf: Beim Telefonieren Musik pausieren'
|
alias: 'Anruf: Beim Telefonieren Musik pausieren'
|
||||||
description: ''
|
description: ''
|
||||||
triggers:
|
triggers:
|
||||||
- entity_id: sensor.fritz_box_7490_call_monitor_telefonbuch
|
- entity_id:
|
||||||
to: talking
|
- sensor.fritz_box_7690_anrufmonitor_dem_commander1024_seine_cloud
|
||||||
|
to:
|
||||||
|
- talking
|
||||||
trigger: state
|
trigger: state
|
||||||
- entity_id:
|
- entity_id:
|
||||||
- sensor.le2123_phone_state_2
|
- sensor.le2123_phone_state_2
|
||||||
@@ -362,9 +366,8 @@
|
|||||||
- data:
|
- data:
|
||||||
scene_id: media_last_state
|
scene_id: media_last_state
|
||||||
snapshot_entities:
|
snapshot_entities:
|
||||||
- media_player.wohnzimmer_main
|
- media_player.wohnzimmer
|
||||||
- media_player.lg_webos_smart_tv
|
- media_player.lg_webos_smart_tv
|
||||||
- media_player.ccze
|
|
||||||
- media_player.spotify_marcus_scholz
|
- media_player.spotify_marcus_scholz
|
||||||
- media_player.xboxonex
|
- media_player.xboxonex
|
||||||
action: scene.create
|
action: scene.create
|
||||||
@@ -385,7 +388,7 @@
|
|||||||
trigger: state
|
trigger: state
|
||||||
- target:
|
- target:
|
||||||
entity_id:
|
entity_id:
|
||||||
- scene.media_last_state
|
- scene.wled_last_state
|
||||||
action: scene.turn_on
|
action: scene.turn_on
|
||||||
data: {}
|
data: {}
|
||||||
mode: single
|
mode: single
|
||||||
@@ -1393,113 +1396,3 @@
|
|||||||
device_id: 6d1be741876624a70ab5b01b54c6fd6f
|
device_id: 6d1be741876624a70ab5b01b54c6fd6f
|
||||||
entity_id: 621e4377a089b7f5d92c7c9f2cc171a1
|
entity_id: 621e4377a089b7f5d92c7c9f2cc171a1
|
||||||
domain: switch
|
domain: switch
|
||||||
- id: '1773000000001'
|
|
||||||
alias: epaperframe - Kalender Events laden
|
|
||||||
description: Ruft alle Events von calendar.privat und calendar.tkrz_kalender für heute und morgen ab und schreibt sie in input_text-Helfer.
|
|
||||||
triggers:
|
|
||||||
# Täglich um 00:01 neu holen
|
|
||||||
- trigger: time
|
|
||||||
at: '00:01:00'
|
|
||||||
id: daily
|
|
||||||
actions:
|
|
||||||
# ── Events für heute (00:00 bis 23:59) holen ──
|
|
||||||
- action: calendar.get_events
|
|
||||||
target:
|
|
||||||
entity_id:
|
|
||||||
- calendar.privat
|
|
||||||
- calendar.tkrz_kalender
|
|
||||||
data:
|
|
||||||
start_date_time: "{{ now().replace(hour=0, minute=0, second=0) }}"
|
|
||||||
end_date_time: "{{ (now().replace(hour=0, minute=0, second=0) + timedelta(days=1)) }}"
|
|
||||||
response_variable: today_evts
|
|
||||||
|
|
||||||
# ── Events für morgen (00:00 bis 23:59) holen ──
|
|
||||||
- action: calendar.get_events
|
|
||||||
target:
|
|
||||||
entity_id:
|
|
||||||
- calendar.privat
|
|
||||||
- calendar.tkrz_kalender
|
|
||||||
data:
|
|
||||||
start_date_time: "{{ (now() + timedelta(days=1)).replace(hour=0, minute=0, second=0) }}"
|
|
||||||
end_date_time: "{{ (now() + timedelta(days=1)).replace(hour=0, minute=0, second=0) + timedelta(days=1) }}"
|
|
||||||
response_variable: tomorrow_evts
|
|
||||||
|
|
||||||
# ── Helper: Events als kompakte Liste formatieren ──
|
|
||||||
|
|
||||||
# Privat heute
|
|
||||||
- set_var:
|
|
||||||
name: fmt_today_privat
|
|
||||||
value: >-
|
|
||||||
{%- set evts = today_evts['calendar.privat']['events'] if 'calendar.privat' in today_evts else [] %}
|
|
||||||
{%- set items = [] %}
|
|
||||||
{%- for e in evts[:5] %}
|
|
||||||
{%- set s = (e['summary'] | default('', true) | string)[:40] if e is mapping and 'summary' in e else '-' %}
|
|
||||||
{%- set st = e['start'] if e is mapping and 'start' in e else '' %}
|
|
||||||
{%- set hhmm = st[11:16] if st|length >= 16 else '??' %}
|
|
||||||
{%- set items = items + [hhmm ~ ' - ' ~ s] %}
|
|
||||||
{%- endfor -%}
|
|
||||||
{{ items | join('│') if items else '-' }}
|
|
||||||
|
|
||||||
# Arbeit heute
|
|
||||||
- set_var:
|
|
||||||
name: fmt_today_arbeit
|
|
||||||
value: >-
|
|
||||||
{%- set evts = today_evts['calendar.tkrz_kalender']['events'] if 'calendar.tkrz_kalender' in today_evts else [] %}
|
|
||||||
{%- set items = [] %}
|
|
||||||
{%- for e in evts[:5] %}
|
|
||||||
{%- set s = (e['summary'] | default('', true) | string)[:40] if e is mapping and 'summary' in e else '-' %}
|
|
||||||
{%- set st = e['start'] if e is mapping and 'start' in e else '' %}
|
|
||||||
{%- set hhmm = st[11:16] if st|length >= 16 else '??' %}
|
|
||||||
{%- set items = items + [hhmm ~ ' - ' ~ s] %}
|
|
||||||
{%- endfor -%}
|
|
||||||
{{ items | join('│') if items else '-' }}
|
|
||||||
|
|
||||||
# Privat morgen
|
|
||||||
- set_var:
|
|
||||||
name: fmt_tomorrow_privat
|
|
||||||
value: >-
|
|
||||||
{%- set evts = tomorrow_evts['calendar.privat']['events'] if 'calendar.privat' in tomorrow_evts else [] %}
|
|
||||||
{%- set items = [] %}
|
|
||||||
{%- for e in evts[:5] %}
|
|
||||||
{%- set s = (e['summary'] | default('', true) | string)[:40] if e is mapping and 'summary' in e else '-' %}
|
|
||||||
{%- set st = e['start'] if e is mapping and 'start' in e else '' %}
|
|
||||||
{%- set hhmm = st[11:16] if st|length >= 16 else '??' %}
|
|
||||||
{%- set items = items + [hhmm ~ ' - ' ~ s] %}
|
|
||||||
{%- endfor -%}
|
|
||||||
{{ items | join('│') if items else '-' }}
|
|
||||||
|
|
||||||
# Arbeit morgen
|
|
||||||
- set_var:
|
|
||||||
name: fmt_tomorrow_arbeit
|
|
||||||
value: >-
|
|
||||||
{%- set evts = tomorrow_evts['calendar.tkrz_kalender']['events'] if 'calendar.tkrz_kalender' in tomorrow_evts else [] %}
|
|
||||||
{%- set items = [] %}
|
|
||||||
{%- for e in evts[:5] %}
|
|
||||||
{%- set s = (e['summary'] | default('', true) | string)[:40] if e is mapping and 'summary' in e else '-' %}
|
|
||||||
{%- set st = e['start'] if e is mapping and 'start' in e else '' %}
|
|
||||||
{%- set hhmm = st[11:16] if st|length >= 16 else '??' %}
|
|
||||||
{%- set items = items + [hhmm ~ ' - ' ~ s] %}
|
|
||||||
{%- endfor -%}
|
|
||||||
{{ items | join('│') if items else '-' }}
|
|
||||||
|
|
||||||
# ── input_text Helfer schreiben ──
|
|
||||||
- action: input_text.set_value
|
|
||||||
target:
|
|
||||||
entity_id: input_text.kalender_heute_privat
|
|
||||||
data:
|
|
||||||
value: "{{ fmt_today_privat }}"
|
|
||||||
- action: input_text.set_value
|
|
||||||
target:
|
|
||||||
entity_id: input_text.kalender_heute_arbeit
|
|
||||||
data:
|
|
||||||
value: "{{ fmt_today_arbeit }}"
|
|
||||||
- action: input_text.set_value
|
|
||||||
target:
|
|
||||||
entity_id: input_text.kalender_morgen_privat
|
|
||||||
data:
|
|
||||||
value: "{{ fmt_tomorrow_privat }}"
|
|
||||||
- action: input_text.set_value
|
|
||||||
target:
|
|
||||||
entity_id: input_text.kalender_morgen_arbeit
|
|
||||||
data:
|
|
||||||
value: "{{ fmt_tomorrow_arbeit }}"
|
|
||||||
|
|||||||
+5
-3
@@ -49,9 +49,6 @@ sql:
|
|||||||
# calendar integration
|
# calendar integration
|
||||||
calendar: !include calendars.yaml
|
calendar: !include calendars.yaml
|
||||||
|
|
||||||
# Helpers for epaperframe calendar display
|
|
||||||
input_text: !include text_helpers.yaml
|
|
||||||
|
|
||||||
# DB-recorder configuration
|
# DB-recorder configuration
|
||||||
recorder: !include recorder.yaml
|
recorder: !include recorder.yaml
|
||||||
|
|
||||||
@@ -85,3 +82,8 @@ generic_hygrostat:
|
|||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
themes: !include_dir_merge_named themes
|
themes: !include_dir_merge_named themes
|
||||||
|
|
||||||
|
freedns:
|
||||||
|
url: !secret freedns6_url
|
||||||
|
# - name: IPv4
|
||||||
|
# url: !secret freedns4_url
|
||||||
|
|||||||
@@ -38,10 +38,13 @@ ota:
|
|||||||
platform: esphome
|
platform: esphome
|
||||||
password: !secret ota
|
password: !secret ota
|
||||||
|
|
||||||
|
network:
|
||||||
|
enable_ipv6: true
|
||||||
|
|
||||||
wifi:
|
wifi:
|
||||||
ssid: "Voltage-legacy"
|
ssid: "Voltage-legacy"
|
||||||
password: !secret voltage_legacy_psk
|
password: !secret voltage_legacy_psk
|
||||||
use_address: atorch-dc-load.home
|
#use_address: atorch-dc-load.home
|
||||||
power_save_mode: high
|
power_save_mode: high
|
||||||
fast_connect: on
|
fast_connect: on
|
||||||
min_auth_mode: WPA2
|
min_auth_mode: WPA2
|
||||||
|
|||||||
@@ -16,10 +16,13 @@ esp32:
|
|||||||
framework:
|
framework:
|
||||||
type: arduino
|
type: arduino
|
||||||
|
|
||||||
|
network:
|
||||||
|
enable_ipv6: true
|
||||||
|
|
||||||
wifi:
|
wifi:
|
||||||
ssid: "Voltage-legacy"
|
ssid: "Voltage-legacy"
|
||||||
password: !secret voltage_legacy_psk
|
password: !secret voltage_legacy_psk
|
||||||
use_address: bathroom.home
|
#use_address: bathroom.home
|
||||||
power_save_mode: high
|
power_save_mode: high
|
||||||
fast_connect: on
|
fast_connect: on
|
||||||
min_auth_mode: WPA2
|
min_auth_mode: WPA2
|
||||||
|
|||||||
+178
-686
File diff suppressed because it is too large
Load Diff
@@ -16,10 +16,13 @@ esp32:
|
|||||||
framework:
|
framework:
|
||||||
type: esp-idf
|
type: esp-idf
|
||||||
|
|
||||||
|
network:
|
||||||
|
enable_ipv6: true
|
||||||
|
|
||||||
wifi:
|
wifi:
|
||||||
ssid: "Voltage-legacy"
|
ssid: "Voltage-legacy"
|
||||||
password: !secret voltage_legacy_psk
|
password: !secret voltage_legacy_psk
|
||||||
use_address: ${name}.home
|
#use_address: ${name}.home
|
||||||
power_save_mode: high
|
power_save_mode: high
|
||||||
fast_connect: on
|
fast_connect: on
|
||||||
min_auth_mode: WPA2
|
min_auth_mode: WPA2
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ esp32:
|
|||||||
framework:
|
framework:
|
||||||
type: esp-idf
|
type: esp-idf
|
||||||
|
|
||||||
|
network:
|
||||||
|
enable_ipv6: true
|
||||||
|
|
||||||
wifi:
|
wifi:
|
||||||
ssid: "Voltage-legacy"
|
ssid: "Voltage-legacy"
|
||||||
password: !secret voltage_legacy_psk
|
password: !secret voltage_legacy_psk
|
||||||
|
|||||||
@@ -16,10 +16,13 @@ esp32:
|
|||||||
framework:
|
framework:
|
||||||
type: esp-idf
|
type: esp-idf
|
||||||
|
|
||||||
|
network:
|
||||||
|
enable_ipv6: true
|
||||||
|
|
||||||
wifi:
|
wifi:
|
||||||
ssid: "Voltage-legacy"
|
ssid: "Voltage-legacy"
|
||||||
password: !secret voltage_legacy_psk
|
password: !secret voltage_legacy_psk
|
||||||
use_address: cam-livingroom.home
|
#use_address: cam-livingroom.home
|
||||||
power_save_mode: high
|
power_save_mode: high
|
||||||
fast_connect: on
|
fast_connect: on
|
||||||
|
|
||||||
|
|||||||
+17
-80
@@ -25,16 +25,12 @@ namespace owon_b35t {
|
|||||||
using esphome::Color;
|
using esphome::Color;
|
||||||
using esphome::display::Display;
|
using esphome::display::Display;
|
||||||
|
|
||||||
// Log tags used by ESPHome for BLE meter parsing and Core2 power-management messages.
|
|
||||||
static const char *const TAG = "owon_b35t";
|
static const char *const TAG = "owon_b35t";
|
||||||
static const char *const POWER_TAG = "core2_power";
|
static const char *const POWER_TAG = "core2_power";
|
||||||
|
|
||||||
// The M5Stack Core2 routes LCD/backlight power through an AXP192 PMIC on I2C.
|
|
||||||
static constexpr uint8_t AXP192_ADDR = 0x34;
|
static constexpr uint8_t AXP192_ADDR = 0x34;
|
||||||
static esphome::i2c::I2CDevice axp192;
|
static esphome::i2c::I2CDevice axp192;
|
||||||
static bool axp192_ready = false; // Guard PMIC accesses until the I2C bus/address has been configured.
|
static bool axp192_ready = false;
|
||||||
|
|
||||||
// Write one byte to an AXP192 register and log failures instead of silently losing power changes.
|
|
||||||
static bool axp_write(uint8_t reg, uint8_t value) {
|
static bool axp_write(uint8_t reg, uint8_t value) {
|
||||||
if (!axp192_ready) return false;
|
if (!axp192_ready) return false;
|
||||||
bool ok = axp192.write_byte(reg, value);
|
bool ok = axp192.write_byte(reg, value);
|
||||||
@@ -42,7 +38,6 @@ static bool axp_write(uint8_t reg, uint8_t value) {
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read one byte from an AXP192 register; callers use this before read/modify/write updates.
|
|
||||||
static bool axp_read(uint8_t reg, uint8_t *value) {
|
static bool axp_read(uint8_t reg, uint8_t *value) {
|
||||||
if (!axp192_ready) return false;
|
if (!axp192_ready) return false;
|
||||||
bool ok = axp192.read_byte(reg, value);
|
bool ok = axp192.read_byte(reg, value);
|
||||||
@@ -50,7 +45,6 @@ static bool axp_read(uint8_t reg, uint8_t *value) {
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear and set selected bits in an AXP192 register while preserving all other bits.
|
|
||||||
static void axp_update(uint8_t reg, uint8_t clear_mask, uint8_t set_mask) {
|
static void axp_update(uint8_t reg, uint8_t clear_mask, uint8_t set_mask) {
|
||||||
uint8_t value = 0;
|
uint8_t value = 0;
|
||||||
if (!axp_read(reg, &value)) return;
|
if (!axp_read(reg, &value)) return;
|
||||||
@@ -58,28 +52,24 @@ static void axp_update(uint8_t reg, uint8_t clear_mask, uint8_t set_mask) {
|
|||||||
axp_write(reg, value);
|
axp_write(reg, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert a desired DCDC output voltage to the 25 mV-step register encoding used by AXP192.
|
|
||||||
static uint8_t axp_dc_voltage_data(uint16_t millivolts) {
|
static uint8_t axp_dc_voltage_data(uint16_t millivolts) {
|
||||||
if (millivolts < 700) millivolts = 700;
|
if (millivolts < 700) millivolts = 700;
|
||||||
if (millivolts > 3500) millivolts = 3500;
|
if (millivolts > 3500) millivolts = 3500;
|
||||||
return static_cast<uint8_t>((millivolts - 700) / 25) & 0x7F;
|
return static_cast<uint8_t>((millivolts - 700) / 25) & 0x7F;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert a desired LDO voltage to the 100 mV-step register encoding used by AXP192.
|
|
||||||
static uint8_t axp_ldo_voltage_data(uint16_t millivolts) {
|
static uint8_t axp_ldo_voltage_data(uint16_t millivolts) {
|
||||||
if (millivolts < 1800) millivolts = 1800;
|
if (millivolts < 1800) millivolts = 1800;
|
||||||
if (millivolts > 3300) millivolts = 3300;
|
if (millivolts > 3300) millivolts = 3300;
|
||||||
return static_cast<uint8_t>((millivolts - 1800) / 100) & 0x0F;
|
return static_cast<uint8_t>((millivolts - 1800) / 100) & 0x0F;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the LCD backlight rail (DCDC3) voltage; brightness is controlled by this rail voltage.
|
|
||||||
static void core2_axp192_set_lcd_voltage(uint16_t millivolts) {
|
static void core2_axp192_set_lcd_voltage(uint16_t millivolts) {
|
||||||
uint8_t value = 0;
|
uint8_t value = 0;
|
||||||
axp_read(0x27, &value);
|
axp_read(0x27, &value);
|
||||||
axp_write(0x27, (value & 0x80) | axp_dc_voltage_data(millivolts)); // DCDC3, LCD backlight
|
axp_write(0x27, (value & 0x80) | axp_dc_voltage_data(millivolts)); // DCDC3, LCD backlight
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map a 0.0-1.0 brightness value to the Core2 LCD backlight voltage and enable/disable DCDC3.
|
|
||||||
static void core2_axp192_set_backlight(float brightness) {
|
static void core2_axp192_set_backlight(float brightness) {
|
||||||
if (brightness <= 0.0f) {
|
if (brightness <= 0.0f) {
|
||||||
axp_update(0x12, 0x02, 0x00); // DCDC3 off
|
axp_update(0x12, 0x02, 0x00); // DCDC3 off
|
||||||
@@ -91,7 +81,6 @@ static void core2_axp192_set_backlight(float brightness) {
|
|||||||
axp_update(0x12, 0x00, 0x02); // DCDC3 on
|
axp_update(0x12, 0x00, 0x02); // DCDC3 on
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the Core2 PMIC rails needed by ESP32, LCD logic, LCD backlight, and the display reset line.
|
|
||||||
static void core2_axp192_init(esphome::i2c::I2CBus *bus) {
|
static void core2_axp192_init(esphome::i2c::I2CBus *bus) {
|
||||||
axp192.set_i2c_bus(bus);
|
axp192.set_i2c_bus(bus);
|
||||||
axp192.set_i2c_address(AXP192_ADDR);
|
axp192.set_i2c_address(AXP192_ADDR);
|
||||||
@@ -124,7 +113,6 @@ static void core2_axp192_init(esphome::i2c::I2CBus *bus) {
|
|||||||
core2_axp192_set_backlight(1.0f);
|
core2_axp192_set_backlight(1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 16x16 monochrome status icons drawn manually into the ESPHome display framebuffer.
|
|
||||||
static const uint8_t ACCU_BMP[32] = {
|
static const uint8_t ACCU_BMP[32] = {
|
||||||
0b00000000, 0b00000000,
|
0b00000000, 0b00000000,
|
||||||
0b00000000, 0b00000000,
|
0b00000000, 0b00000000,
|
||||||
@@ -241,8 +229,6 @@ static const uint8_t BUZZ_BMP[32] = {
|
|||||||
|
|
||||||
class Meter {
|
class Meter {
|
||||||
public:
|
public:
|
||||||
// Offsets and bit masks for the 14-byte "classic" OWON frame layout. B35T+ frames are
|
|
||||||
// normalized into this same buffer so the rest of the code can use one representation.
|
|
||||||
static constexpr uint8_t REGPLUSMINUS = 0x00;
|
static constexpr uint8_t REGPLUSMINUS = 0x00;
|
||||||
static constexpr uint8_t FLAGPLUS = 0b00101011;
|
static constexpr uint8_t FLAGPLUS = 0b00101011;
|
||||||
static constexpr uint8_t FLAGMINUS = 0b00101101;
|
static constexpr uint8_t FLAGMINUS = 0b00101101;
|
||||||
@@ -282,29 +268,24 @@ class Meter {
|
|||||||
static constexpr uint8_t FLAGUNITAMP = 0b01000000;
|
static constexpr uint8_t FLAGUNITAMP = 0b01000000;
|
||||||
static constexpr uint8_t FLAGUNITVOLT = 0b10000000;
|
static constexpr uint8_t FLAGUNITVOLT = 0b10000000;
|
||||||
|
|
||||||
// Connection and decoded meter state consumed by ESPHome sensors and the local display.
|
bool connected{false};
|
||||||
bool connected{false}; // BLE link to the OWON meter is currently established.
|
bool write_available{false};
|
||||||
bool write_available{false}; // The writable BLE characteristic is ready for button commands.
|
bool is_plus{false};
|
||||||
bool is_plus{false}; // True when the last notification used the compact B35T+ frame format.
|
bool low_battery{false};
|
||||||
bool low_battery{false}; // Battery indicator decoded from the meter status bits.
|
bool overload{false};
|
||||||
bool overload{false}; // True when the meter reports OL instead of numeric digits.
|
bool has_reading{false};
|
||||||
bool has_reading{false}; // At least one valid BLE notification has been decoded.
|
uint8_t selected_button{1};
|
||||||
uint8_t selected_button{1}; // UI-selected OWON remote button command (1..6).
|
uint32_t last_notify_ms{0};
|
||||||
uint32_t last_notify_ms{0}; // Timestamp of the last accepted meter notification.
|
|
||||||
|
|
||||||
// Decode a BLE notification from either supported OWON protocol variant and refresh cached values.
|
|
||||||
bool handle_notify(const std::vector<uint8_t> &data) {
|
bool handle_notify(const std::vector<uint8_t> &data) {
|
||||||
if (data.size() > sizeof(this->raw_))
|
if (data.size() > sizeof(this->raw_))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// B35T+ sends a compact 6-byte binary frame; convert it into the classic 14-byte layout.
|
|
||||||
if (data.size() == 6 && data[1] >= 0xF0) {
|
if (data.size() == 6 && data[1] >= 0xF0) {
|
||||||
memset(this->raw_, 0, sizeof(this->raw_));
|
memset(this->raw_, 0, sizeof(this->raw_));
|
||||||
memcpy(this->raw_, data.data(), data.size());
|
memcpy(this->raw_, data.data(), data.size());
|
||||||
this->is_plus = true;
|
this->is_plus = true;
|
||||||
this->parse_plus_();
|
this->parse_plus_();
|
||||||
} else if (data.size() == 14 && data[12] == 0x0D && data[13] == 0x0A) {
|
} else if (data.size() == 14 && data[12] == 0x0D && data[13] == 0x0A) {
|
||||||
// Classic B35T sends an ASCII-like 14-byte frame terminated by CR/LF; it can be used directly.
|
|
||||||
memset(this->value_, 0, sizeof(this->value_));
|
memset(this->value_, 0, sizeof(this->value_));
|
||||||
memcpy(this->value_, data.data(), data.size());
|
memcpy(this->value_, data.data(), data.size());
|
||||||
this->is_plus = false;
|
this->is_plus = false;
|
||||||
@@ -313,32 +294,26 @@ class Meter {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache derived states so display drawing and Home Assistant sensors do not have to re-parse frames.
|
|
||||||
this->overload = memcmp(this->value_, OVERLOAD_FRAME, sizeof(OVERLOAD_FRAME)) == 0;
|
this->overload = memcmp(this->value_, OVERLOAD_FRAME, sizeof(OVERLOAD_FRAME)) == 0;
|
||||||
this->display_value = this->calc_display_value_(); // Numeric value exactly as shown on the meter.
|
this->display_value = this->calc_display_value_();
|
||||||
this->base_value = this->calc_base_value_(); // Same reading converted to base SI scale.
|
this->base_value = this->calc_base_value_();
|
||||||
this->has_reading = true;
|
this->has_reading = true;
|
||||||
this->last_notify_ms = millis();
|
this->last_notify_ms = millis();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by the ESPHome BLE client once the meter connection and write characteristic are usable.
|
|
||||||
void on_connect() {
|
void on_connect() {
|
||||||
this->connected = true;
|
this->connected = true;
|
||||||
this->write_available = true;
|
this->write_available = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset connection flags when BLE disconnects; the last reading is kept for display/sensor continuity.
|
|
||||||
void on_disconnect() {
|
void on_disconnect() {
|
||||||
this->connected = false;
|
this->connected = false;
|
||||||
this->write_available = false;
|
this->write_available = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Numeric accessors used by ESPHome lambdas when publishing sensor state to Home Assistant.
|
|
||||||
float value() const { return this->display_value; }
|
float value() const { return this->display_value; }
|
||||||
float value_base() const { return this->base_value; }
|
float value_base() const { return this->base_value; }
|
||||||
|
|
||||||
// Boolean flag helpers keep the bit-mask details out of UI and sensor publishing code.
|
|
||||||
bool negative() const { return (this->value_[REGPLUSMINUS] & FLAGMINUS) == FLAGMINUS; }
|
bool negative() const { return (this->value_[REGPLUSMINUS] & FLAGMINUS) == FLAGMINUS; }
|
||||||
bool auto_range() const { return (this->value_[REGMODE] & FLAGMODEAUTO) == FLAGMODEAUTO; }
|
bool auto_range() const { return (this->value_[REGMODE] & FLAGMODEAUTO) == FLAGMODEAUTO; }
|
||||||
bool hold() const { return (this->value_[REGMODE] & FLAGMODEHOLD) == FLAGMODEHOLD; }
|
bool hold() const { return (this->value_[REGMODE] & FLAGMODEHOLD) == FLAGMODEHOLD; }
|
||||||
@@ -350,7 +325,6 @@ class Meter {
|
|||||||
bool diode() const { return (this->value_[REGSCALE] & FLAGSCALEDIODE) == FLAGSCALEDIODE; }
|
bool diode() const { return (this->value_[REGSCALE] & FLAGSCALEDIODE) == FLAGSCALEDIODE; }
|
||||||
bool continuity() const { return (this->value_[REGSCALE] & FLAGSCALEBUZZ) == FLAGSCALEBUZZ; }
|
bool continuity() const { return (this->value_[REGSCALE] & FLAGSCALEBUZZ) == FLAGSCALEBUZZ; }
|
||||||
|
|
||||||
// Translate decoded unit bits into the text suffix shown on the display and exposed via sensors.
|
|
||||||
const char *unit() const {
|
const char *unit() const {
|
||||||
switch (this->value_[REGUNIT]) {
|
switch (this->value_[REGUNIT]) {
|
||||||
case FLAGUNITFAHR: return "°F";
|
case FLAGUNITFAHR: return "°F";
|
||||||
@@ -365,7 +339,6 @@ class Meter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate decoded scale bits into SI prefixes or duty-cycle percent.
|
|
||||||
const char *scale() const {
|
const char *scale() const {
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEDUTY) == FLAGSCALEDUTY) return "%";
|
if ((this->value_[REGSCALE] & FLAGSCALEDUTY) == FLAGSCALEDUTY) return "%";
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEMEGA) == FLAGSCALEMEGA) return "M";
|
if ((this->value_[REGSCALE] & FLAGSCALEMEGA) == FLAGSCALEMEGA) return "M";
|
||||||
@@ -375,7 +348,6 @@ class Meter {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a compact human-readable list of active meter modes for Home Assistant text sensors.
|
|
||||||
std::string mode_text() const {
|
std::string mode_text() const {
|
||||||
std::string out;
|
std::string out;
|
||||||
if (this->dc()) out += "DC ";
|
if (this->dc()) out += "DC ";
|
||||||
@@ -391,7 +363,6 @@ class Meter {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format the current reading as a single string, including disconnected/waiting/overload states.
|
|
||||||
std::string reading_text() const {
|
std::string reading_text() const {
|
||||||
if (!this->connected) return "Disconnected";
|
if (!this->connected) return "Disconnected";
|
||||||
if (!this->has_reading) return "Waiting for data";
|
if (!this->has_reading) return "Waiting for data";
|
||||||
@@ -401,7 +372,6 @@ class Meter {
|
|||||||
return std::string(buf);
|
return std::string(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Classify the reading so ESPHome can publish it to the correct measurement-specific sensor.
|
|
||||||
enum Kind { KIND_OTHER, KIND_VOLTAGE, KIND_CURRENT, KIND_RESISTANCE, KIND_FREQUENCY, KIND_CAPACITANCE, KIND_TEMP_C, KIND_TEMP_F, KIND_DUTY };
|
enum Kind { KIND_OTHER, KIND_VOLTAGE, KIND_CURRENT, KIND_RESISTANCE, KIND_FREQUENCY, KIND_CAPACITANCE, KIND_TEMP_C, KIND_TEMP_F, KIND_DUTY };
|
||||||
Kind kind() const {
|
Kind kind() const {
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEDUTY) == FLAGSCALEDUTY) return KIND_DUTY;
|
if ((this->value_[REGSCALE] & FLAGSCALEDUTY) == FLAGSCALEDUTY) return KIND_DUTY;
|
||||||
@@ -417,7 +387,6 @@ class Meter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI label for the currently selected remote-control command sent back to the OWON meter.
|
|
||||||
const char *selected_button_name() const {
|
const char *selected_button_name() const {
|
||||||
static const char *const names[] = {"SELECT", "RANGE", "HLD/LIG", "REL/BT", "HZ/DUTY", "MAX/MIN"};
|
static const char *const names[] = {"SELECT", "RANGE", "HLD/LIG", "REL/BT", "HZ/DUTY", "MAX/MIN"};
|
||||||
uint8_t index = this->selected_button;
|
uint8_t index = this->selected_button;
|
||||||
@@ -426,7 +395,6 @@ class Meter {
|
|||||||
return names[index - 1];
|
return names[index - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move the selected remote button left/right; bounds match the six commands in selected_button_name().
|
|
||||||
void previous_button() {
|
void previous_button() {
|
||||||
if (this->selected_button > 1) this->selected_button--;
|
if (this->selected_button > 1) this->selected_button--;
|
||||||
}
|
}
|
||||||
@@ -434,7 +402,6 @@ class Meter {
|
|||||||
if (this->selected_button < 6) this->selected_button++;
|
if (this->selected_button < 6) this->selected_button++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw either the OWON multimeter page or the Atorch DL24 load page on the M5Stack display.
|
|
||||||
void render(esphome::display::Display &it, esphome::display::BaseFont *font,
|
void render(esphome::display::Display &it, esphome::display::BaseFont *font,
|
||||||
esphome::display::BaseFont *value_font, int display_page = 0,
|
esphome::display::BaseFont *value_font, int display_page = 0,
|
||||||
bool atorch_connected = false,
|
bool atorch_connected = false,
|
||||||
@@ -455,9 +422,7 @@ class Meter {
|
|||||||
if (display_page == 0) {
|
if (display_page == 0) {
|
||||||
// --- PAGE 1: OWON Multimeter ---
|
// --- PAGE 1: OWON Multimeter ---
|
||||||
it.fill(bg);
|
it.fill(bg);
|
||||||
bool status_active = this->connected && this->has_reading; // Only highlight decoded flags after data arrives.
|
bool status_active = this->connected && this->has_reading;
|
||||||
|
|
||||||
// Top status row mirrors the physical meter annunciators: battery, BLE, range, min/max, hold, rel, diode, buzzer.
|
|
||||||
this->draw_icon_(it, 12, 8, 16, 16, ACCU_BMP, status_active ? (this->low_battery ? red : green) : inactive);
|
this->draw_icon_(it, 12, 8, 16, 16, ACCU_BMP, status_active ? (this->low_battery ? red : green) : inactive);
|
||||||
this->draw_icon_(it, 46, 8, 16, 16, BLE_BMP, this->connected ? blue : inactive);
|
this->draw_icon_(it, 46, 8, 16, 16, BLE_BMP, this->connected ? blue : inactive);
|
||||||
this->label_(it, font, 86, 8, "AUTO", status_active && this->auto_range() ? fg : inactive);
|
this->label_(it, font, 86, 8, "AUTO", status_active && this->auto_range() ? fg : inactive);
|
||||||
@@ -471,7 +436,6 @@ class Meter {
|
|||||||
this->label_(it, font, 8, 104, "DC", status_active && this->dc() ? cyan : inactive);
|
this->label_(it, font, 8, 104, "DC", status_active && this->dc() ? cyan : inactive);
|
||||||
this->label_(it, font, 8, 124, "AC", status_active && this->ac() ? magenta : inactive);
|
this->label_(it, font, 8, 124, "AC", status_active && this->ac() ? magenta : inactive);
|
||||||
|
|
||||||
// Main seven-segment area distinguishes connection, waiting-for-first-frame, overload, and normal readings.
|
|
||||||
if (!this->connected) {
|
if (!this->connected) {
|
||||||
this->draw_digits_(it, "----", false, inactive);
|
this->draw_digits_(it, "----", false, inactive);
|
||||||
it.print(160, 148, font, inactive, esphome::display::TextAlign::CENTER, "scan/connect");
|
it.print(160, 148, font, inactive, esphome::display::TextAlign::CENTER, "scan/connect");
|
||||||
@@ -498,8 +462,6 @@ class Meter {
|
|||||||
|
|
||||||
bool bargraph_active = status_active && !this->overload;
|
bool bargraph_active = status_active && !this->overload;
|
||||||
this->draw_bargraph_(it, bargraph_active ? this->digits_from_buffer_() : 0, bargraph_active);
|
this->draw_bargraph_(it, bargraph_active ? this->digits_from_buffer_() : 0, bargraph_active);
|
||||||
|
|
||||||
// Bottom soft-button row shows the currently selected OWON command and whether BLE writes are available.
|
|
||||||
it.filled_rectangle(34, 212, 40, 24, this->write_available ? fg : inactive);
|
it.filled_rectangle(34, 212, 40, 24, this->write_available ? fg : inactive);
|
||||||
it.filled_rectangle(108, 212, 100, 24, this->write_available ? fg : inactive);
|
it.filled_rectangle(108, 212, 100, 24, this->write_available ? fg : inactive);
|
||||||
it.filled_rectangle(242, 212, 40, 24, this->write_available ? fg : inactive);
|
it.filled_rectangle(242, 212, 40, 24, this->write_available ? fg : inactive);
|
||||||
@@ -526,7 +488,6 @@ class Meter {
|
|||||||
const Color temp_color = atorch_connected ? magenta : dim_accent;
|
const Color temp_color = atorch_connected ? magenta : dim_accent;
|
||||||
const Color header_color = atorch_connected ? cyan : dim_value;
|
const Color header_color = atorch_connected ? cyan : dim_value;
|
||||||
|
|
||||||
// Prepare formatted metric strings before drawing so missing/NaN Atorch values display consistently.
|
|
||||||
char voltage_text[24];
|
char voltage_text[24];
|
||||||
char current_text[24];
|
char current_text[24];
|
||||||
char power_text[24];
|
char power_text[24];
|
||||||
@@ -547,7 +508,6 @@ class Meter {
|
|||||||
}
|
}
|
||||||
it.print(310, 7, font, inactive, esphome::display::TextAlign::TOP_RIGHT, "DC LOAD");
|
it.print(310, 7, font, inactive, esphome::display::TextAlign::TOP_RIGHT, "DC LOAD");
|
||||||
|
|
||||||
// Four primary Atorch measurements are shown as dashboard cards; accumulated counters fit in the footer.
|
|
||||||
this->draw_metric_card_(it, font, value_font, 10, 42, 145, 76, "VOLTAGE", voltage_text, voltage_color, panel, border, value_color);
|
this->draw_metric_card_(it, font, value_font, 10, 42, 145, 76, "VOLTAGE", voltage_text, voltage_color, panel, border, value_color);
|
||||||
this->draw_metric_card_(it, font, value_font, 165, 42, 145, 76, "CURRENT", current_text, current_color, panel, border, value_color);
|
this->draw_metric_card_(it, font, value_font, 165, 42, 145, 76, "CURRENT", current_text, current_color, panel, border, value_color);
|
||||||
this->draw_metric_card_(it, font, value_font, 10, 128, 145, 76, "POWER", power_text, power_color, panel, border, value_color);
|
this->draw_metric_card_(it, font, value_font, 10, 128, 145, 76, "POWER", power_text, power_color, panel, border, value_color);
|
||||||
@@ -566,13 +526,12 @@ class Meter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t raw_[14]{}; // Last raw BLE payload, padded to the maximum supported OWON frame length.
|
uint8_t raw_[14]{};
|
||||||
uint8_t value_[14]{}; // Normalized classic-frame view used by all accessors and display rendering.
|
uint8_t value_[14]{};
|
||||||
float display_value{NAN}; // Meter reading with its displayed prefix still applied, e.g. 12.3 kΩ -> 12.3.
|
float display_value{NAN};
|
||||||
float base_value{NAN}; // Meter reading converted to base units, e.g. 12.3 kΩ -> 12300 Ω.
|
float base_value{NAN};
|
||||||
static constexpr uint8_t OVERLOAD_FRAME[5] = {0x2B, 0x3F, 0x30, 0x3A, 0x3F}; // "+?0:?" OL marker.
|
static constexpr uint8_t OVERLOAD_FRAME[5] = {0x2B, 0x3F, 0x30, 0x3A, 0x3F};
|
||||||
|
|
||||||
// Reconstruct the four visible seven-segment digits as an integer for value calculations/bargraph scaling.
|
|
||||||
uint16_t digits_from_buffer_() const {
|
uint16_t digits_from_buffer_() const {
|
||||||
uint16_t out = 0;
|
uint16_t out = 0;
|
||||||
if (this->value_[REGDIG1] >= '0' && this->value_[REGDIG1] <= '9') out += (this->value_[REGDIG1] - '0') * 1000;
|
if (this->value_[REGDIG1] >= '0' && this->value_[REGDIG1] <= '9') out += (this->value_[REGDIG1] - '0') * 1000;
|
||||||
@@ -582,12 +541,10 @@ class Meter {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the normalized frame into the floating-point number shown by the meter display.
|
|
||||||
float calc_display_value_() const {
|
float calc_display_value_() const {
|
||||||
if (this->overload) return NAN;
|
if (this->overload) return NAN;
|
||||||
|
|
||||||
if (this->is_plus) {
|
if (this->is_plus) {
|
||||||
// B35T+ encodes decimal placement and sign/magnitude as packed little-endian words.
|
|
||||||
uint16_t pair1 = static_cast<uint16_t>(this->raw_[0]) | (static_cast<uint16_t>(this->raw_[1]) << 8);
|
uint16_t pair1 = static_cast<uint16_t>(this->raw_[0]) | (static_cast<uint16_t>(this->raw_[1]) << 8);
|
||||||
uint8_t decimal = pair1 & 0x07;
|
uint8_t decimal = pair1 & 0x07;
|
||||||
if (decimal >= 7) return NAN;
|
if (decimal >= 7) return NAN;
|
||||||
@@ -599,7 +556,6 @@ class Meter {
|
|||||||
return negative ? -v : v;
|
return negative ? -v : v;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Classic frames carry decimal placement as one of three point flags in the normalized buffer.
|
|
||||||
uint8_t decimal = 0;
|
uint8_t decimal = 0;
|
||||||
switch (this->value_[REGPOINT] & 0x07) {
|
switch (this->value_[REGPOINT] & 0x07) {
|
||||||
case 0b001: decimal = 1; break;
|
case 0b001: decimal = 1; break;
|
||||||
@@ -611,7 +567,6 @@ class Meter {
|
|||||||
return this->negative() ? -v : v;
|
return this->negative() ? -v : v;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the decoded SI prefix so Home Assistant receives comparable base-unit sensor values.
|
|
||||||
float calc_base_value_() const {
|
float calc_base_value_() const {
|
||||||
if (std::isnan(this->display_value)) return NAN;
|
if (std::isnan(this->display_value)) return NAN;
|
||||||
if (this->value_[REGUNIT] == FLAGUNITNF) return this->display_value * 1e-9f;
|
if (this->value_[REGUNIT] == FLAGUNITNF) return this->display_value * 1e-9f;
|
||||||
@@ -622,20 +577,17 @@ class Meter {
|
|||||||
return this->display_value;
|
return this->display_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode the compact B35T+ payload and synthesize the 14-byte classic representation.
|
|
||||||
void parse_plus_() {
|
void parse_plus_() {
|
||||||
memset(this->value_, 0, sizeof(this->value_));
|
memset(this->value_, 0, sizeof(this->value_));
|
||||||
this->value_[5] = 0x20;
|
this->value_[5] = 0x20;
|
||||||
this->value_[12] = 0x0D;
|
this->value_[12] = 0x0D;
|
||||||
this->value_[13] = 0x0A;
|
this->value_[13] = 0x0A;
|
||||||
|
|
||||||
// First word: measurement function, SI scale, and decimal-point position.
|
|
||||||
uint16_t pair1 = static_cast<uint16_t>(this->raw_[0]) | (static_cast<uint16_t>(this->raw_[1]) << 8);
|
uint16_t pair1 = static_cast<uint16_t>(this->raw_[0]) | (static_cast<uint16_t>(this->raw_[1]) << 8);
|
||||||
uint8_t function = (pair1 >> 6) & 0x0F;
|
uint8_t function = (pair1 >> 6) & 0x0F;
|
||||||
uint8_t scale = (pair1 >> 3) & 0x07;
|
uint8_t scale = (pair1 >> 3) & 0x07;
|
||||||
uint8_t decimal = pair1 & 0x07;
|
uint8_t decimal = pair1 & 0x07;
|
||||||
|
|
||||||
// Translate B35T+ decimal count to the classic point-flag convention used by draw_decimal_points_().
|
|
||||||
switch (decimal) {
|
switch (decimal) {
|
||||||
case 0: this->value_[REGPOINT] = FLAGPOINT0; break;
|
case 0: this->value_[REGPOINT] = FLAGPOINT0; break;
|
||||||
case 1: this->value_[REGPOINT] = FLAGPOINT3; break;
|
case 1: this->value_[REGPOINT] = FLAGPOINT3; break;
|
||||||
@@ -644,7 +596,6 @@ class Meter {
|
|||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate the function selector into classic unit and AC/DC/mode flags.
|
|
||||||
switch (function) {
|
switch (function) {
|
||||||
case 0: this->value_[REGUNIT] |= FLAGUNITVOLT; this->value_[REGMODE] |= FLAGMODEDC; break;
|
case 0: this->value_[REGUNIT] |= FLAGUNITVOLT; this->value_[REGMODE] |= FLAGMODEDC; break;
|
||||||
case 1: this->value_[REGUNIT] |= FLAGUNITVOLT; this->value_[REGMODE] |= FLAGMODEAC; break;
|
case 1: this->value_[REGUNIT] |= FLAGUNITVOLT; this->value_[REGMODE] |= FLAGMODEAC; break;
|
||||||
@@ -662,7 +613,6 @@ class Meter {
|
|||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate the B35T+ scale selector into SI-prefix flags.
|
|
||||||
switch (scale) {
|
switch (scale) {
|
||||||
case 2: this->value_[REGSCALE] |= FLAGSCALEMICRO; break;
|
case 2: this->value_[REGSCALE] |= FLAGSCALEMICRO; break;
|
||||||
case 3: this->value_[REGSCALE] |= FLAGSCALEMILLI; break;
|
case 3: this->value_[REGSCALE] |= FLAGSCALEMILLI; break;
|
||||||
@@ -671,7 +621,6 @@ class Meter {
|
|||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second word: status flags such as hold, relative, auto-range, battery, and min/max.
|
|
||||||
uint16_t pair2 = static_cast<uint16_t>(this->raw_[2]) | (static_cast<uint16_t>(this->raw_[3]) << 8);
|
uint16_t pair2 = static_cast<uint16_t>(this->raw_[2]) | (static_cast<uint16_t>(this->raw_[3]) << 8);
|
||||||
if (pair2 & (1 << 0)) this->value_[REGMODE] |= FLAGMODEHOLD;
|
if (pair2 & (1 << 0)) this->value_[REGMODE] |= FLAGMODEHOLD;
|
||||||
if (pair2 & (1 << 1)) this->value_[REGMODE] |= FLAGMODEREL;
|
if (pair2 & (1 << 1)) this->value_[REGMODE] |= FLAGMODEREL;
|
||||||
@@ -680,7 +629,6 @@ class Meter {
|
|||||||
if (pair2 & (1 << 4)) this->value_[REGMINMAX] |= FLAGMIN;
|
if (pair2 & (1 << 4)) this->value_[REGMINMAX] |= FLAGMIN;
|
||||||
if (pair2 & (1 << 5)) this->value_[REGMINMAX] |= FLAGMAX;
|
if (pair2 & (1 << 5)) this->value_[REGMINMAX] |= FLAGMAX;
|
||||||
|
|
||||||
// Third word: signed 4-digit display value, with bit 15 indicating a negative reading.
|
|
||||||
uint16_t pair3 = static_cast<uint16_t>(this->raw_[4]) | (static_cast<uint16_t>(this->raw_[5]) << 8);
|
uint16_t pair3 = static_cast<uint16_t>(this->raw_[4]) | (static_cast<uint16_t>(this->raw_[5]) << 8);
|
||||||
if (decimal < 7) {
|
if (decimal < 7) {
|
||||||
uint16_t digits = pair3;
|
uint16_t digits = pair3;
|
||||||
@@ -695,18 +643,15 @@ class Meter {
|
|||||||
this->value_[REGDIG3] = '0' + ((digits / 10) % 10);
|
this->value_[REGDIG3] = '0' + ((digits / 10) % 10);
|
||||||
this->value_[REGDIG4] = '0' + (digits % 10);
|
this->value_[REGDIG4] = '0' + (digits % 10);
|
||||||
} else {
|
} else {
|
||||||
// Decimal values 7 and above are used by this protocol to indicate overload/OL.
|
|
||||||
memcpy(this->value_, OVERLOAD_FRAME, sizeof(OVERLOAD_FRAME));
|
memcpy(this->value_, OVERLOAD_FRAME, sizeof(OVERLOAD_FRAME));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a printable digit for a display register, using a blank for non-digit status bytes.
|
|
||||||
char digit_char_(uint8_t reg) const {
|
char digit_char_(uint8_t reg) const {
|
||||||
uint8_t c = this->value_[reg];
|
uint8_t c = this->value_[reg];
|
||||||
return (c >= '0' && c <= '9') ? static_cast<char>(c) : ' ';
|
return (c >= '0' && c <= '9') ? static_cast<char>(c) : ' ';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format Atorch metrics, using "--" for missing values while keeping units visible.
|
|
||||||
void format_metric_(char *buffer, size_t size, float value, const char *unit, uint8_t decimals) const {
|
void format_metric_(char *buffer, size_t size, float value, const char *unit, uint8_t decimals) const {
|
||||||
if (!std::isfinite(value)) {
|
if (!std::isfinite(value)) {
|
||||||
snprintf(buffer, size, "-- %s", unit);
|
snprintf(buffer, size, "-- %s", unit);
|
||||||
@@ -717,7 +662,6 @@ class Meter {
|
|||||||
snprintf(buffer, size, format, value, unit);
|
snprintf(buffer, size, format, value, unit);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw one Atorch dashboard tile with a colored accent, label, and centered numeric value.
|
|
||||||
void draw_metric_card_(Display &it, esphome::display::BaseFont *label_font, esphome::display::BaseFont *value_font,
|
void draw_metric_card_(Display &it, esphome::display::BaseFont *label_font, esphome::display::BaseFont *value_font,
|
||||||
int x, int y, int w, int h, const char *title, const char *value,
|
int x, int y, int w, int h, const char *title, const char *value,
|
||||||
Color accent, Color fill, Color border, Color value_color) {
|
Color accent, Color fill, Color border, Color value_color) {
|
||||||
@@ -734,12 +678,10 @@ class Meter {
|
|||||||
it.filled_rectangle(x + 10, y + h - 11, w - 20, 1, accent);
|
it.filled_rectangle(x + 10, y + h - 11, w - 20, 1, accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Small wrapper for consistent top-left aligned labels.
|
|
||||||
void label_(Display &it, esphome::display::BaseFont *font, int x, int y, const char *text, Color color) {
|
void label_(Display &it, esphome::display::BaseFont *font, int x, int y, const char *text, Color color) {
|
||||||
it.print(x, y, font, color, esphome::display::TextAlign::TOP_LEFT, text);
|
it.print(x, y, font, color, esphome::display::TextAlign::TOP_LEFT, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the large four-digit OWON readout, including a separate minus sign when needed.
|
|
||||||
void draw_digits_(Display &it, const char *text, bool negative, Color color) {
|
void draw_digits_(Display &it, const char *text, bool negative, Color color) {
|
||||||
if (negative) this->draw_segment_(it, 8, 83, 26, 9, true, color);
|
if (negative) this->draw_segment_(it, 8, 83, 26, 9, true, color);
|
||||||
constexpr int digit_x = 40;
|
constexpr int digit_x = 40;
|
||||||
@@ -752,7 +694,6 @@ class Meter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw decimal points between the large seven-segment digits according to the normalized frame flags.
|
|
||||||
void draw_decimal_points_(Display &it, Color color) {
|
void draw_decimal_points_(Display &it, Color color) {
|
||||||
uint8_t p = this->value_[REGPOINT];
|
uint8_t p = this->value_[REGPOINT];
|
||||||
if ((p & FLAGPOINT1) == FLAGPOINT1) it.filled_rectangle(95, 135, 8, 10, color);
|
if ((p & FLAGPOINT1) == FLAGPOINT1) it.filled_rectangle(95, 135, 8, 10, color);
|
||||||
@@ -760,7 +701,6 @@ class Meter {
|
|||||||
if ((p & FLAGPOINT3) == FLAGPOINT3) it.filled_rectangle(223, 135, 8, 10, color);
|
if ((p & FLAGPOINT3) == FLAGPOINT3) it.filled_rectangle(223, 135, 8, 10, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw one beveled horizontal or vertical segment used by the custom seven-segment font.
|
|
||||||
void draw_segment_(Display &it, int x, int y, int w, int h, bool horizontal, Color color) {
|
void draw_segment_(Display &it, int x, int y, int w, int h, bool horizontal, Color color) {
|
||||||
if (horizontal) {
|
if (horizontal) {
|
||||||
int cap = h / 2;
|
int cap = h / 2;
|
||||||
@@ -775,7 +715,6 @@ class Meter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plot a packed 1-bit bitmap icon, where each set bit becomes one colored display pixel.
|
|
||||||
void draw_icon_(Display &it, int x, int y, int w, int h, const uint8_t *data, Color color) {
|
void draw_icon_(Display &it, int x, int y, int w, int h, const uint8_t *data, Color color) {
|
||||||
for (int row = 0; row < h; row++) {
|
for (int row = 0; row < h; row++) {
|
||||||
for (int col = 0; col < w; col++) {
|
for (int col = 0; col < w; col++) {
|
||||||
@@ -787,7 +726,6 @@ class Meter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map a digit/OL/minus character to seven segment states and draw the active segments.
|
|
||||||
void draw_seven_segment_(Display &it, int x, int y, int w, int h, char ch, Color color) {
|
void draw_seven_segment_(Display &it, int x, int y, int w, int h, char ch, Color color) {
|
||||||
bool a=false,b=false,c=false,d=false,e=false,f=false,g=false;
|
bool a=false,b=false,c=false,d=false,e=false,f=false,g=false;
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
@@ -828,7 +766,6 @@ class Meter {
|
|||||||
if (g) this->draw_segment_(it, x + t / 2, mid_y, w - t, t, true, color);
|
if (g) this->draw_segment_(it, x + t / 2, mid_y, w - t, t, true, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the OWON-style bottom bargraph by scaling raw 0..6000 display digits to a 240 px ruler.
|
|
||||||
void draw_bargraph_(Display &it, uint16_t digits, bool active) {
|
void draw_bargraph_(Display &it, uint16_t digits, bool active) {
|
||||||
const Color fg(255, 255, 255);
|
const Color fg(255, 255, 255);
|
||||||
const Color inactive(80, 80, 80);
|
const Color inactive(80, 80, 80);
|
||||||
|
|||||||
@@ -29,9 +29,6 @@ esphome:
|
|||||||
on_boot:
|
on_boot:
|
||||||
priority: 850
|
priority: 850
|
||||||
then:
|
then:
|
||||||
# The Core2 LCD/backlight rails are controlled by the AXP192 PMIC. ESPHome's
|
|
||||||
# stock M5CORE2 display setup does not initialize those rails here, so the
|
|
||||||
# custom header performs the M5Stack-specific power sequencing once I2C exists.
|
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
owon_b35t::core2_axp192_init(id(core2_i2c));
|
owon_b35t::core2_axp192_init(id(core2_i2c));
|
||||||
project:
|
project:
|
||||||
@@ -80,14 +77,10 @@ wifi:
|
|||||||
captive_portal:
|
captive_portal:
|
||||||
|
|
||||||
globals:
|
globals:
|
||||||
# 0 = OWON meter page, 1 = Atorch DL24 page. This is intentionally volatile so
|
|
||||||
# every boot starts with the multimeter view until one device connects.
|
|
||||||
- id: display_page
|
- id: display_page
|
||||||
type: int
|
type: int
|
||||||
initial_value: "0"
|
initial_value: "0"
|
||||||
restore_value: no
|
restore_value: no
|
||||||
# The Atorch external component does not expose a simple connected flag that the
|
|
||||||
# display lambda can query, so BLE callbacks maintain this state explicitly.
|
|
||||||
- id: atorch_connected
|
- id: atorch_connected
|
||||||
type: bool
|
type: bool
|
||||||
initial_value: "false"
|
initial_value: "false"
|
||||||
@@ -107,19 +100,15 @@ script:
|
|||||||
- delay: 2min
|
- delay: 2min
|
||||||
- if:
|
- if:
|
||||||
condition:
|
condition:
|
||||||
# Only dim when both BLE devices are disconnected; active lab instruments
|
|
||||||
# keep the display fully lit so readings remain visible at a glance.
|
|
||||||
lambda: |-
|
lambda: |-
|
||||||
return !owon_meter.connected && !id(atorch_connected);
|
return !owon_meter.connected && !id(atorch_connected);
|
||||||
then:
|
then:
|
||||||
- light.turn_on:
|
- light.turn_on:
|
||||||
id: backlight
|
id: backlight
|
||||||
brightness: 70%
|
brightness: 50%
|
||||||
- delay: 3min
|
- delay: 3min
|
||||||
- if:
|
- if:
|
||||||
condition:
|
condition:
|
||||||
# Re-check before turning the backlight off, because a connection may
|
|
||||||
# have been established during the preceding delay.
|
|
||||||
lambda: |-
|
lambda: |-
|
||||||
return !owon_meter.connected && !id(atorch_connected);
|
return !owon_meter.connected && !id(atorch_connected);
|
||||||
then:
|
then:
|
||||||
@@ -128,8 +117,6 @@ script:
|
|||||||
interval:
|
interval:
|
||||||
- interval: 10s
|
- interval: 10s
|
||||||
then:
|
then:
|
||||||
# Periodic memory telemetry is useful on the Core2 because BLE, PSRAM, the
|
|
||||||
# MIPI framebuffer, and external components can fragment different heap pools.
|
|
||||||
- lambda: |-
|
- 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",
|
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<unsigned>(esp_get_free_heap_size()),
|
static_cast<unsigned>(esp_get_free_heap_size()),
|
||||||
@@ -152,16 +139,12 @@ ble_client:
|
|||||||
on_connect:
|
on_connect:
|
||||||
then:
|
then:
|
||||||
- script.execute: wake_backlight
|
- script.execute: wake_backlight
|
||||||
# Notify the custom parser/display object and switch to the meter page when
|
|
||||||
# the OWON connects, so the on-device screen follows the active instrument.
|
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
owon_meter.on_connect();
|
owon_meter.on_connect();
|
||||||
id(display_page) = 0;
|
id(display_page) = 0;
|
||||||
id(lcd).update();
|
id(lcd).update();
|
||||||
on_disconnect:
|
on_disconnect:
|
||||||
then:
|
then:
|
||||||
# Keep the UI useful after a disconnect: if the Atorch is still online,
|
|
||||||
# automatically fall back from the OWON page to the Atorch page.
|
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
owon_meter.on_disconnect();
|
owon_meter.on_disconnect();
|
||||||
if (id(display_page) == 0 && id(atorch_connected)) {
|
if (id(display_page) == 0 && id(atorch_connected)) {
|
||||||
@@ -170,8 +153,6 @@ ble_client:
|
|||||||
id(lcd).update();
|
id(lcd).update();
|
||||||
- if:
|
- if:
|
||||||
condition:
|
condition:
|
||||||
# Start the idle timer only after both BLE clients are gone; otherwise
|
|
||||||
# the remaining connected instrument still deserves an active display.
|
|
||||||
lambda: |-
|
lambda: |-
|
||||||
return !owon_meter.connected && !id(atorch_connected);
|
return !owon_meter.connected && !id(atorch_connected);
|
||||||
then:
|
then:
|
||||||
@@ -182,16 +163,12 @@ ble_client:
|
|||||||
on_connect:
|
on_connect:
|
||||||
then:
|
then:
|
||||||
- script.execute: wake_backlight
|
- script.execute: wake_backlight
|
||||||
# The Atorch component handles measurements, while this explicit flag/page
|
|
||||||
# switch keeps the shared display state synchronized with the BLE client.
|
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(atorch_connected) = true;
|
id(atorch_connected) = true;
|
||||||
id(display_page) = 1;
|
id(display_page) = 1;
|
||||||
id(lcd).update();
|
id(lcd).update();
|
||||||
on_disconnect:
|
on_disconnect:
|
||||||
then:
|
then:
|
||||||
# If the load disconnects while the meter is still connected, move the
|
|
||||||
# physical display back to the meter instead of leaving a stale load page.
|
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(atorch_connected) = false;
|
id(atorch_connected) = false;
|
||||||
if (id(display_page) == 1 && owon_meter.connected) {
|
if (id(display_page) == 1 && owon_meter.connected) {
|
||||||
@@ -200,8 +177,6 @@ ble_client:
|
|||||||
id(lcd).update();
|
id(lcd).update();
|
||||||
- if:
|
- if:
|
||||||
condition:
|
condition:
|
||||||
# Same idle rule as OWON disconnect: only dim/off when neither BLE
|
|
||||||
# target is connected anymore.
|
|
||||||
lambda: |-
|
lambda: |-
|
||||||
return !owon_meter.connected && !id(atorch_connected);
|
return !owon_meter.connected && !id(atorch_connected);
|
||||||
then:
|
then:
|
||||||
@@ -228,8 +203,6 @@ output:
|
|||||||
type: float
|
type: float
|
||||||
id: lcd_backlight
|
id: lcd_backlight
|
||||||
write_action:
|
write_action:
|
||||||
# Bridge ESPHome's generic monochromatic light level to the Core2-specific
|
|
||||||
# AXP192 backlight voltage helper implemented in lab-ble-proxy.h.
|
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
owon_b35t::core2_axp192_set_backlight(state);
|
owon_b35t::core2_axp192_set_backlight(state);
|
||||||
|
|
||||||
@@ -244,8 +217,6 @@ font:
|
|||||||
- file: "fonts/Roboto-Regular.ttf"
|
- file: "fonts/Roboto-Regular.ttf"
|
||||||
id: meter_font
|
id: meter_font
|
||||||
size: 15
|
size: 15
|
||||||
# Glyphs are whitelisted to save flash/RAM; include symbols used by units,
|
|
||||||
# status labels, soft-button names, and Atorch metric text.
|
|
||||||
glyphs:
|
glyphs:
|
||||||
[
|
[
|
||||||
" ",
|
" ",
|
||||||
@@ -322,7 +293,6 @@ font:
|
|||||||
- file: "fonts/Roboto-Medium.ttf"
|
- file: "fonts/Roboto-Medium.ttf"
|
||||||
id: atorch_value_font
|
id: atorch_value_font
|
||||||
size: 22
|
size: 22
|
||||||
# Larger Atorch value font only needs numeric characters and measurement units.
|
|
||||||
glyphs:
|
glyphs:
|
||||||
[
|
[
|
||||||
" ",
|
" ",
|
||||||
@@ -352,8 +322,6 @@ display:
|
|||||||
id: lcd
|
id: lcd
|
||||||
model: M5CORE2
|
model: M5CORE2
|
||||||
update_interval: 500ms
|
update_interval: 500ms
|
||||||
# Rendering is delegated to the custom C++ helper because ESPHome display YAML
|
|
||||||
# primitives would be unwieldy for the OWON seven-segment clone and Atorch dashboard.
|
|
||||||
lambda: |-
|
lambda: |-
|
||||||
owon_meter.render(
|
owon_meter.render(
|
||||||
it,
|
it,
|
||||||
@@ -378,8 +346,6 @@ touchscreen:
|
|||||||
- script.execute: wake_backlight
|
- script.execute: wake_backlight
|
||||||
- if:
|
- if:
|
||||||
condition:
|
condition:
|
||||||
# Touching an idle/disconnected unit wakes the display briefly, then
|
|
||||||
# restarts the idle timer if no BLE target is available.
|
|
||||||
lambda: |-
|
lambda: |-
|
||||||
return !owon_meter.connected && !id(atorch_connected);
|
return !owon_meter.connected && !id(atorch_connected);
|
||||||
then:
|
then:
|
||||||
@@ -395,7 +361,6 @@ binary_sensor:
|
|||||||
y_max: 195
|
y_max: 195
|
||||||
on_press:
|
on_press:
|
||||||
then:
|
then:
|
||||||
# The large middle touch zone toggles between the two custom render pages.
|
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(display_page) = 1 - id(display_page);
|
id(display_page) = 1 - id(display_page);
|
||||||
id(lcd).update();
|
id(lcd).update();
|
||||||
@@ -409,7 +374,6 @@ binary_sensor:
|
|||||||
internal: true
|
internal: true
|
||||||
on_press:
|
on_press:
|
||||||
then:
|
then:
|
||||||
# Left soft button changes which OWON remote command the center button will send.
|
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
owon_meter.previous_button();
|
owon_meter.previous_button();
|
||||||
- platform: touchscreen
|
- platform: touchscreen
|
||||||
@@ -432,8 +396,6 @@ binary_sensor:
|
|||||||
id: owon_ble_client
|
id: owon_ble_client
|
||||||
service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb"
|
service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb"
|
||||||
characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb"
|
characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb"
|
||||||
# OWON remote-control payload: first byte selects the meter button,
|
|
||||||
# second byte selects the normal/short-press action variant.
|
|
||||||
value: !lambda |-
|
value: !lambda |-
|
||||||
std::vector<uint8_t> data = {owon_meter.selected_button, 0x01};
|
std::vector<uint8_t> data = {owon_meter.selected_button, 0x01};
|
||||||
return data;
|
return data;
|
||||||
@@ -448,8 +410,6 @@ binary_sensor:
|
|||||||
id: owon_ble_client
|
id: owon_ble_client
|
||||||
service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb"
|
service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb"
|
||||||
characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb"
|
characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb"
|
||||||
# Long-press encoding is not uniform: SELECT and HZ/DUTY still use
|
|
||||||
# 0x01, while RANGE/HOLD/REL/MAX-MIN use 0x00 for their alternate action.
|
|
||||||
value: !lambda |-
|
value: !lambda |-
|
||||||
uint8_t press_type = (owon_meter.selected_button == 1 || owon_meter.selected_button == 5) ? 0x01 : 0x00;
|
uint8_t press_type = (owon_meter.selected_button == 1 || owon_meter.selected_button == 5) ? 0x01 : 0x00;
|
||||||
std::vector<uint8_t> data = {owon_meter.selected_button, press_type};
|
std::vector<uint8_t> data = {owon_meter.selected_button, press_type};
|
||||||
@@ -464,14 +424,12 @@ binary_sensor:
|
|||||||
internal: true
|
internal: true
|
||||||
on_press:
|
on_press:
|
||||||
then:
|
then:
|
||||||
# Right soft button advances to the next OWON command label.
|
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
owon_meter.next_button();
|
owon_meter.next_button();
|
||||||
|
|
||||||
- platform: template
|
- platform: template
|
||||||
name: "${friendly_name} OWON Connected"
|
name: "${friendly_name} OWON Connected"
|
||||||
icon: "mdi:bluetooth-connect"
|
icon: "mdi:bluetooth-connect"
|
||||||
# Template binary sensors expose state maintained by the custom OWON parser object.
|
|
||||||
lambda: |-
|
lambda: |-
|
||||||
return owon_meter.connected;
|
return owon_meter.connected;
|
||||||
- platform: template
|
- platform: template
|
||||||
@@ -492,8 +450,6 @@ binary_sensor:
|
|||||||
return id(atorch_connected);
|
return id(atorch_connected);
|
||||||
|
|
||||||
sensor:
|
sensor:
|
||||||
# This hidden characteristic sensor is the bridge from OWON BLE notifications into
|
|
||||||
# custom C++ parsing. The returned float becomes the source numeric meter reading.
|
|
||||||
- platform: ble_client
|
- platform: ble_client
|
||||||
type: characteristic
|
type: characteristic
|
||||||
ble_client_id: owon_ble_client
|
ble_client_id: owon_ble_client
|
||||||
@@ -532,8 +488,6 @@ sensor:
|
|||||||
name: "${friendly_name} Atorch Dim Backlight"
|
name: "${friendly_name} Atorch Dim Backlight"
|
||||||
id: atorch_dim_backlight
|
id: atorch_dim_backlight
|
||||||
|
|
||||||
# The DL24 component exposes power, but this config computes Wh locally so the
|
|
||||||
# accumulated energy can be restored and reset alongside Atorch's own counters.
|
|
||||||
- platform: integration
|
- platform: integration
|
||||||
name: "${friendly_name} Atorch Energy"
|
name: "${friendly_name} Atorch Energy"
|
||||||
id: atorch_energy_calculated
|
id: atorch_energy_calculated
|
||||||
@@ -552,21 +506,18 @@ text_sensor:
|
|||||||
name: "${friendly_name} OWON Reading"
|
name: "${friendly_name} OWON Reading"
|
||||||
update_interval: 1s
|
update_interval: 1s
|
||||||
icon: "mdi:gauge"
|
icon: "mdi:gauge"
|
||||||
# Human-readable mirror of the meter LCD, including waiting/disconnected/OL states.
|
|
||||||
lambda: |-
|
lambda: |-
|
||||||
return owon_meter.reading_text();
|
return owon_meter.reading_text();
|
||||||
- platform: template
|
- platform: template
|
||||||
name: "${friendly_name} OWON Unit"
|
name: "${friendly_name} OWON Unit"
|
||||||
update_interval: 1s
|
update_interval: 1s
|
||||||
icon: "mdi:ruler"
|
icon: "mdi:ruler"
|
||||||
# Combines SI prefix and unit into one HA text entity, e.g. mV, kΩ, µA, %.
|
|
||||||
lambda: |-
|
lambda: |-
|
||||||
return std::string(owon_meter.scale()) + owon_meter.unit();
|
return std::string(owon_meter.scale()) + owon_meter.unit();
|
||||||
- platform: template
|
- platform: template
|
||||||
name: "${friendly_name} OWON Mode"
|
name: "${friendly_name} OWON Mode"
|
||||||
icon: "mdi:knob"
|
icon: "mdi:knob"
|
||||||
update_interval: 1s
|
update_interval: 1s
|
||||||
# Exposes decoded annunciators such as AC/DC, AUTO, HOLD, REL, MIN/MAX.
|
|
||||||
lambda: |-
|
lambda: |-
|
||||||
return owon_meter.mode_text();
|
return owon_meter.mode_text();
|
||||||
|
|
||||||
@@ -584,7 +535,6 @@ button:
|
|||||||
reset_all:
|
reset_all:
|
||||||
name: "${friendly_name} Atorch Reset All"
|
name: "${friendly_name} Atorch Reset All"
|
||||||
on_press:
|
on_press:
|
||||||
# Keep the locally integrated Wh counter in sync with the DL24 reset-all command.
|
|
||||||
- sensor.integration.reset: atorch_energy_calculated
|
- sensor.integration.reset: atorch_energy_calculated
|
||||||
usb_plus:
|
usb_plus:
|
||||||
name: "${friendly_name} Atorch Plus"
|
name: "${friendly_name} Atorch Plus"
|
||||||
@@ -594,8 +544,6 @@ button:
|
|||||||
name: "${friendly_name} Atorch Setup"
|
name: "${friendly_name} Atorch Setup"
|
||||||
enter:
|
enter:
|
||||||
name: "${friendly_name} Atorch Enter"
|
name: "${friendly_name} Atorch Enter"
|
||||||
# Home Assistant button entities for direct OWON remote commands. The payloads
|
|
||||||
# are the same two-byte command format used by the touchscreen soft buttons.
|
|
||||||
- platform: template
|
- platform: template
|
||||||
name: "OWON SELECT"
|
name: "OWON SELECT"
|
||||||
id: owon_btn_select
|
id: owon_btn_select
|
||||||
@@ -604,7 +552,6 @@ button:
|
|||||||
id: owon_ble_client
|
id: owon_ble_client
|
||||||
service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb"
|
service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb"
|
||||||
characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb"
|
characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb"
|
||||||
# SELECT normal press.
|
|
||||||
value: !lambda "return std::vector<uint8_t>({1, 0x01});"
|
value: !lambda "return std::vector<uint8_t>({1, 0x01});"
|
||||||
|
|
||||||
- platform: template
|
- platform: template
|
||||||
@@ -615,7 +562,6 @@ button:
|
|||||||
id: owon_ble_client
|
id: owon_ble_client
|
||||||
service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb"
|
service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb"
|
||||||
characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb"
|
characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb"
|
||||||
# RANGE normal press.
|
|
||||||
value: !lambda "return std::vector<uint8_t>({2, 0x01});"
|
value: !lambda "return std::vector<uint8_t>({2, 0x01});"
|
||||||
|
|
||||||
- platform: template
|
- platform: template
|
||||||
@@ -626,7 +572,6 @@ button:
|
|||||||
id: owon_ble_client
|
id: owon_ble_client
|
||||||
service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb"
|
service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb"
|
||||||
characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb"
|
characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb"
|
||||||
# HOLD is the short/normal action on the shared HLD/LIG physical key.
|
|
||||||
value: !lambda "return std::vector<uint8_t>({3, 0x01});"
|
value: !lambda "return std::vector<uint8_t>({3, 0x01});"
|
||||||
|
|
||||||
- platform: template
|
- platform: template
|
||||||
@@ -637,7 +582,6 @@ button:
|
|||||||
id: owon_ble_client
|
id: owon_ble_client
|
||||||
service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb"
|
service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb"
|
||||||
characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb"
|
characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb"
|
||||||
# LIGHT is the alternate/long action on the same HLD/LIG key.
|
|
||||||
value: !lambda "return std::vector<uint8_t>({3, 0x00});"
|
value: !lambda "return std::vector<uint8_t>({3, 0x00});"
|
||||||
|
|
||||||
- platform: template
|
- platform: template
|
||||||
@@ -648,7 +592,6 @@ button:
|
|||||||
id: owon_ble_client
|
id: owon_ble_client
|
||||||
service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb"
|
service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb"
|
||||||
characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb"
|
characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb"
|
||||||
# RELATIVE is the short/normal action on the shared REL/BT key.
|
|
||||||
value: !lambda "return std::vector<uint8_t>({4, 0x01});"
|
value: !lambda "return std::vector<uint8_t>({4, 0x01});"
|
||||||
|
|
||||||
- platform: template
|
- platform: template
|
||||||
@@ -659,7 +602,6 @@ button:
|
|||||||
id: owon_ble_client
|
id: owon_ble_client
|
||||||
service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb"
|
service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb"
|
||||||
characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb"
|
characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb"
|
||||||
# BT is the alternate/long action on the same REL/BT key.
|
|
||||||
value: !lambda "return std::vector<uint8_t>({4, 0x00});"
|
value: !lambda "return std::vector<uint8_t>({4, 0x00});"
|
||||||
|
|
||||||
- platform: template
|
- platform: template
|
||||||
@@ -670,7 +612,6 @@ button:
|
|||||||
id: owon_ble_client
|
id: owon_ble_client
|
||||||
service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb"
|
service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb"
|
||||||
characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb"
|
characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb"
|
||||||
# HZ/DUTY command uses the normal action variant.
|
|
||||||
value: !lambda "return std::vector<uint8_t>({5, 0x01});"
|
value: !lambda "return std::vector<uint8_t>({5, 0x01});"
|
||||||
|
|
||||||
- platform: template
|
- platform: template
|
||||||
@@ -681,5 +622,4 @@ button:
|
|||||||
id: owon_ble_client
|
id: owon_ble_client
|
||||||
service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb"
|
service_uuid: "0000fff0-0000-1000-8000-00805f9b34fb"
|
||||||
characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb"
|
characteristic_uuid: "0000fff3-0000-1000-8000-00805f9b34fb"
|
||||||
# MAX/MIN command uses the normal action variant.
|
|
||||||
value: !lambda "return std::vector<uint8_t>({6, 0x01});"
|
value: !lambda "return std::vector<uint8_t>({6, 0x01});"
|
||||||
|
|||||||
@@ -53,11 +53,16 @@ esp32:
|
|||||||
board: nodemcu-32s
|
board: nodemcu-32s
|
||||||
framework:
|
framework:
|
||||||
type: esp-idf
|
type: esp-idf
|
||||||
|
advanced:
|
||||||
|
minimum_chip_revision: "3.0"
|
||||||
|
|
||||||
|
network:
|
||||||
|
enable_ipv6: true
|
||||||
|
|
||||||
wifi:
|
wifi:
|
||||||
ssid: "Voltage-legacy"
|
ssid: "Voltage-legacy"
|
||||||
password: !secret voltage_legacy_psk
|
password: !secret voltage_legacy_psk
|
||||||
use_address: ${name}.home
|
# use_address: ${name}.home
|
||||||
power_save_mode: high
|
power_save_mode: high
|
||||||
fast_connect: on
|
fast_connect: on
|
||||||
min_auth_mode: WPA2
|
min_auth_mode: WPA2
|
||||||
|
|||||||
@@ -26,12 +26,15 @@ ota:
|
|||||||
id: ota_esphome
|
id: ota_esphome
|
||||||
password: !secret ota
|
password: !secret ota
|
||||||
|
|
||||||
|
network:
|
||||||
|
enable_ipv6: true
|
||||||
|
|
||||||
wifi:
|
wifi:
|
||||||
on_connect:
|
on_connect:
|
||||||
- delay: 5s # Gives time for improv results to be transmitted
|
- delay: 5s # Gives time for improv results to be transmitted
|
||||||
ssid: Voltage-legacy
|
ssid: Voltage-legacy
|
||||||
password: !secret voltage_legacy_psk
|
password: !secret voltage_legacy_psk
|
||||||
use_address: ${name}.home
|
#use_address: ${name}.home
|
||||||
power_save_mode: high
|
power_save_mode: high
|
||||||
fast_connect: on
|
fast_connect: on
|
||||||
min_auth_mode: WPA2
|
min_auth_mode: WPA2
|
||||||
|
|||||||
@@ -25,10 +25,13 @@ external_components:
|
|||||||
components: [es8388]
|
components: [es8388]
|
||||||
refresh: 0s
|
refresh: 0s
|
||||||
|
|
||||||
|
network:
|
||||||
|
enable_ipv6: true
|
||||||
|
|
||||||
wifi:
|
wifi:
|
||||||
ssid: Voltage-legacy
|
ssid: Voltage-legacy
|
||||||
password: !secret voltage_legacy_psk
|
password: !secret voltage_legacy_psk
|
||||||
use_address: ${name}.home
|
#use_address: ${name}.home
|
||||||
power_save_mode: high
|
power_save_mode: high
|
||||||
fast_connect: on
|
fast_connect: on
|
||||||
min_auth_mode: WPA2
|
min_auth_mode: WPA2
|
||||||
|
|||||||
@@ -50,10 +50,13 @@ esphome:
|
|||||||
esp8266:
|
esp8266:
|
||||||
board: esp12e
|
board: esp12e
|
||||||
|
|
||||||
|
network:
|
||||||
|
enable_ipv6: true
|
||||||
|
|
||||||
wifi:
|
wifi:
|
||||||
ssid: "Voltage-legacy"
|
ssid: "Voltage-legacy"
|
||||||
password: !secret voltage_legacy_psk
|
password: !secret voltage_legacy_psk
|
||||||
use_address: riden-labornetzteil-18a.home
|
#use_address: riden-labornetzteil-18a.home
|
||||||
power_save_mode: high
|
power_save_mode: high
|
||||||
fast_connect: on
|
fast_connect: on
|
||||||
min_auth_mode: WPA2
|
min_auth_mode: WPA2
|
||||||
|
|||||||
@@ -50,10 +50,13 @@ esphome:
|
|||||||
esp8266:
|
esp8266:
|
||||||
board: esp12e
|
board: esp12e
|
||||||
|
|
||||||
|
network:
|
||||||
|
enable_ipv6: true
|
||||||
|
|
||||||
wifi:
|
wifi:
|
||||||
ssid: "Voltage-legacy"
|
ssid: "Voltage-legacy"
|
||||||
password: !secret voltage_legacy_psk
|
password: !secret voltage_legacy_psk
|
||||||
use_address: riden-labornetzteil-6a.home
|
#use_address: riden-labornetzteil-6a.home
|
||||||
power_save_mode: high
|
power_save_mode: high
|
||||||
fast_connect: on
|
fast_connect: on
|
||||||
min_auth_mode: WPA2
|
min_auth_mode: WPA2
|
||||||
|
|||||||
@@ -16,10 +16,13 @@ esp32:
|
|||||||
framework:
|
framework:
|
||||||
type: esp-idf
|
type: esp-idf
|
||||||
|
|
||||||
|
network:
|
||||||
|
enable_ipv6: true
|
||||||
|
|
||||||
wifi:
|
wifi:
|
||||||
ssid: "Voltage-legacy"
|
ssid: "Voltage-legacy"
|
||||||
password: !secret voltage_legacy_psk
|
password: !secret voltage_legacy_psk
|
||||||
use_address: serverroom.home
|
#use_address: serverroom.home
|
||||||
power_save_mode: high
|
power_save_mode: high
|
||||||
fast_connect: on
|
fast_connect: on
|
||||||
min_auth_mode: WPA2
|
min_auth_mode: WPA2
|
||||||
|
|||||||
+2
-28
@@ -139,34 +139,8 @@
|
|||||||
attributes:
|
attributes:
|
||||||
last_reset: '1970-01-01T00:00:00+00:00'
|
last_reset: '1970-01-01T00:00:00+00:00'
|
||||||
- name: internet_speed_out
|
- name: internet_speed_out
|
||||||
unique_id: '9519483670667'
|
|
||||||
state: >
|
state: >
|
||||||
{{ (( states('sensor.wan_out_derivative') | float * 8 / 1000000 ) | round(2)) }}
|
{{ (( states('sensor.wan_out_derivative') | float * 8 / 1000000 ) | round(2)) }}
|
||||||
unit_of_measurement: 'Mbps'
|
unit_of_measurement: 'Mbps'
|
||||||
|
attributes:
|
||||||
# ── Kalender-Ereignisse für epaperframe ──
|
last_reset: '1970-01-01T00:00:00+00:00'
|
||||||
# Alle Events werden per Automation (automations.yaml) in input_text Helfer geschrieben.
|
|
||||||
# Diese template sensors lesen nur noch daraus. Das verhindert doppelte Logik und garantiert,
|
|
||||||
# dass sowohl "heute" als auch "morgen" Events mit Titeln, Uhrzeiten und Beschreibungen verfügbar sind.
|
|
||||||
- text_sensor:
|
|
||||||
# ── HEUTE ──
|
|
||||||
- name: "Kalender heute Privat"
|
|
||||||
unique_id: kalender_heute_privat
|
|
||||||
state: "{{ states('input_text.kalender_heute_privat') }}"
|
|
||||||
icon: mdi:calendar-account
|
|
||||||
|
|
||||||
- name: "Kalender heute Arbeit"
|
|
||||||
unique_id: kalender_heute_arbeit
|
|
||||||
state: "{{ states('input_text.kalender_heute_arbeit') }}"
|
|
||||||
icon: mdi:calendar-account
|
|
||||||
|
|
||||||
# ── MORGEN ──
|
|
||||||
- name: "Kalender morgen Privat"
|
|
||||||
unique_id: kalender_morgen_privat
|
|
||||||
state: "{{ states('input_text.kalender_morgen_privat') }}"
|
|
||||||
icon: mdi:calendar-calendar-today
|
|
||||||
|
|
||||||
- name: "Kalender morgen Arbeit"
|
|
||||||
unique_id: kalender_morgen_arbeit
|
|
||||||
state: "{{ states('input_text.kalender_morgen_arbeit') }}"
|
|
||||||
icon: mdi:calendar-calendar-today
|
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
# Helpers for epaperframe calendar display
|
|
||||||
input_text:
|
|
||||||
- name: "Kalender heute Privat"
|
|
||||||
initial: "-"
|
|
||||||
icon: mdi:calendar-account
|
|
||||||
mode: text
|
|
||||||
- name: "Kalender heute Arbeit"
|
|
||||||
initial: "-"
|
|
||||||
icon: mdi:calendar-account
|
|
||||||
mode: text
|
|
||||||
- name: "Kalender morgen Privat"
|
|
||||||
initial: "-"
|
|
||||||
icon: mdi:calendar-calendar-today
|
|
||||||
mode: text
|
|
||||||
- name: "Kalender morgen Arbeit"
|
|
||||||
initial: "-"
|
|
||||||
icon: mdi:calendar-calendar-today
|
|
||||||
mode: text
|
|
||||||
Reference in New Issue
Block a user