OmniPower implementation

Parse Kamstrup OmniPower wm-bus telegrams

platform

Python 3.5.10 on Linux, OS X

synopsis

Implements parsing functionality for C1 telegrams and log handling for data series

author

Janus Bo Andersen

date

28 October 2020

Version history

  • Ver 1.0: Set up parser and decryption. Janus.

  • Ver 2.0: Implement CRC16, timezone. Janus.

  • Ver 2.1: More robust exception handling, parse ELL-SN. Janus

  • Ver 2.2: Utilize new MeterMeasurement.is_empty() in validation during parsing. Janus

Overview

  • This module implements parsing for the Kamstrup OmniPower meter, single-phase.

  • The meter sends wm-bus C1 (compact one-way) telegrams.

  • Telegrams on wm-bus are little-endian, i.e. LSB first.

  • The meter sends 1 long and 7 short telegrams, and then repeats.

  • Long telegrams include data record headers (DRH) and data, that is DIF/VIF codes + data.

  • Short telegrams only include data.

Telegram fields

In a telegram C1 telegram, the data fields are:

#

Byte#

Bytes

M-bus field

Description

Expected value (little-endian)

0

0

1

L

Telegram length

0x27 (39 bytes, short frame), or 0x2D (45 bytes, long frame)

1

1

1

C

Control field (type and purpose of message)

0x44 (SND_NR)

2

2-3

2

M

Manufacturer ID (official ID code)

0x2D2C (KAM)

3

4-7

4

A

Address (meter serial number)

0x57686632 (big-endian:32666857)

4

8

1

Ver.

Version number of the wm-bus firmware

0x30

5

9

1

Medium

Type / medium of meter

0x02 (Electricity)

6

10

1

CI

Control Information

0x8D (Extended Link Layer 2)

7

11

1

CC

Communication Control

0x20 (Slow response sync.)

8

12

1

ACC

Access field

Varies

9

13-16

4

AES CTR / SN

AES counter (Session number)

Varies, see below

10

17-39 17-45

23 29

Data

Contains AES-encrypted data frame, varying for short and long frames

Encrypted data

11

2

CRC16

CRC16 check

The fields 0-9 of the telegram can be unpacked using the little-endian format <BBHIBBBBB, where

  • < marks little-endian,

  • B is an unsigned 1 byte (char),

  • H is an unsigned 2 byte (short),

  • I is an unsigned 4 byte (int)

AES counter / Session number

The AES_CTR / Extended Link Layer SN field (ELL-SN) is is structured as per EN 13757-4, p. 54.

The example shows ELL-SN value of 0x01870320 (little-endian) -> 0x20038701 (big-endian). Bit readout:

Byte:

3

2

1

0

Hex:

0x20

0x03

0x87

0x01

Binary:

0010 0000

0000 0011

1000 0111

0000 0001

Should give the following slicing and interpretation:

Bits:

31-29

28-04

03-00

Field:

ENC

Time

Session

Example:

001

0 0000 0000 0011 1000 0111 0000

0001

Hex:

0x1

0x3870 (14448)

0x1

  • ENC (Encryption): 0 -> No encryption, 1 -> AES-CTR mode, higher -> reserved.

  • Time: Minute counter since 01/01/2013 (?), or since meter started, requires RTC set on meter. So time measurement was taken about 10 days after the meter was started.

  • Session: Incremented by meter for each transmission, unless using partial/fractured frames.

Parsing. The whole ELL-SN field is read out and masks are used to extract fields.

Telegram examples

Encrypted short telegrams:

L

C

M

A

Ver

Med

CI

CC

ACC

AES CTR

Encrypted payload

CRC 16

27

44

2D 2C

5768 6632

30

02

8D

20

2E

2187 0320

D3A4F149 B1B8F578 3DF7434B 8A66A557 86499ABE 7BAB59

xxxx

27

44

2d 2c

5768 6632

30

02

8d

20

63

60dd 0320

c42b87f4 6fc048d4 2498b44b 5e34f083 e93e6af1 617631

3d9c

27

44

2d 2c

5768 6632

30

02

8d

20

8e

11de 0320

188851bd c4b72dd3 c2954a34 1be369e9 089b4eb3 858169

494e

Encrypted long telegrams:

L

C

M

A

Ver

Med

CI

CC

ACC

AES CTR

Encrypted payload

CRC 16

2D

44

2D 2C

5768 6632

30

02

8D

20

64

61DD 0320

38931d14 b405536e 0250592f 8b908138 d58602ec a676ff79 e0caf0b1 4d

0e7d

Decryption

  • The wireless m-bus on OmniPower uses AES-128 Mode CTR (if enabled, otherwise no encryption).

  • See EN 13757-4:2019, p. 54, as ELL (Ext. Link-Layer) with ENC = 0x1 => AES-CTR.

  • A decryption prefix (initial counter block) is built from some of the fields.

  • See table 54 on p. 55 of EN 13757-4:2019.

It can be packed using the format <HIBBBIB.

M

A

Ver

Med

CC

AES_CTR

FN

BC

2D2C

