Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 0 additions & 18 deletions lib/nectar.ex

This file was deleted.

19 changes: 15 additions & 4 deletions lib/nectar/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,27 @@ defmodule Nectar.Application do
@moduledoc false

use Application
alias Nectar.Gateway
alias Nectar.PIRSensor.Agent, as: PIRSensorAgent
alias Nectar.Lamp.Agent, as: LampAgent
alias Nectar.Camera.Agent, as: CameraAgent

@impl true
def start(_type, _args) do
# Gateway.start_link()
# PIRSensorAgent.start_link()
# LampAgent.start_link()
# CameraAgent.start_link()

:timer.sleep(4000)

children = [
# Starts a worker by calling: Nectar.Worker.start_link(arg)
# {Nectar.Worker, arg}
{Gateway, []},
{PIRSensorAgent, []},
{LampAgent, []},
{CameraAgent, []}
]

# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Nectar.Supervisor]
Supervisor.start_link(children, opts)
end
Expand Down
56 changes: 56 additions & 0 deletions lib/nectar/camera/agent.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
defmodule Nectar.Camera.Agent do
import Nectar.DeviceInfo
alias Nectar.Camera

@device_type :camera

def child_spec(_args) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, []},
type: :worker,
restart: :permanent,
shutdown: 500
}
end

def start_link() do
with {:ok, pid} <- Agent.start_link(fn -> Camera.new() end) do
handshake(pid)
{:ok, pid}
end
end

defp handshake(pid) do
handshake(@device_type, pid)
end

# Camera Functions
def get_snapshot(agent) do
Agent.get(agent, fn camera -> Camera.get_snapshot(camera) end)
end

def start_recording(agent, duration_seconds) when is_integer(duration_seconds) and duration_seconds > 0 do
Agent.update(agent, fn camera -> Camera.start_recording(camera, duration_seconds) end)
end

def stop_recording(agent) do
Agent.update(agent, fn camera -> Camera.stop_recording(camera) end)
end

def get_video_sequence(agent, duration_seconds) when is_integer(duration_seconds) and duration_seconds > 0 do
Agent.get(agent, fn camera -> Camera.get_video_sequence(camera, duration_seconds) end)
end

def set_motion_detected(agent, motion_detected) do
Agent.update(agent, fn camera -> Camera.set_motion_detected(camera, motion_detected) end)
end

def set_night_mode(agent, night_mode) do
Agent.update(agent, fn camera -> Camera.set_night_mode(camera, night_mode) end)
end

def set_ptz(agent, {pan, tilt, zoom}) do
Agent.update(agent, fn camera -> Camera.set_ptz(camera, {pan, tilt, zoom}) end)
end
end
101 changes: 101 additions & 0 deletions lib/nectar/camera/camera.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
defmodule Nectar.Camera do
alias Nectar.Types.GeoCoordinate
alias Nectar.DeviceInfo

@type image() :: binary
@type video() :: binary
@type motion_detected() :: boolean
@type night_mode() :: boolean
@type ptz() :: {float, float, float}
@type device_info() :: Nectar.DeviceInfo.t()

defstruct [
:continuous_stream,
:snapshot,
:video_sequence,
:motion_detected,
:night_mode,
:ptz,
:device_info
]

# Camera Initialization
def new() do
%__MODULE__{
continuous_stream: nil,
snapshot: nil,
video_sequence: nil,
motion_detected: false,
night_mode: false,
ptz: {0.0, 0.0, 1.0},
device_info: %DeviceInfo{
geo_coordinate: %GeoCoordinate{
lat: 50.09431362821687,
lng: 14.449474425948397,
alt: 0
},
current_time: :os.system_time(:second)
}
}
end

# Camera Functions
def get_snapshot(camera) do
%{camera | snapshot: generate_image_snapshot()}
end

def start_recording(camera, _duration) do
IO.puts("Starting video recording")
%{camera | continuous_stream: :recording}
end

def stop_recording(camera) do
IO.puts("Stopping video recording")
%{camera | continuous_stream: nil}
end

def get_video_sequence(_camera, duration_seconds) when is_integer(duration_seconds) and duration_seconds > 0 do
frame_rate = 30 # Number of frames per second
frame_count = duration_seconds * frame_rate

frame_data = generate_video_frames(frame_count)

{:ok, frame_data}
end

def set_motion_detected(camera, motion_detected) do
%{camera | motion_detected: motion_detected}
end

def set_night_mode(camera, night_mode) do
%{camera | night_mode: night_mode}
end

def set_ptz(camera, {pan, tilt, zoom}) do
%{camera | ptz: {pan, tilt, zoom}}
end

defp generate_image_snapshot() do
{:ok, snapshot_data} = :crypto.strong_rand_bytes(1024 * 1024) # 1 MB snapshot
snapshot_data
end

