Source for BYD Atto 3 battery state of charge

I can now read the values. How can I send the data to my MQTT broker?

With these commands i can read out on my Samsung Galaxy S10 the SoC, the range, the charging status, the odometer and the charging power. But I don’t now how to pass the values to the MQTT broker.

adb shell am force-stop com.byd.bydautolink
adb shell monkey -p com.byd.bydautolink -c android.intent.category.LAUNCHER 1
sleep 20
adb exec-out screencap -p > screenshot.png # Take a screenshot
convert screenshot.png -crop 90x50+280+260 cropped_soc.png # Crop the screenshot
tesseract cropped_soc.png stdout -c tessedit_char_whitelist=0123456789 --psm 6
sleep 5
convert screenshot.png -crop 90x50+60+260 cropped_range.png # Crop the screenshot
tesseract cropped_range.png stdout -c tessedit_char_whitelist=0123456789 --psm 6
sleep 5
convert screenshot.png -crop 160x50+570+1120 cropped_charging.png # Crop the screenshot
tesseract cropped_charging.png stdout -c tessedit_char_whitelist=0123456789 --psm 6
sleep 5
convert screenshot.png -crop 100x50+730+1120 cropped_power.png # Crop the screenshot
tesseract cropped_power.png stdout -c tessedit_char_whitelist=0123456789. --psm 6

perfect!
to pass things to mqtt please google the information. I guess there are many tutorials to pass data from bash to mqtt
ChatGPT is also very helpful!

This is my finished script.

#!/bin/bash

BROKER="192.168.1.40"
TOPIC_BASE="auto/byd"
USER="username"
PASSWORD="password"

while true; do
    adb shell am force-stop com.byd.bydautolink
    adb shell monkey -p com.byd.bydautolink -c android.intent.category.LAUNCHER 1
    sleep 20

    adb exec-out screencap -p > screenshot.png

    # SOC
    convert screenshot.png -crop 90x50+280+260 cropped_soc.png
    SOC=$(tesseract cropped_soc.png stdout -c tessedit_char_whitelist=0123456789 --psm 6 | tr -dc '0-9')
    mosquitto_pub -h "$BROKER" -u "$USER" -P "$PASSWORD" -t "$TOPIC_BASE/soc" -m "$SOC"

    # Reichweite
    convert screenshot.png -crop 90x50+60+260 cropped_range.png
    RANGE=$(tesseract cropped_range.png stdout -c tessedit_char_whitelist=0123456789 --psm 6 | tr -dc '0-9')
    mosquitto_pub -h "$BROKER" -u "$USER" -P "$PASSWORD" -t "$TOPIC_BASE/range" -m "$RANGE"

    # Restliche Ladezeit (hh:mm)
    convert screenshot.png -crop 160x50+570+1120 cropped_remaining_time.png
    RAW_TIME=$(tesseract cropped_remaining_time.png stdout -c tessedit_char_whitelist=0123456789hm --psm 6)
    CLEAN_TIME=$(echo "$RAW_TIME" | tr -d '[:space:]')

    if [[ "$CLEAN_TIME" =~ ^([0-9]+)h([0-9]+)min$ ]]; then
        HOURS="${BASH_REMATCH[1]}"
        MINS="${BASH_REMATCH[2]}"
        REMAINING_TIME=$(printf "%02d:%02d" "$HOURS" "$MINS")
    elif [[ "$CLEAN_TIME" =~ ^([0-9]+)h$ ]]; then
        HOURS="${BASH_REMATCH[1]}"
        REMAINING_TIME=$(printf "%02d:00" "$HOURS")
    elif [[ "$CLEAN_TIME" =~ ^([0-9]+)min$ ]]; then
        MINS="${BASH_REMATCH[1]}"
        REMAINING_TIME=$(printf "00:%02d" "$MINS")
    else
        REMAINING_TIME="$RAW_TIME"
    fi

    mosquitto_pub -h "$BROKER" -u "$USER" -P "$PASSWORD" -t "$TOPIC_BASE/remaining_time" -m "$REMAINING_TIME"

    # Ladeleistung (kW)
    convert screenshot.png -crop 100x50+730+1120 cropped_power.png
    POWER=$(tesseract cropped_power.png stdout -c tessedit_char_whitelist=0123456789. --psm 6 | tr -dc '0-9.')
    mosquitto_pub -h "$BROKER" -u "$USER" -P "$PASSWORD" -t "$TOPIC_BASE/power" -m "$POWER"

    # Odometer
    adb shell input tap 900 2100
    sleep 20
    adb exec-out screencap -p > screenshot_me.png
    convert screenshot_me.png -crop 175x50+100+880 cropped_odo.png
    ODO=$(tesseract cropped_odo.png stdout -c tessedit_char_whitelist=0123456789 --psm 6 | tr -dc '0-9')
    mosquitto_pub -h "$BROKER" -u "$USER" -P "$PASSWORD" -t "$TOPIC_BASE/odo" -m "$ODO"

    # Warten bis zur nächsten Minute
    sleep 60
