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, %{solar_energy_day: 0, solar_energy_total1: 0, solar_energy_total2: 0}} 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 current_time = Time.utc_now() current_hour = current_time.hour new_state = if current_hour == 0, do: %{state | solar_energy_day: 0}, else: state 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, new_state} else {:ok, new_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", "deye/1/total_energy", "deye/2/total_energy"] 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, state) do if reading >= state.solar_energy_day do timestamp = DateTime.utc_now() |> DateTime.to_unix() data = %{ dateTime: timestamp, solarEnergyDay: reading * 1000.0 } :ok = Publisher.publish("weewx/ingest_si", data) :ok = Publisher.publish_value_map("hadata/deye", data) %{state | solar_energy_day: reading} else state end end defp handle_reading("deye/ac/active_power", reading, state) do timestamp = DateTime.utc_now() |> DateTime.to_unix() data = %{ dateTime: timestamp, solarEnergyActive: reading } :ok = Publisher.publish("weewx/ingest_si", data) :ok = Publisher.publish_value_map("hadata/deye", data) state end defp handle_reading("deye/1/total_energy", reading, state) do if reading > 0.1 and reading >= state.solar_energy_total1 do timestamp = DateTime.utc_now() |> DateTime.to_unix() data = %{ dateTime: timestamp, solarEnergyTotal1: reading } # :ok = Publisher.publish("weewx/ingest_si", data) :ok = Publisher.publish_value_map("hadata/deye", data) %{state | solar_energy_total1: reading} else state end end defp handle_reading("deye/2/total_energy", reading, state) do if reading > 0.1 and reading >= state.solar_energy_total2 do timestamp = DateTime.utc_now() |> DateTime.to_unix() data = %{ dateTime: timestamp, solarEnergyTotal2: reading } # :ok = Publisher.publish("weewx/ingest_si", data) :ok = Publisher.publish_value_map("hadata/deye", data) %{state | solar_energy_total2: reading} else state end end end