diff --git a/custom_components/vimar_intercom/const.py b/custom_components/vimar_intercom/const.py index fb41de5..5cd4153 100644 --- a/custom_components/vimar_intercom/const.py +++ b/custom_components/vimar_intercom/const.py @@ -37,11 +37,22 @@ # ─── RTP / Media ────────────────────────────────────────────────────────────── RTP_AUDIO_PORT = 7200 RTP_VIDEO_PORT = 9200 -# Bandwidth we declare in the SDP (b=AS, kbit/s). The 40515 panel's encoder honours it: -# 256 gave ~14 KB keyframes at 16 fps, 2048 gives 30-58 KB at 25 fps (~1.5 Mbit/s, its -# ceiling: 4096 is the same), same 720x576, no loss over the cloud. Session = video + audio. -SDP_VIDEO_BANDWIDTH = 2048 -SDP_SESSION_BANDWIDTH = 2200 +# Bandwidth we declare in the SDP (b=AS, kbit/s); the panels' encoders honour it. +# Measured over the cloud relay (#161): +# - Tab 5S Up 40515: 2048 gives 720x576 at 24 fps (~1.9 Mbit/s, its ceiling: 4096 is +# the same); 256 drops to 320x240 at 15 fps (~240 kbit/s). No loss on that path at +# either value, first frame ~1.8-1.9 s at both. +# - Tab 7S Up 40517: 2048 gives about ten times the packets of 256 through a relay that +# loses a few percent of them: ten times the damaged frames (smeared, frozen, laggy). +# CONF_VIDEO_BANDWIDTH chooses: "auto" (default) is 2048 on local UDP (no relay in the +# way) and 256 over the cloud, as view_keepalive's default follows the transport; +# "256" and "2048" force one. Session = video + audio. +CONF_VIDEO_BANDWIDTH = "video_bandwidth" +VIDEO_BANDWIDTH_AUTO = "auto" +VIDEO_BANDWIDTH_LOW = "256" +VIDEO_BANDWIDTH_HIGH = "2048" +DEFAULT_VIDEO_BANDWIDTH = VIDEO_BANDWIDTH_AUTO +SDP_BANDWIDTH = {VIDEO_BANDWIDTH_LOW: (256, 512), VIDEO_BANDWIDTH_HIGH: (2048, 2200)} # Porte locali dell'ffmpeg AV (/api/vimar_intercom/av). Devono essere PARI e # distanti almeno 2: per ogni riga m= dell'SDP ffmpeg apre la porta RTP **e** la # RTCP (= RTP + 1). Fino alla 1.0.7 erano 19201/19202: l'RTCP del video cadeva diff --git a/custom_components/vimar_intercom/options_flow.py b/custom_components/vimar_intercom/options_flow.py index 4a8a03e..db986e5 100644 --- a/custom_components/vimar_intercom/options_flow.py +++ b/custom_components/vimar_intercom/options_flow.py @@ -57,11 +57,13 @@ CONF_HOMEKIT_ANSWER, CONF_HOMEKIT_RING_BUTTON, CONF_HOMEKIT_SMOOTH, + CONF_VIDEO_BANDWIDTH, DEFAULT_HOMEKIT_ACCESSORY, DEFAULT_HOMEKIT_ANSWER, DEFAULT_HOMEKIT_RING_BUTTON, DEFAULT_HOMEKIT_SMOOTH, DEFAULT_SNAPSHOT_DELAY, + DEFAULT_VIDEO_BANDWIDTH, DOMAIN, HOMEKIT_ANSWER_OPEN, HOMEKIT_ANSWER_TALK, @@ -71,6 +73,9 @@ MY_NAME, PICG_TARGET, SGA_TARGET, + VIDEO_BANDWIDTH_AUTO, + VIDEO_BANDWIDTH_HIGH, + VIDEO_BANDWIDTH_LOW, ) from .runtime import ( MEDIA_ENC_MODES, @@ -285,6 +290,15 @@ def _photos_schema(form: dict) -> dict: default=form.get(KEY_VIEW_KA, view_keepalive_default( form.get(KEY_USE_LOCAL_UDP, True))), ): vol.All(vol.Coerce(int), vol.Range(min=0, max=3600)), + # Every video from the panel: card, camera, /av and HomeKit. + vol.Optional( + CONF_VIDEO_BANDWIDTH, + default=form.get(CONF_VIDEO_BANDWIDTH, DEFAULT_VIDEO_BANDWIDTH), + ): selector.SelectSelector(selector.SelectSelectorConfig( + options=[VIDEO_BANDWIDTH_AUTO, VIDEO_BANDWIDTH_LOW, VIDEO_BANDWIDTH_HIGH], + translation_key="video_bandwidth", + mode=selector.SelectSelectorMode.LIST, + )), } @@ -381,6 +395,9 @@ async def async_step_settings( KEY_SNAP_DIR: snap_dir, KEY_SNAP_DELAY: snap_delay, KEY_VIEW_KA: view_ka, + CONF_VIDEO_BANDWIDTH: user_input.get( + CONF_VIDEO_BANDWIDTH, + current.get(CONF_VIDEO_BANDWIDTH, DEFAULT_VIDEO_BANDWIDTH)), KEY_ALLOWED_USERS: allowed_users, KEY_RING_WEBHOOK_URL: ring_webhook_url, KEY_RING_END_WEBHOOK_URL: ring_end_webhook_url, diff --git a/custom_components/vimar_intercom/runtime.py b/custom_components/vimar_intercom/runtime.py index 08761ce..672024b 100644 --- a/custom_components/vimar_intercom/runtime.py +++ b/custom_components/vimar_intercom/runtime.py @@ -123,9 +123,20 @@ def away_message_configured() -> bool: # Foto di chi suona: cartella (vuoto = non salvare) e secondi dopo lo squillo. SNAPSHOT_DIR: str = "" SNAPSHOT_DELAY: int = _const.DEFAULT_SNAPSHOT_DELAY +# kbit/s declared in the SDP (b=AS) for the video and the session: see const.SDP_BANDWIDTH. +VIDEO_BANDWIDTH, SESSION_BANDWIDTH = _const.SDP_BANDWIDTH[_const.VIDEO_BANDWIDTH_LOW] VIEW_KEEPALIVE: float = _const.DEFAULT_VIEW_KEEPALIVE_CLOUD # s di silenzio a "Vedi esterno", 0 = nessuno +def video_bandwidth_choice(option, use_local_udp: bool) -> str: + """The bandwidth to ask for (const.SDP_BANDWIDTH key): the option when it is + one of the values, else (automatic, unset, unknown) by transport: 2048 on + local UDP, 256 over the cloud relay, which loses packets (#161).""" + if str(option) in _const.SDP_BANDWIDTH: + return str(option) + return _const.VIDEO_BANDWIDTH_HIGH if use_local_udp else _const.VIDEO_BANDWIDTH_LOW + + def view_keepalive_default(use_local_udp: bool) -> int: """Locale (UDP, il 2 fili): niente silenzio, occuperebbe l'appartamento; cloud: 120 s.""" return 0 if use_local_udp else _const.DEFAULT_VIEW_KEEPALIVE_CLOUD @@ -259,6 +270,7 @@ def configure(data: dict) -> None: global CAMERA_TARGET, INTERNAL_PANEL_TARGET, DOOR_TARGET, CAMERA_TARGET_CONFIGURED global AWAY_MESSAGE_FILE, AWAY_MESSAGE_TEXT, AWAY_MESSAGE_TTS, AWAY_MESSAGE_DELAY global SNAPSHOT_DIR, SNAPSHOT_DELAY, VIEW_KEEPALIVE, ALLOWED_USERS + global VIDEO_BANDWIDTH, SESSION_BANDWIDTH global RING_WEBHOOK_URL, RING_END_WEBHOOK_URL global DEVICE_IMEI, DEVICE_UUID, DEVICE_NAME, AV_KEY @@ -355,6 +367,8 @@ def configure(data: dict) -> None: SNAPSHOT_DELAY = int(data.get("snapshot_delay", _const.DEFAULT_SNAPSHOT_DELAY)) _ka = data.get("view_keepalive") VIEW_KEEPALIVE = int(_ka) if _ka is not None else view_keepalive_default(USE_LOCAL_UDP) + VIDEO_BANDWIDTH, SESSION_BANDWIDTH = _const.SDP_BANDWIDTH[ + video_bandwidth_choice(data.get(_const.CONF_VIDEO_BANDWIDTH), USE_LOCAL_UDP)] ALLOWED_USERS = [str(u) for u in data.get("allowed_users") or []] RING_WEBHOOK_URL = str(data.get("ring_webhook_url") or "").strip() RING_END_WEBHOOK_URL = str(data.get("ring_end_webhook_url") or "").strip() diff --git a/custom_components/vimar_intercom/sdp.py b/custom_components/vimar_intercom/sdp.py index 151e91e..ec37b27 100644 --- a/custom_components/vimar_intercom/sdp.py +++ b/custom_components/vimar_intercom/sdp.py @@ -177,7 +177,7 @@ def _key(sec, current): ), "video": ( f"m=video {video_port} {video_proto} {' '.join(pt for pt, _ in h264)}\r\n" - f"b=AS:{C.SDP_VIDEO_BANDWIDTH}\r\n" + f"b=AS:{R.VIDEO_BANDWIDTH}\r\n" f"{h264_lines}" f"a=sendrecv\r\n" f"{video_crypto}" @@ -191,7 +191,7 @@ def _key(sec, current): f"o=- {sid} {sid} IN IP4 {MY_IP}\r\n" f"s=Talk\r\n" f"c=IN IP4 {MY_IP}\r\n" - f"b=AS:{C.SDP_SESSION_BANDWIDTH}\r\n" + f"b=AS:{R.SESSION_BANDWIDTH}\r\n" f"t=0 0\r\n" f"a=rtcp-xr:rcvr-rtt=all:10000 stat-summary=loss,dup,jitt,TTL voip-metrics\r\n" + "".join(blocks.get(kind) or _refused_block(offer[kind]) diff --git a/custom_components/vimar_intercom/strings.json b/custom_components/vimar_intercom/strings.json index 85c4b8f..a0dbc04 100644 --- a/custom_components/vimar_intercom/strings.json +++ b/custom_components/vimar_intercom/strings.json @@ -112,7 +112,8 @@ "view_keepalive": "Secondi di silenzio a «Vedi esterno» (0 = nessuno)", "allowed_users": "Utenti ammessi a squilli e media live (vuoto = tutti)", "ring_webhook_url": "Webhook a inizio squillo (URL)", - "ring_end_webhook_url": "Webhook a fine squillo (URL)" + "ring_end_webhook_url": "Webhook a fine squillo (URL)", + "video_bandwidth": "Qualità video dalla targa" }, "data_description": { "voice_answer": "Chi può rispondere alla chiamata parlando mentre squilla. Solo dichiarato: risponde solo chi si collega con /audio_ws?voice_answer=1 (Echo Show, HomeKit via Scrypted). Mai: la voce non risponde. Chiunque: risponde qualsiasi connessione con microfono; attenzione, un tablet a muro con il microfono rimasto aperto può rispondere da solo con i rumori di casa.", @@ -133,7 +134,8 @@ "view_keepalive": "Durante la vista dell'esterno l'integrazione manda silenzio audio, senza il quale via cloud la targa chiude la vista dopo ~10 s. Si ferma dopo questi secondi dall'inizio della vista (poi la vista si chiude da sola). Sul 2 fili in locale il silenzio tiene occupato l'appartamento: il predefinito è 0 in locale e 120 via cloud; 0 = nessun silenzio (sul 2 fili va bene 0 o 30).", "allowed_users": "Limita la card, lo storico squilli (foto e clip) e /audio_ws a questi utenti di Home Assistant. Gli amministratori sempre. Vuoto = ogni utente autenticato. NON limita l'entità camera né /av dalla rete locale: chi può aprire la camera vede (e sente) lo stream; e se la cartella foto è sotto una cartella media di HA, foto e clip compaiono nel browser media per tutti gli utenti.", "ring_webhook_url": "GET (fire-and-forget) inviata quando inizia uno squillo, es. per accendere un interruttore fittizio (Scrypted Dummy Switch) collegato a un Custom Doorbell Button. Un fallimento si logga come warning e non ferma lo squillo. Vuoto = disattivato.", - "ring_end_webhook_url": "GET (fire-and-forget) inviata quando lo squillo finisce, risposto, annullato o scaduto: stessa idea di sopra, per spegnere l'interruttore. Vuoto = disattivato." + "ring_end_webhook_url": "GET (fire-and-forget) inviata quando lo squillo finisce, risposto, annullato o scaduto: stessa idea di sopra, per spegnere l'interruttore. Vuoto = disattivato.", + "video_bandwidth": "Vale per tutto il video della targa: card, camera, /av e HomeKit. Automatico (consigliato): 2 Mbit/s in UDP locale, 256 kbit/s tramite il relay cloud, che perde qualche pacchetto su cento. 256 kbit/s: flusso più leggero, meno fotogrammi rovinati con un relay che perde pacchetti, ma immagine più piccola su alcune targhe (un Tab 5S Up 40515 scende a 320x240 a 15 fps). 2 Mbit/s: l'immagine più nitida (720x576 a 24 fps su un 40515) e dieci volte il traffico; con un relay che perde pacchetti (misurato su un Tab 7S Up 40517) il video si sgrana, si blocca e va in ritardo." } }, "homekit": { @@ -442,6 +444,13 @@ "talk": "Rispondi quando parli", "open": "Rispondi all'apertura" } + }, + "video_bandwidth": { + "options": { + "auto": "Automatico (2 Mbit/s in locale, 256 kbit/s in cloud)", + "256": "256 kbit/s (risoluzione più bassa su alcune targhe)", + "2048": "2 Mbit/s (più nitido, serve una connessione pulita)" + } } }, "exceptions": { diff --git a/custom_components/vimar_intercom/translations/en.json b/custom_components/vimar_intercom/translations/en.json index 2ab3d4c..69341be 100644 --- a/custom_components/vimar_intercom/translations/en.json +++ b/custom_components/vimar_intercom/translations/en.json @@ -112,7 +112,8 @@ "view_keepalive": "Seconds of silence for “Vedi esterno” (0 = none)", "allowed_users": "Users allowed to see rings and live media (empty = everyone)", "ring_webhook_url": "Ring-start webhook (URL)", - "ring_end_webhook_url": "Ring-end webhook (URL)" + "ring_end_webhook_url": "Ring-end webhook (URL)", + "video_bandwidth": "Video quality from the panel" }, "data_description": { "voice_answer": "Who can answer the call by talking while it rings. Declared only: only connections using /audio_ws?voice_answer=1 (Echo Show, HomeKit via Scrypted). Never: talking never answers. Anyone: any connection with a microphone can answer; beware, a wall tablet whose microphone was left open can answer by itself with household noise.", @@ -133,7 +134,8 @@ "view_keepalive": "While viewing the outside the integration sends audio silence; without it, over the cloud the panel closes the view after ~10 s. It stops this many seconds after the view starts (the view then ends by itself). On the 2-wire plant in local mode the silence keeps the apartment busy: the default is 0 in local mode and 120 over the cloud; 0 = no silence (on the 2-wire use 0 or 30).", "allowed_users": "Limits the card, the ring history (photos and clips) and /audio_ws to these Home Assistant users. Admins always. Empty = every logged-in user. It does NOT limit the camera entity or /av from the local network: anyone who can open the camera sees (and hears) the stream; and if the photo folder is under an HA media directory, photos and clips show up in the media browser for every user.", "ring_webhook_url": "GET (fire-and-forget) sent when a ring starts, e.g. to turn on a dummy switch (Scrypted Dummy Switch) linked to a Custom Doorbell Button. A failure is only logged as a warning and never stops the ring. Empty = off.", - "ring_end_webhook_url": "GET (fire-and-forget) sent when the ring ends, answered, cancelled or timed out: same idea as above, to turn the switch off. Empty = off." + "ring_end_webhook_url": "GET (fire-and-forget) sent when the ring ends, answered, cancelled or timed out: same idea as above, to turn the switch off. Empty = off.", + "video_bandwidth": "Applies to every video from the panel: the card, the camera, /av and HomeKit. Automatic (recommended): 2 Mbit/s on local UDP, 256 kbit/s over the cloud relay, which loses a few packets in a hundred. 256 kbit/s: a lighter stream, fewer damaged frames on a lossy relay, but a smaller picture on some panels (a Tab 5S Up 40515 drops to 320x240 at 15 fps). 2 Mbit/s: the sharpest picture (720x576 at 24 fps on a 40515) and ten times the traffic; on a relay that loses packets (measured on a Tab 7S Up 40517) the video smears, freezes and lags." } }, "homekit": { @@ -442,6 +444,13 @@ "talk": "Answer when you talk", "open": "Answer when the view opens" } + }, + "video_bandwidth": { + "options": { + "auto": "Automatic (2 Mbit/s locally, 256 kbit/s over the cloud)", + "256": "256 kbit/s (lower resolution on some panels)", + "2048": "2 Mbit/s (sharper, needs a clean connection)" + } } }, "exceptions": { diff --git a/custom_components/vimar_intercom/translations/it.json b/custom_components/vimar_intercom/translations/it.json index 85c4b8f..a0dbc04 100644 --- a/custom_components/vimar_intercom/translations/it.json +++ b/custom_components/vimar_intercom/translations/it.json @@ -112,7 +112,8 @@ "view_keepalive": "Secondi di silenzio a «Vedi esterno» (0 = nessuno)", "allowed_users": "Utenti ammessi a squilli e media live (vuoto = tutti)", "ring_webhook_url": "Webhook a inizio squillo (URL)", - "ring_end_webhook_url": "Webhook a fine squillo (URL)" + "ring_end_webhook_url": "Webhook a fine squillo (URL)", + "video_bandwidth": "Qualità video dalla targa" }, "data_description": { "voice_answer": "Chi può rispondere alla chiamata parlando mentre squilla. Solo dichiarato: risponde solo chi si collega con /audio_ws?voice_answer=1 (Echo Show, HomeKit via Scrypted). Mai: la voce non risponde. Chiunque: risponde qualsiasi connessione con microfono; attenzione, un tablet a muro con il microfono rimasto aperto può rispondere da solo con i rumori di casa.", @@ -133,7 +134,8 @@ "view_keepalive": "Durante la vista dell'esterno l'integrazione manda silenzio audio, senza il quale via cloud la targa chiude la vista dopo ~10 s. Si ferma dopo questi secondi dall'inizio della vista (poi la vista si chiude da sola). Sul 2 fili in locale il silenzio tiene occupato l'appartamento: il predefinito è 0 in locale e 120 via cloud; 0 = nessun silenzio (sul 2 fili va bene 0 o 30).", "allowed_users": "Limita la card, lo storico squilli (foto e clip) e /audio_ws a questi utenti di Home Assistant. Gli amministratori sempre. Vuoto = ogni utente autenticato. NON limita l'entità camera né /av dalla rete locale: chi può aprire la camera vede (e sente) lo stream; e se la cartella foto è sotto una cartella media di HA, foto e clip compaiono nel browser media per tutti gli utenti.", "ring_webhook_url": "GET (fire-and-forget) inviata quando inizia uno squillo, es. per accendere un interruttore fittizio (Scrypted Dummy Switch) collegato a un Custom Doorbell Button. Un fallimento si logga come warning e non ferma lo squillo. Vuoto = disattivato.", - "ring_end_webhook_url": "GET (fire-and-forget) inviata quando lo squillo finisce, risposto, annullato o scaduto: stessa idea di sopra, per spegnere l'interruttore. Vuoto = disattivato." + "ring_end_webhook_url": "GET (fire-and-forget) inviata quando lo squillo finisce, risposto, annullato o scaduto: stessa idea di sopra, per spegnere l'interruttore. Vuoto = disattivato.", + "video_bandwidth": "Vale per tutto il video della targa: card, camera, /av e HomeKit. Automatico (consigliato): 2 Mbit/s in UDP locale, 256 kbit/s tramite il relay cloud, che perde qualche pacchetto su cento. 256 kbit/s: flusso più leggero, meno fotogrammi rovinati con un relay che perde pacchetti, ma immagine più piccola su alcune targhe (un Tab 5S Up 40515 scende a 320x240 a 15 fps). 2 Mbit/s: l'immagine più nitida (720x576 a 24 fps su un 40515) e dieci volte il traffico; con un relay che perde pacchetti (misurato su un Tab 7S Up 40517) il video si sgrana, si blocca e va in ritardo." } }, "homekit": { @@ -442,6 +444,13 @@ "talk": "Rispondi quando parli", "open": "Rispondi all'apertura" } + }, + "video_bandwidth": { + "options": { + "auto": "Automatico (2 Mbit/s in locale, 256 kbit/s in cloud)", + "256": "256 kbit/s (risoluzione più bassa su alcune targhe)", + "2048": "2 Mbit/s (più nitido, serve una connessione pulita)" + } } }, "exceptions": { diff --git a/docs/CONFIGURATION.it.md b/docs/CONFIGURATION.it.md index 8dab604..6d0743e 100644 --- a/docs/CONFIGURATION.it.md +++ b/docs/CONFIGURATION.it.md @@ -70,6 +70,7 @@ Impostazioni → Vimar Intercom → **Configura**: | **Cartella foto squillo** (`snapshot_dir`) | dove salvare, a ogni squillo, la foto di chi suona (`squillo_AAAAMMGG_HHMMSS_mmm.jpg` + `ultimo_squillo.jpg`) e il clip dello squillo (`squillo_AAAAMMGG_HHMMSS_mmm.mp4`: il video dell'anteprima, e della chiamata se si risponde da HA, fino a 60 s, senza audio), es. `/config/media/citofono`. Deve essere scrivibile da HA. Vuoto = disattivato | | **Secondi per la foto migliore** (`snapshot_delay`) | la prima foto si salva appena arriva il primo fotogramma (~1 s dallo squillo); dopo questi secondi la sostituisce un fotogramma con l'esposizione regolata (il primo keyframe della targa è scuro). Default 3 (Tab 5S Up 40515), 0 = resta la prima | | **Silenzio della vista** (`view_keepalive`) | secondi di silenzio audio mandati durante «Vedi esterno» (0 = nessuno). Via cloud senza silenzio la targa chiude la vista a ~10 s; sul 2 fili in locale tiene occupato l'appartamento (fino a 300 s). Default: 120 via cloud, 0 in locale; sul 2 fili metti 0 o 30 | +| **Qualità video dalla targa** (`video_bandwidth`) | la banda video chiesta alla targa, per tutto il video (card, camera, `/av`, HomeKit). **Automatico** (default): 2 Mbit/s in UDP locale, 256 kbit/s tramite il relay cloud. **256 kbit/s**: meno fotogrammi rovinati con un relay che perde pacchetti, ma immagine più piccola su alcune targhe (un Tab 5S Up 40515 scende a 320x240 a 15 fps). **2 Mbit/s**: l'immagine più nitida (720x576 a 24 fps su un 40515), dieci volte il traffico; con un relay che perde pacchetti (un Tab 7S Up 40517) il video si sgrana, si blocca e va in ritardo | | **Utenti ammessi** (`allowed_users`) | Limita la card, lo storico squilli (`GET /api/vimar_intercom/rings`, foto e clip) e `/audio_ws` a questi utenti HA. Gli amministratori sempre. Vuoto = ogni utente autenticato (default). **Non** è per utente per l'entità camera né per `/av` usato con la sua chiave (la camera di HA, go2rtc: solo rete locale, vedi sotto): chi può aprire la camera vede e sente lo stream. Se la cartella foto è sotto una cartella media di HA (es. `/config/media/citofono`), foto e clip compaiono nel browser media per tutti gli utenti | | **Messaggio di assenza** (`away_message_file`, `away_message_delay`) | file audio (mp3, wav…) fatto sentire al visitatore se nessuno risponde entro N secondi; poi l'integrazione riaggancia. Se il Tab espone il ritardo della segreteria si usa quello (*Segreteria · ritardo*) | | **Messaggio di assenza da testo** (`away_message_text`, `away_message_tts`) | se il campo file è vuoto, questo testo lo legge la sintesi vocale di Home Assistant (`away_message_tts` = un'entità `tts.*`; vuoto = il motore predefinito di HA) nella lingua di HA, max 30 s. L'audio si genera all'avvio e resta in cache; se il TTS fallisce il citofono squilla come sempre | diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index d5fcac1..80d5737 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -70,6 +70,7 @@ Settings → Vimar Intercom → **Configure**: | **Ring snapshot folder** (`snapshot_dir`) | Where the visitor's photo (`squillo_YYYYMMDD_HHMMSS_mmm.jpg` + `ultimo_squillo.jpg`) and the ring clip (`squillo_YYYYMMDD_HHMMSS_mmm.mp4`: the preview video, and the call if answered from HA, up to 60 s, no audio) are saved on every ring, e.g. `/config/media/citofono`. Must be writable by HA. Empty = off | | **Seconds for the better photo** (`snapshot_delay`) | The first photo is saved as soon as the first frame arrives (~1 s after the ring); after this many seconds it is replaced by a frame with the exposure settled (the panel's first keyframe is dark). Default 3 (Tab 5S Up 40515), 0 = keep the first | | **View silence** (`view_keepalive`) | Seconds of audio silence sent during "Vedi esterno" (0 = none). Over the cloud the panel closes the view after ~10 s without it; on the 2-wire plant in local mode it keeps the apartment busy (up to 300 s). Default: 120 over the cloud, 0 in local mode; on a 2-wire plant use 0 or 30 | +| **Video quality from the panel** (`video_bandwidth`) | The video bandwidth asked of the panel, for every video (card, camera, `/av`, HomeKit). **Automatic** (default): 2 Mbit/s on local UDP, 256 kbit/s over the cloud relay. **256 kbit/s**: fewer damaged frames on a relay that loses packets, but a smaller picture on some panels (a Tab 5S Up 40515 drops to 320x240 at 15 fps). **2 Mbit/s**: the sharpest picture (720x576 at 24 fps on a 40515), ten times the traffic; over a lossy relay (a Tab 7S Up 40517) the video smears, freezes and lags | | **Allowed users** (`allowed_users`) | Limits the card, the ring history (`GET /api/vimar_intercom/rings`, photos and clips) and `/audio_ws` to these HA users. Admins are always allowed. Empty = every logged-in user (default). It is **not** per user for the camera entity nor for `/av` used with its key (HA's own camera stream, go2rtc: local network only, see below): anyone who can open the camera sees and hears the stream. If the photo folder is under an HA media directory (e.g. `/config/media/citofono`), photos and clips also show up in the media browser for every user | | **Away message** (`away_message_file`, `away_message_delay`) | Audio file (mp3, wav…) played to the visitor if nobody answers within N seconds; then the integration hangs up. If the Tab exposes the voicemail delay, that one is used instead (*Voicemail · delay*) | | **Away message from text** (`away_message_text`, `away_message_tts`) | If the file field is empty, this text is read by Home Assistant's text-to-speech (`away_message_tts` = a `tts.*` entity; empty = HA's default engine) in HA's language, max 30 s. The audio is generated at startup and cached; if TTS fails the doorbell keeps ringing as usual | diff --git a/docs/TROUBLESHOOTING.it.md b/docs/TROUBLESHOOTING.it.md index 4beec65..27476fa 100644 --- a/docs/TROUBLESHOOTING.it.md +++ b/docs/TROUBLESHOOTING.it.md @@ -18,6 +18,7 @@ corretti in una release, indicata nella colonna *Cosa fare*. Poi attiva il log d | `queued` / `202 Accepted` nei log o nel sensore *Intercom Ultimo Comando* | Il relay cloud ha accettato il comando ma non ne ha confermato la consegna; la sua risposta può arrivare dopo ~15 s | 1.0.20 o successiva: un 202 risulta `queued`, non eseguito, e i comandi via cloud aspettano 20 s. Controlla se è successo prima di riprovare | [#120](https://github.com/ha-vimar/ha-vimar-intercom/pull/120), [#139](https://github.com/ha-vimar/ha-vimar-intercom/pull/139), [#140](https://github.com/ha-vimar/ha-vimar-intercom/issues/140) | | Porta e attuatori dicono «Non registrato» per ore dopo un calo di rete (cloud) | Due cicli di riconnessione giravano insieme | 1.0.10 o successiva | [#23](https://github.com/ha-vimar/ha-vimar-intercom/issues/23) | | Camera: `404` da `55100`, niente video | La targa video di default non esiste sull'impianto | Importa la rubrica (imposta *Targa video*) o impostala a mano. Dalla 1.0.15 il campo vuoto usa la targa che ha suonato per ultima con il video | [#3](https://github.com/ha-vimar/ha-vimar-intercom/issues/3), [#129](https://github.com/ha-vimar/ha-vimar-intercom/issues/129) | +| Il video si sgrana, si blocca o va in ritardo, di più dalla 1.0.19; il log di debug mostra pacchetti video persi (`persa`) | La targa manda 2 Mbit/s tramite un relay cloud che perde qualche pacchetto su cento: dieci volte i pacchetti, dieci volte i fotogrammi rovinati (misurato su un Tab 7S Up 40517) | Impostazioni → *Qualità video dalla targa*: *Automatico* (256 kbit/s in cloud) o 256 kbit/s. Torna a 2 Mbit/s se lì l'immagine andava bene e la vuoi più nitida | [#161](https://github.com/ha-vimar/ha-vimar-intercom/pull/161) | | Clip dello squillo salvata ma niente foto (Tab 7S 40517, cloud) | In analisi: la foto aspettava un secondo keyframe e questa targa forse ne manda uno solo | 1.0.20 o successiva (foto dal primo keyframe, decoder nel log di debug). Se manca ancora: aggiungi alla issue il log di debug di uno squillo | [#129](https://github.com/ha-vimar/ha-vimar-intercom/issues/129) (aperta) | | Né anteprima né foto mentre suona, solo dopo la risposta | L'impianto non manda early media | Limite noto, niente da correggere lato HA | — | | «AV stream: call not established after 25s» subito dopo un riaggancio (UDP locale) | La targa era ancora occupata con la chiamata precedente | 1.0.17 o successiva | [#41](https://github.com/ha-vimar/ha-vimar-intercom/issues/41) | diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index e38c618..fa24641 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -18,6 +18,7 @@ reproduce the problem once. Not listed here? Open an issue with that log. | `queued` / `202 Accepted` in the logs or in the *Intercom Ultimo Comando* sensor | The cloud relay accepted the command but did not confirm delivery; its answer can take ~15 s | 1.0.20 or newer: a 202 is reported as `queued`, not as done, and cloud commands wait 20 s. Check whether it happened before retrying | [#120](https://github.com/ha-vimar/ha-vimar-intercom/pull/120), [#139](https://github.com/ha-vimar/ha-vimar-intercom/pull/139), [#140](https://github.com/ha-vimar/ha-vimar-intercom/issues/140) | | Door and actuators say "Not registered" for hours after a network drop (cloud) | Two reconnection loops ran at the same time | 1.0.10 or newer | [#23](https://github.com/ha-vimar/ha-vimar-intercom/issues/23) | | Camera: `404` from `55100`, no video | The default video panel does not exist on the plant | Import the phonebook (it sets *Video entrance panel*) or set it by hand. Since 1.0.15 an empty field uses the panel that last rang with video | [#3](https://github.com/ha-vimar/ha-vimar-intercom/issues/3), [#129](https://github.com/ha-vimar/ha-vimar-intercom/issues/129) | +| Video smears, freezes or lags, more since 1.0.19; the debug log shows lost video packets (`persa`) | The panel sends 2 Mbit/s through a cloud relay that loses a few packets in a hundred: ten times the packets, ten times the damaged frames (measured on a Tab 7S Up 40517) | Settings → *Video quality from the panel*: *Automatic* (256 kbit/s over the cloud) or 256 kbit/s. Back to 2 Mbit/s if the picture was fine there and you want it sharper | [#161](https://github.com/ha-vimar/ha-vimar-intercom/pull/161) | | Ring clip saved but no ring photo (Tab 7S 40517, cloud) | Under investigation: the photo waited for a second keyframe and this panel may send only one | 1.0.20 or newer (photo from the first keyframe, decoder logged at debug). Still missing: add the debug log of one ring to the issue | [#129](https://github.com/ha-vimar/ha-vimar-intercom/issues/129) (open) | | No preview and no photo while it rings, only after answering | The plant sends no early media | Known limitation, nothing to fix on the HA side | — | | "AV stream: call not established after 25s" right after hanging up (local UDP) | The panel was still busy with the previous call | 1.0.17 or newer | [#41](https://github.com/ha-vimar/ha-vimar-intercom/issues/41) | diff --git a/tests/test_config_flow_picg.py b/tests/test_config_flow_picg.py index f351f29..8852cec 100644 --- a/tests/test_config_flow_picg.py +++ b/tests/test_config_flow_picg.py @@ -149,6 +149,28 @@ def test_the_homekit_page_keeps_the_network_settings(of): assert out["data"]["homekit_ring_button"] is False, "off unless ticked" +SETTINGS = {"local_proxy": "192.0.2.1", "use_local_udp": False, "local_udp_port": 5060, + "media_enc": False, "actuators": "", "sga_target": "61000", + "picg_target": "61000", "camera_target": "55001", "door_target": "55001"} + + +def test_the_settings_page_saves_the_video_bandwidth(of): + """It changes every video from the panel, so it lives on the general page.""" + out = _save(of, "async_step_settings", {**SETTINGS, "video_bandwidth": "2048"}, FOREIGN) + assert out["type"] == "create_entry", out + assert out["data"]["video_bandwidth"] == "2048" + out = _save(of, "async_step_settings", SETTINGS, FOREIGN) + assert out["data"]["video_bandwidth"] == "auto", "the default when not chosen" + out = _save(of, "async_step_settings", SETTINGS, {**FOREIGN, "video_bandwidth": "2048"}) + assert out["data"]["video_bandwidth"] == "2048", "a saved choice is kept" + + +def test_the_homekit_page_keeps_the_video_bandwidth(of): + out = _save(of, "async_step_homekit", {"homekit_accessory": True}, + {**FOREIGN, "video_bandwidth": "2048"}) + assert out["data"]["video_bandwidth"] == "2048" + + def test_the_settings_page_keeps_homekit_and_unknown_keys(of): """Saving the network page used to drop the HomeKit options: HomeKit switched itself off after an unrelated change.""" diff --git a/tests/test_sdp.py b/tests/test_sdp.py index 3a4f5e2..b242bd1 100644 --- a/tests/test_sdp.py +++ b/tests/test_sdp.py @@ -33,13 +33,31 @@ def test_build_sdp_plain_rtp_default(): assert sip.SDP._local_video_crypto_key is None -def test_build_sdp_asks_the_panel_for_2_mbit_video(): - """256 kbit/s made the 40515 send 16 fps blocky video; it honours 2048.""" +@pytest.mark.parametrize("option, local_udp, video, session", [ + (None, False, 256, 512), # automatic over the cloud relay + (None, True, 2048, 2200), # automatic on local UDP + ("auto", False, 256, 512), + ("auto", True, 2048, 2200), + ("256", True, 256, 512), # forced either way + ("2048", False, 2048, 2200), + ("bogus", False, 256, 512), # an unknown value is automatic +]) +def test_build_sdp_asks_for_the_chosen_video_bandwidth(monkeypatch, option, local_udp, video, session): + """2048 kbit/s made a 40517 send ten times the packets through a cloud relay + that loses them: automatic is 256 over the cloud, 2048 on local UDP (#161).""" + from custom_components.vimar_intercom import runtime as R + for name in dir(R): # configure() rewrites module globals: put them back + if name.isupper(): + monkeypatch.setattr(R, name, getattr(R, name)) S.MEDIA_ENC = False + data = {"sip_user": "1001", "sip_domain": "example.invalid", "use_local_udp": local_udp} + if option is not None: + data["video_bandwidth"] = option + R.configure(data) offer = sip.parse_sdp(sip.build_sdp()) for sdp in (sip.build_sdp(), sip.build_sdp(offer)): - assert "b=AS:2200\r\nt=0 0" in sdp - assert "m=video" in sdp and "b=AS:2048\r\n" in sdp.split("m=video")[1] + assert f"b=AS:{session}\r\nt=0 0" in sdp + assert "m=video" in sdp and f"b=AS:{video}\r\n" in sdp.split("m=video")[1] def test_build_sdp_srtp_when_enabled():