Skip to content

v10.6.0 — Image rotation 180° for ceiling-mounted indoor cameras

Choose a tag to compare

@mosandlt mosandlt released this 03 May 18:59
· 6 commits to main since this release

New: 180° image rotation switch for ceiling-mounted indoor cameras.

Adds a per-camera switch switch.bosch_<cam>_bild_180deg_drehen (Indoor only — Gen1 360 + Gen2 Indoor II) that rotates the camera image by 180° for upside-down ceiling mounting. Outdoor cameras don't get the switch since their mounting orientation is fixed by design.

Bosch firmware exposes no native rotation API, so the implementation is client-side at three layers:

What it does

  • Lovelace card: applies a CSS transform: rotate(180deg) to the <video> and <img> elements. Zero CPU, zero latency, GPU-composited — toggle is instant with no stream restart and no re-encode.
  • Snapshot path: rotates the JPEG via PIL in camera.async_camera_image() before serving it through HA's camera proxy. So push notifications, NAS clip uploads, and any other consumer that reads the camera entity also see the right-way-up image (~15–30 ms per snapshot).
  • PTZ pan inversion (Gen1 360 only): BoschPanNumber automatically inverts the slider sign when the rotation switch is on, so "right" on the slider stays "right" on the user's screen even when the camera is upside-down.

State persists across HA restarts via RestoreEntity. Default OFF.

Card v2.11.1 ships alongside

The card auto-detects the rotation switch via the entity-id convention switch.<base>_bild_180deg_drehen and applies/removes the CSS class on every set hass() — no card YAML config change required.

Why client-side instead of go2rtc rotation

Researched go2rtc's #rotate=180 parameter: it invokes a full FFmpeg transpose=1,transpose=1 re-encode (~30–60% of one CPU core per 1080p stream, +80–150 ms latency, quality loss from second-generation H.264 encoding) and causes a 3–6 s blackout on every toggle (FFmpeg respawn, HLS playlist invalidation, WebRTC PeerConnection drop). H.264 SEI display-orientation and MP4 tkhd matrix are decoder hints that browsers ignore for live HLS. CSS in the card avoids all of this.

Notes

  • Bosch's official position is that the cameras are not designed for ceiling mounting — for the Gen1 360, the privacy shutter is gravity-actuated and may not open/close reliably when upside-down. This is hardware, not fixable in software.
  • For Lovelace cards other than the bundled bosch-camera-card, only the snapshot path applies — live <video> elements in third-party cards won't be rotated unless they also read this switch.

Files changed

  • __init__.py — coordinator dict _image_rotation_180
  • switch.py — new BoschImageRotation180Switch (RestoreEntity)
  • camera.py_rotate_jpeg_180 PIL helper + hook in async_camera_image()
  • number.pyBoschPanNumber sign inversion when rotation on
  • translations/de.json + en.json — "Bild 180° drehen" / "Rotate Image 180°"
  • src/bosch-camera-card.js_applyImageRotation180() method + CSS .rotated-180

Verified live on Innenbereich (Gen2 Indoor II) — snapshot rotation visually confirmed (timestamp overlay flipped + room contents inverted), card CSS rotation verified live in Chrome with no console errors, Pan inversion logic in place but not yet live-verified (Gen1 360 currently offline).