Add Daikin local-API driver — MAC-matched subnet sweep instead of UDP broadcast
Daikin BRP069/BRP072-class units are WiFi/DHCP with no fixed IP. hvac's backend runs as a Docker container on its own bridge network (not directly on the LXC's admin-VLAN NIC), so UDP broadcast discovery risked silently failing to egress — instead, every unit is identified by MAC (external_ref), and a background poller periodically HTTP-sweeps the configured subnet CIDR and matches replies by MAC, self-healing zone_devices.device_ip whenever a lease changes. Only needs plain outbound HTTP, same path already proven by the MHI Modbus TCP driver. - lib/drivers/daikin.js: discover()/getStatus()/setTarget() against the local /common/basic_info + /aircon/get_control_info + /aircon/set_control_info API (confirmed live against firmware 4_2_303, 2026-08-25). Mode/fan-rate encoding taken from community reverse-engineering, not yet verified against a live setTarget() call — flagged in comments for first real control test. - lib/daikin-poller.js: 60s status poll (cached IP) + 5min subnet resweep (self-heal IP + auto-register new units, never auto-assigned to a zone). - lib/device-registry.js: shared discover-upsert, extracted so both the manual Discover button and the background sweep record device_ip the same way. - ac-control.js / routes/override.js: daikin-status/daikin-control endpoints and commandAcDevice() branch, mirroring the existing MHI control path. - Settings page: new "Daikin subnet (CIDR)" field (daikin_subnet_cidr config key) — sweeps do nothing until this is set. - Frontend: DaikinControlPanel.tsx (on/off/mode/setpoint/fan), wired into ZoneDetailModal; daikin moved from NOT_YET_IMPLEMENTED to a real driver. Backend node --check and frontend tsc --noEmit both clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
5fc576aabd
commit
373f66ef5c
12 changed files with 559 additions and 27 deletions
|
|
@ -23,6 +23,7 @@ export default function Settings() {
|
|||
const [defaultDeparture, setDefaultDeparture] = useState('10:00:00')
|
||||
const [maintUrl, setMaintUrl] = useState('')
|
||||
const [maintKey, setMaintKey] = useState('')
|
||||
const [daikinCidr, setDaikinCidr] = useState('')
|
||||
const [configSaving, setConfigSaving] = useState(false)
|
||||
|
||||
// Default occupied/vacant templates — every room inherits these unless it sets its
|
||||
|
|
@ -57,6 +58,7 @@ export default function Settings() {
|
|||
setDefaultDeparture(cfg.default_departure_time ?? '10:00:00')
|
||||
setMaintUrl(cfg.maintenance_url ?? '')
|
||||
setMaintKey(cfg.maintenance_api_key ?? '')
|
||||
setDaikinCidr(cfg.daikin_subnet_cidr ?? '')
|
||||
// Merge onto the shipped default rather than replacing outright — an hvac_defaults
|
||||
// row saved before weather compensation existed won't have ac_occupied_tiers yet.
|
||||
setDefaults({ ...DEFAULT_HVAC_TEMPLATE, ...cfg.hvac_defaults, ac_occupied_tiers: cfg.hvac_defaults?.ac_occupied_tiers ?? DEFAULT_HVAC_TEMPLATE.ac_occupied_tiers })
|
||||
|
|
@ -130,6 +132,7 @@ export default function Settings() {
|
|||
updateConfig('default_departure_time', defaultDeparture),
|
||||
updateConfig('maintenance_url', maintUrl),
|
||||
updateConfig('maintenance_api_key', maintKey),
|
||||
updateConfig('daikin_subnet_cidr', daikinCidr),
|
||||
])
|
||||
setMsg('Settings saved'); setTimeout(() => setMsg(''), 2500)
|
||||
} catch (e) {
|
||||
|
|
@ -425,6 +428,20 @@ export default function Settings() {
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div className="section-title">Daikin Aircon</div>
|
||||
<p className="field-hint" style={{ marginBottom: 10 }}>
|
||||
Daikin units are WiFi/DHCP, so their IP changes over time. Instead of a fixed IP, hvac
|
||||
periodically sweeps this LAN range over HTTP and matches units by their MAC address —
|
||||
set it once and it self-heals if a unit's IP changes. Run "Discover Daikin Split" on the
|
||||
Devices page after saving to find units for the first time.
|
||||
</p>
|
||||
<div className="field-row">
|
||||
<div className="field">
|
||||
<label>Daikin subnet (CIDR)</label>
|
||||
<input type="text" value={daikinCidr} onChange={e => setDaikinCidr(e.target.value)} placeholder="10.4.0.0/22" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="section-title">Maintenance Integration</div>
|
||||
<p className="field-hint" style={{ marginBottom: 10 }}>
|
||||
Not yet available — maintenance needs its own Settings → API Keys page first.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue