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

14 October 2020

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

AES counter

Varies, used for decryption

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)

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 encrypted wireless m-bus on OmniPower uses AES-128 Mode CTR.

  • See EN 13757-4:2019, p. 54, as ELL (Ext. Link-Layer) with ECN = 001 => 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

Prefix: M, …, AES CTR. Counter: FN, BC FN: frame number (frame # sent by meter within same session number, in case of multi-frame transmissions). 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 are

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)

Decrypted short telegram

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

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 OmniPower.OmniPower.C1Telegram(telegram: bytes)[source]

Implements capture of data fields for a C1 telegram from OmniPower

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

Decrypts a telegram using the key from the specified meter. Updates the decrypted field of self. Requires instantiated OmniPower meter with valid AES-key.

The OmniPower class

class OmniPower.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: OmniPower.MeterMeasurement.MeterMeasurement)None[source]

Pushes a new measurement to the tail end of the log

decrypt(telegram: OmniPower.OmniPower.C1Telegram)bytes[source]
Decrypt a telegram. Requires:
  • the prefix from the telegram (telegram.prefix), and

  • the encryption key from the meter.

Decrypts the data stored telegram.encrypted

dump_log_to_json()str[source]

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

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

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

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

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

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

Does entire processing chain for a telegram, including adding to log

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