Skip to content

Releases: yodax/ha-trappers

v1.0.0 — first stable release

Choose a tag to compare

@yodax yodax released this 07 Sep 16:47

First stable release. Nothing in the integration's behaviour changes from
v0.2.3 — no new sensors, no new settings, nothing to reconfigure. The version
number marks that this has been running against a live account and a real Home
Assistant instance for a while now, and that it is being submitted to the HACS
default store.

What it does

Seven sensors, one device per Trappers account:

Sensor What it tells you
Points balance Points you have to spend in the webshop
Points balance value What that balance is worth in euro, at the shop's gift-card rate
Cycling days total Days cycled since you joined
Cycling days this month Days cycled this calendar month
Last cycling day The most recent date your bike tag was registered
Points earned this month Points credited this calendar month
Commute distance Your registered one-way commute

English and Dutch throughout. Updates five times a day between 08:00 and 20:00,
nothing overnight — points are credited at most once per working day.

In this release

The README was rewritten for someone who has never heard of Trappers or
FiscFree: what each sensor means rather than how it is worked out, a numbered
install path, and the engineering rationale moved out to the developer notes
where it belongs.

If you are coming from 0.1.x

Two earlier fixes are worth repeating, because both changed numbers you may
have been reading:

  • The euro value was overstating your balance by about 5% before v0.2.0.
    It used a conversion field from the API that turns out to reproduce the
    scheme's own cost basis rather than a price you can buy at. It now reads the
    webshop catalogue instead, and reports unknown rather than a guess when no
    clear rate can be read.
  • Entity IDs contained your email address before v0.1.1, employer domain
    included, which then travelled into any dashboard YAML or screenshot you
    shared. They are named from your first name now.

Upgrading from 0.1.x renames entities, so dashboards and automations pointing
at the old IDs need updating once.

Install

HACS → ⋮ → Custom repositories → https://github.com/yodax/ha-trappers,
category Integration. Requires Home Assistant 2026.3.0 or newer.

Unofficial and community-built; not affiliated with or endorsed by Trappers or
FiscFree.

v0.2.3 — completing the review fixes

Choose a tag to compare

@yodax yodax released this 07 Sep 09:48

The v0.2.2 fixes were themselves re-reviewed, and several turned out to be
partial. No sensor values change; no reconfiguration needed.

The miss that mattered

Transactions had their own pagination loop, a few hundred lines away from
the shared one fixed in v0.2.2 — and it kept both of the same bugs. A server
answering "here are 0 records, there are 100" produced 0 points earned this
month
. There is now exactly one pagination implementation; the duplication
was the entire cause.

Also fixed

  • Advancing the offset does not help against a server that ignores it. Two
    identical pages turned one 10-point transaction into 20. Records are now
    matched by id, and a page containing only repeats is an error — matched by
    id rather than by content precisely so that Trappers' own duplicate tag
    reads, which are separate records sharing a date, still work normally.
  • A fast poll could still schedule a redundant one. If a refresh finished
    just before the slot it was woken for, the next poll was scheduled a minute
    later instead of three hours later.
  • The monthly reset timestamp could name the wrong month. A poll starting
    at 23:59:59 on the 31st computes that month's figures and can finish a second
    into the next one — and would then file them under the new month.
  • The euro rate accepted a catalogue split exactly 80/20, treated an unreadable
    expiry date as "never expires", and read "Cadeaukaart € 25,00 2026 editie"
    as 25.002026. The rate is also re-checked after midnight now, since article
    availability is expressed in whole days.

The leak guard, where two holes were in the code written to close a hole

  • The new commit-message check skipped lines starting with #, assuming git
    strips comments. git commit -m does not, so a commented-out secret went
    straight through the very hook added to catch it.
  • Files with non-ASCII names were committed unscanned, because git renders
    such paths escaped and the escaped form matches no file.
  • The hook's own test suite counted any failed commit as a successful block,
    so a broken test environment would have reported passes without ever reaching
    the hook — the same fail-open the guard exists to prevent, one level up.

171 tests, 34 hook cases.

v0.2.2 — fixes from an adversarial review

Choose a tag to compare

@yodax yodax released this 07 Sep 09:35

0.2.1 was reviewed adversarially by a second model after it had been declared
ready, and it found real defects. Every one below was reproduced before being
fixed, and each now has a test that fails against the old behaviour.

No reconfiguration needed. The two "this month" sensors change state class,
which affects how Home Assistant records their long-term statistics.

The one that mattered

