Packages
Maintaines a connection pool to the AWS IOT service in Exred flows.
Current section
Files
Jump to
Current section
Files
lib/aws_iot_client.ex
defmodule AwsIotClient do
@moduledoc """
Client based on Gen MQTT
"""
@options [
name: __MODULE__,
host: "a198unk2hf0hqn.iot.us-east-1.amazonaws.com",
port: 8883,
client: "exred 1",
transport: {:ssl, [
keyfile: "/exred_data/certs/private.pem.key",
certfile: "/exred_data/certs/certificate.pem.crt",
cacertfile: "/exred_data/certs/symantec_ca_root.pem"]}
]
@topics [
"$aws/things/rpi/shadow/get/accepted",
"$aws/things/rpi/shadow/get/rejected"
]
use GenMQTT
require Logger
# API
def start_link do
state = %{ready: false, subscribe_queue: [], subscriptions: []}
start_options = Keyword.put(@options, :name, __MODULE__)
GenMQTT.start_link(__MODULE__, state, start_options)
end
def start_link(options) do
state = %{ready: false, subscribe_queue: [], subscriptions: []}
start_options = Keyword.put(options, :name, __MODULE__)
GenMQTT.start_link(__MODULE__, state, start_options)
end
def get_state, do: GenMQTT.call(__MODULE__, :get_state)
def subscribe(topic, qos \\ 0, retain \\ false)
def subscribe(topic, qos, retain) when is_bitstring(topic), do: subscribe([topic], qos, retain)
def subscribe(topics, qos, retain) when is_list(topics) do
GenMQTT.call(__MODULE__, {:subscribe, topics, qos, retain})
end
def publish(topic \\ "$aws/things/rpi/shadow/get", payload \\ "", qos \\ 0, retain \\ false) do
Logger.debug "PUBLISH to topic: #{inspect topic}"
GenMQTT.publish(__MODULE__, topic, payload, qos, retain)
end
# Callbacks
def process_subscribe_request(req, from) do
{:subscribe, topics, qos, retain} = req
{from_pid, _from_tag} = from
topic_qos_tuples = Enum.map topics, & {&1, qos}
:ok = GenMQTT.subscribe(self(), topic_qos_tuples)
new_subscriptions = Enum.map topics, & {&1, from_pid}
end
def on_connect(%{subscribe_queue: subscribe_queue, subscriptions: subscriptions} = state) do
Logger.info "CONNECTED subscribe_queue length: #{inspect length(subscribe_queue)}, subscriptions length: #{inspect length(subscriptions)}"
new_subscriptions = Enum.reduce subscribe_queue, subscriptions, fn({req, from}, acc) ->
new = process_subscribe_request(req, from)
acc ++ new
end
{:ok, %{state| subscribe_queue: [], subscriptions: new_subscriptions, ready: true}}
end
def on_subscribe(list, state) do
list_str = list
|> Enum.map(fn({topic, qos}) -> " "<>topic end)
|> Enum.join("\n")
Logger.info "SUBSCRIBED to:\n#{list_str}"
{:ok, state}
end
def on_publish(topic, message, state) do
Logger.debug "Received msg on topic: #{inspect topic}"
# convert the topic from a list to a string
topic_str = Enum.join topic, "/"
# decode message, it comes as serialized json
payload = Poison.decode! message
# TODO: fix sequential message forwarding
# there's probably a better way to do this as this is similar to what
# the deprecated GenEvent does
Enum.each state.subscriptions, fn
({^topic_str, subscriber_pid}) ->
# received msg's topic matched one in subscriptions
send subscriber_pid, %{topic: topic, topic_str: topic_str, payload: payload}
(_) -> :skip
end
{:ok, state}
end
def handle_call(:get_state, _from, state) do
{:reply, state, state}
end
# not connected to aws, save subscribe requests in a queue
def handle_call({:subscribe, topics, qos, retain} = req, from, %{subscribe_queue: subscribe_queue, ready: false} = state) do
new_queue = [{req, from} | subscribe_queue]
{:reply, :queued, %{state| subscribe_queue: new_queue}}
end
# subscribe to the requested topics
def handle_call({:subscribe, topics, qos, retain} = req, from, %{subscriptions: subscriptions, ready: true} = state) do
# store subscriptions in state as {topic, subscriber_pid} tuples
new_subscriptions = process_subscribe_request(req, from)
new_state = %{state | subscriptions: subscriptions ++ new_subscriptions}
{:reply, :ok, new_state}
end
def child_spec(opts) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [opts]},
type: :worker,
restart: :permanent,
shutdown: 500
}
end
end