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
23 changes: 6 additions & 17 deletions lib/elevator.ex
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
defmodule Elevator do
@num_floors 4
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DxMarch was it intentional that we removed Application.compile_env(:elevator, :num_floors, 4) ?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we define most things in elevator.ex so I thought it made sense.

@resend_period_ms 50
@msg_cutoff_ms 10000
@door_open_duration_ms 1000
# ms
@resend_period 50
@msg_ts_cutoff 10000
@test_convergence_wait_time 3 * @resend_period

def num_floors do
@num_floors
Expand All @@ -14,20 +12,11 @@ defmodule Elevator do
@door_open_duration_ms
end

def resend_period do
@resend_period
def resend_period_ms do
@resend_period_ms
end

def msg_ts_cutoff do
@msg_ts_cutoff
end

def test_convergence_wait_time do
@test_convergence_wait_time
end

def time_to_serve_executable do
{:ok, path} = Application.fetch_env(:elevator, :time_to_serve_executable)
path
def msg_cutoff_ms do
@msg_cutoff_ms
end
end
7 changes: 6 additions & 1 deletion lib/elevator/cab_orders.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ defmodule Elevator.CabOrders do
GenServer.start_link(__MODULE__, arg, name: __MODULE__)
end

@impl true
@spec init(any()) :: {:ok, state_t()}
def init(_arg \\ []) do
state = %{Communicator.my_id() => %{version: 0, orders: MapSet.new()}}
Expand Down Expand Up @@ -50,18 +51,20 @@ defmodule Elevator.CabOrders do
end

# --- Handle calls ---

@impl true
def handle_call(:get_my_orders, _from, state) do
orders = state[Communicator.my_id()].orders
{:reply, orders, state}
end

@impl true
def handle_call(:get_state, _from, state) do
{:reply, state, state}
end

# --- Handle casts ---

@impl true
@spec handle_cast({:receive_state, state_t()}, state_t()) :: {:noreply, state_t()}
def handle_cast({:receive_state, other_state}, state) do
new_state =
Expand All @@ -78,6 +81,7 @@ defmodule Elevator.CabOrders do
{:noreply, new_state}
end

@impl true
@spec handle_cast({:button_press, floor_t()}, state_t()) :: {:noreply, state_t()}
def handle_cast({:button_press, floor}, state) do
new_state =
Expand All @@ -88,6 +92,7 @@ defmodule Elevator.CabOrders do
{:noreply, new_state}
end

@impl true
@spec handle_cast({:arrived_at_floor, floor_t()}, state_t()) :: {:noreply, state_t()}
def handle_cast({:arrived_at_floor, floor}, state) do
new_state =
Expand Down
30 changes: 17 additions & 13 deletions lib/elevator/communicator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ defmodule Elevator.Communicator do
@type cab_orders_t :: Elevator.Types.cab_order_map()
@type state_t :: Elevator.Types.communicator_state_map()

@type communicator_options :: [do_resend: boolean()]
@type communicator_options :: [do_resend: boolean(), do_logging: boolean()]

def start_link(arg \\ [do_resend: true]) do
def start_link(arg \\ [do_resend: true, do_logging: false]) do
GenServer.start_link(__MODULE__, arg, name: __MODULE__)
end

@impl true
def init(opts \\ [do_resend: true, do_logging: false]) do
if Keyword.get(opts, :do_resend, true) do
schedule_state_broadcast()
Expand All @@ -45,7 +46,6 @@ defmodule Elevator.Communicator do
Returns the ID of this node.
"""
@spec my_id() :: node_id_t()
# TODO: decide on this
def my_id, do: Node.self()

@doc """
Expand All @@ -60,14 +60,15 @@ defmodule Elevator.Communicator do
end

@doc """
Updates the operational key in the state map.
Updates the `operational` part of the state.
Signals to peers whether this node can serve orders.
"""
@spec update_operation_status(boolean()) :: :ok
def update_operation_status(status) do
GenServer.cast(__MODULE__, {:update_operation_status, status})
end

# Updates the timestamp when a message is recieved from a node
# Updates the timestamp when a message is received from a node
@spec update_state_map(state_t(), node_id_t(), boolean()) :: state_t()
defp update_state_map(state, from_node, operational) do
from_node_map = %{operational: operational, timestamp: Time.utc_now()}
Expand All @@ -76,13 +77,14 @@ defmodule Elevator.Communicator do

