Monitor Pepwave MAX BR1 MK2 router via local API

Hey All. I haven’t shared piece of my setup in a while. I wanted to contribute something I couldn’t find anywhere else.

In the spring I will be putting Home Assistant in my Airstream RV. I wanted to monitor my Pepwave MAX BR1 MK2 cellular router for connectivity, data usage and to be able to reset the cell connection. Here is what I came up with:

Screenshot from 2022-02-18 21-56-20

I am pulling this info using the Pepwave local device API, which I found documentation for on a fluke googling other things about the router: https://download.peplink.com/resources/Peplink-Router-API-Documentation-for-Firmware-8.1.3.pdf

To use my configuration you will first need to generate a ClientId and ClientSecret using your regular admin login. Replace XXXXX with your admin password and generate a cookie using this command:

curl -k -c cookies.txt -H "Content-Type: application/json" -X POST -d '{"username":"admin","password":"XXXXXXXXXXXXXXXXXXXXXX"}' https://192.168.50.1/api/login

Then generate the ClientId and ClientSecret using this command:

curl -k -b cookies.txt -H "Content-Type: application/json" -X POST -d '{"action":"add","name":"ha","scope":"api"}' https://192.168.50.1/api/auth.client

Now you you can use the following configuration, replacing XXXXX with your generated clientId and clientSecret. The first rest resource below generates a auth token on HA start and every 2 days when the token expires. This auth token is what allows the other rest calls to work.

rest:
  - resource: "https://192.168.50.1/api/auth.token.grant"
    scan_interval: 172700
    verify_ssl: false
    method: POST
    headers:
      Content-Type: application/json
    payload: '{"clientId":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","clientSecret":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","scope":"api"}'
    sensor:
      - name: pep_token
        value_template: "{{ value_json.response.accessToken }}"

  - resource_template: "https://192.168.50.1/api/status.wan.connection?accessToken={{ states('sensor.pep_token') }}&id=2"
    scan_interval: 5
    verify_ssl: false
    sensor:
      - name: pep_message
        value_template: "{{ value_json.response['2'].message }}"
      - name: pep_mobiletype
        value_template: "{{ value_json.response['2'].cellular.mobileType }}"
      - name: pep_signal
        value_template: "{{ value_json.response['2'].cellular.signalLevel }}"
      - name: pep_uptime
        value_template: "{{ value_json.response['2'].uptime }}"

  - resource_template: "https://192.168.50.1/api/status.wan.connection.allowance?accessToken={{ states('sensor.pep_token') }}&connId=2"
    scan_interval: 60
    verify_ssl: false
    sensor:
      - name: pep_usage
        value_template: "{{ value_json.response['2']['1'].usage }}"

template:
  - sensor:
      - name: pepwave_message
        unique_id: pepwave_message
        state: "{{ states('sensor.pep_message') }}"

      - name: pepwave_mobiletype
        unique_id: pepwave_mobiletype
        state: "{{ states('sensor.pep_mobiletype') }}"

      - name: pepwave_signal
        unique_id: pepwave_signal
        state: >-
          {{ states('sensor.pep_signal') }} / 5

      - name: pepwave_uptime
        unique_id: pepwave_uptime
        state: >-
          {% set time = (states.sensor.pep_uptime.state | int) | int %}
          {% set minutes = ((time % 3600) / 60) | int %}
          {% set hours = ((time % 86400) / 3600) | int %}
          {% set days = (time / 86400) | int %}
          {%- if time < 60 -%}
            Less than a minute
            {%- else -%}
            {%- if days > 0 -%}
              {{ days }}d
            {%- endif -%}
            {%- if hours > 0 -%}
              {%- if days > 0 -%}
                {{ ' ' }}
              {%- endif -%}
              {{ hours }}h
            {%- endif -%}
            {%- if minutes > 0 -%}
              {%- if days > 0 or hours > 0 -%}
                {{ ' ' }}
              {%- endif -%}
              {{ minutes }}m
            {%- endif -%}
          {%- endif -%}

      - name: pepwave_usage
        unique_id: pepwave_usage
        unit_of_measurement: GB
        state: >-
          {{ ( states('sensor.pep_usage') | float / 1048576 ) | round(2) }}

