Skip to content

Releases: lvogt/wireless-mbus-parser

v1.5.0

Choose a tag to compare

@lvogt lvogt released this 07 Sep 20:46

Manufacturer specific data, without writing a parser

Handlers no longer have to live inside the library, and most of them no longer have to be code at all.

Handlers from the configuration

A handler can be passed to the parser instead of being registered in it, so a meter can be decoded without changing this package:

const parser = new WirelessMbusParser({
  manufacturerSpecificHandlers: { ACM: decodeAcmeData },
});

A handler of the configuration takes precedence over the one shipped for the same manufacturer.

Handlers as a description

createManufacturerSpecificHandler() builds a handler from a description of where the values sit in the blob:

const decodeAcmeData = createManufacturerSpecificHandler([
  { byte: 0, bit: 0, description: "Backflow detected" },
  { byte: 1, description: "Battery", unit: "%" },
  { byte: 2, bytes: 3, description: "Volume", unit: "l" },
  { byte: 5, flags: ["Leakage", "Burst", null, "Removal"] },
]);

A field is bytes wide (1 to 6, little endian) starting at byte. bit picks one bit, bits an inclusive range which may cross the bytes of the field, flags names one bit each (null for reserved ones) and values names the possible values of a field. The description is plain data, so it can be read from a configuration file — no code has to be evaluated to support a new meter.

Several kinds of blob are described as layouts, chosen by device type, VIF, the length of the blob, or its position among the manufacturer specific records of the telegram:

const decodeAcmeData = createManufacturerSpecificHandler([
  { deviceType: 0x07, length: 4, fields: [...] },
  { deviceType: 0x07, index: 1, fields: [...] },
]);

The position matters more than it sounds: several Itron meters send two manufacturer specific records with the same VIF and the same size, and nothing but their order tells them apart. Every handler receives it as a fourth argument now.

Values have names in the legacy result

A value of a manufacturer specific handler used to be typed VIF_MANUFACTURER_SPECIFIC — all 26 values of an Itron smoke detector alike. Each is now named after its description, so Warning: smoke alarm becomes VIF_WARNING_SMOKE_ALARM. Consumers which build an identifier from the type of a legacy record get one per value that way. legacyName still overrides it.

The descriptions of the Itron smoke detector changed along with it: they are shorter, and each flag says whether the modem or the detector reports it.

Also in this release

  • Mode 13 encryption, fragmented messages and every MAC length of the authentication and fragmentation layer are covered by tests now — the layer went from 46% to 96% branch coverage
  • The README says what the parser does before what it does not, and how to install it

No breaking changes. The added fourth argument of ManufacturerSpecificDataRecordHandler is source compatible; a handler which ignores it keeps working. Node 22 or newer, as before.

v1.4.0

Choose a tag to compare

@lvogt lvogt released this 05 Sep 22:11

Manufacturer specific data records are decoded now.

Meters put data the standard does not describe into manufacturer specific data
records, several values packed into a few bytes. Such a record is still kept as
it is, and its content is decoded additionally when a handler for the
manufacturer exists.

New

  • Manufacturer specific data records are passed to a handler, which turns
    the raw bytes into named values. It works for compact frames as well, and the
    values inherit the storage number, tariff and function field of the record
    they were taken from.
  • The Itron smoke detector is the first supported device: its configuration
    and error codes become 26 individual values.

Warning: smoke alarm = 0 Removal occurred = 1
Warning: perimeter intrusion = 1 Product installed = 1
Remaining battery lifetime = 83 month
Network mode = Walk-by

Upgrading

The decoded values are appended, after the records of the telegram itself, so
existing values keep their position. data can therefore contain more entries
than dataRecords - the data records are not touched, they describe the
telegram.

Only telegrams of a meter with a handler are affected; everything else decodes
exactly as before.

Adding a handler

Writing one needs no knowledge about telegram structures - it is called with the
raw bytes and returns one entry per value:

function decodeAcmeData(data: Buffer): ManufacturerSpecificValue[] {
  return [
    { description: "Backflow detected", value: data[0] & 0b1 },
    { description: "Battery", value: data[1], unit: "%" },
  ];
}

See Manufacturer Specific Data (https://github.com/lvogt/wireless-mbus-parser#manufacturer-specific-data)
for the details.

No API changes.

Full changelog: https://github.com/lvogt/wireless-mbus-parser#changelog

v1.3.1

Choose a tag to compare

@lvogt lvogt released this 05 Sep 14:06

A bug fix release. Techem telegrams decode to different dates than in 1.3.0
if you store decoded dates, expect a one-off discontinuity for Techem meters.

Fixes

  • Techem: the year of the current date is now taken from the last period
    date carried in the same telegram instead of from the wall clock. A telegram
    no longer decodes differently depending on when it is parsed, and telegrams
    received in early January are no longer dated a year into the future. Only a
    telegram without a usable last period date still falls back to the current
    year.
  • Techem heat meters: the day of the current date is a 16 bit field but was
    read as a single byte, so the day could only ever be 0 or 1. A day of 0 does
    not throw — it silently becomes the last day of the previous month, which is
    why the wrong date looked plausible.
  • VIFE descriptors: the object returned by applying a VIFE is used again.
    Every descriptor of the shipped tables modifies the evaluated data in place,
    so a descriptor returning a new object instead was silently doing nothing.

Internal

  • VIFs which only differ in their power of ten are generated from a range
    instead of being written out, and a snapshot lists every entry of every VIF
    table. Three entries which scaled by multiply(x, 1) now use the identity
    like every other entry with an exponent of zero.
  • The units and scaling exponents of the primary, FD and FB tables were cross
    checked against libmbus and FHEM: every comparable entry agreed.

No API changes.

Full changelog: https://github.com/lvogt/wireless-mbus-parser#changelog

v1.3.0

Choose a tag to compare

@lvogt lvogt released this 04 Sep 21:09

Breaking changes

  • Requires node 22 — node 20 reached its end of life in April 2026
  • EvaluatedData.type now describes the value that is actually returned:
    scaling a 64 bit value yields a Number and is no longer reported as
    BigInt. In the legacy result such a value changes from string to number.
  • Malformed telegrams always throw a ParserError. Reading beyond the end of
    a telegram surfaced as a RangeError before, and the manufacturer specific
    decoders threw plain Errors.

Fixes

  • Techem and PRIOS telegrams were decoded incorrectly — the first data
    record was skipped, which shifted every decoded value
  • The current period energy of TCH heat meters was truncated to 16 bit
  • CRC auto detection now ignores trailing data instead of failing
  • The ELL encryption flag was reported as a negative number when its most
    significant bit was set
  • Checking the AFL MAC without the required AFL fields threw a TypeError

Other changes

  • ParserError is exported as a class, so errors can be checked with
    instanceof instead of comparing name
  • Error handling and compact frames are documented in the README
  • The package ships unminified with source maps and is marked side effect free

Full changelog: https://github.com/lvogt/wireless-mbus-parser#changelog

v1.1.0

Choose a tag to compare

@lvogt lvogt released this 05 Apr 19:26

What's Changed

  • Upgrade dependencies by @lvogt in #26
  • Enforce erasable syntax only by @lvogt in #27
  • Do not throw on DIF_SPECIAL_FUNCTIONS by @lvogt in #28

Full Changelog: v1.0.1...v1.1.0

v1.0.1

Choose a tag to compare

@lvogt lvogt released this 15 Jan 20:04

What's Changed

New Contributors

Full Changelog: v1.0.0...v1.0.1

v1.0.0

Choose a tag to compare

@lvogt lvogt released this 05 Nov 22:06

Initial Release 🥳