defmodule WeewxProxy.Modbus.Deye do @moduledoc false require Logger use Tortoise311.Handler alias WeewxProxy.{Publisher, Utils} # Callbacks @impl true def init(_opts) do _ = Logger.info("Initializing handler") {:ok, nil} end @impl true def connection(:up, state) do _ = Logger.info("Connection has been established") {:ok, state} end @impl true def connection(:down, state) do _ = Logger.warning("Connection has been dropped") {:ok, state} end @impl true def connection(:terminating, state) do _ = Logger.warning("Connection is terminating") {:ok, state} end @impl true def subscription(:up, topic, state) do _ = Logger.info("Subscribed to `#{topic}'") {:ok, state} end @impl true def subscription({:warn, [requested: req, accepted: qos]}, topic, state) do _ = Logger.warning("Subscribed to `#{topic}'; requested #{req} but got accepted with QoS #{qos}") {:ok, state} end @impl true def subscription({:error, reason}, topic, state) do _ = Logger.error("Error subscribing to `#{topic}'; #{inspect(reason)}") {:ok, state} end @impl true def subscription(:down, topic, state) do _ = Logger.info("Unsubscribed from `#{topic}'") {:ok, state} end @impl true def handle_message(["bitshake", "tele", "smartmeter", topic], publish, state) do if topic == "SENSOR" do {:ok, meter_data} = Jason.decode(publish) timestamp = DateTime.utc_now() |> DateTime.to_unix() day = get_in(meter_data, ["eBZ", "E_in"]) day_export = get_in(meter_data, ["eBZ", "E_out"]) active = get_in(meter_data, ["eBZ", "Power"]) mqtt_data = %{ dateTime: timestamp, homeEnergyDay: day, homeEnergyExportDay: day_export, homeEnergyActive: active } :ok = Publisher.publish("weewx/ingest_si", mqtt_data) :ok = Publisher.publish_value_map("hadata/bitShake", mqtt_data) {:ok, state} else {:ok, state} end end @impl true def handle_message(topic, publish, state) do full_topic = Enum.join(topic, "/") parsed_message = parse_message(full_topic, publish) state = handle_reading(full_topic, parsed_message, state) {:ok, state} end @impl true def terminate(reason, _state) do _ = Logger.warning("Client has been terminated with reason: `#{inspect(reason)}'") :ok end # Helper @spec parse_message(String.t(), String.t()) :: float() | nil defp parse_message(topic, message) when topic in ["deye/day_energy", "deye/ac/active_power"] do Utils.parse_float(message) end defp parse_message(_topic, _message), do: nil @spec handle_reading(String.t(), float() | nil, {:day | :active, float()}) :: {:day | :active, float()} | nil defp handle_reading(_topic, nil, state), do: state defp handle_reading("deye/day_energy", reading, {:active, active}) do timestamp = DateTime.utc_now() |> DateTime.to_unix() data = %{ dateTime: timestamp, solarEnergyDay: reading * 1000.0, solarEnergyActive: active } :ok = Publisher.publish("weewx/ingest_si", data) :ok = Publisher.publish_value_map("hadata/deye", data) nil end defp handle_reading("deye/day_energy", reading, _other) do {:day, reading} end defp handle_reading("deye/ac/active_power", reading, {:day, day}) do timestamp = DateTime.utc_now() |> DateTime.to_unix() data = %{ dateTime: timestamp, solarEnergyDay: day * 1000.0, solarEnergyActive: reading } :ok = Publisher.publish("weewx/ingest_si", data) :ok = Publisher.publish_value_map("hadata/deye", data) nil end defp handle_reading("deye/ac/active_power", reading, _other) do {:active, reading} end end