Skip to content

Instantly share code, notes, and snippets.

@Jwsonic
Created October 2, 2019 20:21
Show Gist options
  • Save Jwsonic/fe161cdeebd76612226bffe557c2740e to your computer and use it in GitHub Desktop.
Save Jwsonic/fe161cdeebd76612226bffe557c2740e to your computer and use it in GitHub Desktop.
defmodule Sutro.ServiceMode.Controller do
@moduledoc """
A Controller process that waits for various service mode messages to come in.
It also includes various helper methods for sending messages to the correct process.
"""
require Logger
require ServiceModeConstants
alias Sutro.ServiceMode.{ServiceSession, ServiceSessionManager}
@doc """
route_message routes a MQTT message to the correct service mode controller process.
It will attempt to start that process if it's not already started.
This logic probably belongs in the hypotheitcal MagicRegistry or a new router module.
It is mostly likely directly called from EventConsumer.
"""
def route_message(
%{"session" => ServiceModeConstants.service_session_prefix() <> session_id} = message
) do
Logger.metadata(session_id: session_id)
Logger.info("Routing message: #{inspect(message)}")
with {session_id, _rest} <- Integer.parse(session_id),
{:ok, pid} <- locate_process(session_id) do
send(pid, {:message, message})
else
:error -> Logger.error("Invalid session id")
end
end
@doc """
Starts a Controller process for a given service mode session.
"""
def start(session_id) do
Logger.metadata(session_id: session_id)
with %ServiceSession{} = session <-
ServiceSessionManager.get_session(session_id) || :no_session,
:ok <- register_for_messages(session_id) do
do_service_mode(session)
else
:no_session -> Logger.error("No such service mode session")
{:error, error} -> Logger.error(error)
end
end
# Retract requested handler
defp do_service_mode(
%ServiceSession{
status: ServiceModeConstants.in_progress(),
step: ServiceModeConstants.screw_requested()
} = session
) do
receive do
{:message, %{"screwRetract" => "ack"}} ->
# Ack means the screw is retracting and we should wait for it's response
update_and_continue(session, %{step: ServiceModeConstants.screw_retracting()})
after
screw_ack_timeout(session) ->
time_out_and_finish(session)
end
end
# Autoretracted handler
defp do_service_mode(
%ServiceSession{
status: ServiceModeConstants.in_progress(),
step: ServiceModeConstants.screw_autoretracted()
} = session
) do
receive do
{:message, %{"battery" => _battery}} ->
# Battery response means move to the pool_removal step
update_and_continue(session, %{step: ServiceModeConstants.pool_removal()})
after
battery_timeout(session) ->
# Autoretract + no battery response means move to the change_cartridge
update_and_continue(session, %{step: ServiceModeConstants.change_cartridge()})
end
end
##
# Other step handlers would be here...
##
# There's nothing more to be done with the session
defp do_service_mode(_session), do: :noop
# Make some changes, log any errors, then continue processing
defp update_and_continue(session, changes) do
case ServiceSessionManager.update_service_session(session, changes) do
{:ok, session} ->
# Continue with the service mode process chain
do_service_mode(session)
{:error, error} ->
Logger.error(error)
end
end
defp time_out_and_finish(session) do
case ServiceSessionManager.update_service_session(session, %{
status: ServiceModeConstants.timed_out()
}) do
{:error, error} ->
Logger.error(error)
_ ->
# We've marked the session as timed out. We won't continue down the chain
:done
end
end
# Returns the number of millseconds we need to wait before the battery has timed out
defp battery_timeout(%ServiceSession{inserted_at: inserted_at}) do
inserted_at
|> Timex.shift(seconds: ServiceModeConstants.battery_check_timeout_seconds())
|> Timex.diff(Timex.now(), :milliseconds)
|> max(0)
end
# This would have actual logic, like battery_timeout
defp screw_ack_timeout(_session), do: 10_000
# This would register the process for messages
defp register_for_messages(_session_id), do: :ok
# This looks up a registered process, or starts it if one isn't running.
# MagicRegistry to come later
defp locate_process(session_id) do
case MagicRegistry.find(session_id) do
{:ok, pid} -> {:ok, pid}
:no_pid -> MagicRegistry.start_process(session_id)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment