From c8e8e1dc243e9d728078c6e227a2deba9d16c6a2 Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 3 Aug 2025 19:39:59 -0700 Subject: [PATCH] Fix dashboard MQTT connection and remove simulation code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace SimpleMqtt simulation with real MqttSubscriber - Fix String.split bug when handling MQTT topic parsing - Use hostname-based client ID to avoid MQTT client conflicts - Add process management tools (hivemind, just) to development environment 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- dashboard/lib/dashboard/application.ex | 4 +-- dashboard/lib/dashboard/mqtt_subscriber.ex | 41 +++++++++++++++------ dashboard/lib/dashboard/simple_mqtt.ex | 42 ---------------------- flake.nix | 20 +++++++---- 4 files changed, 46 insertions(+), 61 deletions(-) delete mode 100644 dashboard/lib/dashboard/simple_mqtt.ex diff --git a/dashboard/lib/dashboard/application.ex b/dashboard/lib/dashboard/application.ex index 026697f..7e07041 100644 --- a/dashboard/lib/dashboard/application.ex +++ b/dashboard/lib/dashboard/application.ex @@ -13,8 +13,8 @@ defmodule Dashboard.Application do {Phoenix.PubSub, name: Dashboard.PubSub}, # Start the Finch HTTP client for sending emails {Finch, name: Dashboard.Finch}, - # Start simple MQTT subscriber - Dashboard.SimpleMqtt, + # Start real MQTT subscriber + Dashboard.MqttSubscriber, # Start to serve requests, typically the last entry DashboardWeb.Endpoint ] diff --git a/dashboard/lib/dashboard/mqtt_subscriber.ex b/dashboard/lib/dashboard/mqtt_subscriber.ex index 047558f..5001cfb 100644 --- a/dashboard/lib/dashboard/mqtt_subscriber.ex +++ b/dashboard/lib/dashboard/mqtt_subscriber.ex @@ -19,17 +19,27 @@ defmodule Dashboard.MqttSubscriber do @impl true def init(_opts) do - # Start MQTT connection in a supervised way - {:ok, _pid} = Tortoise.Supervisor.start_child( - :dashboard_mqtt, - client_id: :dashboard_mqtt, + # Start MQTT connection directly with hostname-based client ID to avoid conflicts + {:ok, hostname} = :inet.gethostname() + client_id = "systant-dashboard-#{hostname}" + connection_opts = [ + client_id: client_id, server: {Tortoise.Transport.Tcp, host: "mqtt.home", port: 1883}, handler: {__MODULE__, []}, subscriptions: [{"systant/+/stats", 0}] - ) - - Logger.info("Dashboard MQTT subscriber started") - {:ok, %{hosts: %{}}} + ] + + case Tortoise.Connection.start_link(connection_opts) do + {:ok, _pid} -> + Logger.info("Dashboard MQTT subscriber connected successfully") + {:ok, %{hosts: %{}}} + {:error, {:already_started, _pid}} -> + Logger.info("Dashboard MQTT connection already exists, reusing") + {:ok, %{hosts: %{}}} + {:error, reason} -> + Logger.error("Failed to connect to MQTT broker: #{inspect(reason)}") + {:stop, reason} + end end @impl true @@ -43,11 +53,19 @@ defmodule Dashboard.MqttSubscriber do end # Tortoise handler callbacks - def connection(_status, _state), do: [] - def subscription(_status, _topic, _state), do: [] + def connection(status, state) do + Logger.info("MQTT connection status: #{status}") + {:ok, state} + end + + def subscription(status, topic, state) do + Logger.info("MQTT subscription status for #{topic}: #{status}") + {:ok, state} + end def handle_message(topic, payload, _state) do - case String.split(topic, "/") do + topic_parts = if is_binary(topic), do: String.split(topic, "/"), else: topic + case topic_parts do ["systant", hostname, "stats"] -> case Jason.decode(payload) do {:ok, data} -> @@ -74,5 +92,6 @@ defmodule Dashboard.MqttSubscriber do {:noreply, %{state | hosts: updated_hosts}} end + @impl true def terminate(_reason, _state), do: [] end \ No newline at end of file diff --git a/dashboard/lib/dashboard/simple_mqtt.ex b/dashboard/lib/dashboard/simple_mqtt.ex deleted file mode 100644 index 4a9fff3..0000000 --- a/dashboard/lib/dashboard/simple_mqtt.ex +++ /dev/null @@ -1,42 +0,0 @@ -defmodule Dashboard.SimpleMqtt do - @moduledoc """ - Simple GenServer that polls for MQTT data instead of complex subscriptions. - """ - use GenServer - require Logger - - alias Phoenix.PubSub - - def start_link(_opts) do - GenServer.start_link(__MODULE__, [], name: __MODULE__) - end - - @impl true - def init(_) do - # Start a timer that simulates receiving MQTT data - # In a real implementation, you'd use a proper MQTT client here - Logger.info("Starting simple MQTT poller") - - # For now, just generate fake data that matches what systant publishes - :timer.send_interval(5000, self(), :simulate_mqtt) - - {:ok, %{}} - end - - @impl true - def handle_info(:simulate_mqtt, state) do - # Simulate receiving an MQTT message from orion - hostname = "orion" - host_data = %{ - "message" => "Hello from systant", - "hostname" => hostname, - "timestamp" => DateTime.utc_now() |> DateTime.to_iso8601(), - "last_seen" => DateTime.utc_now() - } - - Logger.info("Simulating MQTT message from #{hostname}") - PubSub.broadcast(Dashboard.PubSub, "systant:hosts", {:host_update, hostname, host_data}) - - {:noreply, state} - end -end \ No newline at end of file diff --git a/flake.nix b/flake.nix index 950cdf6..fc3bb74 100644 --- a/flake.nix +++ b/flake.nix @@ -26,15 +26,22 @@ # Elixir/Erlang for server elixir erlang - - # AI/Development tools + + # File watching for Phoenix live reload + inotifyTools + + # Process management and task running + hivemind + just + + # AI/Development tools claude-code aider-chat - + # Node.js for Phoenix assets nodejs_20 - npm - + nodePackages.npm + # Database for development postgresql ]; @@ -69,7 +76,8 @@ }; }; } - ) // { + ) + // { nixosModules.default = import ./nix/nixos-module.nix; }; }