HA destroyed my battery and roaming data

[This thread is not about the the camera issue others have experienced]

TLDR: the Companion app for Android (at least the minimal version) has a design defect that keeps the device awake for at least 5 minutes, if not permanently, destroying battery and data. The more entities you have in your HA, or the more frequently they update, the more this affects you.

I recently went on a trip and found out first day during the trip that my roaming data was very low, and my battery was dropping like a rock. Since I VPN home using OpenVPN for Android, I could see that there was a constant stream of data, 3-5 kbit/s download. I force-closed the Companion app on my phone, and that immediately dropped to literal zero.

I checked my battery usage and, sure enough, Home Assistant Companion was causing an enormous radio battery usage. I decided to force close HA during the vacation and stopped using it altogether.

Coming back home I decided to reproduce the scenario (connect my phone to VPN and the data network) then tcpdump the data between my phone and HA. What I discovered is that when the app is backgrounded, there is a constant WebSocket stream of events from HA to the app, for every entity that gets updated in every single tile of every single tab of the home dashboard I left open when I backgrounded the app in my HA setup. This is literally hundreds of events a minute!

This stream stops when the app is force closed.

Since I use the minimal version of the app, and HA still has yet to implement NTFY support, I must keep the WebSocket connection open so my home can notify me about important events. But I can’t realistically keep the WebSocket connection open, if the app insists on keeping subscribed to a stream of events I’m not even viewing (because the app is clearly backgrounded, and most of these entities aren’t even visible at all on the main tab of the dashboard in question).

What is my path forward here?

EDIT: I created a totally empty dashboard, set that dashboard as the default dashboard, and then simply restarted + force-closed the app. I began tcpdumping, opened the app, and indeed I keep getting pretty much every entity event sent to my phone, even though there is nothing to display in the dashboard!

UPDATE: This appears to be a bug in the HA frontend. I have analyzed the issue further:

  • The problem of the dashboard subscribing to all entities happens on mobile and desktop web views, not just the app.
  • Switching from the web view (browser app) to another app on the phone does not stop the subscription, and therefore HA continues to stream data to the view.
  • HACS frontend extensions are not involved (I literally deleted every extension I was using, and even disabled Browser Mod).
  • Whichever dashboard is being displayed on HA frontend startup or being displayed at the time of switching apps does not influence which entities send updates to the frontend. All entity value changes send events to the frontend. Therefore there is no way to mitigate this by switching to another (blank) dashboard when switching away from the HA app to another app.

OK I can trace back all the entity updates to the following WebSocket request sent by the client:

{"type":"subscribe_entities","id":3}

This sucks, because every entity value update gets sent to the client, whether the client is viewing a dashboard or not. I’m seeing a crapton of updates per second as a consequence of this.

The client should unsubscribe from the subscribe_entities stream when the client is backgrounded. There is no point in keeping this subscription going, because the client app does nothing with it. I have no widgets, no tiles, no nothing, that would justify the subscription to a single entity, much less to all the entities on my system.

I would classify this as a serious companion app bug.

More tcpdump investigations reveal that the app opens two WebSocket connections. One is used to get events for the dashboards. The other is used to receive messages using the notify service (ahem, action).

The first connection sends the following subscription requests (with the subscribe_entities being problematic):

{"type":"supported_features","id":1,"features":{"coalesce_messages":1}}
{"type":"subscribe_entities","id":3}
{"type":"subscribe_events","event_type":"entity_registry_updated","id":4}
{"type":"config/entity_registry/list_for_display","id":5},{"type":"subscribe_events","event_type":"device_registry_updated","id":6},{"type":"config/device_registry/list","id":7},{"type":"subscribe_events","event_type":"area_registry_updated","id":8},{"type":"config/area_registry/list","id":9},{"type":"subscribe_events","event_type":"component_loaded","id":10},{"type":"subscribe_events","event_type":"core_config_updated","id":11}
{"type":"get_config","id":12},{"type":"subscribe_events","event_type":"service_registered","id":13},{"type":"subscribe_events","event_type":"service_removed","id":14},{"type":"get_services","id":15},{"type":"subscribe_events","event_type":"panels_updated","id":16},{"type":"get_panels","id":17},{"type":"subscribe_events","event_type":"themes_updated","id":18},{"type":"frontend/get_themes","id":19},{"type":"auth/current_user","id":20},{"type":"frontend/get_user_data","key":"core","id":21},{"type":"subscribe_events","event_type":"repairs_issue_registry_updated","id":22},{"type":"repairs/list_issues","id":23},{"type":"recorder/info","id":24},{"type":"sensor/numeric_device_classes","id":25},{"type":"frontend/get_user_data","key":"language","id":26},{"type":"subscribe_events","event_type":"component_loaded","id":27},{"type":"frontend/get_translations","language":"en","category":"entity_component","id":28},{"type":"frontend/get_translations","language":"en","category":"entity","id":29}

The first connection is the one that causes enormous amounts of data. The data looks like hundreds and hundreds of tiny packets containing stuff analogous to this:

{"type":"event","event":{"c":{"sensor.reduit_washer_wall_plug_rms_voltage":{"+":{"s":"234.1","lc":1724195650.7270436,"c":"01J5S13F574Y80MKXTG72R9B15"}}}},"id":3}

This first connection can be made to disconnect after five minutes by using Settings → (User) → Mobile app settings → Automatically close connection. If this slider is slid to the off position, then, after five minutes, the Companion app closes the Web socket (it sends a close frame) and the connection that is the data hog ceases to be a data hog because it is now dead.

The second connection is not a data hog and it remains open. This is the connection that will receive the message from the server when the notify service (action) is used to send a message.