done
1 Like

Looks good. I’ll not test it, because my solution using PHP and Loxone works well for me.

found another solution:

adb shell uiautomator dump /sdcard/view.xml >/dev/null 2>&1
adb exec-out cat /sdcard/view.xml

you get a full xml dump of the screen and can parse it to get the needed values.

How do you know you can trust evlinkha? I mean, I’m not saying it is harmful or that its owner has is a bad actor, but giving access to all my EV data to a third party that will keep that data on the cloud is not easy to accept for me. If evlinkha is compromised, bad actors can have access to my EV usage data and location.

Actually, the same applies to enode. I’m not comfortable sharing my data with a third-party. But at least enode is well-established and has good reputation.

Ideally we should be able to link our EV to HA directly.

How do you know you can trust anyone? I mean I tried it, but I’m not going to use it long term. I’m just going to keep using my bluetooth reading of the OBDLink CX

Let’s for a moment consider the fact that we’re driving Chinese cars that already capture a lot of telemetry about us, let alone are already listening for every word we say, just in case we say “hi BYD”. :slight_smile:

1 Like

that is true. still, the less vulnerable points the better.

1 Like

I don’t disagree with your point though - I would much rather be able to connect direct to BYD as that’s where the data is, instead of via someone’s service which is utilising another service.

Look at this code. It works perfect for me. The BYD app runs on a badly scratched Samsung Galaxy S10. At least now I have a use for it.

import json
import subprocess
import time
import xml.etree.ElementTree as ET
import paho.mqtt.client as mqtt

# ===== MQTT Einstellungen =====
MQTT_BROKER = "192.168.1.40"
MQTT_PORT = 1883
MQTT_USER = "user"
MQTT_PASS = "password"
MQTT_TOPIC_BASE = "byd/app"

# ===== XML Mapping =====
XML_MAP = {
    "com.byd.bydautolink:id/h_km_tv": "range_km",
    "com.byd.bydautolink:id/tv_batter_percentage": "battery_soc",
    "com.byd.bydautolink:id/tv_charging_tip": "charging_tip",
    "com.byd.bydautolink:id/tv_banner": "warning",
    "com.byd.bydautolink:id/car_inner_temperature": "cabin_temp",
    "com.byd.bydautolink:id/tem_tv": "ac_set_temp",
    "com.byd.bydautolink:id/h_car_name_tv": "car_status",
    "com.byd.bydautolink:id/tv_update_time": "last_update",
    "com.byd.bydautolink:id/tire_tv_01": "tire_pressure_fl",
    "com.byd.bydautolink:id/tire_tv_02": "tire_pressure_rl",
    "com.byd.bydautolink:id/tire_tv_03": "tire_pressure_fr",
    "com.byd.bydautolink:id/tire_tv_04": "tire_pressure_rr",
    "com.byd.bydautolink:id/iv_value_front_hood": "front_hood",
    "com.byd.bydautolink:id/iv_value_door": "door",
    "com.byd.bydautolink:id/iv_value_window": "window",
    "com.byd.bydautolink:id/iv_value_sunroof": "sunroof",
    "com.byd.bydautolink:id/iv_value_trunk": "trunk",
    "com.byd.bydautolink:id/tv_value_mileage": "odometer",
    "com.byd.bydautolink:id/tv_recent_energy_value": "recent_energy_value",
    "com.byd.bydautolink:id/tv_total_energy_value": "total_energy_value",
    "com.byd.bydautolink:id/tv_tv_car_state_value": "car_state_value",
    "com.byd.bydautolink:id/tv_car_travel_state_title": "car_travel_state_title",
}

# ===== ADB Aktionen =====
ACTIONS = {
    "refresh": ("input tap 546 370", 2),
    "car_status": ("input tap 284 1131", 1),
    "car_position": ("input tap 854 1822", 1),
    "navigation": ("input tap 726 1983", 1),
    "map_back": ("input tap 92 242", 1),
    "finish": ("input tap 84 170", 1),
    "swipe_up": ("input swipe 500 1500 500 500 500", 2),
    "swipe_down": ("input swipe 500 500 500 1500 500", 2),
    "stop_google_maps": ("am force-stop com.google.android.apps.maps", 2),
}


# ===== MQTT Setup =====
def connect_mqtt():
    client = mqtt.Client()
    client.username_pw_set(MQTT_USER, MQTT_PASS)
    client.on_disconnect = lambda c, u, rc: print("⚠️ MQTT getrennt, versuche Reconnect...")
    client.connect(MQTT_BROKER, MQTT_PORT, 60)
    client.loop_start()
    return client


