Exmbus.Parser.DataType (Exmbus v0.3.0)
View SourceContains functions for working with data encoded according to EN 13757-3:2018 section 6.3.3 (Table 4) and specified in Annex A of same document.
It also implements working with variable-length data (LVAR) according to same document same section (Table 5)
Summary
Functions
Decode an LVAR value from binary. This is a variable-length-style encoding, but with a twist (as with most things in mbus), so it requires a bit more work than <<length, data::binary-size(length), rest::binary>>.
Type A BCD integer. if MSB 0xF then the remanining digits are interpreted as a negative number
Type B Signed little-endian integer
Type C Unsigned little-endian integer
Type D Bool list from bits.
Type F Convert 32 bits to a NaiveDateTime with seconds truncated to 0. (Type F is a date+time without seconds specified, but we just truncate. Good Enough :tm:)
Type G Convert 16 bits to a Date
Type I Convert 48 bits to a NaiveDateTime (This isn't 1:1 with spec but without creating a new type for DateTime this is the closest we get)
Type J Convert 24 bits to a Time
Encode a Date to type G (16 bit)
Functions
Decode an LVAR value from binary. This is a variable-length-style encoding, but with a twist (as with most things in mbus), so it requires a bit more work than <<length, data::binary-size(length), rest::binary>>.
It takes an optional argument mode
which is an atom indicating what decode method to apply
in case of LVAR 0x00–0xBF, since that can be both ISO 8859-1 encoded text, or it can be
raw bytes in case of VIFs that need a container type.
Currently the following are container types: # If VIF is from the FD extension table and is: # - 0bE0111011 then it's wmbus # - 0bE1110110 then it's manifacturer specific bytes
The default is currently :container
mode which just returns the bytes as a binary.
This is what the old parser does as well, but we could a latin1 decoder in here if someone
actually sends non-ascii text to us, which is the only case where it would be needed
(because in elixir Strings are just binary data in UTF-8, and both Latin-1 and UTF-8 are extensions
to ASCII, so by coincedence, if you send ASCII-only characters it will be the same in both UTF-8 and Latin-1,
and elixir binary data and elixir strings are the same, so we don't need to do any conversion.)
# raw bytes container iex> decode_lvar(<<5, "hello", 0xFF>>, :container)
# empty container iex> decode_lvar(<<0, 0xFF>>, :container)
# empty positive BCD number iex> decode_lvar(<<0xC0, 0xFF>>)
# positive BCD number iex> decode_lvar(<<0xC1, 0x10, 0xFF>>)
# empty negative BCD number iex> decode_lvar(<<0xD0, 0xFF>>)
# negative BCD number iex> decode_lvar(<<0xD1, 0x10, 0xFF>>)
# negative multi-byte BCD number iex> decode_lvar(<<0xD2, 0x34, 0x12, 0xFF>>)
# LVAR binary number iex> decode_lvar(<<0xE1, 123::signed-little-size(8), 0xFF>>)
# LVAR binary number iex> decode_lvar(<<0xE4, -123456789::signed-little-size(32), 0xFF>>)
iex> decode_lvar(<<5, "hel">>, :container) {:error, {:not_enough_bytes_for_lvar, 5, "hel"}, <<>>}
Type A BCD integer. if MSB 0xF then the remanining digits are interpreted as a negative number
iex> decode_type_a(<<0x34, 0x12, 0xFF>>, 16)
iex> decode_type_a(<<0x23, 0xF1, 0xFF>>, 16)
iex> decode_type_a(<<0x23, 0xC1, 0xFF>>, 16) {:ok, {:invalid, {:type_a, 0xC123}}, <<0xFF>>}
Type B Signed little-endian integer
iex> decode_type_b(<<-1234::signed-little-size(16), 0xFF>>, 16)
Type C Unsigned little-endian integer
iex> decode_type_c(<<1234::unsigned-little-size(16), 0xFF>>, 16)
Type D Bool list from bits.
iex> decode_type_d(<<0b0000111111110000::unsigned-little-size(16), 0xFF>>, 16)
Type F Convert 32 bits to a NaiveDateTime with seconds truncated to 0. (Type F is a date+time without seconds specified, but we just truncate. Good Enough :tm:)
iex> decode_type_f(<<0b00::2, 10::6, 0::1, 0::2, 20::5, 0b101::3, 1::5, 0b0010::4, 2::4, 0xFF>>)
Type G Convert 16 bits to a Date
iex> decode_type_g(<<0b101::3, 1::5, 0b0010::4, 2::4, 0xFF>>)
iex> decode_type_g(<<0xFF, 0xFF, 0xFF>>)
Type I Convert 48 bits to a NaiveDateTime (This isn't 1:1 with spec but without creating a new type for DateTime this is the closest we get)
Type J Convert 24 bits to a Time
iex> decode_type_j(<<59, 48, 11, 0xFF>>)
Encode a Date to type G (16 bit)
# <<161, 34>> = <<5::3, 1::5, 2::4, 2::4>> iex> {:ok, <<161, 34>>} = encode_type_g(~D[2021-02-01])