A failed poll could pin the schedule. The next poll time was recalculated
only after a successful update, and Home Assistant re-arms a failed refresh
with the interval unchanged. So a failure froze whatever interval was last set
and repeated it forever — through the night, and including the 60-second
minimum, which turned the guard against a runaway loop into the cause of one.
A failed poll now simply waits for the next scheduled slot.

Wrong values that looked right

  • An empty page from the API counted as "all done", so a server answering
    "here are 0 records, there are 100" produced a confident zero lifetime
    cycling days
    . Now an error. Paging also follows what was actually returned,
    so a server that caps its page size can no longer make the integration skip
    records silently.
  • The euro value over-claimed. A catalogue split 60/40 between two rates
    still produced a number, despite the README promising unknown. Articles not
    yet on sale, expired, or carrying a handling fee were all counted. And
    "Cadeaukaart € 1 000" was read as one euro, while a name like
    "Artikel van € 100 voor € 25" took the wrong figure. Ambiguous names are
    now dropped rather than guessed at.
  • The commute distance ignored whether a registration was in force, so one
    starting next month immediately replaced the current one, and an expired one
    was reported indefinitely.
  • "This month" sensors could lose a month. They were total_increasing,
    which infers a reset from a decrease it observes — but nothing polls
    overnight, so a month ending on one cycling day followed by a first day that
    already shows one looks like no change at all, and the new month's first day
    vanished from statistics. They are now total with an explicit reset time.

Privacy

  • Error messages could echo content from the API response. A malformed date
    field was quoted verbatim into the log, which contradicts this integration's
    rule of logging only the endpoint and status code.
  • The repository's leak guard now checks commit messages as well as file
    contents. It previously did not, and that gap had already been used: an
    account figure was removed from the files and from a published release note,
    and then described in the commit message that removed it. Two further holes
    in the guard are fixed as well.

Also

The documentation claimed the session token always expires between polls and
that the shop catalogue is fetched at most once a day. Neither was true.
Real traffic is roughly 25-30 requests a day, not 20, and the README now says
so.

v0.2.1 — spread polls, and publication tidy-up

Choose a tag to compare

@yodax yodax released this 07 Sep 08:58

A politeness release. No sensor values change and nothing needs
reconfiguring.

Polls no longer land on the dot

Every installation of this integration was hitting the API at exactly
08:00:00, 11:00:00 and so on. Each installation now picks a fixed offset of up
to 15 minutes and keeps it, so yours might poll at 08:06, 11:06, 14:06 every
day.

The offset is stable for your installation, not re-rolled each time —
spreading load is about not colliding with other people's installations, so
a fixed offset achieves that while "when does it poll?" still has an answer.
Two accounts in one household get two different offsets. The offset is always
added after the slot, so nothing ever fires before 08:00.

It also keeps polls off the top of the hour, where every cron job and every
other integration already is.

Documentation and repository

  • The README, the release notes and the test fixtures were carrying the test
    account's real points balance as the worked example. Replaced throughout
    with a round illustrative figure. The v0.2.0 release note has been edited
    too.
  • Issue templates, which ask for versions and — more importantly — remind you
    to redact your address, telephone number, employee numbers and email before
    pasting any log. This integration never logs those, but other components'
    debug output can, and entity IDs can carry your name.
  • README badges, "My Home Assistant" buttons for adding the repository and
    starting the config flow, and a Contributing section.
  • The README now says plainly that this is not in the HACS default store, so
    adding it as a custom repository is the expected route rather than a
    workaround.

v0.2.0 — the euro value was overstating your balance

Choose a tag to compare

@yodax yodax released this 07 Sep 08:39

Two behaviour changes: the euro sensor was wrong, and polling is much gentler.

The euro value was ~5% too high

"Points balance value" overstated your balance and now reports what the
points actually buy.
A balance of 10 000 points read as € 100,00; the
same points buy € 95,24 of gift cards in the webshop.

The API has a field called trapperToEuroConversionRatio, and v0.1.x used it.
It is a real field, it is named like the answer, and it is not the answer — it
reproduces the scheme's own purchase price, which sits above shop prices by
the operator's margin. Across the whole catalogue, purchasePrice divided by
trappersPrice is exactly that ratio for every single article, so it can
never tell you anything about what a thing costs to buy.

The sensor now reads the webshop catalogue and works out the rate that can
actually be spent: a "bol. cadeaukaart € 25" costing 2625 points is 105 points
per euro. The rate is read from your own employer's catalogue every time —
it is a contract term and it differs between employers, so nothing is
hardcoded.

Gift cards are deliberately the yardstick because they are the best rate
in the shop; physical goods run around 131 points per euro of their supplier
price. So the sensor reports the best achievable value of your balance.

