Releases: foXaCe/ajax-security-hass
Releases · foXaCe/ajax-security-hass
v0.28.1
Fixed
ajax_armed/ajax_disarmed/ajax_armed_night/ajax_armed_homebus events fire even when REST polling consumed the state change first (#133, thanks @Kolia56). The previous gateif state_changed:in SSE/SQS_handle_security_eventsuppressed the fire wheneverold_state == new_state— typically when REST had already updated the local state via optimistic update — so automations listening to the bus never sawnightmodeon/nightmodeofftransitions in particular. The coordinator's_skip_state_change_eventflag already guarantees the REST poller doesn't emit a duplicate, so the gate was redundant and harmful.
Changed
- Cleaned up the now-explanatory comment around the unconditional fire (no behaviour change).
v0.28.0
Added
- Dynamic device discovery via dispatcher signals. Devices, video edges and smart locks added to the Ajax account between two coordinator refreshes are now picked up live instead of waiting for a full HA restart. New
SIGNAL_NEW_DEVICEandSIGNAL_NEW_VIDEO_EDGEsignals (alongside the existingSIGNAL_NEW_SMART_LOCK); every entity platform that owns per-device entities (binary_sensor, sensor, event, switch, light, number, select, valve, update, camera) listens to the relevant signal and creates entities throughasync_dispatcher_connect, with entity-registry guard against duplicates. verify_ssltogglable from the reconfigure flow. Users on a self-signed proxy certificate no longer have to delete and recreate the entry to flip the SSL verification flag. Strings + 7 translations updated; reconfigure step exposes the field with a description explaining the self-signed proxy use case.- 2FA proxy session bootstrap is now complete. After a successful 2FA verification in proxy mode the API client captures the same auth payload as a normal proxy login:
refreshToken,userId,sseUrl,apiKey, plus the same TTL bookkeeping. Without this, a 2FA-protected proxy account had no SSE endpoint and no API key after first login, forcing a full reconfigure to recover. Hubs discovery after 2FA now also degrades gracefully when the proxy doesn't expose/hubsto a freshly-2FA'd session. - Translations test suite under
tests/(with the necessary.gitignorecarve-out sotests/test_*.pyis tracked while root-level scratch tests stay ignored). Smoke-tests thatstrings.jsonand everytranslations/<lang>.jsonare valid JSON and share the same key tree.
Changed
- Dependency bumps (dependabot):
aiobotocore>=3.5.0,pytest-asyncio>=1.3.0,pytest-homeassistant-custom-component>=0.13.326,coverage>=7.13.5,homeassistant>=2026.4.4. - Pre-commit hooks autoupdate.
v0.27.0
Added
ajax_armed/ajax_disarmed/ajax_armed_night/ajax_armed_home/ajax_security_state_changedbus events now carrysource_nameandsource_type(resolves request from #93). The source_name is the Ajax user/keypad/space-control that triggered the arm/disarm (or"Home Assistant"for a local HA action); source_type mirrors the AjaxsourceObjectType(USER,KEYPAD,SPACE_CONTROL,APP, ...) or"HA". Makes automations such as "disarm by user X → open gate" possible without needing SIA events. The fields are populated by both the SSE and SQS managers — the REST fallback omits them since Ajax REST doesn't expose the actor.- Logbook describers for the same events now append
par <source>/by <source>(7 languages) when the info is present.
Changed
- Dependency bumps (dependabot):
aiohttp>=3.13.5,pytest>=9.0.3,pytest-cov>=7.1.0,pytest-mock>=3.15.1,homeassistant>=2026.4.3. - Pre-commit hooks autoupdate.
v0.26.4
Fixed
- Camera detection events now fire reliably (resolves #33).
_handle_video_eventin both SSE and SQS managers used to update channel state but never triggerevent.<camera>_detection— the entity stayed inunknownfor users without ONVIF. Added_fire_video_detection_eventhelper inEventHandlerMixinso SSE and SQS reach parity with the doorbell handler. - ONVIF NVR routing no longer mis-attributes events (likely fixes #114). NVR
sourceAliases.sources[0]could point to the doorbell, so motion on the front camera was triggeringevent.sonnette_2instead ofevent.camera_devant. The integration now skips the NVR for ONVIF events entirely and connects directly to each camera + doorbell — every Ajax camera runs its own AI and exposes ONVIF events directly, so the NVR added nothing for the events path. - SSE proxy users no longer miss button presses or wire-input alarms. SSE dispatch was missing
BUTTON_EVENTSandWIRE_INPUT_EVENTS(only SQS had them). Imported the mappings and added_handle_button_event/_handle_wire_input_eventtosse_manager. - SSE doorbell now fires the event entity like SQS already did.
- Firmware sensors are now correctly categorised
DIAGNOSTIC.AjaxDeviceSensorandAjaxBinarySensorignored theentity_categorykey (43 occurrences acrossdevices/), so smoke, flood, socket, dimmer, lightswitch and waterstop firmware sensors landed in the main entity list. Addedresolve_entity_category()helper indevices/base.pyconsumed by both sensor classes plus the existing video-edge sensor (replaces its inline str→enum mapping). - Concurrent arm/disarm calls can no longer reach the API out-of-order. Added per-
space_idasyncio.Lockforasync_arm_space,async_disarm_space,async_arm_night_mode,async_arm_group,async_disarm_group. - Optimistic switch updates are no longer overwritten by the next poll.
night_mode_arm,always_active,chimes_enabled,siren_triggers,settingsSwitchwere silently rolled back. Addedmark_optimistic/is_optimistichelpers onAjaxDevicereserving an attribute against polling overwrite for 15 s. - Panic button now rejects double-taps within 5 s with a translated
HomeAssistantError(anti false police dispatch). Translationpanic_cooldownavailable in 7 languages. _security_event_lockpreviously declared but unused is now actually held around the_skip_state_change_eventflag flips in both SSE and SQS managers — concurrent security events can no longer race the cache-bypass / skip flag.- SSE deduplication key now includes
event_code(parity with SQS timestamp-based key) so back-to-back events of the same tag with different codes are no longer silently dropped. userIdno longer leaks into INFO logs:sse_urlis masked viaurlsplitinsse_client.py,api.pyand__init__.py; login and refresh logs print only the first 8 characters ofuser_id.- SSE callback tasks are drained at stop:
AjaxSSEClient.stop()nowgather()s_pending_callback_tasksbefore closing the session, so they cannot keep writing to the coordinator afterasync_shutdown. - Alarm persistent-notification id is now stable (
f"ajax_alarm_{space.id}_{event_code}") instead oftime.time()per millisecond — a burst of alarms updates the same notification instead of spamming the dashboard.
Performance
- ~40-50% fewer API calls in proxy/SSE mode.
- Cache
async_get_space(5 s TTL) — coalescesvideo_edges+smart_locksfetches inside the same coordinator tick (was hitting/spaces/{id}twice per cycle). - Skip
video_edgesandsmart_lockslight fetch on 2 cycles out of 3 when SSE/SQS is active (state is event-driven anyway). - Skip
groupsfetch on light cycles when SSE/SQS is active (group arm/disarm is pushed in real time and forces a metadata refresh).
- Cache
TCPConnectorlimit reduced 20 → 5 (single-tenant proxy; 5 in-flight is plenty for one coordinator and avoids bursting Julien's shared proxy).@functools.lru_cache(maxsize=4096)onparse_event_code— finite key space (~200 codes × 7 languages), called on every SSE/SQS event.
Changed
- ONVIF strategy: connect directly to every camera and doorbell, never via the NVR. Comment in
onvif_manager.async_start()explains why (channel-mapping unreliability). - 10 of 11
tamperdeclarations migrated toself._tamper_binary_sensor()indevices/{transmitter,smoke_detector,life_quality,manual_call_point,motion_detector,waterstop,flood_detector,hub,door_contact}.py.siren.pykept inline because it needs theis not Noneguard onattributes['tampered'](helper unconditionally adds the sensor with defaultFalse). - 6
problemdeclarations migrated toself._problem_binary_sensor()indevices/{hub,light,socket,waterstop,lightswitch,dimmer}.py. - Remove dead
LightHandler(devices/light.pydeleted, import + export cleaned fromdevices/__init__.py). The HA light platform instantiatesAjaxDimmerLightdirectly — the handler had been unused since 0.25.x. services.yaml: integration-level services now exposefields.config_entry_id(Quality Scale Silver requirement) forget_raw_devices,refresh_metadata,get_nvr_recordings,get_smart_locks. Translations added in 7 languages.event.py:via_deviceset on event sub-entities so they appear under their parent space in the device hierarchy (Gold).- Logbook: new
ajax_camera_detectionbus event fired by both ONVIF and SSE/SQS managers, with a localised describer that prints<Camera> a détecté un mouvement / une personne / un véhicule / un animal / un franchissement de ligne(7 languages) instead of HA's generica détecté un événementfallback.
v0.26.3
Changed
- Migrate 15 device handlers to shared helpers in
devices/base.py(dimmer, door_contact, flood_detector, hub, life_quality, lightswitch, manual_call_point, motion_detector, siren, smoke_detector, socket, transmitter, waterstop — on top of the 4 already done in 0.26.2). Removes ~500 lines of duplicated battery/signal/tamper/temperature/firmware sensor boilerplate. - Extract
EventHandlerMixinin_event_helpers.py: SSE and SQS managers now share the same implementation of_find_video_edge,_update_video_detectionand_reset_doorbell_ring(-196 lines of duplication). coordinator.py: cachespace_bindingper hub soasync_get_space_by_hubis only hit onfull_refresh(not on every poll tick); motion-reset error path drops the unparsablemotion_detected_atinstead of spamming a WARNING every tick.event_codes.py: extendEVENT_MESSAGESto all 7 supported languages (de/nl/sv/uk added) — 861 messages total, up from fr/en/es only.- Extend
entity.camera.nvr_channel/nvr_channel_subtranslation keys so untitled NVR channels follow the user's HA language (7 languages).
Fixed
camera.py: guard snapshot cache with anasyncio.Lockso two concurrent requests can no longer spawn two FFmpeg processes against the same RTSP stream.__init__.py: addasync_remove_config_entry_deviceso users can delete orphaned Ajax devices (e.g. entities removed by previous releases) from the registry; redact sensitive fields before writingajax_raw_devices.json; escape markdown in persistent-notification source/space names to neutralise[text](javascript:…)injection.api.py: bound aiohttp connector pool (limit=20,limit_per_host=10) to prevent connector exhaustion under stalls; exposebypass_cache_next()as public helper instead of poking_bypass_cache_oncefrom the coordinator.fr.json: use "serrures intelligentes" instead of "smart locks" inlock_not_supportedfor terminology consistency.
v0.26.2
Security
- Redact sensitive fields (hub_id, mac, IP, tokens…) in
ajax_raw_devices.jsonbefore writing it to disk - Escape markdown in user-supplied source/space names rendered in persistent notifications to neutralise
[text](javascript:…)injection
Fixed
- Previous optimistic fix on device availability was reading the wrong source (
attributes["online"]instead ofdevice.online), leaving entities stuck "Indisponible" — now readsdevice.online
Changed
- Factorise
_get_recording_nvr_idintoAjaxSpace.get_recording_nvr_id()(removes 3× duplication across camera/sensor/binary_sensor) - Cache
space_bindingresults per hub soasync_get_space_by_hubis only hit onfull_refresh, not on every tick - Expose
AjaxRestApi.bypass_cache_next()as a public helper (coordinator no longer pokes_bypass_cache_oncedirectly) - Bound aiohttp session connector (
limit=20,limit_per_host=10) to avoid connector exhaustion - Guard
camera.async_camera_imagewith anasyncio.Lockso two concurrent requests don't spawn two FFmpeg processes - Remove ~15 empty
_handle_coordinator_updateoverrides that merely re-calledasync_write_ha_state() async_migrate_entry: explicit while-loop-friendly multi-version pattern- Smart-lock
Storeschema now versioned viaSMART_LOCK_STORE_VERSIONconstant with migration hook ready - ONVIF object detection now recognises
bicycle/motorcycle/car/truck/busas vehicles - Camera "sub stream" entity uses
translation_keyso the label follows the user's HA language - Logbook messages translated across 7 languages (fr/en/es/de/nl/sv/uk)
event_codes.EVENT_TYPESextended with de/nl/sv/uk translationspytest.ini: scopeDeprecationWarningsuppression to third-party deps so HA breaking changes stay visible
Removed
issues.critical_firmware_update,issues.device_offline,issues.firmware_updatefrom strings/translations — redundant with theUpdateEntityplatform
v0.26.1
Security
- Mask
userId/sseUrland RTSP credentials in INFO logs (DEBUG only) - Scrub RTSP URL from FFmpeg error messages in snapshot path
- Expand diagnostics
TO_REDACT(hub_id, mac, ip, camelCase keys, auth headers) - Drop response body from 401 refresh-token log
Fixed
- WallSwitch Jeweller relay state (#120) — keep prior SQS fix
- SSE: persistent 401/403 now surfaces as auth failure with exponential backoff
- SQS: reuse a single
aiobotocoreclient per thread, fail-fast on IAM errors, callback timeout kept below visibility to prevent redelivery loops - Video Edge: strict ISO 8601 regex for uptime, defensive divisions on storage/temperature
- Optimistic light/valve rollback preserves the absence of a previous value
life_quality: fix °C vs 0.1°C unit mismatch in temperature comfort checkalarm_control_panel: returnNoneon unknown security state (no longer silently maps to DISARMED)MULTI_TRANSMITTER/KEYPADmapped to correct handlersevent_codes.py: explicit transition overrides + added KeyPad variant6A;M_22_24reclassified asarm_failedforce_arm/force_arm_nightservices now honor theentity_idtarget (previously fanned out to all hubs)eventplatform: unregister entities cleanly on reload to avoid stale dispatch targetssensor/binary_sensor: consultdevice.onlineattribute (notattributesdict) for availability- Panic button: disabled by default and no longer advertised as
IDENTIFY - Doorbell: drop misleading
OCCUPANCYbinary sensor (press handled via event platform) - ONVIF:
asyncio.Lockon client map; subscription loss now triggers recreation on the next poll
Changed
- Raise
ConfigEntryAuthFailedon authentication errors to trigger the Home Assistant reauth flow HomeAssistantErroracrossnumber,select,switch,locknow usetranslation_domain+translation_key- Replace hardcoded unit strings with Home Assistant constants (
PERCENTAGE,UnitOfTime,UnitOfTemperature,CONCENTRATION_PARTS_PER_MILLION,DEGREE,UnitOfInformation) - Pass
config_entryto theDataUpdateCoordinatorconstructor manifest.json: addquality_scale: bronze, removeaiohttp(provided by core)- Extract
_parse_door_state_from_wiringhelper in the coordinator (4× duplication removed) - Add shared helpers in
devices/base.py(_battery_sensor,_tamper_binary_sensor,_temperature_sensor,_signal_strength_percent_sensor,_problem_binary_sensor,_firmware_version_sensor) - Track
call_laterhandles and background tasks in SSE / SQS managers for clean teardown - Translations audit across 7 languages: add missing exceptions (
hub_not_found,device_not_found,no_api_key,lock_not_supported),services.get_smart_locks,config.step.dhcp_confirm; remove orphanoptions.step.dhcp_confirm; fix double-escaped newlines in French; expandicons.json(event, lock, light, update, valve) - SQS (relay): write state to
is_onattribute rather thanstate(#120)
v0.26.0
Added
- Smart lock event entity exposing
doorbell_pressedanddoor_left_openas triggers in automation UI (#88) - Translations for smart lock events in all 7 languages
Fixed
- NO_EOL/ONE_EOL door state detection: use OR logic between
externalContactStateandcontactStateto support both static and dynamic firmwares (#103)