defp generate_video_frames(frame_count) do
Enum.reduce_while(1..frame_count, <<>>, fn _frame_number, acc ->
frame_data = generate_single_frame()
new_acc = <<acc::binary, frame_data::binary>>

if byte_size(new_acc) >= frame_count * byte_size(frame_data) do
{:halt, new_acc}
else
{:cont, new_acc}
end
end)
end

defp generate_single_frame() do
# You can generate image data or any other simulated video content here.
# For simplicity, we'll just generate random binary data.
<<255::size(8)>> <> <<255::size(8)>> <> <<255::size(8)>>
end
end
20 changes: 20 additions & 0 deletions lib/nectar/device_info/device_info.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,25 @@ defmodule Nectar.DeviceInfo do
@type current_time :: integer

defstruct [:geo_coordinate, :current_time]

def gateway_pid() do
case Process.whereis(:gateway) do
nil -> :error
pid when is_pid(pid) -> pid
end
end

def handshake(device_type, device_pid) when is_atom(device_type) and is_pid(device_pid) do
send_to_gateway(:register_device, %{type: device_type, pid: device_pid})
end

def send_to_gateway(message_type, message) when is_atom(message_type) do
pid = gateway_pid()
GenServer.cast(pid, {message_type, message})
end

def send_to_gateway(_, _) do
IO.puts("error: message_type must be an atom.")
end
end

110 changes: 110 additions & 0 deletions lib/nectar/gateway/gateway.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
defmodule Nectar.Gateway do
use GenServer

alias Nectar.Camera.Agent, as: CameraAgent
alias Nectar.Lamp.Agent, as: LampAgent
alias Nectar.PIRSensor.Agent, as: PIRSensorAgent

@recording_time_seconds 15
@valid_device_types [:pir_sensor, :camera, :lamp]

def start_link(opts \\ %{}) do
GenServer.start_link(__MODULE__, opts, name: :gateway)
end

def init(_) do
initial_state = %{
pir_sensor: nil,
lamp: nil,
camera: nil
}

{:ok, initial_state}
end

def get_gateway_pid() do
Process.whereis(:gateway)
end

def get_device_pid(device_type) do
gateway = get_gateway_pid()
GenServer.call(gateway, {:get_device_pid, device_type})
end

def send_message(agent_pid, message) do
GenServer.cast(__MODULE__, {:send_message, agent_pid, message})
end

def get_camera_agent_pid(state) do
state.agents[CameraAgent]
end

def get_lamp_agent_pid(state) do
state.agents[LampAgent]
end

def get_pir_sensor_agent_pid(state) do
state.agents[PIRSensorAgent]
end

def handle_pir_event(gateway_pid, lamp_pid, camera_pid) do
IO.puts("PIR sensor detected movement")

LampAgent.turn_on(lamp_pid)
CameraAgent.start_recording(camera_pid, @recording_time_seconds)

Process.send_after(gateway_pid, {:turn_off_lamp, lamp_pid}, @recording_time_seconds * 1000)
Process.send_after(gateway_pid, {:get_video_sequence, camera_pid}, @recording_time_seconds * 1000)

{:noreply, gateway_pid}
end

def handle_info({:turn_off_lamp, lamp_pid}, state) do
IO.puts("Turning off the lamp")
LampAgent.turn_off(lamp_pid)

{:noreply, state}
end

def handle_info({:get_video_sequence, camera_pid}, state) do
IO.puts("Retrieving video sequence")

case CameraAgent.get_video_sequence(camera_pid, @recording_time_seconds) do
{:ok, video_data} ->
store_video_sequence(video_data)
IO.puts("Video sequence stored in a mock storage location.")

{:error, reason} ->
IO.puts("Failed to retrieve video sequence: #{reason}")
end

{:noreply, state}
end

def handle_cast({:register_device, message = %{type: _device_type, pid: device_pid}}, state) do
if message.type in @valid_device_types && is_pid(device_pid) do
new_state = Map.put(state, message.type, device_pid)
{:noreply, new_state}
else
{:noreply, state}
end
end

def handle_call({:get_device_pid, device_type}, _from, state) do
case Map.get(state, device_type) do
nil ->
{:reply, {:error, :device_not_found}, state}
device_pid when is_pid(device_pid) ->
{:reply, {:ok, device_pid}, state}
end
end

defp store_video_sequence(video_data) do
# Actual storage logic will be implemented here.
sample = binary_part(video_data, 0, 12)
hex_strings = for <<byte::size(8) <- sample>>, do: Integer.to_string(byte, 10)
formatted_hex = "<<" <> Enum.join(hex_strings, ", ") <> "...>>"
IO.puts("Video sequence stored (Hexadecimal): #{formatted_hex}")
:ok
end
end
Loading