PostNL Integration

Looking at the DHL app, this should be easy. They use a simple POST call to my.dhlparcel.nl/api/user/login with the username and password. The response headers include two cookies (access_token and refresh_token) which are JWT tokens.

https://my.dhlparcel.nl/receiver-parcel-api/parcels returns all parcels.

https://my.dhlparcel.nl/receiver-parcel-api/track-trace?key={TT_CODE}%2B{ZIPCODE}&role=receiver will return the details.

I try to avoid DHL as much as possible, so I am not really able to properly build and test a new version of the integration.

I created a beta release (11.2.0 beta 1). In this release I try to solve an issue which I encountered a couple of times. Sometimes the PostNL API doesn’t return any data (5xx error) or is unreachable. This will break the sensor and therefor the lovelace. I try to solve this by adding a retry strategy. I also changed the behaviour in retrieving data (using async now). This will improve the start time of the integration.

Please test the beta release and let me know if there are (no) issues :smile: .

3 Likes

Does this addon includes the ‘specific time block’ wherein the delivery will take place?

Would be great to get a notification every 20 minutes or so when the timerange updates.

If there is an expected delivery date and time, then this data is added to the delivery data. However, the original lovelace does not show this data. I forked the lovelace and changed the code. In my HA env I can see the data.

You need to do some template magic if you want to create an automation.

1 Like

Is it true you cant find it in HACS anymore?
Couldnt find it in integration and frontend.

First off all great work, but…cant get the lovelace to work.
My resource.yaml:

- url: local/community/lovelace-postnl-card/postnl-card.js
  type: module

Full file path:
…/homeassistant/config/www/community/lovelace-postnl-card/postnl-card.js.

I have got a lot off lovelace resources that working well.
What am I overlooking?

Dit mag best wel pupbliekelijk gedeeld worden hoe ik het voor elkaar heb gekregen:
\custom_components\postnl: GitHub - iMicknl/python-postnl-api: Python wrapper for the PostNL API, a way to track packages using their online portal.
Browser Extention: GitHub - arjenbos/ha-postnl: Custom PostNL Home Assistant integration
Card \www\postnl-card.js: GitHub - peternijssen/lovelace-postnl-card at 0.12.2
Resource path: /local/postnl-card.js as Javacscript Module

1 Like

Please respond in English, this is an English-speaking forum. If it’s easier for you, use google translate or something similar.

1 Like

Can you describe it more detailed please how you did this? Because I got the postnl integration from arjenbos working but the letters entity is not in there. So how did you solve this with the python api wrapper? Please a short step-by-step procedure would be awesome!

Hi,

On Gihub you closed my ticket with the message its should not working in HA anymore but, my entties does get information which seems to be accurate

This is not PostNL related, but since we discussed DHL earlier I thought I should share some findings here.
Long story short, I got my DHL packages which are on the way available within Home Assistant through the multiscrape component.

So far it has been running for almost 24 hours and I haven’t seen any issues yet. As my package meanwhile got delivered, I can’t garantuee that it works flawlessly though.
The only issue that is present is whenever you (re)start home assistant, the sensor might be unavailable, but becomes available after reloading the multiscrape configuration.

This is my multiscrape setup. Replace YOUR EMAIL and YOUR PASSWORD with your credentials.
It might also work with the default rest integration. Haven’t tried that.

multiscrape:
  - resource: "https://my.dhlecommerce.nl/api/user/login"
    scan_interval: 3600
    method: "post"
    headers:
      Content-Type: "application/json"
    payload: '{"email":"YOUR EMAIL","password":"YOUR PASSWORD"}'
  - resource: "https://my.dhlecommerce.nl/receiver-parcel-api/parcels"
    scan_interval: 3600
    method: "get"
    headers:
      Content-Type: "application/json"
    sensor:
      - unique_id: dhl_packages
        name: DHL Pakketten
        value_template: "{{ value_json.parcels | selectattr('category', 'search', '(PROBLEM|CUSTOMS|DATA_RECEIVED|EXCEPTION|INTERVENTION|IN_DELIVERY|LEG|UNDERWAY|UNKNOWN)') | list | count }}"
        attributes:
          - name: parcels
            value_template: "{{ value_json.parcels | selectattr('category', 'search', '(PROBLEM|CUSTOMS|DATA_RECEIVED|EXCEPTION|INTERVENTION|IN_DELIVERY|LEG|UNDERWAY|UNKNOWN)') | list }}"