Unfortunately, the slider is really not a solution to the problem, because five minutes of keeping the device running and eating data while keeping the radio on is simply not acceptable. Ten megabytes of data were sent in the interim, and the battery dropped a couple percentage points, and remember — this is happening on a device that is supposed to be locked in my pocket.

What should really happen as a fix is that the app must send an unsubscribe_entities message as soon as it is backgrounded, with another subscribe_entities message when the app is foregrounded again. The app is then free to keep the connection open, or heck, even react to the other subscriptions (which are for events that are very rare compared to entity updates). Features like tiles and widgets could theoretically subscribe only to the events of the entities they themselves display. Ultimately, if the device is locked or HA Companion is backgrounded, then the device should not be awake, or at the very least it should not be processing every entity update that the server sends. I understand that the messages sent through the second connection must be processed in a timely manner, but the first connection is just wasting data and battery in a ludicrous manner — it is doing exactly what every app developer is told not to do: keep the radio on and the device awake by sending tiny little messages multiple times a second.

Additionally, it would perhaps be prudent for HA Companion only to send subscription requests for the entities currently visible in the dashboard (or the dashboard tab) being displayed. It seems highly pointless to waste CPU-used and radio-used battery processing events for my UPS when my UPS is only ever shown in the Office tab of my dashboard.

Remember that my findings here are for the minimal version of the app. I have also turned on the setting (under server settings) to keep a connection open permanently, because there is no support for Google Play (Firebase Cloud Messaging notifications) on my device. I am pretty sure this is what is causing the second connection (which I am fine with, because it is not a data hog) to be opened in the first place and then kept open.

Unfortunately camera streams is a known issue

The correct workaround is to remove live view cameras from the main dashboard and to make sure you don’t exit the app with one in view

No, camera stream is not the issue here. My device is not downloading camera images or videos. That’s a whole different problem, and that’s megabytes a second when I accidentally leave the camera open before switching to another app. But I want to stress none of this that I’ve discovered has anything to do with the camera issue. All the battery and data consumption stems from every single entity update in my HA setup being streamed to the app, even when it is in the background.

your talking about entities ON your dashboard correct? its important to separate dashboard from app features here to understand the issue.

teh app uses websockets and so does the frontend, teh app does not control teh frontend connections

No, I’m not talking about entities on my dashboard. I initially thought that too, but no.

I already tried the experiment with a 100% empty and fresh dashboard.

I still get events. From every entity. As they update.

ok if you believe you found an issue please file a bug on github and provide the companion app logs while reproducing the issue along with steps to reproduce. The websocket logs you have are incomplete and we need actual app logs to understand if its the app or not.

our websocket subscription created by the app itself only subscribe in certain circumstances.

widgets: subscription with screen on/off
tiles: subscription when notification panel is open
device controls: subscription when open
persistent connection: depends on settings

all of the above will be visible with app logs

dashboard or anything else from the frontend are not app controlled, its very likely the frontend may always request a subscription to all entities but the app does not control that.

do you see a similar issue when using chrome for android?

I will collect a log later, but so far in my phone:

  • No tiles. I deleted them.
  • All device controls disabled. I disabled them.
  • No widgets. I never added one.
  • Persistent connection: always. Not sure why that setting must require full subscription to every entity…

I see no evidence of this in the logs.

Please stand by for log upload.

Pastebin log: --------- beginning of main08-21 13:13:54.068 16734 16734 I android.minimal: U - Pastebin.com

Noteworthy from the log:

  • No evidence that HA Companion has dropped the connection, though it is clear the connection has dropped because the updates stop coming.
  • No evidence that the app is logging anythibg regarding device controls, tiles or widgets (of which I have none enabled anyway).

BTW thanks for fixing that traceback of update notification count in PR #4525!

1 Like

It does not this is a incorrect assessment.

the only subscription here is the one for grabbing the notifications, nothing being registered by the app itself for entities so your most likely seeing something from the HA frontend

08-21 13:13:54.665 16734 16864 D WebSocketRepository: Sending message 2: {type=mobile_app/push_notification_channel, webhook_id=ID, support_confirm=true, id=2}

no but we do see the persistent connection ping messages to keep that connection alive at the end of the logs

08-21 13:20:24.633 16734 16812 D WebSocketRepository: Sending message 16: {type=ping, id=16}
08-21 13:20:24.633 16734 16812 D WebSocketRepository: Message number 16 sent
08-21 13:20:24.700 16734 16814 D WebSocketRepository: Websocket: onMessage (text)
08-21 13:20:24.700 16734 16814 D WebSocketRepository: Message number 16 received

Please note this is clearly visible in the pcap of one of the persistent connections:

{"type":"subscribe_entities","id":3}

So regardless of what the catlog says, it’s fairly clear the app demands that data, and does not actually stop demanding that data.

I just resetup the app with the latest prerelease 2024.8.3 version (minimal), replicating the settings I had before — took me quite a lot of time. I’m still experiencing the same issue. Exiting the app (without force close) or switching to another app does not stop the traffic from the server.

I think I’m really, really close to root-causing the issue. It appears to be related to mini graph card in a sub-tab of a dashboard.

Hang on please let me collect more info.

No, it isn’t related to mini graph card. The entities that are streaming events to my app are not on the dashboard that the app loads up. In fact, many of the entities whose events are streaming are simply not in any of my dashboards at all.

It’s not from the app. The WebView may use the same connection but the app is not creating it. It’s the frontend. Please don’t go by pcap as yes it will show the same device. Notice how the logs you posted don’t contain it. That’s because the Android app itself is not creating that subscription.

Look, I’m not sure whether it’s the WebView or the app creating the subscription. To me, the app is largely the WebView that the app embeds, but yeah I understand the distinction.

I will try running the app separately in the phone as a Web page, and see what happens when I exit the browser and switch to another app.

Please stand by for more info.