From a085aee5c8f789f9a792eecfd35e7a7cbe47f9af Mon Sep 17 00:00:00 2001 From: silentsilas Date: Wed, 17 Jul 2024 18:20:05 -0400 Subject: [PATCH] switch mp3 to tts, add genserver to app supervisor tree that periodically checks for newly connected devices and plays their associated tts message --- assets/package-lock.json | 164 ------------------ assets/package.json | 14 -- assets/tailwind.config.js | 1 - config/test.exs | 3 +- lib/here_i_am/application.ex | 1 + lib/here_i_am/device_monitor.ex | 71 ++++++++ lib/here_i_am/devices.ex | 7 + lib/here_i_am/devices/device.ex | 6 +- .../components/layouts/root.html.heex | 2 +- .../controllers/page_html/home.html.heex | 4 +- .../live/device_live/form_component.ex | 2 +- .../live/device_live/index.html.heex | 2 +- .../live/device_live/show.html.heex | 2 +- .../20240713185614_create_devices.exs | 2 +- test/here_i_am/devices_test.exs | 10 +- .../controllers/page_controller_test.exs | 2 +- test/here_i_am_web/live/device_live_test.exs | 6 +- test/support/fixtures/devices_fixtures.ex | 2 +- 18 files changed, 101 insertions(+), 200 deletions(-) delete mode 100644 assets/package-lock.json delete mode 100644 assets/package.json create mode 100644 lib/here_i_am/device_monitor.ex diff --git a/assets/package-lock.json b/assets/package-lock.json deleted file mode 100644 index b2a1e2d..0000000 --- a/assets/package-lock.json +++ /dev/null @@ -1,164 +0,0 @@ -{ - "name": "here_i_am", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "here_i_am", - "version": "1.0.0", - "license": "MIT", - "devDependencies": { - "daisyui": "^4.12.10" - } - }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/css-selector-tokenizer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz", - "integrity": "sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==", - "dev": true, - "dependencies": { - "cssesc": "^3.0.0", - "fastparse": "^1.1.2" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/culori": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/culori/-/culori-3.3.0.tgz", - "integrity": "sha512-pHJg+jbuFsCjz9iclQBqyL3B2HLCBF71BwVNujUYEvCeQMvV97R59MNK3R2+jgJ3a1fcZgI9B3vYgz8lzr/BFQ==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/daisyui": { - "version": "4.12.10", - "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.10.tgz", - "integrity": "sha512-jp1RAuzbHhGdXmn957Z2XsTZStXGHzFfF0FgIOZj3Wv9sH7OZgLfXTRZNfKVYxltGUOBsG1kbWAdF5SrqjebvA==", - "dev": true, - "dependencies": { - "css-selector-tokenizer": "^0.8", - "culori": "^3", - "picocolors": "^1", - "postcss-js": "^4" - }, - "engines": { - "node": ">=16.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/daisyui" - } - }, - "node_modules/fastparse": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", - "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", - "dev": true - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "peer": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", - "dev": true - }, - "node_modules/postcss": { - "version": "8.4.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", - "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "peer": true, - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-js": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", - "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - } - } -} diff --git a/assets/package.json b/assets/package.json deleted file mode 100644 index 2bfdf43..0000000 --- a/assets/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "here_i_am", - "version": "1.0.0", - "main": "tailwind.config.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "silentsilas", - "license": "MIT", - "description": "", - "devDependencies": { - "daisyui": "^4.12.10" - } -} diff --git a/assets/tailwind.config.js b/assets/tailwind.config.js index 7e170fa..6ec694f 100644 --- a/assets/tailwind.config.js +++ b/assets/tailwind.config.js @@ -19,7 +19,6 @@ module.exports = { }, }, plugins: [ - require('daisyui'), require("@tailwindcss/forms"), require("@tailwindcss/typography"), // Allows prefixing tailwind classes with LiveView classes to add rules diff --git a/config/test.exs b/config/test.exs index 0e15b79..0fe93ef 100644 --- a/config/test.exs +++ b/config/test.exs @@ -11,7 +11,8 @@ config :here_i_am, HereIAm.Repo, hostname: "localhost", database: "here_i_am_test#{System.get_env("MIX_TEST_PARTITION")}", pool: Ecto.Adapters.SQL.Sandbox, - pool_size: System.schedulers_online() * 2 + pool_size: System.schedulers_online() * 2, + port: 5560 # We don't run a server during test. If one is required, # you can enable the server option below. diff --git a/lib/here_i_am/application.ex b/lib/here_i_am/application.ex index a6f491a..b54f46f 100644 --- a/lib/here_i_am/application.ex +++ b/lib/here_i_am/application.ex @@ -16,6 +16,7 @@ defmodule HereIAm.Application do {Finch, name: HereIAm.Finch}, # Start a worker by calling: HereIAm.Worker.start_link(arg) # {HereIAm.Worker, arg}, + HereIAm.DeviceMonitor, # Start to serve requests, typically the last entry HereIAmWeb.Endpoint ] diff --git a/lib/here_i_am/device_monitor.ex b/lib/here_i_am/device_monitor.ex new file mode 100644 index 0000000..482e6bb --- /dev/null +++ b/lib/here_i_am/device_monitor.ex @@ -0,0 +1,71 @@ +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 diff --git a/lib/here_i_am/devices.ex b/lib/here_i_am/devices.ex index 79dd768..11c71b5 100644 --- a/lib/here_i_am/devices.ex +++ b/lib/here_i_am/devices.ex @@ -101,4 +101,11 @@ defmodule HereIAm.Devices do def change_device(%Device{} = device, attrs \\ %{}) do Device.changeset(device, attrs) end + + def get_device_by_ip(ip_address) do + case Repo.get_by(Device, ip_address: ip_address) do + nil -> {:error, :not_found} + device -> {:ok, device} + end + end end diff --git a/lib/here_i_am/devices/device.ex b/lib/here_i_am/devices/device.ex index b086449..2e17aa0 100644 --- a/lib/here_i_am/devices/device.ex +++ b/lib/here_i_am/devices/device.ex @@ -4,7 +4,7 @@ defmodule HereIAm.Devices.Device do schema "devices" do field :ip_address, :string - field :audio, :string + field :tts, :string timestamps(type: :utc_datetime) end @@ -12,7 +12,7 @@ defmodule HereIAm.Devices.Device do @doc false def changeset(device, attrs) do device - |> cast(attrs, [:ip_address, :audio]) - |> validate_required([:ip_address, :audio]) + |> cast(attrs, [:ip_address, :tts]) + |> validate_required([:ip_address, :tts]) end end diff --git a/lib/here_i_am_web/components/layouts/root.html.heex b/lib/here_i_am_web/components/layouts/root.html.heex index e0a885b..bf1eccb 100644 --- a/lib/here_i_am_web/components/layouts/root.html.heex +++ b/lib/here_i_am_web/components/layouts/root.html.heex @@ -5,7 +5,7 @@ <.live_title suffix=" ยท Phoenix Framework"> - <%= assigns[:page_title] || "HereIAm" %> + <%= assigns[:page_title] || "Here I Am" %>