defmodule HereIAm.DeviceMonitor do use GenServer alias HereIAm.Devices def start_link(_opts) do GenServer.start_link(__MODULE__, %{}, name: __MODULE__) end @impl true def init(state) do schedule_check() {:ok, Map.put(state, :connected_ips, %{})} end @impl true def handle_info(:check_devices, state) do connected_ips = get_connected_ips() # Determine new devices and TTS new_devices = MapSet.difference(MapSet.new(connected_ips), MapSet.new(Map.keys(state.connected_ips))) Enum.each(new_devices, fn ip -> case Devices.get_device_by_ip(ip) do {:ok, device} -> play_message(device) {:error, _reason} -> :ok end end) # Update the registry new_registry = Map.new(connected_ips, fn ip -> {ip, :connected} end) schedule_check() {:noreply, %{state | connected_ips: new_registry}} end defp schedule_check() do Process.send_after(self(), :check_devices, 10_000) # Check every 60 seconds end defp get_connected_ips() do {output, 0} = System.cmd("sudo", ["arp-scan", "-l", "--localnet"]) parse_ips_from_arp_scan(output) end defp parse_ips_from_arp_scan(output) do output |> String.split("\n") |> Enum.filter(&String.contains?(&1, "\t")) # Filter lines containing tabs (IP-MAC pairs) |> Enum.map(&String.split(&1, "\t")) |> Enum.map(&List.first(&1)) |> Enum.filter(&valid_ip?/1) end defp valid_ip?(ip) do case :inet.parse_address(to_charlist(ip)) do {:ok, _} -> true {:error, _} -> false end end defp play_message(%{tts: tts_message}) do cond do not is_nil(tts_message) and tts_message != "" -> play_tts(tts_message) true -> IO.puts("No audio or TTS message to play.") end end defp play_tts(tts_message) do IO.puts("Playing TTS message: #{tts_message}") System.cmd("espeak", [tts_message]) end end