Current section
Files
Jump to
Current section
Files
lib/p1_parser.ex
defmodule P1Parser do
use Combine
import Combine.Parsers.Base
import Combine.Parsers.Text
@moduledoc """
Understands the P1 format of Smartmeters and translates them to elixir types
"""
# credo:disable-for-this-file Credo.Check.Refactor.PipeChainStart
@doc """
Parses a line of text according to the P1 protocol
## Examples
iex> P1Parser.parse("1-0:1.7.0(01.193*kW)")
{:ok, [:current_energy, :consume, 1.193]}
"""
def parse(line) do
case Combine.parse(line, parser()) do
{:error, reason} -> {:error, reason}
result -> {:ok, result}
end
end
defp parser do
choice(nil, [
header_parser(),
version_parser(),
timestamp_parser(),
total_energy_parser(),
current_energy_parser(),
amperage_parser(),
voltage_parser(),
gas_parser(),
# catchall_parser()
])
end
# /ISk5MT382-1000
defp header_parser do
map(char("/"), fn _ -> :header end)
|> word_of(~r/\w{3}/)
|> ignore(char("5"))
|> word_of(~r/.+/)
end
# 1-3:0.2.8(50)
defp version_parser do
map(string("1-3:0.2.8"), fn _ -> :version end)
|> word_of(~r/.+/)
end
# 0-0:1.0.0(101209113020W)
defp timestamp_parser do
map(string("0-0:1.0.0"), fn _ -> :timestamp end)
|> map(between(char("("), word_of(~r/\d+/), char("W")), &(timestamp(&1)))
|> ignore(char(")"))
end
# 1-0:1.8.1(123456.789*kWh)
# 1-0:1.8.2(123456.789*kWh)
# 1-0:2.8.1(123456.789*kWh)
# 1-0:2.8.2(123456.789*kWh)
defp total_energy_parser do
map(string("1-0:"), fn _ -> :total_energy end)
|> map(digit(), &(direction(&1)))
|> ignore(string(".8."))
|> map(digit(), &(tariff(&1)))
|> between(char("("), float(), char("*"))
|> ignore(string("kWh)"))
end
# 1-0:1.7.0(01.193*kW)
# 1-0:2.7.0(00.000*kW)
defp current_energy_parser do
map(string("1-0:"), fn _ -> :current_energy end)
|> map(digit(), &(direction(&1)))
|> ignore(string(".7."))
|> ignore(digit())
|> between(char("("), float(), char("*"))
|> ignore(string("kW)"))
end
# 1-0:32.7.0(220.1*V)
# 1-0:52.7.0(220.2*V)
# 1-0:72.7.0(220.3*V)
defp voltage_parser do
map(string("1-0:"), fn _ -> :voltage end)
|> map(both(digit(), digit(), fn a, b -> Enum.join([a, b]) |> String.to_integer end), &(phase(&1)))
|> ignore(string(".7.0"))
|> between(char("("), float(), char("*"))
|> ignore(string("V)"))
end
# 1-0:31.7.0(001*A)
# 1-0:51.7.0(002*A)
# 1-0:71.7.0(003*A)
defp amperage_parser do
map(string("1-0:"), fn _ -> :amperage end)
|> map(both(digit(), digit(), fn a, b -> Enum.join([a, b]) |> String.to_integer end), &(phase(&1)))
|> ignore(string(".7.0"))
|> between(char("("), integer(), char("*"))
|> ignore(string("A)"))
end
# 0-1:24.2.1(101209112500W)(12785.123*m3)
defp gas_parser do
map(string("0-1:24.2.1"), fn _ -> :gas end)
|> map(between(char("("), word_of(~r/\d+/), char("W")), &(timestamp(&1)))
|> ignore(char(")"))
|> between(char("("), float(), char("*"))
|> ignore(string("m3)"))
end
defp timestamp(text) do
[date | time] = text
|> String.codepoints
|> Enum.chunk(2)
|> Enum.map(&Enum.join/1)
|> Enum.chunk(3)
"20" <> Enum.join(date, "-") <> " " <> Enum.join(hd(time), ":")
end
defp phase(x) do
case x do
l when l in [31,32] -> :l1
l when l in [51,52] -> :l2
l when l in [71,72] -> :l3
_ -> x
end
end
defp direction(x) do
case x do
1 -> :consume
2 -> :produce
_ -> x
end
end
defp tariff(x) do
case x do
1 -> :low
2 -> :normal
_ -> x
end
end
end