defmodule DaProductApp.TerminalManagement.MQTTCommandBuilder do @moduledoc """ Builder module for generating device-specific MQTT command payloads. Handles different command structures for various device models (MF919, SR600, etc.) """ require Logger @doc """ Main entry point for building MQTT command payloads. Routes to device-specific builder based on model. """ def build_command(device_model, download_params) when is_binary(device_model) do case String.downcase(device_model) do "mf919" -> build_mf919_command(download_params) "sr600" -> build_sr600_command(download_params) "kozen" -> build_kozen_command(download_params) _ -> build_sr600_command(download_params) # Default to SR600 structure end end @doc """ Builds MF919-specific MQTT payload. MF919 uses TMS command structure with specific command types. """ def build_mf919_command(params) do command_type = Map.get(params, "command_type", "UPDATE_PARAMS") request_id = Map.get(params, "request_id", generate_request_id()) download_url = Map.get(params, "url_path_from_download", "") payload = %{ "type" => "tms_command", "command" => command_type, "requestId" => request_id, "downloadUrl" => download_url } |> Enum.reject(fn {_k, v} -> is_nil(v) or v == "" end) |> Map.new() Logger.info("MF919 MQTT Command: #{inspect(payload)}") {:ok, Jason.encode!(payload)} end @doc """ Builds SR600-specific MQTT payload. SR600 uses file_download command structure with path and file metadata. """ def build_sr600_command(params) do request_id = Map.get(params, "request_id", generate_request_id()) payload = %{ "command" => "file_download", "local_path_to_save" => Map.get(params, "local_path_to_save"), "url_path_from_download" => Map.get(params, "url_path_from_download"), "file_size" => Map.get(params, "file_size"), "file_category" => Map.get(params, "file_category"), "file_name" => Map.get(params, "file_name"), "merchant_config" => Map.get(params, "merchant_config", "true"), "retry_count" => Map.get(params, "retry_count", "3"), "request_id" => request_id } |> Enum.reject(fn {_k, v} -> is_nil(v) or v == "" end) |> Map.new() Logger.info("SR600 MQTT Command: #{inspect(payload)}") {:ok, Jason.encode!(payload)} end @doc """ Builds Kozen-specific MQTT payload. Currently uses SR600 structure as baseline. """ def build_kozen_command(params) do build_sr600_command(params) end @doc """ Generates a unique request ID for MQTT commands. Format: req-{timestamp}-{random} """ def generate_request_id do timestamp = System.os_time(:second) random_id = :rand.uniform(1_000_000) |> Integer.to_string() "req-#{timestamp}-#{random_id}" end @doc """ Returns available file categories for a specific device model. """ def get_file_categories("mf919") do [ {"params", "📦 Parameters (params.zip)"}, {"l3config", "⚙️ L3 Config (l3config.zip)"}, {"keys", "🔑 Keys (keys.json)"} ] end def get_file_categories("sr600") do [ {"firmware", "🔧 Firmware"}, {"application", "📱 Application"}, {"config", "⚙️ Configuration"}, {"logo_image", "🖼️ Logo Image"} ] end def get_file_categories("kozen") do # Kozen might have its own categories - add here as needed get_file_categories("sr600") end def get_file_categories(_), do: get_file_categories("sr600") @doc """ Returns available command types for a specific device model. """ def get_command_types("mf919") do [ {"UPDATE_PARAMS", "Update Parameters"}, {"UPDATE_L3_CONFIG", "Update L3 Config"}, {"LOAD_KEYS", "Load Keys"} ] end def get_command_types("sr600") do [ {"file_download", "File Download"} ] end def get_command_types(_), do: get_command_types("sr600") @doc """ Returns the URL pattern/hint for a specific device model and file category. """ def get_url_hint("mf919", category) do case category do "params" -> "http://demo.ctrmv.com/ota/mf919/params.zip" "l3config" -> "http://demo.ctrmv.com/ota/mf919/l3config.zip" "keys" -> "http://demo.ctrmv.com/ota/mf919/keys.json" _ -> "http://demo.ctrmv.com/ota/mf919/" end end def get_url_hint("sr600", _category) do "http://demo.ctrmv.com/ota/sr600/" end def get_url_hint(_, category) do get_url_hint("sr600", category) end @doc """ Validates parameters based on device model requirements. """ def validate_device_params("mf919", params) do required = ["command_type", "url_path_from_download"] case Enum.find(required, fn field -> is_nil(params[field]) or params[field] == "" end) do nil -> {:ok, params} missing -> {:error, "Missing required field for MF919: #{missing}"} end end def validate_device_params("sr600", params) do required = ["local_path_to_save", "url_path_from_download", "file_name"] case Enum.find(required, fn field -> is_nil(params[field]) or params[field] == "" end) do nil -> {:ok, params} missing -> {:error, "Missing required field for SR600: #{missing}"} end end def validate_device_params(_model, params) do validate_device_params("sr600", params) end end