Skip to content

Releases: foXaCe/ajax-security-hass

v0.28.1

03 May 07:32

Choose a tag to compare

Fixed

  • ajax_armed / ajax_disarmed / ajax_armed_night / ajax_armed_home bus events fire even when REST polling consumed the state change first (#133, thanks @Kolia56). The previous gate if state_changed: in SSE/SQS _handle_security_event suppressed the fire whenever old_state == new_state — typically when REST had already updated the local state via optimistic update — so automations listening to the bus never saw nightmodeon/nightmodeoff transitions in particular. The coordinator's _skip_state_change_event flag 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

02 May 17:05

Choose a tag to compare

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_DEVICE and SIGNAL_NEW_VIDEO_EDGE signals (alongside the existing SIGNAL_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 through async_dispatcher_connect, with entity-registry guard against duplicates.
  • verify_ssl togglable 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 /hubs to a freshly-2FA'd session.
  • Translations test suite under tests/ (with the necessary .gitignore carve-out so tests/test_*.py is tracked while root-level scratch tests stay ignored). Smoke-tests that strings.json and every translations/<lang>.json are 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

22 Apr 04:31

Choose a tag to compare

Added

  • ajax_armed / ajax_disarmed / ajax_armed_night / ajax_armed_home / ajax_security_state_changed bus events now carry source_name and source_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 Ajax sourceObjectType (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

18 Apr 19:18

Choose a tag to compare

Fixed

  • Camera detection events now fire reliably (resolves #33). _handle_video_event in both SSE and SQS managers used to update channel state but never trigger event.<camera>_detection — the entity stayed in unknown for users without ONVIF. Added _fire_video_detection_event helper in EventHandlerMixin so 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 triggering event.sonnette_2 instead of event.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_EVENTS and WIRE_INPUT_EVENTS (only SQS had them). Imported the mappings and added _handle_button_event / _handle_wire_input_event to sse_manager.
  • SSE doorbell now fires the event entity like SQS already did.
  • Firmware sensors are now correctly categorised DIAGNOSTIC. AjaxDeviceSensor and AjaxBinarySensor ignored the entity_category key (43 occurrences across devices/), so smoke, flood, socket, dimmer, lightswitch and waterstop firmware sensors landed in the main entity list. Added resolve_entity_category() helper in devices/base.py consumed 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_id asyncio.Lock for async_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, settingsSwitch were silently rolled back. Added mark_optimistic / is_optimistic helpers on AjaxDevice reserving 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). Translation panic_cooldown available in 7 languages.
  • _security_event_lock previously declared but unused is now actually held around the _skip_state_change_event flag 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.
  • userId no longer leaks into INFO logs: sse_url is masked via urlsplit in sse_client.py, api.py and __init__.py; login and refresh logs print only the first 8 characters of user_id.
  • SSE callback tasks are drained at stop: AjaxSSEClient.stop() now gather()s _pending_callback_tasks before closing the session, so they cannot keep writing to the coordinator after async_shutdown.
  • Alarm persistent-notification id is now stable (f"ajax_alarm_{space.id}_{event_code}") instead of time.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) — coalesces video_edges + smart_locks fetches inside the same coordinator tick (was hitting /spaces/{id} twice per cycle).
    • Skip video_edges and smart_locks light fetch on 2 cycles out of 3 when SSE/SQS is active (state is event-driven anyway).
    • Skip groups fetch on light cycles when SSE/SQS is active (group arm/disarm is pushed in real time and forces a metadata refresh).
  • TCPConnector limit 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) on parse_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 tamper declarations migrated to self._tamper_binary_sensor() in devices/{transmitter,smoke_detector,life_quality,manual_call_point,motion_detector,waterstop,flood_detector,hub,door_contact}.py. siren.py kept inline because it needs the is not None guard on attributes['tampered'] (helper unconditionally adds the sensor with default False).
  • 6 problem declarations migrated to self._problem_binary_sensor() in devices/{hub,light,socket,waterstop,lightswitch,dimmer}.py.
  • Remove dead LightHandler (devices/light.py deleted, import + export cleaned from devices/__init__.py). The HA light platform instantiates AjaxDimmerLight directly — the handler had been unused since 0.25.x.
  • services.yaml: integration-level services now expose fields.config_entry_id (Quality Scale Silver requirement) for get_raw_devices, refresh_metadata, get_nvr_recordings, get_smart_locks. Translations added in 7 languages.
  • event.py: via_device set on event sub-entities so they appear under their parent space in the device hierarchy (Gold).
  • Logbook: new ajax_camera_detection bus 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 generic a détecté un événement fallback.

v0.26.3

18 Apr 11:44

Choose a tag to compare

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 EventHandlerMixin in _event_helpers.py: SSE and SQS managers now share the same implementation of _find_video_edge, _update_video_detection and _reset_doorbell_ring (-196 lines of duplication).
  • coordinator.py: cache space_binding per hub so async_get_space_by_hub is only hit on full_refresh (not on every poll tick); motion-reset error path drops the unparsable motion_detected_at instead of spamming a WARNING every tick.
  • event_codes.py: extend EVENT_MESSAGES to 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_sub translation keys so untitled NVR channels follow the user's HA language (7 languages).

Fixed

  • camera.py: guard snapshot cache with an asyncio.Lock so two concurrent requests can no longer spawn two FFmpeg processes against the same RTSP stream.
  • __init__.py: add async_remove_config_entry_device so users can delete orphaned Ajax devices (e.g. entities removed by previous releases) from the registry; redact sensitive fields before writing ajax_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; expose bypass_cache_next() as public helper instead of poking _bypass_cache_once from the coordinator.
  • fr.json: use "serrures intelligentes" instead of "smart locks" in lock_not_supported for terminology consistency.

v0.26.2

18 Apr 11:18

Choose a tag to compare

Security

  • Redact sensitive fields (hub_id, mac, IP, tokens…) in ajax_raw_devices.json before 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 of device.online), leaving entities stuck "Indisponible" — now reads device.online