And this is my lovelace configuration for displaying a table of all packages on the way from either PostNL or DHL:

      - type: conditional
        conditions:
          - condition: or
            conditions:
              - condition: "numeric_state"
                entity: sensor.postnl_delivery
                above: 0
              - condition: "numeric_state"
                entity: sensor.dhl_packages
                above: 0
        card:
          type: markdown
          card_mod:
            style:
              ha-markdown$: |
                table {
                  width: 100%;
                }
                thead {
                  font-weight: bold;
                }
          content: |
              <table>
              <thead>
                <tr>
                  <td>Bezorger</td>
                  <td>Pakket</td>
                  <td>Datum</td>
                  <td>Tijd</td>
                </tr>
              </thead>
              <tbody>
              {% for package in state_attr('sensor.postnl_delivery', 'enroute') %}
                {% if package.planned_date is not none %}
                <tr>
                  <td>PostNL</td>
                  <td>{{ package.name }}</td>
                  <td>{{ as_timestamp(package.planned_date) | timestamp_custom('%-d %b') }}</td>
                  <td>{{ as_timestamp(package.planned_from) | timestamp_custom('%H:%M') }} - {{ as_timestamp(package.planned_to) | timestamp_custom('%H:%M') }}</td>
                </tr>
                {% endif %}
              {%- endfor %}


              {% for package in state_attr('sensor.dhl_packages', 'parcels') %}
                {% if package.receivingTimeIndication is not none %}
                <tr>
                  <td>DHL</td>
                  <td>{{ package.sender.name }}</td>
                  <td>{{ as_timestamp(package.receivingTimeIndication.start) | timestamp_custom('%-d %b') }}</td>
                  <td>{{ as_timestamp(package.receivingTimeIndication.start) | timestamp_custom('%H:%M') }} - {{ as_timestamp(package.receivingTimeIndication.end) | timestamp_custom('%H:%M') }}</td>
                </tr>
                {% endif %}
              {%- endfor %}
              </tbody>
              </table>
8 Likes

Tried it here, but it’s one of those days there are no packages for me. So I have to make the wife order something :smiley:

Be carefull, what you wish for… :stuck_out_tongue:

LoL, it’s not a wish, I know it will happen! :smiley:

1 Like

So you dont make her to order, she will do it anyway :joy:

Of course risking to deviate too much from the PostNL integration (thanks!), I am struggling in getting the DHL code you posted to work.

I get the following errors:

Scraper_noname_1 # Updating failed with exception: Client error '403 Forbidden' for url 'https://my.dhlparcel.nl/receiver-parcel-api/parcels' For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403
Scraper_noname_0 # Updating failed with exception: Client error '401 Unauthorized' for url 'https://my.dhlparcel.nl/api/user/login' For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401

Did DHL change something on their end? Or should I have a business account?

You don’t need a business account. At my place it still works.
Do note that it doesn’t work on restart of home assistant. After an hour it will work or after manually reloading the multiscrape component within the developer tools.

Check the DHL package sensor. It should say zero or more (except on restart, then it says unavailable).

Thanks I got it to work by adding my username/password in the code instead of referring to secrets.yaml.

Since this weekend, I had an error in HA in showing the joint PostNL & DHL card:

TypeError: 'NoneType' object is not iterable

Via ChatGTP I was able to adjust the code to get it working again, it might help you as well

