diff --git a/.env.example b/.env.example index 0884ff09..63646936 100644 --- a/.env.example +++ b/.env.example @@ -228,6 +228,16 @@ OPENCHARGEMAP_API_KEY= # TYRE_PRESSURE_GOOD_BAR=2.6 # TYRE_PRESSURE_HIGH_BAR=3.2 +# ── Alert levels ─────────────────────────────────────────────────────────────── +# When the low 12V battery, low fuel, low EV battery and "no data from the car" +# notifications fire. The defaults match the dashboard's own warning colours. +# 0 switches an alert off. STALE_DATA_HOURS should stay above the gateway's poll +# of a sleeping car (once a day by default). +# AUX_BATTERY_LOW_VOLT=12.0 +# LOW_FUEL_PERCENT=15 +# LOW_EV_PERCENT=20 +# STALE_DATA_HOURS=48 + # ── High-voltage battery capacity ────────────────────────────────────────────── # Usable capacity of the traction battery, in kWh. The MQTT gateway does not read # this off the pack: it scales the BMS percentage by an EV-sized default, so the diff --git a/README.md b/README.md index 685661fd..c96bff00 100644 --- a/README.md +++ b/README.md @@ -309,14 +309,18 @@ Telemetry older than a year is folded into one row per quarter of an hour, so th ## Push notifications -GarageStack checks your vehicle's state every 5 minutes and sends both a browser push notification and an in-app notification (bell icon) when any of the following conditions are detected. "Engine started" and MG app messages go out the moment they arrive over MQTT instead. Each alert has a 1-hour cooldown per vehicle to avoid repeated notifications. +GarageStack checks your vehicle's state every 5 minutes and sends both a browser push notification and an in-app notification (bell icon) when any of the following conditions are detected. "Engine started", MG app messages and a failed MG sign-in go out the moment they arrive over MQTT instead. Each alert has a 1-hour cooldown per vehicle to avoid repeated notifications, and goes out once per report from the car: a car that sleeps for days with a soft tyre is reported once, not every hour. | Alert | Condition | | ------- | --------- | | Engine started | Engine transitions from off to running | | Low tyre pressure | Any tyre below `TYRE_PRESSURE_LOW_BAR` (default 2.2 bar) | | High tyre pressure | Any tyre above `TYRE_PRESSURE_HIGH_BAR` (default 3.2 bar) | -| Low EV battery | EV state-of-charge below 20 % (plug-in vehicles) | +| Low EV battery | EV state-of-charge below `LOW_EV_PERCENT` (default 20 %, plug-in vehicles) | +| Low fuel | Fuel level below `LOW_FUEL_PERCENT` (default 15 %, hybrids and plug-in hybrids) | +| Low 12V battery | 12V battery below `AUX_BATTERY_LOW_VOLT` (default 12.0 V) on several polls in a row while the engine is off | +| No data from the car | Nothing reported for `STALE_DATA_HOURS` (default 48 hours): the MG session, the gateway or the car's connection needs a look | +| MG sign-in failed | The gateway could not sign in to the MG account, so no new data arrives (sent once per outage, as it happens) | | Car left unlocked | `doors/locked = false` while engine is off | | Door left open | Any door, boot, or bonnet open while engine is off | | Window left open | Any window open while engine is off | @@ -327,7 +331,7 @@ GarageStack checks your vehicle's state every 5 minutes and sends both a browser Push notifications require VAPID keys to be configured (`VAPID_PUBLIC_KEY` / `VAPID_PRIVATE_KEY`). Without them, alerts still appear in the in-app notification panel. While GarageStack is open in a browser that allowed notifications but is not subscribed to push, the same alerts show up as system notifications too; a subscribed browser gets them through push instead, so nothing arrives twice. -Settings has a per-type checklist for these alerts, which offers only the types the car's drivetrain can produce: the two plug-in alerts (low EV battery, charging complete) are left out for a plain hybrid. Deselecting every type offered unsubscribes the browser from push entirely; selecting one again resubscribes. +Settings has a push switch for the device you are on and a per-type checklist for these alerts, which offers only the types the car's drivetrain can produce: the two plug-in alerts (low EV battery, charging complete) are left out for a plain hybrid. Both are kept per device: a type switched off on your phone still reaches your laptop, and still shows up in the notification panel. Only the switch turns push on or off; the checklist never asks the browser for permission. Notification texts are written by the background worker, which has no browser to take a language from, so their language is a deployment setting: `NOTIFICATION_LANGUAGE=en` (default) or `nl`. The web UI's own language toggle does not affect them. diff --git a/docker-compose.yml b/docker-compose.yml index caea2003..c7303ed9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -119,6 +119,11 @@ services: NOTIFICATION_LANGUAGE: "${NOTIFICATION_LANGUAGE:-}" # Days of telemetry kept at full detail before the Worker compacts it. See .env.example. TELEMETRY_FULL_DETAIL_DAYS: "${TELEMETRY_FULL_DETAIL_DAYS:-}" + # When the Worker's level alerts fire. See .env.example. + AUX_BATTERY_LOW_VOLT: "${AUX_BATTERY_LOW_VOLT:-}" + LOW_FUEL_PERCENT: "${LOW_FUEL_PERCENT:-}" + LOW_EV_PERCENT: "${LOW_EV_PERCENT:-}" + STALE_DATA_HOURS: "${STALE_DATA_HOURS:-}" volumes: - worker_logs:/app/logs diff --git a/documentation/CONFIGURATION.md b/documentation/CONFIGURATION.md index 8da7cbb1..91492dac 100644 --- a/documentation/CONFIGURATION.md +++ b/documentation/CONFIGURATION.md @@ -96,6 +96,10 @@ The full guide, with provider examples, is [`AUTHENTICATION.md`](AUTHENTICATION. | Variable | Default | What it does | | --- | --- | --- | | `TYRE_PRESSURE_LOW_BAR` / `TYRE_PRESSURE_GOOD_BAR` / `TYRE_PRESSURE_HIGH_BAR` | `2.2` / `2.6` / `3.2` | Tyre pressure bands in bar, for the diagram's colours and the tyre notifications. Set them from your car's placard. | +| `AUX_BATTERY_LOW_VOLT` | `12.0` | 12V battery voltage a parked car is warned below, once several polls in a row show it. `0` switches the alert off. | +| `LOW_FUEL_PERCENT` | `15` | Fuel level, in percent, a hybrid or plug-in hybrid is warned below. `0` switches the alert off. | +| `LOW_EV_PERCENT` | `20` | Charge level, in percent, a plug-in car is warned below. `0` switches the alert off. | +| `STALE_DATA_HOURS` | `48` | Hours without a report from the car before GarageStack warns that data has stopped arriving. Keep it above the gateway's poll of a sleeping car, once a day by default. `0` switches the alert off. | | `HV_BATTERY_CAPACITY_KWH` | none | Usable size of the traction battery in kWh. The gateway assumes an EV-sized pack, so its kWh figures are far too large on a plain hybrid (an MG HS Hybrid+ carries 1.83 and is reported as 72.5). Empty keeps the gateway's figure on a plug-in car and shows a hybrid's charge as a percentage only. | ## Stored data diff --git a/frontend/src/assets/main.css b/frontend/src/assets/main.css index 0329c715..9d64ad42 100644 --- a/frontend/src/assets/main.css +++ b/frontend/src/assets/main.css @@ -1528,7 +1528,7 @@ textarea.form-control { /* Own internal scroll so a long category list never pushes the rest of the settings modal's sections down. minmax(170px, 1fr) against .detail-modal's actual width budget: the mobile bottom-sheet content is ~300-335px wide -> 1 column; the desktop dialog is capped at - max-width: 440px -> 2 columns, where all 9 categories fit in 5 rows with no scrollbar at all. + max-width: 440px -> 2 columns, where the up to 15 categories take 8 rows and scroll a little. max-height keeps the 1-column mobile case to ~5-6 visible rows before scrolling. */ .notif-type-checklist { display: grid; diff --git a/frontend/src/components/NotificationPanel.vue b/frontend/src/components/NotificationPanel.vue index 02fd0aeb..8f53e101 100644 --- a/frontend/src/components/NotificationPanel.vue +++ b/frontend/src/components/NotificationPanel.vue @@ -19,6 +19,10 @@ const CATEGORY_ICONS: Record = { 'windows-open-parked': ['fas', 'wind'], 'vehicle-message': ['fas', 'message'], 'climate-schedule': ['fas', 'calendar-days'], + 'low-12v': ['fas', 'car-battery'], + 'low-fuel': ['fas', 'gas-pump'], + 'gateway-login': ['fas', 'triangle-exclamation'], + 'data-stale': ['fas', 'clock'], } function categoryIcon(category: string | null): string[] { diff --git a/frontend/src/components/SettingsToggle.vue b/frontend/src/components/SettingsToggle.vue index d1582ac3..00b180c1 100644 --- a/frontend/src/components/SettingsToggle.vue +++ b/frontend/src/components/SettingsToggle.vue @@ -5,6 +5,7 @@ defineProps<{ inputId?: string /** FontAwesome icon shown before the label. */ icon?: string + disabled?: boolean }>() const model = defineModel() @@ -31,6 +32,7 @@ const model = defineModel() type="checkbox" role="switch" :aria-label="label" + :disabled="disabled" /> diff --git a/frontend/src/components/settings/NotificationTypeSettings.vue b/frontend/src/components/settings/NotificationTypeSettings.vue index ddc43d6b..d8abbc4a 100644 --- a/frontend/src/components/settings/NotificationTypeSettings.vue +++ b/frontend/src/components/settings/NotificationTypeSettings.vue @@ -4,17 +4,23 @@ import { useI18n } from 'vue-i18n' import { useUiSettingsStore } from '@/stores/settingsUi' import { useVehicleStore } from '@/stores/vehicle' import { useNotificationPushSync } from '@/composables/useNotificationPushSync' +import SettingsToggle from '../SettingsToggle.vue' import { notificationCategoryIdsFor, type NotificationCategoryId, } from '@/utils/notificationCategories' -// The notification types to show and to be pushed. This also drives the push subscription: -// deselecting every type unsubscribes, selecting at least one (re)subscribes. +// Push for this device, and the notification types it shows and gets pushed. const { t } = useI18n() const settings = useUiSettingsStore() const vehicleStore = useVehicleStore() -const { showPermissionDeniedNotice, showSubscribeFailedNotice } = useNotificationPushSync() +const { + pushSupported, + pushEnabled, + pushSwitchDisabled, + showPermissionDeniedNotice, + showSubscribeFailedNotice, +} = useNotificationPushSync() const NOTIFICATION_CATEGORY_LABEL_KEYS: Record = { 'low-tyre': 'lowTyre', @@ -27,6 +33,10 @@ const NOTIFICATION_CATEGORY_LABEL_KEYS: Record = 'windows-open-parked': 'windowsOpenParked', 'vehicle-message': 'vehicleMessage', 'climate-schedule': 'climateSchedule', + 'low-12v': 'low12v', + 'low-fuel': 'lowFuel', + 'gateway-login': 'gatewayLogin', + 'data-stale': 'dataStale', maintenance: 'maintenance', } @@ -67,6 +77,24 @@ function toggleNotificationType(id: NotificationCategoryId, checked: boolean) {