Merge remote-tracking branch 'origin/dev' into apply-entities-number

# Conflicts:
#	esphome/components/ld2450/number/__init__.py
This commit is contained in:
J. Nick Koston
2026-10-20 03:19:27 -10:00
312 changed files with 3899 additions and 2558 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:
+2 -1
View File
@@ -313,6 +313,7 @@ jobs:
- "3.12"
- "3.13"
- "3.14"
- "3.15"
os:
- ubuntu-latest
- macOS-latest
@@ -629,7 +630,7 @@ jobs:
apt-get install -y libc6-dbg
- name: Run CodSpeed benchmarks
uses: CodSpeedHQ/action@f842b9a3ffe213dbb81768fd7f7eeb4dfcf1c369 # v5.4.2
uses: CodSpeedHQ/action@821a40cd6c80e3cfe387caf55668287addd808da # v5.5.0
with:
run: |
. venv/bin/activate
+2 -2
View File
@@ -56,7 +56,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -84,6 +84,6 @@ jobs:
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
with:
category: "/language:${{matrix.language}}"
+1 -1
View File
@@ -14,4 +14,4 @@ jobs:
permissions:
issues: write # issues.lock on closed issues
pull-requests: write # issues.lock on closed pull requests
uses: esphome/workflows/.github/workflows/lock.yml@cc3e76de337dc59bc1cba8da58d963cd23b873f1 # 2026.9.0
uses: esphome/workflows/.github/workflows/lock.yml@31d160e5f0ffa0ee65d5b27cb55c153d415f2abe # 2026.10.2
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
# No GITHUB_TOKEN permissions: the reusable workflow mints an ESPHome
# GitHub App token so the labels, comments and closures come from
# esphome[bot] instead of github-actions[bot].
uses: esphome/workflows/.github/workflows/stale.yml@4fbce13026c393d6eb6b247e394dcd9ef1af76c2 # main
uses: esphome/workflows/.github/workflows/stale.yml@31d160e5f0ffa0ee65d5b27cb55c153d415f2abe # main
secrets:
ESPHOME_GITHUB_APP_PRIVATE_KEY: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
with:
+1 -1
View File
@@ -10,7 +10,7 @@ ci:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.17.0
rev: v0.17.1
hooks:
# Run the linter.
- id: ruff
+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).
+3 -1
View File
@@ -133,7 +133,7 @@ esphome/components/climate/* @esphome/core
esphome/components/climate_ir/* @glmnet
esphome/components/cm1106/* @andrewjswan
esphome/components/color_temperature/* @jesserockz
esphome/components/combination/* @Cat-Ion @kahrendt
esphome/components/combination/* @kahrendt
esphome/components/const/* @esphome/core
esphome/components/coolix/* @glmnet
esphome/components/copy/* @OttoWinter
@@ -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
+12
View File
@@ -637,6 +637,18 @@ float ATM90E32Component::get_chip_temperature_() {
return (float) ctemp;
}
void ATM90E32Component::press_calibration_button(EntityBase *entity, void (ATM90E32Component::*action)(),
const LogString *hint, const LogString *hint2) {
ESP_LOGI(TAG, "%s", entity->get_name().c_str());
if (hint != nullptr) {
ESP_LOGI(TAG, "%s", LOG_STR_ARG(hint));
}
if (hint2 != nullptr) {
ESP_LOGI(TAG, "%s", LOG_STR_ARG(hint2));
}
(this->*action)();
}
void ATM90E32Component::run_gain_calibrations() {
const char *cs = this->get_calibration_id_();
if (!this->enable_gain_calibration_) {
+3
View File
@@ -139,6 +139,9 @@ class ATM90E32Component final : public PollingComponent,
int16_t calibrate_offset(uint8_t phase, bool voltage);
int16_t calibrate_power_offset(uint8_t phase, bool reactive);
void run_gain_calibrations();
// Body of the calibration buttons: logs the button name and the hint lines, then runs action.
void press_calibration_button(EntityBase *entity, void (ATM90E32Component::*action)(),
const LogString *hint = nullptr, const LogString *hint2 = nullptr);
#ifdef USE_NUMBER
void set_reference_voltage(uint8_t phase, number::Number *ref_voltage) { ref_voltages_[phase] = ref_voltage; }
void set_reference_current(uint8_t phase, number::Number *ref_current) { ref_currents_[phase] = ref_current; }
+58 -74
View File
@@ -4,7 +4,6 @@ import esphome.config_validation as cv
from esphome.const import CONF_ID, ENTITY_CATEGORY_CONFIG, ICON_SCALE
from esphome.types import ConfigType
from .. import atm90e32_ns
from ..sensor import ATM90E32Component
CONF_RUN_GAIN_CALIBRATION = "run_gain_calibration"
@@ -14,83 +13,68 @@ CONF_CLEAR_OFFSET_CALIBRATION = "clear_offset_calibration"
CONF_RUN_POWER_OFFSET_CALIBRATION = "run_power_offset_calibration"
CONF_CLEAR_POWER_OFFSET_CALIBRATION = "clear_power_offset_calibration"
ATM90E32GainCalibrationButton = atm90e32_ns.class_(
"ATM90E32GainCalibrationButton", button.Button
)
ATM90E32ClearGainCalibrationButton = atm90e32_ns.class_(
"ATM90E32ClearGainCalibrationButton", button.Button
)
ATM90E32OffsetCalibrationButton = atm90e32_ns.class_(
"ATM90E32OffsetCalibrationButton", button.Button
)
ATM90E32ClearOffsetCalibrationButton = atm90e32_ns.class_(
"ATM90E32ClearOffsetCalibrationButton", button.Button
)
ATM90E32PowerOffsetCalibrationButton = atm90e32_ns.class_(
"ATM90E32PowerOffsetCalibrationButton", button.Button
)
ATM90E32ClearPowerOffsetCalibrationButton = atm90e32_ns.class_(
"ATM90E32ClearPowerOffsetCalibrationButton", button.Button
)
def _calibration_button(icon: str) -> cv.Schema:
return button.button_schema(
button.ApplyButton, entity_category=ENTITY_CATEGORY_CONFIG, icon=icon
)
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.use_id(ATM90E32Component),
cv.Optional(CONF_RUN_GAIN_CALIBRATION): button.button_schema(
ATM90E32GainCalibrationButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon="mdi:scale-balance",
),
cv.Optional(CONF_CLEAR_GAIN_CALIBRATION): button.button_schema(
ATM90E32ClearGainCalibrationButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon="mdi:delete",
),
cv.Optional(CONF_RUN_OFFSET_CALIBRATION): button.button_schema(
ATM90E32OffsetCalibrationButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_SCALE,
),
cv.Optional(CONF_CLEAR_OFFSET_CALIBRATION): button.button_schema(
ATM90E32ClearOffsetCalibrationButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon="mdi:delete",
),
cv.Optional(CONF_RUN_POWER_OFFSET_CALIBRATION): button.button_schema(
ATM90E32PowerOffsetCalibrationButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_SCALE,
),
cv.Optional(CONF_CLEAR_POWER_OFFSET_CALIBRATION): button.button_schema(
ATM90E32ClearPowerOffsetCalibrationButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon="mdi:delete",
),
cv.Optional(CONF_RUN_GAIN_CALIBRATION): _calibration_button("mdi:scale-balance"),
cv.Optional(CONF_CLEAR_GAIN_CALIBRATION): _calibration_button("mdi:delete"),
cv.Optional(CONF_RUN_OFFSET_CALIBRATION): _calibration_button(ICON_SCALE),
cv.Optional(CONF_CLEAR_OFFSET_CALIBRATION): _calibration_button("mdi:delete"),
cv.Optional(CONF_RUN_POWER_OFFSET_CALIBRATION): _calibration_button(ICON_SCALE),
cv.Optional(CONF_CLEAR_POWER_OFFSET_CALIBRATION): _calibration_button("mdi:delete"),
}
def _press(action: str, hint: str | None = None, hint2: str | None = None) -> str:
"""press_calibration_button() logs at most two hint lines."""
args = "".join(
f", {cg.LogStringLiteral(h)}" for h in (hint, hint2) if h is not None
)
return f"press_calibration_button(entity, &{ATM90E32Component}::{action}{args})"
async def to_code(config: ConfigType) -> None:
parent = await cg.get_variable(config[CONF_ID])
if run_gain := config.get(CONF_RUN_GAIN_CALIBRATION):
b = await button.new_button(run_gain)
await cg.register_parented(b, parent)
if clear_gain := config.get(CONF_CLEAR_GAIN_CALIBRATION):
b = await button.new_button(clear_gain)
await cg.register_parented(b, parent)
if run_offset := config.get(CONF_RUN_OFFSET_CALIBRATION):
b = await button.new_button(run_offset)
await cg.register_parented(b, parent)
if clear_offset := config.get(CONF_CLEAR_OFFSET_CALIBRATION):
b = await button.new_button(clear_offset)
await cg.register_parented(b, parent)
if run_power := config.get(CONF_RUN_POWER_OFFSET_CALIBRATION):
b = await button.new_button(run_power)
await cg.register_parented(b, parent)
if clear_power := config.get(CONF_CLEAR_POWER_OFFSET_CALIBRATION):
b = await button.new_button(clear_power)
await cg.register_parented(b, parent)
buttons = button.sub_buttons(config, parent=config[CONF_ID])
await buttons(
CONF_RUN_GAIN_CALIBRATION,
None,
apply=_press(
"run_gain_calibrations",
"[CALIBRATION] Use gain_ct: & gain_voltage: under each phase_x: in your config file to save these values",
),
)
await buttons(
CONF_CLEAR_GAIN_CALIBRATION, None, apply=_press("clear_gain_calibrations")
)
await buttons(
CONF_RUN_OFFSET_CALIBRATION,
None,
apply=_press(
"run_offset_calibrations",
"[CALIBRATION] **NOTE: CTs and ACVs must be 0 during this process. USB power only**",
"[CALIBRATION] Use offset_voltage: & offset_current: under each phase_x: in your config file to save these values",
),
)
await buttons(
CONF_CLEAR_OFFSET_CALIBRATION, None, apply=_press("clear_offset_calibrations")
)
await buttons(
CONF_RUN_POWER_OFFSET_CALIBRATION,
None,
apply=_press(
"run_power_offset_calibrations",
"[CALIBRATION] **NOTE: CTs must be 0 during this process. Voltage reference should be present**",
"[CALIBRATION] Use offset_active_power: & offset_reactive_power: under each phase_x: in your config file to save these values",
),
)
await buttons(
CONF_CLEAR_POWER_OFFSET_CALIBRATION,
None,
apply=_press("clear_power_offset_calibrations"),
)
@@ -1,77 +0,0 @@
#include "atm90e32_button.h"
#include "esphome/core/component.h"
#include "esphome/core/log.h"
namespace esphome::atm90e32 {
ESPHOME_LOG_TAG(TAG, "atm90e32.button");
void ATM90E32GainCalibrationButton::press_action() {
if (this->parent_ == nullptr) {
ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Gain Calibration button [%s]", this->get_name().c_str());
return;
}
ESP_LOGI(TAG, "%s", this->get_name().c_str());
ESP_LOGI(TAG,
"[CALIBRATION] Use gain_ct: & gain_voltage: under each phase_x: in your config file to save these values");
this->parent_->run_gain_calibrations();
}
void ATM90E32ClearGainCalibrationButton::press_action() {
if (this->parent_ == nullptr) {
ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Clear Gain button [%s]", this->get_name().c_str());
return;
}
ESP_LOGI(TAG, "%s", this->get_name().c_str());
this->parent_->clear_gain_calibrations();
}
void ATM90E32OffsetCalibrationButton::press_action() {
if (this->parent_ == nullptr) {
ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Offset Calibration button [%s]", this->get_name().c_str());
return;
}
ESP_LOGI(TAG, "%s", this->get_name().c_str());
ESP_LOGI(TAG, "[CALIBRATION] **NOTE: CTs and ACVs must be 0 during this process. USB power only**");
ESP_LOGI(TAG, "[CALIBRATION] Use offset_voltage: & offset_current: under each phase_x: in your config file to save "
"these values");
this->parent_->run_offset_calibrations();
}
void ATM90E32ClearOffsetCalibrationButton::press_action() {
if (this->parent_ == nullptr) {
ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Clear Offset button [%s]", this->get_name().c_str());
return;
}
ESP_LOGI(TAG, "%s", this->get_name().c_str());
this->parent_->clear_offset_calibrations();
}
void ATM90E32PowerOffsetCalibrationButton::press_action() {
if (this->parent_ == nullptr) {
ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Power Calibration button [%s]", this->get_name().c_str());
return;
}
ESP_LOGI(TAG, "%s", this->get_name().c_str());
ESP_LOGI(TAG, "[CALIBRATION] **NOTE: CTs must be 0 during this process. Voltage reference should be present**");
ESP_LOGI(TAG, "[CALIBRATION] Use offset_active_power: & offset_reactive_power: under each phase_x: in your config "
"file to save these values");
this->parent_->run_power_offset_calibrations();
}
void ATM90E32ClearPowerOffsetCalibrationButton::press_action() {
if (this->parent_ == nullptr) {
ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Clear Power button [%s]", this->get_name().c_str());
return;
}
ESP_LOGI(TAG, "%s", this->get_name().c_str());
this->parent_->clear_power_offset_calibrations();
}
} // namespace esphome::atm90e32
@@ -1,57 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/atm90e32/atm90e32.h"
#include "esphome/components/button/button.h"
namespace esphome::atm90e32 {
class ATM90E32GainCalibrationButton final : public button::Button, public Parented<ATM90E32Component> {
public:
ATM90E32GainCalibrationButton() = default;
protected:
void press_action() override;
};
class ATM90E32ClearGainCalibrationButton final : public button::Button, public Parented<ATM90E32Component> {
public:
ATM90E32ClearGainCalibrationButton() = default;
protected:
void press_action() override;
};
class ATM90E32OffsetCalibrationButton final : public button::Button, public Parented<ATM90E32Component> {
public:
ATM90E32OffsetCalibrationButton() = default;
protected:
void press_action() override;
};
class ATM90E32ClearOffsetCalibrationButton final : public button::Button, public Parented<ATM90E32Component> {
public:
ATM90E32ClearOffsetCalibrationButton() = default;
protected:
void press_action() override;
};
class ATM90E32PowerOffsetCalibrationButton final : public button::Button, public Parented<ATM90E32Component> {
public:
ATM90E32PowerOffsetCalibrationButton() = default;
protected:
void press_action() override;
};
class ATM90E32ClearPowerOffsetCalibrationButton final : public button::Button, public Parented<ATM90E32Component> {
public:
ATM90E32ClearPowerOffsetCalibrationButton() = default;
protected:
void press_action() override;
};
} // namespace esphome::atm90e32
+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 -1
View File
@@ -17,7 +17,7 @@ from esphome.const import (
from esphome.core.entity_helpers import inherit_property_from
from esphome.types import ConfigType
CODEOWNERS = ["@Cat-Ion", "@kahrendt"]
CODEOWNERS = ["@kahrendt"]
combination_ns = cg.esphome_ns.namespace("combination")
+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"
+7 -20
View File
@@ -1,29 +1,18 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_ID,
CONF_SOURCE_ID,
)
from esphome.core.entity_helpers import inherit_property_from
from esphome.types import ConfigType
from .. import copy_ns
CopyButton = copy_ns.class_("CopyButton", button.Button, cg.Component)
CONFIG_SCHEMA = (
button.button_schema(CopyButton)
.extend(
{
cv.Required(CONF_SOURCE_ID): cv.use_id(button.Button),
}
)
.extend(cv.COMPONENT_SCHEMA)
CONFIG_SCHEMA = button.button_schema(button.ApplyButton).extend(
{
cv.Required(CONF_SOURCE_ID): cv.use_id(button.Button),
}
)
FINAL_VALIDATE_SCHEMA = cv.All(
@@ -34,8 +23,6 @@ FINAL_VALIDATE_SCHEMA = cv.All(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await button.register_button(var, config)
await cg.register_component(var, config)
await cg.add_variable(var.set_source, config[CONF_SOURCE_ID])
await button.sub_buttons(config, parent=config[CONF_SOURCE_ID]).create(
config, None, apply="press()"
)
@@ -1,12 +0,0 @@
#include "copy_button.h"
#include "esphome/core/log.h"
namespace esphome::copy {
ESPHOME_LOG_TAG(TAG, "copy.button");
void CopyButton::dump_config() { LOG_BUTTON("", "Copy Button", this); }
void CopyButton::press_action() { source_->press(); }
} // namespace esphome::copy
@@ -1,19 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/button/button.h"
namespace esphome::copy {
class CopyButton final : public button::Button, public Component {
public:
void set_source(button::Button *source) { source_ = source; }
void dump_config() override;
protected:
void press_action() override;
button::Button *source_;
};
} // namespace esphome::copy
+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]
+4
View File
@@ -549,6 +549,10 @@ bool LD2410Component::handle_ack_data_() {
break;
case CMD_QUERY: { // Query parameters response
// Config values can contain the footer bytes, so wait for the whole 38 byte frame
if (this->buffer_pos_ < 38) {
return false;
}
if (this->buffer_data_[10] != HEADER)
return true; // value head=0xAA
#ifdef USE_NUMBER
+4 -1
View File
@@ -116,7 +116,10 @@ void LD2410SComponent::loop() {
#ifdef USE_LD2410S_PARAMETERS
} else if (this->parameters_dirty_) {
this->parameters_dirty_ = false;
this->start_sequence_(WRITE_SEQUENCE, WRITE_SEQUENCE_LENGTH, this->next_send_at_);
// Keep the pause after the last acknowledgement; a start time further ahead than that is long past,
// and after 2^31 ms the run_sequence_() check would read it as the future
const bool in_gap = this->next_send_at_ - now <= COMMAND_GAP_MS;
this->start_sequence_(WRITE_SEQUENCE, WRITE_SEQUENCE_LENGTH, in_gap ? this->next_send_at_ : now);
#endif
}
}
+12
View File
@@ -683,6 +683,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
this->handle_cmd_error(this->cmd_reply_.error);
}
}
this->cmd_active_ = false; // Resend and give-up are done; let loop() read the UART again
return error;
}
@@ -916,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;

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