57686632

30

02

20

21870320

0000

00

  • AES Prefix (initialization vector): Fields M, …, AES_CTR, FN.

  • FN: frame number (frame # sent by meter within same session number, in case of multi-frame transmissions).

  • AES Counter: BC

  • BC: Block counter (encryption block number, counts up for each 16 byte block decrypted within the telegram).

Decrypted payload examples

The interpretation of the fields in the OmniPower is

Field

Kamstrup name

Data fmt (DIF)

Value type (VIF/E)

VIF/E meaning

DIF VIF/E

Data 1

A+

32-bit uint

Energy, 10^1 Wh

Consumption from grid, accum.

04 04

Data 2

A-

32-bit uint

Energy, 10^1 Wh

Production to grid, accum.

04 84 3C

Data 3

P+

32-bit uint

Power, 10^0 W

Consumption from grid, instantan.

04 2B

Data 4

P-

32-bit uint

Power, 10^0 W

Production to grid, instantan.

04 AB 3C

Transport layer control information fields (TPL-CI), ref. EN 13757-7:2018, p. 17, introduce Application Layer (APL) as:

  • 0x78 with full frames (Response from device, full M-Bus frame)

  • 0x79 with compact frames (Response from device, M-Bus compact frame)

Data integrity check (CRC16)

The first 2 bytes (16 bits) of a payload is always the CRC16 value of the sent message. This value must be checked versus CRC16 calculated on the received payload.

Decrypted short telegram payload

CRC16

TPL-CI

Data fmt. sign.

CRC16 data

Data 1

Data 2

Data 3

Data 4

1170

79

138C

4491

CE000000

00000000

03000000

00000000

Measurement data starts at byte 7, and can easily be extracted using <IIII little-endian format.

In this example, 206 10^1 Wh (2.06 kWh) have been consumed, and the current power draw is 3 10^0 W (0.003 kW).

Decrypted long telegram payload

In this kind of telegram, the DRHs are included.

CRC16

TPL-CI

DIF/VIF 1

Data 1

DIF/VIF/VIFE 2

Data 2

DIF/VIF 3

Data 3

DIF/VIF 4

Data 4

9831

78

04 04

D7000000

04 84 3C

00000000

04 2B

03000000

04 AB 3C

00000000

Extraction is slightly more complex, requiring either a longer parsing pattern or perhaps a regex.

In this example, 215 10^1 Wh (2.15 kWh) have been consumed, and the current power draw is 3 10^0 W (0.003 kW).

The C1 Telegram class

class meter.OmniPower.C1Telegram(telegram: bytes)[source]

Implements capture of data fields for a C1 telegram from OmniPower

decrypt_using(meter: meter.OmniPower.OmniPower)bool[source]

Decrypts a telegram and inserts it into telegram (self) Uses a meter object and its key to perform decryption. Requires instantiated OmniPower meter with valid AES-key.

The OmniPower class

class meter.OmniPower.OmniPower(name: str = 'Kamstrup OmniPower one-phase', meter_id: str = '32666857', manufacturer_id: str = '2C2D', medium: str = '02', version: str = '30', aes_key: str = '9A25139E3244CC2E391A8EF6B915B697')[source]

Implementation of our OmniPower single-phase meter Passed values are hex encoded as string, e.g. ‘2C2D’ for value 0x2C2D.

add_measurement_to_log(measurement: meter.MeterMeasurement.MeterMeasurement)bool[source]

Pushes a new measurement to the tail end of the log

decrypt(telegram: meter.OmniPower.C1Telegram)bytes[source]

Decrypt a telegram. Returns decrypted bytes. Raises CrcCheckException if CRCs do not match after decryption.

Requires:

  • the prefix from the telegram (telegram.prefix), and

  • the encryption key stored in the meter object.

Decrypts the data stored in field telegram.encrypted

dump_log_to_json()str[source]

Returns a JSON string of all measurement frames in log, with an incremented number for each observation.

extract_measurement_frame(telegram: meter.OmniPower.C1Telegram)meter.MeterMeasurement.MeterMeasurement[source]

Requires that the telegram is already decrypted, otherwise returns empty measurement frame.

is_this_my(telegram: meter.OmniPower.C1Telegram)bool[source]

Check whether a given telegram is from this meter by comparing meter setting to telegram

process_telegram(telegram: meter.OmniPower.C1Telegram)bool[source]

Does entire processing chain for a telegram, including adding to log. Returns True if processing is OK and added to log OK. Otherwise False.

classmethod unpack_long_telegram_data(data: bytes) → Tuple[int, ][source]

Long C1 telegrams contain DIF/VIF information and field data values

classmethod unpack_short_telegram_data(data: bytes) → Tuple[int, ][source]

Short C1 telegrams only contain field data values, no information about DIF/VIF

Exception classes

class meter.OmniPower.TelegramParseException(exception_message: str)[source]

Use this to raise an exception if a bytestream telegram fails to parse into C1 format.

class meter.OmniPower.AesKeyException(exception_message: str)[source]

Use this to raise an exception when an AES key is missing or wrong length.