merge master

This commit is contained in:
anlicheng 2023-06-14 14:43:31 +08:00
parent 79e7125fb8
commit ac27280299
50 changed files with 1920 additions and 3667 deletions

View File

@ -9,256 +9,15 @@
-author("licheng5").
%%
-define(HOST_STATUS_INACTIVE, 0).
-define(HOST_STATUS_ONLINE, 1).
-define(HOST_STATUS_OFFLINE, 2).
-define(HOST_STATUS_DELETE, 3).
-define(HOST_STATUS_INACTIVE, -1). %%
-define(HOST_STATUS_OFFLINE, 0). %% 线
-define(HOST_STATUS_ONLINE, 1). %% 线
%%
-define(TERMINAL_STATUS_OFFLINE, 0).
-define(TERMINAL_STATUS_ONLINE, 1).
%%
-define(TASK_STATUS_INIT, -1). %%
-define(TASK_STATUS_FAILED, 0). %% 线
-define(TASK_STATUS_OK, 1). %% 线
%% cpu相关
-record(cpu_metric, {
%% cpu编号
num = 1,
%%
load = 0
}).
%% , kb
-record(memory_metric, {
%% 使
used = 0,
%%
free = 0,
%%
total = 0
}).
%% , kb
-record(disk_metric, {
%% 使
used = 0,
%%
free = 0,
%%
total = 0
}).
%%
-record(interface_metric, {
%%
name = <<>>,
%%
desc = <<>>,
%% , json
detail = <<>>,
%%
status = 0
}).
%%
-record(service_metric, {
%%
symbol :: binary(),
%%
name :: binary(),
%%
last_value = 0 :: number(),
%% , : av
unit :: any(),
%% , 100, : [{ts, value}]
queue = queue:new() :: queus:queue(),
%%
update_ts = 0 :: integer()
}).
%% ,
-record(host_metric, {
%% cpu相关
cpus = [],
%% cpu温度
cpu_temperature = 0,
%%
memory = #memory_metric{},
%%
disk = #disk_metric{},
%%
interfaces = []
}).
%% id生成器
-record(id_generator, {
name :: atom(),
seq = 0 :: integer()
}).
%%
-record(host, {
%% ID
host_id :: binary(),
%%
serial_number = <<>> :: binary(),
%%
name = <<>> :: binary(),
%%
model = <<>> :: binary(),
%%
cell_id :: integer(),
%% rsa公钥
public_rsa = <<>> :: binary(),
%% aes的key,
aes = <<>> :: binary(),
metric = #host_metric{},
%%
activated_ts = 0 :: integer(),
%% 线
update_ts = 0 :: integer(),
%%
status = ?HOST_STATUS_INACTIVE :: integer()
}).
%%
-record(terminal, {
%% ID
terminal_id :: integer(),
%%
host_id :: binary(),
%%
serial_number = <<>> :: binary(),
%%
name :: binary(),
%%
code :: binary(),
%%
access_protocol :: binary(),
%% ID
product_id = 0 :: integer(),
%% ID
vendor_id = 0 :: integer(),
%%
model = <<>> :: binary(),
%% ID
cell_id = 0 :: integer(),
%%
status = 0 :: integer(),
%% 线
update_ts = 0 :: integer()
}).
%%
-record(service, {
%% ID, UUID生成
service_id :: binary(),
%%
host_id :: binary(),
%%
name :: binary(),
%%
category :: binary(),
%%
apply_object :: binary(),
%%
version :: binary(),
%%
execute_count = 0,
%%
deploy_ts = 0 :: integer(),
%% ,
metrics = [] :: [#service_metric{}],
status = 0
}).
%% scenario
-record(scenario, {
%% idid
scenario_id :: integer(),
%%
name :: binary(),
%%
desc :: binary(),
%%
rule :: binary(),
%%
update_ts = 0 :: integer(),
%%
status :: integer()
}).
%%
-record(scenario_deploy, {
%% idid
deploy_id :: integer(),
%% id
scenario_id :: integer(),
%% id
host_id :: binary(),
%%
create_ts = 0 :: integer(),
%%
update_ts = 0 :: integer(),
%%
status = 0:: integer()
}).
%%
-record(issue, {
issue_id :: integer(),
%%
name :: binary(),
%%
uid :: integer(),
%%
deploy_type :: integer(),
%% id, id或者场景id
assoc_id :: any(),
%% id列表
hosts = [] :: list(),
%%
timeout = 0 :: integer(),
%%
results = [],
%%
create_ts = 0 :: integer(),
%%
status = 0 :: integer()
}).
-record(http_endpoint, {
url = <<>>
}).
-record(kafka_endpoint, {
}).
%%
-record(router, {
router_id :: integer(),
%%
name = <<>>,
%%
rule = <<>>,
%%
endpoint,
%%
status = 0
}).
%%
-define(DEVICE_HOST, 1).
-define(DEVICE_TERMINAL, 2).
%%
-record(log, {
log_id :: integer(),
%%
action_name = <<>>,
%%
device_type :: integer(),
%% ID
assoc_id :: term(),
%%
create_ts = 0 :: integer()
router_id
}).

View File

@ -0,0 +1,17 @@
%%%-------------------------------------------------------------------
%%% @author aresei
%%% @copyright (C) 2023, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 16. 5 2023 12:48
%%%-------------------------------------------------------------------
-module(device_bo).
-author("aresei").
-include("iot.hrl").
%% API
-export([get_device_by_uuid/1]).
get_device_by_uuid(UUID) when is_binary(UUID) ->
mysql_client:get_row(<<"SELECT * FROM device WHERE uuid = ? LIMIT 1">>, [UUID]).

View File

@ -0,0 +1,46 @@
%%%-------------------------------------------------------------------
%%% @author aresei
%%% @copyright (C) 2023, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 16. 5 2023 12:48
%%%-------------------------------------------------------------------
-module(host_bo).
-author("aresei").
-include("iot.hrl").
%% API
-export([get_all_hosts/0, change_status/2, is_authorized/1, get_host/1, get_host_by_uuid/1, create_host/1]).
-spec get_all_hosts() -> UUIDList :: [binary()].
get_all_hosts() ->
case mysql_client:get_all(<<"SELECT uuid FROM host where uuid != ''">>) of
{ok, Hosts} ->
lists:map(fun(#{<<"uuid">> := UUID}) -> UUID end, Hosts);
{error, _Reason} ->
[]
end.
get_host(HostId) when is_integer(HostId) ->
mysql_client:get_row(<<"SELECT * FROM host WHERE id = ? LIMIT 1">>, [HostId]).
get_host_by_uuid(UUID) when is_binary(UUID) ->
mysql_client:get_row(<<"SELECT * FROM host WHERE uuid = ? LIMIT 1">>, [UUID]).
create_host(UUID) when is_binary(UUID) ->
mysql_client:insert(<<"host">>, #{<<"UUID">> => UUID, <<"status">> => ?HOST_STATUS_INACTIVE}, true).
%%
-spec change_status(UUID :: binary(), Status :: integer()) -> {ok, AffectedRows :: integer()} | {error, Reason :: any()}.
change_status(UUID, Status) when is_binary(UUID), is_integer(Status) ->
mysql_client:update_by(<<"UPDATE host SET status = ? WHERE uuid = ? LIMIT 1">>, [Status, UUID]).
%%
is_authorized(HostId) when is_integer(HostId) ->
case mysql_client:get_row(<<"SELECT * FROM host WHERE id = ? LIMIT 1">>, [HostId]) of
{ok, #{<<"status">> := Status}} when Status =/= ?HOST_STATUS_INACTIVE ->
true;
_ ->
false
end.

View File

@ -0,0 +1,19 @@
%%%-------------------------------------------------------------------
%%% @author aresei
%%% @copyright (C) 2023, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 16. 5 2023 12:48
%%%-------------------------------------------------------------------
-module(task_logs_bo).
-author("aresei").
-include("iot.hrl").
%% API
-export([change_status/2]).
%%
-spec change_status(TaskId :: integer(), Status :: integer()) -> {ok, AffectedRow :: integer()} | {error, Reason :: any()}.
change_status(TaskId, Status) when is_integer(TaskId), is_integer(Status) ->
mysql_client:update_by(<<"UPDATE task_logs SET status = ? WHERE id = ? LIMIT 1">>, [Status, TaskId]).

View File

@ -17,131 +17,103 @@
%% helper methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
handle_request("POST", "/host/create", _, HostInfo) ->
lager:debug("[host_handler] create post params: ~p", [HostInfo]),
case convert_host_info(HostInfo) of
{ok, Host} ->
HostId = iot_util:uuid(),
case host_model:add_host(Host#host{host_id = HostId}) of
ok ->
{ok, 200, iot_util:json_data(HostId)};
{error, Reason} when is_binary(Reason) ->
{ok, 200, iot_util:json_error(-1, Reason)};
{error, Reason} when is_binary(Reason) ->
{ok, 200, iot_util:json_error(-1, <<"database error">>)}
end;
{error, Reason} ->
{ok, 200, iot_util:json_error(-1, Reason)}
end;
%%
handle_request("POST", "/host/batch_import", _, HostInfos) ->
lager:debug("[host_handler] batch_create post params: ~p", [HostInfos]),
{SuccessList, FailedList} = lists:foldl(fun(HostInfo, {SuccessList0, FailedList0}) ->
case convert_host_info(HostInfo) of
{ok, Host = #host{serial_number = SerialNumber}} ->
HostId = iot_util:uuid(),
case host_model:add_host(Host#host{host_id = HostId}) of
ok ->
SuccessItem = #{<<"serial_number">> => SerialNumber, <<"host_id">> => HostId},
{[SuccessItem|SuccessList0], FailedList0};
{error, Reason} when is_binary(Reason) ->
ErrorItem = maps:put(<<"error_message">>, Reason, HostInfo),
{SuccessList0, [ErrorItem|FailedList0]}
end;
{error, Reason} ->
ErrorItem = maps:put(<<"error_message">>, Reason, HostInfo),
{SuccessList0, [ErrorItem|FailedList0]}
end
end, {[], []}, HostInfos),
ImportResult = #{
<<"success_list">> => lists:reverse(SuccessList),
<<"failed_list">> => lists:reverse(FailedList)
},
{ok, 200, iot_util:json_data(ImportResult)};
handle_request(_, "/host/list", GetParams, PostParams) ->
Page0 = maps:get(<<"page">>, GetParams, <<"1">>),
Size0 = maps:get(<<"size">>, GetParams, <<"10">>),
Page = binary_to_integer(Page0),
Size = binary_to_integer(Size0),
true = Page > 0 andalso Size > 0,
Start = (Page - 1) * Size,
%%
Model = maps:get(<<"model">>, PostParams, <<"">>),
CellId = maps:get(<<"cell_id">>, PostParams, <<"">>),
CellId1 = case CellId =/= <<>> of
true -> binary_to_integer(CellId);
false -> 0
end,
MatchSpec = host_model:match_spec([{model, Model}, {cell, CellId1}]),
case host_model:get_hosts(MatchSpec, Start, Size) of
{ok, Hosts, TotalNum} ->
Response = #{
<<"hosts">> => lists:map(fun host_model:to_map/1, Hosts),
<<"stat">> => host_model:get_stat(),
<<"total_num">> => TotalNum
},
lager:debug("resp is: ~p", [Response]),
{ok, 200, iot_util:json_data(Response)};
{error, Reason} ->
lager:warning("[host_handler] get a error: ~p", [Reason]),
{ok, 200, iot_util:json_error(404, <<"database error">>)}
end;
handle_request("GET", "/host/detail", #{<<"id">> := HostId}, _) ->
lager:debug("[host_handler] detail id is: ~p", [HostId]),
case host_model:get_host(HostId) of
handle_request("GET", "/host/metric", #{<<"uuid">> := UUID}, _) ->
lager:debug("[host_handler] get host metric uuid is: ~p", [UUID]),
case iot_host:get_pid(UUID) of
undefined ->
{ok, 200, iot_util:json_error(404, <<"host not found">>)};
{ok, Host} ->
HostInfo = host_model:to_map(Host),
%%
{ok, Terminals0} = terminal_model:get_host_terminals(HostId),
Terminals = lists:map(fun terminal_model:to_map/1, Terminals0),
HostInfo1 = maps:put(<<"terminals">>, Terminals, HostInfo),
%%
{ok, Services0} = service_model:get_host_services(HostId),
Services = lists:map(fun service_model:to_map/1, Services0),
HostInfo2 = maps:put(<<"services">>, Services, HostInfo1),
%%
{ok, DeployList0} = scenario_deploy_model:get_host_deploy_list(HostId),
DeployList = lists:map(fun scenario_deploy_model:to_map/1, DeployList0),
%%
DeployList1 = lists:map(fun(DeployInfo = #{<<"scenario_id">> := ScenarioId}) ->
case scenario_model:get_scenario(ScenarioId) of
{ok, Scenario} ->
ScenarioInfo = scenario_model:to_map(Scenario),
DeployInfo#{<<"scenario_info">> => ScenarioInfo};
undefined ->
DeployInfo#{<<"scenario_info">> => #{}}
end
end, DeployList),
HostInfo3 = maps:put(<<"deploy_list">>, DeployList1, HostInfo2),
{ok, 200, iot_util:json_data(HostInfo3)}
Pid when is_pid(Pid) ->
{ok, MetricInfo} = iot_host:get_metric(Pid),
case map_size(MetricInfo) > 0 of
true ->
{ok, 200, iot_util:json_data(MetricInfo)};
false ->
{ok, 200, iot_util:json_error(404, <<"no metric info">>)}
end
end;
handle_request("POST", "/host/update", _, Fields0 = #{<<"host_id">> := HostId}) when is_binary(HostId) ->
lager:debug("[host_handler] post params is: ~p", [Fields0]),
%%
handle_request("POST", "/host/reload", _, #{<<"uuid">> := UUID}) when is_binary(UUID) ->
lager:debug("[host_handler] will reload host uuid: ~p", [UUID]),
case iot_host_sup:ensured_host_started(UUID) of
{ok, Pid} when is_pid(Pid) ->
lager:debug("[host_handler] already_started reload host uuid: ~p, success", [UUID]),
case iot_host:reload(Pid) of
ok ->
{ok, 200, iot_util:json_data(<<"success">>)};
{error, ReloadError} ->
lager:debug("[host_handler] reload host uuid: ~p, error: ~p", [UUID, ReloadError]),
{ok, 200, iot_util:json_error(400, <<"reload error">>)}
end;
Error ->
lager:debug("[host_handler] reload host uuid: ~p, error: ~p", [UUID, Error]),
{ok, 200, iot_util:json_error(404, <<"reload error">>)}
end;
Fields = maps:remove(<<"host_id">>, Fields0),
case host_model:update_host(HostId, Fields) of
%%
handle_request("POST", "/host/delete", _, #{<<"uuid">> := UUID}) when is_binary(UUID) ->
lager:debug("[host_handler] will delete host uuid: ~p", [UUID]),
case iot_host_sup:delete_host(UUID) of
ok ->
{ok, 200, iot_util:json_data(<<"success">>)};
{error, Reason} when is_binary(Reason) ->
{ok, 200, iot_util:json_error(-1, Reason)}
{error, Reason} ->
lager:debug("[host_handler] delete host uuid: ~p, get error is: ~p", [UUID, Reason]),
{ok, 200, iot_util:json_error(404, <<"error">>)}
end;
%%
handle_request("POST", "/host/publish_command", _,
PostParams = #{<<"uuid">> := UUID, <<"command_type">> := CommandType, <<"task_id">> := TaskId, <<"timeout">> := Timeout, <<"params">> := Params})
when is_binary(UUID), is_integer(TaskId), is_integer(Timeout), is_integer(CommandType) ->
lager:debug("[http_host_handler] publish_command body is: ~p", [PostParams]),
case iot_host:get_pid(UUID) of
undefined ->
{ok, 200, iot_util:json_error(404, <<"host not found">>)};
Pid when is_pid(Pid) ->
Reply = #{
<<"t_id">> => integer_to_binary(TaskId),
<<"t">> => Timeout,
<<"ts">> => iot_util:current_time(),
<<"m">> => Params
},
BinReply = jiffy:encode(append_service_name(PostParams, Reply), [force_utf8]),
case iot_host:build_command(Pid, CommandType, BinReply) of
{error, Reason} when is_binary(Reason) ->
task_logs_bo:change_status(TaskId, ?TASK_STATUS_FAILED),
{ok, 200, iot_util:json_error(400, Reason)};
{ok, Topic, BinCommand} ->
case iot_mqtt_publisher:publish(Topic, BinCommand, 2) of
{ok, Ref} ->
receive
{ok, Ref, _PacketId} ->
{ok, _} = task_logs_bo:change_status(TaskId, ?TASK_STATUS_OK),
{ok, 200, iot_util:json_data(<<"success">>)}
after Timeout * 1000 ->
lager:debug("[iot_host_handler] host_id uuid: ~p, publish topic success, but get ack timeout", [UUID]),
{ok, _} = task_logs_bo:change_status(TaskId, ?TASK_STATUS_FAILED),
{ok, 200, iot_util:json_error(401, <<"命令执行超时, 请重试"/utf8>>)}
end;
{error, Reason} ->
lager:debug("[iot_host] host_id uuid: ~p, publish topic get error: ~p", [UUID, Reason]),
{ok, _} = task_logs_bo:change_status(TaskId, ?TASK_STATUS_FAILED),
{ok, 200, iot_util:json_error(402, <<"发送命令到mqtt服务失败"/utf8>>)}
end
end
end;
%% ,
handle_request("POST", "/host/activate", _, #{<<"uuid">> := UUID}) when is_binary(UUID) ->
case iot_host_sup:ensured_host_started(UUID) of
{error, Reason} ->
lager:debug("[host_handler] activate host_id: ~p, failed with reason: ~p", [UUID, Reason]),
{ok, 200, iot_util:json_error(-1, <<"host not found">>)};
{ok, Pid} when is_pid(Pid) ->
lager:debug("[host_handler] reload host_id: ~p, success", [UUID]),
ok = iot_host:activate(Pid),
{ok, 200, iot_util:json_data(<<"success">>)}
end;
handle_request(_, Path, _, _) ->
@ -152,31 +124,8 @@ handle_request(_, Path, _, _) ->
%% helper methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
convert_host_info(#{<<"name">> := Name, <<"model">> := Model, <<"cell_id">> := CellId, <<"serial_number">> := SerialNumber}) ->
CheckResult = if
Name == <<>> ->
{error, <<"name is empty">>};
Model == <<>> ->
{error, <<"model is empty">>};
SerialNumber == <<>> ->
{error, <<"serial_number is empty">>};
not is_integer(CellId) orelse CellId < 0 ->
{ok, <<"cell_id is error">>};
true ->
ok
end,
case CheckResult of
ok ->
{ok, #host{
serial_number = SerialNumber,
name = Name,
model = Model,
cell_id = CellId,
status = ?HOST_STATUS_INACTIVE
}};
{error, Reason} ->
{error, Reason}
end;
convert_host_info(_) ->
{error, <<"miss required param">>}.
%% service_name参数
append_service_name(#{<<"serivce_name">> := ServiceName}, Reply) when is_binary(ServiceName), ServiceName =/= <<"">> ->
Reply#{<<"to">> => ServiceName};
append_service_name(_, Reply) ->
Reply.

View File

@ -1,160 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2023, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 06. 3 2023 14:29
%%%-------------------------------------------------------------------
-module(http_iot_handler).
-author("licheng5").
-include("iot.hrl").
%% API
-export([handle_request/4]).
%%
handle_request("POST", "/iot/send_params", _, PostParams = #{<<"host_id">> := HostId, <<"service_name">> := ServiceName, <<"params">> := Params}) ->
lager:debug("body is: ~p", [PostParams]),
case host_model:activate(HostId) of
{error, Reason} when is_binary(Reason) ->
{ok, 200, iot_util:json_error(404, Reason)};
{ok, #host{aes = Aes}} ->
Reply = #{
<<"t">> => 1,
<<"b">> => #{
<<"t_id">> => iot_util:uuid() ,
<<"to">> => ServiceName,
<<"t">> => 10,
<<"m">> => Params
}
},
EncMsg = iot_cipher_aes:encrypt(Aes, Aes, Reply),
iot_emqtt_client:publish(<<"clients.cmd.", HostId/binary>>, EncMsg, 1),
lager:debug("enc_reply is: ~p", [EncMsg]),
{ok, 200, iot_util:json_data(<<"success">>)}
end;
%%
handle_request("POST", "/iot/send_metrics", _,
PostParams = #{<<"host_id">> := HostId, <<"service_name">> := ServiceName, <<"metrics">> := Metrics}) ->
lager:debug("body is: ~p", [PostParams]),
case host_model:activate(HostId) of
{error, Reason} when is_binary(Reason) ->
{ok, 200, iot_util:json_error(404, Reason)};
{ok, #host{aes = Aes}} ->
Reply = #{
<<"t">> => 2,
<<"b">> => #{
<<"t_id">> => iot_util:uuid() ,
<<"to">> => ServiceName,
<<"t">> => 10,
<<"m">> => Metrics
}
},
EncMsg = iot_cipher_aes:encrypt(Aes, Aes, Reply),
iot_emqtt_client:publish(<<"clients.cmd.", HostId/binary>>, EncMsg, 1),
lager:debug("enc_reply is: ~p", [EncMsg]),
{ok, 200, iot_util:json_data(<<"success">>)}
end;
%%
handle_request("POST", "/iot/send_mirco_service", _,
PostParams = #{<<"host_id">> := HostId, <<"args">> := Args}) ->
lager:debug("body is: ~p", [PostParams]),
case host_model:activate(HostId) of
{error, Reason} when is_binary(Reason) ->
{ok, 200, iot_util:json_error(404, Reason)};
{ok, #host{aes = Aes}} ->
Reply = #{
<<"t">> => 3,
<<"b">> => #{
<<"t_id">> => iot_util:uuid() ,
<<"t">> => 10,
<<"m">> => Args
}
},
EncMsg = iot_cipher_aes:encrypt(Aes, Aes, Reply),
iot_emqtt_client:publish(<<"clients.cmd.", HostId/binary>>, EncMsg, 1),
lager:debug("enc_reply is: ~p", [EncMsg]),
{ok, 200, iot_util:json_data(<<"success">>)}
end;
%%
handle_request("POST", "/iot/send_data_flow", _,
PostParams = #{<<"host_id">> := HostId, <<"args">> := Args}) ->
lager:debug("body is: ~p", [PostParams]),
case host_model:activate(HostId) of
{error, Reason} when is_binary(Reason) ->
{ok, 200, iot_util:json_error(404, Reason)};
{ok, #host{aes = Aes}} ->
Reply = #{
<<"t">> => 4,
<<"b">> => #{
<<"t_id">> => iot_util:uuid() ,
<<"t">> => 10,
<<"m">> => Args
}
},
EncMsg = iot_cipher_aes:encrypt(Aes, Aes, Reply),
iot_emqtt_client:publish(<<"clients.cmd.", HostId/binary>>, EncMsg, 1),
lager:debug("enc_reply is: ~p", [EncMsg]),
{ok, 200, iot_util:json_data(<<"success">>)}
end;
%%
handle_request("POST", "/iot/send_command", _, Params = #{<<"host_id">> := HostId}) ->
lager:debug("body is: ~p", [Params]),
case host_model:activate(HostId) of
{error, Reason} when is_binary(Reason) ->
{ok, 200, iot_util:json_error(404, Reason)};
{ok, #host{aes = Aes, public_rsa = PubKey}} ->
Reply = #{
<<"a">> => true,
<<"aes">> => Aes,
<<"reply">> => <<"client.reply.", HostId/binary>>
},
EncReply = iot_cipher_rsa:encode(Reply, PubKey),
%% TODO
lager:debug("enc_reply is: ~p", [EncReply]),
{ok, 200, iot_util:json_data(<<"success">>)}
end;
%%
handle_request("POST", "/iot/host_auth", _, Params = #{<<"host_id">> := HostId}) ->
lager:debug("body is: ~p", [Params]),
case host_model:activate(HostId) of
{error, Reason} when is_binary(Reason) ->
{ok, 200, iot_util:json_error(404, Reason)};
{ok, #host{aes = Aes, public_rsa = PubKey}} ->
Reply = #{
<<"a">> => true,
<<"aes">> => Aes,
<<"reply">> => <<"client.reply.", HostId/binary>>
},
EncReply = iot_cipher_rsa:encode(Reply, PubKey),
%% TODO
lager:debug("enc_reply is: ~p", [EncReply]),
{ok, 200, iot_util:json_data(<<"success">>)}
end.

View File

@ -1,41 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2020, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 26. 4 2020 3:36
%%%-------------------------------------------------------------------
-module(http_log_handler).
-author("licheng5").
-include("iot.hrl").
%% API
-export([handle_request/4]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% helper methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
handle_request(_, "/log/list", GetParams, _PostParams) ->
Size0 = maps:get(<<"size">>, GetParams, <<"10">>),
Size = binary_to_integer(Size0),
true = Size > 0,
case log_model:get_last_logs(Size) of
{ok, Logs} ->
LogInfos = lists:map(fun log_model:to_map/1, Logs),
{ok, 200, iot_util:json_data(LogInfos)};
{error, Reason} ->
lager:warning("[host_handler] get a error: ~p", [Reason]),
{ok, 200, iot_util:json_error(-1, <<"database error">>)}
end;
handle_request(_, Path, _, _) ->
Path1 = list_to_binary(Path),
{ok, 200, iot_util:json_error(-1, <<"url: ", Path1/binary, " not found">>)}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% helper methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

View File

@ -1,64 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2020, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 26. 4 2020 3:36
%%%-------------------------------------------------------------------
-module(http_router_handler).
-author("licheng5").
-include("iot.hrl").
%% API
-export([handle_request/4]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% helper methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
handle_request(_, "/router/list", Params, PostParams) ->
Page0 = maps:get(<<"page">>, Params, <<"1">>),
Size0 = maps:get(<<"size">>, Params, <<"10">>),
Page = binary_to_integer(Page0),
Size = binary_to_integer(Size0),
true = Page > 0 andalso Size > 0,
Start = (Page - 1) * Size,
%%
Name = maps:get(<<"name">>, PostParams, <<"">>),
MatchSpec = router_model:match_spec([{name, Name}]),
case router_model:get_routers(MatchSpec, Start, Size) of
{ok, Routers, TotalNum} ->
Response = #{
<<"routers">> => lists:map(fun(R) -> router_model:to_map(R) end, Routers),
<<"total_num">> => TotalNum
},
{ok, 200, iot_util:json_data(Response)};
{error, Reason} ->
lager:warning("[host_handler] get a error: ~p", [Reason]),
{ok, 200, iot_util:json_error(404, <<"database error">>)}
end;
handle_request("GET", "/router/detail", #{<<"router_id">> := RouterId}, _) ->
case router_model:get_router(RouterId) of
undefined ->
{ok, 200, iot_util:json_error(404, <<"router not found">>)};
{ok, Router} ->
RouterInfo = router_model:to_map(Router),
{ok, 200, iot_util:json_data(RouterInfo)}
end;
handle_request("POST", "/router/changer_status", _, Params = #{<<"router_id">> := RouterId, <<"status">> := NStatus}) ->
lager:debug("[router_handler] post params is: ~p", [Params]),
case router_model:change_status(RouterId, NStatus) of
ok ->
{ok, 200, iot_util:json_data(<<"success">>)};
{error, _} ->
{ok, 200, iot_util:json_error(404, <<"error">>)}
end.

View File

@ -1,97 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2020, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 26. 4 2020 3:36
%%%-------------------------------------------------------------------
-module(http_scenario_handler).
-author("licheng5").
-include("iot.hrl").
%% API
-export([handle_request/4]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% helper methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
handle_request(_, "/scenario/list", Params, PostParams) ->
Page0 = maps:get(<<"page">>, Params, <<"1">>),
Size0 = maps:get(<<"size">>, Params, <<"10">>),
Page = binary_to_integer(Page0),
Size = binary_to_integer(Size0),
true = Page > 0 andalso Size > 0,
Start = (Page - 1) * Size,
%%
Name = maps:get(<<"name">>, PostParams, <<"">>),
MatchSpec = scenario_model:match_spec([{name, Name}]),
case scenario_model:get_scenario_list(MatchSpec, Start, Size) of
{ok, ScenarioList, TotalNum} ->
Response = #{
<<"scenarios">> => lists:map(fun scenario_model:to_map/1, ScenarioList),
<<"total_num">> => TotalNum
},
{ok, 200, iot_util:json_data(Response)};
{error, Reason} ->
lager:warning("[http_scenario_handler] get a error: ~p", [Reason]),
{ok, 200, iot_util:json_error(404, <<"database error">>)}
end;
handle_request("GET", "/scenario/detail", #{<<"id">> := ScenarioId0}, _) ->
ScenarioId = binary_to_integer(ScenarioId0),
case scenario_model:get_scenario(ScenarioId) of
undefined ->
{ok, 200, iot_util:json_error(404, <<"scenario not found">>)};
{ok, Scenario} ->
ScenarioInfo = scenario_model:to_map(Scenario),
%%
{ok, DeployList0} = scenario_deploy_model:get_scenario_deploy_list(ScenarioId),
DeployList = lists:map(fun scenario_deploy_model:to_map/1, DeployList0),
ScenarioInfo1 = maps:put(<<"deploy_list">>, ScenarioInfo, DeployList),
%%
ScenarioInfo2 = lists:map(fun(Info = #{<<"host_id">> := HostId}) ->
case host_model:get_host(HostId) of
{ok, Host} ->
HostInfo = host_model:to_map(Host),
Info#{<<"host_info">> => HostInfo};
undefined ->
Info#{<<"host_info">> => #{}}
end
end, ScenarioInfo1),
{ok, 200, iot_util:json_data(ScenarioInfo2)}
end;
handle_request("POST", "/scenario/change_status", _, #{<<"scenario_id">> := ScenarioId, <<"status">> := Status}) when is_integer(ScenarioId), is_integer(Status) ->
case scenario_model:change_status(ScenarioId, Status) of
ok ->
{ok, 200, iot_util:json_data(<<"success">>)};
{error, Reason} when is_binary(Reason) ->
lager:warning("[http_scenario_handler] change_status get a error: ~p", [Reason]),
{ok, 200, iot_util:json_error(404, Reason)}
end;
%%
handle_request("POST", "/scenario/deploy", _, #{<<"scenario_id">> := ScenarioId, <<"status">> := Status}) when is_integer(ScenarioId), is_integer(Status) ->
case scenario_model:change_status(ScenarioId, Status) of
ok ->
{ok, 200, iot_util:json_data(<<"success">>)};
{error, Reason} when is_binary(Reason) ->
lager:warning("[http_scenario_handler] change_status get a error: ~p", [Reason]),
{ok, 200, iot_util:json_error(404, Reason)}
end;
%% ??
handle_request("POST", "/scenario/change_status", _, #{<<"scenario_id">> := ScenarioId, <<"status">> := Status}) when is_integer(ScenarioId), is_integer(Status) ->
case scenario_model:change_status(ScenarioId, Status) of
ok ->
{ok, 200, iot_util:json_data(<<"success">>)};
{error, Reason} when is_binary(Reason) ->
lager:warning("[http_scenario_handler] change_status get a error: ~p", [Reason]),
{ok, 200, iot_util:json_error(404, Reason)}
end.

View File

@ -1,211 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2020, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 26. 4 2020 3:36
%%%-------------------------------------------------------------------
-module(http_terminal_handler).
-author("licheng5").
-include("iot.hrl").
%% API
-export([handle_request/4]).
%%
handle_request("POST", "/terminal/create", _, TerminalInfo) ->
lager:debug("[terminal_handler] create post params: ~p", [TerminalInfo]),
case convert_terminal_info(TerminalInfo) of
{ok, Terminal} ->
TerminalId = terminal_model:next_id(),
case terminal_model:add_terminal(Terminal#terminal{terminal_id = TerminalId}) of
ok ->
{ok, 200, iot_util:json_data(TerminalId)};
{error, Reason} when is_binary(Reason) ->
lager:warning("[host_handler] get a error: ~p", [Reason]),
{ok, 200, iot_util:json_error(-1, Reason)};
{error, Reason} when is_binary(Reason) ->
{ok, 200, iot_util:json_error(-1, <<"database error">>)}
end;
{error, Error} ->
{ok, 200, iot_util:json_error(-1, Error)}
end;
%%
handle_request("POST", "/terminal/batch_import", _, TerminalInfos) ->
lager:debug("[terminal_handler] batch_import post params: ~p", [TerminalInfos]),
{SuccessList, FailedList} = lists:foldl(fun(TerminalInfo, {SuccessList0, FailedList0}) ->
case convert_terminal_info(TerminalInfo) of
{ok, Terminal = #terminal{serial_number = SerialNumber}} ->
TerminalId = terminal_model:next_id(),
case terminal_model:add_terminal(Terminal#terminal{terminal_id = TerminalId}) of
ok ->
SuccessItem = #{<<"serial_number">> => SerialNumber, <<"terminal_id">> => TerminalId},
{[SuccessItem|SuccessList0], FailedList0};
{error, Reason} when is_binary(Reason) ->
ErrorItem = maps:put(<<"error_message">>, Reason, TerminalInfo),
{SuccessList0, [ErrorItem|FailedList0]}
end;
{error, Reason} ->
ErrorItem = maps:put(<<"error_message">>, Reason, TerminalInfo),
{SuccessList0, [ErrorItem|FailedList0]}
end
end, {[], []}, TerminalInfos),
ImportResult = #{
<<"success_list">> => lists:reverse(SuccessList),
<<"failed_list">> => lists:reverse(FailedList)
},
{ok, 200, iot_util:json_data(ImportResult)};
%%
handle_request("GET", "/terminal/stat", _, _) ->
case terminal_model:get_stat() of
{ok, {TotalNum, Stat}} ->
StatInfo = #{
<<"total_num">> => TotalNum,
<<"stat">> => lists:map(fun({Status, Num}) -> #{<<"status">> => Status, <<"num">> => Num} end, maps:to_list(Stat))
},
{ok, 200, iot_util:json_data(StatInfo)};
{error, Reason} ->
lager:warning("[host_handler] get a error: ~p", [Reason]),
{ok, 200, iot_util:json_error(404, <<"database error">>)}
end;
%%
handle_request(_, "/terminal/list", GetParams, PostParams) ->
Page0 = maps:get(<<"page">>, GetParams, <<"1">>),
Size0 = maps:get(<<"size">>, GetParams, <<"10">>),
Page = binary_to_integer(Page0),
Size = binary_to_integer(Size0),
true = Page > 0 andalso Size > 0,
Start = (Page - 1) * Size,
%%
Model = maps:get(<<"model">>, PostParams, <<"">>),
CellId = maps:get(<<"cell_id">>, PostParams, <<"">>),
CellId1 = case CellId =/= <<>> of
true -> binary_to_integer(CellId);
false -> 0
end,
case terminal_model:find_terminals([{model, Model}, {cell, CellId1}], Start, Size) of
{ok, Terminals, TotalNum} ->
Response = #{
<<"total_num">> => TotalNum,
<<"terminals">> => lists:map(fun terminal_model:to_map/1, Terminals)
},
lager:debug("resp is: ~p", [Response]),
{ok, 200, iot_util:json_data(Response)};
{error, Reason} ->
lager:warning("[host_handler] get a error: ~p", [Reason]),
{ok, 200, iot_util:json_error(404, <<"database error">>)}
end;
%%
handle_request("GET", "/terminal/detail", #{<<"terminal_id">> := TerminalId}, _) ->
lager:debug("[terminal_handler] terminal detail id is: ~p", [TerminalId]),
case terminal_model:get_terminal(TerminalId) of
undefined ->
{ok, 200, iot_util:json_error(404, <<"terminal not found">>)};
{ok, Terminal} ->
TerminalInfo = terminal_model:to_map(Terminal),
%% TODO
{ok, 200, iot_util:json_data(TerminalInfo)}
end;
%%
handle_request("POST", "/terminal/change_status", _, #{<<"terminal_id">> := TerminalId, <<"status">> := Status}) when is_integer(TerminalId), is_integer(Status) ->
lager:debug("[terminal_handler] change_status id is: ~p", [TerminalId]),
case terminal_model:change_status(TerminalId, Status) of
{error, Reason} when is_binary(Reason) ->
{ok, 200, iot_util:json_error(-1, Reason)};
ok ->
{ok, 200, iot_util:json_data(<<"success">>)}
end;
%%
handle_request("POST", "/terminal/update", _, Fields0 = #{<<"terminal_id">> := TerminalId}) ->
lager:debug("[terminal_handler] update terminal params is: ~p", [Fields0]),
Fields = maps:remove(<<"terminal_id">>, Fields0),
case terminal_model:update_terminal(TerminalId, Fields) of
ok ->
{ok, 200, iot_util:json_data(<<"success">>)};
{error, Reason} when is_binary(Reason) ->
{ok, 200, iot_util:json_error(-1, Reason)}
end;
handle_request(_, Path, _, _) ->
Path1 = list_to_binary(Path),
{ok, 200, iot_util:json_error(-1, <<"url: ", Path1/binary, " not found">>)}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% helper methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% map转换成record
convert_terminal_info(#{<<"host_id">> := HostId, <<"serial_number">> := SerialNumber, <<"name">> := Name, <<"code">> := Code, <<"access_protocol">> := AccessProtocol,
<<"product_id">> := ProductId, <<"vendor_id">> := VendorId, <<"model">> := Model, <<"cell_id">> := CellId}) ->
CheckResult = if
Name == <<>> ->
{error, <<"name is empty">>};
HostId == <<>> ->
{error, <<"host_id is empty">>};
SerialNumber == <<>> ->
{error, <<"serial_number is empty">>};
Code == <<>> ->
{error, <<"code is empty">>};
AccessProtocol == <<>> ->
{error, <<"access_protocol is empty">>};
Model == <<>> ->
{error, <<"model is empty">>};
not is_integer(ProductId) orelse ProductId < 0 ->
{error, <<"product_id is error">>};
not is_integer(VendorId) orelse VendorId < 0 ->
{error, <<"vendor_id is error">>};
not is_integer(CellId) orelse CellId < 0 ->
{error, <<"cell_id is error">>};
true ->
ok
end,
case CheckResult of
ok ->
{ok, #terminal{
%%
host_id = HostId,
%%
serial_number = SerialNumber,
%%
name = Name,
%%
code = Code,
%%
access_protocol = AccessProtocol,
%% ID
product_id = ProductId,
%% ID
vendor_id = VendorId,
%%
model = Model,
%% ID
cell_id = CellId,
%%
status = ?TERMINAL_STATUS_OFFLINE,
%% 线
update_ts = iot_util:current_time()
}};
{error, Reason} ->
{error, Reason}
end;
convert_terminal_info(_) ->
{error, <<"miss required param">>}.

View File

@ -0,0 +1,190 @@
%%%-------------------------------------------------------------------
%%% @author aresei
%%% @copyright (C) 2023, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 30. 5 2023 10:48
%%%-------------------------------------------------------------------
-module(influx_client).
-author("aresei").
-behaviour(gen_server).
%% API
-export([start_link/1, write/4, write/5, test/0]).
-export([get_precision/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
-define(INFLUX_POOl, influx_pool).
-record(state, {
host,
port,
token :: binary()
}).
%%%===================================================================
%%% API
%%%===================================================================
test() ->
UUID = <<"device123123">>,
lists:foreach(fun(Id) ->
Point = influx_point:new(<<"shui_biao">>,
[{<<"uuid">>, UUID}, {<<"service_name">>, <<"shui_biao">>}],
[{<<"cost">>, Id}],
iot_util:timestamp()),
poolboy:transaction(influx_pool, fun(Pid) ->
write(Pid, <<"iot">>, <<"iot">>, [Point])
end)
end, lists:seq(1, 100)).
%%
-spec get_precision(Timestamp :: integer()) -> binary().
get_precision(Timestamp) when is_integer(Timestamp) ->
case length(integer_to_list(Timestamp)) of
10 ->
<<"s">>;
13 ->
<<"ms">>;
16 ->
<<"u">>;
19 ->
<<"ns">>;
_ ->
<<"ms">>
end.
-spec write(Pid :: pid(), Bucket :: binary(), Org :: binary(), Points :: list()) -> no_return().
write(Pid, Bucket, Org, Points) when is_pid(Pid), is_binary(Bucket), is_binary(Org), is_list(Points) ->
write(Pid, Bucket, Org, <<"ms">>, Points).
%% Precision的值为: ms|ns|s; (ms)
-spec write(Pid :: pid(), Bucket :: binary(), Org :: binary(), Precision :: binary(), Points :: list()) -> no_return().
write(Pid, Bucket, Org, Precision, Points) when is_pid(Pid), is_binary(Bucket), is_binary(Org), is_binary(Precision), is_list(Points) ->
gen_server:cast(Pid, {write, Bucket, Org, Precision, Points}).
%% @doc Spawns the server and registers the local name (unique)
-spec(start_link(Opts :: list()) ->
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link(Opts) when is_list(Opts) ->
gen_server:start_link(?MODULE, [Opts], []).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
%% @private
%% @doc Initializes the server
-spec(init(Args :: term()) ->
{ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
{stop, Reason :: term()} | ignore).
init([InfluxProps]) ->
Token = proplists:get_value(token, InfluxProps),
Host = proplists:get_value(host, InfluxProps),
Port = proplists:get_value(port, InfluxProps),
{ok, #state{host = Host, port = Port, token = Token}}.
%% @private
%% @doc Handling call messages
-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()},
State :: #state{}) ->
{reply, Reply :: term(), NewState :: #state{}} |
{reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_call(_Request, _From, State = #state{}) ->
{reply, ok, State}.
%% @private
%% @doc Handling cast messages
-spec(handle_cast(Request :: term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_cast({write, Bucket, Org, Precision, Points}, State = #state{host = Host, port = Port, token = Token}) ->
%% headers
Headers = [
{<<"Accept">>, <<"application/json">>},
{<<"Accept-Encoding">>, <<"identity">>},
{<<"Content-Type">>, <<"text/plain; charset=utf-8">>},
{<<"Content-Encoding">>, <<"gzip">>},
{<<"Authorization">>, <<"Token ", Token/binary>>}
],
%% points
PointLines = lists:map(fun influx_point:normalized/1, Points),
Body = iolist_to_binary(lists:join(<<"\n">>, PointLines)),
%% gzip压缩
GZipBody = zlib:gzip(Body),
Query = uri_string:compose_query([{<<"bucket">>, Bucket}, {<<"org">>, Org}, {<<"precision">>, Precision}]),
Url = uri_string:normalize(#{
scheme => "http",
host => Host,
port => Port,
path => "/api/v2/write",
query => Query
}),
lager:debug("[influx_client] url is: ~p, headers: ~p, body: ~p", [Url, Headers, Body]),
case hackney:request(post, Url, Headers, GZipBody, [{pool, influx_pool}]) of
{ok, StatusCode, _RespHeaders, ClientRef} ->
case StatusCode >= 200 andalso StatusCode < 300 of
true ->
case hackney:body(ClientRef) of
{ok, RespBody} ->
lager:debug("[influx_client] status_code: ~p, response body is: ~p", [StatusCode, RespBody]);
{error, Error} ->
lager:warning("[influx_client] response error is: ~p", [Error])
end;
false ->
lager:debug("[influx_client] status_code: ~p, response body is: ~p", [StatusCode]),
hackney:close(ClientRef)
end;
{error, Reason} ->
lager:warning("[influx_client] request result is: ~p", [Reason])
end,
{noreply, State}.
%% @private
%% @doc Handling all non call/cast messages
-spec(handle_info(Info :: timeout() | term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_info(_Info, State = #state{}) ->
{noreply, State}.
%% @private
%% @doc This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
State :: #state{}) -> term()).
terminate(_Reason, _State = #state{}) ->
ok.
%% @private
%% @doc Convert process state when code is changed
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{},
Extra :: term()) ->
{ok, NewState :: #state{}} | {error, Reason :: term()}).
code_change(_OldVsn, State = #state{}, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================

View File

@ -0,0 +1,54 @@
%%%-------------------------------------------------------------------
%%% @author aresei
%%% @copyright (C) 2023, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 30. 5 2023 11:28
%%%-------------------------------------------------------------------
-module(influx_point).
-author("aresei").
-record(point, {
measurement,
tags = [],
fields = [],
time = 0 :: integer()
}).
%% API
-export([new/4, normalized/1]).
new(Measurement, Tags, Fields, Timestamp) when is_binary(Measurement), is_list(Tags); is_map(Tags), is_list(Fields); is_map(Fields), is_integer(Timestamp) ->
#point{measurement = Measurement, tags = as_list(Tags), fields = as_list(Fields), time = Timestamp}.
normalized(#point{measurement = Name, tags = Tags, fields = Fields, time = Time}) ->
NTags = lists:map(fun({N, V}) -> <<N/binary, $=, V/binary>> end, Tags),
NFields = lists:map(fun({K, V}) -> <<K/binary, $=, (field_val(V))/binary>> end, Fields),
TagItems = lists:join(<<",">>, [Name | NTags]),
FieldItems = lists:join(<<",">>, NFields),
NTime = case Time > 0 of
true -> Time;
false -> iot_util:timestamp()
end,
erlang:iolist_to_binary([TagItems, <<" ">>, FieldItems, <<" ">>, integer_to_binary(NTime)]).
field_val(V) when is_integer(V) ->
<<(integer_to_binary(V))/binary, "i">>;
field_val(V) when is_number(V) ->
<<(integer_to_binary(V))/binary, "u">>;
field_val(V) when is_binary(V) ->
<<$", V/binary, $">>;
field_val(true) ->
<<"true">>;
field_val(false) ->
<<"false">>.
as_list(L) when is_list(L) ->
L;
as_list(L) when is_map(L) ->
maps:to_list(L).

View File

@ -13,6 +13,7 @@
parse_trans,
hackney,
poolboy,
mysql,
emqtt,
mnesia,
crypto,

View File

@ -13,17 +13,16 @@
start(_StartType, _StartArgs) ->
io:setopts([{encoding, unicode}]),
%%
mnesia:start(),
Tables = mnesia:system_info(tables),
%%
lists:member(router, Tables) andalso mnesia:wait_for_tables([router], infinity),
lists:member(host, Tables) andalso mnesia:wait_for_tables([host], infinity),
% start_mnesia(),
%%
erlang:system_flag(fullsweep_after, 16),
%% http服务
start_http_server(),
%%
ok = hackney_pool:start_pool(influx_pool, [{timeout, 150000}, {max_connections, 100}]),
iot_sup:start_link().
stop(_State) ->
@ -41,19 +40,24 @@ start_http_server() ->
Dispatcher = cowboy_router:compile([
{'_', [
{"/host/[...]", http_protocol, [http_host_handler]},
{"/terminal/[...]", http_protocol, [http_terminal_handler]},
{"/scenario/[...]", http_protocol, [http_scenario_handler]},
{"/log/[...]", http_protocol, [http_log_handler]},
{"/iot/[...]", http_protocol, [http_iot_handler]},
{"/router/[...]", http_protocol, [http_router_handler]}
{"/host/[...]", http_protocol, [http_host_handler]}
]}
]),
TransOpts = [
{port, Port},
{num_acceptors, Acceptors},
{backlog, Backlog},
{max_connections, MaxConnections}
],
{ok, _} = cowboy:start_clear(http_listener, TransOpts, #{env => #{dispatch => Dispatcher}}),
lager:debug("[booking_app] the http server start at: ~p", [Port]).
{ok, Pid} = cowboy:start_clear(http_listener, TransOpts, #{env => #{dispatch => Dispatcher}}),
lager:debug("[iot_app] the http server start at: ~p, pid is: ~p", [Port, Pid]).
%start_mnesia() ->
% %%
% mnesia:start(),
% Tables = mnesia:system_info(tables),
% %%
% lists:member(router, Tables) andalso mnesia:wait_for_tables([router], infinity),
% lists:member(host, Tables) andalso mnesia:wait_for_tables([host], infinity).

View File

@ -10,14 +10,24 @@
-author("aresei").
%% API
-export([encrypt/3, decrypt/3]).
-export([encrypt/2, decrypt/2]).
-export([test/0]).
%% aes的加密算法
-spec encrypt(binary(), binary(), binary()) -> binary().
encrypt(Key, IVec, PlainText) when is_binary(Key), is_binary(IVec), is_binary(PlainText) ->
crypto:crypto_one_time(aes_128_cfb128, Key, IVec, PlainText, true).
test() ->
Aes = list_to_binary(iot_util:rand_bytes(32)),
Enc = encrypt(Aes, <<"sdfsff hesdfs sfsdfsdffffffffffxyz yes call me">>),
Data = decrypt(Aes, Enc),
lager:debug("enc: ~p, size: ~p, data len is: ~p, data: ~p", [Enc, byte_size(Enc), byte_size(Data), Data]).
%% aes的加密算法, aes_256_cbc
-spec encrypt(binary(), binary()) -> binary().
encrypt(Key, PlainText) when is_binary(Key), is_binary(PlainText), byte_size(PlainText) >= 32 ->
IV = binary:part(Key, {0, 16}),
crypto:crypto_one_time(aes_256_cbc, Key, IV, PlainText, [{encrypt, true}, {padding, pkcs_padding}]).
%% aes的解密算法
-spec decrypt(binary(), binary(), binary()) -> binary().
decrypt(Key, IVec, CipherText) when is_binary(Key), is_binary(IVec), is_binary(CipherText) ->
crypto:crypto_one_time(aes_128_cfb128, Key, IVec, CipherText, false).
-spec decrypt(binary(), binary()) -> binary().
decrypt(Key, CipherText) when is_binary(Key), is_binary(CipherText) ->
IV = binary:part(Key, {0, 16}),
crypto:crypto_one_time(aes_256_cbc, Key, IV, CipherText, [{encrypt, false}, {padding, pkcs_padding}]).

View File

@ -10,17 +10,27 @@
-author("aresei").
%% API
-export([decode/1, encode/2]).
-export([encode/2, decode/2]).
%%
decode(EncBin) when is_binary(EncBin) ->
%PrivateKey = private_key(),
%public_key:decrypt_private(EncBin, PrivateKey).
ok.
decode(Data, PrivateKey) when is_binary(Data), is_binary(PrivateKey) ->
[Pri] = public_key:pem_decode(PrivateKey),
PriKeyEntry = public_key:pem_entry_decode(Pri),
public_key:decrypt_private(Data, PriKeyEntry).
%%
encode(Data, PublicKey) when is_map(Data), is_binary(PublicKey) ->
BinData = jiffy:encode(Data, [force_utf8]),
encode(BinData, PublicKey);
encode(Data, PublicKey) when is_binary(Data), is_binary(PublicKey) ->
PubBin = <<"-----BEGIN PUBLIC KEY-----\n", PublicKey/binary, "-----END PUBLIC KEY-----">>,
PubBin = format_public_key(PublicKey),
[Pub] = public_key:pem_decode(PubBin),
PubKey = public_key:pem_entry_decode(Pub),
public_key:encrypt_public(Data, PubKey).
public_key:encrypt_public(Data, PubKey).
%% pubic_key
format_public_key(PubKey = <<"-----BEGIN PUBLIC KEY-----\n", _/binary>>) ->
PubKey;
format_public_key(PubKey) ->
<<"-----BEGIN PUBLIC KEY-----\n", PubKey/binary, "-----END PUBLIC KEY-----">>.

View File

@ -1,13 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2023, <COMPANY>
%%% @doc
%%% mqtt协议下发的命令
%%% @end
%%% Created : 17. 4 2023 19:20
%%%-------------------------------------------------------------------
-module(iot_commander).
-author("licheng5").
%% API
-export([]).

View File

@ -10,9 +10,9 @@
-author("licheng5").
%% API
-export([emqt_opts/0]).
-export([emqt_opts/1]).
emqt_opts() ->
emqt_opts(ClientSuffix) when is_binary(ClientSuffix) ->
%% emqx服务器的连接
{ok, Props} = application:get_env(iot, emqx_server),
EMQXHost = proplists:get_value(host, Props),
@ -21,7 +21,11 @@ emqt_opts() ->
Password = proplists:get_value(password, Props),
RetryInterval = proplists:get_value(retry_interval, Props, 5),
Keepalive = proplists:get_value(keepalive, Props, 86400),
Node = atom_to_binary(node()),
ClientId = <<"mqtt-client-", Node/binary, "-", ClientSuffix/binary>>,
[
{clientid, ClientId},
{host, EMQXHost},
{port, EMQXPort},
{owner, self()},
@ -32,4 +36,4 @@ emqt_opts() ->
{auto_ack, true},
{proto_ver, v5},
{retry_interval, RetryInterval}
].
].

View File

@ -2,7 +2,8 @@
%%% @author aresei
%%% @copyright (C) 2023, <COMPANY>
%%% @doc
%%%
%%% TODO
%%% 1. copy的完整名称: (modbus:12345)
%%% @end
%%% Created : 12. 3 2023 21:27
%%%-------------------------------------------------------------------
@ -12,9 +13,12 @@
-behaviour(gen_server).
%% , host上传的间隔大一些才行
-define(TICKER_INTERVAL, 5000 * 2).
%% API
-export([test/1]).
-export([start_link/2, get_name/1, get_pid/1, publish/2, publish_result/2]).
-export([start_link/2, get_name/1, get_pid/1, handle/2, reload/1, activate/1]).
-export([get_metric/1, build_command/3, downstream_topic/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
@ -22,47 +26,76 @@
-define(SERVER, ?MODULE).
-record(state, {
host :: #host{},
emqx_pid :: pid(),
%%
inflight = #{}
}).
%%
uuid :: binary(),
%%
status :: integer(),
test(Msg) when is_binary(Msg) ->
Pid = get_pid(<<"1">>),
publish(Pid, Msg).
%% rsa公钥
pub_key = <<>> :: binary(),
%% aes的key,
aes = <<>> :: binary(),
%%
metrics = #{} :: map(),
%% ping请求
is_answered = false :: boolean(),
%% ; status的值判断是否已经激活-1
is_activated :: boolean(),
%%
has_session = false :: boolean()
}).
%%%===================================================================
%%% API
%%%===================================================================
get_pid(HostId) when is_binary(HostId) ->
Name = get_name(HostId),
global:whereis_name(Name).
get_name(HostId) when is_binary(HostId) ->
binary_to_atom(<<"iot_host:", HostId/binary>>).
-spec get_pid(UUID :: binary()) -> undefined | pid().
get_pid(UUID) when is_binary(UUID) ->
Name = get_name(UUID),
whereis(Name).
-spec publish(pid(), binary()) -> {ok, Ref :: reference()} | {error, term()}.
publish(Pid, Message) when is_pid(Pid), is_binary(Message) ->
gen_server:call(Pid, {publish, self(), Message}).
-spec get_name(UUID :: binary()) -> atom().
get_name(UUID) when is_binary(UUID) ->
binary_to_atom(<<"iot_host:", UUID/binary>>).
%% publish的结果
-spec publish_result(tuple(), Timeout :: integer()) -> {ok, PacketId :: integer()} | {error, Reason :: any()}.
publish_result({error, Reason}, _) ->
{error, Reason};
publish_result({ok, Ref}, Timeout) when is_reference(Ref), is_integer(Timeout) ->
receive
{ok, Ref, PacketId} ->
{ok, PacketId}
after Timeout ->
{error, timeout}
end.
%%
-spec downstream_topic(UUID :: binary()) -> Topic :: binary().
downstream_topic(UUID) when is_binary(UUID) ->
<<"host/downstream/", UUID/binary>>.
%%
-spec handle(Pid :: pid(), Payload :: binary() | map()) -> no_return().
handle(Pid, Payload) when is_pid(Pid), is_binary(Payload); is_map(Payload) ->
gen_server:cast(Pid, {handle, Payload}).
%%
-spec reload(Pid :: pid()) -> ok | {error, Reason :: any()}.
reload(Pid) when is_pid(Pid) ->
gen_server:call(Pid, reload).
%%
-spec activate(Pid :: pid()) -> ok.
activate(Pid) when is_pid(Pid) ->
gen_server:call(Pid, activate).
-spec get_metric(Pid :: pid()) -> {ok, MetricInfo :: map()}.
get_metric(Pid) when is_pid(Pid) ->
gen_server:call(Pid, get_metric).
-spec build_command(Pid :: pid(), CommandType :: integer(), Params :: binary()) ->
{ok, Topic :: binary(), Command :: binary()} | {error, Reason :: any()}.
build_command(Pid, CommandType, Params) when is_pid(Pid), is_integer(CommandType), is_binary(Params) ->
gen_server:call(Pid, {build_command, CommandType, Params}).
%% @doc Spawns the server and registers the local name (unique)
-spec(start_link(Name :: atom(), Host :: #host{}) ->
-spec(start_link(Name :: atom(), UUID :: binary()) ->
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link(Name, Host = #host{}) ->
gen_server:start_link({global, Name}, ?MODULE, [Host], []).
start_link(Name, UUID) when is_atom(Name), is_binary(UUID) ->
gen_server:start_link({local, Name}, ?MODULE, [UUID], []).
%%%===================================================================
%%% gen_server callbacks
@ -73,23 +106,16 @@ start_link(Name, Host = #host{}) ->
-spec(init(Args :: term()) ->
{ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
{stop, Reason :: term()} | ignore).
init([Host = #host{host_id = HostId}]) ->
lager:debug("[iot_host] host_id: ~p, host is: ~p", [HostId, Host]),
init([UUID]) ->
case host_bo:get_host_by_uuid(UUID) of
{ok, #{<<"status">> := Status}} ->
%%
erlang:start_timer(?TICKER_INTERVAL, self(), ping_ticker),
Opts = iot_config:emqt_opts(),
case emqtt:start_link(Opts) of
{ok, ConnPid} ->
lager:debug("[iot_host] connect success, pid: ~p", [ConnPid]),
%% iot_host_sup的启动
erlang:start_timer(0, self(), subscribe_ticker),
{ok, #state{host = Host, emqx_pid = ConnPid}};
ignore ->
lager:debug("[iot_host] connect emqx get ignore"),
{stop, ignore};
{error, Reason} ->
lager:debug("[iot_host] connect emqx get error: ~p", [Reason]),
{stop, Reason}
{ok, #state{uuid = UUID, is_activated = (Status /= ?HOST_STATUS_INACTIVE), status = Status, has_session = false}};
undefined ->
lager:warning("[iot_host] host uuid: ~p, loaded from mysql failed", [UUID]),
ignore
end.
%% @private
@ -102,15 +128,39 @@ init([Host = #host{host_id = HostId}]) ->
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_call({publish, ReceiverPid, Message}, _From, State = #state{emqx_pid = ConnPid, inflight = InFlight, host = #host{host_id = HostId}}) ->
Topic = <<"/host/", HostId/binary, "/downstream">>,
case emqtt:publish(ConnPid, Topic, #{}, Message, [{qos, 2}, {retain, true}]) of
{ok, PacketId} ->
Ref = make_ref(),
{reply, {ok, Ref}, State#state{inflight = maps:put(PacketId, {ReceiverPid, Ref, Message}, InFlight)}};
{error, Reason} ->
{reply, {error, Reason}, State}
end.
handle_call(get_metric, _From, State = #state{metrics = Metrics}) ->
{reply, {ok, Metrics}, State};
%%
handle_call(reload, _From, State = #state{uuid = UUID}) ->
%%
case host_bo:get_host_by_uuid(UUID) of
{ok, Host = #{<<"status">> := Status}} ->
lager:debug("[iot_host] reload host uuid: ~p, successed", [Host]),
{reply, ok, State#state{is_activated = (Status /= ?HOST_STATUS_INACTIVE), status = Status}};
undefined ->
lager:debug("[iot_host] reload host uuid: ~p, failed", [UUID]),
{reply, {error, <<"host not found">>}, State}
end;
%
handle_call(activate, _From, State = #state{uuid = UUID, is_activated = IsActivated}) ->
lager:debug("[iot_host] host uuid: ~p, activated, before status is: ~p", [UUID, IsActivated]),
{reply, ok, State#state{is_activated = true}};
%%
handle_call({build_command, _, _}, _From, State = #state{has_session = false}) ->
{reply, {error, <<"会话未建立,发送命令失败"/utf8>>}, State};
handle_call({build_command, CommandType, Command}, _From, State = #state{aes = AES, uuid = UUID, has_session = true}) ->
DownstreamTopic = downstream_topic(UUID),
EncCommand = iot_cipher_aes:encrypt(AES, Command),
{reply, {ok, DownstreamTopic, <<CommandType:8, EncCommand/binary>>}, State};
handle_call(Info, _From, State = #state{}) ->
lager:debug("[iot_host] handle info: ~p", [Info]),
{reply, ok, State}.
%% @private
%% @doc Handling cast messages
@ -118,8 +168,10 @@ handle_call({publish, ReceiverPid, Message}, _From, State = #state{emqx_pid = Co
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_cast(_Request, State = #state{}) ->
{noreply, State}.
handle_cast({handle, Payload}, State) ->
%%
NState = handle_message(Payload, State),
{noreply, NState#state{is_answered = true}}.
%% @private
%% @doc Handling all non call/cast messages
@ -127,38 +179,34 @@ handle_cast(_Request, State = #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
%% topic的订阅事件
handle_info({timeout, _, subscribe_ticker}, State = #state{host = #host{host_id = HostId}, emqx_pid = ConnPid}) ->
%% host相关的全部事件
{ok, _} = emqtt:connect(ConnPid),
Topics = [
{<<"/host/", HostId/binary, "/upstream">>, 1}
],
SubscribeResult = emqtt:subscribe(ConnPid, Topics),
lager:debug("[iot_host] host_id: ~p, subscribe result is: ~p", [HostId, SubscribeResult]),
%%
handle_info({timeout, _, ping_ticker}, State = #state{uuid = UUID, is_answered = IsAnswered, status = Status}) ->
erlang:start_timer(?TICKER_INTERVAL, self(), ping_ticker),
%% : keep_status
NextStatus = if
not IsAnswered andalso Status == ?HOST_STATUS_ONLINE ->
{next_status, ?HOST_STATUS_OFFLINE};
IsAnswered andalso Status == ?HOST_STATUS_OFFLINE ->
{next_status, ?HOST_STATUS_ONLINE};
true ->
keep_status
end,
{noreply, State#state{emqx_pid = ConnPid}};
handle_info({disconnect, ReasonCode, Properties}, State = #state{host = #host{host_id = HostId}}) ->
lager:debug("[iot_host] host: ~p, Recv a DISONNECT packet - ReasonCode: ~p, Properties: ~p", [HostId, ReasonCode, Properties]),
{stop, disconnected, State};
handle_info({publish, Message = #{packet_id := _PacketId, payload := Payload}}, State = #state{emqx_pid = _ConnPid, host = #host{host_id = HostId}}) ->
lager:debug("[iot_host] host: ~p, Recv a publish packet: ~p, payload: ~p", [HostId, Message, Payload]),
{noreply, State};
handle_info({puback, Packet = #{packet_id := PacketId}}, State = #state{inflight = Inflight, host = #host{host_id = HostId}}) ->
case maps:take(PacketId, Inflight) of
{{ReceiverPid, Ref, Message}, Inflight1} ->
lager:debug("[iot_host] host: ~p, receive puback packet: ~p, assoc message: ~p", [HostId, Packet, Message]),
ReceiverPid ! {ok, Ref, PacketId},
{noreply, State#state{inflight = Inflight1}};
error ->
lager:warning("[iot_host] host: ~p, receive unknown puback packet: ~p", [HostId, Packet]),
{noreply, State}
case NextStatus of
keep_status ->
{noreply, State};
{next_status, NStatus} ->
case host_bo:change_status(UUID, NStatus) of
{ok, _} ->
{noreply, State#state{status = NStatus}};
{error, Reason} ->
lager:warning("[iot_host] change host status of uuid: ~p, error: ~p", [UUID, Reason]),
{noreply, State}
end
end;
handle_info(Info, State = #state{host = #host{host_id = HostId}}) ->
lager:debug("host_id: ~p, get info: ~p", [HostId, Info]),
handle_info(Info, State = #state{uuid = UUID}) ->
lager:warning("[iot_host] host uuid: ~p, get unknown info: ~p", [UUID, Info]),
{noreply, State}.
%% @private
@ -168,14 +216,10 @@ handle_info(Info, State = #state{host = #host{host_id = HostId}}) ->
%% with Reason. The return value is ignored.
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
State :: #state{}) -> term()).
terminate(Reason, _State = #state{emqx_pid = ConnPid}) when is_pid(ConnPid) ->
{ok, _Props, _ReasonCode} = emqtt:unsubscribe(ConnPid, #{}, <<"hello">>),
ok = emqtt:disconnect(ConnPid),
ok = emqtt:stop(ConnPid),
lager:debug("[iot_host] terminate with reason: ~p", [Reason]),
ok;
terminate(Reason, _State) ->
terminate(Reason, #state{uuid = UUID}) ->
lager:debug("[iot_host] terminate with reason: ~p", [Reason]),
ChangeResult = host_bo:change_status(UUID, ?HOST_STATUS_OFFLINE),
lager:debug("[iot_host] change host: ~p, status result is: ~p", [UUID, ChangeResult]),
ok.
%% @private
@ -188,4 +232,72 @@ code_change(_OldVsn, State = #state{}, _Extra) ->
%%%===================================================================
%%% Internal functions
%%%===================================================================
%%%===================================================================
%% topic分离了uuid
handle_message(Payload, State) when is_binary(Payload) ->
Message = catch jiffy:decode(Payload, [return_maps]),
lager:debug("[iot_host] get message: ~p", [Message]),
handle_message(Message, State);
%% create_session操作
handle_message(#{<<"method">> := <<"create_session">>, <<"params">> := #{<<"pub_key">> := PubKey}}, State = #state{is_activated = IsActivated, uuid = UUID}) ->
lager:debug("[iot_host] host_id uuid: ~p, create_session", [UUID]),
Aes = list_to_binary(iot_util:rand_bytes(32)),
Reply = case IsActivated of
true ->
#{<<"a">> => true, <<"aes">> => Aes};
false ->
#{<<"a">> => false, <<"aes">> => <<"">>}
end,
EncReply = iot_cipher_rsa:encode(Reply, PubKey),
{ok, Ref} = iot_mqtt_publisher:publish(downstream_topic(UUID), <<10:8, EncReply/binary>>, 1),
receive
{ok, Ref, PacketId} ->
lager:debug("[iot_host] host_id uuid: ~p, packet_id: ~p, publish register reply success", [UUID, PacketId]),
State#state{pub_key = PubKey, aes = Aes, has_session = true}
after 10000 ->
lager:debug("[iot_host] host_id uuid: ~p, publish register reply get error is: timeout", [UUID]),
State
end;
%% , TODO
handle_message(#{<<"method">> := <<"data">>, <<"params">> := Data}, State = #state{has_session = true, aes = AES, uuid = UUID}) ->
PlainData = iot_cipher_aes:decrypt(AES, base64:decode(Data)),
case catch jiffy:decode(PlainData, [return_maps]) of
Infos when is_list(Infos) ->
lager:debug("[iot_host] the data is: ~p", [Infos]),
insert_metrics(UUID, Infos);
_ ->
lager:debug("[iot_message_handler] the metric is invalid json")
end,
State;
%% ping
handle_message(#{<<"method">> := <<"ping">>, <<"params">> := CipherMetric}, State = #state{has_session = true, uuid = UUID, aes = AES}) ->
MetricsInfo = iot_cipher_aes:decrypt(AES, base64:decode(CipherMetric)),
Metrics = jiffy:decode(MetricsInfo, [return_maps]),
lager:debug("[iot_host] host_id uuid: ~p, get ping: ~p", [UUID, Metrics]),
State#state{metrics = Metrics};
handle_message(Message, State = #state{uuid = UUID, has_session = HasSession}) ->
lager:warning("[iot_host] host_id uuid: ~p, get a unknown message: ~p, session: ~p", [UUID, Message, HasSession]),
State.
%% , TODO , Fields里面包含了 <<"device_id">>
insert_metrics(UUID, Infos) when is_binary(UUID), is_list(Infos) ->
[insert_metrics0(UUID, Info) || Info <- Infos].
insert_metrics0(UUID, Info = #{<<"service_name">> := ServiceName, <<"fields">> := FieldsList, <<"tags">> := Tags}) when is_binary(ServiceName) ->
Timestamp = maps:get(<<"at">>, Info, iot_util:timestamp()),
NTags = Tags#{<<"uuid">> => UUID, <<"service_name">> => ServiceName},
%% TODO measurement来保存数据
[Measurement | _] = binary:split(ServiceName, <<":">>),
Points = lists:map(fun(Fields) -> influx_point:new(Measurement, NTags, Fields, Timestamp) end, FieldsList),
Precision = influx_client:get_precision(Timestamp),
poolboy:transaction(influx_pool, fun(Pid) -> influx_client:write(Pid, <<"iot">>, <<"iot">>, Precision, Points) end).

View File

@ -1,163 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author aresei
%%% @copyright (C) 2023, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 12. 3 2023 21:27
%%%-------------------------------------------------------------------
-module(iot_host_mocker).
-author("aresei").
-include("iot.hrl").
-behaviour(gen_server).
%% API
-export([start_link/1, publish/2]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
-record(state, {
host_id :: binary(),
emqx_pid :: pid()
}).
%%%===================================================================
%%% API
%%%===================================================================
-spec publish(pid(), binary()) -> {ok, PacketId :: integer()} | {error, term()}.
publish(Pid, Message) when is_pid(Pid), is_binary(Message) ->
gen_server:call(Pid, {publish, Message}).
%% @doc Spawns the server and registers the local name (unique)
-spec(start_link(HostId :: binary()) ->
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link(HostId) when is_binary(HostId) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [HostId], []).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
%% @private
%% @doc Initializes the server
-spec(init(Args :: term()) ->
{ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
{stop, Reason :: term()} | ignore).
init([HostId]) ->
%% emqx服务器的连接
Opts = iot_config:emqt_opts(),
case emqtt:start_link(Opts) of
{ok, ConnPid} ->
lager:debug("[iot_host_mocker] connect success, pid: ~p", [ConnPid]),
%% iot_host_mocker_sup的启动
erlang:start_timer(0, self(), subscribe_ticker),
{ok, #state{host_id = HostId, emqx_pid = ConnPid}};
ignore ->
lager:debug("[iot_host_mocker] connect emqx get ignore"),
{stop, ignore};
{error, Reason} ->
lager:debug("[iot_host_mocker] connect emqx get error: ~p", [Reason]),
{stop, Reason}
end.
%% @private
%% @doc Handling call messages
-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()},
State :: #state{}) ->
{reply, Reply :: term(), NewState :: #state{}} |
{reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_call({publish, Message}, _From, State = #state{emqx_pid = ConnPid, host_id = HostId}) ->
Topic = <<"/host/", HostId/binary, "/upstream">>,
Result = emqtt:publish(ConnPid, Topic, #{}, Message, [{qos, 1}]),
{reply, Result, State}.
%% @private
%% @doc Handling cast messages
-spec(handle_cast(Request :: term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_cast(_Request, State = #state{}) ->
{noreply, State}.
%% @private
%% @doc Handling all non call/cast messages
-spec(handle_info(Info :: timeout() | term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
%% topic的订阅事件
handle_info({timeout, _, subscribe_ticker}, State = #state{emqx_pid = ConnPid, host_id = HostId}) ->
%% host相关的全部事件
{ok, _} = emqtt:connect(ConnPid),
Topics = [
{<<"/host/", HostId/binary, "/downstream">>, 2}
],
SubscribeResult = emqtt:subscribe(ConnPid, Topics),
lager:debug("[iot_host_mocker] host_id: ~p, subscribe result is: ~p", [HostId, SubscribeResult]),
{noreply, State#state{emqx_pid = ConnPid}};
handle_info({disconnect, ReasonCode, Properties}, State = #state{host_id = HostId}) ->
lager:debug("[iot_host_mocker] host: ~p, Recv a DISONNECT packet - ReasonCode: ~p, Properties: ~p", [HostId, ReasonCode, Properties]),
{stop, disconnected, State};
%% qos为2的包
handle_info({publish, Message = #{packet_id := PacketId, payload := Payload, reason_code := ReasonCode, qos := 2}}, State = #state{emqx_pid = ConnPid, host_id = HostId}) ->
lager:debug("[iot_host_mocker] host: ~p, qos: 2, Recv a publish packet: ~p, payload: ~p", [HostId, Message, Payload]),
%%
% emqtt:pubrec(ConnPid, PacketId, ReasonCode),
{noreply, State};
handle_info({publish, Message = #{packet_id := PacketId, payload := Payload}}, State = #state{emqx_pid = ConnPid, host_id = HostId}) ->
lager:debug("[iot_host_mocker] host: ~p, Recv a publish packet: ~p, payload: ~p, packet_id: ~p", [HostId, Message, Payload, PacketId]),
%%
% emqtt:pubrec(ConnPid, PacketId),
{noreply, State};
handle_info({puback, #{packet_id := PacketId, reason_code := ReasonCode}}, State = #state{host_id = HostId}) ->
lager:debug("[iot_host_mocker] host: ~p, receive puback packet_id: ~p, reason_code: ~p", [HostId, PacketId, ReasonCode]),
{noreply, State};
handle_info(Info, State = #state{host_id = HostId}) ->
lager:debug("host_id: ~p, get info: ~p", [HostId, Info]),
{noreply, State}.
%% @private
%% @doc This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
State :: #state{}) -> term()).
terminate(_Reason, _State = #state{emqx_pid = ConnPid}) when is_pid(ConnPid) ->
{ok, _Props, _ReasonCode} = emqtt:unsubscribe(ConnPid, #{}, <<"hello">>),
ok = emqtt:disconnect(ConnPid),
ok = emqtt:stop(ConnPid),
ok;
terminate(Reason, _State) ->
lager:debug("[iot_host_mocker] terminate with reason: ~p", [Reason]),
ok.
%% @private
%% @doc Convert process state when code is changed
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{},
Extra :: term()) ->
{ok, NewState :: #state{}} | {error, Reason :: term()}).
code_change(_OldVsn, State = #state{}, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================

View File

@ -9,25 +9,41 @@
-behaviour(supervisor).
-export([start_link/0, init/1]).
-export([start_link/0, init/1, delete_host/1, ensured_host_started/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
Specs = case host_model:get_all_hosts() of
{ok, Hosts} ->
lists:map(fun(Host = #host{host_id = HostId}) ->
Id = iot_host:get_name(HostId),
#{id => Id,
start => {iot_host, start_link, [Id, Host]},
restart => permanent,
shutdown => 2000,
type => worker,
modules => ['iot_host']}
end, Hosts);
{error, _} ->
[]
end,
Specs = lists:map(fun child_spec/1, host_bo:get_all_hosts()),
{ok, {#{strategy => one_for_one, intensity => 1000, period => 3600}, Specs}}.
{ok, {#{strategy => one_for_one, intensity => 1000, period => 3600}, Specs}}.
-spec ensured_host_started(UUID :: binary()) -> {ok, Pid :: pid()} | {error, Reason :: any()}.
ensured_host_started(UUID) when is_binary(UUID) ->
case iot_host:get_pid(UUID) of
undefined ->
case supervisor:start_child(?MODULE, child_spec(UUID)) of
{ok, Pid} when is_pid(Pid) ->
{ok, Pid};
{error, {'already_started', Pid}} when is_pid(Pid) ->
{ok, Pid};
{error, Error} ->
{error, Error}
end;
Pid when is_pid(Pid) ->
{ok, Pid}
end.
delete_host(UUID) when is_binary(UUID) ->
Id = iot_host:get_name(UUID),
supervisor:terminate_child(?MODULE, Id).
child_spec(UUID) ->
Id = iot_host:get_name(UUID),
#{id => Id,
start => {iot_host, start_link, [Id, UUID]},
restart => permanent,
shutdown => 2000,
type => worker,
modules => ['iot_host']}.

View File

@ -1,119 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2023, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 10. 3 2023 16:44
%%%-------------------------------------------------------------------
-module(iot_issue).
-author("licheng5").
-include("iot.hrl").
-behaviour(gen_server).
%% API
-export([start_link/1]).
-export([get_pid/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
-record(state, {
issue :: #issue{},
timer_ref
}).
%%%===================================================================
%%% API
%%%===================================================================
get_pid(IssueId) when is_integer(IssueId) ->
whereis(get_name(IssueId)).
get_name(IssueId) when is_integer(IssueId) ->
list_to_atom("iot_issue:" ++ integer_to_list(IssueId)).
%% @doc Spawns the server and registers the local name (unique)
-spec(start_link(Issue :: #issue{}) ->
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link(Issue = #issue{issue_id = IssueId}) ->
Name = get_name(IssueId),
gen_server:start_link({local, Name}, ?MODULE, [Issue], []).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
%% @private
%% @doc Initializes the server
-spec(init(Args :: term()) ->
{ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
{stop, Reason :: term()} | ignore).
init([Issue = #issue{timeout = Timeout0, hosts = Hosts}]) ->
lager:debug("iot_issue started!!: ~p", [Issue]),
%% , 1
Timeout = if Timeout0 > 0 -> Timeout0; true -> 3600 end,
TimerRef = erlang:start_timer(Timeout * 1000, self(), issue_task_timeout),
%% TODO
lists:map(fun(Host) ->
iot_emqtt_client:publish(Host, x, 1)
end, Hosts),
{ok, #state{issue = Issue, timer_ref = TimerRef}}.
%% @private
%% @doc Handling call messages
-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()},
State :: #state{}) ->
{reply, Reply :: term(), NewState :: #state{}} |
{reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_call(_Request, _From, State = #state{}) ->
{reply, ok, State}.
%% @private
%% @doc Handling cast messages
-spec(handle_cast(Request :: term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_cast(_Request, State = #state{}) ->
{noreply, State}.
%% @private
%% @doc Handling all non call/cast messages
-spec(handle_info(Info :: timeout() | term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_info(_Info, State = #state{}) ->
{noreply, State}.
%% @private
%% @doc This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
State :: #state{}) -> term()).
terminate(_Reason, _State = #state{}) ->
ok.
%% @private
%% @doc Convert process state when code is changed
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{},
Extra :: term()) ->
{ok, NewState :: #state{}} | {error, Reason :: term()}).
code_change(_OldVsn, State = #state{}, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================

View File

@ -1,73 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2023, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 10. 3 2023 16:44
%%%-------------------------------------------------------------------
-module(iot_issue_sup).
-author("licheng5").
-include("iot.hrl").
-behaviour(supervisor).
%% API
-export([start_link/0, start_issue/1]).
%% Supervisor callbacks
-export([init/1]).
-define(SERVER, ?MODULE).
%%%===================================================================
%%% API functions
%%%===================================================================
%% @doc Starts the supervisor
-spec(start_link() -> {ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
%%%===================================================================
%%% Supervisor callbacks
%%%===================================================================
%% @private
%% @doc Whenever a supervisor is started using supervisor:start_link/[2,3],
%% this function is called by the new process to find out about
%% restart strategy, maximum restart frequency and child
%% specifications.
-spec(init(Args :: term()) ->
{ok, {SupFlags :: {RestartStrategy :: supervisor:strategy(),
MaxR :: non_neg_integer(), MaxT :: non_neg_integer()},
[ChildSpec :: supervisor:child_spec()]}}
| ignore | {error, Reason :: term()}).
init([]) ->
SupFlags = #{strategy => simple_one_for_one, intensity => 1000, period => 3600},
AChild = #{
id => 'iot_issue',
start => {'iot_issue', start_link, []},
restart => temporary,
shutdown => 2000,
type => worker,
modules => ['iot_issue']
},
{ok, {SupFlags, [AChild]}}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
%%
-spec start_issue(Issue :: #issue{}) -> {ok, Pid :: pid()} | {error, Reason :: any()}.
start_issue(Issue = #issue{}) ->
case supervisor:start_child(?MODULE, [Issue]) of
{ok, Pid} ->
{ok, Pid};
{error, {'already_started', Pid}} ->
{ok, Pid};
Error ->
Error
end.

View File

@ -24,77 +24,13 @@ init_database() ->
ok = mnesia:start(),
%%
%% id生成表
mnesia:create_table(id_generator, [
{attributes, record_info(fields, id_generator)},
{record_name, id_generator},
{disc_copies, [node()]},
{type, ordered_set}
]),
%%
mnesia:create_table(host, [
{attributes, record_info(fields, host)},
{record_name, host},
{disc_copies, [node()]},
{type, ordered_set}
]),
%%
mnesia:create_table(terminal, [
{attributes, record_info(fields, terminal)},
{record_name, terminal},
{disc_copies, [node()]},
{type, ordered_set}
]),
%%
mnesia:create_table(service, [
{attributes, record_info(fields, service)},
{record_name, service},
{disc_copies, [node()]},
{type, ordered_set}
]),
%%
mnesia:create_table(router, [
{attributes, record_info(fields, router)},
{record_name, router},
{disc_copies, [node()]},
{type, ordered_set}
]),
%%
mnesia:create_table(scenario, [
{attributes, record_info(fields, scenario)},
{record_name, scenario},
{disc_copies, [node()]},
{type, ordered_set}
]),
%%
mnesia:create_table(scenario_deploy, [
{attributes, record_info(fields, scenario_deploy)},
{record_name, scenario_deploy},
{disc_copies, [node()]},
{type, ordered_set}
]),
%%
mnesia:create_table(issue, [
{attributes, record_info(fields, issue)},
{record_name, issue},
{disc_copies, [node()]},
{type, ordered_set}
]),
%%
mnesia:create_table(log, [
{attributes, record_info(fields, log)},
{record_name, log},
{disc_copies, [node()]},
{type, ordered_set}
]),
%mnesia:create_table(host, [
% {attributes, record_info(fields, host)},
% {record_name, host},
% {disc_copies, [node()]},
% {type, ordered_set}
%]),
ok.
@ -114,11 +50,5 @@ copy_database(MasterNode) when is_atom(MasterNode) ->
mnesia:change_table_copy_type(schema, node(), disc_copies),
%%
mnesia:add_table_copy(host, node(), ram_copies),
mnesia:add_table_copy(terminal, node(), ram_copies),
mnesia:add_table_copy(service, node(), ram_copies),
mnesia:add_table_copy(scenario, node(), ram_copies),
mnesia:add_table_copy(scenario_deploy, node(), ram_copies),
mnesia:add_table_copy(issue, node(), ram_copies),
mnesia:add_table_copy(log, node(), ram_copies),
% mnesia:add_table_copy(host, node(), ram_copies),
ok.

View File

@ -1,157 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2023, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 14. 2 2023 20:32
%%%-------------------------------------------------------------------
-module(iot_mock).
-author("licheng5").
-include("iot.hrl").
%% API
-export([insert_hosts/0, insert_services/1, insert_terminals/1, insert_routers/0, insert_logs/0]).
-export([start_router/1]).
-export([rsa_encode/1]).
-export([start_issue/0]).
start_issue() ->
iot_issue_sup:start_issue(#issue{
issue_id = 1,
name = <<"issue 1">>,
uid = 1234,
deploy_type = 1,
assoc_id = 1,
hosts = [<<"host1">>, <<"host2">>],
timeout = 6,
create_ts = 0,
status = 0
}).
insert_hosts() ->
lists:foreach(fun(Id0) ->
%Id0 = iot_util:rand_bytes(16),
Host = #host{
host_id = integer_to_binary(Id0),
name = <<"N1000_0001">>,
model = <<"N1000">>,
cell_id = rand:uniform(100),
status = 1
},
host_model:add_host(Host)
end, lists:seq(1, 1)).
insert_logs() ->
lists:foreach(fun(Id0) ->
Log = #log{
log_id = Id0,
device_type = ?DEVICE_HOST,
action_name = <<"主机上线"/utf8>>,
assoc_id = <<"1">>,
create_ts = Id0 + 123456
},
log_model:add_log(Log)
end, lists:seq(1, 500000)).
insert_services(HostId) ->
lists:foreach(fun(Id0) ->
Q0 = queue:new(),
Q = queue:in({1234, 21}, Q0),
Service = #service{
service_id = integer_to_binary(Id0),
host_id = HostId,
name = <<"Service_0001">>,
category = <<"电器"/utf8>>,
%%
apply_object = <<"暂时不清楚"/utf8>>,
%%
version = <<"v1.1">>,
%%
execute_count = 10,
%%
deploy_ts = 0,
metrics = [#service_metric{symbol = <<"X10">>, name = <<"温度"/utf8>>, last_value = 10, unit = <<"E">>, queue = Q}],
status = 0
},
service_model:add_service(Service)
end, lists:seq(1, 100)).
insert_terminals(HostId) ->
lists:foreach(fun(Id0) ->
Terminal = #terminal{
terminal_id = integer_to_binary(Id0),
host_id = HostId,
name = <<"Service_0001">>,
code = <<"1234">>,
access_protocol = <<"TCP/IP">>,
product_id = 12,
vendor_id = 13,
model = <<"1345">>,
cell_id = 12,
status = 0
},
terminal_model:add_terminal(Terminal)
end, lists:seq(1, 100)).
insert_routers() ->
lists:foreach(fun(Id0) ->
R = #router{
router_id = Id0,
name = <<"计费电表"/utf8>>,
rule = <<"测试规则"/utf8>>,
endpoint = #http_endpoint{url = <<"http://127.0.0.1:8080/data">>},
status = 1
},
router_model:add_router(R)
end, lists:seq(1, 100)).
start_router(Id0) when is_integer(Id0) ->
R = #router{
router_id = Id0,
name = <<"计费电表"/utf8>>,
rule = <<"测试规则"/utf8>>,
endpoint = #http_endpoint{url = <<"http://127.0.0.1:8080/data">>},
status = 1
},
router_model:add_router(R),
iot_router_sup:start_new_router(R).
rsa_encode(Data) when is_binary(Data) ->
%%
PublicPemFile = "/tmp/keys/public.pem",
%%
{ok, PubBin} = file:read_file(PublicPemFile),
lager:debug("pub bin is: ~p", [PubBin]),
[Pub] = public_key:pem_decode(PubBin),
lager:debug("pub pem bin is: ~p", [Pub]),
PubKey = public_key:pem_entry_decode(Pub),
lager:debug("the public key is: ~p", [PubKey]),
EncData = public_key:encrypt_public(Data, PubKey),
lager:debug("enc data is: ~p", [EncData]),
rsa_decode(EncData),
ok.
rsa_decode(EncData) when is_binary(EncData) ->
%%
PublicPemFile = "/tmp/keys/pri.pem",
%%
{ok, PubBin} = file:read_file(PublicPemFile),
lager:debug("pub bin is: ~p", [PubBin]),
[Pub] = public_key:pem_decode(PubBin),
lager:debug("pub pem bin is: ~p", [Pub]),
PubKey = public_key:pem_entry_decode(Pub),
lager:debug("the public key is: ~p", [PubKey]),
PlainData = public_key:decrypt_private(EncData, PubKey),
lager:debug("plain data is: ~p", [PlainData]),
ok.

View File

@ -1,170 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author aresei
%%% @copyright (C) 2023, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 18. 2 2023 21:39
%%%-------------------------------------------------------------------
-module(iot_mqtt_message_handler).
-author("aresei").
-include("iot.hrl").
%% API
-export([handle/2]).
handle(<<"server.register">>, Msg = #{<<"c_id">> := ClientId, <<"r">> := PubKey,
<<"m">> := #{<<"cpu_core">> := CpuCore, <<"memory">> := Memory, <<"disk">> := Disk, <<"boot_time">> := BootTime, <<"efka_version">> := EfkaVersion, <<"kernel_arch">> := KernelArch, <<"ipv4_1">> := Ip1, <<"ipv4_2">> := Ip2}}) ->
lager:debug("[iot_message_handler] get server register message: ~p", [Msg]),
Aes = iot_util:rand_bytes(16),
Host = #host{
host_id = ClientId,
aes = Aes,
metric = #host_metric{
%% cpu相关
cpus = [
#cpu_metric{
%% cpu编号
num = 1,
%%
load = 0
},
#cpu_metric{
%% cpu编号
num = 2,
%%
load = 0
}
],
%% cpu温度
cpu_temperature = 0,
%%
memory = #memory_metric{
%% 使
used = 0,
%%
total = Memory
},
%%
disk = #disk_metric{
total = Disk
},
%%
interfaces = []
},
activated_ts = iot_util:current_time(),
update_ts = iot_util:current_time(),
status = ?HOST_STATUS_INACTIVE
},
case host_model:add_host(Host) of
ok ->
Reply = #{
<<"a">> => true,
<<"aes">> => Aes,
<<"reply">> => <<"client.reply.", ClientId/binary>>
},
EncReply = iot_cipher_rsa:encode(Reply, PubKey),
lager:debug("enc_reply is: ~p", [EncReply]);
{error, Reason} ->
lager:debug("register error is: ~p", [Reason]),
Reply = #{
<<"a">> => false,
<<"aes">> => <<"">>,
<<"reply">> => <<"client.reply.", ClientId/binary>>
},
EncReply = iot_cipher_rsa:encode(Reply, PubKey),
lager:debug("enc_reply is: ~p", [EncReply])
end;
handle(<<"server.data">>, #{<<"c_id">> := HostId, <<"d">> := Data}) ->
case host_model:get_host(HostId) of
{ok, #host{aes = Aes}} ->
Services = service_model:get_host_services(HostId),
PlainData = iot_cipher_aes:decrypt(Aes, Aes, Data),
case jiffy:decode(PlainData, [return_maps]) of
Infos when is_list(Infos) ->
lager:debug("[iot_message_handler] the data is: ~p", [Infos]),
%%
lists:foreach(fun(#{<<"service_name">> := ServiceName, <<"data">> := Items}) ->
case lists:search(fun(#service{name = Name}) -> Name =:= ServiceName end, Services) of
{value, #service{service_id = ServiceId, metrics = Metrics}} ->
%%
NMetrics = lists:foldl(fun(MetricData, MetricsAcc) -> append_metric(MetricsAcc, MetricData) end, Metrics, Items),
case service_model:update_metric(ServiceId, NMetrics) of
ok ->
lager:debug("[iot_message_handler] update metrics success");
{ok, Reason} ->
lager:debug("[iot_message_handler] update metrics error: ~p", [Reason])
end;
false ->
lager:warning("[iot_message_handler] host_id: ~p, not found service_name: ~p", [HostId, ServiceName])
end
end, Infos);
_ ->
lager:debug("[iot_message_handler] the metric is invalid json")
end;
undefined ->
lager:warning("[iot_message_handler] host_id: ~p, not exists", [HostId])
end;
%% ping
handle(<<"server.ping">>, #{<<"c">> := HostId, <<"at">> := At,
<<"h">> := #{<<"fm">> := FreeMemory, <<"fd">> := FreeDisk, <<"cp">> := CpuLoad}}) ->
case host_model:get_host(HostId) of
{ok, Host=#host{metric = Metric = #host_metric{disk = Disk, memory = Memory}}} ->
NMetric = Metric#host_metric{
disk = Disk#disk_metric{free = FreeDisk},
memory = Memory#memory_metric{free = FreeMemory}
},
case mnesia:transaction(fun() -> mnesia:write(host, Host#host{metric = NMetric}, write) end) of
{atomic, ok} ->
ok;
{error, Reason} ->
lager:warning("[iot_message_handler] host_id: ~p, ping get error: ~p", [HostId, Reason])
end;
undefined ->
lager:warning("[iot_message_handler] host_id: ~p, not exists", [HostId])
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% helper methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
append_metric(Metrics, MetricData) when is_map(MetricData) ->
Name = maps:get(<<"name">>, MetricData, <<"">>),
Symbol = maps:get(<<"symbol">>, MetricData, <<"">>),
Value = maps:get(<<"value">>, MetricData),
Ts = maps:get(<<"time">>, MetricData, 0),
case lists:any(fun(#service_metric{symbol = Symbol0}) -> Symbol =:= Symbol0 end, Metrics) of
true ->
lists:map(fun(Metric=#service_metric{symbol = Symbol0, queue = Q}) ->
case Symbol =:= Symbol0 of
true ->
Q1 = iot_util:queue_limited_in({Ts, Value}, Q, 100),
Metric#service_metric{
name = Name,
last_value = Value,
update_ts = Ts,
queue = Q1
};
false ->
Metric
end
end, Metrics);
false ->
Q0 = queue:new(),
Metric = #service_metric{
name = Name,
symbol = Symbol,
last_value = Value,
update_ts = Ts,
queue = queue:in({Ts, Value}, Q0)
},
Metrics ++ [Metric]
end.

View File

@ -13,7 +13,7 @@
-behaviour(gen_server).
%% API
-export([start_link/0, publish/3, publish_result/2]).
-export([start_link/0, publish/3]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
@ -33,18 +33,6 @@
publish(Topic, Message, Qos) when is_binary(Topic), is_binary(Message), is_integer(Qos) ->
gen_server:call(?MODULE, {publish, self(), Topic, Message, Qos}).
%% publish的结果
-spec publish_result(tuple(), Timeout :: integer()) -> {ok, PacketId :: integer()} | {error, Reason :: any()}.
publish_result({error, Reason}, _) ->
{error, Reason};
publish_result({ok, Ref}, Timeout) when is_reference(Ref), is_integer(Timeout) ->
receive
{ok, Ref, PacketId} ->
{ok, PacketId}
after Timeout ->
{error, timeout}
end.
%% @doc Spawns the server and registers the local name (unique)
-spec(start_link() ->
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
@ -62,11 +50,11 @@ start_link() ->
{stop, Reason :: term()} | ignore).
init([]) ->
%% emqx服务器的连接
Opts = iot_config:emqt_opts(),
Opts = iot_config:emqt_opts(<<"publisher">>),
case emqtt:start_link(Opts) of
{ok, ConnPid} ->
lager:debug("[iot_mqtt_publisher] connect success, pid: ~p", [ConnPid]),
% {ok, _} = emqtt:connect(ConnPid),
{ok, _} = emqtt:connect(ConnPid),
{ok, #state{conn_pid = ConnPid}};
ignore ->
lager:debug("[iot_mqtt_publisher] connect emqx get ignore"),
@ -87,8 +75,8 @@ init([]) ->
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_call({publish, ReceiverPid, Topic, Message, Qos}, _From, State = #state{conn_pid = ConnPid, inflight = InFlight}) ->
lager:debug("call me publish"),
case emqtt:publish(ConnPid, Topic, #{}, Message, [{qos, Qos}, {retain, true}]) of
%% [{qos, Qos}, {retain, true}]
case emqtt:publish(ConnPid, Topic, #{}, Message, [{qos, Qos}]) of
{ok, PacketId} ->
Ref = make_ref(),
{reply, {ok, Ref}, State#state{inflight = maps:put(PacketId, {ReceiverPid, Ref, Message}, InFlight)}};
@ -119,10 +107,10 @@ handle_info({publish, Message = #{packet_id := _PacketId, payload := Payload}},
{noreply, State};
handle_info({puback, Packet = #{packet_id := PacketId}}, State = #state{inflight = Inflight}) ->
case maps:take(PacketId, Inflight) of
{{ReceiverPid, Ref, Message}, Inflight1} ->
{{ReceiverPid, Ref, Message}, RestInflight} ->
lager:debug("[iot_mqtt_publisher] receive puback packet: ~p, assoc message: ~p", [Packet, Message]),
ReceiverPid ! {ok, Ref, PacketId},
{noreply, State#state{inflight = Inflight1}};
{noreply, State#state{inflight = RestInflight}};
error ->
lager:warning("[iot_mqtt_publisher] receive unknown puback packet: ~p", [Packet]),
{noreply, State}
@ -140,7 +128,6 @@ handle_info(Info, State = #state{}) ->
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
State :: #state{}) -> term()).
terminate(Reason, _State = #state{conn_pid = ConnPid}) when is_pid(ConnPid) ->
{ok, _Props, _ReasonCode} = emqtt:unsubscribe(ConnPid, #{}, <<"hello">>),
ok = emqtt:disconnect(ConnPid),
ok = emqtt:stop(ConnPid),
lager:debug("[iot_mqtt_publisher] terminate with reason: ~p", [Reason]),

View File

@ -2,7 +2,8 @@
%%% @author aresei
%%% @copyright (C) 2023, <COMPANY>
%%% @doc
%%%
%%% 1.
%%% 2. host进程不能直接去监听topic线
%%% @end
%%% Created : 12. 3 2023 21:27
%%%-------------------------------------------------------------------
@ -13,47 +14,25 @@
-behaviour(gen_server).
%% API
-export([test/1]).
-export([start_link/0, get_name/1, get_pid/1, publish/2, publish_result/2]).
-export([start_link/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
%%
-define(Topics,[
{<<"host/upstream/+">>, 1}
]).
-record(state, {
conn_pid :: pid()
}).
test(Msg) when is_binary(Msg) ->
Pid = get_pid(<<"1">>),
publish(Pid, Msg).
%%%===================================================================
%%% API
%%%===================================================================
get_pid(HostId) when is_binary(HostId) ->
Name = get_name(HostId),
global:whereis_name(Name).
get_name(HostId) when is_binary(HostId) ->
binary_to_atom(<<"iot_host:", HostId/binary>>).
-spec publish(pid(), binary()) -> {ok, Ref :: reference()} | {error, term()}.
publish(Pid, Message) when is_pid(Pid), is_binary(Message) ->
gen_server:call(Pid, {publish, self(), Message}).
%% publish的结果
-spec publish_result(tuple(), Timeout :: integer()) -> {ok, PacketId :: integer()} | {error, Reason :: any()}.
publish_result({error, Reason}, _) ->
{error, Reason};
publish_result({ok, Ref}, Timeout) when is_reference(Ref), is_integer(Timeout) ->
receive
{ok, Ref, PacketId} ->
{ok, PacketId}
after Timeout ->
{error, timeout}
end.
%% @doc Spawns the server and registers the local name (unique)
-spec(start_link() ->
@ -72,26 +51,22 @@ start_link() ->
{stop, Reason :: term()} | ignore).
init([]) ->
%% emqx服务器的连接
Opts = iot_config:emqt_opts(),
Opts = iot_config:emqt_opts(<<"host-subscriber">>),
case emqtt:start_link(Opts) of
{ok, ConnPid} ->
lager:debug("[iot_mqtt_subscriber] start connecting"),
%% host相关的全部事件
%{ok, _} = emqtt:connect(ConnPid),
lager:debug("[iot_mqtt_subscriber] connect success, pid: ~p", [ConnPid]),
Topics = [
{<<"$share/nodes_group//server/register">>, 1},
{<<"$share/nodes_group//host/+/upstream">>, 1}
],
%SubscribeResult = emqtt:subscribe(ConnPid, Topics),
% lager:debug("[iot_mqtt_subscriber] subscribe topics: ~p, result is: ~p", [Topics, SubscribeResult]),
{ok, _} = emqtt:connect(ConnPid),
lager:debug("[iot_mqtt_host_subscriber] connect success, pid: ~p", [ConnPid]),
SubscribeResult = emqtt:subscribe(ConnPid, ?Topics),
lager:debug("[iot_mqtt_host_subscriber] subscribe topics: ~p, result is: ~p", [?Topics, SubscribeResult]),
{ok, #state{conn_pid = ConnPid}};
ignore ->
lager:debug("[iot_mqtt_subscriber] connect emqx get ignore"),
lager:debug("[iot_mqtt_host_subscriber] connect emqx get ignore"),
{stop, ignore};
{error, Reason} ->
lager:debug("[iot_mqtt_subscriber] connect emqx get error: ~p", [Reason]),
lager:debug("[iot_mqtt_host_subscriber] connect emqx get error: ~p", [Reason]),
{stop, Reason}
end.
@ -124,10 +99,29 @@ handle_cast(_Request, State = #state{}) ->
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_info({disconnect, ReasonCode, Properties}, State = #state{}) ->
lager:debug("[iot_mqtt_subscriber] Recv a DISONNECT packet - ReasonCode: ~p, Properties: ~p", [ReasonCode, Properties]),
lager:debug("[iot_mqtt_host_subscriber] Recv a DISONNECT packet - ReasonCode: ~p, Properties: ~p", [ReasonCode, Properties]),
{stop, disconnected, State};
handle_info({publish, Message = #{packet_id := _PacketId, payload := Payload}}, State = #state{conn_pid = _ConnPid}) ->
lager:debug("[iot_mqtt_subscriber] Recv a publish packet: ~p, payload: ~p", [Message, Payload]),
%% json反序列需要在host进程进行
handle_info({publish, Message = #{packet_id := _PacketId, payload := Payload, qos := Qos, topic := Topic}}, State = #state{conn_pid = _ConnPid}) ->
lager:debug("[iot_mqtt_subscriber] Recv a publish packet: ~p, qos: ~p", [Message, Qos]),
%% host进程去处理
case Topic of
<<"host/upstream/", UUID/binary>> ->
case iot_host:get_pid(UUID) of
HostPid when is_pid(HostPid) ->
iot_host:handle(HostPid, Payload);
undefined ->
%%
case iot_host_sup:ensured_host_started(UUID) of
{ok, NewHostPid} ->
iot_host:handle(NewHostPid, Payload);
{error, Reason} ->
lager:warning("[iot_mqtt_subscriber] try start_new_host get error: ~p, assoc packet: ~p", [Reason, Message])
end
end;
_ ->
lager:warning("[iot_mqtt_subscriber] invalid topic: ~p, packet: ~p, qos: ~p", [Topic, Message, Qos])
end,
{noreply, State};
handle_info({puback, Packet = #{packet_id := _PacketId}}, State = #state{}) ->
lager:debug("[iot_mqtt_subscriber] receive puback packet: ~p", [Packet]),
@ -145,9 +139,11 @@ handle_info(Info, State = #state{}) ->
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
State :: #state{}) -> term()).
terminate(Reason, _State = #state{conn_pid = ConnPid}) when is_pid(ConnPid) ->
{ok, _Props, _ReasonCode} = emqtt:unsubscribe(ConnPid, #{}, <<"hello">>),
%% topic的订阅
TopicNames = lists:map(fun({Name, _}) -> Name end, ?Topics),
{ok, _Props, _ReasonCode} = emqtt:unsubscribe(ConnPid, #{}, TopicNames),
ok = emqtt:disconnect(ConnPid),
ok = emqtt:stop(ConnPid),
lager:debug("[iot_mqtt_subscriber] terminate with reason: ~p", [Reason]),
ok;
terminate(Reason, _State) ->
@ -164,4 +160,4 @@ code_change(_OldVsn, State = #state{}, _Extra) ->
%%%===================================================================
%%% Internal functions
%%%===================================================================
%%%===================================================================

View File

@ -0,0 +1,223 @@
%%%-------------------------------------------------------------------
%%% @author aresei
%%% @copyright (C) 2023, <COMPANY>
%%% @doc
%%% 1.
%%% 2. host进程不能直接去监听topic线
%%% @end
%%% Created : 12. 3 2023 21:27
%%%-------------------------------------------------------------------
-module(iot_mqtt_sys_subscriber).
-author("aresei").
-include("iot.hrl").
-behaviour(gen_server).
%% API
-export([start_link/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
%%
-define(Topics,[
{<<"system/upstream">>, 1}
]).
-record(state, {
conn_pid :: pid(),
%%
inflight = #{} :: map()
}).
%%%===================================================================
%%% API
%%%===================================================================
%% @doc Spawns the server and registers the local name (unique)
-spec(start_link() ->
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
%% @private
%% @doc Initializes the server
-spec(init(Args :: term()) ->
{ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
{stop, Reason :: term()} | ignore).
init([]) ->
%% emqx服务器的连接
Opts = iot_config:emqt_opts(<<"system-subscriber">>),
case emqtt:start_link(Opts) of
{ok, ConnPid} ->
%% host相关的全部事件
{ok, _} = emqtt:connect(ConnPid),
lager:debug("[iot_mqtt_sys_subscriber] connect success, pid: ~p", [ConnPid]),
SubscribeResult = emqtt:subscribe(ConnPid, ?Topics),
lager:debug("[iot_mqtt_sys_subscriber] subscribe topics: ~p, result is: ~p", [?Topics, SubscribeResult]),
{ok, #state{conn_pid = ConnPid}};
ignore ->
lager:debug("[iot_mqtt_sys_subscriber] connect emqx get ignore"),
{stop, ignore};
{error, Reason} ->
lager:debug("[iot_mqtt_sys_subscriber] connect emqx get error: ~p", [Reason]),
{stop, Reason}
end.
%% @private
%% @doc Handling call messages
-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()},
State :: #state{}) ->
{reply, Reply :: term(), NewState :: #state{}} |
{reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_call(_Info, _From, State = #state{conn_pid = _ConnPid}) ->
{reply, ok, State}.
%% @private
%% @doc Handling cast messages
-spec(handle_cast(Request :: term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_cast(_Request, State = #state{}) ->
{noreply, State}.
%% @private
%% @doc Handling all non call/cast messages
-spec(handle_info(Info :: timeout() | term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_info({disconnect, ReasonCode, Properties}, State = #state{}) ->
lager:debug("[iot_mqtt_sys_subscriber] Recv a DISONNECT packet - ReasonCode: ~p, Properties: ~p", [ReasonCode, Properties]),
{stop, disconnected, State};
%% json反序列需要在host进程进行
handle_info({publish, #{payload := Payload, qos := Qos, topic := <<"system/upstream">>}}, State) ->
lager:debug("[iot_mqtt_sys_subscriber] Recv a register packet: ~p, qos: ~p", [Payload, Qos]),
Message = catch jiffy:decode(Payload, [return_maps]),
NState = handle_message(Message, State),
{noreply, NState};
handle_info({puback, Packet = #{packet_id := _PacketId}}, State = #state{}) ->
lager:debug("[iot_mqtt_sys_subscriber] receive puback packet: ~p", [Packet]),
{noreply, State};
%%
handle_info({ok, Ref, _PacketId}, State = #state{inflight = Inflight}) ->
case maps:take(Ref, Inflight) of
error ->
{noreply, State};
{{UUID, Msg}, NInflight} ->
lager:debug("[iot_mqtt_sys_subscriber] send message: ~p, to uuid: ~p, success", [Msg, UUID]),
{noreply, State#state{inflight = NInflight}}
end;
handle_info(Info, State = #state{}) ->
lager:debug("[iot_mqtt_sys_subscriber] get info: ~p", [Info]),
{noreply, State}.
%% @private
%% @doc This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
State :: #state{}) -> term()).
terminate(Reason, _State = #state{conn_pid = ConnPid}) when is_pid(ConnPid) ->
%% topic的订阅
TopicNames = lists:map(fun({Name, _}) -> Name end, ?Topics),
{ok, _Props, _ReasonCode} = emqtt:unsubscribe(ConnPid, #{}, TopicNames),
ok = emqtt:disconnect(ConnPid),
lager:debug("[iot_mqtt_sys_subscriber] terminate with reason: ~p", [Reason]),
ok;
terminate(Reason, _State) ->
lager:debug("[iot_mqtt_sys_subscriber] terminate with reason: ~p", [Reason]),
ok.
%% @private
%% @doc Convert process state when code is changed
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{},
Extra :: term()) ->
{ok, NewState :: #state{}} | {error, Reason :: term()}).
code_change(_OldVsn, State = #state{}, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
%%
handle_message(#{<<"method">> := <<"register">>, <<"params">> := #{<<"uuid">> := UUID}}, State = #state{inflight = Inflight}) when is_binary(UUID) ->
Topic = iot_host:downstream_topic(UUID),
Qos = 2,
%%
case host_bo:get_host_by_uuid(UUID) of
{ok, Host} ->
lager:debug("[iot_mqtt_sys_subscriber] register, host uuid: ~p, info: ~p, exists", [UUID, Host]),
%%
{ok, _} = iot_host_sup:ensured_host_started(UUID),
Reply = jiffy:encode(#{
<<"code">> => 1,
<<"message">> => <<"ok">>
}),
case iot_mqtt_publisher:publish(Topic, <<0:8, Reply/binary>>, Qos) of
{ok, Ref} ->
State#state{inflight = maps:put(Ref, {UUID, Reply}, Inflight)};
{error, Reason} ->
lager:debug("[iot_host] publish topic get error: ~p", [Reason]),
State
end;
undefined ->
case host_bo:create_host(UUID) of
{ok, HostId} ->
lager:debug("[iot_mqtt_sys_subscriber] create host success, uuid: ~p, host_id: ~p", [UUID, HostId]),
%%
{ok, _} = iot_host_sup:ensured_host_started(UUID),
Reply = jiffy:encode(#{
<<"code">> => 1,
<<"message">> => <<"ok">>
}),
case iot_mqtt_publisher:publish(Topic, <<0:8, Reply/binary>>, Qos) of
{ok, Ref} ->
State#state{inflight = maps:put(Ref, {UUID, Reply}, Inflight)};
{error, Reason} ->
lager:debug("[iot_host] publish topic get error: ~p", [Reason]),
State
end;
{error, Reason} ->
lager:debug("[iot_mqtt_sys_subscriber] create host failed, reason: ~p", [Reason]),
Reply = jiffy:encode(#{
<<"code">> => 0,
<<"message">> => <<"create host failed">>
}),
case iot_mqtt_publisher:publish(Topic, <<0:8, Reply/binary>>, Qos) of
{ok, Ref} ->
State#state{inflight = maps:put(Ref, {UUID, Reply}, Inflight)};
{error, Reason} ->
lager:debug("[iot_host] publish topic get error: ~p", [Reason]),
State
end
end
end;
handle_message(Msg, State) ->
lager:warning("[iot_mqtt_sys_subscriber] get invalid message: ~p", [Msg]),
State.

View File

@ -1,105 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2023, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 01. 3 2023 16:03
%%%-------------------------------------------------------------------
-module(iot_router).
-author("licheng5").
-include("iot.hrl").
-behaviour(gen_server).
%% API
-export([start_link/2]).
-export([get_name/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
-record(state, {
}).
get_name(Id) when is_integer(Id) ->
list_to_atom("iot_router:" ++ integer_to_list(Id)).
%%%===================================================================
%%% API
%%%===================================================================
%% @doc Spawns the server and registers the local name (unique)
-spec(start_link(Name :: atom(), Router :: #router{}) ->
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link(Name, Router = #router{}) ->
gen_server:start_link({global, Name}, ?MODULE, [Router], []).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
%% @private
%% @doc Initializes the server
-spec(init(Args :: term()) ->
{ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
{stop, Reason :: term()} | ignore).
init([Router]) ->
lager:debug("router is: ~p", [Router]),
{ok, #state{}}.
%% @private
%% @doc Handling call messages
-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()},
State :: #state{}) ->
{reply, Reply :: term(), NewState :: #state{}} |
{reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_call(_Request, _From, State = #state{}) ->
{reply, ok, State}.
%% @private
%% @doc Handling cast messages
-spec(handle_cast(Request :: term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_cast(_Request, State = #state{}) ->
{noreply, State}.
%% @private
%% @doc Handling all non call/cast messages
-spec(handle_info(Info :: timeout() | term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_info(_Info, State = #state{}) ->
{noreply, State}.
%% @private
%% @doc This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
State :: #state{}) -> term()).
terminate(_Reason, _State = #state{}) ->
ok.
%% @private
%% @doc Convert process state when code is changed
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{},
Extra :: term()) ->
{ok, NewState :: #state{}} | {error, Reason :: term()}).
code_change(_OldVsn, State = #state{}, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================

View File

@ -1,86 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2023, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 01. 3 2023 16:01
%%%-------------------------------------------------------------------
-module(iot_router_sup).
-author("licheng5").
-include("iot.hrl").
-behaviour(supervisor).
%% API
-export([start_link/0, start_new_router/1]).
%% Supervisor callbacks
-export([init/1]).
-define(SERVER, ?MODULE).
%%%===================================================================
%%% API functions
%%%===================================================================
%% @doc Starts the supervisor
-spec(start_link() -> {ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
%%%===================================================================
%%% Supervisor callbacks
%%%===================================================================
%% @private
%% @doc Whenever a supervisor is started using supervisor:start_link/[2,3],
%% this function is called by the new process to find out about
%% restart strategy, maximum restart frequency and child
%% specifications.
-spec(init(Args :: term()) ->
{ok, {SupFlags :: {RestartStrategy :: supervisor:strategy(),
MaxR :: non_neg_integer(), MaxT :: non_neg_integer()},
[ChildSpec :: supervisor:child_spec()]}}
| ignore | {error, Reason :: term()}).
init([]) ->
SupFlags = #{strategy => one_for_one, intensity => 1000, period => 3600},
%%
Specs = case router_model:get_all_valid_routers() of
{ok, Routers} ->
lists:map(fun generate_router_spec/1, Routers);
error ->
[]
end,
{ok, {SupFlags, Specs}}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
%%
-spec start_new_router(Router :: #router{}) -> {ok, Pid :: pid()} | {error, Reason :: any()}.
start_new_router(Router = #router{}) ->
Spec = generate_router_spec(Router),
case supervisor:start_child(?MODULE, Spec) of
{ok, Pid} ->
{ok, Pid};
{error, {already_started, Pid}} ->
{ok, Pid};
{error, Error} ->
lager:debug("start router get a error: ~p", [Error]),
{error, Error}
end.
generate_router_spec(Router = #router{router_id = RouterId}) ->
Id = iot_router:get_name(RouterId),
#{
id => Id,
start => {'iot_router', start_link, [Id, Router]},
restart => permanent,
shutdown => 2000,
type => worker,
modules => ['iot_router']
}.

View File

@ -28,31 +28,13 @@ start_link() ->
init([]) ->
SupFlags = #{strategy => one_for_one, intensity => 1000, period => 3600},
ChildSpecs = [
%#{
% id => 'iot_host_sup',
% start => {'iot_host_sup', start_link, []},
% restart => permanent,
% shutdown => 2000,
% type => supervisor,
% modules => ['iot_host_sup']
%},
#{
id => 'iot_router_sup',
start => {'iot_router_sup', start_link, []},
id => 'iot_host_sup',
start => {'iot_host_sup', start_link, []},
restart => permanent,
shutdown => 2000,
type => supervisor,
modules => ['iot_router_sup']
},
#{
id => 'iot_issue_sup',
start => {'iot_issue_sup', start_link, []},
restart => permanent,
shutdown => 2000,
type => supervisor,
modules => ['iot_issue_sup']
modules => ['iot_host_sup']
},
#{
@ -63,6 +45,16 @@ init([]) ->
type => worker,
modules => ['iot_mqtt_subscriber']
},
#{
id => 'iot_mqtt_sys_subscriber',
start => {'iot_mqtt_sys_subscriber', start_link, []},
restart => permanent,
shutdown => 2000,
type => worker,
modules => ['iot_mqtt_sys_subscriber']
},
#{
id => 'iot_mqtt_publisher',
start => {'iot_mqtt_publisher', start_link, []},
@ -72,6 +64,12 @@ init([]) ->
modules => ['iot_mqtt_publisher']
}
],
{ok, {SupFlags, ChildSpecs}}.
{ok, {SupFlags, pools() ++ ChildSpecs}}.
%% internal functions
pools() ->
{ok, Pools} = application:get_env(iot, pools),
lists:map(fun({Name, PoolArgs, WorkerArgs}) ->
poolboy:child_spec(Name, [{name, {local, Name}}|PoolArgs], WorkerArgs)
end, Pools).

View File

@ -13,7 +13,7 @@
-export([timestamp/0, number_format/2, current_time/0]).
-export([step/3, chunks/2, rand_bytes/1, uuid/0]).
-export([json_data/1, json_error/2]).
-export([queue_limited_in/3]).
-export([queue_limited_in/3, assert_call/2]).
%%
timestamp() ->
@ -67,6 +67,7 @@ json_error(ErrCode, ErrMessage) when is_integer(ErrCode), is_binary(ErrMessage)
uuid() ->
rand_bytes(16).
-spec rand_bytes(Size :: integer()) -> string().
rand_bytes(Size) when is_integer(Size), Size > 0 ->
Size1 = erlang:ceil(Size / 2),
Bytes = crypto:strong_rand_bytes(Size1),
@ -80,4 +81,9 @@ queue_limited_in(Item, Q, Num) when is_integer(Num) ->
queue:in(Item, Q1);
false ->
queue:in(Item, Q)
end.
end.
assert_call(true, Fun) ->
Fun();
assert_call(false, _) ->
ok.

View File

@ -0,0 +1,306 @@
%%%-------------------------------------------------------------------
%%% @author aresei
%%% @copyright (C) 2023, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 14. 6 2023 09:50
%%%-------------------------------------------------------------------
-module(host_mocker).
-author("aresei").
-behaviour(gen_server).
%% API
-export([start_link/1, stop/0]).
-export([test/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
-define(TICKER_INTERVAL, 5000).
-record(state, {
conn_pid,
topic :: binary(),
pub_key = <<"-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBMd0+XIwiBAIijwPets3N/MNP
zQwm/j6Zlmu5bC+NEDPwmz64OnoLkOTqSnerh7dN2pYeByEkTBu1prtb0cjEf0EP
2PP5liUQ6ykEnNfo20vMmSTlKFQZ2qb8jkRltBXTworR4cw92luFuJ0q9VUI3cx2
JbOKI9SX52aXGYAc4QIDAQAB
-----END PUBLIC KEY-----">>,
pri_key = <<"-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMEx3T5cjCIEAiKP
A962zc38w0/NDCb+PpmWa7lsL40QM/CbPrg6eguQ5OpKd6uHt03alh4HISRMG7Wm
u1vRyMR/QQ/Y8/mWJRDrKQSc1+jbS8yZJOUoVBnapvyORGW0FdPCitHhzD3aW4W4
nSr1VQjdzHYls4oj1JfnZpcZgBzhAgMBAAECgYEAuf49l6oVpzHgtFCnUyT+4c70
YcFwb6HZtpegQjUrWk09E+kB6u8xTC1ElWL37wWLrcJBP8txVraG/sz0F6PR+dxz
D3pNaDtZNx1Ey5+LPBtvQRdW3PsUzHYOABX7oXaiB4Wsap9QVOlvjThfTjI+77Fq
jwgJr0b2hkrYMgqzUAECQQD2OnlRDmA2Y1kSBwfKyhfQfBBvcHlV/6Qlv/NfXqAo
lyl+okF4dK6TMHtuHstz6bf/VZ/7ImPKbCS8VtXuZN3BAkEAyNyaGIp7rbwb4Nee
hjeLcC9iB1BpglOFM92v3OGzQtHsUD5J2HrAw3IRKiMzO4unZ5SLF9fTNa/nhZjk
cr9HIQJAMXwQ85RTC7stpGzbSQsSfCji2LKfAASPhbKtA6atw1qV0UhkpgO1LgmZ
VMlFlDcNbnhT3ZHMwlq3i05cUjvdQQJAXjtwZ7cAUv9/LLq7ekgwuI7iNIA7H5ND
WJPWX4/bY6vMa9DtIAxmxsqK1vPwoyzfeq6rmqH8SqGdwoV6F4M5QQJAKZKnhlrx
1W9r2qGkn9Bds6CPYUwKZLLXJu32GFe+IXA184DpqBCo9xSxSQ+SN9FuSH5bUKcB
c17GntH5fO32Tg==
-----END PRIVATE KEY-----">>,
aes = <<>>
}).
%%%===================================================================
%%% API
%%%===================================================================
test() ->
catch stop(),
host_mocker:start_link(<<"123123123123123">>).
stop() ->
gen_server:stop(?MODULE).
%% @doc Spawns the server and registers the local name (unique)
-spec(start_link(UUID :: binary()) ->
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link(UUID) when is_binary(UUID) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [UUID], []).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
%% @private
%% @doc Initializes the server
-spec(init(Args :: term()) ->
{ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
{stop, Reason :: term()} | ignore).
init([UUID]) ->
%% emqx服务器的连接
Opts = iot_config:emqt_opts(<<"host-subscriber", UUID/binary>>),
{ok, ConnPid} = emqtt:start_link(Opts),
%% host相关的全部事件
{ok, _} = emqtt:connect(ConnPid),
lager:debug("[iot_mqtt_sys_subscriber] connect success, pid: ~p", [ConnPid]),
SubscribeResult = emqtt:subscribe(ConnPid, [
{<<"host/downstream/", UUID/binary>>, 1}
]),
lager:debug("[iot_mqtt_sys_subscriber] subscribe result is: ~p", [SubscribeResult]),
%%
Message = #{
<<"method">> => <<"register">>,
<<"params">> => #{
<<"uuid">> => UUID
}
},
Req = jiffy:encode(Message, [force_utf8]),
{ok, Ref} = iot_mqtt_publisher:publish(<<"system/upstream">>, Req, 1),
receive
{ok, Ref, PacketId} ->
lager:debug("[host_mocker] send register success, packet_id: ~p", [PacketId]);
{error, Reason} ->
lager:debug("[host_mocker] send register failed, reason: ~p", [Reason])
end,
{ok, #state{conn_pid = ConnPid, topic = <<"host/upstream/", UUID/binary>>}}.
%% @private
%% @doc Handling call messages
-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()},
State :: #state{}) ->
{reply, Reply :: term(), NewState :: #state{}} |
{reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_call(_Request, _From, State = #state{}) ->
{reply, ok, State}.
%% @private
%% @doc Handling cast messages
-spec(handle_cast(Request :: term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_cast(_Request, State = #state{}) ->
{noreply, State}.
%% @private
%% @doc Handling all non call/cast messages
-spec(handle_info(Info :: timeout() | term(), State :: #state{}) ->
{noreply, NewState :: #state{}} |
{noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}).
handle_info({disconnect, ReasonCode, Properties}, State = #state{}) ->
lager:debug("[host_mocker] Recv a DISONNECT packet - ReasonCode: ~p, Properties: ~p", [ReasonCode, Properties]),
{stop, disconnected, State};
%% json反序列需要在host进程进行
handle_info({publish, #{payload := Payload, qos := Qos, topic := FromTopic}},
State = #state{topic = Topic, pub_key = PubKey, pri_key = PrivateKey}) ->
lager:debug("[host_mocker] Recv a publish packet: ~p, qos: ~p, from topic: ~p", [Payload, Qos, FromTopic]),
case Payload of
<<0:8, Reply/binary>> ->
Json = jiffy:decode(Reply, [return_maps]),
lager:debug("[host_mocker] get reply: ~p", [Json]),
case Json of
#{<<"code">> := 1, <<"message">> := <<"ok">>} ->
%% iot的会话
Req = jiffy:encode(#{
<<"method">> => <<"create_session">>,
<<"params">> => #{
<<"pub_key">> => PubKey
}
}, [force_utf8]),
{ok, Ref} = iot_mqtt_publisher:publish(Topic, Req, 1),
receive
{ok, Ref, PacketId} ->
lager:debug("[host_mocker] send create_session success, packet_id: ~p", [PacketId]);
{error, Reason} ->
lager:debug("[host_mocker] send create_session failed, reason: ~p", [Reason])
end;
Info ->
Info
end,
{noreply, State};
<<Type:8, Command0/binary>> ->
Command = iot_cipher_rsa:decode(Command0, PrivateKey),
CommandJson = jiffy:decode(Command, [return_maps]),
lager:debug("[host_mocker] get command: ~p, json: ~p, type: ~p", [Command, CommandJson, Type]),
NState = handle_command(Type, CommandJson, State),
{noreply, NState}
end;
handle_info({puback, Packet = #{packet_id := _PacketId}}, State = #state{}) ->
lager:debug("[iot_mqtt_subscriber] receive puback packet: ~p", [Packet]),
{noreply, State};
%%
handle_info({timeout, _, data_ticker}, State = #state{aes = Aes, topic = Topic}) ->
InfoList = [
#{
<<"service_name">> => <<"shuibiao">>,
<<"at">> => iot_util:timestamp(),
<<"fields">> => [
#{
<<"used">> => rand:uniform(2048 * 2)
}
],
<<"tags">> => #{
}
},
#{
<<"service_name">> => <<"shuibiao:123">>,
<<"at">> => iot_util:timestamp(),
<<"fields">> => [
#{
<<"used">> => rand:uniform(2048 * 2)
}
],
<<"tags">> => #{
}
}
],
Info = jiffy:encode(InfoList),
Msg = jiffy:encode(#{
<<"method">> => <<"data">>,
<<"params">> => base64:encode(iot_cipher_aes:encrypt(Aes, Info))
}, [force_utf8]),
{ok, Ref} = iot_mqtt_publisher:publish(Topic, Msg, 1),
receive
{ok, Ref, PacketId} ->
lager:debug("[host_mocker] send data success, packet_id: ~p", [PacketId]);
{error, Reason} ->
lager:debug("[host_mocker] send data failed, reason: ~p", [Reason])
end,
erlang:start_timer(?TICKER_INTERVAL + 2000 + rand:uniform(5000), self(), data_ticker),
{noreply, State};
%% ping逻辑
handle_info({timeout, _, ping_ticker}, State = #state{aes = Aes, topic = Topic}) ->
Metric = jiffy:encode(#{
<<"cpu_load">> => rand:uniform(100),
<<"cpu_temperature">> => rand:uniform(100),
<<"memory">> => #{
<<"used">> => rand:uniform(2048 * 2),
<<"total">> => 2048 * 2
},
<<"disk">> => #{
<<"used">> => rand:uniform(2048 * 2),
<<"total">> => 2048 * 2
},
<<"interfaces">> => [
#{
<<"name">> => <<"WiFi无线网络"/utf8>>,
<<"detail">> => <<"Managed">>,
<<"status">> => 0
}
]
}, [force_utf8]),
Data = iot_cipher_aes:encrypt(Aes, Metric),
Msg = jiffy:encode(#{
<<"method">> => <<"ping">>,
<<"params">> => base64:encode(Data)
}, [force_utf8]),
{ok, Ref} = iot_mqtt_publisher:publish(Topic, Msg, 1),
receive
{ok, Ref, PacketId} ->
lager:debug("[host_mocker] send ping success, packet_id: ~p", [PacketId]);
{error, Reason} ->
lager:debug("[host_mocker] send ping failed, reason: ~p", [Reason])
end,
erlang:start_timer(?TICKER_INTERVAL, self(), ping_ticker),
{noreply, State}.
%% @private
%% @doc This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
State :: #state{}) -> term()).
terminate(_Reason, _State = #state{}) ->
ok.
%% @private
%% @doc Convert process state when code is changed
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{},
Extra :: term()) ->
{ok, NewState :: #state{}} | {error, Reason :: term()}).
code_change(_OldVsn, State = #state{}, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
handle_command(10, #{<<"aes">> := Aes, <<"a">> := true}, State) ->
%% ping
erlang:start_timer(?TICKER_INTERVAL, self(), ping_ticker),
%%
erlang:start_timer(?TICKER_INTERVAL + 1000, self(), data_ticker),
State#state{aes = Aes}.

View File

@ -0,0 +1,65 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2023, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 14. 2 2023 20:32
%%%-------------------------------------------------------------------
-module(iot_mock).
-author("licheng5").
-include("iot.hrl").
%% API
-export([rsa_encode/1]).
-export([insert_services/1]).
insert_services(Num) ->
lists:foreach(fun(Id) ->
Res = mysql_client:insert(<<"micro_service">>,
#{
<<"name">> => <<"微服务"/utf8, (integer_to_binary(Id))/binary>>,
<<"code">> => <<"1223423423423423"/utf8>>,
<<"type">> => 1,
<<"version">> => <<"v1.0">>,
<<"url">> => <<"https://www.baidu.com">>,
<<"detail">> => <<"这是一个关于测试的微服务"/utf8>>
}, false),
lager:debug("insert service result is: ~p", [Res])
end, lists:seq(1, Num)).
rsa_encode(Data) when is_binary(Data) ->
%%
PublicPemFile = "/tmp/keys/public.pem",
%%
{ok, PubBin} = file:read_file(PublicPemFile),
lager:debug("pub bin is: ~p", [PubBin]),
[Pub] = public_key:pem_decode(PubBin),
lager:debug("pub pem bin is: ~p", [Pub]),
PubKey = public_key:pem_entry_decode(Pub),
lager:debug("the public key is: ~p", [PubKey]),
EncData = public_key:encrypt_public(Data, PubKey),
lager:debug("enc data is: ~p", [EncData]),
rsa_decode(EncData),
ok.
rsa_decode(EncData) when is_binary(EncData) ->
%%
PublicPemFile = "/tmp/keys/pri.pem",
%%
{ok, PubBin} = file:read_file(PublicPemFile),
lager:debug("pub bin is: ~p", [PubBin]),
[Pub] = public_key:pem_decode(PubBin),
lager:debug("pub pem bin is: ~p", [Pub]),
PubKey = public_key:pem_entry_decode(Pub),
lager:debug("the public key is: ~p", [PubKey]),
PlainData = public_key:decrypt_private(EncData, PubKey),
lager:debug("plain data is: ~p", [PlainData]),
ok.

View File

@ -1,285 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2021, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 27. 4 2021 4:38
%%%-------------------------------------------------------------------
-module(host_model).
-author("licheng5").
-include("iot.hrl").
-include_lib("stdlib/include/qlc.hrl").
-define(TAB_NAME, host).
%% API
-export([get_host/1, get_hosts/3, get_all_hosts/0, get_stat/0, add_host/1, change_status/2, delete/1, table_size/0, find_hosts/3, activate/1, update_host/2]).
-export([to_map/1, match_spec/1]).
get_host(HostId) when is_binary(HostId) ->
case mnesia:dirty_read(?TAB_NAME, HostId) of
[Host = #host{}] ->
{ok, Host};
_ ->
undefined
end.
get_all_hosts() ->
Fun = fun() ->
Q = qlc:q([E || E <- mnesia:table(?TAB_NAME), E#host.status == ?HOST_STATUS_ONLINE]),
qlc:e(Q)
end,
case mnesia:transaction(Fun) of
{atomic, Items} when is_list(Items) ->
{ok, sort(Items)};
{aborted, Error} ->
{error, Error}
end.
%% app信息
-spec get_hosts(Spec :: tuple(), Start :: integer(), Limit :: integer()) ->
{ok, Items :: list(), TotalNum :: integer()} |
{error, Reason :: any()}.
get_hosts(Spec, Start, Limit) when is_integer(Limit), is_integer(Start), Start >= 0, Limit > 0 ->
Hosts0 = mnesia:dirty_select(?TAB_NAME, [Spec]),
SortedHosts = sort(Hosts0),
Len = length(SortedHosts),
case Len >= Start + 1 of
true ->
{ok, lists:sublist(SortedHosts, Start + 1, Limit), Len};
false ->
{ok, [], Len}
end.
%%
get_stat() ->
Fun = fun() ->
mnesia:foldl(fun(#host{status = Status}, Acc) ->
Num = maps:get(Status, Acc, 0),
Acc#{Status => Num + 1}
end, #{}, ?TAB_NAME)
end,
case mnesia:transaction(Fun) of
{atomic, Stat} when is_map(Stat) ->
lists:map(fun({Status, Num}) -> #{<<"status">> => Status, <<"num">> => Num} end, maps:to_list(Stat));
{aborted, _} ->
#{}
end.
-spec find_hosts(Pred :: fun((#host{}) -> boolean()), Start :: integer(), Limit :: integer()) ->
{ok, Items :: [#host{}], Num :: integer()} |
{error, Reason :: any()}.
find_hosts(Pred, Start, Limit) when is_function(Pred, 1), is_integer(Limit), is_integer(Start), Start >= 0, Limit > 0 ->
Fun = fun() ->
Q = qlc:q([E || E <- mnesia:table(?TAB_NAME)]),
qlc:fold(fun(Host, Acc) ->
case Pred(Host) of
true -> [Host|Acc];
false -> Acc
end
end, [], Q)
end,
case mnesia:transaction(Fun) of
{atomic, Hosts0} when is_list(Hosts0) ->
Hosts = sort(Hosts0),
Len = length(Hosts),
case Len >= Start + 1 of
true ->
{ok, lists:sublist(Hosts, Start + 1, Limit), Len};
false ->
{ok, [], Len}
end;
{aborted, Error} ->
{error, Error}
end.
-spec add_host(Host :: #host{}) -> ok | {error, Reason :: binary()}.
add_host(Host = #host{serial_number = SerialNumber}) ->
Fun = fun() ->
Q = qlc:q([E || E <- mnesia:table(?TAB_NAME), E#host.serial_number =:= SerialNumber]),
case qlc:e(Q) of
[_SomeHost|_] ->
mnesia:abort(<<"serial_number exists">>);
[] ->
mnesia:write(?TAB_NAME, Host, write)
end
end,
case mnesia:transaction(Fun) of
{atomic, ok} ->
ok;
{aborted, Error} ->
{error, Error}
end.
-spec change_status(HostId :: binary(), Status :: integer()) -> ok | {error, Reason :: any()}.
change_status(HostId, Status) when is_binary(HostId), is_integer(Status) ->
Fun = fun() ->
case mnesia:read(?TAB_NAME, HostId) of
[] ->
mnesia:abort(<<"host not found">>);
[Host] ->
mnesia:write(?TAB_NAME, Host#host{status = Status}, write)
end
end,
case mnesia:transaction(Fun) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
{error, Reason}
end.
-spec update_host(HostId :: binary(), Fields :: #{}) -> ok | {error, Reason :: any()}.
update_host(HostId, Fields) when is_binary(HostId), is_map(Fields) ->
Fun = fun() ->
case mnesia:read(?TAB_NAME, HostId) of
[] ->
mnesia:abort(<<"host not found">>);
[Host] ->
NHost = lists:foldl(fun(E, Host0) ->
case E of
{<<"name">>, Name} when is_binary(Name) ->
Host0#host{name = Name};
{<<"serial_number">>, SerialNumber} when is_binary(SerialNumber) ->
Host0#host{serial_number = SerialNumber};
{<<"model">>, Model} when is_binary(Model) ->
Host0#host{model = Model};
{<<"cell_id">>, CellId} when is_integer(CellId) ->
Host0#host{cell_id = CellId};
{Name, _} ->
mnesia:abort(<<"invalid: ", Name/binary>>)
end
end, Host, maps:to_list(Fields)),
mnesia:write(?TAB_NAME, NHost, write)
end
end,
case mnesia:transaction(Fun) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
{error, Reason}
end.
-spec activate(HostId :: binary()) -> ok | {error, Reason :: any()}.
activate(HostId) when is_binary(HostId) ->
Fun = fun() ->
case mnesia:read(host, HostId) of
[] ->
mnesia:abort(<<"host not found">>);
[Host = #host{status = Status}] ->
case Status =:= ?HOST_STATUS_INACTIVE of
true ->
Aes = iot_util:rand_bytes(16),
NHost = Host#host{
aes = Aes,
activated_ts = iot_util:current_time(),
update_ts = iot_util:current_time(),
status = ?HOST_STATUS_ONLINE
},
ok = mnesia:write(host, NHost, write),
NHost;
false ->
mnesia:abort(<<"host status invalid">>)
end
end
end,
case mnesia:transaction(Fun) of
{atomic, Host} ->
{ok, Host};
{aborted, Reason} ->
{error, Reason}
end.
-spec delete(HostId :: binary()) -> ok | {error, Reason :: any()}.
delete(HostId) when is_binary(HostId) ->
case mnesia:transaction(fun() -> mnesia:delete(host, HostId, write) end) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
{error, Reason}
end.
%%
match_spec(Specs) when is_list(Specs) ->
MatchHead = #host{model = '$1', cell_id = '$2', _ = '_'},
Guard = guard(Specs),
Result = ['$_'],
{MatchHead, Guard, Result}.
guard(Specs) when is_list(Specs) ->
guard(Specs, []).
guard([], Guard) ->
Guard;
guard([{model, Model}|Tail], Guard) when Model =/= <<"">> ->
guard(Tail, [{'=:=', '$1', Model}|Guard]);
guard([{cell, CellId}|Tail], Guard) when CellId > 0 ->
guard(Tail, [{'=:=', '$2', CellId}|Guard]);
guard([_|Tail], Guard) ->
guard(Tail, Guard).
%% app表的数据大小
table_size() ->
mnesia:table_info(host, size).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% helper methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
sort(Hosts) when is_list(Hosts) ->
lists:sort(fun(#host{name = N1}, #host{name = N2}) -> N1 < N2 end, Hosts).
%% hash
to_map(#host{host_id = HostId, name = Name, model = Model, cell_id = CellId, activated_ts = ActivatedTs, update_ts = UpdateTs, status = Status, metric = Metric}) ->
#{
<<"host_id">> => HostId,
<<"name">> => Name,
<<"model">> => Model,
<<"cell_id">> => CellId,
<<"activated_ts">> => ActivatedTs,
<<"update_ts">> => UpdateTs,
<<"status">> => Status,
<<"metric">> => host_metric(Metric)
}.
host_metric(undefined) ->
#{};
host_metric(#host_metric{cpus = Cpus, cpu_temperature = CpuTemperature, memory = Memory, disk = Disk, interfaces = Interfaces}) ->
#{
<<"cpus">> => host_cpus(Cpus),
<<"cpu_temperature">> => CpuTemperature,
<<"memory">> => host_memory(Memory),
<<"disk">> => host_disk(Disk),
<<"interfaces">> => host_interfaces(Interfaces)
}.
host_cpus(Cpus) when is_list(Cpus) ->
lists:map(fun(#cpu_metric{num = Num, load = Load}) -> #{<<"num">> => Num, <<"load">> => Load } end, Cpus);
host_cpus(_) ->
[].
host_memory(#memory_metric{used = MemoryUsed, total = MemoryTotal}) ->
#{<<"used">> => MemoryUsed, <<"total">> => MemoryTotal};
host_memory(_) ->
#{}.
host_disk(#disk_metric{used = DiskUsed, total = DiskTotal}) ->
#{<<"used">> => DiskUsed, <<"total">> => DiskTotal};
host_disk(_) ->
#{}.
%%
host_interfaces(Interfaces) when is_list(Interfaces) ->
lists:map(fun(#interface_metric{name = IName, desc = IDesc, status = IStatus, detail = IDetail}) ->
#{
<<"name">> => IName,
<<"desc">> => IDesc,
<<"status">> => IStatus,
<<"detatil">> => IDetail
}
end, Interfaces);
host_interfaces(_) ->
[].

View File

@ -1,23 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2021, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 27. 4 2021 4:38
%%%-------------------------------------------------------------------
-module(id_generator_model).
-author("licheng5").
-include("iot.hrl").
%% API
-export([generate/1, new_terminal_id/0]).
%% id
-spec generate(Name :: atom()) -> Id :: integer().
generate(Name) when is_atom(Name) ->
mnesia:dirty_update_counter(id_generator, Name, 1).
-spec new_terminal_id() -> Id :: integer().
new_terminal_id() ->
generate(terminal).

View File

@ -1,126 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2021, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 27. 4 2021 4:38
%%%-------------------------------------------------------------------
-module(issue_model).
-author("licheng5").
-include("iot.hrl").
-include_lib("stdlib/include/qlc.hrl").
-define(TAB_NAME, issue).
%% API
-export([get_issue/1, get_issues/3, add_issue/1, change_status/2, delete/1, table_size/0, get_user_issues/3]).
-export([to_map/1]).
get_issue(IssueId) when is_integer(IssueId) ->
case mnesia:dirty_read(?TAB_NAME, IssueId) of
[Issue = #issue{}] ->
{ok, Issue};
_ ->
undefined
end.
%% app信息
-spec get_issues(Filter :: any(), Start :: integer(), Limit :: integer()) ->
{ok, Items :: list(), TotalNum :: integer()} |
{error, Reason :: any()}.
get_issues(Spec, Start, Limit) when is_integer(Limit), is_integer(Start), Start >= 0, Limit > 0 ->
Issues0 = mnesia:dirty_select(?TAB_NAME, [Spec]),
Issues = sort(Issues0),
Len = length(Issues),
case Len >= Start + 1 of
true ->
{ok, lists:sublist(Issues, Start + 1, Limit), Len};
false ->
{ok, [], Len}
end.
-spec get_user_issues(UserId :: integer(), Start :: integer(), Limit :: integer()) ->
{ok, Items :: [#host{}], Num :: integer()} |
{error, Reason :: any()}.
get_user_issues(UserId, Start, Limit) when is_integer(UserId), is_integer(Limit), is_integer(Start), Start >= 0, Limit > 0 ->
Fun = fun() ->
Q = qlc:q([E || E <- mnesia:table(?TAB_NAME), E#issue.uid =:= UserId]),
qlc:e(Q)
end,
case mnesia:transaction(Fun) of
{atomic, Issues0} when is_list(Issues0) ->
Issues = sort(Issues0),
Len = length(Issues),
case Len >= Start + 1 of
true ->
{ok, lists:sublist(Issues, Start + 1, Limit), Len};
false ->
{ok, [], Len}
end;
{aborted, Error} ->
{error, Error}
end.
-spec add_issue(Issue :: #issue{}) -> ok | {error, Reason :: any()}.
add_issue(Issue = #issue{}) ->
case mnesia:transaction(fun() -> mnesia:write(?TAB_NAME, Issue, write) end) of
{atomic, ok} ->
ok;
{aborted, Error} ->
{error, Error}
end.
-spec change_status(IssueId :: integer(), Status :: integer()) -> ok | {error, Reason :: any()}.
change_status(IssueId, Status) when is_integer(IssueId), is_integer(Status) ->
Fun = fun() ->
case mnesia:read(?TAB_NAME, IssueId) of
[] ->
mnesia:abort(<<"issue not found">>);
[Issue] ->
mnesia:write(?TAB_NAME, Issue#issue{status = Status}, write)
end
end,
case mnesia:transaction(Fun) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
{error, Reason}
end.
-spec delete(IssueId :: binary()) -> ok | {error, Reason :: any()}.
delete(IssueId) when is_integer(IssueId) ->
case mnesia:transaction(fun() -> mnesia:delete(?TAB_NAME, IssueId, write) end) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
{error, Reason}
end.
%% app表的数据大小
table_size() ->
mnesia:table_info(?TAB_NAME, size).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% helper methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% id倒序进行排序
sort(Issues) when is_list(Issues) ->
lists:sort(fun(#issue{issue_id = Id0}, #issue{issue_id = Id1}) -> Id0 < Id1 end, Issues).
to_map(#issue{issue_id = IssueId, name = Name, uid = Uid, deploy_type = DeployType, assoc_id = AssocId, hosts = Hosts, timeout = Timeout,
create_ts = CreateTs, results = Results, status = Status}) ->
#{
<<"issue_id">> => IssueId,
<<"name">> => Name,
<<"uid">> => Uid,
<<"deploy_type">> => DeployType,
<<"assoc_id">> => AssocId,
<<"hosts">> => Hosts,
<<"timeout">> => Timeout,
<<"status">> => Status,
<<"create_ts">> => CreateTs,
<<"results">> => Results
}.

View File

@ -1,126 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2021, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 27. 4 2021 4:38
%%%-------------------------------------------------------------------
-module(log_model).
-author("licheng5").
-include("iot.hrl").
-include_lib("stdlib/include/qlc.hrl").
-define(TAB_NAME, log).
%% API
-export([get_logs/1, add_log/1, delete/1, table_size/0, get_last_logs/1]).
-export([to_map/1]).
%% app信息,
-spec get_logs(Limit :: integer()) -> {ok, Logs :: list()} | {error, Reason :: any()}.
get_logs(Limit) when is_integer(Limit), Limit > 0 ->
Fun = fun() ->
Q = qlc:q([E || E <- mnesia:table(?TAB_NAME)]),
%%
Order = fun(A, B) -> A#log.create_ts > B#log.create_ts end,
Q1 = qlc:sort(Q, [{order, Order}]),
QC = qlc:cursor(Q1),
qlc:next_answers(QC, Limit)
end,
case mnesia:transaction(Fun) of
{atomic, Logs} when is_list(Logs) ->
{ok, Logs};
{aborted, Error} ->
{error, Error}
end.
%% n条记录
-spec get_last_logs(N :: integer()) -> {ok, Logs :: [#log{}]} | {error, Reason :: any()}.
get_last_logs(N) when N > 0 ->
Fun = fun() ->
Keys = read_last_keys(N),
lists:flatmap(fun(Key) -> mnesia:read(?TAB_NAME, Key, read) end, lists:reverse(Keys))
end,
case mnesia:transaction(Fun) of
{atomic, Logs} when is_list(Logs) ->
{ok, Logs};
{aborted, Error} ->
{error, Error}
end.
-spec add_log(Log :: #log{}) -> ok | {error, Reason :: binary()}.
add_log(Log = #log{}) ->
case mnesia:transaction(fun() -> mnesia:write(?TAB_NAME, Log, write) end) of
{atomic, ok} ->
ok;
{aborted, Error} ->
{error, Error}
end.
-spec delete(LogId :: integer()) -> ok | {error, Reason :: any()}.
delete(LogId) when is_binary(LogId) ->
case mnesia:transaction(fun() -> mnesia:delete(?TAB_NAME, LogId, write) end) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
{error, Reason}
end.
%%
read_last_keys(N) when N >= 1 ->
case mnesia:last(?TAB_NAME) of
'$end_of_table' ->
[];
LastKey ->
read_last_keys0(N - 1, LastKey, [LastKey])
end.
read_last_keys0(0, _, Keys) ->
Keys;
read_last_keys0(N, Key, Keys) ->
case mnesia:prev(?TAB_NAME, Key) of
'$end_of_table' ->
Keys;
PrevKey ->
read_last_keys0(N - 1, PrevKey, [PrevKey|Keys])
end.
%% app表的数据大小
table_size() ->
mnesia:table_info(?TAB_NAME, size).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% helper methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
sort(Logs) when is_list(Logs) ->
lists:sort(fun(#log{create_ts = Ts0}, #log{create_ts = Ts1}) -> Ts0 > Ts1 end, Logs).
%% hash
to_map(#log{log_id = LogId, action_name = ActionName, device_type = DeviceType, assoc_id = AssocId, create_ts = CreateTs}) ->
DeviceInfo = case DeviceType of
?DEVICE_HOST ->
case host_model:get_host(AssocId) of
{ok, Host} ->
host_model:to_map(Host);
_ ->
#{}
end;
?DEVICE_TERMINAL ->
case terminal_model:get_terminal(AssocId) of
{ok, Terminal} ->
terminal_model:to_map(Terminal);
_ ->
#{}
end
end,
#{
<<"log_id">> => LogId,
<<"action_name">> => ActionName,
<<"device_type">> => DeviceType,
<<"device_info">> => DeviceInfo,
<<"create_ts">> => CreateTs
}.

View File

@ -1,149 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2021, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 27. 4 2021 4:38
%%%-------------------------------------------------------------------
-module(router_model).
-author("licheng5").
-include("iot.hrl").
-include_lib("stdlib/include/qlc.hrl").
%% API
-export([get_router/1, get_routers/3, add_router/1, change_status/2, delete/1, table_size/0, get_all_routers/0, get_all_valid_routers/0]).
-export([to_map/1, match_spec/1]).
get_router(RouterId) when is_integer(RouterId) ->
case mnesia:dirty_read(router, RouterId) of
[Router] ->
{ok, Router};
_ ->
undefined
end.
%% app信息
-spec get_all_routers() -> {ok, Routers :: list()} | {error, Reason :: any()}.
get_all_routers() ->
Fun = fun() ->
Q = qlc:q([E || E <- mnesia:table(router)]),
qlc:e(Q)
end,
case mnesia:transaction(Fun) of
{atomic, Routers} ->
{ok, sort(Routers)};
{aborted, Reason} ->
{error, Reason}
end.
%% app信息
-spec get_all_valid_routers() -> {ok, Routers :: list()} | {error, Reason :: any()}.
get_all_valid_routers() ->
Fun = fun() ->
Q = qlc:q([E || E <- mnesia:table(router), E#router.status =:= 1]),
qlc:e(Q)
end,
case mnesia:transaction(Fun) of
{atomic, Routers} ->
{ok, sort(Routers)};
{aborted, Reason} ->
{error, Reason}
end.
%% app信息
-spec get_routers(Filter :: any(), Start :: integer(), Limit :: integer()) ->
{ok, Items :: list(), TotalNum :: integer()} |
{error, Reason :: any()}.
get_routers(Spec, Start, Limit) when is_integer(Limit), is_integer(Start), Start >= 0, Limit > 0 ->
Routers0 = mnesia:dirty_select(router, [Spec]),
Routers = sort(Routers0),
Len = length(Routers),
case Len >= Start + 1 of
true ->
{ok, lists:sublist(Routers, Start + 1, Limit), Len};
false ->
{ok, [], Len}
end.
-spec add_router(Router :: #router{}) -> ok | {error, Reason :: any()}.
add_router(Router = #router{}) ->
case mnesia:transaction(fun() -> mnesia:write(router, Router, write) end) of
{atomic, ok} ->
ok;
{aborted, Error} ->
{error, Error}
end.
-spec change_status(RouterId :: integer(), Status :: integer()) -> ok | {error, Reason :: any()}.
change_status(RouterId, Status) when is_integer(RouterId), is_integer(Status) ->
Fun = fun() ->
case mnesia:read(router, RouterId) of
[] ->
mnesia:abort(<<"router not found">>);
[Router] ->
mnesia:write(host, Router#router{status = Status}, write)
end
end,
case mnesia:transaction(Fun) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
{error, Reason}
end.
-spec delete(HostId :: integer()) -> ok | {error, Reason :: any()}.
delete(RouterId) when is_integer(RouterId) ->
case mnesia:transaction(fun() -> mnesia:delete(router, RouterId, write) end) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
{error, Reason}
end.
%%
match_spec(Specs) when is_list(Specs) ->
MatchHead = #router{name = '$1', _ = '_'},
Result = ['$_'],
Guard = guard(Specs),
Result = ['$_'],
{MatchHead, Guard, Result}.
guard(Specs) when is_list(Specs) ->
guard(Specs, []).
guard([], Guard) ->
Guard;
guard([{name, Name}|Tail], Guard) when Name =/= <<"">> ->
guard(Tail, [{'=:=', '$1', Name}|Guard]);
guard([_|Tail], Guard) ->
guard(Tail, Guard).
%% app表的数据大小
table_size() ->
mnesia:table_info(router, size).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% helper methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
sort(Routers) when is_list(Routers) ->
lists:sort(fun(#router{router_id = Id0}, #router{router_id = Id1}) -> Id0 < Id1 end, Routers).
to_map(#router{router_id = RouterId, name = Name, status = Status, endpoint = Endpoint}) ->
EndpointInfo = case Endpoint of
undefined ->
#{};
#http_endpoint{url = Url} ->
#{
<<"type">> => <<"http">>,
<<"url">> => Url
};
#kafka_endpoint{} ->
#{}
end,
#{
<<"router_id">> => RouterId,
<<"name">> => Name,
<<"status">> => Status,
<<"endpoint">> => EndpointInfo
}.

View File

@ -1,100 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2021, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 27. 4 2021 4:38
%%%-------------------------------------------------------------------
-module(scenario_deploy_model).
-author("licheng5").
-include("iot.hrl").
-include_lib("stdlib/include/qlc.hrl").
-define(TAB_NAME, scenario_deploy).
%% API
-export([get_host_deploy_list/1, get_scenario_deploy_list/1, add_deploy/1, change_status/2, delete/1, table_size/0]).
-export([to_map/1]).
-spec get_host_deploy_list(HostId :: binary()) -> {ok, List :: [#scenario_deploy{}]} | {error, Reason :: any()}.
get_host_deploy_list(HostId) when is_binary(HostId) ->
Fun = fun() ->
Q = qlc:q([E || E <- mnesia:table(?TAB_NAME), E#scenario_deploy.host_id =:= HostId]),
qlc:e(Q)
end,
case mnesia:transaction(Fun) of
{atomic, Items} ->
{ok, sort(Items)};
{aborted, Reason} ->
{error, Reason}
end.
-spec get_scenario_deploy_list(ScenarioId :: integer()) -> {ok, List :: [#scenario_deploy{}]} | {error, Reason :: any()}.
get_scenario_deploy_list(ScenarioId) when is_integer(ScenarioId) ->
Fun = fun() ->
Q = qlc:q([E || E <- mnesia:table(?TAB_NAME), E#scenario_deploy.scenario_id =:= ScenarioId]),
qlc:e(Q)
end,
case mnesia:transaction(Fun) of
{atomic, Items} ->
{ok, sort(Items)};
{aborted, Reason} ->
{error, Reason}
end.
-spec add_deploy(Deploy :: #scenario_deploy{}) -> ok | {error, Reason :: any()}.
add_deploy(Deploy = #scenario_deploy{}) ->
case mnesia:transaction(fun() -> mnesia:write(?TAB_NAME, Deploy, write) end) of
{atomic, _} ->
ok;
{aborted, Error} ->
{error, Error}
end.
-spec change_status(DeployId :: integer(), Status :: integer()) -> ok | {error, Reason :: any()}.
change_status(DeployId, Status) when is_integer(DeployId), is_integer(Status) ->
Fun = fun() ->
case mnesia:read(?TAB_NAME, DeployId) of
[] ->
mnesia:abort(<<"deploy not found">>);
[Deploy] ->
mnesia:write(?TAB_NAME, Deploy#scenario_deploy{status = Status}, write)
end
end,
case mnesia:transaction(Fun) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
{error, Reason}
end.
-spec delete(DeployId :: binary()) -> ok | {error, Reason :: any()}.
delete(DeployId) when is_integer(DeployId) ->
case mnesia:transaction(fun() -> mnesia:delete(?TAB_NAME, DeployId, write) end) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
{error, Reason}
end.
%% app表的数据大小
table_size() ->
mnesia:table_info(?TAB_NAME, size).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% helper methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
sort(DeployList) when is_list(DeployList) ->
lists:sort(fun(#scenario_deploy{deploy_id = Id0}, #scenario_deploy{deploy_id = Id1}) -> Id0 < Id1 end, DeployList).
to_map(#scenario_deploy{deploy_id = DeployId, scenario_id = ScenarioId, host_id = HostId, create_ts = CreateTs, update_ts = UpdateTs, status = Status}) ->
#{
<<"deploy_id">> => DeployId,
<<"scenario_id">> => ScenarioId,
<<"host_id">> => HostId,
<<"create_ts">> => CreateTs,
<<"update_ts">> => UpdateTs,
<<"status">> => Status
}.

View File

@ -1,115 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2021, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 27. 4 2021 4:38
%%%-------------------------------------------------------------------
-module(scenario_model).
-author("licheng5").
-include("iot.hrl").
-include_lib("stdlib/include/qlc.hrl").
-define(TAB_NAME, scenario).
%% API
-export([get_scenario/1, get_scenario_list/3, add_scenario/1, change_status/2, delete/1, table_size/0]).
-export([to_map/1, match_spec/1]).
-spec get_scenario(ScenarioId :: integer()) -> {ok, #scenario{}} | undefined.
get_scenario(ScenarioId) when is_integer(ScenarioId) ->
case mnesia:dirty_read(?TAB_NAME, ScenarioId) of
[Scenario = #scenario{}] ->
{ok, Scenario};
_ ->
undefined
end.
%% app信息
-spec get_scenario_list(Filter :: any(), Start :: integer(), Limit :: integer()) ->
{ok, Items :: list(), TotalNum :: integer()} |
{error, Reason :: any()}.
get_scenario_list(Spec, Start, Limit) when is_integer(Limit), is_integer(Start), Start >= 0, Limit > 0 ->
Scenarios0 = mnesia:dirty_select(?TAB_NAME, [Spec]),
Scenarios = sort(Scenarios0),
Len = length(Scenarios),
case Len >= Start + 1 of
true ->
{ok, lists:sublist(Scenarios, Start + 1, Limit), Len};
false ->
{ok, [], Len}
end.
-spec add_scenario(Scenario :: #scenario{}) -> ok | {error, Reason :: any()}.
add_scenario(Scenario = #scenario{}) ->
case mnesia:transaction(fun() -> mnesia:write(?TAB_NAME, Scenario, write) end) of
{atomic, ok} ->
ok;
{aborted, Error} ->
{error, Error}
end.
-spec change_status(ScenarioId :: integer(), Status :: integer()) -> ok | {error, Reason :: any()}.
change_status(ScenarioId, Status) when is_integer(ScenarioId), is_integer(Status) ->
Fun = fun() ->
case mnesia:read(scenario, ScenarioId) of
[] ->
mnesia:abort(<<"scenario not found">>);
[Scenario] ->
mnesia:write(?TAB_NAME, Scenario#scenario{status = Status}, write)
end
end,
case mnesia:transaction(Fun) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
{error, Reason}
end.
-spec delete(ScenarioId :: binary()) -> ok | {error, Reason :: any()}.
delete(ScenarioId) when is_integer(ScenarioId) ->
case mnesia:transaction(fun() -> mnesia:delete(?TAB_NAME, ScenarioId, write) end) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
{error, Reason}
end.
%%
match_spec(Specs) when is_list(Specs) ->
MatchHead = #scenario{name = '$1', _ = '_'},
Result = ['$_'],
Guard = guard(Specs),
Result = ['$_'],
{MatchHead, Guard, Result}.
guard(Specs) when is_list(Specs) ->
guard(Specs, []).
guard([], Guard) ->
Guard;
guard([{name, Name}|Tail], Guard) when Name =/= <<"">> ->
guard(Tail, [{'=:=', '$1', Name}|Guard]);
guard([_|Tail], Guard) ->
guard(Tail, Guard).
%% app表的数据大小
table_size() ->
mnesia:table_info(scenario, size).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% helper methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
sort(Scenarios) when is_list(Scenarios) ->
lists:sort(fun(#scenario{scenario_id = Id0}, #scenario{scenario_id = Id1}) -> Id0 < Id1 end, Scenarios).
to_map(#scenario{scenario_id = ScenarioId, name = Name, desc = Desc, rule = Rule, update_ts = UpdateTs, status = Status}) ->
#{
<<"scenario_id">> => ScenarioId,
<<"name">> => Name,
<<"desc">> => Desc,
<<"rule">> => Rule,
<<"update_ts">> => UpdateTs,
<<"status">> => Status
}.

View File

@ -1,128 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2021, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 27. 4 2021 4:38
%%%-------------------------------------------------------------------
-module(service_model).
-author("licheng5").
-include("iot.hrl").
-include_lib("stdlib/include/qlc.hrl").
%% API
-export([get_host_services/1, get_service/2, add_service/1, change_status/2, delete/1, table_size/0]).
-export([update_metric/2, to_map/1]).
get_service(HostId, ServiceName) when is_binary(HostId), is_binary(ServiceName) ->
Fun = fun() ->
Q = qlc:q([E || E <- mnesia:table(service), E#service.host_id =:= HostId, E#service.name =:= ServiceName]),
qlc:e(Q)
end,
case mnesia:transaction(Fun) of
[Service] ->
{ok, Service};
_ ->
undefined
end.
get_host_services(HostId) when is_binary(HostId) ->
Fun = fun() ->
Q = qlc:q([E || E <- mnesia:table(service), E#service.host_id =:= HostId]),
qlc:e(Q)
end,
case mnesia:transaction(Fun) of
{atomic, Services} when is_list(Services) ->
{ok, Services};
{aborted, Error} ->
{error, Error}
end.
add_service(Service = #service{}) ->
case mnesia:transaction(fun() -> mnesia:write(service, Service, write) end) of
{atomic, _} ->
ok;
{aborted, Error} ->
{error, Error}
end.
change_status(ServiceId, Status) when is_binary(ServiceId), is_integer(Status) ->
Fun = fun() ->
case mnesia:read(service, ServiceId) of
[] ->
mnesia:abort(<<"host not found">>);
[Service] ->
NService = Service#service{status = Status},
mnesia:write(service, NService, write)
end
end,
case mnesia:transaction(Fun) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
{error, Reason}
end.
update_metric(ServiceId, Metrics) when is_binary(ServiceId), is_list(Metrics) ->
Fun = fun() ->
case mnesia:read(service, ServiceId) of
[] ->
mnesia:abort(<<"service not found">>);
[Service = #service{metrics = Metrics}] ->
NService = Service#service{metrics = Metrics},
mnesia:write(service, NService, write)
end
end,
case mnesia:transaction(Fun) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
{error, Reason}
end.
-spec delete(HostId :: binary()) -> ok | {error, Reason :: any()}.
delete(ServiceId) when is_binary(ServiceId) ->
case mnesia:transaction(fun() -> mnesia:delete(service, ServiceId, write) end) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
{error, Reason}
end.
%% app表的数据大小
table_size() ->
mnesia:table_info(service, size).
to_map(#service{service_id = ServiceId, host_id = HostId, name = Name, category = Category, apply_object = ApplyObject,
version = Version, execute_count = ExecuteCount, deploy_ts = DeployTs, metrics = Metrics, status = Status}) ->
Metrics1 = lists:map(fun(#service_metric{symbol = MetricSymbol, name = MetricName, last_value = LastValue, unit = Unit, queue = Queue, update_ts = UpdateTs}) ->
Q = lists:map(fun({Ts, Val}) -> #{<<"time">> => Ts, <<"value">> => Val} end, queue:to_list(Queue)),
#{
<<"symbol">> => MetricSymbol,
<<"name">> => MetricName,
<<"last_value">> => LastValue,
<<"queue">> => Q,
<<"unit">> => Unit,
<<"update_ts">> => UpdateTs
}
end, Metrics),
#{
<<"service_id">> => ServiceId,
<<"host_id">> => HostId,
<<"name">> => Name,
<<"category">> => Category,
<<"apply_object">> => ApplyObject,
<<"version">> => Version,
<<"execute_count">> => ExecuteCount,
<<"deploy_ts">> => DeployTs,
<<"metrics">> => Metrics1,
<<"status">> => Status
}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% helper methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

View File

@ -1,214 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2021, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 27. 4 2021 4:38
%%%-------------------------------------------------------------------
-module(terminal_model).
-author("licheng5").
-include("iot.hrl").
-include_lib("stdlib/include/qlc.hrl").
-define(TAB_NAME, terminal).
%% API
-export([next_id/0]).
-export([get_terminal/1, get_all_terminals/0, get_host_terminals/1, find_terminals/3, get_stat/0, table_size/0, match_spec/1]).
-export([change_status/2, delete/1, to_map/1, add_terminal/1, update_terminal/2]).
%% id信息
-spec next_id() -> Id :: integer().
next_id() ->
id_generator_model:new_terminal_id().
-spec get_terminal(TerminalId :: integer()) -> {ok, #terminal{}} | undefined.
get_terminal(TerminalId) when is_integer(TerminalId) ->
case mnesia:dirty_read(?TAB_NAME, TerminalId) of
[Terminal] ->
{ok, Terminal};
_ ->
undefined
end.
%% app信息
-spec get_all_terminals() -> {ok, Terminals :: [#terminal{}]} | {error, Reason :: any()}.
get_all_terminals() ->
Fun = fun() ->
Q = qlc:q([E || E <- mnesia:table(?TAB_NAME)]),
qlc:e(Q)
end,
case mnesia:transaction(Fun) of
{atomic, Terminals} when is_list(Terminals) ->
{ok, sort(Terminals)};
{aborted, Error} ->
{error, Error}
end.
-spec get_host_terminals(HostId :: binary()) -> {ok, Terminals :: [#terminal{}]} | {error, Reason :: any()}.
get_host_terminals(HostId) when is_binary(HostId) ->
Fun = fun() ->
Q = qlc:q([E || E <- mnesia:table(?TAB_NAME), E#terminal.host_id =:= HostId]),
qlc:e(Q)
end,
case mnesia:transaction(Fun) of
{atomic, Terminals} when is_list(Terminals) ->
{ok, sort(Terminals)};
{aborted, Error} ->
{error, Error}
end.
%% app信息
-spec find_terminals(Matches :: [{Name :: atom(), Val :: any()}], Start :: integer(), Limit :: integer()) ->
{ok, Terminals :: list(), TotalNum :: integer()} |
{error, Reason :: any()}.
find_terminals(Matches, Start, Limit) when is_list(Matches), is_integer(Limit), is_integer(Start), Start >= 0, Limit > 0 ->
MatchSpec = terminal_model:match_spec(Matches),
Terminals0 = mnesia:dirty_select(?TAB_NAME, [MatchSpec]),
Terminals = sort(Terminals0),
Len = length(Terminals),
case Len >= Start + 1 of
true ->
{ok, lists:sublist(Terminals, Start + 1, Limit), Len};
false ->
{ok, [], Len}
end.
%%
-spec get_stat() -> {ok, {Total :: integer(), Stat :: #{}}} | {error, Reason :: any()}.
get_stat() ->
Fun = fun() ->
mnesia:foldl(fun(#terminal{status = Status}, {Total, Acc}) ->
Num = maps:get(Status, Acc, 0),
{Total + 1, Acc#{Status => Num + 1}}
end, {0, #{}}, ?TAB_NAME)
end,
case mnesia:transaction(Fun) of
{atomic, Stat} ->
{ok, Stat};
{aborted, Reason} ->
{error, Reason}
end.
-spec add_terminal(Terminal :: #terminal{}) -> ok | {error, Reason :: any()}.
add_terminal(Terminal = #terminal{}) ->
case mnesia:transaction(fun() -> mnesia:write(?TAB_NAME, Terminal, write) end) of
{atomic, _} ->
ok;
{aborted, Error} ->
{error, Error}
end.
-spec update_terminal(TerminalId :: integer(), Fields :: #{}) -> ok | {error, Reason :: any()}.
update_terminal(TerminalId, Fields) when is_integer(TerminalId), is_map(Fields) ->
Fun = fun() ->
case mnesia:read(?TAB_NAME, TerminalId) of
[] ->
mnesia:abort(<<"terminal not found">>);
[Terminal] ->
NTerminal = lists:foldl(fun(E, Terminal0) ->
case E of
{<<"name">>, Name} when is_binary(Name) ->
Terminal0#terminal{name = Name};
{<<"serial_number">>, SerialNumber} when is_binary(SerialNumber) ->
Terminal0#terminal{serial_number = SerialNumber};
{<<"code">>, Code} when is_binary(Code) ->
Terminal0#terminal{code = Code};
{<<"access_protocol">>, AccessProtocol} when is_binary(AccessProtocol) ->
Terminal0#terminal{access_protocol = AccessProtocol};
{<<"product_id">>, ProductId} when is_integer(ProductId) ->
Terminal0#terminal{product_id = ProductId};
{<<"vendor_id">>, VendorId} when is_integer(VendorId) ->
Terminal0#terminal{vendor_id = VendorId};
{<<"model">>, Model} when is_binary(Model) ->
Terminal0#terminal{model = Model};
{<<"cell_id">>, CellId} when is_integer(CellId) ->
Terminal0#terminal{cell_id = CellId};
{Name, _} ->
mnesia:abort(<<"invalid: ", Name/binary>>)
end
end, Terminal, maps:to_list(Fields)),
mnesia:write(?TAB_NAME, NTerminal, write)
end
end,
case mnesia:transaction(Fun) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
{error, Reason}
end.
-spec change_status(TerminalId :: integer(), Status :: integer()) -> ok | {error, Reason :: any()}.
change_status(TerminalId, Status) when is_integer(TerminalId), is_integer(Status) ->
Fun = fun() ->
case mnesia:read(?TAB_NAME, TerminalId) of
[] ->
mnesia:abort(<<"terminal not found">>);
[Terminal] ->
mnesia:write(?TAB_NAME, Terminal#host{status = Status}, write)
end
end,
case mnesia:transaction(Fun) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
{error, Reason}
end.
-spec delete(TerminalId :: integer()) -> ok | {error, Reason :: any()}.
delete(TerminalId) when is_integer(TerminalId) ->
case mnesia:transaction(fun() -> mnesia:delete(?TAB_NAME, TerminalId, write) end) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
{error, Reason}
end.
%% app表的数据大小
table_size() ->
mnesia:table_info(host, size).
%%
match_spec(Specs) when is_list(Specs) ->
MatchHead = #host{model = '$1', cell_id = '$2', _ = '_'},
Guard = guard(Specs),
Result = ['$_'],
{MatchHead, Guard, Result}.
guard(Specs) when is_list(Specs) ->
guard(Specs, []).
guard([], Guard) ->
Guard;
guard([{model, Model}|Tail], Guard) when Model =/= <<"">> ->
guard(Tail, [{'=:=', '$1', Model}|Guard]);
guard([{cell, CellId}|Tail], Guard) when CellId > 0 ->
guard(Tail, [{'=:=', '$2', CellId}|Guard]);
guard([_|Tail], Guard) ->
guard(Tail, Guard).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% helper methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
sort(Terminals) when is_list(Terminals) ->
lists:sort(fun(#terminal{terminal_id = Id0}, #terminal{terminal_id = Id1}) -> Id0 < Id1 end, Terminals).
to_map(#terminal{terminal_id = TerminalId, host_id = HostId, serial_number = SerialNumber, name = Name, code = Code, access_protocol = AccessProtocol,
product_id = ProductId, vendor_id = VendorId, model = Model, cell_id = CellId, status = Status, update_ts = UpdateTs}) ->
#{
<<"terminal_id">> => TerminalId,
<<"host_id">> => HostId,
<<"serial_number">> => SerialNumber,
<<"name">> => Name,
<<"code">> => Code,
<<"access_protocol">> => AccessProtocol,
<<"product_id">> => ProductId,
<<"vendor_id">> => VendorId,
<<"model">> => Model,
<<"cell_id">> => CellId,
<<"status">> => Status,
<<"update_ts">> => UpdateTs
}.

View File

@ -0,0 +1,178 @@
%%%-------------------------------------------------------------------
%%% @author aresei
%%% @copyright (C) 2018, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 29. 2018 17:01
%%%-------------------------------------------------------------------
-module(mysql_client).
-author("aresei").
-define(POOL_NAME, mysql_pool).
%% API
-export([get_row/1, get_row/2, get_all/1, get_all/2]).
-export([update/3, update_by/1, update_by/2, insert/3]).
-export([test/0]).
test() ->
Result = get_row(<<"select * from Carport limit 10">>),
lager:info("[mysql_client] the result is: ~p", [Result]),
Result1 = get_all(<<"select * from Carport limit 10">>),
lager:info("[mysql_client] the result is: ~p", [Result1]),
InsertResult = insert(<<"CarInfo">>, [{<<"owner_name">>, <<"anlicheng">>}], false),
lager:info("[mysql_client] the insert id is: ~p", [InsertResult]),
UpdateResult = update_by(<<"update CarInfo set owner_name = AiYaLing123 where car_id = ? limit 1">>, [1]),
lager:info("[mysql_client] the update result is: ~p", [UpdateResult]),
ok.
%%
-spec get_row(Sql::binary()) -> {ok, Record::map()} | undefined.
get_row(Sql) when is_binary(Sql) ->
lager:debug("[mysql_client] the get_row sql is: ~p", [Sql]),
poolboy:transaction(?POOL_NAME, fun(ConnPid) ->
case mysql:query(ConnPid, Sql) of
{ok, Names, [Row | _]} ->
{ok, maps:from_list(lists:zip(Names, Row))};
{ok, _, []} ->
undefined;
Error ->
lager:warning("[mysql_client] get error: ~p", [Error]),
undefined
end
end).
-spec get_row(Sql::binary(), Params::list()) -> {ok, Record::map()} | undefined.
get_row(Sql, Params) when is_binary(Sql), is_list(Params) ->
lager:debug("[mysql_client] the get_row sql is: ~p, params: ~p", [Sql, Params]),
poolboy:transaction(?POOL_NAME, fun(ConnPid) ->
case mysql:query(ConnPid, Sql, Params) of
{ok, Names, [Row | _]} ->
{ok, maps:from_list(lists:zip(Names, Row))};
{ok, _, []} ->
undefined;
Error ->
lager:warning("[mysql_client] get error: ~p", [Error]),
undefined
end
end).
-spec get_all(Sql::binary()) -> {ok, Rows::list()} | {error, Reason :: any()}.
get_all(Sql) when is_binary(Sql) ->
lager:debug("[mysql_client] the get_all sql is: ~p", [Sql]),
poolboy:transaction(?POOL_NAME, fun(ConnPid) ->
case mysql:query(ConnPid, Sql) of
{ok, Names, Rows} ->
{ok, lists:map(fun(Row) -> maps:from_list(lists:zip(Names, Row)) end, Rows)};
Error ->
lager:warning("[mysql_client] get error: ~p", [Error]),
Error
end
end).
-spec get_all(Sql::binary(), Params::list()) -> {ok, Rows::list()} | {error, Reason::any()}.
get_all(Sql, Params) when is_binary(Sql), is_list(Params) ->
lager:debug("[mysql_client] the get_all sql is: ~p, params: ~p", [Sql, Params]),
poolboy:transaction(?POOL_NAME, fun(ConnPid) ->
case mysql:query(ConnPid, Sql, Params) of
{ok, Names, Rows} ->
{ok, lists:map(fun(Row) -> maps:from_list(lists:zip(Names, Row)) end, Rows)};
Error ->
lager:warning("[mysql_client] get error: ~p", [Error]),
{ok, []}
end
end).
-spec insert(Table :: binary(), Fields :: map() | list(), boolean()) -> ok | {ok, InsertId :: integer()} | {error, Reason :: any()}.
insert(Table, Fields, FetchInsertId) when is_binary(Table), is_map(Fields), is_boolean(FetchInsertId) ->
insert(Table, maps:to_list(Fields), FetchInsertId);
insert(Table, Fields, FetchInsertId) when is_binary(Table), is_list(Fields), is_boolean(FetchInsertId) ->
{Keys, Values} = kvs(Fields),
FieldSql = iolist_to_binary(lists:join(<<", ">>, Keys)),
Placeholders = lists:duplicate(length(Keys), <<"?">>),
ValuesPlaceholder = iolist_to_binary(lists:join(<<", ">>, Placeholders)),
Sql = <<"INSERT INTO ", Table/binary, "(", FieldSql/binary, ") VALUES(", ValuesPlaceholder/binary, ")">>,
lager:debug("[mysql_client] the insert sql is: ~p, params: ~p", [Sql, Values]),
poolboy:transaction(?POOL_NAME, fun(ConnPid) ->
case mysql:query(ConnPid, Sql, Values) of
ok ->
case FetchInsertId of
true ->
InsertId = mysql:insert_id(ConnPid),
{ok, InsertId};
false ->
ok
end;
Error ->
Error
end
end).
update_by(UpdateSql) when is_binary(UpdateSql) ->
lager:debug("[mysql_client] updateBySql sql: ~p", [UpdateSql]),
poolboy:transaction(?POOL_NAME, fun(ConnPid) ->
case mysql:query(ConnPid, UpdateSql) of
ok ->
AffectedRows = mysql:affected_rows(ConnPid),
{ok, AffectedRows};
Error ->
Error
end
end).
-spec update_by(UpdateSql :: binary(), Params :: list()) -> {ok, AffectedRows :: integer()} | {error, Reason :: any()}.
update_by(UpdateSql, Params) when is_binary(UpdateSql) ->
lager:debug("[mysql_client] updateBySql sql: ~p, params: ~p", [UpdateSql, Params]),
poolboy:transaction(?POOL_NAME, fun(ConnPid) ->
case mysql:query(ConnPid, UpdateSql, Params) of
ok ->
AffectedRows = mysql:affected_rows(ConnPid),
{ok, AffectedRows};
Error ->
Error
end
end).
-spec update(Sql :: binary(), Fields :: map(), WhereFields :: map()) -> {ok, AffectedRows::integer()} | {error, Reason::any()}.
update(Table, Fields, WhereFields) when is_binary(Table), is_map(Fields), is_map(WhereFields) ->
%% set
{SetKeys, SetVals} = kvs(Fields),
SetKeys1 = lists:map(fun(K) when is_binary(K) -> <<"`", K/binary, "` = ?">> end, SetKeys),
SetSql = iolist_to_binary(lists:join(<<", ">>, SetKeys1)),
%% where
{WhereKeys, WhereVals} = kvs(WhereFields),
WhereKeys1 = lists:map(fun(K) when is_binary(K) -> <<"`", K/binary, "` = ?">> end, WhereKeys),
WhereSql = iolist_to_binary(lists:join(<<" AND ">>, WhereKeys1)),
Params = SetVals ++ WhereVals,
Sql = <<"UPDATE ", Table/binary, " SET ", SetSql/binary, " WHERE ", WhereSql/binary>>,
lager:debug("[mysql_client] update sql is: ~p, params: ~p", [Sql, Params]),
poolboy:transaction(?POOL_NAME, fun(ConnPid) ->
case mysql:query(ConnPid, Sql, Params) of
ok ->
AffectedRows = mysql:affected_rows(ConnPid),
{ok, AffectedRows};
Error ->
lager:error("[mysql_client] update sql: ~p, params: ~p, get a error: ~p", [Sql, Params, Error]),
Error
end
end).
-spec kvs(Fields :: map() | list()) -> {Keys :: list(), Values :: list()}.
kvs(Fields) when is_map(Fields) ->
kvs(maps:to_list(Fields));
kvs(Fields) when is_list(Fields) ->
{Keys0, Values0} = lists:foldl(fun({K, V}, {Acc0, Acc1}) -> {[K|Acc0], [V|Acc1]} end, {[], []}, Fields),
{lists:reverse(Keys0), lists:reverse(Values0)}.

View File

@ -1,7 +1,7 @@
[
{iot, [
{http_server, [
{port, 8080},
{port, 18080},
{acceptors, 500},
{max_connections, 10240},
{backlog, 10240}
@ -9,17 +9,44 @@
%% 目标服务器地址
{emqx_server, [
{host, {103, 113, 157, 7}},
{host, {39, 98, 184, 67}},
{port, 1883},
{tcp_opts, []},
{username, "test"},
{password, "test1234"},
{keepalive, 86400},
{retry_interval, 5}
]},
{pools, [
%% mysql连接池配置
{mysql_pool,
[{size, 10}, {max_overflow, 20}, {worker_module, mysql}],
[
{host, {39, 98, 184, 67}},
{port, 3306},
{user, "nannonguser"},
{password, "nannong@Fe7w"},
{database, "nannong"},
{queries, [<<"set names utf8">>]}
]
},
%% influxdb数据库配置
{influx_pool,
[{size, 100}, {max_overflow, 200}, {worker_module, influx_client}],
[
{host, "39.98.184.67"},
{port, 8086},
{token, <<"HcnjcIaoPWGxnDOHpW2HV-xBsZSyqtC2UR98SO8LNgNhczuNyU6PQ4s1Blfz7vKy-CB_A-Kyjzed_EX7Ak2RAA==">>}
]
}
]}
]},
%% 系统日志配置系统日志为lager, 支持日志按日期自动分割
{lager, [
{colored, true},

299
docs/host-mqtt-jiaohu.md Normal file
View File

@ -0,0 +1,299 @@
# 交互流程数据格式说明
## 主题说明
### system/upstream qos = 1
用于主机注册到平台
### host/upstream/$uuid qos = 1
用于主机向平台汇报数据
### host/downstream/$uuid qos = 2
用于平台向主机下发所有的指令信息
## 需要注意的点
1. 主机的uuid是采用主动发现的机制因此host主机在启动时需要先subscribe主题: host/downstream/${uuid}
## register主机注册会话
1. 请求: 设备端向主题`system/upstream`发送注册信息
```json
{
"method": "register",
"params": {
"uuid": "$uuid",
"os_version": "系统版本",
}
}
```
2. 响应: `host/downstream/$uuid`
t = 0
```json
{
"code": 0 | 1, // 0表示失败1表示成功
// 内容
"m": "$bytes"
}
```
## create_session主机注册会话
register的时候设备端向主题`host/upstream/${uuid}`发送注册信息由于topic里面已经携带了主机标识因此body里面不需要$uuid, 该信息格式如下:
```json
{
"method": "create_session",
"params": {
"pub_key": "该客户端自己生成的公钥"
}
}
```
其中,`pub_key`表示客户端自身的公钥该公钥用于与服务端交换aes密钥。
需要注意,这里的交互逻辑有变更(topic: host/downstream/$uuid)
1. 当主机未激活时,返回: #{<<"a">> => false, <<"aes">> => <<"">>},
2. 当主机已经激活时,返回: #{<<"a">> => true, <<"aes">> => Aes}
3. 当主机超过一定的时间没有收到任何返回的时候需要重试执行create_session操作
t = 10
```json
{
"a": true/false, // 表示是授权还是拒绝授权
"aes": "", // 如果a字段为true表示服务端允许授权该aes为一个加密字符串以后的通信服务端和设备端都使用该aes密钥进行加解密。
}
```
## 主机授权消息格式
t = 8
```json
{
"auth": true/false, // true表示授权此时aes的值不为空false表示取消授权
"aes": "",
"reply": {
"topic": "", // 主机端操作成功后需要回写的topic名称
"assoc": "" // 关联的信息,回写的时候需要带上
}
}
// 回写的消息格式, 其中topic为: reply.topic的值
{
"code": 0 | 1, // 0表示操作失败1表示成功
messge: "",
assoc: "下发给你值"
}
```
## 数据上传结构
设备端在采集到数据之后会向topic为`host/upstream/$uuid`发送消息,消息格式如下:
```json
{
"methods": "data",
"params": "$bytes"
}
```
其中`params`代表具体的采集信息,这部分数据通过与服务端交互商量的: base64:encode(aes加密),加密之前是一个列表,列表里面的数据格式如下:
```json:
[
{
"service_name": "从该设备端的哪个服务采集的数据",
"node_name": "节点名称", insert到influxdb的时候就是表名
"at": int, 精确到毫秒
// 该微服务采集的数据是一个包含map的列表类型map的内容可以由微服务自己指定
// 目前一般的格式是"metric-name": $value样式的数据
"fields": [
{
"name1": "test"
"name2": 124,
"name3": false,
"device_id": $uuid 非设备产生的device_id为空
}
],
// 微服务自身可以生成tag用于微服务指定自己的一些性质目前使用得不多以后可以扩展
// 是一个map[string]string类型的数据
"tags": {
"tag1": "value1",
"tag2", "value2"
}
// todo 在insert数据到influxdb的时候需要增加service_name + host_uuid
}
]
```
## 命令下发结构
命令下发,用于服务端或者其他的系统,通过调用接口,向设备端发送消息,设备端会监听`host/downstream/$uuid`的消息。
服务端在发送之前应先用该客户端的aes密钥进行加密将加密之后的二进制数据发送到该topic。
TODO 命令下发是需要增加当前的时间戳host主机用来协调任务的一致性
加密前的消息结构如下:
// 消息类型,目前支持四种消息类型:
// 1代表参数下发就是向该设备端的微服务发送消息,该消息会辗转发送给微服务进行处理比如设置modbus微服务的波特率等消息
// 2代表采集向下发比如设置某个设备短上的modbus微服务采集某个地址的数据
// 3代表下发微服务文件。
// 4代表下发数据流图这个指令用于设置设备端上各个微服务之间的逐句流转。
占用数据项的第一个字节
"t": 1|2|3|4,
```json
{
// 针对不同的命令类型,这个字段里的`to``m`数据有所不同,具体在下面的小节描述
// 任务id服务端在下发数据的时候需要生成一个唯一的uuid
// 用于标识一个任务
"t_id": "任务id",
// 表示发给哪个微服务,这里是服务的标识,即服务名称
"to": "",
// 命令执行的超时时间,单位为秒
"t": 10,
// 实际内容
"m": "$bytes",
}
```
下面介绍几种下发类型:
### 参数下发的结构
对于参数下发下发内容中的m为一个`map[string]interface{}`结构,用于向某个微服务发送参数,具体参数内容由微服务的参数配置提供。
### 采集项下发的结构
采集项下发时下发内容中的m为一个`[]map[string]interface{}`结构的列表,每一个条目是一个采集项内容,具体采集向内容由微服务的采集项配置提供。
### 微服务下发的结构
在微服务下发中,`to`字段会被忽略可以填写空字符串而m字段为json化之后的数据json化之前结构如下
```json
{
"f": "微服务名",
"v": "微服务版本"
"k": "微服务下载的token"
"md5": "微服务的md5值用于验证下载完整性"
// ms表示是微服务config表示配置文件self表示efka的新版本
"t": "ms|config|self"
// o代表oldversion老版本如果t为ms且o不为空字符串
// 则表示要升级微服务版本,老版本的内容会被删除和替换。
"o": "old-version"
}
```
## ping结构
ping结构会上传到topic为"host/upstream/$uuid", 结构体由msgpack格式编码, ping结构每隔20秒发送一次, 其中params的数据格式为: base64:encode(aes("加密"))
```json
{
"method": "ping",
"params": {
// 硬件信息目前有剩余内存剩余磁盘和cpu负载
// 剩余内存单位为mb
"memory": {
"total": 1024, // 内存数mb
"used": "$int" // 剩余内存数
},
"disk": {
"total": 1024, // 硬盘容量GB
"used": "$int" // 剩余硬盘内容GB
},
"cpu_load": $float, // 浮点数
"cpu_temperature": $float // 稳定信息
"cpu_core": $int,
"boot_time": 2000, // 启动时间
"efka_version": "1.0.0", // 客户端版本
"kernel_arch": "arm64", // 客户端硬件架构
"province": "", // 所在省
"city": "", // 所在市
"adcode": 100, // 所在城市的编号
"ips": [
"ip地址1",
"ip地址2"
],
// 接口信息
"interfaces": [
{
"status": 0|1, "接口状态0离线1在线"
"name": "接口名称",
"desc": "接口描述",
}
]
}
}
```
## 命令下发步骤上报结构
在任务下发之后设备端会根据命令到哪个环节进行步骤上报上报的消息写入到topic为`host/upstream/$uuid`,上报结构如下:
```json
{
"method": "feedback_step",
"params": {
"task_id": "任务的task id",
// sc为step code具体地
// 0代表该任务开始了服务端创建该任务之后是这个代码
// 1代表任务被分发了服务端向nats(mqtt)发送消息之后,是这个代码
// 2代表任务被设备端接收到了
// 3代表该任务已经被发送给微服务进行处理了
// 4代表该任务已经被微服务收到了微服务正在处理
// 5代表任务已经完成微服务已经处理完成。
"code": $int
}
}
```
有了这个步骤,最后任务超时等情况,就可以知道任务卡在哪里。
## 命令下发结果上报结构
任务在微服务处理完成之后,设备端会向服务端反馈任务执行的结果。该结果会写入`host/upstream/$uuid`,上报的结构如下:
```json
{
"method": "feedback_result",
"params": {
"task_id": "任务id",
// unix nano类型
"time": $int,
// 返回的结果码0代表成功其他代表出错
"code": $int,
"reason": "任务执行的结果",
"error": "错误消息当c为非0时这个字段会表示出错消息",
// 返回任务类型1表示任务是微服务下发0代表是命令下发
"type": 0 | 1,
}
}
```
## inform结构
inform用于客户端主动通知服务端本地事情的发生比如自身的某个微服务下线了就会发送一个inform信息给服务端。 topic: host/upstream/$uuid
inform信息会发送给``, 结构如下:
```json
{
"method": "inform",
"params": {
"at": $int64,
// 微服务信息
"services": [{
"name": "微服务名称",
"version": "微服务版本",
"version_copy": "微服务副本",
// 微服务是否在线0表示离线1表示在线
"status": 0|1
}]
}
}
```

View File

@ -1,233 +0,0 @@
# 交互流程数据格式说明
## register
register的时候设备端向`server.register`发送注册信息,该信息格式如下:
```json
{
"c_id": "client_id",
"r": "该客户端自己生成的公钥",
"m": $object,
}
```
其中,`c_id`代表客户端本身的id用于唯一标识一个客户端`r`表示客户端自身的公钥该公钥用于与服务端交换aes密钥。
`m`是设备端的一些固有属性,目前结构如下:
```json
{
"cpu_core": 4, // 设备cpu核心数
"memory": 1024, // 内存数MB
"disk": 1024, // 硬盘容量GB
"boot_time": 2000, // 启动时间
"efka_version": "1.0.0", // 客户端版本
"kernel_arch": "arm64", // 客户端硬件架构
"province": "", // 所在省
"city": "", // 所在市
"adcode": 100, // 所在城市的编号
"IPv4 1": "ip地址1",
"IPv4 2": "ip地址2",
...
}
```
在服务端在第一次收到该设备端的注册请求之后会先将该信息保存下来当用户在操作界面上对该设备进行授权之后服务端会生成一个AES密钥用该设备端的公钥进行加密然后向`client.auth.$clientid`写入授权信息客户端收到之后可以得到本身的授权情况。服务端用RSA公钥加密的结构如下
```json
{
"a": true/false, // 表示是授权还是拒绝授权
"aes": "", // 如果a字段为true表示服务端允许授权该aes为一个加密字符串以后的通信服务端和设备端都使用该aes密钥进行加解密。
"reply": "client.reply.$uuid" // 服务端设置的一个客户端回复的topic如果服务端在5秒之内没有回复就认为这个动作失败服务端就会反馈给操作界面的用户。
}
```
## 数据上传结构
设备端在采集到数据之后,会向`server.data`发送消息,消息格式如下:
```json
{
"c_id": "设备端ID",
"d": $bytes
}
```
其中`c_id`是自身的`clientid``d`代表具体的采集信息这部分数据通过与服务端交互商量的aes加密加密之前是一个列表列表里面的数据格式如下:
```json:
[
{
"client_id": "设备端ID",
"service_name": "从该设备端的哪个服务采集的数据",
"time": $time, // 采集时间unixnano的int64
// 该微服务采集的数据是一个包含map的列表类型map的内容可以由微服务自己指定
// 目前一般的格式是"metric-name": $value样式的数据
"data": [
{
"name1": "test"
"name2": 124,
"name3": false
}
],
// 微服务自身可以生成tag用于微服务指定自己的一些性质目前使用得不多以后可以扩展
// 是一个map[string]string类型的数据
"tag": {
"tag1": "value1",
"tag2", "value2"
}
}
]
```
## 命令下发结构
命令下发,用于服务端或者其他的系统,通过调用接口,向设备端发送消息,设备端会监听`clients.cmd.$id`的消息。
服务端在发送之前应先用该客户端的aes密钥进行加密将加密之后的二进制数据发送到该topic。
加密前的消息结构如下:
```json
{
// 消息类型,目前支持四种消息类型:
// 1代表参数下发就是向该设备端的微服务发送消息,该消息会辗转发送给微服务进行处理比如设置modbus微服务的波特率等消息
// 2代表采集向下发比如设置某个设备短上的modbus微服务采集某个地址的数据
// 3代表下发微服务文件。
// 4代表下发数据流图这个指令用于设置设备端上各个微服务之间的逐句流转。
"t": 1|2|3|4,
// 针对不同的命令类型,这个字段里的`to``m`数据有所不同,具体在下面的小节描述
"b": {
// 任务id服务端在下发数据的时候需要生成一个唯一的uuid
// 用于标识一个任务
"t_id": "任务id",
// 表示发给哪个微服务,这里是服务的标识,即服务名称
"to": "",
// 命令执行的超时时间,单位为秒
"t": 10,
// 实际内容
"m": $bytes
}
}
```
下面介绍几种下发类型:
### 参数下发的结构
对于参数下发下发内容中的m为一个`map[string]interface{}`结构,用于向某个微服务发送参数,具体参数内容由微服务的参数配置提供。
### 采集项下发的结构
采集项下发时下发内容中的m为一个`[]map[string]interface{}`结构的列表,每一个条目是一个采集项内容,具体采集向内容由微服务的采集项配置提供。
### 微服务下发的结构
在微服务下发中,`to`字段会被忽略可以填写空字符串而m字段为json化之后的数据json化之前结构如下
```json
{
"f": "微服务名",
"v": "微服务版本"
"k": "微服务下载的token"
"md5": "微服务的md5值用于验证下载完整性"
// ms表示是微服务config表示配置文件self表示efka的新版本
"t": "ms|config|self"
// o代表oldversion老版本如果t为ms且o不为空字符串
// 则表示要升级微服务版本,老版本的内容会被删除和替换。
"o": "old-version"
}
```
## ping结构
ping结构会上传到"server.ping", 结构体由msgpack格式编码, ping结构每隔20秒发送一次
```json
{
"c": "client_id",
// 这个ping上传的时间
"at": $int64,
// 硬件信息目前有剩余内存剩余磁盘和cpu负载
"h": {
// 剩余内存单位为MB
"fm": $int
// 剩余磁盘单位为MB
"fd": $int,
// cpu负载是一个浮点数
"cp": $float
}
}
```
## 命令下发步骤上报结构
在任务下发之后,设备端会根据命令到哪个环节,进行步骤上报,上报的消息写入到`server.step.feedback`,上报结构如下:
```json
{
"c_id": "设备端id",
"d": "aes加密后的二进制数据"
}
```
其中,`d`是具体数据经过aes加密后的二进制数据加密前的结构如下
```json
{
"t_id": "任务的task id",
// sc为step code具体地
// 0代表该任务开始了服务端创建该任务之后是这个代码
// 1代表任务被分发了服务端向nats(mqtt)发送消息之后,是这个代码
// 2代表任务被设备端接收到了
// 3代表该任务已经被发送给微服务进行处理了
// 4代表该任务已经被微服务收到了微服务正在处理
// 5代表任务已经完成微服务已经处理完成。
"sc": $int
}
```
有了这个步骤,最后任务超时等情况,就可以知道任务卡在哪里。
## 命令下发结果上报结构
任务在微服务处理完成之后,设备端会向服务端反馈任务执行的结果。该结果会写入`server.result.feedback`,上报的结构如下:
```json
{
"c_id": "设备端id",
"d": "aes加密后的二进制数据"
}
```
其中,`d`是aes加密后的二进制数据加密前的结构如下
```json
{
"t_id": "任务id",
// unix nano类型
"t": $int,
// 返回的结果码0代表成功其他代表出错
"c": $int,
"r": "任务执行的结果",
"e": "错误消息当c为非0时这个字段会表示出错消息",
// 返回任务类型1表示任务是微服务下发0代表是命令下发
"t": 0 | 1,
}
```
## inform结构
inform用于客户端主动通知服务端本地事情的发生比如自身的某个微服务下线了就会发送一个inform信息给服务端。
inform信息会发送给``, 结构如下:
```json
{
"c": "client_id",
// 这个inform上传的时间
"at": $int64,
// 微服务信息
"s": [{
"n": "微服务名称",
"v": "微服务版本",
"c": "微服务副本",
// 微服务是否在线0表示离线1表示在线
"s": 0|1
}]
}
```

View File

@ -5,6 +5,7 @@
{sync, ".*", {git, "https://github.com/rustyio/sync.git", {branch, "master"}}},
{cowboy, ".*", {git, "https://github.com/ninenines/cowboy.git", {tag, "2.5.0"}}},
{jiffy, ".*", {git, "https://github.com/davisp/jiffy.git", {tag, "1.1.1"}}},
{mysql, ".*", {git, "https://github.com/mysql-otp/mysql-otp", {tag, "1.8.0"}}},
{parse_trans, ".*", {git, "https://github.com/uwiger/parse_trans", {tag, "3.0.0"}}},
{emqtt, ".*", {git, "https://github.com/emqx/emqtt", {tag, "v1.2.0"}}},
{lager, ".*", {git,"https://github.com/erlang-lager/lager.git", {tag, "3.9.2"}}}

View File

@ -34,6 +34,10 @@
0},
{<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},1},
{<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.2.0">>},1},
{<<"mysql">>,
{git,"https://github.com/mysql-otp/mysql-otp",
{ref,"caf5ff96c677a8fe0ce6f4082bc036c8fd27dd62"}},
0},
{<<"parse_trans">>,
{git,"https://github.com/uwiger/parse_trans",
{ref,"6f3645afb43c7c57d61b54ef59aecab288ce1013"}},