# Schedules another round of state broadcasting.
defp schedule_state_broadcast do
time_ms = Elevator.resend_period()
time_ms = Elevator.resend_period_ms()
Process.send_after(self(), :broadcast_state, time_ms)
end

@doc """
Sends the cab and hall state to all connected nodes.
"""
@impl true
def handle_info(:broadcast_state, state) do
# For periodic execution
schedule_state_broadcast()
Expand All @@ -104,31 +106,31 @@ defmodule Elevator.Communicator do
end

# Update the state map when a new node connects
@impl true
def handle_info({:nodeup, node}, state) do
{:noreply, update_state_map(state, node, true)}
end

# Delete node from state map on disconnect
@impl true
def handle_info({:nodedown, node}, state) do
{:noreply, %{state | connected_nodes: Map.delete(state.connected_nodes, node)}}
end

@impl true
def handle_info(:log_debug, state) do
Process.send_after(self(), :log_debug, 1000)
Logger.debug("My id: #{my_id()}")
others = who_can_serve() |> Enum.map(fn x -> "#{x}" end) |> Enum.join(", ")
others = who_can_serve() |> Enum.map(fn node -> "#{node}" end) |> Enum.join(", ")
Logger.debug("Others: #{others}")
{:noreply, state}
end

# --- Handle calls ---

def handle_call(:self, _, state) do
{:reply, my_id(), state}
end

@impl true
def handle_call(:who_can_serve, _from, state) do
cutoff_ms = Elevator.msg_ts_cutoff()
cutoff_ms = Elevator.msg_cutoff_ms()

