Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: CI

on:
push:
branches:
- '**'
pull_request:
branches:
- '**'

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.11]
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Install test dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-asyncio

- name: Run tests
run: |
pytest -q
100 changes: 100 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ cards:
# and adds the package. This avoids the common Lovelace issue where the
# client sends the literal template string instead of its evaluated value.
service: track17.add_package_from_helper
- show_name: true
show_icon: true
type: button
name: Remove package
tap_action:
action: call-service
# Remove the package named in the helper using the server-side service.
service: track17.remove_package_from_helper
- type: markdown
title: 17TRACK Overview
content: >
Expand All @@ -117,6 +125,79 @@ cards:
{% endif %}
```

Per-package remove buttons (two approaches)
------------------------------------------

If you'd like a per-package remove control in your dashboard (a remove
button next to each tracked package), here are two options: a simple script
approach (no custom cards) and a dynamic approach using HACS custom cards.

1) Simple script + manual rows

- Add a small script that accepts a `tracking_number` variable and calls
the integration's removal service. Put this in `scripts.yaml` or create
it in the Scripts UI:

```yaml
track17_remove_package:
alias: "17TRACK: Remove package"
sequence:
- service: track17.remove_package
data:
tracking_number: "{{ tracking_number }}"
```

- Then in Lovelace, add an `entities` card and for each package row add a
manual `entity` entry with a `tap_action` that calls the script and
passes the package string. This is easy for a small or static set of
tracked packages.

2) Dynamic list with per-item buttons (recommended for many packages)

- Install the HACS custom cards `auto-entities` and `button-card`.

- The following Lovelace snippet uses `auto-entities` to discover
`sensor.track17_*` sensors and renders each as a `button-card` with a
remove action. The `button-card` JS template extracts the tracked
number from the sensor attributes and passes it to the `remove_package`
service.

```yaml
type: 'custom:auto-entities'
card:
type: grid
columns: 1
filter:
include:
- domain: sensor
entity_id: sensor.track17_*
sort:
method: name
card:
type: 'custom:button-card'
entity: '${entity}'
name: '[[[ return entity.attributes.tracking_number || entity.entity_id ]]]'
show_state: true
show_icon: false
tap_action:
action: call-service
service: track17.remove_package
service_data:
tracking_number: "[[[ return entity.attributes.tracking_number ]]]"
```

Notes:
- `auto-entities` automatically builds one child card per matching entity.
The `card:` block controls how each entity is rendered.
- `button-card` uses JavaScript templating (the `[[[ ... ]]]` syntax) to
access the `entity` object and its attributes. We call the integration
service and pass `entity.attributes.tracking_number` as the argument.
- This approach requires HACS to install the custom cards but provides a
fully dynamic UI that updates when packages are added or removed.

Pick the approach that fits your setup — the dynamic `auto-entities` +
`button-card` approach works best for larger or frequently-changing lists.

Add this helper script to your Home Assistant `scripts.yaml` (or create it
from the Scripts UI). The script templates the `input_text` value server-side
and calls the integration's `add_package` service correctly:
Expand Down Expand Up @@ -193,6 +274,25 @@ automation:
- mon
action:
- service: track17.remove_delivered_packages

Included example files
----------------------

This repository includes a couple of ready-made examples you can copy into
your Home Assistant config:

- `examples/lovelace/track17.yaml` — a small Lovelace view with the helper
input and Add / Remove buttons. It also contains a commented dynamic
`auto-entities` + `button-card` example (requires HACS) for per-item remove
buttons.
- `examples/automations/track17_remove_notify.yaml` — an example automation
that creates a persistent notification when the `track17.remove_package`
service is called (useful as a demo confirmation of remove actions).
- `examples/lovelace/track17_full.yaml` — a fully fleshed-out Lovelace view
with tiles, icons and a layout you can copy into a Raw Lovelace view.

Copy the files into the corresponding folders in your Home Assistant config
or open them and paste into the UI editors.
```


Expand Down
17 changes: 17 additions & 0 deletions custom_components/track17/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,28 @@ async def handle_add_from_helper(call):
if added:
await hass.config_entries.async_reload(entry.entry_id)

async def handle_remove_from_helper(call):
"""Read the input_text helper and remove that package.

This mirrors `add_package_from_helper` but removes the package named
in the helper. Useful for Lovelace buttons that shouldn't send
client-side templates.
"""
state = hass.states.get(DEFAULT_HELPER_ENTITY)
if not state:
return
number = state.state
removed = await coordinator.async_remove_package(number)
if removed:
await hass.config_entries.async_reload(entry.entry_id)

