This repository contains a Home Assistant custom integration that wraps the async library pycityvisitorparking to manage Dutch municipal visitor parking in a consistent, Home Assistant-native way.
Key goals:
- HA-native UX: config flow, options flow, reauth, diagnostics, translations, services, and frontend behavior.
- Strict async behavior and reliable runtime operation.
- Generic HA surface: avoid provider-specific concepts, fields, and user-facing terminology.
- Provider-specific API behavior, parsing logic, request quirks, and municipality-specific backend handling MUST live in
pycityvisitorparking, not in this integration. - HACS-installable, but architected and implemented as if it were an official Home Assistant integration to make future migration straightforward.
Treat this repository as four connected surfaces that must stay aligned:
- Python Home Assistant integration code in
custom_components/city_visitor_parking/ - A custom frontend bundle in
custom_components/city_visitor_parking/frontend/ - A websocket API layer used by the frontend and tests
- GitHub automation for validation, labeling, release drafting, dependency review, and release packaging
Changes in one surface often require follow-up updates in the others.
Treat this repository as "official-ready":
- Follow Home Assistant Core integration patterns and conventions.
- No HACS-specific runtime shortcuts; HACS is only a distribution channel.
- Use standard
manifest.jsonrequirements and hassfest-friendly translations and schemas. - Avoid brittle assumptions about file paths, working directories, or runtime environment.
- Keep module naming and responsibilities aligned with official integrations:
const.py,config_flow.py,coordinator.py,services.py,diagnostics.py,sensor.py
Document any migration steps in project docs when needed.
Treat Home Assistant Core and Home Assistant Frontend as the compatibility baseline for implementation style, testing, copy, and review workflow.
- Prefer patterns used by mature Gold/Platinum-quality Home Assistant integrations when choosing structure, naming, or error handling.
- Keep tests fully typed where practical; test function parameters SHOULD use concrete type annotations.
- Do not rewrite commit history after review has started; avoid amend, squash, or rebase once feedback is in progress unless explicitly requested.
- Pull request titles MUST follow the repository's standard conventional format, using a recognized prefix such as
feat:,fix:,docs:,chore:,refactor:,perf:,ci:,deps:,test:, ortests:so CI label automation can categorize the PR correctly.
- User-facing text MUST be localization-friendly, concise, and consistent with Home Assistant terminology.
- Prefer Home Assistant wording conventions:
- use "add" / "remove" for attaching or detaching existing items
- use "create" / "delete" for creating or permanently removing items
- Frontend assets, cards, dialogs, editors, and configuration UX MUST follow Home Assistant frontend expectations for accessibility, responsive behavior, error states, and internationalization.
- Avoid hardcoded UI copy when translation keys or shared wording patterns are appropriate.
- If a local repository convention conflicts with HA Core or HA Frontend conventions, resolve it in favor of the option that is most likely to remain acceptable in an eventual Home Assistant Core migration.
- Maintain
custom_components/city_visitor_parking/quality_scale.yamlrule-by-rule with implemented or exempt status.
If any rule is not implemented, document an explicit exemption and rationale.
The integration currently targets the quality level declared in custom_components/city_visitor_parking/manifest.json. Keep code, tests, docs, diagnostics, and CI aligned with that declared target.
- Versions MUST come from GitHub Releases.
- For every version bump:
- update
custom_components/city_visitor_parking/manifest.jsonversionto the new release version - publish a GitHub Release, not just a tag
- use a tag name matching the integration version, preferably
v1.2.3
- update
Release automation and release notes MUST stay aligned with:
.github/release-drafter.yml.github/workflows/release-drafter.yml.github/workflows/release.yml
The repository currently relies on GitHub Actions workflows for validation, labeling, release drafting, dependency review, and release packaging. Treat these workflows as part of the product surface, not as incidental tooling.
- HACS validation using
hacs/action - Hassfest validation using
home-assistant/actions/hassfest - Python quality checks
- Python tests
- Frontend quality checks
- Release policy validation
- Dependency review
- PR labeling and release categorization
- Python development and CI use
uvwith dependency groups frompyproject.toml. - Frontend development and CI use the bundle under
custom_components/city_visitor_parking/frontend/. - Treat PR labeling, Release Drafter, dependency review, and release packaging workflows as part of the maintained repository surface.
- Async-only. No blocking calls in integration runtime code.
- Do not perform HTTP directly from integration modules; only
pycityvisitorparkinguses aiohttp for provider communication. - Always inject Home Assistant's shared aiohttp session into
pycityvisitorparking.Client. - Never close injected HA-managed sessions.
- Do not add hardcoded provider-specific logic, provider-specific API branching, municipality-specific hacks, or provider-specific field handling in the Home Assistant integration.
- If behavior is specific to one provider or municipality, it MUST be implemented in
pycityvisitorparkingand exposed through a generic integration-facing contract. - The integration may select a configured provider, but it MUST NOT become the place where provider quirks are embedded.
- Never log credentials, tokens, raw license plates, or other PII.
- Mask plates if they ever appear outside the library boundary, for example
AB***12. - Diagnostics MUST redact sensitive data.
- Error messages, service exceptions, websocket errors, and logs MUST avoid leaking PII.
- Docstrings required for all public modules, classes, and functions.
- Add inline comments for non-obvious logic such as flow branching, validation, time handling, fallback updates, routing, frontend resource registration, or websocket contract handling.
- Prefer typing throughout.
- Use a typed config entry alias for
entry.runtime_data.
- Treat
devas the default working branch for day-to-day changes and small fixes. - Use a dedicated feature branch only for larger, clearly scoped change sets, especially when work spans multiple maintained surfaces such as backend, frontend, websocket API, docs, or CI.
- Do not put partial, half-reviewed, or long-running multi-commit work directly on
devwhen isolated review would be safer or clearer. - Keep feature branches focused on one clustered change set and do not use long-lived catch-all branches for unrelated work.
- Feature branches SHOULD use a conventional prefix such as
feat/,fix/,refactor/,docs/,ci/,deps/,test/, ortests/. - Do not add agent-related prefixes or suffixes such as
[codex],[agent], or similar markers in branch names, commit titles, or pull request titles. - Keep feature branches current with
devas needed, but do not rewrite shared history once review is in progress unless explicitly requested. - After a feature branch is merged, delete it on GitHub and clean up the local branch as soon as it is no longer needed unless there is an explicit reason to keep it temporarily.
Use uv for local Python commands and dependency resolution.
Preferred commands:
uv run --only-group dev ruff check .uv run --only-group dev ruff format --check .uv sync --group test --no-install-projectuv run pytest tests/components/city_visitor_parking
Frontend code lives in custom_components/city_visitor_parking/frontend/.
If frontend files change, run:
yarn installyarn buildyarn lintyarn test
If frontend output or frontend translation assets are expected by runtime code, confirm they remain compatible with the backend registration logic in custom_components/city_visitor_parking/__init__.py.
- Keep
pycityvisitorparkingpinned consistently in both:pyproject.tomlcustom_components/city_visitor_parking/manifest.json
- Be careful when bumping Home Assistant test dependencies.
pytest-homeassistant-custom-componentmay pin compatible versions ofpytest,pytest-cov, and related packages.- Do not accept dependency bumps that make the
uvdependency graph unsatisfiable in CI. - If dependency review has temporary advisory exceptions, remove them as soon as upstream constraints allow.
- Keep
.github/dependabot.ymlaligned with real repository constraints. - If a dependency must stay pinned for CI compatibility, document that in config comments and avoid allowing automated PRs to repeatedly propose broken combinations.
custom_components/city_visitor_parking/providers.yamlmaps municipality to provider configuration.- Load it via
importlib.resources; do not depend on filesystem-relative assumptions.
- Config flow MUST present a municipality dropdown built from
providers.yaml, plusOther. - If a known municipality is selected, the provider config must be applied automatically.
- If
Otheris selected, prompt for manual entry:provider_idfrompycityvisitorparking.Client.list_providers()municipality_namebase_urlapi_url
- Manual provider configs are stored in config entry data only and must not be written back to YAML.
Recommended steps:
- Select municipality
- If
Other, enter manual municipality/provider config - Enter credentials
- Validate connection/login with a safe test call
- Fetch permits and select
permit_id - Optional description
- Set a stable
unique_idand prevent duplicates. - Preferred
unique_idstrategy: includeprovider_idandpermit_idif stable and non-PII. - Entry title MUST be unique and follow:
"{description} - {permit_id}"if description is provided- otherwise
"{municipality_name} - {permit_id}"
async_setup_entrycreates runtime objects and stores them inentry.runtime_data.async_unload_entryunloads platforms and releases only resources owned by the integration.- Never close Home Assistant-owned shared resources.
- Auth failures during setup or coordinator updates must raise
ConfigEntryAuthFailed. - Implement
async_step_reauthlinked to the existing entry to update credentials and revalidate.
- Options flow MUST support per-weekday overrides for operating times across all 7 days.
- Store overrides in
entry.options. - Default is no override, meaning provider or library-derived chargeable windows are used.
- Validate each day entry; if both start and end exist, enforce
end > start.
- Add option
auto_end_reservation_when_freeas a boolean, defaultfalse. - When enabled, automatically end an active reservation early if the permit zone is currently not chargeable according to computed
permit.zone_availability. - Scope this behavior to the single config entry only.
- Implement safeguards:
- track reservation IDs already auto-ended or attempted
- use cooldowns to avoid repeated attempts and log spam
- never act when there is no active reservation
Use a DataUpdateCoordinator with a conservative interval to fetch:
- selected permit and its
zone_validity - reservations
- favorites
Error handling:
- Temporary or network issues must raise
UpdateFailed - Auth issues must raise
ConfigEntryAuthFailed
Runtime objects in entry.runtime_data should include:
pycityvisitorparking.Client- selected provider instance
- coordinator
- any derived state needed by entities, services, websocket handlers, or frontend support
- Treat
custom_components/city_visitor_parking/websocket_api.py,custom_components/city_visitor_parking/payloads.py, andcustom_components/city_visitor_parking/frontend/src/types.tsas one contract surface. - When changing payload fields or semantics, update backend payload builders, websocket handlers, frontend TypeScript types, and related tests together.
- Frontend registration, generated assets, Lovelace resource synchronization, and websocket response shapes must remain compatible with runtime setup in
custom_components/city_visitor_parking/__init__.py.
Expose at least these sensors. All entities MUST:
- have a stable
unique_id - set
_attr_has_entity_name = True - use
translation_key - avoid PII in state and attributes
active_reservations
- State: integer count of reservations active now
remaining_time
- State:
H:mm - If none active:
0:00 - Attribute
has_active_reservation=false - Non-PII attributes only
permit_zone_validity
- State: integer count of chargeable validity blocks
- Attribute
zone_validitywith UTC ISO8601 blocks
permit_zone_availability
- State:
chargeableorfree - Derived from chargeable-only validity plus overrides
- Attributes:
is_chargeable_nownext_change_timewindows_today
favorites
- State: integer count of favorites
- Avoid frequent state churn.
- Use
always_update=Falsewhere possible. - Only write state changes when values truly change.
- Create exactly one device per config entry.
- Attach all entities to that device using
device_info. - Use identifiers based on the entry identity.
- Derive each entity
unique_idfromentry.unique_idplus a fixed suffix.
Home Assistant services are domain-wide, so routing is mandatory when multiple config entries exist.
- Every service MUST target exactly one entry.
- The service schema MUST require
device_idas the routing target. - The handler MUST map
device_idto the config entry and use that entry's runtime objects.
- If
device_idcannot be resolved, or the entry is not loaded:- raise
ServiceValidationErrorusing translation keys
- raise
- Never broadcast a service call across all entries.
All services are domain-scoped and require device_id:
start_reservationupdate_reservationend_reservationadd_favoriteupdate_favoriteremove_favorite
start_reservationrequiresstart_time,end_time, andlicense_plateupdate_reservationrequiresreservation_idand at least one changed fieldend_reservationrequiresreservation_id- Validate time ordering when both times are present
- Invalid input must raise
ServiceValidationError - Operational failures must raise
HomeAssistantError - Never include PII in messages
- If the library or provider does not support native reservation updates, only use a destructive fallback when enough information is present to do so safely.
- Never perform partial destructive fallback behavior without the required data.
- English is canonical.
- Dutch is required.
- Maintain:
strings.jsontranslations/en.jsontranslations/nl.json
Also keep frontend translation assets aligned where applicable.
Translate:
- config flow steps and errors
- options
- services fields and errors
- entity names
- frontend strings and editors where applicable
- Implement diagnostics in
diagnostics.py. - Redact credentials, tokens, raw plates, and other sensitive fields.
- Include helpful non-PII troubleshooting data such as:
provider_id- municipality
permit_id- polling interval
- last update status
- Tests must not call real municipal services.
- Use pytest plus Home Assistant test helpers.
- Mock
pycityvisitorparkingand aiohttp.
Minimum expected coverage includes:
- config flow
- reauth flow
- options flow
- multi-entry behavior
- services routing and validation
- coordinator behavior
- entity behavior
- websocket API behavior
- diagnostics redaction
- translations parsing
- frontend contract behavior where backend payload shape matters
If a backend change affects frontend payloads, websocket responses, config entry exposure, or translations used by the frontend, add or update the corresponding tests.
- Pull request titles MUST use a recognized conventional prefix.
- PRs are expected to end up with exactly one release category label used by PR labeling and Release Drafter.
- Keep
.github/workflows/pr-labeling.yml,.github/release-drafter.yml, and.github/workflows/release-drafter.ymlaligned with real title and label conventions. - If a workflow change alters categorization behavior, update the documentation and PR expectations with it.
Maintain these docs as part of the integration surface:
README.mdMIGRATION.md- wiki pages when they are part of the maintained user or developer workflow
- release-drafter and release-policy documentation when behavior changes
README should cover:
- installation
- UI setup
- options
- services reference
- frontend expectations if relevant
- troubleshooting
- privacy notes
MIGRATION.md should describe concrete steps to move the integration into Home Assistant Core.