client = connect_mqtt()


# ===== ADB Hilfsfunktionen =====
def adb_command(cmd: str, wait: float = 1.0):
    subprocess.run(["adb", "shell"] + cmd.split(), check=False)
    if wait:
        time.sleep(wait)


def perform(action: str):
    if action not in ACTIONS:
        print(f"❌ Unbekannte Aktion: {action}")
        return
    cmd, wait = ACTIONS[action]
    adb_command(cmd, wait)


def get_ui_dump(filename="window_dump.xml"):
    remote_file = f"/sdcard/{filename}"
    adb_command(f"uiautomator dump {remote_file}", 1)
    subprocess.run(["adb", "pull", remote_file, filename],
                   check=False,
                   stdout=subprocess.DEVNULL,
                   stderr=subprocess.DEVNULL)
    return filename


# ===== XML Parsing =====
def extract_values(xml_file: str):
    tree = ET.parse(xml_file)
    root = tree.getroot()
    values = {}

    for node in root.iter("node"):
        rid = node.attrib.get("resource-id", "")
        text = node.attrib.get("text", "")
        bounds = node.attrib.get("bounds", "")
        cls = node.attrib.get("class", "")

        if not text:
            continue

        if rid in XML_MAP:
            values[XML_MAP[rid]] = text
        elif cls == "android.widget.TextView" and "[83,153]" in bounds:
            values["car_position"] = text
    return values


# ===== MQTT Publish Funktionen =====
def publish_car_position(raw_coords):
    try:
        lat, lon = map(str.strip, raw_coords.split(","))
        payload = {
            "latitude": float(lat),
            "longitude": float(lon),
            "gps_accuracy": 10,
            "status": "driving"
        }
        client.publish(f"{MQTT_TOPIC_BASE}/location", json.dumps(payload))
        print(f"MQTT publish -> {payload}")
    except Exception as e:
        print(f"Fehler beim Verarbeiten der Koordinaten: {e}")


def publish_values(values: dict):
    if not values:
        print("⚠️ Keine Werte gefunden.")
        return

    # Einzelne Sensoren
    for key, val in values.items():
        topic = f"{MQTT_TOPIC_BASE}/{key}"
        client.publish(topic, val)
        print(f"➡️ Publish {topic} -> {val}")


# ===== Hauptschleife =====
def main_loop():
    while True:
        all_values = {}

        # Reihenfolge der Dumps/Aktionen
        dumps_sequence = [
            ("window_dump_1.xml", []),
            ("window_dump_2.xml", ["swipe_up", "car_status"]),
            ("window_dump_3.xml", ["swipe_up"]),
            ("window_dump_4.xml", ["finish", "swipe_down", "car_position"]),
            ("window_dump_5.xml", ["navigation"]),
        ]

        for filename, actions in dumps_sequence:
            for action in actions:
                perform(action)
            get_ui_dump(filename)
            all_values.update(extract_values(filename))

        # Position separat publishen
        if "car_position" in all_values:
            publish_car_position(all_values["car_position"])

        # Alle Sensorwerte publishen
        publish_values(all_values)

        # Google Maps stoppen & zurück
        perform("stop_google_maps")
        perform("map_back")

        time.sleep(60)


if __name__ == "__main__":
    try:
        main_loop()
    except KeyboardInterrupt:
        print("🚪 Script beendet.")
        client.loop_stop()
        client.disconnect()
1 Like

Looks like these Nissan Leaf owners are pursuing something along the lines of connecting to ESPHome to BLE dongle. Nissan Leaf via LeLink 2 (ELM327) BLE - ESPHome

@dgaust has already done this for the Seal, and I have extended it to an Atto 3 and iCar Pro 2S OBD2 dongle. It works very well.

Hi, I’m sure the best route would be to have an app installed in the card that would send the car’s sensor data to Home Assistant.

This app exposes a lot of features of the byd car to a mobile app:
https://electro.app.br/

It’s still in beta, but I gives an idea of what is possible. Just wish there was something simple (an open source) that would allow just to send data to hassio.

2 Likes

Installed the app. I think it uses “Debug mode when USB is connected”/“Wireless adb” to get info from the car.
https://electro.app.br/usb
The app is also running when the car is off, so you can get notification if a door is open.

This looks interesting, will try it out. Is there anyway to connect this to home assistant?

Not yet, unfortunately.

Oh wow! Been looking for a way to bring my BYD into HA for 2 years. Can’t believe how easy it was in the end. Thanks.

Hi, using the Chinese MIK app, I managed to transfer various data via MQTT to HA.
The MIK app allows you to configure an MQTT broker, in my case the Mosquitto addon. After viewing the messages it sends with MQTT Explorer, I decrypted the messages and created several sensors.