Changed

  • Factorise _get_recording_nvr_id into AjaxSpace.get_recording_nvr_id() (removes 3× duplication across camera/sensor/binary_sensor)
  • Cache space_binding results per hub so async_get_space_by_hub is only hit on full_refresh, not on every tick
  • Expose AjaxRestApi.bypass_cache_next() as a public helper (coordinator no longer pokes _bypass_cache_once directly)
  • Bound aiohttp session connector (limit=20, limit_per_host=10) to avoid connector exhaustion
  • Guard camera.async_camera_image with an asyncio.Lock so two concurrent requests don't spawn two FFmpeg processes
  • Remove ~15 empty _handle_coordinator_update overrides that merely re-called async_write_ha_state()
  • async_migrate_entry: explicit while-loop-friendly multi-version pattern
  • Smart-lock Store schema now versioned via SMART_LOCK_STORE_VERSION constant with migration hook ready
  • ONVIF object detection now recognises bicycle/motorcycle/car/truck/bus as vehicles
  • Camera "sub stream" entity uses translation_key so the label follows the user's HA language
  • Logbook messages translated across 7 languages (fr/en/es/de/nl/sv/uk)
  • event_codes.EVENT_TYPES extended with de/nl/sv/uk translations
  • pytest.ini: scope DeprecationWarning suppression to third-party deps so HA breaking changes stay visible

Removed

  • issues.critical_firmware_update, issues.device_offline, issues.firmware_update from strings/translations — redundant with the UpdateEntity platform

v0.26.1

18 Apr 07:33

Choose a tag to compare

Security

  • Mask userId/sseUrl and 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 aiobotocore client 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 check
  • alarm_control_panel: return None on unknown security state (no longer silently maps to DISARMED)
  • MULTI_TRANSMITTER / KEYPAD mapped to correct handlers
  • event_codes.py: explicit transition overrides + added KeyPad variant 6A; M_22_24 reclassified as arm_failed
  • force_arm / force_arm_night services now honor the entity_id target (previously fanned out to all hubs)
  • event platform: unregister entities cleanly on reload to avoid stale dispatch targets
  • sensor / binary_sensor: consult device.online attribute (not attributes dict) for availability
  • Panic button: disabled by default and no longer advertised as IDENTIFY
  • Doorbell: drop misleading OCCUPANCY binary sensor (press handled via event platform)
  • ONVIF: asyncio.Lock on client map; subscription loss now triggers recreation on the next poll

Changed

  • Raise ConfigEntryAuthFailed on authentication errors to trigger the Home Assistant reauth flow
  • HomeAssistantError across number, select, switch, lock now use translation_domain + translation_key
  • Replace hardcoded unit strings with Home Assistant constants (PERCENTAGE, UnitOfTime, UnitOfTemperature, CONCENTRATION_PARTS_PER_MILLION, DEGREE, UnitOfInformation)
  • Pass config_entry to the DataUpdateCoordinator constructor
  • manifest.json: add quality_scale: bronze, remove aiohttp (provided by core)
  • Extract _parse_door_state_from_wiring helper 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_later handles 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 orphan options.step.dhcp_confirm; fix double-escaped newlines in French; expand icons.json (event, lock, light, update, valve)
  • SQS (relay): write state to is_on attribute rather than state (#120)

v0.26.0

13 Apr 04:51

Choose a tag to compare

Added

  • Smart lock event entity exposing doorbell_pressed and door_left_open as 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 externalContactState and contactState to support both static and dynamic firmwares (#103)

v0.25.1

11 Apr 06:10

Choose a tag to compare

Fixed

  • SQS connection error with newer botocore: bump aiobotocore minimum to >=2.22.0 (#116)

Changed

  • Pre-commit hooks updated (#115)

v0.25.0

29 Mar 11:21

Choose a tag to compare

Added

  • Smart lock doorbell button press event (M_7E_40) and door left open warning (M_7E_37) for LockBridge L3 (#88)

Fixed

  • AI detection binary sensors (human, vehicle, pet) stuck ON: now cleared when object type disappears or motion ends (#114)

Changed

  • Pre-commit hooks updated (#113)