Integrate OBD2 data using Torque to both Home Assistant and ABRP

I wanted to use an ELM327 v1.5 adapter to integrate OBD2 data into both Home Assistant and ABRP using the Torque app. Initially I created an automation to relay the OBD2 data to ABRP via rest_command. The approach works but creates unnecessary traffic in HA. Since I use nginx as a reverse proxy for HA, I can just use ngx_http_mirror_module to mirror the GET request from Torque to both end points. This is a write-up to record the two setups in case they are useful for similar tasks.

Common

Both setups require setting up the HA Torque integration. For the Data Logging & Upload settings in the Torque app, “Webserver URL” should be the HA torque api endpoint (must be https), “Send https: Bearer Token” should be checked, and “Set Bearer Token” should be set to the HA Long-Lived Access Token. “User Email address” varies depending on the setup (see below).

HTTP mirror requests

In this setup, I use the Torque option to integrate in-car live data to ABRP. Use the token provided by ABRP as the email address for the HA Torque integration. The official integration works perfectly with an arbitrary string as the email address, but the JOHLC/ha-torque-2.0 custom integration does not, as it gives an invalid_email error.

I use the nginx-proxy Docker image in a docker-compose.yml file:

  nginx-proxy:
    image: jwilder/nginx-proxy:alpine
    ports:
      - 80:80
      - 443:443
    restart: unless-stopped
    volumes:
      - ./data/certs:/etc/nginx/certs:ro
      - ./data/nginx/conf.d:/etc/nginx/conf.d
      - ./data/nginx/vhost.d:/etc/nginx/vhost.d
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - ./data/nginx/html:/usr/share/nginx/html

./data/nginx/conf.d/ha.conf:

log_format vhost2 '$host $remote_addr - $remote_user [$time_local] '
                  '"$request" $status $body_bytes_sent '
                  '"$http_referer" "$http_user_agent"';

proxy_pass_header X-XSRF-TOKEN;

upstream hass.example.org {
  server 111.222.333.444:8123;
}
server {
        server_name hass.example.org;
        listen 80 ;
        access_log /var/log/nginx/access.log vhost2;
        # Do not HTTPS redirect Let'sEncrypt ACME challenge
        location ^~ /.well-known/acme-challenge/ {
                auth_basic off;
                auth_request off;
                allow all;
                root /usr/share/nginx/html;
                try_files $uri =404;
                break;
        }
        location / {
                return 301 https://$host$request_uri;
        }
}
server {
        server_name hass.example.org;
        listen 443 ssl http2 ;
        access_log /var/log/nginx/access.log vhost2;
        ssl_session_timeout 5m;
        ssl_session_cache shared:SSL:50m;
        ssl_session_tickets off;
        ssl_certificate /etc/nginx/certs/example.org.crt;
        ssl_certificate_key /etc/nginx/certs/example.org.key;
        add_header Strict-Transport-Security "max-age=31536000" always;
        location / {
                proxy_pass http://hass.example.org;
        }
        location /api/torque {
                mirror /api_torque_mirror;
                proxy_pass http://hass.example.org;
        }
        location /api_torque_mirror {
                internal;
                resolver 1.1.1.1 ipv6=off;
                proxy_ssl_server_name on;
                proxy_ssl_name api.iternio.com;
                proxy_set_header Host api.iternio.com;
                proxy_pass https://api.iternio.com/1/tlm/torque$is_args$args;
                proxy_pass_request_body on;
                proxy_set_header Authorization "";
        }
}

I use acme.sh to automate SSL/TLS certificates and Tailscale to proxy from a VPS with a public IP address to my Raspberry Pi (on T-Mobile 5G Home Internet; 111.222.333.444 above is the Tailscale IP for the Pi). To test this setup:

  • Verify that HA can receive the OBD2 data. Note that the Torque entities are dynamic and will disappear when HA is rebooted, but they will be recreated once new data come in.
  • Change “Webserver URL” to https://api.iternio.com/1/tlm/torque in the Torque app, verify that ABRP (and only ABRP) can receive the data.
  • Change “Webserver URL” back to HA torque endpoint in the Torque app and change all occurrences of api.iternio.com in ./data/nginx/conf.d/ha.conf to a capturing URL, e.g., from requestlens.com. Verify that the HTTP GET requests from Torque are correctly mirrored.

Home Assistant REST Relay

In this setup, you do not need to use the ABRP Torque token as the email address in the Torque app or the HA Torque integration. You also do not need the nginx mirror setup. Instead, just first make sure that the Torque data can be received by HA. Then create an automation to relay this data to ABRP using its Generic option for integrating in-car live data. We need to set up the rest_command in configuration.yaml:

rest_command:
  update_abrp:
    method: POST
    content_type: "application/x-www-form-urlencoded"
    url: >-
      https://api.iternio.com/1/tlm/send?api_key=32b2162f-9599-4647-8139-66e9f9528370&token=[ABRP Generic TOKEN]&tlm={{
        {
          "utc": as_timestamp(now()) | int,
          "soc": states('sensor.vehicle_battery_pack_state_of_charge_displayed') | float(0),
          "power": states('sensor.vehicle_inst_kpower') | float(0),
          "speed": states('sensor.vehicle_speed_kmh') | float(0),
          "lat": states('sensor.vehicle_gps_latitude') | float(0),
          "lon": states('sensor.vehicle_gps_longitude') | float(0),
          "is_charging": 1 if states('sensor.vehicle_speed_kmh') | float(0) < 1 and states('sensor.vehicle_inst_kpower') | float(0) < 0 else 0,
          "is_dcfc": 1 if states('sensor.vehicle_speed_kmh') | float(0) < 1 and states('sensor.vehicle_inst_kpower') | float(0) < 0 and states('sensor.vehicle_charger_dc_port_current') | float(0) > 10 else 0,
          "capacity": states('sensor.vehicle_battery_pack_capacity_kwh_estimated_2017_2018') | float(66.0),
          "elevation": states('sensor.vehicle_gps_altitude') | float(0) * 0.3048,
          "ext_temp": (states('sensor.vehicle_air_temp_ambient') | float(0) - 32) * 5/9,
          "batt_temp": (states('sensor.vehicle_battery_pack_temp_avg_temp') | float(0) - 32) * 5/9,
          "voltage": states('sensor.vehicle_hv_mg_voltage') | float(0),
          "current": states('sensor.vehicle_hv_current_hd') | float(0),
          "car_model": "chevy:bolt:17:66:other"
        } | to_json | urlencode
      }}

And create an automation in HA:

alias: Forward Torque Data to ABRP
description: ""
triggers:
  - seconds: /5
    trigger: time_pattern
conditions:
  - condition: template
    value_template: "{{ states('sensor.vehicle_inst_kpower') | float(0) != 0 }}"
  - condition: template
    value_template: >-
      {{ (now() -
      states.sensor.vehicle_inst_kpower.last_updated).total_seconds() < 60 }}
actions:
  - action: rest_command.update_abrp

Note that we are using a different ABRP endpoint, https://api.iternio.com/1/tlm/send, a public api_key, and the unique ABRP user token. For some reason, I can only get it to work by putting all the OBD2 data in the query arguments.