Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PiPup — Home Assistant integration

Generated by aurora@aurora-smart-home (ha-integration-dev skill)

hacs_badge

Control PiPup popups on Android TV / Fire TV devices from Home Assistant — including showing a camera stream for as long as there is motion.

Requires the PiPup fork APK on the TV (the original Play Store version lacks the /state endpoint and indefinite popups).

Features

  • Config flow per TV (host + port) with automatic mDNS discovery (app ≥ 0.2.5 advertises _pipup._tcp); discovered TVs carry a stable device id, so the config entry follows the TV across DHCP address changes (existing host:port entries migrate automatically on first contact). Each TV becomes a device with:
    • Popup binary sensor (on = a popup is visible; attributes: popup_id, duration, indefinite, elapsed)
    • Screen binary sensor (TV screen on/interactive — requires app ≥ 0.2.3)
    • Connectivity binary sensor — FireTV sticks cut their network in standby; entities now stay available with their last state (popup/screen report unknown) and this sensor tells you whether the TV currently answers, so automations can trigger on its off→on edge
    • Notification notify entity — the TV works as a standard notify.send_message target (text-only popups honoring the per-device defaults)
    • Current popup sensor (the visible popup id) and Popups shown counter
    • Default position select — the position used when pipup.show is called without one
    • Dismiss popup button
    • PiPup app update entity — checks the fork's GitHub releases and, with app >= 0.6.0, the Install button makes the TV update itself (silent on Android 12+, system confirmation on older devices)
    • App uptime diagnostic sensor and a diagnostics download
  • Action pipup.show — title/message/media popup with all PiPup fields, plus:
    • duration: 0 → popup stays until dismissed or replaced
    • popup_id → re-sending the same id+content only extends the timer (stream keeps playing, no flicker)
    • camera_entity → shows the camera without any URLs; camera_mode picks mjpeg (default — software-decoded, audio-free, safe while the TV is playing video), stream (HLS, hardware decoder — can freeze concurrent live-TV playback on some devices) or snapshot
    • position is optional — omitted means the device's configured default position
    • muted: true by default (requires app ≥ 0.2.4) — audio in a popup can freeze video playback on some TVs; pass muted: false when you do want sound
    • per-device defaults for everything — duration, position, muted, media size, colors and text sizes are configurable per TV in the integration options (Settings → Devices & Services → PiPup → Configure); action fields act as one-off overrides
    • tts → text spoken aloud on the TV when the popup appears (app ≥ 0.2.5); optional tts_language takes a BCP-47 tag such as nl-NL (device default when omitted)
    • buttons → up to 3 remote-operable buttons on the popup (app ≥ 0.3.0): OK activates, BACK dismisses. A press fires the pipup_button event (with popup_id, button, label, device_id) via an automatically registered local-only webhook — no configuration needed; see the doorbell example below
    • show_progress → animated countdown bar for finite durations (app ≥ 0.3.0)
    • urgencyinfo/warning/critical colored border presets (app ≥ 0.3.0)
  • Action pipup.dismiss — remove the popup, optionally only when it has a given popup_id.

Installation

HACS (Recommended)

  1. HACS → Integrations → Custom repositories
  2. Add https://github.com/mhoogenbosch/ha-pipup as Integration
  3. Install "PiPup" and restart Home Assistant

Manual

  1. Copy custom_components/pipup to your custom_components/
  2. Restart Home Assistant

Configuration

TVs running the fork app ≥ 0.2.5 are discovered automatically — they show up under Settings → Devices & Services as a discovered PiPup device; press Configure, optionally set a name and a name suffix, done. Because entries key on the app's stable device id, they keep working when the TV gets a different DHCP address.

Manual fallback (older app, or discovery blocked between VLANs): Settings → Devices & Services → Add Integration → PiPup → enter the TV's IP and port (default 7979).

Per-TV popup defaults (position, duration, muted, sizes, colors) live under Configure on each entry; action fields override them per call.

Examples

Real-world recipes from the household this integration was built for. All popup styling (position, colors, size) falls back to the per-device defaults, so the calls stay short.

House-wide announcement ("dinner is ready") with TTS

One script pops a message on every TV — straight through games and headphone sessions — and speaks it out loud (app ≥ 0.2.5). Unreachable/sleeping TVs are skipped silently.

script:
  announce:
    alias: House announcement
    fields:
      message: {required: true}
    sequence:
      - action: pipup.show
        continue_on_error: true    # sleeping TVs must not abort the rest
        target:
          entity_id:
            - binary_sensor.pipup_living_room_popup
            - binary_sensor.pipup_kids_room_popup
            - binary_sensor.pipup_bedroom_popup
        data:
          popup_id: announce
          title: "📢 Announcement"
          message: "{{ message }}"
          duration: 15
          tts: "{{ message }}"
          tts_language: nl-NL
          muted: false