rest_command:
  pep_cell_reset:
    url: "https://192.168.50.1/api/cmd.cellularModule.reset?accessToken={{ states('sensor.pep_token') }}"
    verify_ssl: false
    method: POST
    headers:
      Content-Type: application/json
    payload: '{"connId":"2"}'

script:
  pep_cell_reset:
    alias: Cell Reset
    icon: mdi:refresh
    sequence:
      - service: rest_command.pep_cell_reset

If you have issues troubleshooting is pretty straight forward executing the curl commands in a terminal, using the auth token or cookie authentication. Explore the rest of the API documentation for more information! jsonpathfinder.com is also very helpful for isolating the json value_template.

There is also documentation on accessing information from the router via SSH, SNMP and SMS (I could not get SMS to work), but I found the information from the rest calls sufficient for my needs: https://www.peplink.com/support/downloads/miscellaneous-downloads/

- Rob

7 Likes

Just wanted to say thank you for posting this! Have had a pepwave in my RV for the last year and with Victron recently adding support for Ruuvi temp sensors I’m going to build out more components including hass on an odroid n2+.

Have you integrated the GPS data from your pepwave into device location? Seems like you might get the info from the json payload?

ref: How to dynamically set location in Home Assistant for boats and RVs (boathackers.com)

I have integrated GPS in two ways. Using the gps forwarder function in the Pepwave settings, that’s pretty straight forward. Also I recently built this addition:

rest:
  - resource_template: "https://192.168.50.1/api/info.location?accessToken={{ states('sensor.pep_token') }}&connId=2"
    scan_interval: 10
    verify_ssl: false
    sensor:
      - name: pep_gps
        value_template: "{{ value_json.response.gps }}"
        json_attributes_path: "$.response.location"
        json_attributes:
          - "latitude"
          - "longitude"
          - "altitude"
          - "heading"
          - "timestamp"
      - name: pep_speed
        value_template: "{{ ( value_json.response.location.speed ) | round(0) }}"

and then added this automation to update a device tracker and set the home assistant location, every time the pepwave moves:

automation:
  - alias: update_pep_gps
    trigger:
    - platform: state
      entity_id: sensor.pep_gps
    action:
    - service: device_tracker.see
      data:
        dev_id: airstream
        gps:
          - "{{ state_attr('sensor.pep_gps', 'latitude') }}"
          - "{{ state_attr('sensor.pep_gps', 'longitude') }}"
    - service: homeassistant.set_location
      data_template:
        latitude: "{{ state_attr('sensor.pep_gps', 'latitude') }}"
        longitude: "{{ state_attr('sensor.pep_gps', 'longitude') }}"
1 Like

Appreciate the follow up! Is there a reason why you would use the GPS forwarder given that you are already using the API scrape for other purposes?

I use the api for getting location inside of the pepwave network and use the forwarder to sending the location to a tracking server through the internet.

1 Like
rest:
  - resource_template: "https://192.168.50.1/api/info.location?accessToken={{ states('sensor.pep_token') }}&connId=2"
    scan_interval: 10
    verify_ssl: false
    sensor:
      - name: pep_gps
        value_template: "{{ value_json.response.gps }}"
        json_attributes_path: "$.response.location"
        json_attributes:
          - "latitude"
          - "longitude"
          - "altitude"
          - "heading"
          - "timestamp"
      - name: pep_speed
        value_template: "{{ ( value_json.response.location.speed ) | round(0) }}"

Are you placing this in your configuration.yaml? Also could you give a rough idea on how you configured you GPS forwarding?

Is the config example that you provided supposed to go in the main configuration.yaml file? That is where I placed it and I get errors such as

Logger: homeassistant.helpers.event
Source: helpers/template.py:423
First occurred: 11:13:45 (2 occurrences)
Last logged: 11:13:45

