Decoder for SML (Smart Message Language) telegrams.
SML (BSI TR-03109-1) is the protocol German electricity meters ("moderne
Messeinrichtung", eHZ) use on their optical interface. A meter typically
pushes a telegram every one to four seconds that contains the current
readings, e.g. the energy counter (1-0:1.8.0) and the current power
(1-0:16.7.0).
Reading values
{:ok, bezug_wh, 30} = Sml.value(telegram, :bezug)
{:ok, leistung_w, 27} = Sml.value(telegram, :leistung)The third element is the DLMS unit code (30 = Wh, 27 = W), see
Sml.Units. Values are scaled with the scaler sent by the meter: the result
is an integer if the scaler is not negative and a float otherwise.
Keys can be an alias atom (see Sml.Obis for the list), an OBIS string like
"1-0:1.8.0" or "1-0:1.8.0*255", an Sml.Obis struct or the raw 6 byte
OBIS binary.
All functions accept either the raw telegram binary or an already decoded
Sml.File. When reading several values from one telegram, decode it once:
{:ok, file} = Sml.decode(telegram)
{:ok, bezug, _unit} = Sml.value(file, :bezug)
{:ok, einspeisung, _unit} = Sml.value(file, :einspeisung)Inspecting telegrams
decode/2 returns the full structure: an Sml.File with a list of
Sml.Messages whose bodies are the structs in Sml.Messages. Since
everything is a plain struct, pattern matching works as expected:
{:ok, %Sml.File{messages: messages}} = Sml.decode(telegram)
for %Sml.Message{body: %Sml.Messages.GetListResponse{val_list: entries}} <- messages,
%Sml.ListEntry{obis: obis, unit: 27} = entry <- entries do
{to_string(obis), Sml.ListEntry.scaled_value(entry)}
endReading from a serial port
Telegrams arrive in arbitrary chunks. Sml.Transport.extract/1 splits a
buffer into complete telegrams and returns the rest for the next read:
{telegrams, buffer} = Sml.Transport.extract(buffer <> chunk)
Summary
Functions
Decodes a telegram into an Sml.File.
Like decode/2 but raises Sml.DecodeError on failure.
Returns all list entries (readings) of all GetListResponse messages.
Returns the list entry for key.
Returns the scaled value and the unit code for key.
Returns all readings as a map of Sml.Obis to {scaled_value, unit}.
Types
@type error() :: Sml.Transport.unwrap_error() | Sml.Decoder.error() | :not_found | :invalid_obis | {:unknown_alias, atom()}
@type key() :: atom() | String.t() | Sml.Obis.t()
An alias atom, OBIS string, Sml.Obis struct or 6 byte OBIS binary.
@type source() :: binary() | Sml.File.t()
A raw telegram binary or a decoded Sml.File.
Functions
@spec decode(binary(), keyword()) :: {:ok, Sml.File.t()} | {:error, error()}
Decodes a telegram into an Sml.File.
data may be a complete transport frame (starting with
1B 1B 1B 1B 01 01 01 01, leading garbage is skipped) or the bare message
bytes without framing.
Options
:crc- checksum algorithm used to verify the transport frame and each message::auto(default) accepts CRC-16/X-25 as well as CRC-16/Kermit (used by e.g. Holley DTZ541),:x25or:kermitaccept only that one,falseskips verification for meters that send wrong checksums.
Errors
:no_start_sequence,:incomplete,:invalid_escape,:invalid_padding- broken transport framing:invalid_crc- a checksum does not match{:parse_error, reason, offset}- invalid SML encoding at byteoffsetof the message payload
@spec decode!(binary(), keyword()) :: Sml.File.t()
Like decode/2 but raises Sml.DecodeError on failure.
@spec entries(source()) :: {:ok, [Sml.ListEntry.t()]} | {:error, error()}
Returns all list entries (readings) of all GetListResponse messages.
@spec get(source(), key()) :: {:ok, Sml.ListEntry.t()} | {:error, error()}
Returns the list entry for key.
If several entries match a pattern without F group (e.g. some meters send
1-0:1.8.0*255 along with historic values 1-0:1.8.0*96), the one with
F = 255 (current value) is preferred, otherwise the first match is
returned.
@spec value(source(), key()) :: {:ok, number() | binary() | boolean() | nil, Sml.Units.code() | nil} | {:error, error()}
Returns the scaled value and the unit code for key.
Non-numeric values (like the manufacturer ID) are returned as binaries; the
unit is nil if the meter does not send one.
{:ok, 12_345.6, 30} = Sml.value(telegram, :bezug)
{:ok, 230, 27} = Sml.value(telegram, "1-0:16.7.0")
{:ok, "EMH", nil} = Sml.value(telegram, :hersteller)
@spec values(source()) :: {:ok, %{required(Sml.Obis.t()) => {term(), Sml.Units.code() | nil}}} | {:error, error()}
Returns all readings as a map of Sml.Obis to {scaled_value, unit}.