type: conditional
conditions:
  - condition: or
    conditions:
      - condition: numeric_state
        entity: sensor.postnl_delivery
        above: 0
      - condition: numeric_state
        entity: sensor.dhl_packages
        above: 0
card:
  type: markdown
  card_mod:
    style:
      ha-markdown$: |
        table {
          width: 100%;
        }
        thead {
          font-weight: bold;
        }
        ha-icon {           
             --icon-primary-color: #44739e;
        }        
  content: |
    <header style="display: flex; align-items: flex-end;">
      <ha-icon icon="mdi:package-variant" style="vertical-align: bottom;"></ha-icon>
      <font size="5">Pakketjes</font>
    </header>
    <table>
    <thead>
      <tr>
        <td>Bezorger</td>
        <td>Pakket</td>
        <td>Datum</td>
        <td>Tijd</td>
      </tr>
    </thead>
    <tbody>
    {% for package in state_attr('sensor.postnl_delivery', 'enroute') %}
      {% if package.planned_date is not none and package.planned_from is defined and package.planned_to is defined %}
      <tr>
        <td>PostNL</td>
        <td>{{ package.name }}</td>
        <td>{{ as_timestamp(package.planned_date) | timestamp_custom('%-d %b') }}</td>
        <td>{{ as_timestamp(package.planned_from) | timestamp_custom('%H:%M') }} - {{ as_timestamp(package.planned_to) | timestamp_custom('%H:%M') }}</td>
      </tr>
      {% endif %}
    {% endfor %}

    {% for package in state_attr('sensor.dhl_packages', 'parcels') %}
      {% if package.receivingTimeIndication is not none and package.receivingTimeIndication.start is defined and package.receivingTimeIndication.end is defined %}
      <tr>
        <td>DHL</td>
        <td>{{ package.sender.name if package.sender is defined else 'Unknown' }}</td>
        <td>{{ as_timestamp(package.receivingTimeIndication.start) | timestamp_custom('%-d %b') }}</td>
        <td>{{ as_timestamp(package.receivingTimeIndication.start) | timestamp_custom('%H:%M') }} - {{ as_timestamp(package.receivingTimeIndication.end) | timestamp_custom('%H:%M') }}</td>
      </tr>
      {% endif %}
    {% endfor %}


    </tbody>
    </table>

The following steps were suggested to resolve this (by ChatGTP):

  • Check if Attributes are None:
    Ensure that the attributes you are trying to iterate over (‘enroute’ for sensor.postnl_delivery and ‘parcels’ for sensor.dhl_packages) are not None.
  • Add Checks for None:
    You can add a check to ensure these attributes are not None before attempting to iterate over them.
1 Like

I don’t get it…When I enter the following:

# Multiscrape
multiscrape:
  - resource: "https://my.dhlparcel.nl/api/user/login"
    scan_interval: 3600
    method: "post"
    headers:
      Content-Type: "application/json"
    payload: '{"email":"[email protected]","password":"mypassword"}'
  - resource: "https://my.dhlparcel.nl/receiver-parcel-api/parcels"
    scan_interval: 3600
    method: "get"
    headers:
      Content-Type: "application/json"
    sensor:
      - unique_id: dhl_packages
        name: DHL Pakketten
        value_template: "{{ value_json.parcels | selectattr('category', 'search', '(PROBLEM|CUSTOMS|DATA_RECEIVED|EXCEPTION|INTERVENTION|IN_DELIVERY|LEG|UNDERWAY|UNKNOWN)') | list | count }}"
        attributes:
          - name: parcels
            value_template: "{{ value_json.parcels | selectattr('category', 'search', '(PROBLEM|CUSTOMS|DATA_RECEIVED|EXCEPTION|INTERVENTION|IN_DELIVERY|LEG|UNDERWAY|UNKNOWN)') | list }}"

What is wrong here? Any idea, @ptnijssen ?

I receive unvailable , also after manual reloading Multiscrape.

1 Like