Error while processing template: Template("{% set time = (states.sensor.pep_uptime.state | int) | int %} {% set minutes = ((time % 3600) / 60) | int %} {% set hours = ((time % 86400) / 3600) | int %} {% set days = (time / 86400) | int %} {%- if time < 60 -%} Less than a minute {%- else -%} {%- if days > 0 -%} {{ days }}d {%- endif -%} {%- if hours > 0 -%} {%- if days > 0 -%} {{ ' ' }} {%- endif -%} {{ hours }}h {%- endif -%} {%- if minutes > 0 -%} {%- if days > 0 or hours > 0 -%} {{ ' ' }} {%- endif -%} {{ minutes }}m {%- endif -%} {%- endif -%}")
Error while processing template: Template("{{ ( states('sensor.pep_usage') | float / 1048576 ) | round(2) }}")
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 421, in async_render
    render_result = _render_with_context(self.template, compiled, **kwargs)
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 1906, in _render_with_context
    return template.render(**kwargs)
  File "/usr/local/lib/python3.10/site-packages/jinja2/environment.py", line 1301, in render
    self.environment.handle_exception()
  File "/usr/local/lib/python3.10/site-packages/jinja2/environment.py", line 936, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "<template>", line 1, in top-level template code
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 1698, in forgiving_int_filter
    raise_no_default("int", value)
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 1395, in raise_no_default
    raise ValueError(
ValueError: Template error: int got invalid input 'unknown' when rendering template '{% set time = (states.sensor.pep_uptime.state | int) | int %} {% set minutes = ((time % 3600) / 60) | int %} {% set hours = ((time % 86400) / 3600) | int %} {% set days = (time / 86400) | int %} {%- if time < 60 -%}
  Less than a minute
  {%- else -%}
  {%- if days > 0 -%}
    {{ days }}d
  {%- endif -%}
  {%- if hours > 0 -%}
    {%- if days > 0 -%}
      {{ ' ' }}
    {%- endif -%}
    {{ hours }}h
  {%- endif -%}
  {%- if minutes > 0 -%}
    {%- if days > 0 or hours > 0 -%}
      {{ ' ' }}
    {%- endif -%}
    {{ minutes }}m
  {%- endif -%}
{%- endif -%}' but no default was specified

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 537, in async_render_to_info
    render_info._result = self.async_render(variables, strict=strict, **kwargs)
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 423, in async_render
    raise TemplateError(err) from err
homeassistant.exceptions.TemplateError: ValueError: Template error: int got invalid input 'unknown' when rendering template '{% set time = (states.sensor.pep_uptime.state | int) | int %} {% set minutes = ((time % 3600) / 60) | int %} {% set hours = ((time % 86400) / 3600) | int %} {% set days = (time / 86400) | int %} {%- if time < 60 -%}
  Less than a minute
  {%- else -%}
  {%- if days > 0 -%}
    {{ days }}d
  {%- endif -%}
  {%- if hours > 0 -%}
    {%- if days > 0 -%}
      {{ ' ' }}
    {%- endif -%}
    {{ hours }}h
  {%- endif -%}
  {%- if minutes > 0 -%}
    {%- if days > 0 or hours > 0 -%}
      {{ ' ' }}
    {%- endif -%}
    {{ minutes }}m
  {%- endif -%}
{%- endif -%}' but no default was specified

@robwolff3 hello. im trying to use this api to send and reciece sms using cellular on my pepwave transit duo. i cant seem to login using this (ofcourse with correct password and ip):

curl -k -c cookies.txt -H “Content-Type: application/json” -X POST -d ‘{“username”:“admin”,“password”:“XXXXXXXXXXXXXXXXXXXXXX”}’ https://192.168.50.1/api/login

it returns the following:
{
“stat”: “fail”,
“code”: 301,
“message”: “Unauthorized”
}

am i just being stupid here or did you also encounter some issues?

Best Regards
John.

Hello,
I am trying to set this up today. The rest sensor for gps right now shows “false”, is that because I haven’t setup the automation yet?

I’m the automation, what is “dev_id: airstream” is that the hostname of the router or your HA instance?
Thank you!

Same issue here. My gps sensor is showing “false” with no attributes.

Mine started working after a while once I setup the automation.

Still not sure what the dev_id should be. I have it set to my HA name and appears to be working.

just a quick update that mine started working without setting up the automation. it seemed to kick in after a recent trip, so maybe my Pepwave just needed to see a change in location to start reporting.

I am using this node red flow for dynamic weather and gps location of my rv.

@robwolff3 Thanks for the code snippet for pulling the GPS info from the Pepwave router. Given the issues I was having with the HA GPSD integration, I’ve transitioned to this approach. One problem is that the access token expires after 48 hours. How did you get around the token expiration problem? Thx.

Personally I just set up an instance of Traccar and use the GPS forwarding function from the pep wave to update Traccar. Then in HA I have an automation that updates the location when Traccar says it’s moving. Let me know if you need more details