Wire it to a dashboard button with tap_actionperform-action: script.announce, data: {message: "Dinner is ready!"} — add a confirmation: so nobody summons the whole house by accident.

Info popup with a templated message (energy summary)

The message field takes Jinja, so a "mini dashboard" is just a multi-line template. With show_progress: true (app ≥ 0.3.0) the popup shows a countdown bar.

action: pipup.show
target:
  entity_id: binary_sensor.pipup_living_room_popup
data:
  popup_id: energy
  duration: 25
  position: center
  title: "⚡ Energy & car"
  show_progress: true
  message: >-
    {% set net = states('sensor.grid_power') | int(0) %}
    ☀️ Solar: {{ states('sensor.pv_power') | int(0) }} W
    🏠 House: {{ states('sensor.house_power') | int(0) }} W
    🔌 Grid: {{ net | abs }} W {{ 'import' if net > 0 else 'export' }}
    🚗 Battery: {{ states('sensor.car_battery') | int(0) }}%

Doorbell popup with an "open the door" button

Buttons (app ≥ 0.3.0) are operated with the TV remote: OK activates, BACK dismisses. A press fires the pipup_button event, so the acting automation can (and should) filter on both the button id and the device id — only the intended TV can open the door.

# 1) the doorbell automation shows the popup with a button
- action: pipup.show
  target:
    entity_id: binary_sensor.pipup_living_room_popup
  data:
    popup_id: doorbell
    duration: 45                 # finite: never leave an undismissable popup
    title: Front door
    tts: "Someone is at the door"
    web_url: "http://go2rtc.local:1984/stream.html?src=doorbell&mode=webrtc"
    media_width: 720
    media_height: 540
    buttons:
      - id: unlock_front_door
        label: "🔓 Open the door"

# 2) a second automation acts on the press
triggers:
  - trigger: event
    event_type: pipup_button
    event_data:
      button: unlock_front_door
      device_id: "1d91b16a-…"    # the app's stable id — check the Diagnostics download
actions:
  - action: lock.open
    target:
      entity_id: lock.front_door
  - action: notify.mobile_app_phone   # audit trail
    data:
      message: "Front door opened via the living-room TV"

Calendar reminder 15 minutes ahead

triggers:
  - trigger: calendar
    entity_id: calendar.family
    event: start
    offset: "-00:15:00"
conditions:
  - "{{ not trigger.calendar_event.all_day }}"   # all-day events would pop at 23:45
actions:
  - action: pipup.show
    target:
      entity_id: binary_sensor.pipup_living_room_popup
    data:
      popup_id: agenda
      duration: 30
      show_progress: true
      title: "📅 Up next ({{ trigger.calendar_event.start | as_timestamp | timestamp_custom('%H:%M') }})"
      message: "{{ trigger.calendar_event.summary }}"

Show camera while there is motion

triggers:
  - trigger: state
    entity_id: binary_sensor.driveway_motion
    to: "on"
    id: motion_on
  - trigger: state
    entity_id: binary_sensor.driveway_motion
    to: "off"
    for: "00:00:15"
    id: motion_off
actions:
  - choose:
      - conditions: "{{ trigger.id == 'motion_on' }}"
        sequence:
          - action: pipup.show
            target:
              entity_id: binary_sensor.pipup_living_room_popup
            data:
              title: Motion on the driveway
              duration: 0            # until dismissed
              popup_id: driveway
              camera_entity: camera.driveway   # camera_mode defaults to mjpeg (safe next to live TV)
      - conditions: "{{ trigger.id == 'motion_off' }}"
        sequence:
          - action: pipup.dismiss
            target:
              entity_id: binary_sensor.pipup_living_room_popup
            data:
              popup_id: driveway     # never cancels an unrelated popup

When you show indefinite (duration: 0) popups, make the automation mode: queued and put continue_on_error: true on the pipup actions — with the default mode: single a dismiss that fires while a show is still running is silently dropped, leaving the popup on screen.

Device notes: TCL Google TV needs a keep-alive automation

On TCL Google TV sets, PiPup does not stay up by itself. TCL runs a vendor guard (com.tcl.guard) that both kills and freezes background apps, so sooner or later the TV ends up in a state where the process is still listed but the HTTP server no longer answers: this integration then reports the device as unreachable and every pipup.show fails, even though the TV is online and ADB works. See the app README for the mechanism, the required app-ops and how to recognise a frozen process.

