defmodule LocationSimulator do
@moduledoc """
This is main api of library.
To start simple call
iex> LocationSimulator.start()
Or start with your config
iex> config = LocationSimulator.default_config()
iex> config = Map.put(config, :worker, 1)
iex> LocationSimulator.start(config)
"""
require Logger
alias LocationSimulator.DynamicSupervisor, as: Sup
@app_default_config Application.compile_env(:location_simulator, :default_config)
@doc """
Start with config.
The config is a map type.
Config has info like:
```
%{
group_id: "group_id", # group id for workers, default is nil, using for stop all workers in a group.
worker: 3, # number of worker will run
event: 100, # number of GPS events will be trigger for a worker
interval: 1000, # this & random_range used for sleep between GPS events, if value is :infinity worker will run forever (you still stop by return {:stop, reason})
random_range: 0, # 0 mean no random, other positive will be used for generate extra random sleep time
callback: MyCallbackModule # your module will handle data
}
```
:id is reserved for worker's id.
:group_id if added will be used for stop all workers in a group.
If you need pass your data to callback module you can add that in the config.
The config can be change after every event.
"""
@spec start(%{:worker => non_neg_integer, optional(any) => any}) :: :ok
def start(config) when is_map(config) do
config
|> generate_worker()
|> Sup.start_simulator()
end
@doc """
Start with default config.
In this case, library just uses Logger to log data in every event.
Remember start with default has no id then we can't stop worker by id.
"""
@spec start() :: :ok
def start() do
default_config()
|> generate_worker()
|> Sup.start_simulator()
end
@doc """
Stop all workers.
For using this function, you need to start with group_id options in config.
"""
@spec stop(group_id :: any()) :: :ok
def stop(group_id) do
Registry.dispatch(LocationSimulator.Registry, group_id, fn entries ->
Logger.debug("stop all workers in group: #{inspect group_id}, number of workers: #{length(entries)}")
for {pid, id} <- entries do
send(pid, {:stop_worker, id})
Logger.debug("sent stop to worker: #{inspect id} of groupd #{inspect group_id}")
end
end)
end
@doc """
Get default config of library.
"""
@spec default_config() :: map
def default_config() do
Logger.debug("generating worker from config")
config = @app_default_config
worker =
case config[:worker] do
# default is one worker.
nil ->
1
n when is_integer(n) and n > 0 ->
n
end
event =
case config[:event] do
nil ->
1
n when is_integer(n)->
n
end
interval =
case config[:interval] do
nil ->
100
n when is_integer(n)->
n
:gpx_time ->
:gpx_time
end
random_range =
case config[:random_range] do
nil ->
10
n when is_integer(n)->
n
end
elevation =
case config[:elevation] do
nil ->
0
n when is_integer(n)->
n
end
elevation_way =
case config[:elevation_way] do
nil ->
:no_up_down
direction ->
direction
end
mod =
case config[:callback] do
nil ->
LocationSimulator.LoggerEvent
m ->
m
end
%{
worker: worker,
event: event,
interval: interval,
random_range: random_range,
direction: :random,
callback: mod,
elevation: elevation,
elevation_way: elevation_way
}
end
## private functions ##
@spec generate_worker(map) :: list
# generate worker from config with gpx file.
defp generate_worker(%{gpx_file: wildcard_path} = config) do
list_files = wildcard_path |> Path.wildcard()
if length(list_files) == 0 do
raise "No GPX file found from path: #{wildcard_path}"
end
%{worker: worker} = config
generate_worker_gpx(worker, config, list_files, list_files, [])
end
# generate worker from config with fake data.
defp generate_worker(config) do
%{worker: worker} = config
generate_worker(worker, config, [])
end
@spec generate_worker(non_neg_integer(), map, list) :: list
defp generate_worker(0, _config, workers) do
workers
end
defp generate_worker(counter, config, workers) do
generate_worker(counter-1, config, [{LocationSimulator.Worker, Map.put(config, :id, counter)} | workers])
end
@spec generate_worker_gpx(non_neg_integer(), map, list, list, list) :: list
defp generate_worker_gpx(0, _config, _current_files, _files, workers) do
workers
end
defp generate_worker_gpx(counter, config, [], files, workers) do
generate_worker_gpx(counter, config, files, files, workers)
end
defp generate_worker_gpx(counter, config, [file|rest_files], files, workers) do
config =
config
|> Map.put(:id, counter)
|> Map.put(:gpx_file, file)
generate_worker_gpx(counter-1, config, rest_files, files, [{LocationSimulator.WorkerGpx, config} | workers])
end
end