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" %>