What matters here: the app survives only when it runs as a foreground service at oom_score_adj 200, and that happens only if it was started from a foreground context. So the recovery command must start the activity, not the service — am start-foreground-service lands on adj 500 and gets frozen again within seconds.

This automation restarts PiPup whenever the integration reports the TV as unreachable while ADB is still answering. It needs the Android TV / ADB integration for the same TV.

alias: PiPup keep-alive (TCL)
description: >
  Restarts PiPup on a TCL Google TV after the vendor guard killed or froze it.
  Starts the activity (not the service): only a foreground start survives.
mode: single
triggers:
  - trigger: state
    entity_id: binary_sensor.pipup_living_room_reachable
    to: "off"
    for: "00:10:00"
  - trigger: time_pattern
    minutes: /15          # retry while PiPup stays down
  - trigger: time
    at: "05:00:00"        # daily check
conditions:
  # only when the TV itself is reachable — otherwise there is nothing to fix
  - condition: state
    entity_id: media_player.android_tv_living_room
    state: ["idle", "standby", "off", "paused"]
  - condition: or
    conditions:
      - condition: state
        entity_id: binary_sensor.pipup_living_room_reachable
        state: "off"
      - condition: state
        entity_id: binary_sensor.pipup_living_room_reachable
        state: "unavailable"
actions:
  - action: androidtv.adb_command
    continue_on_error: true
    target:
      entity_id: media_player.android_tv_living_room
    data:
      command: >-
        input keyevent KEYCODE_WAKEUP; sleep 2;
        am start -n nl.rogro82.pipup/.MainActivity; sleep 3;
        input keyevent KEYCODE_HOME

Notes from running this in practice:

  • Do not gate the command on ps | grep pipup. A frozen process is still listed, so the guard short-circuits exactly when you need the restart. Starting unconditionally is safe.
  • The state condition keeps the automation from interrupting viewing: bringing the activity to the front briefly takes over the screen, so skip it while the TV is playing.
  • KEYCODE_WAKEUP is needed because am start does not reach the foreground while the TV is dreaming (screensaver). Note this wakes the screen — that is the price of the recovery.
  • After the app is reachable again, an integration entry that failed to set up earlier stays in setup_retry; reload it (Settings → Devices & Services → PiPup → ⋮ → Reload) or the entities remain unavailable. Enabling/disabling the entry does not help — it is already enabled.
  • If you monitor port 7979 externally (e.g. Uptime Kuma), that sensor is a good extra trigger, because it detects a dead server slightly sooner than the coordinator does.

Security

PiPup runs an unauthenticated HTTP server on each TV, so any device on the network can show popups (including arbitrary JavaScript via a web popup). Keep PiPup TVs on a trusted network segment. See the app's Security section.

Button presses arrive over a local-only webhook. To stop a device on the LAN from forging a pipup_button event (which could drive a security-sensitive automation such as a door lock), this integration mints a single-use token for every button popup, passes it in the callback URL, and rejects any callback whose token is missing, unknown or expired. Traffic to the TV is plain HTTP, so the token can still be sniffed within its short window on an untrusted network — another reason to keep these devices on a segment you control.

Troubleshooting

  • "unsupported_version" while adding — the TV runs the original PiPup; sideload the fork APK first.
  • Connectivity sensor off / popup & screen "unknown" — the TV is off/asleep (FireTV sticks cut their network entirely in standby) or the PiPup service is not running (open the app once after boot). Entities keep their last state on purpose; automate on the connectivity sensor's off→on edge.
  • Buttons don't react to the remote — buttons require app ≥ 0.3.0; also note the popup takes input focus while buttons are visible (BACK gives control back to the TV app).
  • Camera stream does not play — the TV must be able to reach your Home Assistant internal URL; check Settings → System → Network → Home Assistant URL. Snapshot mode (camera_mode: snapshot) works without any URL reachability.
  • Popup does not appear at all — grant the overlay permission once: adb shell appops set nl.rogro82.pipup SYSTEM_ALERT_WINDOW allow.
  • TV is online and ADB works, but PiPup is unreachable (TCL sets) — the vendor guard froze the process: it is still listed by ps while port 7979 no longer answers. Restart PiPup by starting its activity, and automate it — see Device notes: TCL Google TV.
  • Every pipup.show fails with "No loaded PiPup devices in the action target" — either the target is not a PiPup entity/device, or the config entry is not loaded (it stays in setup_retry when the TV was unreachable at startup). Reload the entry; the target itself may be perfectly fine. Any entity of the device works as a target, not just the popup sensor.

Recovery

Remove the device via Settings → Devices & Services; re-adding it restores everything (no state is stored on the TV).

About

Home Assistant integration for PiPup (fork): show camera streams and notifications on Android TV for as long as you want

Topics

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages