Compare commits
4 Commits
owon-ai-test
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a3bd9ff213 | |||
| 24fc4bf9f4 | |||
| 0bfc2eff34 | |||
| 8f18a043af |
+1
-1
@@ -1 +1 @@
|
|||||||
2026.4.4
|
2026.5.3
|
||||||
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
|
||||||
|
|||||||
+9
-9
@@ -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
|
||||||
|
|||||||
@@ -1,293 +0,0 @@
|
|||||||
substitutions:
|
|
||||||
name: "owon-b35t"
|
|
||||||
friendly_name: "OWON B35T Multimeter"
|
|
||||||
device_description: "M5Stack Core 1 BLE client for OWON B35T/B35T+ multimeter with local graphical display"
|
|
||||||
owon_mac_address: !secret owon_b35t_mac_address
|
|
||||||
|
|
||||||
esphome:
|
|
||||||
name: ${name}
|
|
||||||
friendly_name: ${friendly_name}
|
|
||||||
comment: ${device_description}
|
|
||||||
min_version: 2024.6.0
|
|
||||||
includes:
|
|
||||||
- owon_b35t-m5stack-core1.h
|
|
||||||
project:
|
|
||||||
name: "custom.owon-b35t-m5stack"
|
|
||||||
version: "1.0"
|
|
||||||
|
|
||||||
esp32:
|
|
||||||
board: m5stack-core-esp32
|
|
||||||
framework:
|
|
||||||
type: esp-idf
|
|
||||||
advanced:
|
|
||||||
minimum_chip_revision: "3.1"
|
|
||||||
|
|
||||||
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: "OWON B35T Fallback Hotspot"
|
|
||||||
password: !secret fallback_psk
|
|
||||||
|
|
||||||
# Disabled to save RAM on the M5Stack Core 1. Re-enable temporarily if Wi-Fi recovery is needed.
|
|
||||||
# captive_portal:
|
|
||||||
|
|
||||||
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",
|
|
||||||
static_cast<unsigned>(esp_get_free_heap_size()),
|
|
||||||
static_cast<unsigned>(esp_get_minimum_free_heap_size()),
|
|
||||||
static_cast<unsigned>(heap_caps_get_free_size(MALLOC_CAP_INTERNAL)),
|
|
||||||
static_cast<unsigned>(heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL)),
|
|
||||||
static_cast<unsigned>(heap_caps_get_free_size(MALLOC_CAP_DMA)),
|
|
||||||
static_cast<unsigned>(heap_caps_get_largest_free_block(MALLOC_CAP_DMA)));
|
|
||||||
|
|
||||||
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();
|
|
||||||
on_disconnect:
|
|
||||||
then:
|
|
||||||
- lambda: |-
|
|
||||||
owon_meter.on_disconnect();
|
|
||||||
- logger.log:
|
|
||||||
level: WARN
|
|
||||||
format: "OWON BLE meter disconnected; restarting M5Stack to reclaim heap"
|
|
||||||
- delay: 1s
|
|
||||||
- lambda: |-
|
|
||||||
esp_restart();
|
|
||||||
|
|
||||||
spi:
|
|
||||||
clk_pin: GPIO18
|
|
||||||
mosi_pin: GPIO23
|
|
||||||
miso_pin: GPIO19
|
|
||||||
|
|
||||||
output:
|
|
||||||
- platform: ledc
|
|
||||||
pin: GPIO32
|
|
||||||
id: lcd_backlight
|
|
||||||
|
|
||||||
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",
|
|
||||||
"c",
|
|
||||||
"d",
|
|
||||||
"e",
|
|
||||||
"f",
|
|
||||||
"g",
|
|
||||||
"h",
|
|
||||||
"i",
|
|
||||||
"k",
|
|
||||||
"m",
|
|
||||||
"n",
|
|
||||||
"o",
|
|
||||||
"r",
|
|
||||||
"s",
|
|
||||||
"t",
|
|
||||||
"u",
|
|
||||||
"v",
|
|
||||||
"w",
|
|
||||||
"y",
|
|
||||||
"z",
|
|
||||||
"°",
|
|
||||||
"µ",
|
|
||||||
"Ω",
|
|
||||||
]
|
|
||||||
|
|
||||||
display:
|
|
||||||
- platform: ili9xxx
|
|
||||||
id: lcd
|
|
||||||
model: M5STACK
|
|
||||||
cs_pin: GPIO14
|
|
||||||
dc_pin: GPIO27
|
|
||||||
reset_pin: GPIO33
|
|
||||||
invert_colors: true
|
|
||||||
color_palette: 8BIT
|
|
||||||
rotation: 0
|
|
||||||
update_interval: 500ms
|
|
||||||
lambda: |-
|
|
||||||
owon_meter.render(it, id(meter_font));
|
|
||||||
|
|
||||||
binary_sensor:
|
|
||||||
- platform: gpio
|
|
||||||
id: button_a
|
|
||||||
pin:
|
|
||||||
number: GPIO39
|
|
||||||
inverted: true
|
|
||||||
internal: true
|
|
||||||
on_press:
|
|
||||||
then:
|
|
||||||
- lambda: |-
|
|
||||||
owon_meter.previous_button();
|
|
||||||
- platform: gpio
|
|
||||||
id: button_b
|
|
||||||
pin:
|
|
||||||
number: GPIO38
|
|
||||||
inverted: true
|
|
||||||
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<uint8_t> 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<uint8_t> data = {owon_meter.selected_button, press_type};
|
|
||||||
return data;
|
|
||||||
- platform: gpio
|
|
||||||
id: button_c
|
|
||||||
pin:
|
|
||||||
number: GPIO37
|
|
||||||
inverted: true
|
|
||||||
internal: true
|
|
||||||
on_press:
|
|
||||||
then:
|
|
||||||
- lambda: |-
|
|
||||||
owon_meter.next_button();
|
|
||||||
|
|
||||||
- platform: template
|
|
||||||
name: "${friendly_name} Connected"
|
|
||||||
lambda: |-
|
|
||||||
return owon_meter.connected;
|
|
||||||
- platform: template
|
|
||||||
name: "${friendly_name} Overload"
|
|
||||||
lambda: |-
|
|
||||||
return owon_meter.overload;
|
|
||||||
- platform: template
|
|
||||||
name: "${friendly_name} Low Battery"
|
|
||||||
lambda: |-
|
|
||||||
return owon_meter.low_battery;
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
text_sensor:
|
|
||||||
- platform: template
|
|
||||||
name: "${friendly_name} Reading"
|
|
||||||
update_interval: 2s
|
|
||||||
lambda: |-
|
|
||||||
return owon_meter.reading_text();
|
|
||||||
- platform: template
|
|
||||||
name: "${friendly_name} Unit"
|
|
||||||
update_interval: 2s
|
|
||||||
lambda: |-
|
|
||||||
return std::string(owon_meter.scale()) + owon_meter.unit();
|
|
||||||
- platform: template
|
|
||||||
name: "${friendly_name} Mode"
|
|
||||||
update_interval: 2s
|
|
||||||
lambda: |-
|
|
||||||
return owon_meter.mode_text();
|
|
||||||
@@ -1,658 +0,0 @@
|
|||||||
/*
|
|
||||||
* ESPHome helper for OWON B35T/B35T+ BLE meter on M5Stack Core 1.
|
|
||||||
* Parser is based on the standalone Arduino sketch by Reaper7
|
|
||||||
* (Beerware license, Revision 42) and Dean Cording's owonb35 notes.
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include "esp_heap_caps.h"
|
|
||||||
#include "esp_system.h"
|
|
||||||
#include "esphome/core/helpers.h"
|
|
||||||
#include "esphome/components/i2c/i2c.h"
|
|
||||||
#include "esphome/core/log.h"
|
|
||||||
#include "esphome/components/display/display.h"
|
|
||||||
|
|
||||||
namespace owon_b35t {
|
|
||||||
|
|
||||||
using esphome::Color;
|
|
||||||
using esphome::display::Display;
|
|
||||||
|
|
||||||
static const char *const TAG = "owon_b35t";
|
|
||||||
static const char *const POWER_TAG = "core2_power";
|
|
||||||
static constexpr uint8_t AXP192_ADDR = 0x34;
|
|
||||||
static esphome::i2c::I2CDevice axp192;
|
|
||||||
static bool axp192_ready = false;
|
|
||||||
|
|
||||||
static bool axp_write(uint8_t reg, uint8_t value) {
|
|
||||||
if (!axp192_ready) return false;
|
|
||||||
bool ok = axp192.write_byte(reg, value);
|
|
||||||
if (!ok) ESP_LOGW(POWER_TAG, "AXP192 write reg 0x%02X failed", reg);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool axp_read(uint8_t reg, uint8_t *value) {
|
|
||||||
if (!axp192_ready) return false;
|
|
||||||
bool ok = axp192.read_byte(reg, value);
|
|
||||||
if (!ok) ESP_LOGW(POWER_TAG, "AXP192 read reg 0x%02X failed", reg);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void axp_update(uint8_t reg, uint8_t clear_mask, uint8_t set_mask) {
|
|
||||||
uint8_t value = 0;
|
|
||||||
if (!axp_read(reg, &value)) return;
|
|
||||||
value = (value & ~clear_mask) | set_mask;
|
|
||||||
axp_write(reg, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint8_t axp_dc_voltage_data(uint16_t millivolts) {
|
|
||||||
if (millivolts < 700) millivolts = 700;
|
|
||||||
if (millivolts > 3500) millivolts = 3500;
|
|
||||||
return static_cast<uint8_t>((millivolts - 700) / 25) & 0x7F;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint8_t axp_ldo_voltage_data(uint16_t millivolts) {
|
|
||||||
if (millivolts < 1800) millivolts = 1800;
|
|
||||||
if (millivolts > 3300) millivolts = 3300;
|
|
||||||
return static_cast<uint8_t>((millivolts - 1800) / 100) & 0x0F;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void core2_axp192_set_lcd_voltage(uint16_t millivolts) {
|
|
||||||
uint8_t value = 0;
|
|
||||||
axp_read(0x27, &value);
|
|
||||||
axp_write(0x27, (value & 0x80) | axp_dc_voltage_data(millivolts)); // DCDC3, LCD backlight
|
|
||||||
}
|
|
||||||
|
|
||||||
static void core2_axp192_set_backlight(float brightness) {
|
|
||||||
if (brightness <= 0.0f) {
|
|
||||||
axp_update(0x12, 0x02, 0x00); // DCDC3 off
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (brightness > 1.0f) brightness = 1.0f;
|
|
||||||
uint16_t millivolts = static_cast<uint16_t>(2400 + brightness * 900);
|
|
||||||
core2_axp192_set_lcd_voltage(millivolts);
|
|
||||||
axp_update(0x12, 0x00, 0x02); // DCDC3 on
|
|
||||||
}
|
|
||||||
|
|
||||||
static void core2_axp192_init(esphome::i2c::I2CBus *bus) {
|
|
||||||
axp192.set_i2c_bus(bus);
|
|
||||||
axp192.set_i2c_address(AXP192_ADDR);
|
|
||||||
axp192_ready = true;
|
|
||||||
ESP_LOGI(POWER_TAG, "Initializing M5Stack Core2 AXP192 LCD power");
|
|
||||||
axp_update(0x30, 0xF9, 0x02); // Disable VBUS current limit, preserve bit 2.
|
|
||||||
axp_update(0x92, 0x07, 0x00); // GPIO1 open-drain output.
|
|
||||||
axp_update(0x93, 0x07, 0x00); // GPIO2 open-drain output.
|
|
||||||
axp_write(0x35, 0xA2); // RTC battery charging.
|
|
||||||
|
|
||||||
uint8_t value = 0;
|
|
||||||
axp_read(0x26, &value);
|
|
||||||
axp_write(0x26, (value & 0x80) | axp_dc_voltage_data(3350)); // DCDC1 ESP32 VDD.
|
|
||||||
core2_axp192_set_lcd_voltage(2800);
|
|
||||||
|
|
||||||
uint8_t ldo2 = axp_ldo_voltage_data(3300);
|
|
||||||
uint8_t ldo3 = axp_ldo_voltage_data(2000);
|
|
||||||
axp_write(0x28, (ldo2 << 4) | ldo3); // LDO2 LCD logic/SD, LDO3 vibrator.
|
|
||||||
axp_update(0x12, 0x00, 0x07); // Enable DCDC1, DCDC3, LDO2.
|
|
||||||
axp_write(0x82, 0xFF); // ADCs on.
|
|
||||||
axp_update(0x95, 0x8D, 0x84); // GPIO4 setup, as M5Core2 library does.
|
|
||||||
axp_write(0x36, 0x4C); // Power key timing.
|
|
||||||
|
|
||||||
// LCD reset through AXP192 GPIO4.
|
|
||||||
axp_update(0x96, 0x02, 0x00);
|
|
||||||
delay(100);
|
|
||||||
axp_update(0x96, 0x00, 0x02);
|
|
||||||
delay(100);
|
|
||||||
|
|
||||||
core2_axp192_set_backlight(1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const uint8_t ACCU_BMP[32] = {
|
|
||||||
0b00000000, 0b00000000,
|
|
||||||
0b00000000, 0b00000000,
|
|
||||||
0b00000000, 0b00000000,
|
|
||||||
0b11111111, 0b11111110,
|
|
||||||
0b10000000, 0b00000010,
|
|
||||||
0b10000000, 0b00000011,
|
|
||||||
0b10000000, 0b00000011,
|
|
||||||
0b10000000, 0b00000011,
|
|
||||||
0b10000000, 0b00000011,
|
|
||||||
0b10000000, 0b00000011,
|
|
||||||
0b10000000, 0b00000011,
|
|
||||||
0b10000000, 0b00000010,
|
|
||||||
0b11111111, 0b11111110,
|
|
||||||
0b00000000, 0b00000000,
|
|
||||||
0b00000000, 0b00000000,
|
|
||||||
0b00000000, 0b00000000,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const uint8_t BLE_BMP[32] = {
|
|
||||||
0b00000001, 0b10000000,
|
|
||||||
0b00000001, 0b11000000,
|
|
||||||
0b00010001, 0b01100000,
|
|
||||||
0b00011001, 0b00110000,
|
|
||||||
0b00001101, 0b00011000,
|
|
||||||
0b00000111, 0b00110000,
|
|
||||||
0b00000011, 0b01100000,
|
|
||||||
0b00000001, 0b11000000,
|
|
||||||
0b00000001, 0b11000000,
|
|
||||||
0b00000011, 0b01100000,
|
|
||||||
0b00000111, 0b00110000,
|
|
||||||
0b00001101, 0b00011000,
|
|
||||||
0b00011001, 0b00110000,
|
|
||||||
0b00010001, 0b01100000,
|
|
||||||
0b00000001, 0b11000000,
|
|
||||||
0b00000001, 0b10000000,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const uint8_t DIODE_BMP[32] = {
|
|
||||||
0b00001000, 0b00011000,
|
|
||||||
0b00001100, 0b00011000,
|
|
||||||
0b00001110, 0b00011000,
|
|
||||||
0b00001111, 0b00011000,
|
|
||||||
0b00001111, 0b10011000,
|
|
||||||
0b00001111, 0b11011000,
|
|
||||||
0b00001111, 0b11111000,
|
|
||||||
0b11111111, 0b11111111,
|
|
||||||
0b11111111, 0b11111111,
|
|
||||||
0b00001111, 0b11111000,
|
|
||||||
0b00001111, 0b11011000,
|
|
||||||
0b00001111, 0b10011000,
|
|
||||||
0b00001111, 0b00011000,
|
|
||||||
0b00001110, 0b00011000,
|
|
||||||
0b00001100, 0b00011000,
|
|
||||||
0b00001000, 0b00011000,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const uint8_t BUZZ_BMP[32] = {
|
|
||||||
0b00000000, 0b11000000,
|
|
||||||
0b00000001, 0b11000000,
|
|
||||||
0b00000011, 0b11000001,
|
|
||||||
0b00000111, 0b11000001,
|
|
||||||
0b00001111, 0b11000101,
|
|
||||||
0b11111111, 0b11000101,
|
|
||||||
0b11111111, 0b11010101,
|
|
||||||
0b11111111, 0b11010101,
|
|
||||||
0b11111111, 0b11010101,
|
|
||||||
0b11111111, 0b11010101,
|
|
||||||
0b11111111, 0b11000101,
|
|
||||||
0b00001111, 0b11000101,
|
|
||||||
0b00000111, 0b11000001,
|
|
||||||
0b00000011, 0b11000001,
|
|
||||||
0b00000001, 0b11000000,
|
|
||||||
0b00000000, 0b11000000,
|
|
||||||
};
|
|
||||||
|
|
||||||
class Meter {
|
|
||||||
public:
|
|
||||||
static constexpr uint8_t REGPLUSMINUS = 0x00;
|
|
||||||
static constexpr uint8_t FLAGPLUS = 0b00101011;
|
|
||||||
static constexpr uint8_t FLAGMINUS = 0b00101101;
|
|
||||||
static constexpr uint8_t REGDIG1 = 0x01;
|
|
||||||
static constexpr uint8_t REGDIG2 = 0x02;
|
|
||||||
static constexpr uint8_t REGDIG3 = 0x03;
|
|
||||||
static constexpr uint8_t REGDIG4 = 0x04;
|
|
||||||
static constexpr uint8_t REGPOINT = 0x06;
|
|
||||||
static constexpr uint8_t FLAGPOINT0 = 0b00110000;
|
|
||||||
static constexpr uint8_t FLAGPOINT1 = 0b00110001;
|
|
||||||
static constexpr uint8_t FLAGPOINT2 = 0b00110010;
|
|
||||||
static constexpr uint8_t FLAGPOINT3 = 0b00110100;
|
|
||||||
static constexpr uint8_t REGMODE = 0x07;
|
|
||||||
static constexpr uint8_t FLAGMODEHOLD = 0b00000010;
|
|
||||||
static constexpr uint8_t FLAGMODEREL = 0b00000100;
|
|
||||||
static constexpr uint8_t FLAGMODEAC = 0b00001000;
|
|
||||||
static constexpr uint8_t FLAGMODEDC = 0b00010000;
|
|
||||||
static constexpr uint8_t FLAGMODEAUTO = 0b00100000;
|
|
||||||
static constexpr uint8_t REGMINMAX = 0x08;
|
|
||||||
static constexpr uint8_t FLAGMIN = 0b00010000;
|
|
||||||
static constexpr uint8_t FLAGMAX = 0b00100000;
|
|
||||||
static constexpr uint8_t REGSCALE = 0x09;
|
|
||||||
static constexpr uint8_t FLAGSCALEDUTY = 0b00000010;
|
|
||||||
static constexpr uint8_t FLAGSCALEDIODE = 0b00000100;
|
|
||||||
static constexpr uint8_t FLAGSCALEBUZZ = 0b00001000;
|
|
||||||
static constexpr uint8_t FLAGSCALEMEGA = 0b00010000;
|
|
||||||
static constexpr uint8_t FLAGSCALEKILO = 0b00100000;
|
|
||||||
static constexpr uint8_t FLAGSCALEMILLI = 0b01000000;
|
|
||||||
static constexpr uint8_t FLAGSCALEMICRO = 0b10000000;
|
|
||||||
static constexpr uint8_t REGUNIT = 0x0a;
|
|
||||||
static constexpr uint8_t FLAGUNITFAHR = 0b00000001;
|
|
||||||
static constexpr uint8_t FLAGUNITGRAD = 0b00000010;
|
|
||||||
static constexpr uint8_t FLAGUNITNF = 0b00000100;
|
|
||||||
static constexpr uint8_t FLAGUNITHZ = 0b00001000;
|
|
||||||
static constexpr uint8_t FLAGUNITHFE = 0b00010000;
|
|
||||||
static constexpr uint8_t FLAGUNITOHM = 0b00100000;
|
|
||||||
static constexpr uint8_t FLAGUNITAMP = 0b01000000;
|
|
||||||
static constexpr uint8_t FLAGUNITVOLT = 0b10000000;
|
|
||||||
|
|
||||||
bool connected{false};
|
|
||||||
bool write_available{false};
|
|
||||||
bool is_plus{false};
|
|
||||||
bool low_battery{false};
|
|
||||||
bool overload{false};
|
|
||||||
bool has_reading{false};
|
|
||||||
uint8_t selected_button{1};
|
|
||||||
uint32_t last_notify_ms{0};
|
|
||||||
|
|
||||||
bool handle_notify(const std::vector<uint8_t> &data) {
|
|
||||||
if (data.size() > sizeof(this->raw_))
|
|
||||||
return false;
|
|
||||||
if (data.size() == 6 && data[1] >= 0xF0) {
|
|
||||||
memset(this->raw_, 0, sizeof(this->raw_));
|
|
||||||
memcpy(this->raw_, data.data(), data.size());
|
|
||||||
this->is_plus = true;
|
|
||||||
this->parse_plus_();
|
|
||||||
} else if (data.size() == 14 && data[12] == 0x0D && data[13] == 0x0A) {
|
|
||||||
memset(this->value_, 0, sizeof(this->value_));
|
|
||||||
memcpy(this->value_, data.data(), data.size());
|
|
||||||
this->is_plus = false;
|
|
||||||
} else {
|
|
||||||
ESP_LOGW(TAG, "Ignoring unexpected OWON frame length=%u", static_cast<unsigned>(data.size()));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->overload = memcmp(this->value_, OVERLOAD_FRAME, sizeof(OVERLOAD_FRAME)) == 0;
|
|
||||||
this->display_value = this->calc_display_value_();
|
|
||||||
this->base_value = this->calc_base_value_();
|
|
||||||
this->has_reading = true;
|
|
||||||
this->last_notify_ms = millis();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void on_connect() {
|
|
||||||
this->connected = true;
|
|
||||||
this->write_available = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void on_disconnect() {
|
|
||||||
this->connected = false;
|
|
||||||
this->write_available = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
float value() const { return this->display_value; }
|
|
||||||
float value_base() const { return this->base_value; }
|
|
||||||
bool negative() const { return (this->value_[REGPLUSMINUS] & FLAGMINUS) == FLAGMINUS; }
|
|
||||||
bool auto_range() const { return (this->value_[REGMODE] & FLAGMODEAUTO) == FLAGMODEAUTO; }
|
|
||||||
bool hold() const { return (this->value_[REGMODE] & FLAGMODEHOLD) == FLAGMODEHOLD; }
|
|
||||||
bool relative() const { return (this->value_[REGMODE] & FLAGMODEREL) == FLAGMODEREL; }
|
|
||||||
bool ac() const { return (this->value_[REGMODE] & FLAGMODEAC) == FLAGMODEAC; }
|
|
||||||
bool dc() const { return (this->value_[REGMODE] & FLAGMODEDC) == FLAGMODEDC; }
|
|
||||||
bool min_mode() const { return (this->value_[REGMINMAX] & FLAGMIN) == FLAGMIN; }
|
|
||||||
bool max_mode() const { return (this->value_[REGMINMAX] & FLAGMAX) == FLAGMAX; }
|
|
||||||
bool diode() const { return (this->value_[REGSCALE] & FLAGSCALEDIODE) == FLAGSCALEDIODE; }
|
|
||||||
bool continuity() const { return (this->value_[REGSCALE] & FLAGSCALEBUZZ) == FLAGSCALEBUZZ; }
|
|
||||||
|
|
||||||
const char *unit() const {
|
|
||||||
switch (this->value_[REGUNIT]) {
|
|
||||||
case FLAGUNITFAHR: return "°F";
|
|
||||||
case FLAGUNITGRAD: return "°C";
|
|
||||||
case FLAGUNITNF: return "nF";
|
|
||||||
case FLAGUNITHZ: return "Hz";
|
|
||||||
case FLAGUNITHFE: return "hFE";
|
|
||||||
case FLAGUNITOHM: return "Ω";
|
|
||||||
case FLAGUNITAMP: return "A";
|
|
||||||
case FLAGUNITVOLT: return "V";
|
|
||||||
default: return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *scale() const {
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEDUTY) == FLAGSCALEDUTY) return "%";
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEMEGA) == FLAGSCALEMEGA) return "M";
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEKILO) == FLAGSCALEKILO) return "k";
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEMILLI) == FLAGSCALEMILLI) return "m";
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEMICRO) == FLAGSCALEMICRO) return "µ";
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string mode_text() const {
|
|
||||||
std::string out;
|
|
||||||
if (this->dc()) out += "DC ";
|
|
||||||
if (this->ac()) out += "AC ";
|
|
||||||
if (this->auto_range()) out += "AUTO ";
|
|
||||||
if (this->hold()) out += "HOLD ";
|
|
||||||
if (this->relative()) out += "REL ";
|
|
||||||
if (this->min_mode()) out += "MIN ";
|
|
||||||
if (this->max_mode()) out += "MAX ";
|
|
||||||
if (this->diode()) out += "DIODE ";
|
|
||||||
if (this->continuity()) out += "CONT ";
|
|
||||||
if (!out.empty()) out.pop_back();
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string reading_text() const {
|
|
||||||
if (!this->connected) return "Disconnected";
|
|
||||||
if (!this->has_reading) return "Waiting for data";
|
|
||||||
if (this->overload) return "OL " + std::string(this->scale()) + this->unit();
|
|
||||||
char buf[48];
|
|
||||||
snprintf(buf, sizeof(buf), "%s%.4g %s%s", this->negative() ? "-" : "", std::fabs(this->display_value), this->scale(), this->unit());
|
|
||||||
return std::string(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEDUTY) == FLAGSCALEDUTY) return KIND_DUTY;
|
|
||||||
switch (this->value_[REGUNIT]) {
|
|
||||||
case FLAGUNITVOLT: return KIND_VOLTAGE;
|
|
||||||
case FLAGUNITAMP: return KIND_CURRENT;
|
|
||||||
case FLAGUNITOHM: return KIND_RESISTANCE;
|
|
||||||
case FLAGUNITHZ: return KIND_FREQUENCY;
|
|
||||||
case FLAGUNITNF: return KIND_CAPACITANCE;
|
|
||||||
case FLAGUNITGRAD: return KIND_TEMP_C;
|
|
||||||
case FLAGUNITFAHR: return KIND_TEMP_F;
|
|
||||||
default: return KIND_OTHER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *selected_button_name() const {
|
|
||||||
static const char *const names[] = {"SELECT", "RANGE", "HLD/LIG", "REL/BT", "HZ/DUTY", "MAX/MIN"};
|
|
||||||
uint8_t index = this->selected_button;
|
|
||||||
if (index < 1) index = 1;
|
|
||||||
if (index > 6) index = 6;
|
|
||||||
return names[index - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
void previous_button() {
|
|
||||||
if (this->selected_button > 1) this->selected_button--;
|
|
||||||
}
|
|
||||||
void next_button() {
|
|
||||||
if (this->selected_button < 6) this->selected_button++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void render(Display &it, esphome::display::BaseFont *font) {
|
|
||||||
const Color bg(0, 0, 0);
|
|
||||||
const Color fg(210, 210, 210);
|
|
||||||
// Chosen to map to a neutral dark gray in the RGB332 8-bit display palette.
|
|
||||||
const Color inactive(80, 80, 80);
|
|
||||||
const Color yellow(255, 220, 0);
|
|
||||||
const Color blue(0, 80, 255);
|
|
||||||
const Color cyan(0, 255, 255);
|
|
||||||
const Color magenta(255, 0, 255);
|
|
||||||
const Color red(255, 0, 0);
|
|
||||||
const Color green(0, 220, 0);
|
|
||||||
const Color orange(255, 165, 0);
|
|
||||||
|
|
||||||
it.fill(bg);
|
|
||||||
bool status_active = this->connected && this->has_reading;
|
|
||||||
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->label_(it, font, 86, 8, "AUTO", status_active && this->auto_range() ? fg : inactive);
|
|
||||||
this->label_(it, font, 138, 8, "MAX", status_active && this->max_mode() ? red : inactive);
|
|
||||||
this->label_(it, font, 178, 8, "MIN", status_active && this->min_mode() ? green : inactive);
|
|
||||||
this->label_(it, font, 218, 8, "HOLD", status_active && this->hold() ? blue : inactive);
|
|
||||||
this->label_(it, font, 270, 8, "REL", status_active && this->relative() ? Color(128, 128, 0) : inactive);
|
|
||||||
|
|
||||||
this->label_(it, font, 8, 66, "DC", status_active && this->dc() ? cyan : inactive);
|
|
||||||
this->label_(it, font, 8, 102, "AC", status_active && this->ac() ? magenta : inactive);
|
|
||||||
|
|
||||||
if (!this->connected) {
|
|
||||||
this->draw_digits_(it, "----", false, inactive);
|
|
||||||
it.print(160, 148, font, inactive, esphome::display::TextAlign::CENTER, "scan/connect");
|
|
||||||
} else if (!this->has_reading) {
|
|
||||||
this->draw_digits_(it, "8888", false, inactive);
|
|
||||||
it.print(160, 148, font, inactive, esphome::display::TextAlign::CENTER, "waiting");
|
|
||||||
} else if (this->overload) {
|
|
||||||
this->draw_digits_(it, " OL ", false, fg);
|
|
||||||
} else {
|
|
||||||
char d[5];
|
|
||||||
d[0] = this->digit_char_(REGDIG1);
|
|
||||||
d[1] = this->digit_char_(REGDIG2);
|
|
||||||
d[2] = this->digit_char_(REGDIG3);
|
|
||||||
d[3] = this->digit_char_(REGDIG4);
|
|
||||||
d[4] = 0;
|
|
||||||
this->draw_digits_(it, d, this->negative(), fg);
|
|
||||||
this->draw_decimal_points_(it, fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status_active) {
|
|
||||||
std::string unit_line = std::string(this->scale()) + this->unit();
|
|
||||||
it.print(270, 140, font, yellow, esphome::display::TextAlign::CENTER, unit_line.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool bargraph_active = status_active && !this->overload;
|
|
||||||
this->draw_bargraph_(it, bargraph_active ? this->digits_from_buffer_() : 0, bargraph_active);
|
|
||||||
this->draw_icon_(it, 300, 148, 16, 16, DIODE_BMP, status_active && this->diode() ? magenta : inactive);
|
|
||||||
this->draw_icon_(it, 300, 174, 16, 16, BUZZ_BMP, status_active && this->continuity() ? orange : 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(242, 212, 40, 24, this->write_available ? fg : inactive);
|
|
||||||
it.print(54, 216, font, bg, esphome::display::TextAlign::TOP_CENTER, "<");
|
|
||||||
it.print(158, 216, font, bg, esphome::display::TextAlign::TOP_CENTER, this->selected_button_name());
|
|
||||||
it.print(262, 216, font, bg, esphome::display::TextAlign::TOP_CENTER, ">");
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint8_t raw_[14]{};
|
|
||||||
uint8_t value_[14]{};
|
|
||||||
float display_value{NAN};
|
|
||||||
float base_value{NAN};
|
|
||||||
static constexpr uint8_t OVERLOAD_FRAME[5] = {0x2B, 0x3F, 0x30, 0x3A, 0x3F};
|
|
||||||
|
|
||||||
uint16_t digits_from_buffer_() const {
|
|
||||||
uint16_t out = 0;
|
|
||||||
if (this->value_[REGDIG1] >= '0' && this->value_[REGDIG1] <= '9') out += (this->value_[REGDIG1] - '0') * 1000;
|
|
||||||
if (this->value_[REGDIG2] >= '0' && this->value_[REGDIG2] <= '9') out += (this->value_[REGDIG2] - '0') * 100;
|
|
||||||
if (this->value_[REGDIG3] >= '0' && this->value_[REGDIG3] <= '9') out += (this->value_[REGDIG3] - '0') * 10;
|
|
||||||
if (this->value_[REGDIG4] >= '0' && this->value_[REGDIG4] <= '9') out += (this->value_[REGDIG4] - '0');
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
float calc_display_value_() const {
|
|
||||||
if (this->overload) return NAN;
|
|
||||||
|
|
||||||
if (this->is_plus) {
|
|
||||||
uint16_t pair1 = static_cast<uint16_t>(this->raw_[0]) | (static_cast<uint16_t>(this->raw_[1]) << 8);
|
|
||||||
uint8_t decimal = pair1 & 0x07;
|
|
||||||
if (decimal >= 7) return NAN;
|
|
||||||
|
|
||||||
uint16_t pair3 = static_cast<uint16_t>(this->raw_[4]) | (static_cast<uint16_t>(this->raw_[5]) << 8);
|
|
||||||
bool negative = pair3 >= 0x7FFF;
|
|
||||||
uint16_t digits = negative ? (pair3 & 0x7FFF) : pair3;
|
|
||||||
float v = static_cast<float>(digits) / std::pow(10.0f, decimal);
|
|
||||||
return negative ? -v : v;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t decimal = 0;
|
|
||||||
switch (this->value_[REGPOINT] & 0x07) {
|
|
||||||
case 0b001: decimal = 1; break;
|
|
||||||
case 0b010: decimal = 2; break;
|
|
||||||
case 0b100: decimal = 3; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
float v = static_cast<float>(this->digits_from_buffer_()) / std::pow(10.0f, decimal);
|
|
||||||
return this->negative() ? -v : v;
|
|
||||||
}
|
|
||||||
|
|
||||||
float calc_base_value_() const {
|
|
||||||
if (std::isnan(this->display_value)) return NAN;
|
|
||||||
if (this->value_[REGUNIT] == FLAGUNITNF) return this->display_value * 1e-9f;
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEMEGA) == FLAGSCALEMEGA) return this->display_value * 1e6f;
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEKILO) == FLAGSCALEKILO) return this->display_value * 1e3f;
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEMILLI) == FLAGSCALEMILLI) return this->display_value * 1e-3f;
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEMICRO) == FLAGSCALEMICRO) return this->display_value * 1e-6f;
|
|
||||||
return this->display_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void parse_plus_() {
|
|
||||||
memset(this->value_, 0, sizeof(this->value_));
|
|
||||||
this->value_[5] = 0x20;
|
|
||||||
this->value_[12] = 0x0D;
|
|
||||||
this->value_[13] = 0x0A;
|
|
||||||
|
|
||||||
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 scale = (pair1 >> 3) & 0x07;
|
|
||||||
uint8_t decimal = pair1 & 0x07;
|
|
||||||
|
|
||||||
switch (decimal) {
|
|
||||||
case 0: this->value_[REGPOINT] = FLAGPOINT0; break;
|
|
||||||
case 1: this->value_[REGPOINT] = FLAGPOINT3; break;
|
|
||||||
case 2: this->value_[REGPOINT] = FLAGPOINT2; break;
|
|
||||||
case 3: this->value_[REGPOINT] = FLAGPOINT1; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (function) {
|
|
||||||
case 0: this->value_[REGUNIT] |= FLAGUNITVOLT; this->value_[REGMODE] |= FLAGMODEDC; break;
|
|
||||||
case 1: this->value_[REGUNIT] |= FLAGUNITVOLT; this->value_[REGMODE] |= FLAGMODEAC; break;
|
|
||||||
case 2: this->value_[REGUNIT] |= FLAGUNITAMP; this->value_[REGMODE] |= FLAGMODEDC; break;
|
|
||||||
case 3: this->value_[REGUNIT] |= FLAGUNITAMP; this->value_[REGMODE] |= FLAGMODEAC; break;
|
|
||||||
case 4: this->value_[REGUNIT] |= FLAGUNITOHM; break;
|
|
||||||
case 5: this->value_[REGUNIT] |= FLAGUNITNF; break;
|
|
||||||
case 6: this->value_[REGUNIT] |= FLAGUNITHZ; break;
|
|
||||||
case 7: this->value_[REGSCALE] |= FLAGSCALEDUTY; break;
|
|
||||||
case 8: this->value_[REGUNIT] |= FLAGUNITGRAD; break;
|
|
||||||
case 9: this->value_[REGUNIT] |= FLAGUNITFAHR; break;
|
|
||||||
case 10: this->value_[REGSCALE] |= FLAGSCALEDIODE; break;
|
|
||||||
case 11: this->value_[REGSCALE] |= FLAGSCALEBUZZ; break;
|
|
||||||
case 12: this->value_[REGUNIT] |= FLAGUNITHFE; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (scale) {
|
|
||||||
case 2: this->value_[REGSCALE] |= FLAGSCALEMICRO; break;
|
|
||||||
case 3: this->value_[REGSCALE] |= FLAGSCALEMILLI; break;
|
|
||||||
case 5: this->value_[REGSCALE] |= FLAGSCALEKILO; break;
|
|
||||||
case 6: this->value_[REGSCALE] |= FLAGSCALEMEGA; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 << 1)) this->value_[REGMODE] |= FLAGMODEREL;
|
|
||||||
if (pair2 & (1 << 2)) this->value_[REGMODE] |= FLAGMODEAUTO;
|
|
||||||
this->low_battery = (pair2 & (1 << 3)) != 0;
|
|
||||||
if (pair2 & (1 << 4)) this->value_[REGMINMAX] |= FLAGMIN;
|
|
||||||
if (pair2 & (1 << 5)) this->value_[REGMINMAX] |= FLAGMAX;
|
|
||||||
|
|
||||||
uint16_t pair3 = static_cast<uint16_t>(this->raw_[4]) | (static_cast<uint16_t>(this->raw_[5]) << 8);
|
|
||||||
if (decimal < 7) {
|
|
||||||
uint16_t digits = pair3;
|
|
||||||
if (pair3 < 0x7FFF) {
|
|
||||||
this->value_[REGPLUSMINUS] = FLAGPLUS;
|
|
||||||
} else {
|
|
||||||
this->value_[REGPLUSMINUS] = FLAGMINUS;
|
|
||||||
digits = pair3 & 0x7FFF;
|
|
||||||
}
|
|
||||||
this->value_[REGDIG1] = '0' + ((digits / 1000) % 10);
|
|
||||||
this->value_[REGDIG2] = '0' + ((digits / 100) % 10);
|
|
||||||
this->value_[REGDIG3] = '0' + ((digits / 10) % 10);
|
|
||||||
this->value_[REGDIG4] = '0' + (digits % 10);
|
|
||||||
} else {
|
|
||||||
memcpy(this->value_, OVERLOAD_FRAME, sizeof(OVERLOAD_FRAME));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char digit_char_(uint8_t reg) const {
|
|
||||||
uint8_t c = this->value_[reg];
|
|
||||||
return (c >= '0' && c <= '9') ? static_cast<char>(c) : ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw_digits_(Display &it, const char *text, bool negative, Color color) {
|
|
||||||
if (negative) this->draw_segment_(it, 8, 88, 26, 9, true, color);
|
|
||||||
constexpr int digit_x = 40;
|
|
||||||
constexpr int digit_y = 35;
|
|
||||||
constexpr int digit_w = 50;
|
|
||||||
constexpr int digit_h = 88;
|
|
||||||
constexpr int digit_distance = 64;
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
this->draw_seven_segment_(it, digit_x + i * digit_distance, digit_y, digit_w, digit_h, text[i], color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw_decimal_points_(Display &it, Color color) {
|
|
||||||
uint8_t p = this->value_[REGPOINT];
|
|
||||||
if ((p & FLAGPOINT1) == FLAGPOINT1) it.filled_rectangle(95, 117, 8, 10, color);
|
|
||||||
if ((p & FLAGPOINT2) == FLAGPOINT2) it.filled_rectangle(159, 117, 8, 10, color);
|
|
||||||
if ((p & FLAGPOINT3) == FLAGPOINT3) it.filled_rectangle(223, 117, 8, 10, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw_segment_(Display &it, int x, int y, int w, int h, bool horizontal, Color color) {
|
|
||||||
if (horizontal) {
|
|
||||||
int cap = h / 2;
|
|
||||||
it.filled_rectangle(x + cap, y, w - 2 * cap, h +1, color);
|
|
||||||
it.filled_triangle(x, y + cap, x + cap, y, x + cap, y + h, color);
|
|
||||||
it.filled_triangle(x + w, y + cap, x + w - cap, y, x + w - cap, y + h, color);
|
|
||||||
} else {
|
|
||||||
int cap = w / 2;
|
|
||||||
it.filled_rectangle(x, y + cap, w +1, h - 2 * cap, color);
|
|
||||||
it.filled_triangle(x + cap, y, x, y + cap, x + w, y + cap, color);
|
|
||||||
it.filled_triangle(x + cap, y + h, x, y + h - cap, x + w, y + h - cap, 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 col = 0; col < w; col++) {
|
|
||||||
uint8_t byte = data[row * ((w + 7) / 8) + (col / 8)];
|
|
||||||
if ((byte & (0x80 >> (col % 8))) != 0) {
|
|
||||||
it.draw_pixel_at(x + col, y + row, 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;
|
|
||||||
switch (ch) {
|
|
||||||
case '0': a=b=c=d=e=f=true; break;
|
|
||||||
case '1': b=c=true; break;
|
|
||||||
case '2': a=b=d=e=g=true; break;
|
|
||||||
case '3': a=b=c=d=g=true; break;
|
|
||||||
case '4': b=c=f=g=true; break;
|
|
||||||
case '5': a=c=d=f=g=true; break;
|
|
||||||
case '6': a=c=d=e=f=g=true; break;
|
|
||||||
case '7': a=b=c=true; break;
|
|
||||||
case '8': a=b=c=d=e=f=g=true; break;
|
|
||||||
case '9': a=b=c=d=f=g=true; break;
|
|
||||||
case 'O': a=b=c=d=e=f=true; break;
|
|
||||||
case 'L': d=e=f=true; break;
|
|
||||||
case '-': g=true; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
int t = 10;
|
|
||||||
int half = h / 2;
|
|
||||||
int gap = 1;
|
|
||||||
|
|
||||||
int top_y = y;
|
|
||||||
int mid_y = y + half - t / 2;
|
|
||||||
int bot_y = y + h - t;
|
|
||||||
|
|
||||||
int upper_v_y = top_y + t + gap;
|
|
||||||
int upper_v_h = mid_y - gap - upper_v_y;
|
|
||||||
int lower_v_y = mid_y + t + gap;
|
|
||||||
int lower_v_h = bot_y - gap - lower_v_y;
|
|
||||||
|
|
||||||
if (a) this->draw_segment_(it, x + t / 2, top_y, w - t, t, true, color);
|
|
||||||
if (b && upper_v_h > 0) this->draw_segment_(it, x + w - t, upper_v_y, t, upper_v_h, false, color);
|
|
||||||
if (c && lower_v_h > 0) this->draw_segment_(it, x + w - t, lower_v_y, t, lower_v_h, false, color);
|
|
||||||
if (d) this->draw_segment_(it, x + t / 2, bot_y, w - t, t, true, color);
|
|
||||||
if (e && lower_v_h > 0) this->draw_segment_(it, x, lower_v_y, t, lower_v_h, false, color);
|
|
||||||
if (f && upper_v_h > 0) this->draw_segment_(it, x, upper_v_y, t, upper_v_h, false, color);
|
|
||||||
if (g) this->draw_segment_(it, x + t / 2, mid_y, w - t, t, true, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw_bargraph_(Display &it, uint16_t digits, bool active) {
|
|
||||||
const Color fg(255, 255, 255);
|
|
||||||
const Color inactive(80, 80, 80);
|
|
||||||
uint16_t mapped = active ? static_cast<uint16_t>(digits * 240 / 6000) : 0;
|
|
||||||
if (mapped > 240) mapped = 240;
|
|
||||||
for (uint16_t i = 0; i <= 240; i += 4) {
|
|
||||||
Color col = (active && i <= mapped) ? fg : inactive;
|
|
||||||
int h = (i % 40 == 0) ? 20 : ((i % 20 == 0) ? 15 : 10);
|
|
||||||
it.vertical_line(40 + i, 185 - h, h, col);
|
|
||||||
}
|
|
||||||
it.horizontal_line(35, 185, 250, inactive);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace owon_b35t
|
|
||||||
|
|
||||||
static owon_b35t::Meter owon_meter;
|
|
||||||
@@ -1,316 +0,0 @@
|
|||||||
# Derived work based on https://github.com/reaper7/M5Stack_BLE_client_Owon_B35T by reaper7.
|
|
||||||
# AI (ChatGPT) has been used to adopt the Arduino sketch to ESPHome.
|
|
||||||
# Ported to M5Stack Core2 due to memory constraints.
|
|
||||||
|
|
||||||
substitutions:
|
|
||||||
name: "owon-b35t-m5stack-core2"
|
|
||||||
friendly_name: "OWON B35T Multimeter Core2"
|
|
||||||
device_description: "M5Stack Core2 BLE client for OWON B35T/B35T+ multimeter with local graphical display"
|
|
||||||
owon_mac_address: !secret owon_b35t_mac_address
|
|
||||||
|
|
||||||
esphome:
|
|
||||||
name: ${name}
|
|
||||||
friendly_name: ${friendly_name}
|
|
||||||
comment: ${device_description}
|
|
||||||
min_version: 2024.6.0
|
|
||||||
includes:
|
|
||||||
- owon-b35t-m5stack-core2.h
|
|
||||||
on_boot:
|
|
||||||
priority: 850
|
|
||||||
then:
|
|
||||||
- lambda: |-
|
|
||||||
owon_b35t::core2_axp192_init(id(core2_i2c));
|
|
||||||
project:
|
|
||||||
name: "custom.owon-b35t-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
|
|
||||||
|
|
||||||
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: "OWON B35T Fallback Hotspot"
|
|
||||||
password: !secret fallback_psk
|
|
||||||
|
|
||||||
captive_portal:
|
|
||||||
|
|
||||||
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<unsigned>(esp_get_free_heap_size()),
|
|
||||||
static_cast<unsigned>(esp_get_minimum_free_heap_size()),
|
|
||||||
static_cast<unsigned>(heap_caps_get_free_size(MALLOC_CAP_INTERNAL)),
|
|
||||||
static_cast<unsigned>(heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL)),
|
|
||||||
static_cast<unsigned>(heap_caps_get_free_size(MALLOC_CAP_DMA)),
|
|
||||||
static_cast<unsigned>(heap_caps_get_largest_free_block(MALLOC_CAP_DMA)),
|
|
||||||
static_cast<unsigned>(heap_caps_get_free_size(MALLOC_CAP_SPIRAM)),
|
|
||||||
static_cast<unsigned>(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();
|
|
||||||
on_disconnect:
|
|
||||||
then:
|
|
||||||
- lambda: |-
|
|
||||||
owon_meter.on_disconnect();
|
|
||||||
|
|
||||||
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",
|
|
||||||
"c",
|
|
||||||
"d",
|
|
||||||
"e",
|
|
||||||
"f",
|
|
||||||
"g",
|
|
||||||
"h",
|
|
||||||
"i",
|
|
||||||
"k",
|
|
||||||
"m",
|
|
||||||
"n",
|
|
||||||
"o",
|
|
||||||
"r",
|
|
||||||
"s",
|
|
||||||
"t",
|
|
||||||
"u",
|
|
||||||
"v",
|
|
||||||
"w",
|
|
||||||
"y",
|
|
||||||
"z",
|
|
||||||
"°",
|
|
||||||
"µ",
|
|
||||||
"Ω",
|
|
||||||
]
|
|
||||||
|
|
||||||
display:
|
|
||||||
- platform: mipi_spi
|
|
||||||
id: lcd
|
|
||||||
model: M5CORE2
|
|
||||||
update_interval: 500ms
|
|
||||||
lambda: |-
|
|
||||||
owon_meter.render(it, id(meter_font));
|
|
||||||
|
|
||||||
touchscreen:
|
|
||||||
- platform: ft63x6
|
|
||||||
id: touch
|
|
||||||
display: lcd
|
|
||||||
|
|
||||||
binary_sensor:
|
|
||||||
- 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<uint8_t> 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<uint8_t> 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} Connected"
|
|
||||||
lambda: |-
|
|
||||||
return owon_meter.connected;
|
|
||||||
- platform: template
|
|
||||||
name: "${friendly_name} Overload"
|
|
||||||
lambda: |-
|
|
||||||
return owon_meter.overload;
|
|
||||||
- platform: template
|
|
||||||
name: "${friendly_name} Low Battery"
|
|
||||||
lambda: |-
|
|
||||||
return owon_meter.low_battery;
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
text_sensor:
|
|
||||||
- platform: template
|
|
||||||
name: "${friendly_name} Reading"
|
|
||||||
update_interval: 1s
|
|
||||||
lambda: |-
|
|
||||||
return owon_meter.reading_text();
|
|
||||||
- platform: template
|
|
||||||
name: "${friendly_name} Unit"
|
|
||||||
update_interval: 1s
|
|
||||||
lambda: |-
|
|
||||||
return std::string(owon_meter.scale()) + owon_meter.unit();
|
|
||||||
- platform: template
|
|
||||||
name: "${friendly_name} Mode"
|
|
||||||
update_interval: 1s
|
|
||||||
lambda: |-
|
|
||||||
return owon_meter.mode_text();
|
|
||||||
@@ -1,567 +0,0 @@
|
|||||||
/*
|
|
||||||
* ESPHome helper for OWON B35T/B35T+ BLE meter on M5Stack Core 1.
|
|
||||||
* Parser is based on the standalone Arduino sketch by Reaper7
|
|
||||||
* (Beerware license, Revision 42) and Dean Cording's owonb35 notes.
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include "esp_heap_caps.h"
|
|
||||||
#include "esp_system.h"
|
|
||||||
#include "esphome/core/helpers.h"
|
|
||||||
#include "esphome/core/log.h"
|
|
||||||
#include "esphome/components/display/display.h"
|
|
||||||
|
|
||||||
namespace owon_b35t {
|
|
||||||
|
|
||||||
using esphome::Color;
|
|
||||||
using esphome::display::Display;
|
|
||||||
|
|
||||||
static const char *const TAG = "owon_b35t";
|
|
||||||
|
|
||||||
static const uint8_t ACCU_BMP[32] = {
|
|
||||||
0b00000000, 0b00000000,
|
|
||||||
0b00000000, 0b00000000,
|
|
||||||
0b00000000, 0b00000000,
|
|
||||||
0b11111111, 0b11111110,
|
|
||||||
0b10000000, 0b00000010,
|
|
||||||
0b10000000, 0b00000011,
|
|
||||||
0b10000000, 0b00000011,
|
|
||||||
0b10000000, 0b00000011,
|
|
||||||
0b10000000, 0b00000011,
|
|
||||||
0b10000000, 0b00000011,
|
|
||||||
0b10000000, 0b00000011,
|
|
||||||
0b10000000, 0b00000010,
|
|
||||||
0b11111111, 0b11111110,
|
|
||||||
0b00000000, 0b00000000,
|
|
||||||
0b00000000, 0b00000000,
|
|
||||||
0b00000000, 0b00000000,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const uint8_t BLE_BMP[32] = {
|
|
||||||
0b00000001, 0b10000000,
|
|
||||||
0b00000001, 0b11000000,
|
|
||||||
0b00010001, 0b01100000,
|
|
||||||
0b00011001, 0b00110000,
|
|
||||||
0b00001101, 0b00011000,
|
|
||||||
0b00000111, 0b00110000,
|
|
||||||
0b00000011, 0b01100000,
|
|
||||||
0b00000001, 0b11000000,
|
|
||||||
0b00000001, 0b11000000,
|
|
||||||
0b00000011, 0b01100000,
|
|
||||||
0b00000111, 0b00110000,
|
|
||||||
0b00001101, 0b00011000,
|
|
||||||
0b00011001, 0b00110000,
|
|
||||||
0b00010001, 0b01100000,
|
|
||||||
0b00000001, 0b11000000,
|
|
||||||
0b00000001, 0b10000000,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const uint8_t DIODE_BMP[32] = {
|
|
||||||
0b00001000, 0b00011000,
|
|
||||||
0b00001100, 0b00011000,
|
|
||||||
0b00001110, 0b00011000,
|
|
||||||
0b00001111, 0b00011000,
|
|
||||||
0b00001111, 0b10011000,
|
|
||||||
0b00001111, 0b11011000,
|
|
||||||
0b00001111, 0b11111000,
|
|
||||||
0b11111111, 0b11111111,
|
|
||||||
0b11111111, 0b11111111,
|
|
||||||
0b00001111, 0b11111000,
|
|
||||||
0b00001111, 0b11011000,
|
|
||||||
0b00001111, 0b10011000,
|
|
||||||
0b00001111, 0b00011000,
|
|
||||||
0b00001110, 0b00011000,
|
|
||||||
0b00001100, 0b00011000,
|
|
||||||
0b00001000, 0b00011000,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const uint8_t BUZZ_BMP[32] = {
|
|
||||||
0b00000000, 0b11000000,
|
|
||||||
0b00000001, 0b11000000,
|
|
||||||
0b00000011, 0b11000001,
|
|
||||||
0b00000111, 0b11000001,
|
|
||||||
0b00001111, 0b11000101,
|
|
||||||
0b11111111, 0b11000101,
|
|
||||||
0b11111111, 0b11010101,
|
|
||||||
0b11111111, 0b11010101,
|
|
||||||
0b11111111, 0b11010101,
|
|
||||||
0b11111111, 0b11010101,
|
|
||||||
0b11111111, 0b11000101,
|
|
||||||
0b00001111, 0b11000101,
|
|
||||||
0b00000111, 0b11000001,
|
|
||||||
0b00000011, 0b11000001,
|
|
||||||
0b00000001, 0b11000000,
|
|
||||||
0b00000000, 0b11000000,
|
|
||||||
};
|
|
||||||
|
|
||||||
class Meter {
|
|
||||||
public:
|
|
||||||
static constexpr uint8_t REGPLUSMINUS = 0x00;
|
|
||||||
static constexpr uint8_t FLAGPLUS = 0b00101011;
|
|
||||||
static constexpr uint8_t FLAGMINUS = 0b00101101;
|
|
||||||
static constexpr uint8_t REGDIG1 = 0x01;
|
|
||||||
static constexpr uint8_t REGDIG2 = 0x02;
|
|
||||||
static constexpr uint8_t REGDIG3 = 0x03;
|
|
||||||
static constexpr uint8_t REGDIG4 = 0x04;
|
|
||||||
static constexpr uint8_t REGPOINT = 0x06;
|
|
||||||
static constexpr uint8_t FLAGPOINT0 = 0b00110000;
|
|
||||||
static constexpr uint8_t FLAGPOINT1 = 0b00110001;
|
|
||||||
static constexpr uint8_t FLAGPOINT2 = 0b00110010;
|
|
||||||
static constexpr uint8_t FLAGPOINT3 = 0b00110100;
|
|
||||||
static constexpr uint8_t REGMODE = 0x07;
|
|
||||||
static constexpr uint8_t FLAGMODEHOLD = 0b00000010;
|
|
||||||
static constexpr uint8_t FLAGMODEREL = 0b00000100;
|
|
||||||
static constexpr uint8_t FLAGMODEAC = 0b00001000;
|
|
||||||
static constexpr uint8_t FLAGMODEDC = 0b00010000;
|
|
||||||
static constexpr uint8_t FLAGMODEAUTO = 0b00100000;
|
|
||||||
static constexpr uint8_t REGMINMAX = 0x08;
|
|
||||||
static constexpr uint8_t FLAGMIN = 0b00010000;
|
|
||||||
static constexpr uint8_t FLAGMAX = 0b00100000;
|
|
||||||
static constexpr uint8_t REGSCALE = 0x09;
|
|
||||||
static constexpr uint8_t FLAGSCALEDUTY = 0b00000010;
|
|
||||||
static constexpr uint8_t FLAGSCALEDIODE = 0b00000100;
|
|
||||||
static constexpr uint8_t FLAGSCALEBUZZ = 0b00001000;
|
|
||||||
static constexpr uint8_t FLAGSCALEMEGA = 0b00010000;
|
|
||||||
static constexpr uint8_t FLAGSCALEKILO = 0b00100000;
|
|
||||||
static constexpr uint8_t FLAGSCALEMILLI = 0b01000000;
|
|
||||||
static constexpr uint8_t FLAGSCALEMICRO = 0b10000000;
|
|
||||||
static constexpr uint8_t REGUNIT = 0x0a;
|
|
||||||
static constexpr uint8_t FLAGUNITFAHR = 0b00000001;
|
|
||||||
static constexpr uint8_t FLAGUNITGRAD = 0b00000010;
|
|
||||||
static constexpr uint8_t FLAGUNITNF = 0b00000100;
|
|
||||||
static constexpr uint8_t FLAGUNITHZ = 0b00001000;
|
|
||||||
static constexpr uint8_t FLAGUNITHFE = 0b00010000;
|
|
||||||
static constexpr uint8_t FLAGUNITOHM = 0b00100000;
|
|
||||||
static constexpr uint8_t FLAGUNITAMP = 0b01000000;
|
|
||||||
static constexpr uint8_t FLAGUNITVOLT = 0b10000000;
|
|
||||||
|
|
||||||
bool connected{false};
|
|
||||||
bool write_available{false};
|
|
||||||
bool is_plus{false};
|
|
||||||
bool low_battery{false};
|
|
||||||
bool overload{false};
|
|
||||||
bool has_reading{false};
|
|
||||||
uint8_t selected_button{1};
|
|
||||||
uint32_t last_notify_ms{0};
|
|
||||||
|
|
||||||
bool handle_notify(const std::vector<uint8_t> &data) {
|
|
||||||
if (data.size() > sizeof(this->raw_))
|
|
||||||
return false;
|
|
||||||
if (data.size() == 6 && data[1] >= 0xF0) {
|
|
||||||
memset(this->raw_, 0, sizeof(this->raw_));
|
|
||||||
memcpy(this->raw_, data.data(), data.size());
|
|
||||||
this->is_plus = true;
|
|
||||||
this->parse_plus_();
|
|
||||||
} else if (data.size() == 14 && data[12] == 0x0D && data[13] == 0x0A) {
|
|
||||||
memset(this->value_, 0, sizeof(this->value_));
|
|
||||||
memcpy(this->value_, data.data(), data.size());
|
|
||||||
this->is_plus = false;
|
|
||||||
} else {
|
|
||||||
ESP_LOGW(TAG, "Ignoring unexpected OWON frame length=%u", static_cast<unsigned>(data.size()));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->overload = memcmp(this->value_, OVERLOAD_FRAME, sizeof(OVERLOAD_FRAME)) == 0;
|
|
||||||
this->display_value = this->calc_display_value_();
|
|
||||||
this->base_value = this->calc_base_value_();
|
|
||||||
this->has_reading = true;
|
|
||||||
this->last_notify_ms = millis();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void on_connect() {
|
|
||||||
this->connected = true;
|
|
||||||
this->write_available = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void on_disconnect() {
|
|
||||||
this->connected = false;
|
|
||||||
this->write_available = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
float value() const { return this->display_value; }
|
|
||||||
float value_base() const { return this->base_value; }
|
|
||||||
bool negative() const { return (this->value_[REGPLUSMINUS] & FLAGMINUS) == FLAGMINUS; }
|
|
||||||
bool auto_range() const { return (this->value_[REGMODE] & FLAGMODEAUTO) == FLAGMODEAUTO; }
|
|
||||||
bool hold() const { return (this->value_[REGMODE] & FLAGMODEHOLD) == FLAGMODEHOLD; }
|
|
||||||
bool relative() const { return (this->value_[REGMODE] & FLAGMODEREL) == FLAGMODEREL; }
|
|
||||||
bool ac() const { return (this->value_[REGMODE] & FLAGMODEAC) == FLAGMODEAC; }
|
|
||||||
bool dc() const { return (this->value_[REGMODE] & FLAGMODEDC) == FLAGMODEDC; }
|
|
||||||
bool min_mode() const { return (this->value_[REGMINMAX] & FLAGMIN) == FLAGMIN; }
|
|
||||||
bool max_mode() const { return (this->value_[REGMINMAX] & FLAGMAX) == FLAGMAX; }
|
|
||||||
bool diode() const { return (this->value_[REGSCALE] & FLAGSCALEDIODE) == FLAGSCALEDIODE; }
|
|
||||||
bool continuity() const { return (this->value_[REGSCALE] & FLAGSCALEBUZZ) == FLAGSCALEBUZZ; }
|
|
||||||
|
|
||||||
const char *unit() const {
|
|
||||||
switch (this->value_[REGUNIT]) {
|
|
||||||
case FLAGUNITFAHR: return "°F";
|
|
||||||
case FLAGUNITGRAD: return "°C";
|
|
||||||
case FLAGUNITNF: return "nF";
|
|
||||||
case FLAGUNITHZ: return "Hz";
|
|
||||||
case FLAGUNITHFE: return "hFE";
|
|
||||||
case FLAGUNITOHM: return "Ω";
|
|
||||||
case FLAGUNITAMP: return "A";
|
|
||||||
case FLAGUNITVOLT: return "V";
|
|
||||||
default: return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *scale() const {
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEDUTY) == FLAGSCALEDUTY) return "%";
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEMEGA) == FLAGSCALEMEGA) return "M";
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEKILO) == FLAGSCALEKILO) return "k";
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEMILLI) == FLAGSCALEMILLI) return "m";
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEMICRO) == FLAGSCALEMICRO) return "µ";
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string mode_text() const {
|
|
||||||
std::string out;
|
|
||||||
if (this->dc()) out += "DC ";
|
|
||||||
if (this->ac()) out += "AC ";
|
|
||||||
if (this->auto_range()) out += "AUTO ";
|
|
||||||
if (this->hold()) out += "HOLD ";
|
|
||||||
if (this->relative()) out += "REL ";
|
|
||||||
if (this->min_mode()) out += "MIN ";
|
|
||||||
if (this->max_mode()) out += "MAX ";
|
|
||||||
if (this->diode()) out += "DIODE ";
|
|
||||||
if (this->continuity()) out += "CONT ";
|
|
||||||
if (!out.empty()) out.pop_back();
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string reading_text() const {
|
|
||||||
if (!this->connected) return "Disconnected";
|
|
||||||
if (!this->has_reading) return "Waiting for data";
|
|
||||||
if (this->overload) return "OL " + std::string(this->scale()) + this->unit();
|
|
||||||
char buf[48];
|
|
||||||
snprintf(buf, sizeof(buf), "%s%.4g %s%s", this->negative() ? "-" : "", std::fabs(this->display_value), this->scale(), this->unit());
|
|
||||||
return std::string(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEDUTY) == FLAGSCALEDUTY) return KIND_DUTY;
|
|
||||||
switch (this->value_[REGUNIT]) {
|
|
||||||
case FLAGUNITVOLT: return KIND_VOLTAGE;
|
|
||||||
case FLAGUNITAMP: return KIND_CURRENT;
|
|
||||||
case FLAGUNITOHM: return KIND_RESISTANCE;
|
|
||||||
case FLAGUNITHZ: return KIND_FREQUENCY;
|
|
||||||
case FLAGUNITNF: return KIND_CAPACITANCE;
|
|
||||||
case FLAGUNITGRAD: return KIND_TEMP_C;
|
|
||||||
case FLAGUNITFAHR: return KIND_TEMP_F;
|
|
||||||
default: return KIND_OTHER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *selected_button_name() const {
|
|
||||||
static const char *const names[] = {"SELECT", "RANGE", "HLD/LIG", "REL/BT", "HZ/DUTY", "MAX/MIN"};
|
|
||||||
uint8_t index = this->selected_button;
|
|
||||||
if (index < 1) index = 1;
|
|
||||||
if (index > 6) index = 6;
|
|
||||||
return names[index - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
void previous_button() {
|
|
||||||
if (this->selected_button > 1) this->selected_button--;
|
|
||||||
}
|
|
||||||
void next_button() {
|
|
||||||
if (this->selected_button < 6) this->selected_button++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void render(Display &it, esphome::display::BaseFont *font) {
|
|
||||||
const Color bg(0, 0, 0);
|
|
||||||
const Color fg(210, 210, 210);
|
|
||||||
// Chosen to map to a neutral dark gray in the RGB332 8-bit display palette.
|
|
||||||
const Color inactive(80, 80, 80);
|
|
||||||
const Color yellow(255, 220, 0);
|
|
||||||
const Color blue(0, 80, 255);
|
|
||||||
const Color cyan(0, 255, 255);
|
|
||||||
const Color magenta(255, 0, 255);
|
|
||||||
const Color red(255, 0, 0);
|
|
||||||
const Color green(0, 220, 0);
|
|
||||||
const Color orange(255, 165, 0);
|
|
||||||
|
|
||||||
it.fill(bg);
|
|
||||||
this->draw_icon_(it, 12, 8, 16, 16, ACCU_BMP, this->low_battery ? red : green);
|
|
||||||
this->draw_icon_(it, 46, 8, 16, 16, BLE_BMP, this->connected ? blue : inactive);
|
|
||||||
this->label_(it, font, 86, 8, "AUTO", this->auto_range() ? fg : inactive);
|
|
||||||
this->label_(it, font, 138, 8, "MAX", this->max_mode() ? red : inactive);
|
|
||||||
this->label_(it, font, 178, 8, "MIN", this->min_mode() ? green : inactive);
|
|
||||||
this->label_(it, font, 218, 8, "HOLD", this->hold() ? blue : inactive);
|
|
||||||
this->label_(it, font, 270, 8, "REL", this->relative() ? Color(128, 128, 0) : inactive);
|
|
||||||
|
|
||||||
this->label_(it, font, 8, 66, "DC", this->dc() ? cyan : inactive);
|
|
||||||
this->label_(it, font, 8, 102, "AC", this->ac() ? magenta : inactive);
|
|
||||||
|
|
||||||
if (!this->connected) {
|
|
||||||
this->draw_digits_(it, "----", false, inactive);
|
|
||||||
it.print(160, 148, font, inactive, esphome::display::TextAlign::CENTER, "scan/connect");
|
|
||||||
} else if (!this->has_reading) {
|
|
||||||
this->draw_digits_(it, "8888", false, inactive);
|
|
||||||
it.print(160, 148, font, inactive, esphome::display::TextAlign::CENTER, "waiting");
|
|
||||||
} else if (this->overload) {
|
|
||||||
this->draw_digits_(it, " OL ", false, fg);
|
|
||||||
} else {
|
|
||||||
char d[5];
|
|
||||||
d[0] = this->digit_char_(REGDIG1);
|
|
||||||
d[1] = this->digit_char_(REGDIG2);
|
|
||||||
d[2] = this->digit_char_(REGDIG3);
|
|
||||||
d[3] = this->digit_char_(REGDIG4);
|
|
||||||
d[4] = 0;
|
|
||||||
this->draw_digits_(it, d, this->negative(), fg);
|
|
||||||
this->draw_decimal_points_(it, fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string unit_line = std::string(this->scale()) + this->unit();
|
|
||||||
it.print(270, 140, font, yellow, esphome::display::TextAlign::CENTER, unit_line.c_str());
|
|
||||||
|
|
||||||
this->draw_bargraph_(it, this->has_reading && !this->overload ? this->digits_from_buffer_() : 0, this->has_reading && !this->overload);
|
|
||||||
this->draw_icon_(it, 300, 148, 16, 16, DIODE_BMP, this->diode() ? magenta : inactive);
|
|
||||||
this->draw_icon_(it, 300, 174, 16, 16, BUZZ_BMP, this->continuity() ? orange : 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(242, 212, 40, 24, this->write_available ? fg : inactive);
|
|
||||||
it.print(54, 216, font, bg, esphome::display::TextAlign::TOP_CENTER, "<");
|
|
||||||
it.print(158, 216, font, bg, esphome::display::TextAlign::TOP_CENTER, this->selected_button_name());
|
|
||||||
it.print(262, 216, font, bg, esphome::display::TextAlign::TOP_CENTER, ">");
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint8_t raw_[14]{};
|
|
||||||
uint8_t value_[14]{};
|
|
||||||
float display_value{NAN};
|
|
||||||
float base_value{NAN};
|
|
||||||
static constexpr uint8_t OVERLOAD_FRAME[5] = {0x2B, 0x3F, 0x30, 0x3A, 0x3F};
|
|
||||||
|
|
||||||
uint16_t digits_from_buffer_() const {
|
|
||||||
uint16_t out = 0;
|
|
||||||
if (this->value_[REGDIG1] >= '0' && this->value_[REGDIG1] <= '9') out += (this->value_[REGDIG1] - '0') * 1000;
|
|
||||||
if (this->value_[REGDIG2] >= '0' && this->value_[REGDIG2] <= '9') out += (this->value_[REGDIG2] - '0') * 100;
|
|
||||||
if (this->value_[REGDIG3] >= '0' && this->value_[REGDIG3] <= '9') out += (this->value_[REGDIG3] - '0') * 10;
|
|
||||||
if (this->value_[REGDIG4] >= '0' && this->value_[REGDIG4] <= '9') out += (this->value_[REGDIG4] - '0');
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
float calc_display_value_() const {
|
|
||||||
if (this->overload) return NAN;
|
|
||||||
|
|
||||||
if (this->is_plus) {
|
|
||||||
uint16_t pair1 = static_cast<uint16_t>(this->raw_[0]) | (static_cast<uint16_t>(this->raw_[1]) << 8);
|
|
||||||
uint8_t decimal = pair1 & 0x07;
|
|
||||||
if (decimal >= 7) return NAN;
|
|
||||||
|
|
||||||
uint16_t pair3 = static_cast<uint16_t>(this->raw_[4]) | (static_cast<uint16_t>(this->raw_[5]) << 8);
|
|
||||||
bool negative = pair3 >= 0x7FFF;
|
|
||||||
uint16_t digits = negative ? (pair3 & 0x7FFF) : pair3;
|
|
||||||
float v = static_cast<float>(digits) / std::pow(10.0f, decimal);
|
|
||||||
return negative ? -v : v;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t decimal = 0;
|
|
||||||
switch (this->value_[REGPOINT] & 0x07) {
|
|
||||||
case 0b001: decimal = 1; break;
|
|
||||||
case 0b010: decimal = 2; break;
|
|
||||||
case 0b100: decimal = 3; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
float v = static_cast<float>(this->digits_from_buffer_()) / std::pow(10.0f, decimal);
|
|
||||||
return this->negative() ? -v : v;
|
|
||||||
}
|
|
||||||
|
|
||||||
float calc_base_value_() const {
|
|
||||||
if (std::isnan(this->display_value)) return NAN;
|
|
||||||
if (this->value_[REGUNIT] == FLAGUNITNF) return this->display_value * 1e-9f;
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEMEGA) == FLAGSCALEMEGA) return this->display_value * 1e6f;
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEKILO) == FLAGSCALEKILO) return this->display_value * 1e3f;
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEMILLI) == FLAGSCALEMILLI) return this->display_value * 1e-3f;
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEMICRO) == FLAGSCALEMICRO) return this->display_value * 1e-6f;
|
|
||||||
return this->display_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void parse_plus_() {
|
|
||||||
memset(this->value_, 0, sizeof(this->value_));
|
|
||||||
this->value_[5] = 0x20;
|
|
||||||
this->value_[12] = 0x0D;
|
|
||||||
this->value_[13] = 0x0A;
|
|
||||||
|
|
||||||
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 scale = (pair1 >> 3) & 0x07;
|
|
||||||
uint8_t decimal = pair1 & 0x07;
|
|
||||||
|
|
||||||
switch (decimal) {
|
|
||||||
case 0: this->value_[REGPOINT] = FLAGPOINT0; break;
|
|
||||||
case 1: this->value_[REGPOINT] = FLAGPOINT3; break;
|
|
||||||
case 2: this->value_[REGPOINT] = FLAGPOINT2; break;
|
|
||||||
case 3: this->value_[REGPOINT] = FLAGPOINT1; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (function) {
|
|
||||||
case 0: this->value_[REGUNIT] |= FLAGUNITVOLT; this->value_[REGMODE] |= FLAGMODEDC; break;
|
|
||||||
case 1: this->value_[REGUNIT] |= FLAGUNITVOLT; this->value_[REGMODE] |= FLAGMODEAC; break;
|
|
||||||
case 2: this->value_[REGUNIT] |= FLAGUNITAMP; this->value_[REGMODE] |= FLAGMODEDC; break;
|
|
||||||
case 3: this->value_[REGUNIT] |= FLAGUNITAMP; this->value_[REGMODE] |= FLAGMODEAC; break;
|
|
||||||
case 4: this->value_[REGUNIT] |= FLAGUNITOHM; break;
|
|
||||||
case 5: this->value_[REGUNIT] |= FLAGUNITNF; break;
|
|
||||||
case 6: this->value_[REGUNIT] |= FLAGUNITHZ; break;
|
|
||||||
case 7: this->value_[REGSCALE] |= FLAGSCALEDUTY; break;
|
|
||||||
case 8: this->value_[REGUNIT] |= FLAGUNITGRAD; break;
|
|
||||||
case 9: this->value_[REGUNIT] |= FLAGUNITFAHR; break;
|
|
||||||
case 10: this->value_[REGSCALE] |= FLAGSCALEDIODE; break;
|
|
||||||
case 11: this->value_[REGSCALE] |= FLAGSCALEBUZZ; break;
|
|
||||||
case 12: this->value_[REGUNIT] |= FLAGUNITHFE; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (scale) {
|
|
||||||
case 2: this->value_[REGSCALE] |= FLAGSCALEMICRO; break;
|
|
||||||
case 3: this->value_[REGSCALE] |= FLAGSCALEMILLI; break;
|
|
||||||
case 5: this->value_[REGSCALE] |= FLAGSCALEKILO; break;
|
|
||||||
case 6: this->value_[REGSCALE] |= FLAGSCALEMEGA; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 << 1)) this->value_[REGMODE] |= FLAGMODEREL;
|
|
||||||
if (pair2 & (1 << 2)) this->value_[REGMODE] |= FLAGMODEAUTO;
|
|
||||||
this->low_battery = (pair2 & (1 << 3)) != 0;
|
|
||||||
if (pair2 & (1 << 4)) this->value_[REGMINMAX] |= FLAGMIN;
|
|
||||||
if (pair2 & (1 << 5)) this->value_[REGMINMAX] |= FLAGMAX;
|
|
||||||
|
|
||||||
uint16_t pair3 = static_cast<uint16_t>(this->raw_[4]) | (static_cast<uint16_t>(this->raw_[5]) << 8);
|
|
||||||
if (decimal < 7) {
|
|
||||||
uint16_t digits = pair3;
|
|
||||||
if (pair3 < 0x7FFF) {
|
|
||||||
this->value_[REGPLUSMINUS] = FLAGPLUS;
|
|
||||||
} else {
|
|
||||||
this->value_[REGPLUSMINUS] = FLAGMINUS;
|
|
||||||
digits = pair3 & 0x7FFF;
|
|
||||||
}
|
|
||||||
this->value_[REGDIG1] = '0' + ((digits / 1000) % 10);
|
|
||||||
this->value_[REGDIG2] = '0' + ((digits / 100) % 10);
|
|
||||||
this->value_[REGDIG3] = '0' + ((digits / 10) % 10);
|
|
||||||
this->value_[REGDIG4] = '0' + (digits % 10);
|
|
||||||
} else {
|
|
||||||
memcpy(this->value_, OVERLOAD_FRAME, sizeof(OVERLOAD_FRAME));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char digit_char_(uint8_t reg) const {
|
|
||||||
uint8_t c = this->value_[reg];
|
|
||||||
return (c >= '0' && c <= '9') ? static_cast<char>(c) : ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw_digits_(Display &it, const char *text, bool negative, Color color) {
|
|
||||||
if (negative) this->draw_segment_(it, 8, 88, 26, 9, true, color);
|
|
||||||
constexpr int digit_x = 40;
|
|
||||||
constexpr int digit_y = 35;
|
|
||||||
constexpr int digit_w = 50;
|
|
||||||
constexpr int digit_h = 88;
|
|
||||||
constexpr int digit_distance = 64;
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
this->draw_seven_segment_(it, digit_x + i * digit_distance, digit_y, digit_w, digit_h, text[i], color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw_decimal_points_(Display &it, Color color) {
|
|
||||||
uint8_t p = this->value_[REGPOINT];
|
|
||||||
if ((p & FLAGPOINT1) == FLAGPOINT1) it.filled_rectangle(95, 117, 8, 10, color);
|
|
||||||
if ((p & FLAGPOINT2) == FLAGPOINT2) it.filled_rectangle(159, 117, 8, 10, color);
|
|
||||||
if ((p & FLAGPOINT3) == FLAGPOINT3) it.filled_rectangle(223, 117, 8, 10, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw_segment_(Display &it, int x, int y, int w, int h, bool horizontal, Color color) {
|
|
||||||
if (horizontal) {
|
|
||||||
int cap = h / 2;
|
|
||||||
it.filled_rectangle(x + cap, y, w - 2 * cap, h, color);
|
|
||||||
it.filled_triangle(x, y + cap, x + cap, y, x + cap, y + h, color);
|
|
||||||
it.filled_triangle(x + w, y + cap, x + w - cap, y, x + w - cap, y + h, color);
|
|
||||||
} else {
|
|
||||||
int cap = w / 2;
|
|
||||||
it.filled_rectangle(x, y + cap, w, h - 2 * cap, color);
|
|
||||||
it.filled_triangle(x + cap, y, x, y + cap, x + w, y + cap, color);
|
|
||||||
it.filled_triangle(x + cap, y + h, x, y + h - cap, x + w, y + h - cap, 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 col = 0; col < w; col++) {
|
|
||||||
uint8_t byte = data[row * ((w + 7) / 8) + (col / 8)];
|
|
||||||
if ((byte & (0x80 >> (col % 8))) != 0) {
|
|
||||||
it.draw_pixel_at(x + col, y + row, 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;
|
|
||||||
switch (ch) {
|
|
||||||
case '0': a=b=c=d=e=f=true; break;
|
|
||||||
case '1': b=c=true; break;
|
|
||||||
case '2': a=b=d=e=g=true; break;
|
|
||||||
case '3': a=b=c=d=g=true; break;
|
|
||||||
case '4': b=c=f=g=true; break;
|
|
||||||
case '5': a=c=d=f=g=true; break;
|
|
||||||
case '6': a=c=d=e=f=g=true; break;
|
|
||||||
case '7': a=b=c=true; break;
|
|
||||||
case '8': a=b=c=d=e=f=g=true; break;
|
|
||||||
case '9': a=b=c=d=f=g=true; break;
|
|
||||||
case 'O': a=b=c=d=e=f=true; break;
|
|
||||||
case 'L': d=e=f=true; break;
|
|
||||||
case '-': g=true; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
int t = 10;
|
|
||||||
int half = h / 2;
|
|
||||||
int gap = 1;
|
|
||||||
|
|
||||||
int top_y = y;
|
|
||||||
int mid_y = y + half - t / 2;
|
|
||||||
int bot_y = y + h - t;
|
|
||||||
|
|
||||||
int upper_v_y = top_y + t + gap;
|
|
||||||
int upper_v_h = mid_y - gap - upper_v_y;
|
|
||||||
int lower_v_y = mid_y + t + gap;
|
|
||||||
int lower_v_h = bot_y - gap - lower_v_y;
|
|
||||||
|
|
||||||
if (a) this->draw_segment_(it, x + t / 2, top_y, w - t, t, true, color);
|
|
||||||
if (b && upper_v_h > 0) this->draw_segment_(it, x + w - t, upper_v_y, t, upper_v_h, false, color);
|
|
||||||
if (c && lower_v_h > 0) this->draw_segment_(it, x + w - t, lower_v_y, t, lower_v_h, false, color);
|
|
||||||
if (d) this->draw_segment_(it, x + t / 2, bot_y, w - t, t, true, color);
|
|
||||||
if (e && lower_v_h > 0) this->draw_segment_(it, x, lower_v_y, t, lower_v_h, false, color);
|
|
||||||
if (f && upper_v_h > 0) this->draw_segment_(it, x, upper_v_y, t, upper_v_h, false, color);
|
|
||||||
if (g) this->draw_segment_(it, x + t / 2, mid_y, w - t, t, true, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw_bargraph_(Display &it, uint16_t digits, bool active) {
|
|
||||||
const Color fg(255, 255, 255);
|
|
||||||
const Color inactive(80, 80, 80);
|
|
||||||
uint16_t mapped = active ? static_cast<uint16_t>(digits * 240 / 6000) : 0;
|
|
||||||
if (mapped > 240) mapped = 240;
|
|
||||||
for (uint16_t i = 0; i <= 240; i += 4) {
|
|
||||||
Color col = (active && i <= mapped) ? fg : inactive;
|
|
||||||
int h = (i % 40 == 0) ? 20 : ((i % 20 == 0) ? 15 : 10);
|
|
||||||
it.vertical_line(40 + i, 185 - h, h, col);
|
|
||||||
}
|
|
||||||
it.horizontal_line(35, 185, 250, inactive);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace owon_b35t
|
|
||||||
|
|
||||||
static owon_b35t::Meter owon_meter;
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -23,10 +23,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: epaperframe.home
|
#use_address: epaperframe.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: ${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
|
||||||
|
|
||||||
|
|||||||
@@ -1,741 +0,0 @@
|
|||||||
/*
|
|
||||||
* ESPHome helper for OWON B35T/B35T+ BLE meter on M5Stack Core 2.
|
|
||||||
* Parser is based on the standalone Arduino sketch by Reaper7
|
|
||||||
* (Beerware license, Revision 42) and Dean Cording's owonb35 notes.
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include "esp_heap_caps.h"
|
|
||||||
#include "esp_system.h"
|
|
||||||
#include "esphome/core/helpers.h"
|
|
||||||
#include "esphome/components/i2c/i2c.h"
|
|
||||||
#include "esphome/core/log.h"
|
|
||||||
#include "esphome/components/display/display.h"
|
|
||||||
|
|
||||||
namespace owon_b35t {
|
|
||||||
|
|
||||||
using esphome::Color;
|
|
||||||
using esphome::display::Display;
|
|
||||||
|
|
||||||
static const char *const TAG = "owon_b35t";
|
|
||||||
static const char *const POWER_TAG = "core2_power";
|
|
||||||
static constexpr uint8_t AXP192_ADDR = 0x34;
|
|
||||||
static esphome::i2c::I2CDevice axp192;
|
|
||||||
static bool axp192_ready = false;
|
|
||||||
|
|
||||||
static bool axp_write(uint8_t reg, uint8_t value) {
|
|
||||||
if (!axp192_ready) return false;
|
|
||||||
bool ok = axp192.write_byte(reg, value);
|
|
||||||
if (!ok) ESP_LOGW(POWER_TAG, "AXP192 write reg 0x%02X failed", reg);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool axp_read(uint8_t reg, uint8_t *value) {
|
|
||||||
if (!axp192_ready) return false;
|
|
||||||
bool ok = axp192.read_byte(reg, value);
|
|
||||||
if (!ok) ESP_LOGW(POWER_TAG, "AXP192 read reg 0x%02X failed", reg);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void axp_update(uint8_t reg, uint8_t clear_mask, uint8_t set_mask) {
|
|
||||||
uint8_t value = 0;
|
|
||||||
if (!axp_read(reg, &value)) return;
|
|
||||||
value = (value & ~clear_mask) | set_mask;
|
|
||||||
axp_write(reg, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint8_t axp_dc_voltage_data(uint16_t millivolts) {
|
|
||||||
if (millivolts < 700) millivolts = 700;
|
|
||||||
if (millivolts > 3500) millivolts = 3500;
|
|
||||||
return static_cast<uint8_t>((millivolts - 700) / 25) & 0x7F;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint8_t axp_ldo_voltage_data(uint16_t millivolts) {
|
|
||||||
if (millivolts < 1800) millivolts = 1800;
|
|
||||||
if (millivolts > 3300) millivolts = 3300;
|
|
||||||
return static_cast<uint8_t>((millivolts - 1800) / 100) & 0x0F;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void core2_axp192_set_lcd_voltage(uint16_t millivolts) {
|
|
||||||
uint8_t value = 0;
|
|
||||||
axp_read(0x27, &value);
|
|
||||||
axp_write(0x27, (value & 0x80) | axp_dc_voltage_data(millivolts)); // DCDC3, LCD backlight
|
|
||||||
}
|
|
||||||
|
|
||||||
static void core2_axp192_set_backlight(float brightness) {
|
|
||||||
if (brightness <= 0.0f) {
|
|
||||||
axp_update(0x12, 0x02, 0x00); // DCDC3 off
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (brightness > 1.0f) brightness = 1.0f;
|
|
||||||
uint16_t millivolts = static_cast<uint16_t>(2400 + brightness * 900);
|
|
||||||
core2_axp192_set_lcd_voltage(millivolts);
|
|
||||||
axp_update(0x12, 0x00, 0x02); // DCDC3 on
|
|
||||||
}
|
|
||||||
|
|
||||||
static void core2_axp192_init(esphome::i2c::I2CBus *bus) {
|
|
||||||
axp192.set_i2c_bus(bus);
|
|
||||||
axp192.set_i2c_address(AXP192_ADDR);
|
|
||||||
axp192_ready = true;
|
|
||||||
ESP_LOGI(POWER_TAG, "Initializing M5Stack Core2 AXP192 LCD power");
|
|
||||||
axp_update(0x30, 0xF9, 0x02); // Disable VBUS current limit, preserve bit 2.
|
|
||||||
axp_update(0x92, 0x07, 0x00); // GPIO1 open-drain output.
|
|
||||||
axp_update(0x93, 0x07, 0x00); // GPIO2 open-drain output.
|
|
||||||
axp_write(0x35, 0xA2); // RTC battery charging.
|
|
||||||
|
|
||||||
uint8_t value = 0;
|
|
||||||
axp_read(0x26, &value);
|
|
||||||
axp_write(0x26, (value & 0x80) | axp_dc_voltage_data(3350)); // DCDC1 ESP32 VDD.
|
|
||||||
core2_axp192_set_lcd_voltage(2800);
|
|
||||||
|
|
||||||
uint8_t ldo2 = axp_ldo_voltage_data(3300);
|
|
||||||
uint8_t ldo3 = axp_ldo_voltage_data(2000);
|
|
||||||
axp_write(0x28, (ldo2 << 4) | ldo3); // LDO2 LCD logic/SD, LDO3 vibrator.
|
|
||||||
axp_update(0x12, 0x00, 0x07); // Enable DCDC1, DCDC3, LDO2.
|
|
||||||
axp_write(0x82, 0xFF); // ADCs on.
|
|
||||||
axp_update(0x95, 0x8D, 0x84); // GPIO4 setup, as M5Core2 library does.
|
|
||||||
axp_write(0x36, 0x4C); // Power key timing.
|
|
||||||
|
|
||||||
// LCD reset through AXP192 GPIO4.
|
|
||||||
axp_update(0x96, 0x02, 0x00);
|
|
||||||
delay(100);
|
|
||||||
axp_update(0x96, 0x00, 0x02);
|
|
||||||
delay(100);
|
|
||||||
|
|
||||||
core2_axp192_set_backlight(1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const uint8_t ACCU_BMP[32] = {
|
|
||||||
0b00000000, 0b00000000,
|
|
||||||
0b00000000, 0b00000000,
|
|
||||||
0b00000000, 0b00000000,
|
|
||||||
0b11111111, 0b11111110,
|
|
||||||
0b10000000, 0b00000010,
|
|
||||||
0b10000000, 0b00000011,
|
|
||||||
0b10000000, 0b00000011,
|
|
||||||
0b10000000, 0b00000011,
|
|
||||||
0b10000000, 0b00000011,
|
|
||||||
0b10000000, 0b00000011,
|
|
||||||
0b10000000, 0b00000011,
|
|
||||||
0b10000000, 0b00000010,
|
|
||||||
0b11111111, 0b11111110,
|
|
||||||
0b00000000, 0b00000000,
|
|
||||||
0b00000000, 0b00000000,
|
|
||||||
0b00000000, 0b00000000,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const uint8_t BLE_BMP[32] = {
|
|
||||||
0b00000001, 0b10000000,
|
|
||||||
0b00000001, 0b11000000,
|
|
||||||
0b00010001, 0b01100000,
|
|
||||||
0b00011001, 0b00110000,
|
|
||||||
0b00001101, 0b00011000,
|
|
||||||
0b00000111, 0b00110000,
|
|
||||||
0b00000011, 0b01100000,
|
|
||||||
0b00000001, 0b11000000,
|
|
||||||
0b00000001, 0b11000000,
|
|
||||||
0b00000011, 0b01100000,
|
|
||||||
0b00000111, 0b00110000,
|
|
||||||
0b00001101, 0b00011000,
|
|
||||||
0b00011001, 0b00110000,
|
|
||||||
0b00010001, 0b01100000,
|
|
||||||
0b00000001, 0b11000000,
|
|
||||||
0b00000001, 0b10000000,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const uint8_t DIODE_BMP[32] = {
|
|
||||||
0b00001000, 0b00011000,
|
|
||||||
0b00001100, 0b00011000,
|
|
||||||
0b00001110, 0b00011000,
|
|
||||||
0b00001111, 0b00011000,
|
|
||||||
0b00001111, 0b10011000,
|
|
||||||
0b00001111, 0b11011000,
|
|
||||||
0b00001111, 0b11111000,
|
|
||||||
0b11111111, 0b11111111,
|
|
||||||
0b11111111, 0b11111111,
|
|
||||||
0b00001111, 0b11111000,
|
|
||||||
0b00001111, 0b11011000,
|
|
||||||
0b00001111, 0b10011000,
|
|
||||||
0b00001111, 0b00011000,
|
|
||||||
0b00001110, 0b00011000,
|
|
||||||
0b00001100, 0b00011000,
|
|
||||||
0b00001000, 0b00011000,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const uint8_t BUZZ_BMP[32] = {
|
|
||||||
0b00000000, 0b11000000,
|
|
||||||
0b00000001, 0b11000000,
|
|
||||||
0b00000011, 0b11000001,
|
|
||||||
0b00000111, 0b11000001,
|
|
||||||
0b00001111, 0b11000101,
|
|
||||||
0b11111111, 0b11000101,
|
|
||||||
0b11111111, 0b11010101,
|
|
||||||
0b11111111, 0b11010101,
|
|
||||||
0b11111111, 0b11010101,
|
|
||||||
0b11111111, 0b11010101,
|
|
||||||
0b11111111, 0b11000101,
|
|
||||||
0b00001111, 0b11000101,
|
|
||||||
0b00000111, 0b11000001,
|
|
||||||
0b00000011, 0b11000001,
|
|
||||||
0b00000001, 0b11000000,
|
|
||||||
0b00000000, 0b11000000,
|
|
||||||
};
|
|
||||||
|
|
||||||
class Meter {
|
|
||||||
public:
|
|
||||||
static constexpr uint8_t REGPLUSMINUS = 0x00;
|
|
||||||
static constexpr uint8_t FLAGPLUS = 0b00101011;
|
|
||||||
static constexpr uint8_t FLAGMINUS = 0b00101101;
|
|
||||||
static constexpr uint8_t REGDIG1 = 0x01;
|
|
||||||
static constexpr uint8_t REGDIG2 = 0x02;
|
|
||||||
static constexpr uint8_t REGDIG3 = 0x03;
|
|
||||||
static constexpr uint8_t REGDIG4 = 0x04;
|
|
||||||
static constexpr uint8_t REGPOINT = 0x06;
|
|
||||||
static constexpr uint8_t FLAGPOINT0 = 0b00110000;
|
|
||||||
static constexpr uint8_t FLAGPOINT1 = 0b00110001;
|
|
||||||
static constexpr uint8_t FLAGPOINT2 = 0b00110010;
|
|
||||||
static constexpr uint8_t FLAGPOINT3 = 0b00110100;
|
|
||||||
static constexpr uint8_t REGMODE = 0x07;
|
|
||||||
static constexpr uint8_t FLAGMODEHOLD = 0b00000010;
|
|
||||||
static constexpr uint8_t FLAGMODEREL = 0b00000100;
|
|
||||||
static constexpr uint8_t FLAGMODEAC = 0b00001000;
|
|
||||||
static constexpr uint8_t FLAGMODEDC = 0b00010000;
|
|
||||||
static constexpr uint8_t FLAGMODEAUTO = 0b00100000;
|
|
||||||
static constexpr uint8_t REGMINMAX = 0x08;
|
|
||||||
static constexpr uint8_t FLAGMIN = 0b00010000;
|
|
||||||
static constexpr uint8_t FLAGMAX = 0b00100000;
|
|
||||||
static constexpr uint8_t REGSCALE = 0x09;
|
|
||||||
static constexpr uint8_t FLAGSCALEDUTY = 0b00000010;
|
|
||||||
static constexpr uint8_t FLAGSCALEDIODE = 0b00000100;
|
|
||||||
static constexpr uint8_t FLAGSCALEBUZZ = 0b00001000;
|
|
||||||
static constexpr uint8_t FLAGSCALEMEGA = 0b00010000;
|
|
||||||
static constexpr uint8_t FLAGSCALEKILO = 0b00100000;
|
|
||||||
static constexpr uint8_t FLAGSCALEMILLI = 0b01000000;
|
|
||||||
static constexpr uint8_t FLAGSCALEMICRO = 0b10000000;
|
|
||||||
static constexpr uint8_t REGUNIT = 0x0a;
|
|
||||||
static constexpr uint8_t FLAGUNITFAHR = 0b00000001;
|
|
||||||
static constexpr uint8_t FLAGUNITGRAD = 0b00000010;
|
|
||||||
static constexpr uint8_t FLAGUNITNF = 0b00000100;
|
|
||||||
static constexpr uint8_t FLAGUNITHZ = 0b00001000;
|
|
||||||
static constexpr uint8_t FLAGUNITHFE = 0b00010000;
|
|
||||||
static constexpr uint8_t FLAGUNITOHM = 0b00100000;
|
|
||||||
static constexpr uint8_t FLAGUNITAMP = 0b01000000;
|
|
||||||
static constexpr uint8_t FLAGUNITVOLT = 0b10000000;
|
|
||||||
|
|
||||||
bool connected{false};
|
|
||||||
bool write_available{false};
|
|
||||||
bool is_plus{false};
|
|
||||||
bool low_battery{false};
|
|
||||||
bool overload{false};
|
|
||||||
bool has_reading{false};
|
|
||||||
uint8_t selected_button{1};
|
|
||||||
uint32_t last_notify_ms{0};
|
|
||||||
|
|
||||||
bool handle_notify(const std::vector<uint8_t> &data) {
|
|
||||||
if (data.size() > sizeof(this->raw_))
|
|
||||||
return false;
|
|
||||||
if (data.size() == 6 && data[1] >= 0xF0) {
|
|
||||||
memset(this->raw_, 0, sizeof(this->raw_));
|
|
||||||
memcpy(this->raw_, data.data(), data.size());
|
|
||||||
this->is_plus = true;
|
|
||||||
this->parse_plus_();
|
|
||||||
} else if (data.size() == 14 && data[12] == 0x0D && data[13] == 0x0A) {
|
|
||||||
memset(this->value_, 0, sizeof(this->value_));
|
|
||||||
memcpy(this->value_, data.data(), data.size());
|
|
||||||
this->is_plus = false;
|
|
||||||
} else {
|
|
||||||
ESP_LOGW(TAG, "Ignoring unexpected OWON frame length=%u", static_cast<unsigned>(data.size()));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->overload = memcmp(this->value_, OVERLOAD_FRAME, sizeof(OVERLOAD_FRAME)) == 0;
|
|
||||||
this->display_value = this->calc_display_value_();
|
|
||||||
this->base_value = this->calc_base_value_();
|
|
||||||
this->has_reading = true;
|
|
||||||
this->last_notify_ms = millis();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void on_connect() {
|
|
||||||
this->connected = true;
|
|
||||||
this->write_available = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void on_disconnect() {
|
|
||||||
this->connected = false;
|
|
||||||
this->write_available = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
float value() const { return this->display_value; }
|
|
||||||
float value_base() const { return this->base_value; }
|
|
||||||
bool negative() const { return (this->value_[REGPLUSMINUS] & FLAGMINUS) == FLAGMINUS; }
|
|
||||||
bool auto_range() const { return (this->value_[REGMODE] & FLAGMODEAUTO) == FLAGMODEAUTO; }
|
|
||||||
bool hold() const { return (this->value_[REGMODE] & FLAGMODEHOLD) == FLAGMODEHOLD; }
|
|
||||||
bool relative() const { return (this->value_[REGMODE] & FLAGMODEREL) == FLAGMODEREL; }
|
|
||||||
bool ac() const { return (this->value_[REGMODE] & FLAGMODEAC) == FLAGMODEAC; }
|
|
||||||
bool dc() const { return (this->value_[REGMODE] & FLAGMODEDC) == FLAGMODEDC; }
|
|
||||||
bool min_mode() const { return (this->value_[REGMINMAX] & FLAGMIN) == FLAGMIN; }
|
|
||||||
bool max_mode() const { return (this->value_[REGMINMAX] & FLAGMAX) == FLAGMAX; }
|
|
||||||
bool diode() const { return (this->value_[REGSCALE] & FLAGSCALEDIODE) == FLAGSCALEDIODE; }
|
|
||||||
bool continuity() const { return (this->value_[REGSCALE] & FLAGSCALEBUZZ) == FLAGSCALEBUZZ; }
|
|
||||||
|
|
||||||
const char *unit() const {
|
|
||||||
switch (this->value_[REGUNIT]) {
|
|
||||||
case FLAGUNITFAHR: return "°F";
|
|
||||||
case FLAGUNITGRAD: return "°C";
|
|
||||||
case FLAGUNITNF: return "nF";
|
|
||||||
case FLAGUNITHZ: return "Hz";
|
|
||||||
case FLAGUNITHFE: return "hFE";
|
|
||||||
case FLAGUNITOHM: return "Ω";
|
|
||||||
case FLAGUNITAMP: return "A";
|
|
||||||
case FLAGUNITVOLT: return "V";
|
|
||||||
default: return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *scale() const {
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEDUTY) == FLAGSCALEDUTY) return "%";
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEMEGA) == FLAGSCALEMEGA) return "M";
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEKILO) == FLAGSCALEKILO) return "k";
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEMILLI) == FLAGSCALEMILLI) return "m";
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEMICRO) == FLAGSCALEMICRO) return "µ";
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string mode_text() const {
|
|
||||||
std::string out;
|
|
||||||
if (this->dc()) out += "DC ";
|
|
||||||
if (this->ac()) out += "AC ";
|
|
||||||
if (this->auto_range()) out += "AUTO ";
|
|
||||||
if (this->hold()) out += "HOLD ";
|
|
||||||
if (this->relative()) out += "REL ";
|
|
||||||
if (this->min_mode()) out += "MIN ";
|
|
||||||
if (this->max_mode()) out += "MAX ";
|
|
||||||
if (this->diode()) out += "DIODE ";
|
|
||||||
if (this->continuity()) out += "CONT ";
|
|
||||||
if (!out.empty()) out.pop_back();
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string reading_text() const {
|
|
||||||
if (!this->connected) return "Disconnected";
|
|
||||||
if (!this->has_reading) return "Waiting for data";
|
|
||||||
if (this->overload) return "OL " + std::string(this->scale()) + this->unit();
|
|
||||||
char buf[48];
|
|
||||||
snprintf(buf, sizeof(buf), "%s%.4g %s%s", this->negative() ? "-" : "", std::fabs(this->display_value), this->scale(), this->unit());
|
|
||||||
return std::string(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEDUTY) == FLAGSCALEDUTY) return KIND_DUTY;
|
|
||||||
switch (this->value_[REGUNIT]) {
|
|
||||||
case FLAGUNITVOLT: return KIND_VOLTAGE;
|
|
||||||
case FLAGUNITAMP: return KIND_CURRENT;
|
|
||||||
case FLAGUNITOHM: return KIND_RESISTANCE;
|
|
||||||
case FLAGUNITHZ: return KIND_FREQUENCY;
|
|
||||||
case FLAGUNITNF: return KIND_CAPACITANCE;
|
|
||||||
case FLAGUNITGRAD: return KIND_TEMP_C;
|
|
||||||
case FLAGUNITFAHR: return KIND_TEMP_F;
|
|
||||||
default: return KIND_OTHER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *selected_button_name() const {
|
|
||||||
static const char *const names[] = {"SELECT", "RANGE", "HLD/LIG", "REL/BT", "HZ/DUTY", "MAX/MIN"};
|
|
||||||
uint8_t index = this->selected_button;
|
|
||||||
if (index < 1) index = 1;
|
|
||||||
if (index > 6) index = 6;
|
|
||||||
return names[index - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
void previous_button() {
|
|
||||||
if (this->selected_button > 1) this->selected_button--;
|
|
||||||
}
|
|
||||||
void next_button() {
|
|
||||||
if (this->selected_button < 6) this->selected_button++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void render(esphome::display::Display &it, esphome::display::BaseFont *font,
|
|
||||||
esphome::display::BaseFont *value_font, int display_page = 0,
|
|
||||||
bool atorch_connected = false,
|
|
||||||
float atorch_voltage = NAN, float atorch_current = NAN, float atorch_power = NAN,
|
|
||||||
float atorch_capacity = NAN, float atorch_temperature = NAN) {
|
|
||||||
const Color bg(0, 0, 0);
|
|
||||||
const Color fg(210, 210, 210);
|
|
||||||
// Chosen to map to a neutral dark gray in the RGB332 8-bit display palette.
|
|
||||||
const Color inactive(80, 80, 80);
|
|
||||||
const Color yellow(255, 220, 0);
|
|
||||||
const Color blue(0, 80, 255);
|
|
||||||
const Color cyan(0, 255, 255);
|
|
||||||
const Color magenta(255, 0, 255);
|
|
||||||
const Color red(255, 0, 0);
|
|
||||||
const Color green(0, 220, 0);
|
|
||||||
const Color orange(255, 165, 0);
|
|
||||||
|
|
||||||
if (display_page == 0) {
|
|
||||||
// --- PAGE 1: OWON Multimeter ---
|
|
||||||
it.fill(bg);
|
|
||||||
bool status_active = this->connected && this->has_reading;
|
|
||||||
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->label_(it, font, 86, 8, "AUTO", status_active && this->auto_range() ? fg : inactive);
|
|
||||||
this->label_(it, font, 138, 8, "MAX", status_active && this->max_mode() ? red : inactive);
|
|
||||||
this->label_(it, font, 178, 8, "MIN", status_active && this->min_mode() ? green : inactive);
|
|
||||||
this->label_(it, font, 218, 8, "HOLD", status_active && this->hold() ? blue : inactive);
|
|
||||||
this->label_(it, font, 270, 8, "REL", status_active && this->relative() ? Color(128, 128, 0) : inactive);
|
|
||||||
|
|
||||||
this->label_(it, font, 8, 66, "DC", status_active && this->dc() ? cyan : inactive);
|
|
||||||
this->label_(it, font, 8, 102, "AC", status_active && this->ac() ? magenta : inactive);
|
|
||||||
|
|
||||||
if (!this->connected) {
|
|
||||||
this->draw_digits_(it, "----", false, inactive);
|
|
||||||
it.print(160, 148, font, inactive, esphome::display::TextAlign::CENTER, "scan/connect");
|
|
||||||
} else if (!this->has_reading) {
|
|
||||||
this->draw_digits_(it, "8888", false, inactive);
|
|
||||||
it.print(160, 148, font, inactive, esphome::display::TextAlign::CENTER, "waiting");
|
|
||||||
} else if (this->overload) {
|
|
||||||
this->draw_digits_(it, " OL ", false, fg);
|
|
||||||
} else {
|
|
||||||
char d[5];
|
|
||||||
d[0] = this->digit_char_(REGDIG1);
|
|
||||||
d[1] = this->digit_char_(REGDIG2);
|
|
||||||
d[2] = this->digit_char_(REGDIG3);
|
|
||||||
d[3] = this->digit_char_(REGDIG4);
|
|
||||||
d[4] = 0;
|
|
||||||
this->draw_digits_(it, d, this->negative(), fg);
|
|
||||||
this->draw_decimal_points_(it, fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status_active) {
|
|
||||||
std::string unit_line = std::string(this->scale()) + this->unit();
|
|
||||||
it.print(270, 140, font, yellow, esphome::display::TextAlign::CENTER, unit_line.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool bargraph_active = status_active && !this->overload;
|
|
||||||
this->draw_bargraph_(it, bargraph_active ? this->digits_from_buffer_() : 0, bargraph_active);
|
|
||||||
this->draw_icon_(it, 300, 148, 16, 16, DIODE_BMP, status_active && this->diode() ? magenta : inactive);
|
|
||||||
this->draw_icon_(it, 300, 174, 16, 16, BUZZ_BMP, status_active && this->continuity() ? orange : 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(242, 212, 40, 24, this->write_available ? fg : inactive);
|
|
||||||
it.print(54, 216, font, bg, esphome::display::TextAlign::TOP_CENTER, "<");
|
|
||||||
it.print(158, 216, font, bg, esphome::display::TextAlign::TOP_CENTER, this->selected_button_name());
|
|
||||||
it.print(262, 216, font, bg, esphome::display::TextAlign::TOP_CENTER, ">");
|
|
||||||
} else {
|
|
||||||
// --- PAGE 2: Atorch DL24 DC load ---
|
|
||||||
it.fill(bg);
|
|
||||||
const Color panel_active(18, 24, 32);
|
|
||||||
const Color panel_inactive(12, 12, 12);
|
|
||||||
const Color panel_dim(10, 14, 20);
|
|
||||||
const Color border_active(55, 70, 86);
|
|
||||||
const Color border_inactive(42, 42, 42);
|
|
||||||
const Color white(245, 245, 245);
|
|
||||||
const Color dim_value(110, 110, 110);
|
|
||||||
const Color dim_accent(65, 65, 65);
|
|
||||||
const Color panel = atorch_connected ? panel_active : panel_inactive;
|
|
||||||
const Color border = atorch_connected ? border_active : border_inactive;
|
|
||||||
const Color value_color = atorch_connected ? white : dim_value;
|
|
||||||
const Color voltage_color = atorch_connected ? cyan : dim_accent;
|
|
||||||
const Color current_color = atorch_connected ? orange : dim_accent;
|
|
||||||
const Color power_color = atorch_connected ? yellow : dim_accent;
|
|
||||||
const Color temp_color = atorch_connected ? magenta : dim_accent;
|
|
||||||
const Color header_color = atorch_connected ? cyan : dim_value;
|
|
||||||
|
|
||||||
char voltage_text[24];
|
|
||||||
char current_text[24];
|
|
||||||
char power_text[24];
|
|
||||||
char capacity_text[24];
|
|
||||||
char temperature_text[24];
|
|
||||||
this->format_metric_(voltage_text, sizeof(voltage_text), atorch_voltage, "V", 2);
|
|
||||||
this->format_metric_(current_text, sizeof(current_text), atorch_current, "A", 3);
|
|
||||||
this->format_metric_(power_text, sizeof(power_text), atorch_power, "W", 2);
|
|
||||||
this->format_metric_(capacity_text, sizeof(capacity_text), atorch_capacity, "Ah", 3);
|
|
||||||
this->format_metric_(temperature_text, sizeof(temperature_text), atorch_temperature, "°C", 1);
|
|
||||||
|
|
||||||
it.filled_rectangle(0, 0, 320, 30, atorch_connected ? panel_dim : panel_inactive);
|
|
||||||
it.print(10, 7, font, header_color, esphome::display::TextAlign::TOP_LEFT, "ATORCH DL24");
|
|
||||||
if (atorch_connected) {
|
|
||||||
this->draw_icon_(it, 152, 7, 16, 16, BLE_BMP, blue);
|
|
||||||
}
|
|
||||||
it.print(310, 7, font, inactive, esphome::display::TextAlign::TOP_RIGHT, "DC LOAD");
|
|
||||||
|
|
||||||
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, 10, 128, 145, 76, "POWER", power_text, power_color, panel, border, value_color);
|
|
||||||
this->draw_metric_card_(it, font, value_font, 165, 128, 145, 76, "TEMP", temperature_text, temp_color, panel, border, value_color);
|
|
||||||
|
|
||||||
it.filled_rectangle(10, 212, 300, 22, atorch_connected ? panel_dim : panel_inactive);
|
|
||||||
it.filled_rectangle(10, 212, 300, 1, border);
|
|
||||||
it.filled_rectangle(10, 233, 300, 1, border);
|
|
||||||
it.filled_rectangle(10, 212, 1, 22, border);
|
|
||||||
it.filled_rectangle(309, 212, 1, 22, border);
|
|
||||||
it.print(24, 216, font, inactive, esphome::display::TextAlign::TOP_LEFT, "CAPACITY");
|
|
||||||
it.print(306, 212, value_font, value_color, esphome::display::TextAlign::TOP_RIGHT, capacity_text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint8_t raw_[14]{};
|
|
||||||
uint8_t value_[14]{};
|
|
||||||
float display_value{NAN};
|
|
||||||
float base_value{NAN};
|
|
||||||
static constexpr uint8_t OVERLOAD_FRAME[5] = {0x2B, 0x3F, 0x30, 0x3A, 0x3F};
|
|
||||||
|
|
||||||
uint16_t digits_from_buffer_() const {
|
|
||||||
uint16_t out = 0;
|
|
||||||
if (this->value_[REGDIG1] >= '0' && this->value_[REGDIG1] <= '9') out += (this->value_[REGDIG1] - '0') * 1000;
|
|
||||||
if (this->value_[REGDIG2] >= '0' && this->value_[REGDIG2] <= '9') out += (this->value_[REGDIG2] - '0') * 100;
|
|
||||||
if (this->value_[REGDIG3] >= '0' && this->value_[REGDIG3] <= '9') out += (this->value_[REGDIG3] - '0') * 10;
|
|
||||||
if (this->value_[REGDIG4] >= '0' && this->value_[REGDIG4] <= '9') out += (this->value_[REGDIG4] - '0');
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
float calc_display_value_() const {
|
|
||||||
if (this->overload) return NAN;
|
|
||||||
|
|
||||||
if (this->is_plus) {
|
|
||||||
uint16_t pair1 = static_cast<uint16_t>(this->raw_[0]) | (static_cast<uint16_t>(this->raw_[1]) << 8);
|
|
||||||
uint8_t decimal = pair1 & 0x07;
|
|
||||||
if (decimal >= 7) return NAN;
|
|
||||||
|
|
||||||
uint16_t pair3 = static_cast<uint16_t>(this->raw_[4]) | (static_cast<uint16_t>(this->raw_[5]) << 8);
|
|
||||||
bool negative = pair3 >= 0x7FFF;
|
|
||||||
uint16_t digits = negative ? (pair3 & 0x7FFF) : pair3;
|
|
||||||
float v = static_cast<float>(digits) / std::pow(10.0f, decimal);
|
|
||||||
return negative ? -v : v;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t decimal = 0;
|
|
||||||
switch (this->value_[REGPOINT] & 0x07) {
|
|
||||||
case 0b001: decimal = 1; break;
|
|
||||||
case 0b010: decimal = 2; break;
|
|
||||||
case 0b100: decimal = 3; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
float v = static_cast<float>(this->digits_from_buffer_()) / std::pow(10.0f, decimal);
|
|
||||||
return this->negative() ? -v : v;
|
|
||||||
}
|
|
||||||
|
|
||||||
float calc_base_value_() const {
|
|
||||||
if (std::isnan(this->display_value)) return NAN;
|
|
||||||
if (this->value_[REGUNIT] == FLAGUNITNF) return this->display_value * 1e-9f;
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEMEGA) == FLAGSCALEMEGA) return this->display_value * 1e6f;
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEKILO) == FLAGSCALEKILO) return this->display_value * 1e3f;
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEMILLI) == FLAGSCALEMILLI) return this->display_value * 1e-3f;
|
|
||||||
if ((this->value_[REGSCALE] & FLAGSCALEMICRO) == FLAGSCALEMICRO) return this->display_value * 1e-6f;
|
|
||||||
return this->display_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void parse_plus_() {
|
|
||||||
memset(this->value_, 0, sizeof(this->value_));
|
|
||||||
this->value_[5] = 0x20;
|
|
||||||
this->value_[12] = 0x0D;
|
|
||||||
this->value_[13] = 0x0A;
|
|
||||||
|
|
||||||
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 scale = (pair1 >> 3) & 0x07;
|
|
||||||
uint8_t decimal = pair1 & 0x07;
|
|
||||||
|
|
||||||
switch (decimal) {
|
|
||||||
case 0: this->value_[REGPOINT] = FLAGPOINT0; break;
|
|
||||||
case 1: this->value_[REGPOINT] = FLAGPOINT3; break;
|
|
||||||
case 2: this->value_[REGPOINT] = FLAGPOINT2; break;
|
|
||||||
case 3: this->value_[REGPOINT] = FLAGPOINT1; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (function) {
|
|
||||||
case 0: this->value_[REGUNIT] |= FLAGUNITVOLT; this->value_[REGMODE] |= FLAGMODEDC; break;
|
|
||||||
case 1: this->value_[REGUNIT] |= FLAGUNITVOLT; this->value_[REGMODE] |= FLAGMODEAC; break;
|
|
||||||
case 2: this->value_[REGUNIT] |= FLAGUNITAMP; this->value_[REGMODE] |= FLAGMODEDC; break;
|
|
||||||
case 3: this->value_[REGUNIT] |= FLAGUNITAMP; this->value_[REGMODE] |= FLAGMODEAC; break;
|
|
||||||
case 4: this->value_[REGUNIT] |= FLAGUNITOHM; break;
|
|
||||||
case 5: this->value_[REGUNIT] |= FLAGUNITNF; break;
|
|
||||||
case 6: this->value_[REGUNIT] |= FLAGUNITHZ; break;
|
|
||||||
case 7: this->value_[REGSCALE] |= FLAGSCALEDUTY; break;
|
|
||||||
case 8: this->value_[REGUNIT] |= FLAGUNITGRAD; break;
|
|
||||||
case 9: this->value_[REGUNIT] |= FLAGUNITFAHR; break;
|
|
||||||
case 10: this->value_[REGSCALE] |= FLAGSCALEDIODE; break;
|
|
||||||
case 11: this->value_[REGSCALE] |= FLAGSCALEBUZZ; break;
|
|
||||||
case 12: this->value_[REGUNIT] |= FLAGUNITHFE; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (scale) {
|
|
||||||
case 2: this->value_[REGSCALE] |= FLAGSCALEMICRO; break;
|
|
||||||
case 3: this->value_[REGSCALE] |= FLAGSCALEMILLI; break;
|
|
||||||
case 5: this->value_[REGSCALE] |= FLAGSCALEKILO; break;
|
|
||||||
case 6: this->value_[REGSCALE] |= FLAGSCALEMEGA; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 << 1)) this->value_[REGMODE] |= FLAGMODEREL;
|
|
||||||
if (pair2 & (1 << 2)) this->value_[REGMODE] |= FLAGMODEAUTO;
|
|
||||||
this->low_battery = (pair2 & (1 << 3)) != 0;
|
|
||||||
if (pair2 & (1 << 4)) this->value_[REGMINMAX] |= FLAGMIN;
|
|
||||||
if (pair2 & (1 << 5)) this->value_[REGMINMAX] |= FLAGMAX;
|
|
||||||
|
|
||||||
uint16_t pair3 = static_cast<uint16_t>(this->raw_[4]) | (static_cast<uint16_t>(this->raw_[5]) << 8);
|
|
||||||
if (decimal < 7) {
|
|
||||||
uint16_t digits = pair3;
|
|
||||||
if (pair3 < 0x7FFF) {
|
|
||||||
this->value_[REGPLUSMINUS] = FLAGPLUS;
|
|
||||||
} else {
|
|
||||||
this->value_[REGPLUSMINUS] = FLAGMINUS;
|
|
||||||
digits = pair3 & 0x7FFF;
|
|
||||||
}
|
|
||||||
this->value_[REGDIG1] = '0' + ((digits / 1000) % 10);
|
|
||||||
this->value_[REGDIG2] = '0' + ((digits / 100) % 10);
|
|
||||||
this->value_[REGDIG3] = '0' + ((digits / 10) % 10);
|
|
||||||
this->value_[REGDIG4] = '0' + (digits % 10);
|
|
||||||
} else {
|
|
||||||
memcpy(this->value_, OVERLOAD_FRAME, sizeof(OVERLOAD_FRAME));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char digit_char_(uint8_t reg) const {
|
|
||||||
uint8_t c = this->value_[reg];
|
|
||||||
return (c >= '0' && c <= '9') ? static_cast<char>(c) : ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
void format_metric_(char *buffer, size_t size, float value, const char *unit, uint8_t decimals) const {
|
|
||||||
if (!std::isfinite(value)) {
|
|
||||||
snprintf(buffer, size, "-- %s", unit);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
char format[12];
|
|
||||||
snprintf(format, sizeof(format), "%%.%uf %%s", decimals);
|
|
||||||
snprintf(buffer, size, format, value, unit);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
Color accent, Color fill, Color border, Color value_color) {
|
|
||||||
const Color bg(0, 0, 0);
|
|
||||||
const Color inactive(90, 100, 110);
|
|
||||||
it.filled_rectangle(x, y, w, h, fill);
|
|
||||||
it.filled_rectangle(x, y, w, 2, accent);
|
|
||||||
it.filled_rectangle(x, y + h - 1, w, 1, border);
|
|
||||||
it.filled_rectangle(x, y, 1, h, border);
|
|
||||||
it.filled_rectangle(x + w - 1, y, 1, h, border);
|
|
||||||
it.print(x + 10, y + 9, label_font, inactive, esphome::display::TextAlign::TOP_LEFT, title);
|
|
||||||
it.print(x + w / 2, y + 43, value_font, value_color, esphome::display::TextAlign::CENTER, value);
|
|
||||||
it.filled_rectangle(x + 10, y + h - 11, w - 20, 3, bg);
|
|
||||||
it.filled_rectangle(x + 10, y + h - 11, w - 20, 1, accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw_digits_(Display &it, const char *text, bool negative, Color color) {
|
|
||||||
if (negative) this->draw_segment_(it, 8, 88, 26, 9, true, color);
|
|
||||||
constexpr int digit_x = 40;
|
|
||||||
constexpr int digit_y = 35;
|
|
||||||
constexpr int digit_w = 50;
|
|
||||||
constexpr int digit_h = 88;
|
|
||||||
constexpr int digit_distance = 64;
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
this->draw_seven_segment_(it, digit_x + i * digit_distance, digit_y, digit_w, digit_h, text[i], color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw_decimal_points_(Display &it, Color color) {
|
|
||||||
uint8_t p = this->value_[REGPOINT];
|
|
||||||
if ((p & FLAGPOINT1) == FLAGPOINT1) it.filled_rectangle(95, 117, 8, 10, color);
|
|
||||||
if ((p & FLAGPOINT2) == FLAGPOINT2) it.filled_rectangle(159, 117, 8, 10, color);
|
|
||||||
if ((p & FLAGPOINT3) == FLAGPOINT3) it.filled_rectangle(223, 117, 8, 10, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw_segment_(Display &it, int x, int y, int w, int h, bool horizontal, Color color) {
|
|
||||||
if (horizontal) {
|
|
||||||
int cap = h / 2;
|
|
||||||
it.filled_rectangle(x + cap, y, w - 2 * cap, h +1, color);
|
|
||||||
it.filled_triangle(x, y + cap, x + cap, y, x + cap, y + h, color);
|
|
||||||
it.filled_triangle(x + w, y + cap, x + w - cap, y, x + w - cap, y + h, color);
|
|
||||||
} else {
|
|
||||||
int cap = w / 2;
|
|
||||||
it.filled_rectangle(x, y + cap, w +1, h - 2 * cap, color);
|
|
||||||
it.filled_triangle(x + cap, y, x, y + cap, x + w, y + cap, color);
|
|
||||||
it.filled_triangle(x + cap, y + h, x, y + h - cap, x + w, y + h - cap, 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 col = 0; col < w; col++) {
|
|
||||||
uint8_t byte = data[row * ((w + 7) / 8) + (col / 8)];
|
|
||||||
if ((byte & (0x80 >> (col % 8))) != 0) {
|
|
||||||
it.draw_pixel_at(x + col, y + row, 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;
|
|
||||||
switch (ch) {
|
|
||||||
case '0': a=b=c=d=e=f=true; break;
|
|
||||||
case '1': b=c=true; break;
|
|
||||||
case '2': a=b=d=e=g=true; break;
|
|
||||||
case '3': a=b=c=d=g=true; break;
|
|
||||||
case '4': b=c=f=g=true; break;
|
|
||||||
case '5': a=c=d=f=g=true; break;
|
|
||||||
case '6': a=c=d=e=f=g=true; break;
|
|
||||||
case '7': a=b=c=true; break;
|
|
||||||
case '8': a=b=c=d=e=f=g=true; break;
|
|
||||||
case '9': a=b=c=d=f=g=true; break;
|
|
||||||
case 'O': a=b=c=d=e=f=true; break;
|
|
||||||
case 'L': d=e=f=true; break;
|
|
||||||
case '-': g=true; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
int t = 10;
|
|
||||||
int half = h / 2;
|
|
||||||
int gap = 1;
|
|
||||||
|
|
||||||
int top_y = y;
|
|
||||||
int mid_y = y + half - t / 2;
|
|
||||||
int bot_y = y + h - t;
|
|
||||||
|
|
||||||
int upper_v_y = top_y + t + gap;
|
|
||||||
int upper_v_h = mid_y - gap - upper_v_y;
|
|
||||||
int lower_v_y = mid_y + t + gap;
|
|
||||||
int lower_v_h = bot_y - gap - lower_v_y;
|
|
||||||
|
|
||||||
if (a) this->draw_segment_(it, x + t / 2, top_y, w - t, t, true, color);
|
|
||||||
if (b && upper_v_h > 0) this->draw_segment_(it, x + w - t, upper_v_y, t, upper_v_h, false, color);
|
|
||||||
if (c && lower_v_h > 0) this->draw_segment_(it, x + w - t, lower_v_y, t, lower_v_h, false, color);
|
|
||||||
if (d) this->draw_segment_(it, x + t / 2, bot_y, w - t, t, true, color);
|
|
||||||
if (e && lower_v_h > 0) this->draw_segment_(it, x, lower_v_y, t, lower_v_h, false, color);
|
|
||||||
if (f && upper_v_h > 0) this->draw_segment_(it, x, upper_v_y, t, upper_v_h, false, color);
|
|
||||||
if (g) this->draw_segment_(it, x + t / 2, mid_y, w - t, t, true, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw_bargraph_(Display &it, uint16_t digits, bool active) {
|
|
||||||
const Color fg(255, 255, 255);
|
|
||||||
const Color inactive(80, 80, 80);
|
|
||||||
uint16_t mapped = active ? static_cast<uint16_t>(digits * 240 / 6000) : 0;
|
|
||||||
if (mapped > 240) mapped = 240;
|
|
||||||
for (uint16_t i = 0; i <= 240; i += 4) {
|
|
||||||
Color col = (active && i <= mapped) ? fg : inactive;
|
|
||||||
int h = (i % 40 == 0) ? 20 : ((i % 20 == 0) ? 15 : 10);
|
|
||||||
it.vertical_line(40 + i, 185 - h, h, col);
|
|
||||||
}
|
|
||||||
it.horizontal_line(35, 185, 250, inactive);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace owon_b35t
|
|
||||||
|
|
||||||
static owon_b35t::Meter owon_meter;
|
|
||||||
@@ -1,573 +0,0 @@
|
|||||||
# 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
|
|
||||||
|
|
||||||
script:
|
|
||||||
- id: wake_backlight
|
|
||||||
mode: restart
|
|
||||||
then:
|
|
||||||
- script.stop: backlight_idle
|
|
||||||
- light.turn_on:
|
|
||||||
id: backlight
|
|
||||||
brightness: 100%
|
|
||||||
- id: backlight_idle
|
|
||||||
mode: restart
|
|
||||||
then:
|
|
||||||
- delay: 2min
|
|
||||||
- if:
|
|
||||||
condition:
|
|
||||||
lambda: |-
|
|
||||||
return !owon_meter.connected && !id(atorch_connected);
|
|
||||||
then:
|
|
||||||
- light.turn_on:
|
|
||||||
id: backlight
|
|
||||||
brightness: 50%
|
|
||||||
- delay: 3min
|
|
||||||
- if:
|
|
||||||
condition:
|
|
||||||
lambda: |-
|
|
||||||
return !owon_meter.connected && !id(atorch_connected);
|
|
||||||
then:
|
|
||||||
- light.turn_off: backlight
|
|
||||||
|
|
||||||
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<unsigned>(esp_get_free_heap_size()),
|
|
||||||
static_cast<unsigned>(esp_get_minimum_free_heap_size()),
|
|
||||||
static_cast<unsigned>(heap_caps_get_free_size(MALLOC_CAP_INTERNAL)),
|
|
||||||
static_cast<unsigned>(heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL)),
|
|
||||||
static_cast<unsigned>(heap_caps_get_free_size(MALLOC_CAP_DMA)),
|
|
||||||
static_cast<unsigned>(heap_caps_get_largest_free_block(MALLOC_CAP_DMA)),
|
|
||||||
static_cast<unsigned>(heap_caps_get_free_size(MALLOC_CAP_SPIRAM)),
|
|
||||||
static_cast<unsigned>(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:
|
|
||||||
- script.execute: wake_backlight
|
|
||||||
- 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();
|
|
||||||
- if:
|
|
||||||
condition:
|
|
||||||
lambda: |-
|
|
||||||
return !owon_meter.connected && !id(atorch_connected);
|
|
||||||
then:
|
|
||||||
- script.execute: backlight_idle
|
|
||||||
|
|
||||||
- mac_address: ${dl24_mac_address}
|
|
||||||
id: atorch_ble_client
|
|
||||||
on_connect:
|
|
||||||
then:
|
|
||||||
- script.execute: wake_backlight
|
|
||||||
- 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();
|
|
||||||
- if:
|
|
||||||
condition:
|
|
||||||
lambda: |-
|
|
||||||
return !owon_meter.connected && !id(atorch_connected);
|
|
||||||
then:
|
|
||||||
- script.execute: backlight_idle
|
|
||||||
|
|
||||||
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
|
|
||||||
on_touch:
|
|
||||||
then:
|
|
||||||
- script.execute: wake_backlight
|
|
||||||
- if:
|
|
||||||
condition:
|
|
||||||
lambda: |-
|
|
||||||
return !owon_meter.connected && !id(atorch_connected);
|
|
||||||
then:
|
|
||||||
- script.execute: backlight_idle
|
|
||||||
|
|
||||||
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<uint8_t> 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<uint8_t> 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<uint8_t>({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<uint8_t>({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<uint8_t>({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<uint8_t>({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<uint8_t>({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<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
|
||||||
|
|||||||
+1
-3
@@ -36,8 +36,6 @@ exclude:
|
|||||||
- sensor.time
|
- sensor.time
|
||||||
- sensor.awtrix_kitchen_current_app
|
- sensor.awtrix_kitchen_current_app
|
||||||
- sensor.awtrix_desk_current_app
|
- sensor.awtrix_desk_current_app
|
||||||
- sensor.owon_b35t_multimeter_core2_owon_b35t_multimeter_core2_reading
|
|
||||||
- sensor.owon_b35t_multimeter_core2_owon_b35t_multimeter_core2_mode
|
|
||||||
- sensor.owon_b35t_multimeter_core2_owon_b35t_multimeter_core2_unit
|
|
||||||
# event_types:
|
# event_types:
|
||||||
# - call_service # Don't record service calls
|
# - call_service # Don't record service calls
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user