communicating_nodes =
state.connected_nodes
Expand All @@ -153,6 +155,7 @@ defmodule Elevator.Communicator do
@doc """
Sends received hall and cab orders to respective modules, and updates timestamps for when the connected nodes last sent something.
"""
@impl true
@spec handle_cast(
{:state_update, node_id_t(), boolean(), hall_orders_t(), cab_orders_t()},
state_t()
Expand All @@ -165,7 +168,8 @@ defmodule Elevator.Communicator do
{:noreply, new_state}
end

@spec handle_cast({:update_operation_status, boolean()}, state_t()) :: state_t()
@impl true
@spec handle_cast({:update_operation_status, boolean()}, state_t()) :: {:noreply, state_t()}
def handle_cast({:update_operation_status, status}, state) do
{:noreply, %{state | operational: status}}
end
Expand Down
11 changes: 4 additions & 7 deletions lib/elevator/fsm/action.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ defmodule Elevator.FSM.Action do
alias Elevator.HallOrders
alias Elevator.Decision

@motor_timeout 4000
@action_interval 100
@motor_timeout_ms 4000
@action_interval_ms 100

def start_link(_arg) do
pid = spawn_link(fn -> poll_action() end)
Expand All @@ -32,7 +32,7 @@ defmodule Elevator.FSM.Action do
poll_door_timer()
check_motor_timeout()
decide_and_take_action()
Process.sleep(@action_interval)
Process.sleep(@action_interval_ms)
poll_action()
end

Expand All @@ -53,7 +53,7 @@ defmodule Elevator.FSM.Action do
false

last_floor_time ->
Time.diff(Time.utc_now(), last_floor_time, :millisecond) > @motor_timeout
Time.diff(Time.utc_now(), last_floor_time, :millisecond) > @motor_timeout_ms
end

State.set_motor_timed_out(timed_out)
Expand All @@ -70,9 +70,6 @@ defmodule Elevator.FSM.Action do

{new_direction, new_behavior} = Decision.next_action(orders, state)

# Logger.debug("Deciding on behavior from state:\n #{inspect(state)}\n Orders: #{inspect(orders)}")
# Logger.debug("Got behavior #{new_direction} and #{new_behavior}")

cond do
state.behavior == :door_open ->
CabOrders.arrived_at_floor(state.floor)
Expand Down
15 changes: 7 additions & 8 deletions lib/elevator/fsm/state.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ defmodule Elevator.FSM.State do
between_floors: true,
obstructed: false,
motor_timed_out: false,
door_open_time: Time.utc_now(),
door_open_time_ms: Time.utc_now(),
last_floor_time: nil

@type t :: %__MODULE__{
Expand All @@ -22,7 +22,7 @@ defmodule Elevator.FSM.State do
between_floors: boolean(),
obstructed: boolean(),
motor_timed_out: boolean(),
door_open_time: Time.t(),
door_open_time_ms: Time.t(),
last_floor_time: Time.t() | nil
}

Expand All @@ -48,6 +48,10 @@ defmodule Elevator.FSM.State do

def set_behavior(behavior), do: GenServer.cast(__MODULE__, {:set_behavior, behavior})

@doc """
Opens the door if the elevator is at a floor.
Does nothing if the elevator is between floors.
"""
def open_door(), do: GenServer.cast(__MODULE__, :open_door)

def set_motor_timed_out(timed_out),
Expand Down Expand Up @@ -86,18 +90,13 @@ defmodule Elevator.FSM.State do
{:noreply, %{state | behavior: behavior}, {:continue, :set_outputs}}
end

@impl true
def handle_cast({:set_door_open_time, door_open_time}, state) do
{:noreply, %{state | door_open_time: door_open_time}, {:continue, :set_outputs}}
end

@impl true
def handle_cast(:open_door, state) do
new_state =
if state.between_floors do
state
else
%{state | behavior: :door_open, door_open_time: Time.utc_now()}
%{state | behavior: :door_open, door_open_time_ms: Time.utc_now()}
end

{:noreply, new_state, {:continue, :set_outputs}}
Expand Down
21 changes: 11 additions & 10 deletions lib/elevator/hall_orders.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ defmodule Elevator.HallOrders do
@type floor :: Elevator.Types.floor()
@type hall_btn :: Elevator.Types.hall_btn()

@hall_order_refresh_period 1000
@hall_order_refresh_period_ms 1000

def start_link(arg) do
GenServer.start_link(__MODULE__, arg, name: __MODULE__)
Expand All @@ -39,14 +39,14 @@ defmodule Elevator.HallOrders do
|> Enum.map(&{&1, {0, :idle}})
|> Enum.into(%{})

Process.send_after(self(), :refresh_hall_orders, @hall_order_refresh_period)
Process.send_after(self(), :refresh_hall_orders, @hall_order_refresh_period_ms)

{:ok, state}
end

@doc """
Receiving the hall order state from another node.
Merges the states by updating the individual order status.
Receives the hall order state from another node and merges it into local state.
Each order is merged individually using the consensus algorithm in `HallOrders.Order`.
"""
@spec receive_state(hall_order_map()) :: :ok
def receive_state(other_state), do: GenServer.cast(__MODULE__, {:receive_state, other_state})
Expand Down Expand Up @@ -115,6 +115,7 @@ defmodule Elevator.HallOrders do
{:reply, confirmed_orders, order_map}
end

@impl true
def handle_call(:get_state, _, order_map) do
{:reply, order_map, order_map}
end
Expand Down Expand Up @@ -149,7 +150,7 @@ defmodule Elevator.HallOrders do

{old_order_version, old_order_state} = old_order_value

order_map =
new_order_map =
case old_order_state do
:idle ->
Map.put(order_map, key, {old_order_version + 1, {:pending, MapSet.new([Node.self()])}})
Expand All @@ -158,15 +159,15 @@ defmodule Elevator.HallOrders do
order_map
end

new_order_value = order_map[key]
new_order_value = new_order_map[key]

if old_order_value != new_order_value do
Logger.debug(fn ->
"hall_button_press floor=#{floor} button=#{direction} from=#{inspect(old_order_value)} to=#{inspect(new_order_value)}"
end)
end

{:noreply, order_map, {:continue, :hall_update_state}}
{:noreply, new_order_map, {:continue, :hall_update_state}}
end

@impl true
Expand All @@ -177,7 +178,7 @@ defmodule Elevator.HallOrders do
key = {floor, button_type}
order_value = order_map[key]

order_map =
new_order_map =
case order_value do
{order_version, {:confirmed, _}} ->
Map.put(order_map, key, {order_version + 1, :idle})
Expand All @@ -186,12 +187,12 @@ defmodule Elevator.HallOrders do
order_map
end

{:noreply, order_map}
{:noreply, new_order_map}
end

@impl true
def handle_info(:refresh_hall_orders, order_map) do
Process.send_after(self(), :refresh_hall_orders, @hall_order_refresh_period)
Process.send_after(self(), :refresh_hall_orders, @hall_order_refresh_period_ms)
{:noreply, order_map, {:continue, :hall_update_state}}
end

Expand Down
Loading
Loading