If the catalogue ever stops having one consistent rate, the sensor reads
unknown rather than reporting an average that would buy nothing.

Credit where it is due: this was caught by a human noticing that the sensor
disagreed with a real listing in the shop. Both numbers were internally
consistent; nothing in the API alone would have given it away.

Polling: five times a day, none overnight

Was every 30 minutes. Now 08:00, 11:00, 14:00, 17:00 and 20:00 in your
Home Assistant timezone — about 20 requests a day instead of 192.

Points are credited at most once per working day, when the collector unit
reads your bike tag as you arrive at the office, so there is nothing to see
between 20:00 and 08:00. Fixed wall-clock slots rather than a rolling timer,
so "when does it poll?" does not depend on when you last restarted Home
Assistant.

Your sensors keep their values overnight — they do not go unavailable. A
restart at any hour still loads them immediately, and
homeassistant.update_entity or the entry's reload button still forces a
refresh whenever you want one.

Privacy

The integration no longer calls the orders endpoint at all. It was only ever
read for that conversion ratio, and it is the one endpoint in this API that
returns a bank account number and the ordering person's name. Removing a
request for data nothing needed is the better half of this release.

Upgrading

Nothing to do — no entity IDs change and no reconfiguration is needed. The
euro sensor will simply read a lower, correct number after the next poll.

Upgrading from v0.1.0 specifically: entity IDs changed in v0.1.1, so see
that release's note as well.

v0.1.1 — entity IDs no longer contain your email address

Choose a tag to compare

@yodax yodax released this 07 Sep 08:14

Breaking for existing installs: entity IDs change. If you installed v0.1.0,
your dashboard cards, automations and templates will need updating — see below.

The fix

v0.1.0 titled the config entry with the account's email address. Home Assistant
slugifies that title into the device name and from there into every entity ID,
so v0.1.0 produced entities like sensor.you_example_com_points_balance, with
the whole address — local part and employer domain — repeated in every
friendly name.

That is not just untidy. Entity IDs travel: into dashboard YAML pasted into a
forum thread or an issue on this repository, and into any screenshot. Nobody
should have to remember to redact their email address out of a card.

The entry is now titled with the account's first name, which the API
already returns at login — which is also what the title is for, since it is
what tells two accounts in the same household apart. So you get
sensor.michael_points_balance. If the account has no first name set, the
title falls back to the local part of the address, never the whole address.

The unique_id that stops the same account being added twice is unchanged and
is still the full lowercased email; it is never rendered anywhere.

Upgrading from v0.1.0

Entity IDs are fixed when an entity is first created, so updating the
integration is not enough on its own. Remove the Trappers config entry and
add it again
(Settings → Devices & Services → Trappers → ⋮ → Delete, then
Add Integration). Then update any card, automation or template that referred
to the old IDs. Recorder history under the old IDs is not carried over.

A fresh install needs none of this.

Also in this release

async_login() now returns only the account's first name rather than the whole
userDetails object it came from — so the surname, home address, telephone
number and employer employee numbers sitting alongside it never reach a caller
at all.

Two tests pin the whole rendered surface — every entity ID, every state
attribute and every device field — so this cannot quietly come back.

v0.1.0 — first release

Choose a tag to compare

@yodax yodax released this 07 Sep 08:03

First release of the Trappers Home Assistant integration.

Reports a Trappers
(FiscFree) bike-to-work account's points balance and cycling activity as
seven Home Assistant sensors, one device per account.

Sensor Unit
Points balance trappers
Points balance value
Cycling days total
Cycling days this month
Last cycling day date
Points earned this month trappers
Commute distance km
  • Config flow with reauth; add the integration once per account.
  • Polls every 30 minutes — a deliberately gentle default for an unofficial API.
  • English and Dutch entity names, following your Home Assistant language.
  • Only the numbers above are exposed. The API returns a great deal more about
    the account holder; none of it is logged or surfaced.

Two behaviours worth knowing:

Cycling days are counted, not totalled. A collector unit sometimes reads the
same tag twice in a day; Trappers stores that as a duplicate entry that earns
no points. Both cycling-day sensors count distinct non-duplicate days, so they
match the points you were actually paid.

The euro value needs an order to exist. The points-to-euro rate is a term of
your employer's contract and the API only reveals it on an order record — so
"Points balance value" reads unknown until the account has placed its first
order, rather than showing an invented number.

Install: HACS → ⋮ → Custom repositories → this repo's URL, category
"Integration" → install → restart Home Assistant. Requires HA 2026.3.0+.

Unofficial and not affiliated with Trappers or FiscFree.