Compare commits

..
Author SHA1 Message Date
J. Nick Koston 2f76c9f1d6 Terminate a full 32-byte sen6x product name 2026-10-20 05:08:16 -10:00
J. Nick Koston 99d063455d Copy the sen6x serial with a bounded memcpy and keep the type lookup file-local 2026-10-20 04:55:42 -10:00
J. Nick Koston 39d8ecf6e6 Drop the sen6x strings: product name stays local, serial in a fixed buffer, type from the enum 2026-10-20 04:54:17 -10:00
J. Nick Koston c0cbf9cdfd [tests] Add require_service, assert_device_name and exact entity lookups (#21150) 2026-10-20 09:07:31 -04:00
J. Nick Koston 89eb58edce [ld2420][ld2450] Use Apply numbers for hub forwarding numbers (#21146) 2026-10-20 09:06:58 -04:00
J. Nick Koston 442bd4975e [ci] Add the SEN0395, LD2450 zone and held light report flows to the hardware-test skill (#21160) 2026-10-20 09:05:05 -04:00
J. Nick KostonandJonathan Swoboda 808075bf93 [components] Set optional plain values with cg.add_optional (n-z) (#21183)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-10-20 09:03:51 -04:00
J. Nick Koston a2fbedeac3 [components] Set optional plain values with cg.add_optional (a-m) (#21181) 2026-10-20 08:55:50 -04:00
J. Nick Koston 8b951fa620 [haier_tundra][tm1638][bl0940][micronova] Use Apply entities for hub forwarding buttons and switches (#21148) 2026-10-20 08:52:26 -04:00
J. Nick Koston b18a5b09e7 [tests] Drop the external component path replacements the yaml_config fixture already does (#21149) 2026-10-20 08:50:38 -04:00
J. Nick Koston 6c4a42c069 [daikin][daikin_brc][daikin_arc][whirlpool][samsung] Send and read IR frame bytes with the remote_base helpers (#21170) 2026-10-20 08:45:43 -04:00
J. Nick Koston 18120cc4a8 [airton][ballu][delonghi][hitachi_ac424][mitsubishi] Send and read IR frame bytes with the remote_base helpers (#21169) 2026-10-20 08:45:03 -04:00
J. Nick Koston 7719179dcd [tcl112][zhlt01][heatpumpir][fujitsu_general] Send and read IR frame bytes with the remote_base helpers (#21168) 2026-10-20 08:44:13 -04:00
J. Nick Koston 74e1057268 [yashima][remote_base] Send IR frame bytes with item_bytes, including most significant bit first (#21171) 2026-10-20 08:39:49 -04:00
J. Nick Koston 6304411341 [core] Add cg.add_optional for optional plain config values (#21180) 2026-10-20 08:38:02 -04:00
J. Nick Koston fd902a646a [ota] Accept an unknown image size in the RP2040 backend so web_server OTA works (#21178) 2026-10-20 07:09:13 -04:00
John McLear 09b580e4ea [st25r] Add ST25R3916 NFC reader (SPI, ISO14443A) (#15608) 2026-10-19 22:53:50 -10:00
esphome[bot] 89536632dc Bump bundled esphome-device-builder to 1.25.0 (#21175) 2026-10-19 20:18:55 -10:00
Clyde Stubbs 3da581233a [st7305] Fix position of partial updates on landscape panels (#21172) 2026-10-20 16:08:03 +11:00
J. Nick Koston ff1c448ecc [core] Document the new codegen, bus and Apply entity helpers in AGENTS.md (#21130) 2026-10-19 17:35:09 -10:00
J. Nick Koston 1430428f70 [tests] Use wait_or_fail in the api, light, script and other integration tests (#21165) 2026-10-19 17:34:25 -10:00
J. Nick Koston 91f7b0d147 [tests] Use wait_or_fail in the sensor, text, loop and host integration tests (#21164) 2026-10-19 17:34:00 -10:00
J. Nick Koston 5bccdab01d [remote_base] Add helpers to send and read IR frame bytes bit by bit (#21167) 2026-10-19 17:29:11 -10:00
Jesse Hills 68df9248ed [sx126x][sx127x] Move CONF_RST_PIN to the shared component constants (#21174) 2026-10-20 16:11:27 +13:00
J. Nick Koston 0a501b7ef5 [core] Pass the LOG_PIN prefix to the log straight from flash (#21117) 2026-10-19 20:04:39 -04:00
J. Nick Koston b65395174c [core] Log the build time at boot straight from flash (#21118) 2026-10-19 20:04:05 -04:00
J. Nick Koston da0fed0517 [dfrobot_sen0609][airton] Use Apply selects and switches for hub forwarding entities (#21147) 2026-10-19 14:02:53 -10:00
286 changed files with 2955 additions and 2482 deletions
+3 -2
View File
@@ -115,7 +115,7 @@ Per-topic scripts, each described in the matching `reference/recipes/<component>
(host driver, Zephyr driver probe), `rtsp_server/` (protocol client, raw rate reader, measurement set,
API image grab), `cse7766/` (API observer with ping), `zephyr_coredump/` (reader flow, decode with the
Zephyr tools), `libretiny_prefs/` (rtc preference harness header), `libretiny_crash/` (BK72xx crash probe header and
capture decoder), `api_outgoing/` (wake cycle and Wi-Fi drop timing readers), `homeassistant_light/` (direct bulb driver), `usb_audio/` (URL player).
capture decoder), `api_outgoing/` (wake cycle and Wi-Fi drop timing readers), `ld2450/` (zone number driver), `dfrobot_sen0395/` (API driver and case runner), `homeassistant_light/` (direct bulb driver, held-report case driver), `usb_audio/` (URL player).
## Templates in `$SKILL/templates`
@@ -130,7 +130,8 @@ capture decoder), `api_outgoing/` (wake cycle and Wi-Fi drop timing readers), `h
`bk72xx-rtc-prefs-dut.yaml`, `bk72xx-crash-probe-dut.yaml`, `dut-ap-only-wipe-once-bk72xx.yaml`,
`homeassistant-light-dut.yaml`, `deep-sleep-outgoing-dut.yaml`, `outgoing-wifi-drop-dut.yaml`,
`globals-storage-dut.yaml`, `usb-audio-waveshare-s3-eth-dut.yaml`, `usb-audio-sendspin-s3-eth-dut.yaml`,
`cast-pro-1-sendspin-control.yaml`, `aux-2-preprod-stock-dut.yaml`, `aux-2-preprod-usb-audio-dut.yaml`.
`cast-pro-1-sendspin-control.yaml`, `aux-2-preprod-stock-dut.yaml`, `aux-2-preprod-usb-audio-dut.yaml`,
`dfrobot-sen0395-dut.yaml`.
## When something looks wrong
@@ -9,7 +9,7 @@ One file per component, named after it:
- [`cse7766.md`](cse7766.md): Sonoff S31 parser check over the API
- [`espnow.md`](espnow.md): ESP8266 broadcast receive
- [`hdmi_cec.md`](hdmi_cec.md): two-board wire
- [`ld2450.md`](ld2450.md): polygon zones
- [`ld2450.md`](ld2450.md): polygon zones, zone number round trip through the radar's readback
- [`libretiny.md`](libretiny.md): rtc preference emulation, BK72xx crash report on the next boot
- [`mqtt.md`](mqtt.md): broker bench, QoS, reconnect, crash log over MQTT
- [`nrf52.md`](nrf52.md): dongle board support
@@ -25,5 +25,6 @@ One file per component, named after it:
- [`zephyr_coredump.md`](zephyr_coredump.md): crash and decode on nRF52 boards
- [`zwave_proxy.md`](zwave_proxy.md): frame checksums on a live proxy
- [`globals.md`](globals.md): restoring globals with storage rtc and flash across every reset kind
- [`homeassistant.md`](homeassistant.md): light platform mirrored from a local Home Assistant
- [`homeassistant.md`](homeassistant.md): light platform mirrored from a local Home Assistant, held reports versus later calls
- [`usb_audio.md`](usb_audio.md): USB audio speaker and microphone, the usb_host FIFO split, and Music Assistant playback through sendspin
- [`dfrobot_sen0395.md`](dfrobot_sen0395.md): SEN0395 mmWave command flow, module quirks and overlong lines through its error reply
@@ -0,0 +1,21 @@
# dfrobot_sen0395
## DFRobot SEN0395 mmWave on an ESP32 DevKit V1 (#20906, #20979)
Wiring: V to VIN (5 V, the module wants 3.6 to 5 V), G to GND, sensor TX to GPIO16 (RX2), sensor RX to GPIO17
(TX2), IO2 to GPIO18 (presence output, a gpio binary sensor), I1 unconnected. `templates/dfrobot-sen0395-dut.yaml`
has the hub, both detection binary sensors, the four switches, settings buttons and junk-line buttons.
`scripts/dfrobot_sen0395/ctl.py HOST KEY press:NAME sw:NAME=on|off wait:S` drives it and prints binary sensor and
switch updates; `cases.sh DEV_IP KEY CAPTURE LABEL` runs the whole flow against a running serial capture. Flash by
USB, `serial_capture.py PORT 540 CAPTURE --reset`, then the cases; compare the `Sending command` and `Message:` lines
and the counts of `Done`, `Error`, prompts and frames between builds. The module echoes every command, so the echoed
lines are the bytes the component sent and can be compared between builds as a set.
Module facts: it answers `Done` or `Error` and then the `leapMMW:/>` prompt without a line end; a junk line gets
`<line> is not recognized as a CLI command` (36 chars longer than the line, so 220 and 240 byte junk gives 256 and
276 char replies); input lines of 256 bytes or more make the module assert (`..\User_code\main.c line:232`) and
restart, after which the sensor is stopped; a factory reset restarts the module about 7 s later; `sensorStop` on a
stopped sensor answers `sensor stopped already` then `Error`; a settings press takes 5 to 6 s (stop, change, save,
start). Free heap on the ESP32 is Wi-Fi noise (192 to 198 kB) and says nothing about the component.
Results: https://github.com/esphome/esphome/pull/20979#issuecomment-6279129146 and
https://github.com/esphome/esphome/pull/20906#issuecomment-6279129604
@@ -33,3 +33,15 @@ connected but not yet subscribed is dropped by the api server (`client has not s
fallback to an off report the mirror keeps the unsent brightness; a mirror that never saw its light on since boot has
no levels to restore. Results: https://github.com/esphome/esphome/pull/11354#issuecomment-6253630336 and
https://github.com/esphome/esphome/pull/11354#issuecomment-6254161409
## Held report versus a later call (#21144)
A report that arrives during a transition is held and applied when the transition ends, unless a call came after
it. `scripts/homeassistant_light/heldcase.py DEV_IP KEY BULB_IP same|many [BULB_KEY]` (a key of `-` is plaintext) keeps one API connection to the mirror
device and one to the bulb so the steps land inside a 3 s fade: press "A 50 fade 3s", 0.7 s later set the bulb to
90% directly (the report is held), 1.4 s in press the same fade again (or "A many calls", 16 calls in one loop
pass). Pass: the mirror and the bulb end at the second call's target. Only `same` reproduces the bug: a build
without the fix logs `Applying the report that arrived during the transition` and the bulb stays at 90%. `many`
ends at 100% on both builds because the last call's target differs from the held report, so it is a regression
check only, not a reproduction. Separate button presses are too slow (about 1.5 s each) to land inside the fade.
The mirrors are not API entities, so read their state from the "Log state" button's line. Results: https://github.com/esphome/esphome/pull/21144#issuecomment-6278257695
@@ -5,3 +5,11 @@
To make a target "leave" without anyone moving, set the radar's Zone-1 numbers over the target and
switch the zone type to `Filter` (the count drops to 0 within 0.5 s); set it back to `Disabled` after.
Writing those numbers blocks the ld2450 loop about 50 ms.
## Zone numbers round trip (#21146)
`scripts/ld2450/zonedrive.py HOST KEY(- for plaintext) set:NAME=VALUE sel:NAME=OPTION wait:S list` sets numbers and
selects over the API and prints every LD2450 state update. After each set the hub sends the set zone command, queries
the radar and republishes all twelve coordinates from the answer, so set each coordinate of each zone to a distinct
value and check the readbacks; no target is needed. Readbacks can arrive late over Wi-Fi, so match them by content.
Results: https://github.com/esphome/esphome/pull/21146#issuecomment-6278026922
@@ -0,0 +1,22 @@
#!/bin/sh
# cases.sh DEV_IP KEY CAPTURE LABEL : the SEN0395 flow (settings, switches, junk lines, factory reset, reset) against
# templates/dfrobot-sen0395-dut.yaml, with a serial capture of the device running into CAPTURE
D=$1; K=$2; LOG=$3; LABEL=$4; PY=${PYTHON:-python3}; CTL="$(dirname "$0")/ctl.py"
show() { tail -n +$(($1+1)) "$LOG" | sed 's/\x1b\[[0-9;]*m//g' | grep -a 'dfrobot\|Detected\|\[W\]\|\[E\]\|sen\]' | grep -a -v 'JYBSS\|took a long time' | cut -c1-200; }
step() { n0=$(wc -l < "$LOG"); echo "=== $LABEL: $1 ($(date +%T))"; shift; $PY "$CTL" "$D" "$K" "$@" || { rc=$?; echo "driver failed ($rc), aborting"; exit $rc; }; echo "--- device log:"; show "$n0"; }
frames() { echo "frames: $(tail -n +$(($1+1)) "$LOG" | grep -a -c 'JYBSS,1') present, $(tail -n +$(($1+1)) "$LOG" | grep -a -c 'JYBSS,0') absent"; }
n0=$(wc -l < "$LOG"); echo "=== $LABEL: 20 s of frames ($(date +%T))"; sleep 20; frames "$n0"
step "range 0 to 3 m" "press:Range 0 to 3m" wait:12
step "range 0 to 9 m" "press:Range 0 to 9m" wait:12
step "latency 1 s / 2 s" "press:Latency 1s 2s" wait:12
step "latency 0 / 0" "press:Latency 0 0" wait:12
step "sensitivity 7" "press:Sensitivity 7" wait:12
step "LED off then on" "sw:LED=off" wait:10 "sw:LED=on" wait:10
step "sensor active off then on" "sw:Sensor active=off" wait:10 "sw:Sensor active=on" wait:10
step "presence via UART off then on" "sw:Presence via UART=off" wait:10 "sw:Presence via UART=on" wait:10
step "start after boot off then on" "sw:Start after boot=off" wait:10 "sw:Start after boot=on" wait:10
for n in 100 160 200 220 240 256; do step "junk line of $n bytes" "press:Junk $n" wait:7; done
step "junk 220 then a command" "press:Junk 220" wait:6 "press:Range 0 to 9m" wait:12
step "factory reset" "press:Factory reset" wait:15
step "reset" "press:Reset" wait:15
n0=$(wc -l < "$LOG"); echo "=== $LABEL: 20 s of frames after ($(date +%T))"; sleep 20; frames "$n0"
@@ -0,0 +1,63 @@
"""Drive the SEN0395 test device over the API and print its binary sensor and switch updates.
usage: ctl.py HOST KEY(- for plaintext) OP [OP ...] OP: press:NAME | sw:NAME=on|off | wait:SECONDS
"""
import asyncio
import sys
import time
from aioesphomeapi import (
APIClient,
BinarySensorState,
ButtonInfo,
SwitchInfo,
SwitchState,
)
HOST, KEY = sys.argv[1], sys.argv[2]
OPS = sys.argv[3:]
T0 = time.time()
async def main() -> None:
c = APIClient(HOST, 6053, None, noise_psk=None if KEY == "-" else KEY)
await c.connect(login=True)
ents, _ = await c.list_entities_services()
by_key = {e.key: e for e in ents}
buttons = {e.name: e for e in ents if isinstance(e, ButtonInfo)}
switches = {e.name: e for e in ents if isinstance(e, SwitchInfo)}
def on_state(s) -> None:
e = by_key.get(s.key)
if isinstance(s, BinarySensorState):
print(
f"{time.time() - T0:7.3f} BIN {e.name} = {'ON' if s.state else 'off'}"
)
elif isinstance(s, SwitchState):
print(
f"{time.time() - T0:7.3f} SW {e.name} = {'on' if s.state else 'off'}"
)
c.subscribe_states(on_state)
await asyncio.sleep(1.0)
for op in OPS:
kind, _, rest = op.partition(":")
if kind == "wait":
await asyncio.sleep(float(rest))
elif kind == "press":
if rest not in buttons:
sys.exit(f"no button {rest!r}; have {sorted(buttons)}")
print(f"{time.time() - T0:7.3f} >>> press {rest}")
c.button_command(buttons[rest].key)
elif kind == "sw":
name, _, val = rest.partition("=")
if name not in switches:
sys.exit(f"no switch {name!r}; have {sorted(switches)}")
print(f"{time.time() - T0:7.3f} >>> switch {name} {val}")
c.switch_command(switches[name].key, val == "on")
else:
sys.exit(f"unknown op {op!r}")
await c.disconnect()
asyncio.run(main())
@@ -0,0 +1,71 @@
"""Held-report cases for esphome#21144 with persistent API connections, so the steps land inside the 3 s fade.
usage: heldcase.py DEV_IP KEY BULB_IP same|many [BULB_KEY] (KEY or BULB_KEY of - means plaintext)
t0 press "A 50 fade 3s" on the mirror device (3 s fade to 50%)
t0+0.7 set the bulb directly to 90% (Home Assistant reports it to the mirror during the fade: held)
t0+1.4 same: press "A 50 fade 3s" again (same target as the running fade)
many: press "A many calls" (16 calls in one loop pass, the last one 100% with a 1 s fade)
t0+6.5 read the bulb, press Log state so the mirror prints its state
"""
import asyncio
import sys
import time
from aioesphomeapi import APIClient, ButtonInfo, LightInfo, LightState
DEV, KEY, BULB, CASE = sys.argv[1:5]
BULB_KEY = sys.argv[5] if len(sys.argv) > 5 else "-"
if CASE not in ("same", "many"):
sys.exit(f"case must be same or many, got {CASE!r}")
T0 = time.time()
def stamp() -> str:
return f"{time.time() - T0:6.3f}"
async def main() -> None:
dev = APIClient(DEV, 6053, None, noise_psk=None if KEY == "-" else KEY)
bulb = APIClient(BULB, 6053, None, noise_psk=None if BULB_KEY == "-" else BULB_KEY)
await dev.connect(login=True)
await bulb.connect(login=True)
dents, _ = await dev.list_entities_services()
bents, _ = await bulb.list_entities_services()
buttons = {e.name: e for e in dents if isinstance(e, ButtonInfo)}
blight = next(e for e in bents if isinstance(e, LightInfo))
states = {}
def on_bulb(s) -> None:
if isinstance(s, LightState) and s.key == blight.key:
states["bulb"] = s
print(f"{stamp()} bulb state: on={s.state} bri={s.brightness:.2f}")
bulb.subscribe_states(on_bulb)
await asyncio.sleep(1.0)
print(f"{stamp()} >>> bulb to 100% (precondition)")
bulb.light_command(blight.key, state=True, brightness=1.0)
await asyncio.sleep(4.0)
t = time.time()
print(f"{stamp()} >>> t0 press A 50 fade 3s")
dev.button_command(buttons["A 50 fade 3s"].key)
await asyncio.sleep(0.7 - (time.time() - t))
print(f"{stamp()} >>> bulb direct to 90%")
bulb.light_command(blight.key, state=True, brightness=0.9)
await asyncio.sleep(1.4 - (time.time() - t))
name = "A 50 fade 3s" if CASE == "same" else "A many calls"
print(f"{stamp()} >>> press {name}")
dev.button_command(buttons[name].key)
await asyncio.sleep(6.5 - (time.time() - t))
print(f"{stamp()} >>> press Log state (the mirror's state is in the device log)")
dev.button_command(buttons["Log state"].key)
await asyncio.sleep(1.0)
b = states.get("bulb")
print(
f"{stamp()} RESULT bulb "
+ (f"bri={b.brightness:.2f}" if b is not None else "no state")
)
await dev.disconnect()
await bulb.disconnect()
asyncio.run(main())
@@ -0,0 +1,85 @@
"""Drive LD2450 numbers and selects over the API while printing every LD2450 state update.
usage: zonedrive.py HOST KEY(- for plaintext) OP [OP ...]
OP: set:NAME=VALUE | sel:NAME=OPTION | wait:SECONDS | list
Every state update of an entity whose name contains "LD2450" is printed with a timestamp.
"""
import asyncio
import sys
import time
from aioesphomeapi import (
APIClient,
NumberInfo,
NumberState,
SelectInfo,
SelectState,
SensorInfo,
SensorState,
)
HOST, KEY = sys.argv[1], sys.argv[2]
OPS = sys.argv[3:]
T0 = time.time()
def stamp() -> str:
return f"{time.time() - T0:7.3f}"
async def main() -> None:
c = APIClient(HOST, 6053, None, noise_psk=None if KEY == "-" else KEY)
await c.connect(login=True)
ents, _ = await c.list_entities_services()
by_key = {e.key: e for e in ents}
numbers = {e.name: e for e in ents if isinstance(e, NumberInfo)}
selects = {e.name: e for e in ents if isinstance(e, SelectInfo)}
sensors = {e.name: e for e in ents if isinstance(e, SensorInfo)}
def on_state(s) -> None:
e = by_key.get(s.key)
if e is None or "LD2450" not in e.name:
return
if isinstance(s, NumberState):
print(
f"{stamp()} NUM {e.name} = {s.state:.0f}{' (missing)' if s.missing_state else ''}"
)
elif isinstance(s, SelectState):
print(f"{stamp()} SEL {e.name} = {s.state}")
elif isinstance(s, SensorState) and "Count" in e.name:
print(f"{stamp()} CNT {e.name} = {s.state:.0f}")
c.subscribe_states(on_state)
await asyncio.sleep(1.5)
for op in OPS:
if op == "list":
for n in sorted(numbers):
print("number:", n)
for n in sorted(selects):
print("select:", n, selects[n].options)
for n in sorted(sensors):
if "Count" in n:
print("sensor:", n)
continue
kind, _, rest = op.partition(":")
if kind == "wait":
await asyncio.sleep(float(rest))
continue
name, _, value = rest.partition("=")
if kind == "set":
if name not in numbers:
sys.exit(f"no number named {name!r}; have {sorted(numbers)}")
print(f"{stamp()} >>> set {name} = {value}")
c.number_command(numbers[name].key, float(value))
elif kind == "sel":
if name not in selects:
sys.exit(f"no select named {name!r}; have {sorted(selects)}")
print(f"{stamp()} >>> select {name} = {value}")
c.select_command(selects[name].key, value)
else:
sys.exit(f"unknown op {op!r}")
await asyncio.sleep(0.3)
await c.disconnect()
asyncio.run(main())
@@ -0,0 +1,186 @@
# DFRobot SEN0395 on an ESP32 DevKit V1: V to VIN (5 V), G to GND, sensor TX to GPIO16 (RX2), sensor RX to GPIO17 (TX2),
# IO2 to GPIO18, I1 unconnected. Settings buttons, the four switches and junk-line buttons whose echo and
# "is not recognized" reply come back as lines of known length (220 and 240 bytes give replies over 252 chars).
esp32:
board: esp32dev
framework:
type: esp-idf
logger:
level: VERBOSE
logs:
api: DEBUG
api.connection: DEBUG
api.service: DEBUG
wifi: DEBUG
mdns: DEBUG
scheduler: DEBUG
component: DEBUG
esp32: DEBUG
uart: DEBUG
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
api:
encryption:
key: !secret api_key
reboot_timeout: 0s
ota:
- platform: esphome
uart:
id: mmwave_uart
tx_pin: GPIO17
rx_pin: GPIO16
baud_rate: 115200
dfrobot_sen0395:
id: mmwave
uart_id: mmwave_uart
binary_sensor:
- platform: dfrobot_sen0395
dfrobot_sen0395_id: mmwave
name: Detected via UART
id: uart_detected
- platform: gpio
name: Detected via GPIO
device_class: motion
pin:
number: GPIO18
mode: INPUT_PULLDOWN
switch:
- platform: dfrobot_sen0395
dfrobot_sen0395_id: mmwave
type: sensor_active
name: Sensor active
- platform: dfrobot_sen0395
dfrobot_sen0395_id: mmwave
type: presence_via_uart
name: Presence via UART
- platform: dfrobot_sen0395
dfrobot_sen0395_id: mmwave
type: turn_on_led
name: LED
- platform: dfrobot_sen0395
dfrobot_sen0395_id: mmwave
type: start_after_boot
name: Start after boot
button:
- platform: template
name: Range 0 to 3m
on_press:
- dfrobot_sen0395.settings:
id: mmwave
detection_segments:
- [0cm, 3m]
- platform: template
name: Range 0 to 9m
on_press:
- dfrobot_sen0395.settings:
id: mmwave
detection_segments:
- [0cm, 9m]
- platform: template
name: Latency 1s 2s
on_press:
- dfrobot_sen0395.settings:
id: mmwave
output_latency:
delay_after_detect: 1s
delay_after_disappear: 2s
- platform: template
name: Latency 0 0
on_press:
- dfrobot_sen0395.settings:
id: mmwave
output_latency:
delay_after_detect: 0s
delay_after_disappear: 0s
- platform: template
name: Sensitivity 7
on_press:
- dfrobot_sen0395.settings:
id: mmwave
sensitivity: 7
- platform: template
name: Factory reset
on_press:
- dfrobot_sen0395.settings:
id: mmwave
factory_reset: true
- platform: template
name: Reset
on_press:
- dfrobot_sen0395.reset:
id: mmwave
- platform: template
name: Junk 100
on_press:
- uart.write:
id: mmwave_uart
data: !lambda |-
std::vector<uint8_t> v(100, 'x');
v.push_back('\r');
v.push_back('\n');
return v;
- platform: template
name: Junk 160
on_press:
- uart.write:
id: mmwave_uart
data: !lambda |-
std::vector<uint8_t> v(160, 'x');
v.push_back('\r');
v.push_back('\n');
return v;
- platform: template
name: Junk 200
on_press:
- uart.write:
id: mmwave_uart
data: !lambda |-
std::vector<uint8_t> v(200, 'x');
v.push_back('\r');
v.push_back('\n');
return v;
- platform: template
name: Junk 220
on_press:
- uart.write:
id: mmwave_uart
data: !lambda |-
std::vector<uint8_t> v(220, 'x');
v.push_back('\r');
v.push_back('\n');
return v;
- platform: template
name: Junk 240
on_press:
- uart.write:
id: mmwave_uart
data: !lambda |-
std::vector<uint8_t> v(240, 'x');
v.push_back('\r');
v.push_back('\n');
return v;
- platform: template
name: Junk 256
on_press:
- uart.write:
id: mmwave_uart
data: !lambda |-
std::vector<uint8_t> v(256, 'x');
v.push_back('\r');
v.push_back('\n');
return v;
interval:
- interval: 30s
then:
- lambda: 'ESP_LOGI("sen", "alive uart_detected=%d heap=%u", id(uart_detected).state, (unsigned) esp_get_free_heap_size());'
@@ -124,6 +124,20 @@ button:
name: B off
on_press:
- light.turn_off: bulb_b
- platform: template
name: A 50 fade 3s
on_press:
- light.turn_on:
id: bulb_a
brightness: 50%
transition_length: 3s
- platform: template
name: A many calls
on_press:
- lambda: |-
for (int i = 0; i < 16; i++) {
id(bulb_a).make_call().set_brightness(i == 15 ? 1.0f : 0.6f).set_transition_length(1000).perform();
}
- platform: template
name: Log state
on_press:
+28 -4
View File
@@ -80,13 +80,15 @@ file does, and it is the authority when they disagree. The most useful starting
```python
# Bad - looks up CONF_BLAH twice
if CONF_BLAH in config:
cg.add(var.set_blah(config[CONF_BLAH]))
cg.add(var.set_blah_ms(config[CONF_BLAH].total_milliseconds))
# Good - single lookup, value bound inline
if (blah := config.get(CONF_BLAH)) is not None:
cg.add(var.set_blah(blah))
cg.add(var.set_blah_ms(blah.total_milliseconds))
```
The same applies to `while` loops and comprehensions where it avoids recomputing a value. Don't contort code to use it — reach for `:=` only when it genuinely cuts repetition or an extra assignment line.
When the setter takes the value unchanged, use `cg.add_optional(var.set_blah, config.get(CONF_BLAH))`
instead; it is not awaited, and it skips only `None`, so `0` and `False` are still set.
* **C++ Field Visibility:**
* **Prefer `protected`:** Use `protected` for most class fields to enable extensibility and testing. Fields should be `lower_snake_case_with_trailing_underscore_`.
@@ -208,8 +210,14 @@ file does, and it is the authority when they disagree. The most useful starting
* **Fixed sets of names** (status words, model names): a `PROGMEM_STRING_TABLE` (`esphome/core/progmem.h`),
logged with `get_log_str` and `LOG_STR_ARG`, copied out with `ESPHOME_strncpy_P` before `publish_state`.
A `dump_config` helper that picks a name returns `const LogString *` from `LOG_STR(...)`.
* **CRCs and bit tricks** come from `esphome/core/helpers.h` (`crc8`, `crc16`, `reverse_bits`, `encode_uint16`);
pick the helper's polynomial and bit-order arguments instead of writing a CRC loop.
* **CRCs and bit tricks** come from `esphome/core/helpers.h` (`crc8`, `crc16`, `xor_bytes`, `sum16_bytes`,
`reverse_bits`, `encode_uint16`, `encode_value_le`, `sign_extend<BITS>`, `bcd_to_uint8`); pick the helper's
arguments instead of writing a loop. Epson style 7-byte RTC calendars (one-hot weekday) use
`time::decode_bcd_calendar`.
* **Bus helpers:** UART drains with `read_available(on_byte)` and `discard_available()`, text lines with
`LineReader<N>`; I2C has `update_register`, `write_registers` (PROGMEM `I2CRegValue` table), `check_chip_id`
and `read_bytes_16_le`. `if (!this->status_comm_result(ok)) return;` sets or clears the warning.
A child sensor that skips repeated values is `SUB_SENSOR_WITH_DEDUP(name, type)`.
* **An unset float is `NAN`**, not `optional<float>`; the optional costs 4 to 8 bytes of padding per member.
* **`PollingComponent` first runs `update()`** after a random share of the interval; call `this->update()` at the
end of `setup()` when the entities need a state at boot.
@@ -429,6 +437,22 @@ file does, and it is the authority when they disagree. The most useful starting
and `await cg.add_variable(var.set_bus, config[CONF_BUS_ID])`; both skip a `None` value, so pass
`config.get(KEY)` for an optional key, and return what they created. Use `cg.register_parented` for `set_parent`.
- **Hub forwarding entities need no C++ class:** a button, switch, number or select that only calls its hub
uses `button.ApplyButton` (`switch.ApplySwitch`, `number.ApplyNumber`, `select.ApplySelect`) in its schema
and `apply=` on the `sub_*` call. The body is a call on the hub with `entity` and `state`, `value` or
`index`/`option` in scope; on a switch, number or select `publish="before"` or `"after"` publishes the state.
```python
switches = switch.sub_switches(config, parent=hub)
await switches(CONF_BLUETOOTH, None, apply="set_bluetooth(state)", publish="after")
```
- **Other codegen helpers:** `uart.new_uart_component(config, *ctor_args)` (and `i2c.new_i2c_component`,
`spi.new_spi_component`, `modbus.new_modbus_client_component`, `ble_client.new_ble_node_component`)
constructs, registers and attaches in one call; `display.add_writer(var, config)` sets the lambda;
`MyEnum.options("on", prefix="MY_ENUM_")` builds an enum dict; `button.restart_button_schema(cls)` (and
`factory_reset_`, `query_`) are button presets; `fv.get_declaring_config(id)` finds a hub's config in
final validation.
* **Automations (Triggers, Actions, Conditions):**
Automations have three building blocks: **Triggers** (fire when something happens), **Actions** (do something), and **Conditions** (check if something is true).
+2
View File
@@ -601,6 +601,8 @@ esphome/components/ssd1331_base/* @kbx81
esphome/components/ssd1331_spi/* @kbx81
esphome/components/ssd1351_base/* @kbx81
esphome/components/ssd1351_spi/* @kbx81
esphome/components/st25r/* @JohnMcLear
esphome/components/st25r_spi/* @JohnMcLear
esphome/components/st7123/* @miniskipper
esphome/components/st7305/* @clydebarrow
esphome/components/st7567_base/* @latonita
+1 -1
View File
@@ -22,7 +22,7 @@ RUN \
-r /requirements.txt
# Install the ESPHome Device Builder dashboard.
RUN uv pip install --no-cache-dir esphome-device-builder==1.24.0
RUN uv pip install --no-cache-dir esphome-device-builder==1.25.0
RUN \
platformio settings set enable_telemetry No \
+1
View File
@@ -53,6 +53,7 @@ from esphome.cpp_generator import ( # noqa: F401
with_local_variable,
)
from esphome.cpp_helpers import ( # noqa: F401
add_optional,
add_pin,
add_variable,
build_registry_entry,
+5 -17
View File
@@ -136,13 +136,7 @@ void AirtonClimate::transmit_state() {
data->set_carrier_frequency(AIRTON_IR_FREQUENCY);
data->mark(AIRTON_HEADER_MARK);
data->space(AIRTON_HEADER_SPACE);
for (uint8_t payload_byte : remote_state) {
for (uint8_t payload_bit_cursor = 0; payload_bit_cursor < 8; payload_bit_cursor++) {
data->mark(AIRTON_BIT_MARK);
bool bit = payload_byte & (1 << payload_bit_cursor);
data->space(bit ? AIRTON_ONE_SPACE : AIRTON_ZERO_SPACE);
}
}
data->item_bytes<AIRTON_BIT_TIMINGS>(remote_state, AIRTON_STATE_FRAME_SIZE);
data->mark(AIRTON_BIT_MARK);
data->space(AIRTON_MESSAGE_SPACE);
@@ -284,20 +278,14 @@ void AirtonClimate::parse_state_frame_(const uint8_t *frame) {
}
bool AirtonClimate::on_receive(remote_base::RemoteReceiveData data) {
uint8_t remote_state[AIRTON_STATE_FRAME_SIZE] = {};
uint8_t remote_state[AIRTON_STATE_FRAME_SIZE];
if (!data.expect_item(AIRTON_HEADER_MARK, AIRTON_HEADER_SPACE)) {
ESP_LOGV(TAG, "Wrong header encoding detected!");
return false;
}
for (int i = 0; i < AIRTON_STATE_FRAME_SIZE; i++) {
for (int j = 0; j < 8; j++) {
if (data.expect_item(AIRTON_BIT_MARK, AIRTON_ONE_SPACE)) {
remote_state[i] |= 1 << j;
} else if (!data.expect_item(AIRTON_BIT_MARK, AIRTON_ZERO_SPACE)) {
ESP_LOGV(TAG, "Wrong modulation encoding for: Byte %d, bit %d", i, j);
return false;
}
}
if (!data.expect_bytes<AIRTON_BIT_TIMINGS>(remote_state, AIRTON_STATE_FRAME_SIZE)) {
ESP_LOGV(TAG, "Wrong modulation encoding");
return false;
}
if (remote_state[0] != AIRTON_HEADER_BYTE0 || remote_state[1] != AIRTON_HEADER_BYTE1) {
+2 -2
View File
@@ -36,8 +36,8 @@ static constexpr uint32_t AIRTON_IR_FREQUENCY = 38000;
static constexpr uint32_t AIRTON_HEADER_MARK = 6630;
static constexpr uint32_t AIRTON_HEADER_SPACE = 3350;
static constexpr uint32_t AIRTON_BIT_MARK = 400;
static constexpr uint32_t AIRTON_ONE_SPACE = 1260;
static constexpr uint32_t AIRTON_ZERO_SPACE = 430;
static constexpr remote_base::BitTimings AIRTON_BIT_TIMINGS{
.bit_mark = AIRTON_BIT_MARK, .one_space = 1260, .zero_space = 430};
static constexpr uint32_t AIRTON_MESSAGE_SPACE = 100000;
static constexpr uint8_t AIRTON_STATE_FRAME_SIZE = 7;
+6 -5
View File
@@ -8,19 +8,18 @@ from ..climate import CONF_AIRTON_ID, CONF_SLEEP_MODE, AirtonClimate, airton_ns
CODEOWNERS = ["@Procsiab"]
AirtonSwitch = airton_ns.class_("AirtonSwitch", switch.Switch)
AirtonSetting = airton_ns.enum("AirtonSetting", is_class=True)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_AIRTON_ID): cv.use_id(AirtonClimate),
cv.Optional(CONF_DISPLAY): switch.switch_schema(
AirtonSwitch,
switch.ApplySwitch,
entity_category=ENTITY_CATEGORY_CONFIG,
default_restore_mode="DISABLED",
),
cv.Optional(CONF_SLEEP_MODE): switch.switch_schema(
AirtonSwitch,
switch.ApplySwitch,
entity_category=ENTITY_CATEGORY_CONFIG,
default_restore_mode="DISABLED",
),
@@ -32,10 +31,12 @@ async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_AIRTON_ID])
switches = switch.sub_switches(config, parent=hub)
await switches(
CONF_DISPLAY, hub.set_display_switch, AirtonSetting.AIRTON_SETTING_DISPLAY
CONF_DISPLAY,
hub.set_display_switch,
apply=f"set_setting({AirtonSetting.AIRTON_SETTING_DISPLAY}, state)",
)
await switches(
CONF_SLEEP_MODE,
hub.set_sleep_mode_switch,
AirtonSetting.AIRTON_SETTING_SLEEP_MODE,
apply=f"set_setting({AirtonSetting.AIRTON_SETTING_SLEEP_MODE}, state)",
)
@@ -1,19 +0,0 @@
#pragma once
#include "esphome/components/switch/switch.h"
#include "../airton.h"
namespace esphome::airton {
// One of the unit's on/off settings; the parent sends the state and publishes it back
class AirtonSwitch : public switch_::Switch, public Parented<AirtonClimate> {
public:
explicit AirtonSwitch(AirtonSetting setting) : setting_(setting) {}
protected:
void write_state(bool state) override { this->parent_->set_setting(this->setting_, state); }
AirtonSetting setting_;
};
} // namespace esphome::airton
+7 -24
View File
@@ -8,8 +8,8 @@ ESPHOME_LOG_TAG(TAG, "ballu.climate");
const uint16_t BALLU_HEADER_MARK = 9000;
const uint16_t BALLU_HEADER_SPACE = 4500;
const uint16_t BALLU_BIT_MARK = 575;
const uint16_t BALLU_ONE_SPACE = 1675;
const uint16_t BALLU_ZERO_SPACE = 550;
static constexpr remote_base::BitTimings BALLU_BIT_TIMINGS{
.bit_mark = BALLU_BIT_MARK, .one_space = 1675, .zero_space = 550};
const uint32_t BALLU_CARRIER_FREQUENCY = 38000;
@@ -104,13 +104,7 @@ void BalluClimate::transmit_state() {
data->mark(BALLU_HEADER_MARK);
data->space(BALLU_HEADER_SPACE);
// Data
for (uint8_t i : remote_state) {
for (uint8_t j = 0; j < 8; j++) {
data->mark(BALLU_BIT_MARK);
bool bit = i & (1 << j);
data->space(bit ? BALLU_ONE_SPACE : BALLU_ZERO_SPACE);
}
}
data->item_bytes<BALLU_BIT_TIMINGS>(remote_state, BALLU_STATE_LENGTH);
// Footer
data->mark(BALLU_BIT_MARK);
@@ -124,21 +118,10 @@ bool BalluClimate::on_receive(remote_base::RemoteReceiveData data) {
return false;
}
uint8_t remote_state[BALLU_STATE_LENGTH] = {0};
// Read all bytes.
for (int i = 0; i < BALLU_STATE_LENGTH; i++) {
// Read bit
for (int j = 0; j < 8; j++) {
if (data.expect_item(BALLU_BIT_MARK, BALLU_ONE_SPACE)) {
remote_state[i] |= 1 << j;
} else if (!data.expect_item(BALLU_BIT_MARK, BALLU_ZERO_SPACE)) {
ESP_LOGV(TAG, "Byte %d bit %d fail", i, j);
return false;
}
}
ESP_LOGVV(TAG, "Byte %d %02X", i, remote_state[i]);
uint8_t remote_state[BALLU_STATE_LENGTH];
if (!data.expect_bytes<BALLU_BIT_TIMINGS>(remote_state, BALLU_STATE_LENGTH)) {
ESP_LOGV(TAG, "Data fail");
return false;
}
// Validate footer
if (!data.expect_mark(BALLU_BIT_MARK)) {
+1 -2
View File
@@ -43,5 +43,4 @@ async def register_bedjet_child(var: MockObj, config: ConfigType) -> None:
async def to_code(config: ConfigType) -> None:
var = await ble_client.new_ble_node_component(config)
await cg.add_variable(var.set_time_id, config.get(CONF_TIME_ID))
if (receive_timeout := config.get(CONF_RECEIVE_TIMEOUT)) is not None:
cg.add(var.set_status_timeout(receive_timeout))
cg.add_optional(var.set_status_timeout, config.get(CONF_RECEIVE_TIMEOUT))
@@ -111,8 +111,7 @@ async def to_code(config: ConfigType) -> None:
cg.add(var.set_num_leds(config[CONF_NUM_LEDS]))
cg.add(var.set_pin(config[CONF_PIN]))
if CONF_MAX_REFRESH_RATE in config:
cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE]))
cg.add_optional(var.set_max_refresh_rate, config.get(CONF_MAX_REFRESH_RATE))
chipset = CHIPSETS[config[CONF_CHIPSET]]
cg.add(
+1
View File
@@ -122,6 +122,7 @@ float BL0940::calculate_energy_reference_() {
float BL0940::calculate_calibration_value_(float state) { return (100 + state) / 100; }
void BL0940::reset_calibration() {
ESP_LOGI(TAG, "Resetting calibration defaults...");
#ifdef USE_NUMBER
if (this->current_calibration_number_ != nullptr && this->current_cal_ != 1) {
this->current_calibration_number_->make_call().set_value(0).perform();
+9 -18
View File
@@ -1,28 +1,19 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import ENTITY_CATEGORY_CONFIG, ICON_RESTART
from esphome.types import ConfigType
from .. import CONF_BL0940_ID, bl0940_ns
from .. import CONF_BL0940_ID
from ..sensor import BL0940
CalibrationResetButton = bl0940_ns.class_(
"CalibrationResetButton", button.Button, cg.Component
)
CONFIG_SCHEMA = cv.All(
button.button_schema(
CalibrationResetButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART,
)
.extend({cv.GenerateID(CONF_BL0940_ID): cv.use_id(BL0940)})
.extend(cv.COMPONENT_SCHEMA)
)
CONFIG_SCHEMA = button.button_schema(
button.ApplyButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART,
).extend({cv.GenerateID(CONF_BL0940_ID): cv.use_id(BL0940)})
async def to_code(config: ConfigType) -> None:
var = await button.new_button(config)
await cg.register_component(var, config)
await cg.register_parented(var, config[CONF_BL0940_ID])
await button.sub_buttons(config, parent=config[CONF_BL0940_ID]).create(
config, None, apply="reset_calibration()"
)
@@ -1,18 +0,0 @@
#include "calibration_reset_button.h"
#include "../bl0940.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome::bl0940 {
ESPHOME_LOG_TAG(TAG, "bl0940.button.calibration_reset");
void CalibrationResetButton::dump_config() { LOG_BUTTON("", "Calibration Reset Button", this); }
void CalibrationResetButton::press_action() {
ESP_LOGI(TAG, "Resetting calibration defaults...");
this->parent_->reset_calibration();
}
} // namespace esphome::bl0940
@@ -1,17 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/button/button.h"
namespace esphome::bl0940 {
class BL0940; // Forward declaration of BL0940 class
class CalibrationResetButton final : public button::Button, public Component, public Parented<BL0940> {
public:
void dump_config() override;
void press_action() override;
};
} // namespace esphome::bl0940
@@ -83,8 +83,6 @@ async def to_code(config: ConfigType) -> None:
ibeacon_uuid = ble_device_base.as_reversed_hex_array(ibeacon_uuid)
cg.add(var.set_ibeacon_uuid(ibeacon_uuid))
if (ibeacon_major := config.get(CONF_IBEACON_MAJOR)) is not None:
cg.add(var.set_ibeacon_major(ibeacon_major))
cg.add_optional(var.set_ibeacon_major, config.get(CONF_IBEACON_MAJOR))
if (ibeacon_minor := config.get(CONF_IBEACON_MINOR)) is not None:
cg.add(var.set_ibeacon_minor(ibeacon_minor))
cg.add_optional(var.set_ibeacon_minor, config.get(CONF_IBEACON_MINOR))
+2 -4
View File
@@ -79,8 +79,6 @@ async def to_code(config: ConfigType) -> None:
ibeacon_uuid = ble_device_base.as_reversed_hex_array(ibeacon_uuid)
cg.add(var.set_ibeacon_uuid(ibeacon_uuid))
if (ibeacon_major := config.get(CONF_IBEACON_MAJOR)) is not None:
cg.add(var.set_ibeacon_major(ibeacon_major))
cg.add_optional(var.set_ibeacon_major, config.get(CONF_IBEACON_MAJOR))
if (ibeacon_minor := config.get(CONF_IBEACON_MINOR)) is not None:
cg.add(var.set_ibeacon_minor(ibeacon_minor))
cg.add_optional(var.set_ibeacon_minor, config.get(CONF_IBEACON_MINOR))
+1 -2
View File
@@ -69,8 +69,7 @@ async def to_code(config: ConfigType) -> None:
text_sensors = text_sensor.sub_text_sensors(config)
await text_sensors(CONF_VERSION, var.set_version)
if (baseline := config.get(CONF_BASELINE)) is not None:
cg.add(var.set_baseline(baseline))
cg.add_optional(var.set_baseline, config.get(CONF_BASELINE))
await cg.add_variable(var.set_temperature, config.get(CONF_TEMPERATURE))
await cg.add_variable(var.set_humidity, config.get(CONF_HUMIDITY))
+1
View File
@@ -65,6 +65,7 @@ CONF_REMOTE_ID = "remote_id"
CONF_REQUEST_HEADERS = "request_headers"
CONF_ROLE = "role"
CONF_ROWS = "rows"
CONF_RST_PIN = "rst_pin"
CONF_SCAN_PARAMETERS = "scan_parameters"
CONF_SERIAL_NUMBER = "serial_number"
CONF_SHA256 = "sha256"
+1 -2
View File
@@ -117,8 +117,7 @@ async def to_code(config: ConfigType) -> None:
)
cg.add(var.set_obstacle_rollback(config[CONF_OBSTACLE_ROLLBACK]))
if (max_duration := config.get(CONF_MAX_DURATION)) is not None:
cg.add(var.set_max_duration(max_duration))
cg.add_optional(var.set_max_duration, config.get(CONF_MAX_DURATION))
cg.add(var.set_malfunction_detection(config[CONF_MALFUNCTION_DETECTION]))
if malfunction_action := config.get(CONF_MALFUNCTION_ACTION):
await automation.build_automation(
+24 -56
View File
@@ -9,6 +9,14 @@ ESPHOME_LOG_TAG(TAG, "daikin.climate");
static constexpr uint32_t BOOST_TIMEOUT_ID = 0;
static constexpr uint8_t DAIKIN_HEADER_BYTE0 = 0x11;
static constexpr uint8_t DAIKIN_HEADER_BYTE1 = 0xDA;
static constexpr uint8_t DAIKIN_HEADER_BYTE2 = 0x27;
static constexpr uint8_t DAIKIN_HEADER_BYTE3 = 0x00;
static constexpr uint8_t DAIKIN_FRAME_TYPE_INDEX = 4;
static constexpr uint8_t DAIKIN_FRAME_HEADER_SIZE = DAIKIN_FRAME_TYPE_INDEX + 1;
static constexpr uint8_t DAIKIN_STATE_FRAME_OFFSET = 2 * DAIKIN_COMFORT_FRAME_SIZE;
void DaikinClimate::setup() {
climate_ir::ClimateIR::setup();
// A boost preset restored from flash gets its timer too
@@ -42,37 +50,19 @@ void DaikinClimate::transmit_state() {
data->mark(DAIKIN_HEADER_MARK);
data->space(DAIKIN_HEADER_SPACE);
for (int i = 0; i < 8; i++) {
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
data->mark(DAIKIN_BIT_MARK);
bool bit = remote_state[i] & mask;
data->space(bit ? DAIKIN_ONE_SPACE : DAIKIN_ZERO_SPACE);
}
}
data->item_bytes<DAIKIN_BIT_TIMINGS>(remote_state, DAIKIN_COMFORT_FRAME_SIZE);
data->mark(DAIKIN_BIT_MARK);
data->space(DAIKIN_MESSAGE_SPACE);
data->mark(DAIKIN_HEADER_MARK);
data->space(DAIKIN_HEADER_SPACE);
for (int i = 8; i < 16; i++) {
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
data->mark(DAIKIN_BIT_MARK);
bool bit = remote_state[i] & mask;
data->space(bit ? DAIKIN_ONE_SPACE : DAIKIN_ZERO_SPACE);
}
}
data->item_bytes<DAIKIN_BIT_TIMINGS>(remote_state + DAIKIN_COMFORT_FRAME_SIZE, DAIKIN_COMFORT_FRAME_SIZE);
data->mark(DAIKIN_BIT_MARK);
data->space(DAIKIN_MESSAGE_SPACE);
data->mark(DAIKIN_HEADER_MARK);
data->space(DAIKIN_HEADER_SPACE);
for (int i = 16; i < 35; i++) {
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
data->mark(DAIKIN_BIT_MARK);
bool bit = remote_state[i] & mask;
data->space(bit ? DAIKIN_ONE_SPACE : DAIKIN_ZERO_SPACE);
}
}
data->item_bytes<DAIKIN_BIT_TIMINGS>(remote_state + DAIKIN_STATE_FRAME_OFFSET, DAIKIN_STATE_FRAME_SIZE);
data->mark(DAIKIN_BIT_MARK);
data->space(0);
@@ -284,46 +274,24 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
}
bool DaikinClimate::on_receive(remote_base::RemoteReceiveData data) {
uint8_t frame[DAIKIN_STATE_FRAME_SIZE] = {};
uint8_t frame[DAIKIN_STATE_FRAME_SIZE];
if (!data.expect_item(DAIKIN_HEADER_MARK, DAIKIN_HEADER_SPACE)) {
return false;
}
// Frame header, then the frame type that sets the frame size
if (!data.expect_bytes<DAIKIN_BIT_TIMINGS>(frame, DAIKIN_FRAME_HEADER_SIZE) || frame[0] != DAIKIN_HEADER_BYTE0 ||
frame[1] != DAIKIN_HEADER_BYTE1 || frame[2] != DAIKIN_HEADER_BYTE2 || frame[3] != DAIKIN_HEADER_BYTE3) {
return false;
}
uint8_t frame_size = DAIKIN_STATE_FRAME_SIZE;
for (uint8_t pos = 0; pos < frame_size; pos++) {
uint8_t byte = 0;
for (int8_t bit = 0; bit < 8; bit++) {
if (data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE)) {
byte |= 1 << bit;
} else if (!data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE)) {
return false;
}
}
frame[pos] = byte;
if (pos == 0) {
// frame header
if (byte != 0x11)
return false;
} else if (pos == 1) {
// frame header
if (byte != 0xDA)
return false;
} else if (pos == 2) {
// frame header
if (byte != 0x27)
return false;
} else if (pos == 3) { // NOLINT(bugprone-branch-clone)
// frame header
if (byte != 0x00)
return false;
} else if (pos == 4) {
// frame type
if (byte == DAIKIN_FRAME_TYPE_COMFORT) {
frame_size = DAIKIN_COMFORT_FRAME_SIZE;
} else if (byte != DAIKIN_FRAME_TYPE_STATE) {
return false;
}
}
if (frame[DAIKIN_FRAME_TYPE_INDEX] == DAIKIN_FRAME_TYPE_COMFORT) {
frame_size = DAIKIN_COMFORT_FRAME_SIZE;
} else if (frame[DAIKIN_FRAME_TYPE_INDEX] != DAIKIN_FRAME_TYPE_STATE) {
return false;
}
if (!data.expect_bytes<DAIKIN_BIT_TIMINGS>(frame + DAIKIN_FRAME_HEADER_SIZE, frame_size - DAIKIN_FRAME_HEADER_SIZE)) {
return false;
}
if (frame[frame_size - 1] != sum_bytes(frame, frame_size - 1)) {
return false;
+2
View File
@@ -45,6 +45,8 @@ const uint32_t DAIKIN_HEADER_SPACE = 1760;
const uint32_t DAIKIN_BIT_MARK = 520;
const uint32_t DAIKIN_ONE_SPACE = 1370;
const uint32_t DAIKIN_ZERO_SPACE = 360;
static constexpr remote_base::BitTimings DAIKIN_BIT_TIMINGS{
.bit_mark = DAIKIN_BIT_MARK, .one_space = DAIKIN_ONE_SPACE, .zero_space = DAIKIN_ZERO_SPACE};
const uint32_t DAIKIN_MESSAGE_SPACE = 32300;
// Frame types (byte 4) and sizes
+8 -31
View File
@@ -39,13 +39,7 @@ void DaikinArcClimate::transmit_query_() {
data->mark(DAIKIN_HEADER_MARK);
data->space(DAIKIN_HEADER_SPACE);
for (uint8_t i : remote_header) {
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
data->mark(DAIKIN_BIT_MARK);
bool bit = i & mask;
data->space(bit ? DAIKIN_ONE_SPACE : DAIKIN_ZERO_SPACE);
}
}
data->item_bytes<DAIKIN_BIT_TIMINGS>(remote_header, sizeof(remote_header));
data->mark(DAIKIN_BIT_MARK);
data->space(0);
@@ -119,26 +113,14 @@ void DaikinArcClimate::transmit_state() {
data->mark(DAIKIN_HEADER_MARK);
data->space(DAIKIN_HEADER_SPACE);
for (uint8_t i : remote_header) {
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
data->mark(DAIKIN_BIT_MARK);
bool bit = i & mask;
data->space(bit ? DAIKIN_ONE_SPACE : DAIKIN_ZERO_SPACE);
}
}
data->item_bytes<DAIKIN_BIT_TIMINGS>(remote_header, sizeof(remote_header));
data->mark(DAIKIN_BIT_MARK);
data->space(DAIKIN_MESSAGE_SPACE);
data->mark(DAIKIN_HEADER_MARK);
data->space(DAIKIN_HEADER_SPACE);
for (uint8_t i : remote_state) {
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
data->mark(DAIKIN_BIT_MARK);
bool bit = i & mask;
data->space(bit ? DAIKIN_ONE_SPACE : DAIKIN_ZERO_SPACE);
}
}
data->item_bytes<DAIKIN_BIT_TIMINGS>(remote_state, sizeof(remote_state));
data->mark(DAIKIN_BIT_MARK);
data->space(0);
@@ -339,7 +321,7 @@ bool DaikinArcClimate::parse_state_frame_(const uint8_t frame[]) {
}
bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) {
uint8_t state_frame[DAIKIN_STATE_FRAME_SIZE] = {};
uint8_t state_frame[DAIKIN_STATE_FRAME_SIZE];
bool valid_daikin_frame = false;
if (data.expect_item(DAIKIN_HEADER_MARK, DAIKIN_HEADER_SPACE)) {
@@ -418,16 +400,11 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) {
}
for (uint8_t pos = 0; pos < DAIKIN_STATE_FRAME_SIZE; pos++) {
uint8_t byte = 0;
for (int8_t bit = 0; bit < 8; bit++) {
if (data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE)) {
byte |= 1 << bit;
} else if (!data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE)) {
ESP_LOGI(TAG, "non daikin_arc expect item pos: %d", pos);
return false;
}
if (!data.expect_bytes<DAIKIN_BIT_TIMINGS>(&state_frame[pos], 1)) {
ESP_LOGI(TAG, "non daikin_arc expect item pos: %d", pos);
return false;
}
state_frame[pos] = byte;
const uint8_t byte = state_frame[pos];
if (pos == 0) {
// frame header
if (byte != 0x11) {
@@ -36,6 +36,8 @@ const uint32_t DAIKIN_HEADER_SPACE = 1760;
const uint32_t DAIKIN_BIT_MARK = 400;
const uint32_t DAIKIN_ONE_SPACE = 1300;
const uint32_t DAIKIN_ZERO_SPACE = 480;
static constexpr remote_base::BitTimings DAIKIN_BIT_TIMINGS{
.bit_mark = DAIKIN_BIT_MARK, .one_space = DAIKIN_ONE_SPACE, .zero_space = DAIKIN_ZERO_SPACE};
const uint32_t DAIKIN_MESSAGE_SPACE = 35000;
const uint8_t DAIKIN_DBG_TOLERANCE = 25;
+16 -46
View File
@@ -6,6 +6,12 @@ namespace esphome::daikin_brc {
ESPHOME_LOG_TAG(TAG, "daikin_brc.climate");
static constexpr uint8_t DAIKIN_BRC_HEADER_BYTE0 = 0x11;
static constexpr uint8_t DAIKIN_BRC_HEADER_BYTE1 = 0xDA;
static constexpr uint8_t DAIKIN_BRC_HEADER_BYTE2 = 0x17;
static constexpr uint8_t DAIKIN_BRC_HEADER_BYTE3 = 0x18;
static constexpr uint8_t DAIKIN_BRC_FRAME_TYPE_STATE = 0x00;
void DaikinBrcClimate::control(const climate::ClimateCall &call) {
this->mode_button_ = 0x00;
if (call.get_mode().has_value()) {
@@ -42,25 +48,14 @@ void DaikinBrcClimate::transmit_state() {
data->mark(DAIKIN_BRC_HEADER_MARK);
data->space(DAIKIN_BRC_HEADER_SPACE);
for (int i = 0; i < DAIKIN_BRC_PREAMBLE_SIZE; i++) {
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
data->mark(DAIKIN_BRC_BIT_MARK);
bool bit = remote_state[i] & mask;
data->space(bit ? DAIKIN_BRC_ONE_SPACE : DAIKIN_BRC_ZERO_SPACE);
}
}
data->item_bytes<DAIKIN_BRC_BIT_TIMINGS>(remote_state, DAIKIN_BRC_PREAMBLE_SIZE);
data->mark(DAIKIN_BRC_BIT_MARK);
data->space(DAIKIN_BRC_MESSAGE_SPACE);
data->mark(DAIKIN_BRC_HEADER_MARK);
data->space(DAIKIN_BRC_HEADER_SPACE);
for (int i = DAIKIN_BRC_PREAMBLE_SIZE; i < DAIKIN_BRC_TRANSMIT_FRAME_SIZE; i++) {
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
data->mark(DAIKIN_BRC_BIT_MARK);
bool bit = remote_state[i] & mask;
data->space(bit ? DAIKIN_BRC_ONE_SPACE : DAIKIN_BRC_ZERO_SPACE);
}
}
data->item_bytes<DAIKIN_BRC_BIT_TIMINGS>(remote_state + DAIKIN_BRC_PREAMBLE_SIZE,
DAIKIN_BRC_TRANSMIT_FRAME_SIZE - DAIKIN_BRC_PREAMBLE_SIZE);
data->mark(DAIKIN_BRC_BIT_MARK);
data->space(0);
@@ -232,41 +227,16 @@ bool DaikinBrcClimate::parse_state_frame_(const uint8_t frame[]) {
}
bool DaikinBrcClimate::on_receive(remote_base::RemoteReceiveData data) {
uint8_t state_frame[DAIKIN_BRC_STATE_FRAME_SIZE] = {};
uint8_t state_frame[DAIKIN_BRC_STATE_FRAME_SIZE];
if (!data.expect_item(DAIKIN_BRC_HEADER_MARK, DAIKIN_BRC_HEADER_SPACE)) {
return false;
}
for (uint8_t pos = 0; pos < DAIKIN_BRC_STATE_FRAME_SIZE; pos++) {
uint8_t byte = 0;
for (int8_t bit = 0; bit < 8; bit++) {
if (data.expect_item(DAIKIN_BRC_BIT_MARK, DAIKIN_BRC_ONE_SPACE)) {
byte |= 1 << bit;
} else if (!data.expect_item(DAIKIN_BRC_BIT_MARK, DAIKIN_BRC_ZERO_SPACE)) {
return false;
}
}
state_frame[pos] = byte;
if (pos == 0) {
// frame header
if (byte != 0x11)
return false;
} else if (pos == 1) {
// frame header
if (byte != 0xDA)
return false;
} else if (pos == 2) {
// frame header
if (byte != 0x17)
return false;
} else if (pos == 3) {
// frame header, with this unit's address in the low 3 bits
if (byte != (0x18 | this->sub_unit_))
return false;
} else if (pos == 4) {
// frame type
if (byte != 0x00)
return false;
}
// Frame header, with this unit's address in the low 3 bits of byte 3, then the frame type
if (!data.expect_bytes<DAIKIN_BRC_BIT_TIMINGS>(state_frame, DAIKIN_BRC_STATE_FRAME_SIZE) ||
state_frame[0] != DAIKIN_BRC_HEADER_BYTE0 || state_frame[1] != DAIKIN_BRC_HEADER_BYTE1 ||
state_frame[2] != DAIKIN_BRC_HEADER_BYTE2 || state_frame[3] != (DAIKIN_BRC_HEADER_BYTE3 | this->sub_unit_) ||
state_frame[4] != DAIKIN_BRC_FRAME_TYPE_STATE) {
return false;
}
return this->parse_state_frame_(state_frame);
}
+2 -2
View File
@@ -31,8 +31,8 @@ const uint32_t DAIKIN_BRC_IR_FREQUENCY = 38000;
const uint32_t DAIKIN_BRC_HEADER_MARK = 5070;
const uint32_t DAIKIN_BRC_HEADER_SPACE = 2140;
const uint32_t DAIKIN_BRC_BIT_MARK = 370;
const uint32_t DAIKIN_BRC_ONE_SPACE = 1780;
const uint32_t DAIKIN_BRC_ZERO_SPACE = 710;
static constexpr remote_base::BitTimings DAIKIN_BRC_BIT_TIMINGS{
.bit_mark = DAIKIN_BRC_BIT_MARK, .one_space = 1780, .zero_space = 710};
const uint32_t DAIKIN_BRC_MESSAGE_SPACE = 29410;
const uint8_t DAIKIN_BRC_IR_DRY_FAN_TEMP_F = 72; // Dry/Fan mode is always 17 Celsius.
+3 -6
View File
@@ -364,8 +364,7 @@ async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
if CONF_SLEEP_DURATION in config:
cg.add(var.set_sleep_duration(config[CONF_SLEEP_DURATION]))
cg.add_optional(var.set_sleep_duration, config.get(CONF_SLEEP_DURATION))
if (wakeup_pins := config.get(CONF_WAKEUP_PIN)) is not None:
if CORE.is_bk72xx:
cg.add(var.init_wakeup_pins(len(wakeup_pins)))
@@ -380,8 +379,7 @@ async def to_code(config: ConfigType) -> None:
)
else:
await cg.add_pin(var.set_wakeup_pin, wakeup_pins[0][CONF_PIN])
if CONF_WAKEUP_PIN_MODE in config:
cg.add(var.set_wakeup_pin_mode(config[CONF_WAKEUP_PIN_MODE]))
cg.add_optional(var.set_wakeup_pin_mode, config.get(CONF_WAKEUP_PIN_MODE))
if CONF_RUN_DURATION in config:
run_duration_config = config[CONF_RUN_DURATION]
if not isinstance(run_duration_config, dict):
@@ -427,8 +425,7 @@ async def to_code(config: ConfigType) -> None:
await automation.build_automation(trigger, [(WakeupCause, "cause")], wake_conf)
cg.add_define("USE_DEEP_SLEEP_ON_WAKE")
if CONF_TOUCH_WAKEUP in config:
cg.add(var.set_touch_wakeup(config[CONF_TOUCH_WAKEUP]))
cg.add_optional(var.set_touch_wakeup, config.get(CONF_TOUCH_WAKEUP))
if CORE.using_zephyr:
# Devices are suspended when CPU is entering a low power state
# https://github.com/nrfconnect/sdk-zephyr/blob/v3.7.99-ncs2-2/doc/services/pm/device.rst#system-managed-device-power-management
+5 -25
View File
@@ -23,13 +23,7 @@ void DelonghiClimate::transmit_state() {
data->mark(DELONGHI_HEADER_MARK);
data->space(DELONGHI_HEADER_SPACE);
for (unsigned char b : remote_state) {
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
data->mark(DELONGHI_BIT_MARK);
bool bit = b & mask;
data->space(bit ? DELONGHI_ONE_SPACE : DELONGHI_ZERO_SPACE);
}
}
data->item_bytes<DELONGHI_BIT_TIMINGS>(remote_state, DELONGHI_STATE_FRAME_SIZE);
data->mark(DELONGHI_BIT_MARK);
data->space(0);
@@ -155,27 +149,13 @@ bool DelonghiClimate::parse_state_frame_(const uint8_t frame[]) {
}
bool DelonghiClimate::on_receive(remote_base::RemoteReceiveData data) {
uint8_t state_frame[DELONGHI_STATE_FRAME_SIZE] = {};
uint8_t state_frame[DELONGHI_STATE_FRAME_SIZE];
if (!data.expect_item(DELONGHI_HEADER_MARK, DELONGHI_HEADER_SPACE)) {
return false;
}
for (uint8_t pos = 0; pos < DELONGHI_STATE_FRAME_SIZE; pos++) {
uint8_t byte = 0;
for (int8_t bit = 0; bit < 8; bit++) {
if (data.expect_item(DELONGHI_BIT_MARK, DELONGHI_ONE_SPACE)) {
byte |= 1 << bit;
} else if (!data.expect_item(DELONGHI_BIT_MARK, DELONGHI_ZERO_SPACE)) {
return false;
}
}
state_frame[pos] = byte;
if (pos == 0) {
// frame header
if (byte != DELONGHI_ADDRESS) {
return false;
}
}
}
if (!data.expect_bytes<DELONGHI_BIT_TIMINGS>(state_frame, DELONGHI_STATE_FRAME_SIZE) ||
state_frame[0] != DELONGHI_ADDRESS)
return false;
return this->parse_state_frame_(state_frame);
}
+2 -2
View File
@@ -33,8 +33,8 @@ const uint32_t DELONGHI_IR_FREQUENCY = 38000;
const uint32_t DELONGHI_HEADER_MARK = 9000;
const uint32_t DELONGHI_HEADER_SPACE = 4500;
const uint32_t DELONGHI_BIT_MARK = 465;
const uint32_t DELONGHI_ONE_SPACE = 1750;
const uint32_t DELONGHI_ZERO_SPACE = 670;
static constexpr remote_base::BitTimings DELONGHI_BIT_TIMINGS{
.bit_mark = DELONGHI_BIT_MARK, .one_space = 1750, .zero_space = 670};
// State Frame size
const uint8_t DELONGHI_STATE_FRAME_SIZE = 8;
@@ -5,15 +5,13 @@ import esphome.config_validation as cv
from esphome.const import CONF_ID, ENTITY_CATEGORY_CONFIG, ICON_SCALE
from esphome.types import ConfigType
from .. import CONF_DFROBOT_SEN0609_ID, DFRobotSEN0609Component, dfrobot_sen0609_ns
OutPinLevelSelect = dfrobot_sen0609_ns.class_("OutPinLevelSelect", select.Select)
from .. import CONF_DFROBOT_SEN0609_ID, DFRobotSEN0609Component
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_DFROBOT_SEN0609_ID): cv.use_id(DFRobotSEN0609Component),
cv.Optional(CONF_OUT_PIN_LEVEL): select.select_schema(
OutPinLevelSelect,
select.ApplySelect,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_SCALE,
),
@@ -25,5 +23,8 @@ async def to_code(config: ConfigType) -> None:
selects = select.sub_selects(config, parent=hub)
# The level the OUT pin has while a target is present; the index is the module's value
await selects(
CONF_OUT_PIN_LEVEL, hub.set_out_pin_level_select, options=["low", "high"]
CONF_OUT_PIN_LEVEL,
hub.set_out_pin_level_select,
options=["low", "high"],
apply="control_out_pin_level(index)",
)
@@ -1,17 +0,0 @@
#pragma once
#include "esphome/components/select/select.h"
#include "../dfrobot_sen0609.h"
namespace esphome::dfrobot_sen0609 {
class OutPinLevelSelect final : public select::Select, public Parented<DFRobotSEN0609Component> {
public:
// User provided, not "= default": `new(p) OutPinLevelSelect()` would zero-fill .bss that is already zero.
OutPinLevelSelect() {}
protected:
void control(size_t index) override { this->parent_->control_out_pin_level(index); }
};
} // namespace esphome::dfrobot_sen0609
@@ -18,7 +18,6 @@ from .. import (
final_validate_mode,
)
DFRobotSEN0609Switch = dfrobot_sen0609_ns.class_("DFRobotSEN0609Switch", switch.Switch)
SwitchSetting = dfrobot_sen0609_ns.enum("SwitchSetting")
CONF_MICRO_MOTION = "micro_motion"
@@ -29,7 +28,7 @@ CONF_RUN_LED = "run_led"
def _led() -> cv.Schema:
# The module forgets the LED state at power off, so it is restored from here
return switch.switch_schema(
DFRobotSEN0609Switch,
switch.ApplySwitch,
default_restore_mode="RESTORE_DEFAULT_ON",
device_class=DEVICE_CLASS_SWITCH,
entity_category=ENTITY_CATEGORY_CONFIG,
@@ -41,7 +40,7 @@ CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_DFROBOT_SEN0609_ID): cv.use_id(DFRobotSEN0609Component),
cv.Optional(CONF_MICRO_MOTION): switch.switch_schema(
DFRobotSEN0609Switch,
switch.ApplySwitch,
device_class=DEVICE_CLASS_SWITCH,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
@@ -64,5 +63,7 @@ async def to_code(config: ConfigType) -> None:
(CONF_OUT_LED, SwitchSetting.SWITCH_OUT_LED),
):
await switches(
key, lambda s, setting=setting: hub.set_switch(setting, s), setting
key,
lambda s, setting=setting: hub.set_switch(setting, s),
apply=f"control_switch({setting}, state)",
)
@@ -1,18 +0,0 @@
#pragma once
#include "esphome/components/switch/switch.h"
#include "../dfrobot_sen0609.h"
namespace esphome::dfrobot_sen0609 {
class DFRobotSEN0609Switch final : public switch_::Switch, public Parented<DFRobotSEN0609Component> {
public:
explicit DFRobotSEN0609Switch(SwitchSetting setting) : setting_(setting) {}
protected:
void write_state(bool state) override { this->parent_->control_switch(this->setting_, state); }
SwitchSetting setting_;
};
} // namespace esphome::dfrobot_sen0609
@@ -277,8 +277,7 @@ async def menu_item_to_code(menu, config, parent):
if CONF_ITEMS in config:
for c in config[CONF_ITEMS]:
await menu_item_to_code(menu, c, item)
if CONF_IMMEDIATE_EDIT in config:
cg.add(item.set_immediate_edit(config[CONF_IMMEDIATE_EDIT]))
cg.add_optional(item.set_immediate_edit, config.get(CONF_IMMEDIATE_EDIT))
if config[CONF_TYPE] == CONF_SELECT:
await cg.add_variable(item.set_select_variable, config[CONF_SELECT])
if config[CONF_TYPE] == CONF_NUMBER:
+7 -14
View File
@@ -91,20 +91,13 @@ async def to_code(config: ConfigType) -> None:
cg.add(var.set_active_pullup(config[CONF_ACTIVE_PULLUP]))
cg.add(var.set_channel_count(get_channel_count(config)))
if CONF_BUS_SLEEP in config:
cg.add(var.set_bus_sleep(config[CONF_BUS_SLEEP]))
if CONF_HUB_SLEEP in config:
cg.add(var.set_hub_sleep(config[CONF_HUB_SLEEP]))
cg.add_optional(var.set_bus_sleep, config.get(CONF_BUS_SLEEP))
cg.add_optional(var.set_hub_sleep, config.get(CONF_HUB_SLEEP))
if CONF_RESET_LOW_TIME in config:
cg.add(var.set_val_trstl(config[CONF_RESET_LOW_TIME]))
if CONF_MASTER_SAMPLE_TIME in config:
cg.add(var.set_val_tmsp(config[CONF_MASTER_SAMPLE_TIME]))
if CONF_WRITE_0_LOW_TIME in config:
cg.add(var.set_val_tw0l(config[CONF_WRITE_0_LOW_TIME]))
if CONF_RECOVERY_TIME in config:
cg.add(var.set_val_trec0(config[CONF_RECOVERY_TIME]))
if CONF_ACTIVE_PULLUP_RESISTANCE in config:
cg.add(var.set_val_rwpu(config[CONF_ACTIVE_PULLUP_RESISTANCE]))
cg.add_optional(var.set_val_trstl, config.get(CONF_RESET_LOW_TIME))
cg.add_optional(var.set_val_tmsp, config.get(CONF_MASTER_SAMPLE_TIME))
cg.add_optional(var.set_val_tw0l, config.get(CONF_WRITE_0_LOW_TIME))
cg.add_optional(var.set_val_trec0, config.get(CONF_RECOVERY_TIME))
cg.add_optional(var.set_val_rwpu, config.get(CONF_ACTIVE_PULLUP_RESISTANCE))
await cg.add_pin(var.set_sleep_pin, config.get(CONF_SLEEP_PIN))
+1 -2
View File
@@ -55,5 +55,4 @@ async def to_code(config: ConfigType) -> None:
var.get_close_trigger(), [], config[CONF_CLOSE_ACTION]
)
if CONF_MAX_DURATION in config:
cg.add(var.set_max_duration(config[CONF_MAX_DURATION]))
cg.add_optional(var.set_max_duration, config.get(CONF_MAX_DURATION))
+1 -2
View File
@@ -252,8 +252,7 @@ async def to_code(config):
enable = [await cg.gpio_pin_expression(pin) for pin in enable_pin]
cg.add(var.set_enable_pins(enable))
cg.add(var.set_full_update_every(config[CONF_FULL_UPDATE_EVERY]))
if CONF_RESET_DURATION in config:
cg.add(var.set_reset_duration(config[CONF_RESET_DURATION]))
cg.add_optional(var.set_reset_duration, config.get(CONF_RESET_DURATION))
if transform := config.get(CONF_TRANSFORM):
transform[CONF_SWAP_XY] = False
else:
+1 -2
View File
@@ -265,8 +265,7 @@ async def esp32_pin_to_code(config):
# (inverted bit in pin_flags_ bitfield is zero-initialized to false)
if config[CONF_INVERTED]:
cg.add(var.set_inverted(True))
if CONF_DRIVE_STRENGTH in config:
cg.add(var.set_drive_strength(config[CONF_DRIVE_STRENGTH]))
cg.add_optional(var.set_drive_strength, config.get(CONF_DRIVE_STRENGTH))
flags = pins.gpio_flags_expr(config[CONF_MODE])
if config[CONF_HOLD_STATE]:
flags = flags | cg.gpio_Flags.FLAG_HOLD
+4 -8
View File
@@ -535,16 +535,12 @@ async def to_code(config: ConfigType) -> None:
):
cg.add_define("ESPHOME_ESP32_BLE_EXTENDED_AUTH_PARAMS", None)
if CONF_AUTH_REQ_MODE in config:
cg.add(var.set_auth_req(config[CONF_AUTH_REQ_MODE]))
if CONF_MAX_KEY_SIZE in config:
cg.add(var.set_max_key_size(config[CONF_MAX_KEY_SIZE]))
if CONF_MIN_KEY_SIZE in config:
cg.add(var.set_min_key_size(config[CONF_MIN_KEY_SIZE]))
cg.add_optional(var.set_auth_req, config.get(CONF_AUTH_REQ_MODE))
cg.add_optional(var.set_max_key_size, config.get(CONF_MAX_KEY_SIZE))
cg.add_optional(var.set_min_key_size, config.get(CONF_MIN_KEY_SIZE))
cg.add(var.set_advertising_cycle_time(config[CONF_ADVERTISING_CYCLE_TIME]))
if (name := config.get(CONF_NAME)) is not None:
cg.add(var.set_name(name))
cg.add_optional(var.set_name, config.get(CONF_NAME))
await cg.register_component(var, config)
# Define max connections for use in C++ code (e.g., ble_server.h)
@@ -83,8 +83,7 @@ async def to_code(config: ConfigType) -> None:
cg.add(var.set_measured_power(config[CONF_MEASURED_POWER]))
# TX power control only available on native Bluetooth (not ESP-Hosted)
if CONF_TX_POWER in config:
cg.add(var.set_tx_power(config[CONF_TX_POWER]))
cg.add_optional(var.set_tx_power, config.get(CONF_TX_POWER))
cg.add_define("USE_ESP32_BLE_ADVERTISING")
@@ -647,8 +647,7 @@ async def to_code(config):
)
)
)
if CONF_MANUFACTURER_DATA in config:
cg.add(var.set_manufacturer_data(config[CONF_MANUFACTURER_DATA]))
cg.add_optional(var.set_manufacturer_data, config.get(CONF_MANUFACTURER_DATA))
for service_config in config[CONF_SERVICES]:
# Calculate the optimal number of handles based on the number of characteristics and descriptors
num_handles = calculate_num_handles(service_config)
+2 -4
View File
@@ -157,10 +157,8 @@ async def to_code(config: ConfigType) -> None:
cg.add(var.set_rx(config[CONF_RX_PIN]))
cg.add(var.set_tx(config[CONF_TX_PIN]))
cg.add(var.set_mode(config[CONF_MODE]))
if (rx_queue_len := config.get(CONF_RX_QUEUE_LEN)) is not None:
cg.add(var.set_rx_queue_len(rx_queue_len))
if (tx_queue_len := config.get(CONF_TX_QUEUE_LEN)) is not None:
cg.add(var.set_tx_queue_len(tx_queue_len))
cg.add_optional(var.set_rx_queue_len, config.get(CONF_RX_QUEUE_LEN))
cg.add_optional(var.set_tx_queue_len, config.get(CONF_TX_QUEUE_LEN))
if CONF_TX_ENQUEUE_TIMEOUT in config:
tx_enqueue_timeout_ms = config[CONF_TX_ENQUEUE_TIMEOUT].total_milliseconds
@@ -170,10 +170,8 @@ async def to_code(config: ConfigType) -> None:
await power_management.register_power_management(var, config)
add_idf_sdkconfig_option("CONFIG_PM_ENABLE", True)
if (max_mhz := config.get(CONF_MAX_FREQUENCY)) is not None:
cg.add(var.set_max_freq_mhz(max_mhz))
if (min_mhz := config.get(CONF_MIN_FREQUENCY)) is not None:
cg.add(var.set_min_freq_mhz(min_mhz))
cg.add_optional(var.set_max_freq_mhz, config.get(CONF_MAX_FREQUENCY))
cg.add_optional(var.set_min_freq_mhz, config.get(CONF_MIN_FREQUENCY))
if config[CONF_PROFILING]:
add_idf_sdkconfig_option("CONFIG_PM_PROFILING", True)
if config[CONF_TRACE]:
@@ -137,8 +137,7 @@ async def to_code(config: ConfigType) -> None:
if config[CONF_PIN][CONF_INVERTED]:
cg.add(var.set_inverted(True))
if CONF_MAX_REFRESH_RATE in config:
cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE]))
cg.add_optional(var.set_max_refresh_rate, config.get(CONF_MAX_REFRESH_RATE))
if CONF_CHIPSET in config:
chipset = CHIPSETS[config[CONF_CHIPSET]]
@@ -169,5 +168,4 @@ async def to_code(config: ConfigType) -> None:
)
cg.add(var.set_use_psram(config[CONF_USE_PSRAM]))
cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS]))
if CONF_USE_DMA in config:
cg.add(var.set_use_dma(config[CONF_USE_DMA]))
cg.add_optional(var.set_use_dma, config.get(CONF_USE_DMA))
+7 -14
View File
@@ -366,20 +366,13 @@ async def to_code(config: ConfigType) -> None:
cg.add(touch.set_iir_filter(config[CONF_IIR_FILTER]))
if variant in (VARIANT_ESP32S2, VARIANT_ESP32S3, VARIANT_ESP32P4):
if CONF_FILTER_MODE in config:
cg.add(touch.set_filter_mode(config[CONF_FILTER_MODE]))
if CONF_DEBOUNCE_COUNT in config:
cg.add(touch.set_debounce_count(config[CONF_DEBOUNCE_COUNT]))
if CONF_NOISE_THRESHOLD in config:
cg.add(touch.set_noise_threshold(config[CONF_NOISE_THRESHOLD]))
if CONF_JITTER_STEP in config:
cg.add(touch.set_jitter_step(config[CONF_JITTER_STEP]))
if CONF_SMOOTH_MODE in config:
cg.add(touch.set_smooth_level(config[CONF_SMOOTH_MODE]))
if CONF_DENOISE_GRADE in config:
cg.add(touch.set_denoise_grade(config[CONF_DENOISE_GRADE]))
if CONF_DENOISE_CAP_LEVEL in config:
cg.add(touch.set_denoise_cap(config[CONF_DENOISE_CAP_LEVEL]))
cg.add_optional(touch.set_filter_mode, config.get(CONF_FILTER_MODE))
cg.add_optional(touch.set_debounce_count, config.get(CONF_DEBOUNCE_COUNT))
cg.add_optional(touch.set_noise_threshold, config.get(CONF_NOISE_THRESHOLD))
cg.add_optional(touch.set_jitter_step, config.get(CONF_JITTER_STEP))
cg.add_optional(touch.set_smooth_level, config.get(CONF_SMOOTH_MODE))
cg.add_optional(touch.set_denoise_grade, config.get(CONF_DENOISE_GRADE))
cg.add_optional(touch.set_denoise_cap, config.get(CONF_DENOISE_CAP_LEVEL))
if CONF_WATERPROOF_GUARD_RING in config:
cg.add(
touch.set_waterproof_guard_ring_pad(config[CONF_WATERPROOF_GUARD_RING])
+2 -4
View File
@@ -151,10 +151,8 @@ async def to_code(config: ConfigType) -> None:
cg.add(var.set_traffic_generator_target_ip(target_ip))
if (group := config.get(CONF_CSI_TRAFFIC_MULTICAST_GROUP)) is not None:
cg.add(var.set_csi_traffic_multicast_group(group))
if (on_hits := config.get(CONF_MOTION_ON_HITS)) is not None:
cg.add(var.set_motion_on_hits(on_hits))
if (off_hits := config.get(CONF_MOTION_OFF_HITS)) is not None:
cg.add(var.set_motion_off_hits(off_hits))
cg.add_optional(var.set_motion_on_hits, config.get(CONF_MOTION_ON_HITS))
cg.add_optional(var.set_motion_off_hits, config.get(CONF_MOTION_OFF_HITS))
if esp32.get_esp32_variant() == VARIANT_ESP32C5:
band = CORE.config[WIFI_DOMAIN].get(wifi.CONF_BAND_MODE, "AUTO")
cg.add(
+5 -10
View File
@@ -724,8 +724,7 @@ async def _to_code_esp32(var: cg.MockObj, config: ConfigType) -> None:
cg.add(var.set_polling_interval(config[CONF_POLLING_INTERVAL]))
if _is_framework_spi_polling_mode_supported():
cg.add_define("USE_ETHERNET_SPI_POLLING_SUPPORT")
if CONF_RESET_PIN in config:
cg.add(var.set_reset_pin(config[CONF_RESET_PIN]))
cg.add_optional(var.set_reset_pin, config.get(CONF_RESET_PIN))
cg.add(var.set_clock_speed(config[CONF_CLOCK_SPEED]))
cg.add_define("USE_ETHERNET_SPI")
@@ -746,8 +745,7 @@ async def _to_code_esp32(var: cg.MockObj, config: ConfigType) -> None:
elif config[CONF_TYPE] in ("GENERIC", "YT8531"):
# RGMII data pins come from the IDF default config; set MDC/MDIO + PHY addr.
_add_mdio_setters(var, config)
if CONF_POWER_PIN in config:
cg.add(var.set_power_pin(config[CONF_POWER_PIN]))
cg.add_optional(var.set_power_pin, config.get(CONF_POWER_PIN))
_add_phy_registers(var, config)
else:
_add_mdio_setters(var, config)
@@ -755,8 +753,7 @@ async def _to_code_esp32(var: cg.MockObj, config: ConfigType) -> None:
cg.add(var.set_clk_mode(clk_mode))
if (clk_pin := config[CONF_CLK][CONF_PIN]) != DEFAULT_CLK_PIN:
cg.add(var.set_clk_pin(clk_pin))
if CONF_POWER_PIN in config:
cg.add(var.set_power_pin(config[CONF_POWER_PIN]))
cg.add_optional(var.set_power_pin, config.get(CONF_POWER_PIN))
_add_phy_registers(var, config)
# Register Ethernet with the esp32 sdkconfig reconciler. It disables the
@@ -782,10 +779,8 @@ async def _to_code_rp2040(var: cg.MockObj, config: ConfigType) -> None:
cg.add(var.set_miso_pin(config[CONF_MISO_PIN]))
cg.add(var.set_mosi_pin(config[CONF_MOSI_PIN]))
cg.add(var.set_cs_pin(config[CONF_CS_PIN]))
if CONF_INTERRUPT_PIN in config:
cg.add(var.set_interrupt_pin(config[CONF_INTERRUPT_PIN]))
if CONF_RESET_PIN in config:
cg.add(var.set_reset_pin(config[CONF_RESET_PIN]))
cg.add_optional(var.set_interrupt_pin, config.get(CONF_INTERRUPT_PIN))
cg.add_optional(var.set_reset_pin, config.get(CONF_RESET_PIN))
cg.add_define("USE_ETHERNET_SPI")
cg.add_library(_RP2_SPI_LIBRARIES[config[CONF_TYPE]], None)
@@ -83,8 +83,7 @@ async def to_code(config: ConfigType) -> None:
if (time_constant := config.get(CONF_TIME_CONSTANT)) is not None:
cg.add(var.set_time_constant(time_constant))
if (weighting := config.get(CONF_TIME_WEIGHTING)) is not None:
cg.add(var.set_time_weighting(weighting))
cg.add_optional(var.set_time_weighting, config.get(CONF_TIME_WEIGHTING))
else:
cg.add(var.set_alpha(config.get(CONF_ALPHA, DEFAULT_ALPHA)))
cg.add(var.set_restore(config[CONF_RESTORE]))
+1 -2
View File
@@ -41,8 +41,7 @@ async def new_fastled_light(config: ConfigType) -> MockObj:
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
await cg.register_component(var, config)
if CONF_MAX_REFRESH_RATE in config:
cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE]))
cg.add_optional(var.set_max_refresh_rate, config.get(CONF_MAX_REFRESH_RATE))
if CORE.is_esp32:
from esphome.components.esp32 import add_idf_component
+1 -2
View File
@@ -147,8 +147,7 @@ async def to_code(config: ConfigType) -> None:
)
# OTHER
if CONF_MAX_DURATION in config:
cg.add(var.set_max_duration(config[CONF_MAX_DURATION]))
cg.add_optional(var.set_max_duration, config.get(CONF_MAX_DURATION))
cg.add(var.set_has_built_in_endstop(config[CONF_HAS_BUILT_IN_ENDSTOP]))
@@ -66,9 +66,8 @@ constexpr uint8_t FUJITSU_GENERAL_FAN_SILENT = 0x04;
constexpr uint16_t FUJITSU_GENERAL_HEADER_MARK = 3300;
constexpr uint16_t FUJITSU_GENERAL_HEADER_SPACE = 1600;
constexpr uint16_t FUJITSU_GENERAL_BIT_MARK = 420;
constexpr uint16_t FUJITSU_GENERAL_ONE_SPACE = 1200;
constexpr uint16_t FUJITSU_GENERAL_ZERO_SPACE = 420;
static constexpr remote_base::BitTimings FUJITSU_GENERAL_BIT_TIMINGS{
.bit_mark = 420, .one_space = 1200, .zero_space = 420};
constexpr uint16_t FUJITSU_GENERAL_TRL_MARK = 420;
constexpr uint16_t FUJITSU_GENERAL_TRL_SPACE = 8000;
@@ -207,14 +206,7 @@ void FujitsuGeneralClimate::transmit_(uint8_t const *message, uint8_t length) {
data->space(FUJITSU_GENERAL_HEADER_SPACE);
// Data
for (uint8_t i = 0; i < length; ++i) {
const uint8_t byte = message[i];
for (uint8_t mask = 0b00000001; mask > 0; mask <<= 1) { // write from right to left
data->mark(FUJITSU_GENERAL_BIT_MARK);
bool bit = byte & mask;
data->space(bit ? FUJITSU_GENERAL_ONE_SPACE : FUJITSU_GENERAL_ZERO_SPACE);
}
}
data->item_bytes<FUJITSU_GENERAL_BIT_TIMINGS>(message, length);
// Footer
data->mark(FUJITSU_GENERAL_TRL_MARK);
@@ -284,19 +276,12 @@ bool FujitsuGeneralClimate::on_receive(remote_base::RemoteReceiveData data) {
return false;
}
uint8_t recv_message[FUJITSU_GENERAL_STATE_MESSAGE_LENGTH] = {0};
uint8_t recv_message[FUJITSU_GENERAL_STATE_MESSAGE_LENGTH];
// Read header
for (uint8_t byte = 0; byte < FUJITSU_GENERAL_COMMON_LENGTH; ++byte) {
// Read bit
for (uint8_t bit = 0; bit < 8; ++bit) {
if (data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ONE_SPACE)) {
recv_message[byte] |= 1 << bit; // read from right to left
} else if (!data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ZERO_SPACE)) {
ESP_LOGV(TAG, "Byte %d bit %d fail", byte, bit);
return false;
}
}
if (!data.expect_bytes<FUJITSU_GENERAL_BIT_TIMINGS>(recv_message, FUJITSU_GENERAL_COMMON_LENGTH)) {
ESP_LOGV(TAG, "Header bytes fail");
return false;
}
const uint8_t recv_message_type = recv_message[FUJITSU_GENERAL_MESSAGE_TYPE_BYTE];
@@ -319,15 +304,10 @@ bool FujitsuGeneralClimate::on_receive(remote_base::RemoteReceiveData data) {
}
// Read message body
for (uint8_t byte = FUJITSU_GENERAL_COMMON_LENGTH; byte < recv_message_length; ++byte) {
for (uint8_t bit = 0; bit < 8; ++bit) {
if (data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ONE_SPACE)) {
recv_message[byte] |= 1 << bit; // read from right to left
} else if (!data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ZERO_SPACE)) {
ESP_LOGV(TAG, "Byte %d bit %d fail", byte, bit);
return false;
}
}
if (!data.expect_bytes<FUJITSU_GENERAL_BIT_TIMINGS>(recv_message + FUJITSU_GENERAL_COMMON_LENGTH,
recv_message_length - FUJITSU_GENERAL_COMMON_LENGTH)) {
ESP_LOGV(TAG, "Body bytes fail");
return false;
}
for (uint8_t byte = 0; byte < recv_message_length; ++byte) {
+7 -14
View File
@@ -151,21 +151,14 @@ async def to_code(config: ConfigType) -> None:
await cg.register_component(var, config)
# Graph options
if CONF_X_GRID in config:
cg.add(var.set_grid_x(config[CONF_X_GRID]))
if CONF_Y_GRID in config:
cg.add(var.set_grid_y(config[CONF_Y_GRID]))
if CONF_BORDER in config:
cg.add(var.set_border(config[CONF_BORDER]))
cg.add_optional(var.set_grid_x, config.get(CONF_X_GRID))
cg.add_optional(var.set_grid_y, config.get(CONF_Y_GRID))
cg.add_optional(var.set_border, config.get(CONF_BORDER))
# Axis related options
if CONF_MIN_VALUE in config:
cg.add(var.set_min_value(config[CONF_MIN_VALUE]))
if CONF_MAX_VALUE in config:
cg.add(var.set_max_value(config[CONF_MAX_VALUE]))
if CONF_MIN_RANGE in config:
cg.add(var.set_min_range(config[CONF_MIN_RANGE]))
if CONF_MAX_RANGE in config:
cg.add(var.set_max_range(config[CONF_MAX_RANGE]))
cg.add_optional(var.set_min_value, config.get(CONF_MIN_VALUE))
cg.add_optional(var.set_max_value, config.get(CONF_MAX_VALUE))
cg.add_optional(var.set_min_range, config.get(CONF_MIN_RANGE))
cg.add_optional(var.set_max_range, config.get(CONF_MAX_RANGE))
# Trace options
for trace in config[CONF_TRACES]:
tr = cg.new_Pvariable(trace[CONF_ID], GraphTrace())
+6 -12
View File
@@ -373,20 +373,14 @@ async def to_code(config):
await uart.register_uart_device(var, config)
cg.add(var.set_send_wifi(config[CONF_WIFI_SIGNAL]))
if CONF_CONTROL_METHOD in config:
cg.add(var.set_control_method(config[CONF_CONTROL_METHOD]))
if CONF_BEEPER in config:
cg.add(var.set_beeper_state(config[CONF_BEEPER]))
if CONF_DISPLAY in config:
cg.add(var.set_display_state(config[CONF_DISPLAY]))
if CONF_SUPPORTED_MODES in config:
cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES]))
cg.add_optional(var.set_control_method, config.get(CONF_CONTROL_METHOD))
cg.add_optional(var.set_beeper_state, config.get(CONF_BEEPER))
cg.add_optional(var.set_display_state, config.get(CONF_DISPLAY))
cg.add_optional(var.set_supported_modes, config.get(CONF_SUPPORTED_MODES))
if CONF_SUPPORTED_SWING_MODES in config:
cg.add(var.set_supported_swing_modes(config[CONF_SUPPORTED_SWING_MODES]))
if CONF_SUPPORTED_PRESETS in config:
cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS]))
if CONF_ANSWER_TIMEOUT in config:
cg.add(var.set_answer_timeout(config[CONF_ANSWER_TIMEOUT]))
cg.add_optional(var.set_supported_presets, config.get(CONF_SUPPORTED_PRESETS))
cg.add_optional(var.set_answer_timeout, config.get(CONF_ANSWER_TIMEOUT))
if CONF_ALTERNATIVE_SWING_CONTROL in config:
cg.add(
var.set_alternative_swing_control(config[CONF_ALTERNATIVE_SWING_CONTROL])
@@ -1,18 +1,12 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import CONF_LIGHT
from esphome.types import ConfigType
from ..climate import HaierTundra, haier_tundra_ns
from ..climate import HaierTundra
CODEOWNERS = ["@fauxpark"]
HaierTundraButton = haier_tundra_ns.class_(
"HaierTundraButton", button.Button, cg.Component
)
ButtonKind = haier_tundra_ns.enum("ButtonKind", is_class=True)
ICON_LED_OUTLINE = "mdi:led-outline"
ICON_SPRAY_BOTTLE = "mdi:spray-bottle"
@@ -23,21 +17,16 @@ CONFIG_SCHEMA = cv.Schema(
{
cv.Required(CONF_HAIER_TUNDRA_ID): cv.use_id(HaierTundra),
cv.Optional(CONF_LIGHT): button.button_schema(
HaierTundraButton, icon=ICON_LED_OUTLINE
button.ApplyButton, icon=ICON_LED_OUTLINE
),
cv.Optional(CONF_SELF_CLEAN): button.button_schema(
HaierTundraButton, icon=ICON_SPRAY_BOTTLE
button.ApplyButton, icon=ICON_SPRAY_BOTTLE
),
}
)
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_HAIER_TUNDRA_ID])
for key, kind in (
(CONF_LIGHT, ButtonKind.BUTTON_KIND_LIGHT),
(CONF_SELF_CLEAN, ButtonKind.BUTTON_KIND_SELF_CLEAN),
):
if (conf := config.get(key)) is not None:
var = await button.new_button(conf, hub, kind)
await cg.register_component(var, conf)
buttons = button.sub_buttons(config, parent=config[CONF_HAIER_TUNDRA_ID])
await buttons(CONF_LIGHT, None, apply="toggle_light()")
await buttons(CONF_SELF_CLEAN, None, apply="start_self_clean()")
@@ -1,18 +0,0 @@
#include "haier_tundra_button.h"
#include "esphome/core/log.h"
namespace esphome::haier_tundra {
ESPHOME_LOG_TAG(TAG, "haier_tundra.button");
void HaierTundraButton::dump_config() { LOG_BUTTON("", "Haier Tundra Button", this); }
void HaierTundraButton::press_action() {
if (this->kind_ == ButtonKind::BUTTON_KIND_LIGHT) {
this->parent_->toggle_light();
} else {
this->parent_->start_self_clean();
}
}
} // namespace esphome::haier_tundra
@@ -1,25 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/button/button.h"
#include "esphome/components/haier_tundra/haier_tundra.h"
namespace esphome::haier_tundra {
enum class ButtonKind : uint8_t {
BUTTON_KIND_LIGHT,
BUTTON_KIND_SELF_CLEAN,
};
class HaierTundraButton final : public button::Button, public Component, public Parented<HaierTundra> {
public:
HaierTundraButton(HaierTundra *parent, ButtonKind kind) : Parented(parent), kind_(kind) {}
void dump_config() override;
protected:
void press_action() override;
ButtonKind kind_;
};
} // namespace esphome::haier_tundra
+1 -2
View File
@@ -60,5 +60,4 @@ async def to_code(config: ConfigType) -> None:
await cg.add_variable(var.set_pin_b, config[CONF_PIN_B])
await cg.add_variable(var.set_enable_pin, config.get(CONF_ENABLE_PIN))
if CONF_PRESET_MODES in config:
cg.add(var.set_preset_modes(config[CONF_PRESET_MODES]))
cg.add_optional(var.set_preset_modes, config.get(CONF_PRESET_MODES))
@@ -15,24 +15,15 @@ static constexpr uint8_t MODE_MASK = 0x07;
static constexpr uint8_t TEMP_MASK = 0x0F;
static constexpr uint8_t TEMPERATURE_BASE = 17;
static constexpr uint8_t TEMPERATURE_SHIFT = 4;
static constexpr remote_base::BitTimings MITSUBISHI_HEAVY_BIT_TIMINGS{.bit_mark = MITSUBISHI_HEAVY_BIT_MARK,
.one_space = MITSUBISHI_HEAVY_ONE_SPACE,
.zero_space = MITSUBISHI_HEAVY_ZERO_SPACE};
static bool read_raw_bytes(remote_base::RemoteReceiveData &data, uint8_t frame[MITSUBISHI_HEAVY_FRAME_LENGTH]) {
if (!data.expect_item(MITSUBISHI_HEAVY_HDR_MARK, MITSUBISHI_HEAVY_HDR_SPACE))
return false;
for (uint8_t i = 0; i < MITSUBISHI_HEAVY_FRAME_LENGTH; i++) {
uint8_t byte = 0;
for (uint8_t bit = 0; bit < 8; bit++) {
if (data.expect_item(MITSUBISHI_HEAVY_BIT_MARK, MITSUBISHI_HEAVY_ONE_SPACE)) {
byte |= 1 << bit;
} else if (!data.expect_item(MITSUBISHI_HEAVY_BIT_MARK, MITSUBISHI_HEAVY_ZERO_SPACE)) {
return false;
}
}
frame[i] = byte;
}
return true;
return data.expect_bytes<MITSUBISHI_HEAVY_BIT_TIMINGS>(frame, MITSUBISHI_HEAVY_FRAME_LENGTH);
}
static bool validate_prefix(const uint8_t frame[MITSUBISHI_HEAVY_FRAME_LENGTH]) {
@@ -224,13 +224,7 @@ void HitachiClimate::transmit_state() {
// Header
data->item(HITACHI_AC424_HDR_MARK, HITACHI_AC424_HDR_SPACE);
// Data
for (uint8_t i : remote_state_) {
for (uint8_t j = 0; j < 8; j++) {
data->mark(HITACHI_AC424_BIT_MARK);
bool bit = i & (1 << j);
data->space(bit ? HITACHI_AC424_ONE_SPACE : HITACHI_AC424_ZERO_SPACE);
}
}
data->item_bytes<HITACHI_AC424_BIT_TIMINGS>(remote_state_, HITACHI_AC424_STATE_LENGTH);
// Footer
data->item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_MIN_GAP);
}
@@ -320,18 +314,10 @@ bool HitachiClimate::on_receive(remote_base::RemoteReceiveData data) {
return false;
}
uint8_t recv_state[HITACHI_AC424_STATE_LENGTH] = {0};
// Read all bytes.
for (uint8_t pos = 0; pos < HITACHI_AC424_STATE_LENGTH; pos++) {
// Read bit
for (int8_t bit = 0; bit < 8; bit++) {
if (data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ONE_SPACE)) {
recv_state[pos] |= 1 << bit;
} else if (!data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ZERO_SPACE)) {
ESP_LOGVV(TAG, "Byte %d bit %d fail", pos, bit);
return false;
}
}
uint8_t recv_state[HITACHI_AC424_STATE_LENGTH];
if (!data.expect_bytes<HITACHI_AC424_BIT_TIMINGS>(recv_state, HITACHI_AC424_STATE_LENGTH)) {
ESP_LOGVV(TAG, "Data fail");
return false;
}
// Validate footer
@@ -8,8 +8,8 @@ namespace esphome::hitachi_ac424 {
const uint16_t HITACHI_AC424_HDR_MARK = 3416; // ac
const uint16_t HITACHI_AC424_HDR_SPACE = 1604; // ac
const uint16_t HITACHI_AC424_BIT_MARK = 463;
const uint16_t HITACHI_AC424_ONE_SPACE = 1208;
const uint16_t HITACHI_AC424_ZERO_SPACE = 372;
static constexpr remote_base::BitTimings HITACHI_AC424_BIT_TIMINGS{
.bit_mark = HITACHI_AC424_BIT_MARK, .one_space = 1208, .zero_space = 372};
const uint32_t HITACHI_AC424_MIN_GAP = 100000; // just a guess.
const uint16_t HITACHI_AC424_FREQ = 38000; // Hz.
+1 -2
View File
@@ -91,8 +91,7 @@ def setup_home_assistant_entity(
cg.add(var.set_entity_id(cg.RawExpression(buffer)))
else:
cg.add(var.set_entity_id(config[CONF_ENTITY_ID]))
if CONF_ATTRIBUTE in config:
cg.add(var.set_attribute(config[CONF_ATTRIBUTE]))
cg.add_optional(var.set_attribute, config.get(CONF_ATTRIBUTE))
cg.add_define("USE_API_HOMEASSISTANT_STATES")
if state_subscription_count > 0:
data = _get_data()
+2 -4
View File
@@ -156,10 +156,8 @@ async def to_code(config: ConfigType) -> None:
cg.add(var.set_sensor(sens, i))
cg.add(var.set_model(config[CONF_MODEL]))
if CONF_RESOLUTION in config:
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
cg.add_optional(var.set_resolution, config.get(CONF_RESOLUTION))
cg.add(var.set_request_temperature(CONF_TEMPERATURE in config))
if CONF_DISABLE_LED in config:
cg.add(var.set_disable_led(config[CONF_DISABLE_LED]))
cg.add_optional(var.set_disable_led, config.get(CONF_DISABLE_LED))
+4 -8
View File
@@ -333,11 +333,9 @@ async def to_code(config: ConfigType) -> None:
await cg.register_component(var, config)
cg.add(var.set_sda_pin(config[CONF_SDA]))
if CONF_SDA_PULLUP_ENABLED in config:
cg.add(var.set_sda_pullup_enabled(config[CONF_SDA_PULLUP_ENABLED]))
cg.add_optional(var.set_sda_pullup_enabled, config.get(CONF_SDA_PULLUP_ENABLED))
cg.add(var.set_scl_pin(config[CONF_SCL]))
if CONF_SCL_PULLUP_ENABLED in config:
cg.add(var.set_scl_pullup_enabled(config[CONF_SCL_PULLUP_ENABLED]))
cg.add_optional(var.set_scl_pullup_enabled, config.get(CONF_SCL_PULLUP_ENABLED))
cg.add(var.set_frequency(int(config[CONF_FREQUENCY])))
cg.add(var.set_scan(config[CONF_SCAN]))
@@ -350,11 +348,9 @@ async def to_code(config: ConfigType) -> None:
await cg.register_component(var, config)
cg.add(var.set_sda_pin(config[CONF_SDA]))
if CONF_SDA_PULLUP_ENABLED in config:
cg.add(var.set_sda_pullup_enabled(config[CONF_SDA_PULLUP_ENABLED]))
cg.add_optional(var.set_sda_pullup_enabled, config.get(CONF_SDA_PULLUP_ENABLED))
cg.add(var.set_scl_pin(config[CONF_SCL]))
if CONF_SCL_PULLUP_ENABLED in config:
cg.add(var.set_scl_pullup_enabled(config[CONF_SCL_PULLUP_ENABLED]))
cg.add_optional(var.set_scl_pullup_enabled, config.get(CONF_SCL_PULLUP_ENABLED))
cg.add(var.set_frequency(int(config[CONF_FREQUENCY])))
cg.add(var.set_scan(config[CONF_SCAN]))
+3 -6
View File
@@ -370,12 +370,9 @@ async def to_code(config: ConfigType) -> None:
# Helps avoid callbacks being skipped due to processor load
add_idf_sdkconfig_option("CONFIG_I2S_ISR_IRAM_SAFE", True)
if CONF_I2S_LRCLK_PIN in config:
cg.add(var.set_lrclk_pin(config[CONF_I2S_LRCLK_PIN]))
if CONF_I2S_BCLK_PIN in config:
cg.add(var.set_bclk_pin(config[CONF_I2S_BCLK_PIN]))
if CONF_I2S_MCLK_PIN in config:
cg.add(var.set_mclk_pin(config[CONF_I2S_MCLK_PIN]))
cg.add_optional(var.set_lrclk_pin, config.get(CONF_I2S_LRCLK_PIN))
cg.add_optional(var.set_bclk_pin, config.get(CONF_I2S_BCLK_PIN))
cg.add_optional(var.set_mclk_pin, config.get(CONF_I2S_MCLK_PIN))
if config[CONF_FULL_DUPLEX]:
cg.add_define("USE_I2S_AUDIO_FULL_DUPLEX")
+1 -2
View File
@@ -84,5 +84,4 @@ async def to_code(config: dict[str, Any]) -> None:
await remote_base.attach_receiver(var, config, CONF_REMOTE_RECEIVER_ID)
# Set receiver demodulation frequency if specified (metadata only, no hardware effect)
if CONF_RECEIVER_FREQUENCY in config:
cg.add(var.set_receiver_frequency(config[CONF_RECEIVER_FREQUENCY]))
cg.add_optional(var.set_receiver_frequency, config.get(CONF_RECEIVER_FREQUENCY))
+3 -6
View File
@@ -376,22 +376,19 @@ async def to_code(config: ConfigType) -> None:
)
)
cg.add(var.set_full_update_every(config[CONF_FULL_UPDATE_EVERY]))
if (reset_duration := config.get(CONF_RESET_DURATION)) is not None:
cg.add(var.set_reset_duration(reset_duration))
cg.add_optional(var.set_reset_duration, config.get(CONF_RESET_DURATION))
if config.get(CONF_INVERT_COLORS):
cg.add(var.set_invert_colors(True))
if config.get(CONF_SLEEP_WHEN_DONE):
cg.add(var.set_sleep_when_done(True))
cg.add(var.set_vcom(config[CONF_VCOM]))
cg.add(var.set_vcom_register(config[CONF_VCOM_REGISTER]))
if CONF_FORCE_TEMPERATURE in config:
cg.add(var.set_force_temperature(config[CONF_FORCE_TEMPERATURE]))
cg.add_optional(var.set_force_temperature, config.get(CONF_FORCE_TEMPERATURE))
if config.get(CONF_USE_LEGACY_DPY_AREA):
cg.add(var.set_use_legacy_dpy_area(True))
cg.add(var.set_grayscale(config[CONF_GRAYSCALE]))
cg.add(var.set_dithering(config[CONF_DITHERING]))
if (mode := config.get(CONF_UPDATE_MODE)) is not None:
cg.add(var.set_update_mode(mode))
cg.add_optional(var.set_update_mode, config.get(CONF_UPDATE_MODE))
transform = config.get(
CONF_TRANSFORM,
+8 -16
View File
@@ -94,22 +94,14 @@ async def to_code(config: ConfigType) -> None:
await cg.register_component(var, config)
for source_conf in config.get(CONF_SOURCE_ID, ()):
await cg.add_variable(var.add_provider, source_conf)
if CONF_MIN_LENGTH in config:
cg.add(var.set_min_length(config[CONF_MIN_LENGTH]))
if CONF_MAX_LENGTH in config:
cg.add(var.set_max_length(config[CONF_MAX_LENGTH]))
if CONF_START_KEYS in config:
cg.add(var.set_start_keys(config[CONF_START_KEYS]))
if CONF_END_KEYS in config:
cg.add(var.set_end_keys(config[CONF_END_KEYS]))
if CONF_END_KEY_REQUIRED in config:
cg.add(var.set_end_key_required(config[CONF_END_KEY_REQUIRED]))
if CONF_BACK_KEYS in config:
cg.add(var.set_back_keys(config[CONF_BACK_KEYS]))
if CONF_CLEAR_KEYS in config:
cg.add(var.set_clear_keys(config[CONF_CLEAR_KEYS]))
if CONF_ALLOWED_KEYS in config:
cg.add(var.set_allowed_keys(config[CONF_ALLOWED_KEYS]))
cg.add_optional(var.set_min_length, config.get(CONF_MIN_LENGTH))
cg.add_optional(var.set_max_length, config.get(CONF_MAX_LENGTH))
cg.add_optional(var.set_start_keys, config.get(CONF_START_KEYS))
cg.add_optional(var.set_end_keys, config.get(CONF_END_KEYS))
cg.add_optional(var.set_end_key_required, config.get(CONF_END_KEY_REQUIRED))
cg.add_optional(var.set_back_keys, config.get(CONF_BACK_KEYS))
cg.add_optional(var.set_clear_keys, config.get(CONF_CLEAR_KEYS))
cg.add_optional(var.set_allowed_keys, config.get(CONF_ALLOWED_KEYS))
for trigger_name, args in TRIGGER_TYPES.items():
arglist: TemplateArgsType = [(arg.type, arg.name) for arg in args]
+11
View File
@@ -917,6 +917,17 @@ void LD2420Component::init_gate_config_numbers() {
}
}
void LD2420Component::set_gate_threshold_value(number::Number *n, float value) {
for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) {
const bool move = this->gate_move_threshold_numbers_[gate] == n;
if (!move && this->gate_still_threshold_numbers_[gate] != n)
continue;
const uint8_t target = this->is_gate_select() ? this->get_gate_select_value() : gate;
(move ? this->new_config.move_thresh : this->new_config.still_thresh)[target] = value;
return;
}
}
void LD2420Component::refresh_gate_config_numbers() {
this->gate_timeout_number_->publish_state(this->new_config.timeout);
this->min_gate_distance_number_->publish_state(this->new_config.min_gate);
+2
View File
@@ -89,6 +89,8 @@ class LD2420Component final : public Component, public uart::UARTDevice {
void publish_gate_still_threshold(uint8_t gate) {
this->gate_still_threshold_numbers_[0]->publish_state(this->new_config.still_thresh[gate]);
};
// Stores a threshold number's value for its gate, or for the selected gate with gate_select
void set_gate_threshold_value(number::Number *n, float value);
void init_gate_config_numbers();
void refresh_gate_config_numbers();
#endif
+41 -48
View File
@@ -1,8 +1,9 @@
from functools import partial
import esphome.codegen as cg
from esphome.components import number
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_MOVE_THRESHOLD,
CONF_STILL_THRESHOLD,
DEVICE_CLASS_DISTANCE,
@@ -16,22 +17,9 @@ from esphome.types import ConfigType
from .. import CONF_LD2420_ID, LD2420Component, ld2420_ns
LD2420TimeoutNumber = ld2420_ns.class_("LD2420TimeoutNumber", number.Number)
LD2420MoveSensFactorNumber = ld2420_ns.class_(
"LD2420MoveSensFactorNumber", number.Number
)
LD2420StillSensFactorNumber = ld2420_ns.class_(
"LD2420StillSensFactorNumber", number.Number
)
LD2420MinDistanceNumber = ld2420_ns.class_("LD2420MinDistanceNumber", number.Number)
LD2420MaxDistanceNumber = ld2420_ns.class_("LD2420MaxDistanceNumber", number.Number)
LD2420GateSelectNumber = ld2420_ns.class_("LD2420GateSelectNumber", number.Number)
LD2420MoveThresholdNumbers = ld2420_ns.class_(
"LD2420MoveThresholdNumbers", number.Number
)
LD2420StillThresholdNumbers = ld2420_ns.class_(
"LD2420StillThresholdNumbers", number.Number
)
CONF_MIN_GATE_DISTANCE = "min_gate_distance"
CONF_MAX_GATE_DISTANCE = "max_gate_distance"
CONF_GATE_MOVE_SENSITIVITY = "gate_move_sensitivity"
@@ -46,7 +34,7 @@ CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
cv.Inclusive(CONF_PRESENCE_TIMEOUT, TIMEOUT_GROUP): number.number_schema(
LD2420TimeoutNumber,
number.ApplyNumber,
unit_of_measurement=UNIT_SECOND,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_TIMELAPSE,
@@ -70,23 +58,23 @@ CONFIG_SCHEMA = cv.Schema(
icon=ICON_MOTION_SENSOR,
),
cv.Inclusive(CONF_STILL_THRESHOLD, GATE_GROUP): number.number_schema(
LD2420StillThresholdNumbers,
number.ApplyNumber,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Inclusive(CONF_MOVE_THRESHOLD, GATE_GROUP): number.number_schema(
LD2420MoveThresholdNumbers,
number.ApplyNumber,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Optional(CONF_GATE_MOVE_SENSITIVITY): number.number_schema(
LD2420MoveSensFactorNumber,
number.ApplyNumber,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_SCALE,
),
cv.Optional(CONF_GATE_STILL_SENSITIVITY): number.number_schema(
LD2420StillSensFactorNumber,
number.ApplyNumber,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_SCALE,
@@ -98,12 +86,12 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
cv.Optional(f"gate_{x}"): (
{
cv.Required(CONF_MOVE_THRESHOLD): number.number_schema(
LD2420MoveThresholdNumbers,
number.ApplyNumber,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Required(CONF_STILL_THRESHOLD): number.number_schema(
LD2420StillThresholdNumbers,
number.ApplyNumber,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
@@ -123,6 +111,8 @@ async def to_code(config: ConfigType) -> None:
min_value=0,
max_value=255,
step=5,
apply="new_config.timeout = value",
publish="before",
)
await numbers(
CONF_MIN_GATE_DISTANCE,
@@ -144,6 +134,8 @@ async def to_code(config: ConfigType) -> None:
min_value=0.05,
max_value=1,
step=0.025,
apply="gate_move_sensitivity_factor = value",
publish="before",
)
await numbers(
CONF_GATE_STILL_SENSITIVITY,
@@ -151,7 +143,16 @@ async def to_code(config: ConfigType) -> None:
min_value=0.05,
max_value=1,
step=0.025,
apply="gate_still_sensitivity_factor = value",
publish="before",
)
threshold = {
"min_value": 0,
"max_value": 65535,
"step": 25,
"apply": "set_gate_threshold_value(entity, value)",
"publish": "before",
}
if config.get(CONF_GATE_SELECT):
await numbers(
CONF_GATE_SELECT,
@@ -160,35 +161,27 @@ async def to_code(config: ConfigType) -> None:
max_value=15,
step=1,
)
if gate_still_threshold := config.get(CONF_STILL_THRESHOLD):
n = cg.new_Pvariable(gate_still_threshold[CONF_ID])
await number.register_number(
n, gate_still_threshold, min_value=0, max_value=65535, step=25
)
await cg.register_parented(n, hub)
cg.add(hub.set_gate_still_threshold_numbers(0, n))
if gate_move_threshold := config.get(CONF_MOVE_THRESHOLD):
n = cg.new_Pvariable(gate_move_threshold[CONF_ID])
await number.register_number(
n, gate_move_threshold, min_value=0, max_value=65535, step=25
)
await cg.register_parented(n, hub)
cg.add(hub.set_gate_move_threshold_numbers(0, n))
# With gate_select one pair of numbers sits at index 0 and edits the selected gate
await numbers(
CONF_STILL_THRESHOLD,
partial(hub.set_gate_still_threshold_numbers, 0),
**threshold,
)
await numbers(
CONF_MOVE_THRESHOLD,
partial(hub.set_gate_move_threshold_numbers, 0),
**threshold,
)
else:
for x in range(16):
if gate_conf := config.get(f"gate_{x}"):
move_config = gate_conf[CONF_MOVE_THRESHOLD]
n = cg.new_Pvariable(move_config[CONF_ID], x)
await number.register_number(
n, move_config, min_value=0, max_value=65535, step=25
await numbers.create(
gate_conf[CONF_MOVE_THRESHOLD],
partial(hub.set_gate_move_threshold_numbers, x),
**threshold,
)
await cg.register_parented(n, hub)
cg.add(hub.set_gate_move_threshold_numbers(x, n))
still_config = gate_conf[CONF_STILL_THRESHOLD]
n = cg.new_Pvariable(still_config[CONF_ID], x)
await number.register_number(
n, still_config, min_value=0, max_value=65535, step=25
await numbers.create(
gate_conf[CONF_STILL_THRESHOLD],
partial(hub.set_gate_still_threshold_numbers, x),
**threshold,
)
await cg.register_parented(n, hub)
cg.add(hub.set_gate_still_threshold_numbers(x, n))
@@ -6,11 +6,6 @@ ESPHOME_LOG_TAG(TAG, "ld2420.number");
namespace esphome::ld2420 {
void LD2420TimeoutNumber::control(float timeout) {
this->publish_state(timeout);
this->parent_->new_config.timeout = timeout;
}
void LD2420MinDistanceNumber::control(float min_gate) {
if ((uint16_t) min_gate > this->parent_->new_config.max_gate) {
min_gate = this->parent_->get_min_gate_distance_value();
@@ -36,36 +31,4 @@ void LD2420GateSelectNumber::control(float gate_select) {
this->parent_->publish_gate_still_threshold(gate);
}
void LD2420MoveSensFactorNumber::control(float move_factor) {
this->publish_state(move_factor);
this->parent_->gate_move_sensitivity_factor = move_factor;
}
void LD2420StillSensFactorNumber::control(float still_factor) {
this->publish_state(still_factor);
this->parent_->gate_still_sensitivity_factor = still_factor;
}
LD2420MoveThresholdNumbers::LD2420MoveThresholdNumbers(uint8_t gate) : gate_(gate) {}
void LD2420MoveThresholdNumbers::control(float move_threshold) {
this->publish_state(move_threshold);
if (!this->parent_->is_gate_select()) {
this->parent_->new_config.move_thresh[this->gate_] = move_threshold;
} else {
this->parent_->new_config.move_thresh[this->parent_->get_gate_select_value()] = move_threshold;
}
}
LD2420StillThresholdNumbers::LD2420StillThresholdNumbers(uint8_t gate) : gate_(gate) {}
void LD2420StillThresholdNumbers::control(float still_threshold) {
this->publish_state(still_threshold);
if (!this->parent_->is_gate_select()) {
this->parent_->new_config.still_thresh[this->gate_] = still_threshold;
} else {
this->parent_->new_config.still_thresh[this->parent_->get_gate_select_value()] = still_threshold;
}
}
} // namespace esphome::ld2420
@@ -5,14 +5,6 @@
namespace esphome::ld2420 {
class LD2420TimeoutNumber final : public number::Number, public Parented<LD2420Component> {
public:
LD2420TimeoutNumber() = default;
protected:
void control(float timeout) override;
};
class LD2420MinDistanceNumber final : public number::Number, public Parented<LD2420Component> {
public:
LD2420MinDistanceNumber() = default;
@@ -37,40 +29,4 @@ class LD2420GateSelectNumber final : public number::Number, public Parented<LD24
void control(float gate_select) override;
};
class LD2420MoveSensFactorNumber final : public number::Number, public Parented<LD2420Component> {
public:
LD2420MoveSensFactorNumber() = default;
protected:
void control(float move_factor) override;
};
class LD2420StillSensFactorNumber final : public number::Number, public Parented<LD2420Component> {
public:
LD2420StillSensFactorNumber() = default;
protected:
void control(float still_factor) override;
};
class LD2420StillThresholdNumbers final : public number::Number, public Parented<LD2420Component> {
public:
LD2420StillThresholdNumbers() = default;
LD2420StillThresholdNumbers(uint8_t gate);
protected:
uint8_t gate_;
void control(float still_threshold) override;
};
class LD2420MoveThresholdNumbers final : public number::Number, public Parented<LD2420Component> {
public:
LD2420MoveThresholdNumbers() = default;
LD2420MoveThresholdNumbers(uint8_t gate);
protected:
uint8_t gate_;
void control(float move_threshold) override;
};
} // namespace esphome::ld2420
+7 -1
View File
@@ -957,7 +957,13 @@ void LD2450Component::set_direction_text_sensor(uint8_t target, text_sensor::Tex
// Send Zone coordinates data to LD2450
#ifdef USE_NUMBER
void LD2450Component::set_zone_coordinate(uint8_t zone) {
void LD2450Component::set_zone_coordinate(number::Number *n) {
uint8_t zone = 0;
while (zone < MAX_ZONES && this->zone_numbers_[zone].x1 != n && this->zone_numbers_[zone].y1 != n &&
this->zone_numbers_[zone].x2 != n && this->zone_numbers_[zone].y2 != n)
zone++;
if (zone == MAX_ZONES)
return;
number::Number *x1sens = this->zone_numbers_[zone].x1;
number::Number *y1sens = this->zone_numbers_[zone].y1;
number::Number *x2sens = this->zone_numbers_[zone].x2;
+1 -1
View File
@@ -128,7 +128,7 @@ class LD2450Component : public Component, public uart::UARTDevice {
void set_direction_text_sensor(uint8_t target, text_sensor::TextSensor *s);
#endif
#ifdef USE_NUMBER
void set_zone_coordinate(uint8_t zone);
void set_zone_coordinate(number::Number *n);
void set_zone_numbers(uint8_t zone, number::Number *x1, number::Number *y1, number::Number *x2, number::Number *y2);
#endif
#ifdef USE_SENSOR
+21 -33
View File
@@ -25,7 +25,6 @@ ICON_ARROW_TOP_LEFT_BOLD_BOX_OUTLINE = "mdi:arrow-top-left-bold-box-outline"
MAX_ZONES = 3
PresenceTimeoutNumber = ld2450_ns.class_("PresenceTimeoutNumber", number.Number)
ZoneCoordinateNumber = ld2450_ns.class_("ZoneCoordinateNumber", number.Number)
CONFIG_SCHEMA = cv.Schema(
{
@@ -45,28 +44,28 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
cv.Optional(f"zone_{n + 1}"): cv.Schema(
{
cv.Required(CONF_X1): number.number_schema(
ZoneCoordinateNumber,
number.ApplyNumber,
device_class=DEVICE_CLASS_DISTANCE,
unit_of_measurement=UNIT_MILLIMETER,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_ARROW_TOP_LEFT_BOLD_BOX_OUTLINE,
),
cv.Required(CONF_Y1): number.number_schema(
ZoneCoordinateNumber,
number.ApplyNumber,
device_class=DEVICE_CLASS_DISTANCE,
unit_of_measurement=UNIT_MILLIMETER,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_ARROW_TOP_LEFT,
),
cv.Required(CONF_X2): number.number_schema(
ZoneCoordinateNumber,
number.ApplyNumber,
device_class=DEVICE_CLASS_DISTANCE,
unit_of_measurement=UNIT_MILLIMETER,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_ARROW_BOTTOM_RIGHT_BOLD_BOX_OUTLINE,
),
cv.Required(CONF_Y2): number.number_schema(
ZoneCoordinateNumber,
number.ApplyNumber,
device_class=DEVICE_CLASS_DISTANCE,
unit_of_measurement=UNIT_MILLIMETER,
entity_category=ENTITY_CATEGORY_CONFIG,
@@ -91,32 +90,21 @@ async def to_code(config: ConfigType) -> None:
)
for zone_num in range(MAX_ZONES):
if zone_conf := config.get(f"zone_{zone_num + 1}"):
zone_x1_config = zone_conf.get(CONF_X1)
x1 = cg.new_Pvariable(zone_x1_config[CONF_ID], zone_num)
await number.register_number(
x1, zone_x1_config, min_value=-4860, max_value=4860, step=1
)
await cg.register_parented(x1, hub)
zone_y1_config = zone_conf.get(CONF_Y1)
y1 = cg.new_Pvariable(zone_y1_config[CONF_ID], zone_num)
await number.register_number(
y1, zone_y1_config, min_value=0, max_value=7560, step=1
)
await cg.register_parented(y1, hub)
zone_x2_config = zone_conf.get(CONF_X2)
x2 = cg.new_Pvariable(zone_x2_config[CONF_ID], zone_num)
await number.register_number(
x2, zone_x2_config, min_value=-4860, max_value=4860, step=1
)
await cg.register_parented(x2, hub)
zone_y2_config = zone_conf.get(CONF_Y2)
y2 = cg.new_Pvariable(zone_y2_config[CONF_ID], zone_num)
await number.register_number(
y2, zone_y2_config, min_value=0, max_value=7560, step=1
)
await cg.register_parented(y2, hub)
x1, y1, x2, y2 = [
await numbers.create(
zone_conf[key],
None,
min_value=lo,
max_value=hi,
step=1,
apply="set_zone_coordinate(entity)",
publish="before",
)
for key, lo, hi in (
(CONF_X1, -4860, 4860),
(CONF_Y1, 0, 7560),
(CONF_X2, -4860, 4860),
(CONF_Y2, 0, 7560),
)
]
cg.add(hub.set_zone_numbers(zone_num, x1, y1, x2, y2))
@@ -1,12 +0,0 @@
#include "zone_coordinate_number.h"
namespace esphome::ld2450 {
ZoneCoordinateNumber::ZoneCoordinateNumber(uint8_t zone) : zone_(zone) {}
void ZoneCoordinateNumber::control(float value) {
this->publish_state(value);
this->parent_->set_zone_coordinate(this->zone_);
}
} // namespace esphome::ld2450
@@ -1,17 +0,0 @@
#pragma once
#include "esphome/components/number/number.h"
#include "../ld2450.h"
namespace esphome::ld2450 {
class ZoneCoordinateNumber : public number::Number, public Parented<LD2450Component> {
public:
ZoneCoordinateNumber(uint8_t zone);
protected:
uint8_t zone_;
void control(float value) override;
};
} // namespace esphome::ld2450
+2 -4
View File
@@ -67,11 +67,9 @@ async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID], gpio)
await cg.register_component(var, config)
await output.register_output(var, config)
if CONF_CHANNEL in config:
cg.add(var.set_channel(config[CONF_CHANNEL]))
cg.add_optional(var.set_channel, config.get(CONF_CHANNEL))
cg.add(var.set_frequency(config[CONF_FREQUENCY]))
if CONF_PHASE_ANGLE in config:
cg.add(var.set_phase_angle(config[CONF_PHASE_ANGLE]))
cg.add_optional(var.set_phase_angle, config.get(CONF_PHASE_ANGLE))
automation.register_apply_action(
+2 -4
View File
@@ -85,10 +85,8 @@ async def to_code(config: ConfigType) -> None:
)
cg.add(var.set_keys(table))
cg.add(var.set_debounce_time(config[CONF_DEBOUNCE_TIME]))
if CONF_HAS_DIODES in config:
cg.add(var.set_has_diodes(config[CONF_HAS_DIODES]))
if CONF_HAS_PULLDOWNS in config:
cg.add(var.set_has_pulldowns(config[CONF_HAS_PULLDOWNS]))
cg.add_optional(var.set_has_diodes, config.get(CONF_HAS_DIODES))
cg.add_optional(var.set_has_pulldowns, config.get(CONF_HAS_PULLDOWNS))
for conf in config.get(CONF_ON_KEY, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
cg.add(var.register_key_trigger(trigger))
+1 -2
View File
@@ -88,8 +88,7 @@ async def to_code(config: ConfigType) -> None:
cg.add(var.set_warmup_seconds(config[CONF_WARMUP_TIME]))
if CONF_DETECTION_RANGE in config:
cg.add(var.set_detection_range(config[CONF_DETECTION_RANGE]))
cg.add_optional(var.set_detection_range, config.get(CONF_DETECTION_RANGE))
NO_ARGS_ACTION_SCHEMA = maybe_simple_id(
+10 -12
View File
@@ -1,4 +1,3 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.types import ConfigType
@@ -9,12 +8,9 @@ from .. import (
CONF_MICRONOVA_ID,
MICRONOVA_ADDRESS_SCHEMA,
MicroNova,
micronova_ns,
register_micronova_writer,
)
MicroNovaButton = micronova_ns.class_("MicroNovaButton", button.Button, cg.Component)
CONF_CUSTOM_BUTTON = "custom_button"
CONF_MEMORY_DATA = "memory_data"
@@ -22,7 +18,7 @@ CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova),
cv.Optional(CONF_CUSTOM_BUTTON): button.button_schema(
MicroNovaButton,
button.ApplyButton,
)
.extend(
MICRONOVA_ADDRESS_SCHEMA(
@@ -35,11 +31,13 @@ CONFIG_SCHEMA = cv.Schema(
async def to_code(config: ConfigType) -> None:
mv = await cg.get_variable(config[CONF_MICRONOVA_ID])
if custom_button_config := config.get(CONF_CUSTOM_BUTTON):
if conf := config.get(CONF_CUSTOM_BUTTON):
register_micronova_writer()
bt = await button.new_button(custom_button_config, mv)
cg.add(bt.set_memory_location(custom_button_config[CONF_MEMORY_LOCATION]))
cg.add(bt.set_memory_address(custom_button_config[CONF_MEMORY_ADDRESS]))
cg.add(bt.set_memory_data(custom_button_config[CONF_MEMORY_DATA]))
location = conf[CONF_MEMORY_LOCATION]
address = conf[CONF_MEMORY_ADDRESS]
data = conf[CONF_MEMORY_DATA]
await button.sub_buttons(config, parent=config[CONF_MICRONOVA_ID]).create(
conf,
None,
apply=f"queue_write_command(0x{location:02X}, 0x{address:02X}, 0x{data:02X})",
)
@@ -1,16 +0,0 @@
#include "micronova_button.h"
namespace esphome::micronova {
ESPHOME_LOG_TAG(TAG, "micronova.button");
void MicroNovaButton::dump_config() {
LOG_BUTTON("", "Micronova button", this);
this->dump_base_config();
}
void MicroNovaButton::press_action() {
this->micronova_->queue_write_command(this->memory_location_, this->memory_address_, this->memory_data_);
}
} // namespace esphome::micronova
@@ -1,23 +0,0 @@
#pragma once
#include "esphome/components/micronova/micronova.h"
#include "esphome/core/component.h"
#include "esphome/components/button/button.h"
namespace esphome::micronova {
class MicroNovaButton final : public Component, public button::Button, public MicroNovaBaseListener {
public:
MicroNovaButton(MicroNova *m) : MicroNovaBaseListener(m) {}
void dump_config() override;
void set_memory_data(uint8_t f) { this->memory_data_ = f; }
uint8_t get_memory_data() { return this->memory_data_; }
protected:
void press_action() override;
uint8_t memory_data_ = 0;
};
} // namespace esphome::micronova
+1 -2
View File
@@ -427,8 +427,7 @@ async def to_code(config: ConfigType) -> None:
# Only a build with a datasheet model links the power law and its libm
cg.add_define("USE_MICS_5524_DATASHEET")
cg.add(var.set_vcc(config[CONF_VCC]))
if (rl := config.get(CONF_RL)) is not None:
cg.add(var.set_rl(rl))
cg.add_optional(var.set_rl, config.get(CONF_RL))
cg.add(var.set_voltage_multiplier(_voltage_multiplier(config)))
cg.add(var.set_samples(config[CONF_SAMPLE_COUNT]))
cg.add(var.set_warmup_time(config[CONF_WARMUP_TIME]))
+4 -8
View File
@@ -225,16 +225,12 @@ async def to_code(config):
cg.add(var.set_beeper_feedback(config[CONF_BEEPER]))
cg.add(var.set_autoconf(config[CONF_AUTOCONF]))
cg.add(var.set_display_control(config[CONF_DISPLAY_CONTROL]))
if CONF_SUPPORTED_MODES in config:
cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES]))
cg.add_optional(var.set_supported_modes, config.get(CONF_SUPPORTED_MODES))
if CONF_SUPPORTED_SWING_MODES in config:
cg.add(var.set_supported_swing_modes(config[CONF_SUPPORTED_SWING_MODES]))
if CONF_SUPPORTED_PRESETS in config:
cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS]))
if CONF_CUSTOM_PRESETS in config:
cg.add(var.set_custom_presets(config[CONF_CUSTOM_PRESETS]))
if CONF_CUSTOM_FAN_MODES in config:
cg.add(var.set_custom_fan_modes(config[CONF_CUSTOM_FAN_MODES]))
cg.add_optional(var.set_supported_presets, config.get(CONF_SUPPORTED_PRESETS))
cg.add_optional(var.set_custom_presets, config.get(CONF_CUSTOM_PRESETS))
cg.add_optional(var.set_custom_fan_modes, config.get(CONF_CUSTOM_FAN_MODES))
sensors = sensor.sub_sensors(config)
await sensors(CONF_OUTDOOR_TEMPERATURE, var.set_outdoor_temperature_sensor)
await sensors(CONF_POWER_USAGE, var.set_power_sensor)
+14 -32
View File
@@ -40,8 +40,8 @@ const uint8_t MITSUBISHI_NIGHTMODE = 0xC1;
// Pulse parameters in usec
const uint16_t MITSUBISHI_BIT_MARK = 430;
const uint16_t MITSUBISHI_ONE_SPACE = 1250;
const uint16_t MITSUBISHI_ZERO_SPACE = 390;
static constexpr remote_base::BitTimings MITSUBISHI_BIT_TIMINGS{
.bit_mark = MITSUBISHI_BIT_MARK, .one_space = 1250, .zero_space = 390};
const uint16_t MITSUBISHI_HEADER_MARK = 3500;
const uint16_t MITSUBISHI_HEADER_SPACE = 1700;
const uint16_t MITSUBISHI_MIN_GAP = 17500;
@@ -102,8 +102,8 @@ void MitsubishiClimate::transmit_state() {
// Byte 15: HVAC specfic, i.e. POWERFUL, SMART SET, PLASMA, always 0x00
// Byte 16: Left vane control (for specific models). Constants match right vane controls.
// Byte 17: Checksum: SUM[Byte0...Byte16]
uint8_t remote_state[18] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t remote_state[MITSUBISHI_FRAME_SIZE] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
switch (this->mode) {
case climate::CLIMATE_MODE_HEAT:
@@ -259,13 +259,7 @@ void MitsubishiClimate::transmit_state() {
data->mark(MITSUBISHI_HEADER_MARK);
data->space(MITSUBISHI_HEADER_SPACE);
// Data
for (uint8_t i : remote_state) {
for (uint8_t j = 0; j < 8; j++) {
data->mark(MITSUBISHI_BIT_MARK);
bool bit = i & (1 << j);
data->space(bit ? MITSUBISHI_ONE_SPACE : MITSUBISHI_ZERO_SPACE);
}
}
data->item_bytes<MITSUBISHI_BIT_TIMINGS>(remote_state, sizeof(remote_state));
// Footer
if (r == 0) {
data->mark(MITSUBISHI_BIT_MARK);
@@ -280,33 +274,21 @@ void MitsubishiClimate::transmit_state() {
bool MitsubishiClimate::parse_state_frame_(const uint8_t frame[]) { return false; }
bool MitsubishiClimate::on_receive(remote_base::RemoteReceiveData data) {
uint8_t state_frame[18] = {};
uint8_t state_frame[MITSUBISHI_FRAME_SIZE];
if (!data.expect_item(MITSUBISHI_HEADER_MARK, MITSUBISHI_HEADER_SPACE)) {
ESP_LOGV(TAG, "Header fail");
return false;
}
for (uint8_t pos = 0; pos < 18; pos++) {
uint8_t byte = 0;
for (int8_t bit = 0; bit < 8; bit++) {
if (data.expect_item(MITSUBISHI_BIT_MARK, MITSUBISHI_ONE_SPACE)) {
byte |= 1 << bit;
} else if (!data.expect_item(MITSUBISHI_BIT_MARK, MITSUBISHI_ZERO_SPACE)) {
ESP_LOGV(TAG, "Byte %d bit %d fail", pos, bit);
return false;
}
}
state_frame[pos] = byte;
// Check Header && Footer. Byte 16 only ever carries the left vane position (bits 3,4,5).
if ((pos == 0 && byte != MITSUBISHI_BYTE00) || (pos == 1 && byte != MITSUBISHI_BYTE01) ||
(pos == 2 && byte != MITSUBISHI_BYTE02) || (pos == 3 && byte != MITSUBISHI_BYTE03) ||
(pos == 4 && byte != MITSUBISHI_BYTE04) || (pos == 13 && byte != MITSUBISHI_BYTE13) ||
(pos == 16 && (byte & ~MITSUBISHI_VERTICAL_VANE_MASK) != 0)) {
ESP_LOGV(TAG, "Bytes 0,1,2,3,4,13 or 16 fail - invalid value");
return false;
}
// Check Header && Footer. Byte 16 only ever carries the left vane position (bits 3,4,5).
if (!data.expect_bytes<MITSUBISHI_BIT_TIMINGS>(state_frame, sizeof(state_frame)) ||
state_frame[0] != MITSUBISHI_BYTE00 || state_frame[1] != MITSUBISHI_BYTE01 ||
state_frame[2] != MITSUBISHI_BYTE02 || state_frame[3] != MITSUBISHI_BYTE03 ||
state_frame[4] != MITSUBISHI_BYTE04 || state_frame[13] != MITSUBISHI_BYTE13 ||
(state_frame[16] & ~MITSUBISHI_VERTICAL_VANE_MASK) != 0) {
ESP_LOGV(TAG, "Bit decode or bytes 0,1,2,3,4,13 or 16 fail");
return false;
}
// On/Off and Mode
+1 -2
View File
@@ -84,5 +84,4 @@ async def to_code(config: ConfigType) -> None:
await sensors(CONF_FIELD_STRENGTH_Y, var.set_y_sensor)
await sensors(CONF_FIELD_STRENGTH_Z, var.set_z_sensor)
await sensors(CONF_HEADING, var.set_heading_sensor)
if CONF_AUTO_SET_RESET in config:
cg.add(var.set_auto_set_reset(config[CONF_AUTO_SET_RESET]))
cg.add_optional(var.set_auto_set_reset, config.get(CONF_AUTO_SET_RESET))
@@ -451,8 +451,7 @@ async def add_modbus_base_properties(
lambda_param_type: cg.MockObj = cg.float_,
lambda_return_type: Any = float,
) -> None:
if CONF_CUSTOM_PDU in config:
cg.add(var.set_custom_pdu(config[CONF_CUSTOM_PDU]))
cg.add_optional(var.set_custom_pdu, config.get(CONF_CUSTOM_PDU))
if config[CONF_RESPONSE_SIZE] > 0:
cg.add(var.set_register_size(config[CONF_RESPONSE_SIZE]))
@@ -122,8 +122,7 @@ async def to_code(config: ConfigType) -> None:
cg.add(var.set_tank_empty(CONF_SUPPORTED_TANKS_MAP[t][0]))
cg.add(var.set_tank_full(CONF_SUPPORTED_TANKS_MAP[t][1]))
if CONF_PROPANE_BUTANE_MIX in config:
cg.add(var.set_propane_butane_mix(config[CONF_PROPANE_BUTANE_MIX]))
cg.add_optional(var.set_propane_butane_mix, config.get(CONF_PROPANE_BUTANE_MIX))
sensors = sensor.sub_sensors(config)
await sensors(CONF_TEMPERATURE, var.set_temperature)

Some files were not shown because too many files have changed in this diff Show More