# Register services
hass.services.async_register(DOMAIN, "add_package", handle_add_package)
hass.services.async_register(DOMAIN, "remove_package", handle_remove_package)
hass.services.async_register(DOMAIN, "refresh_package", handle_refresh_package)
hass.services.async_register(DOMAIN, "refresh_all_packages", handle_refresh_all)
hass.services.async_register(DOMAIN, "add_package_from_helper", handle_add_from_helper)
hass.services.async_register(DOMAIN, "remove_package_from_helper", handle_remove_from_helper)

# Forward platforms
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
Expand Down Expand Up @@ -105,5 +121,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.services.async_remove(DOMAIN, "refresh_package")
hass.services.async_remove(DOMAIN, "refresh_all_packages")
hass.services.async_remove(DOMAIN, "add_package_from_helper")
hass.services.async_remove(DOMAIN, "remove_package_from_helper")

return unload_ok
4 changes: 2 additions & 2 deletions custom_components/track17/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@

EVENT_DELIVERED = "track17_delivered"

VERSION = "1.1.5"
VERSION = "1.1.6"
API_URL = "https://api.17track.net/track/v2.4/"

DEFAULT_HEADERS = {
"Content-Type": "application/json",
"User-Agent": "HomeAssistant-Track17-Integration/1.1.5"
"User-Agent": "HomeAssistant-Track17-Integration/1.1.6"
}
2 changes: 1 addition & 1 deletion custom_components/track17/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"domain": "track17",
"name": "17TRACK Package Tracking",
"version": "1.1.5",
"version": "1.1.6",
"documentation": "https://api.17track.net",
"config_flow": true,
"iot_class": "cloud_polling"
Expand Down
5 changes: 5 additions & 0 deletions custom_components/track17/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,8 @@ add_package_from_helper:
name: Add Package From Helper
description: "Read the configured input_text helper and add the package value"
fields: {}

remove_package_from_helper:
name: Remove Package From Helper
description: "Read the configured input_text helper and remove the package value"
fields: {}
17 changes: 17 additions & 0 deletions examples/automations/track17_remove_notify.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Example automation: show a persistent notification when a package is removed
# Place this in your automations folder or import via the UI.

alias: "17TRACK: Notify on package removal"
trigger:
- platform: event
event_type: call_service
event_data:
domain: track17
service: remove_package
condition: []
action:
- service: persistent_notification.create
data:
title: "17TRACK — Package removed"
message: "Removed package: {{ trigger.event.data.service_data.tracking_number }}"
mode: single
72 changes: 72 additions & 0 deletions examples/lovelace/track17.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Example Lovelace view for 17TRACK integration
# Copy/paste this into a Lovelace Raw config or use as a view file.
# Requires the integration to be installed and configured.

title: 17TRACK
path: track17
cards:
- type: entities
title: Add / Remove Package
entities:
- entity: input_text.track17_new_package
name: Tracking number
- type: call-service
name: Add package (from helper)
action_name: Add
service: track17.add_package_from_helper
- type: call-service
name: Remove package (from helper)
action_name: Remove
service: track17.remove_package_from_helper

- type: markdown
title: 17TRACK Overview (simple)
content: |
{% set packages = state_attr('sensor.track17_packages', 'packages') %}
{% if packages %}
| Tracking Number | Carrier | Country | Last Event | Delivered At | Link |
|-----------------|---------|---------|------------|--------------|------|
{% for pkg in packages %}
{% set attr = state_attr('sensor.track17_' ~ pkg, 'attributes') %}
{% if attr is not none %}
| {{ attr.tracking_number }} | {{ attr.carrier }} | {{ attr.country }} | {{ attr.last_event }} | {{ attr.delivered_at }} | [Link]({{ attr.url }}) |
{% else %}
| {{ pkg }} | (no data) | | | | |
{% endif %}
{% endfor %}
{% else %}
No packages tracked.
{% endif %}

# Dynamic list (requires HACS custom cards: auto-entities + button-card)
# Uncomment this section if you have installed the required custom cards.

#- type: 'custom:auto-entities'
# card:
# type: grid
# columns: 1
# filter:
# include:
# - domain: sensor
# entity_id: sensor.track17_*
# sort:
# method: name
# card:
# type: 'custom:button-card'
# entity: '${entity}'
# name: '[[[ return entity.attributes.tracking_number || entity.entity_id ]]]'
# show_state: true
# show_icon: false
# tap_action:
# action: call-service
# service: track17.remove_package
# service_data:
# tracking_number: "[[[ return entity.attributes.tracking_number ]]]"

- type: entities
title: Controls
entities:
- entity: button.17track_refresh
name: Refresh all
- entity: sensor.track17_packages
name: Tracked packages
Loading
Loading