support device

This commit is contained in:
anlicheng 2023-08-14 23:23:46 +08:00
parent de77d77176
commit 1c4fbf6415
12 changed files with 470 additions and 53 deletions

View File

@ -31,6 +31,9 @@
-define(PACKET_PUBLISH, 16#03). -define(PACKET_PUBLISH, 16#03).
-define(PACKET_PUBLISH_RESPONSE, 16#04). -define(PACKET_PUBLISH_RESPONSE, 16#04).
%%
-define(EVENT_DEVICE, 16#01).
%% %%
-record(kv, { -record(kv, {
key :: binary(), key :: binary(),

View File

@ -11,7 +11,26 @@
-include("iot.hrl"). -include("iot.hrl").
%% API %% API
-export([get_device_by_uuid/1]). -export([get_host_devices/1, get_device_by_uuid/1, change_status/2, get_host_by_uuid/1]).
get_device_by_uuid(UUID) when is_binary(UUID) -> -spec get_host_devices(HostId :: integer()) -> {ok, Devices::list()} | {error, Reason::any()}.
mysql_pool:get_row(mysql_iot, <<"SELECT * FROM device WHERE uuid = ? LIMIT 1">>, [UUID]). get_host_devices(HostId) when is_integer(HostId) ->
mysql_pool:get_all(mysql_iot, <<"SELECT * FROM device WHERE host_id = ? AND device_uuid != ''">>, [HostId]).
-spec get_device_by_uuid(DeviceUUID :: binary()) -> {ok, DeviceInfo :: map()} | undefined.
get_device_by_uuid(DeviceUUID) when is_binary(DeviceUUID) ->
mysql_pool:get_row(mysql_iot, <<"SELECT * FROM device WHERE device_uuid = ? LIMIT 1">>, [DeviceUUID]).
%%
-spec change_status(DeviceId :: integer(), Status :: integer()) -> {ok, AffectedRows :: integer()} | {error, Reason :: any()}.
change_status(DeviceId, Status) when is_integer(DeviceId), is_integer(Status) ->
mysql_pool:update_by(mysql_iot, <<"UPDATE device SET status = ? WHERE id = ? LIMIT 1">>, [Status, DeviceId]).
-spec get_host_by_uuid(DeviceUUID :: binary()) -> undefined | {ok, HostInfo :: map()}.
get_host_by_uuid(DeviceUUID) when is_binary(DeviceUUID) ->
case get_device_by_uuid(DeviceUUID) of
undefined ->
undefined;
{ok, #{<<"host_id">> := HostId}} ->
host_bo:get_host_by_id(HostId)
end.

View File

@ -11,7 +11,7 @@
-include("iot.hrl"). -include("iot.hrl").
%% API %% API
-export([get_all_hosts/0, change_status/2, get_host_by_uuid/1]). -export([get_all_hosts/0, change_status/2, get_host_by_uuid/1, get_host_by_id/1]).
-spec get_all_hosts() -> UUIDList :: [binary()]. -spec get_all_hosts() -> UUIDList :: [binary()].
get_all_hosts() -> get_all_hosts() ->
@ -22,9 +22,14 @@ get_all_hosts() ->
[] []
end. end.
-spec get_host_by_uuid(UUID :: binary()) -> undefined | {ok, HostInfo :: map()}.
get_host_by_uuid(UUID) when is_binary(UUID) -> get_host_by_uuid(UUID) when is_binary(UUID) ->
mysql_pool:get_row(mysql_iot, <<"SELECT * FROM host WHERE uuid = ? LIMIT 1">>, [UUID]). mysql_pool:get_row(mysql_iot, <<"SELECT * FROM host WHERE uuid = ? LIMIT 1">>, [UUID]).
-spec get_host_by_id(HostId :: integer()) -> undefined | {ok, HostInfo :: map()}.
get_host_by_id(HostId) when is_integer(HostId) ->
mysql_pool:get_row(mysql_iot, <<"SELECT * FROM host WHERE id = ? LIMIT 1">>, [HostId]).
%% %%
-spec change_status(UUID :: binary(), Status :: integer()) -> {ok, AffectedRows :: integer()} | {error, Reason :: any()}. -spec change_status(UUID :: binary(), Status :: integer()) -> {ok, AffectedRows :: integer()} | {error, Reason :: any()}.
change_status(UUID, Status) when is_binary(UUID), is_integer(Status) -> change_status(UUID, Status) when is_binary(UUID), is_integer(Status) ->

View File

@ -0,0 +1,57 @@
%%%-------------------------------------------------------------------
%%% @author licheng5
%%% @copyright (C) 2020, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 26. 4 2020 3:36
%%%-------------------------------------------------------------------
-module(device_handler).
-author("licheng5").
-include("iot.hrl").
%% API
-export([handle_request/4]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% helper methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
handle_request("POST", "/device/reload", _, #{<<"device_uuid">> := DeviceUUID}) when is_binary(DeviceUUID) ->
lager:debug("[device_handler] will reload device uuid: ~p", [DeviceUUID]),
case device_bo:get_host_by_uuid(DeviceUUID) of
undefined ->
{ok, 200, iot_util:json_error(404, <<"device not found, reload error">>)};
{ok, #{<<"uuid">> := UUID}} ->
Pid = iot_host:get_pid(UUID),
ok = iot_host:reload_device(Pid, DeviceUUID),
{ok, 200, iot_util:json_data(<<"success">>)}
end;
%%
handle_request("POST", "/device/delete", _, #{<<"device_uuid">> := DeviceUUID}) when is_binary(DeviceUUID) ->
case device_bo:get_host_by_uuid(DeviceUUID) of
undefined ->
ok;
{ok, #{<<"uuid">> := UUID}} ->
Pid = iot_host:get_pid(UUID),
ok = iot_host:delete_device(Pid, DeviceUUID)
end,
{ok, 200, iot_util:json_data(<<"success">>)};
%%
handle_request("POST", "/device/activate", _, #{<<"device_uuid">> := DeviceUUID, <<"auth">> := Auth}) when is_binary(DeviceUUID) ->
case device_bo:get_host_by_uuid(DeviceUUID) of
undefined ->
ok;
{ok, #{<<"uuid">> := UUID}} ->
Pid = iot_host:get_pid(UUID),
ok = iot_host:auth_device(Pid, DeviceUUID, Auth)
end,
{ok, 200, iot_util:json_data(<<"success">>)};
handle_request(_, Path, _, _) ->
Path1 = list_to_binary(Path),
{ok, 200, iot_util:json_error(-1, <<"url: ", Path1/binary, " not found">>)}.

View File

@ -12,7 +12,7 @@
-behaviour(gen_server). -behaviour(gen_server).
%% API %% API
-export([start_link/1, write/4, write/5]). -export([start_link/1, write/4, write/5, write_data/4]).
-export([get_precision/1]). -export([get_precision/1]).
%% gen_server callbacks %% gen_server callbacks
@ -22,6 +22,9 @@
-define(INFLUX_POOl, influx_pool). -define(INFLUX_POOl, influx_pool).
-define(DEFAULT_BUCKET, <<"metric">>).
-define(DEFAULT_ORG, <<"nannong">>).
-record(state, { -record(state, {
host, host,
port, port,
@ -48,6 +51,22 @@ get_precision(Timestamp) when is_integer(Timestamp) ->
<<"ms">> <<"ms">>
end. end.
-spec write_data(Measurement :: binary(), Tags :: map(), FieldsList :: list(), Timestamp :: integer()) -> no_return().
write_data(Measurement, Tags, FieldsList, Timestamp) when is_binary(Measurement), is_map(Tags), is_list(FieldsList), is_integer(Timestamp) ->
%% uuid进行分组
Points = lists:map(fun(Fields) ->
NFields = case Fields of
#{<<"key">> := Key, <<"value">> := Value, <<"unit">> := Unit} ->
#{Key => jiffy:encode(#{<<"value">> => Value, <<"unit">> => Unit}, [force_utf8])};
#{<<"key">> := Key, <<"value">> := Value} ->
#{Key => #{<<"value">> => Value}}
end,
influx_point:new(Measurement, Tags, NFields, Timestamp)
end, FieldsList),
Precision = influx_client:get_precision(Timestamp),
poolboy:transaction(influx_pool, fun(Pid) -> influx_client:write(Pid, ?DEFAULT_BUCKET, ?DEFAULT_ORG, Precision, Points) end).
-spec write(Pid :: pid(), Bucket :: binary(), Org :: binary(), Points :: list()) -> no_return(). -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, Points) when is_pid(Pid), is_binary(Bucket), is_binary(Org), is_list(Points) ->
write(Pid, Bucket, Org, <<"ms">>, Points). write(Pid, Bucket, Org, <<"ms">>, Points).

View File

@ -19,6 +19,7 @@
%% API %% API
-export([new/4, normalized/1]). -export([new/4, normalized/1]).
-spec new(Measurement :: binary(), Tags :: list() | map(), Fields :: list() | map(), Timestamp :: integer()) -> #point{}.
new(Measurement, Tags, Fields, Timestamp) when is_binary(Measurement), is_list(Tags); is_map(Tags), is_list(Fields); is_map(Fields), is_integer(Timestamp) -> 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}. #point{measurement = Measurement, tags = as_list(Tags), fields = as_list(Fields), time = Timestamp}.

View File

@ -44,6 +44,7 @@ start_http_server() ->
Dispatcher = cowboy_router:compile([ Dispatcher = cowboy_router:compile([
{'_', [ {'_', [
{"/host/[...]", http_protocol, [host_handler]}, {"/host/[...]", http_protocol, [host_handler]},
{"/device/[...]", http_protocol, [device_handler]},
{"/endpoint/[...]", http_protocol, [endpoint_handler]}, {"/endpoint/[...]", http_protocol, [endpoint_handler]},
{"/test/[...]", http_protocol, [test_handler]}, {"/test/[...]", http_protocol, [test_handler]},
{"/ws", ws_channel, []} {"/ws", ws_channel, []}

196
apps/iot/src/iot_device.erl Normal file
View File

@ -0,0 +1,196 @@
%%%-------------------------------------------------------------------
%%% @author aresei
%%% @copyright (C) 2023, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 14. 8 2023 11:40
%%%-------------------------------------------------------------------
-module(iot_device).
-author("aresei").
-behaviour(gen_statem).
%% API
-export([start_link/2, is_activated/1, change_status/2, reload/1, auth/2, stop/1]).
%% gen_statem callbacks
-export([init/1, format_status/2, handle_event/4, terminate/3, code_change/4, callback_mode/0]).
%%
-define(RELOAD_TICKER, 100 * 1000).
%% 线
-define(DEVICE_STATUS_OFFLINE, 0).
-define(DEVICE_STATUS_ONLINE, 1).
%%
-define(DEVICE_AUTH_DENIED, 0).
-define(DEVICE_AUTH_AUTHED, 1).
%%
-define(STATE_DENIED, denied).
-define(STATE_ACTIVATED, activated).
-record(state, {
parent_pid :: pid(),
device_id :: integer(),
device_uuid :: binary(),
status = ?DEVICE_STATUS_OFFLINE
}).
%%%===================================================================
%%% API
%%%===================================================================
-spec is_activated(Pid :: pid()) -> boolean().
is_activated(Pid) when is_pid(Pid) ->
gen_statem:call(Pid, is_activated).
-spec change_status(Pid :: pid(), NewStatus :: integer()) -> no_return().
change_status(Pid, NewStatus) when is_pid(Pid), is_integer(NewStatus) ->
gen_statem:cast(Pid, {change_status, NewStatus}).
-spec reload(Pid :: pid()) -> no_return().
reload(Pid) when is_pid(Pid) ->
gen_statem:cast(Pid, reload).
-spec auth(Pid :: pid(), Auth :: boolean()) -> no_return().
auth(Pid, Auth) when is_pid(Pid), is_boolean(Auth) ->
gen_statem:cast(Pid, {auth, Auth}).
-spec stop(Pid :: pid()) -> no_return().
stop(Pid) when is_pid(Pid) ->
gen_statem:stop(Pid).
%% @doc Creates a gen_statem process which calls Module:init/1 to
%% initialize. To ensure a synchronized start-up procedure, this
%% function does not return until Module:init/1 has returned.
start_link(ParentPid, DeviceUUID) when is_pid(ParentPid), is_binary(DeviceUUID) ->
gen_statem:start_link(?MODULE, [ParentPid, DeviceUUID], []).
%%%===================================================================
%%% gen_statem callbacks
%%%===================================================================
%% @private
%% @doc Whenever a gen_statem is started using gen_statem:start/[3,4] or
%% gen_statem:start_link/[3,4], this function is called by the new
%% process to initialize.
init([ParentPid, DeviceUUID]) ->
%%
erlang:start_timer(?RELOAD_TICKER, self(), reload_ticker),
{ok, #{<<"authorize_status">> := AuthorizeStatus, <<"id">> := DeviceId}} = device_bo:get_device_by_uuid(DeviceUUID),
StateName = case AuthorizeStatus =:= ?DEVICE_AUTH_AUTHED of
false -> ?STATE_DENIED;
true -> ?STATE_ACTIVATED
end,
%% 线
{ok, _} = device_bo:change_status(DeviceId, ?DEVICE_STATUS_OFFLINE),
lager:debug("[iot_device] started device: ~p, state_name: ~p", [DeviceUUID, StateName]),
{ok, StateName, #state{parent_pid = ParentPid, device_id = DeviceId, device_uuid = DeviceUUID, status = ?DEVICE_STATUS_OFFLINE}}.
%% @private
%% @doc This function is called by a gen_statem when it needs to find out
%% the callback mode of the callback module.
callback_mode() ->
handle_event_function.
%% @private
%% @doc Called (1) whenever sys:get_status/1,2 is called by gen_statem or
%% (2) when gen_statem terminates abnormally.
%% This callback is optional.
format_status(_Opt, [_PDict, _StateName, _State]) ->
Status = some_term,
Status.
%% @private
%% @doc If callback_mode is handle_event_function, then whenever a
%% gen_statem receives an event from call/2, cast/2, or as a normal
%% process message, this function is called.
%%
handle_event({call, From}, is_activated, StateName, State = #state{}) ->
{keep_state, State, [{reply, From, StateName =:= ?STATE_ACTIVATED}]};
%% , 线(线)
handle_event(cast, {change_status, ?DEVICE_STATUS_OFFLINE}, ?STATE_ACTIVATED, State = #state{device_id = DeviceId}) ->
{ok, _} = device_bo:change_status(DeviceId, ?DEVICE_STATUS_OFFLINE),
{keep_state, State#state{status = ?DEVICE_STATUS_OFFLINE}};
%% 线线
handle_event(cast, {change_status, ?DEVICE_STATUS_ONLINE}, ?STATE_ACTIVATED, State = #state{status = ?DEVICE_STATUS_ONLINE}) ->
{keep_state, State};
%% 线
handle_event(cast, {change_status, ?DEVICE_STATUS_ONLINE}, ?STATE_ACTIVATED, State = #state{device_id = DeviceId}) ->
{ok, _} = device_bo:change_status(DeviceId, ?DEVICE_STATUS_ONLINE),
{keep_state, State#state{status = ?DEVICE_STATUS_ONLINE}};
%% 线
handle_event(cast, {change_status, _}, _, State = #state{}) ->
{keep_state, State};
%%
handle_event(cast, reload, _, State = #state{device_uuid = DeviceUUID}) ->
lager:debug("[iot_device] will reload: ~p", [DeviceUUID]),
reload_database(State);
%%
handle_event(cast, {auth, Auth}, ?STATE_DENIED, State = #state{device_id = DeviceId, device_uuid = DeviceUUID}) ->
case Auth of
true ->
%% status字段的值
{ok, _} = device_bo:change_status(DeviceId, ?DEVICE_STATUS_OFFLINE),
lager:debug("[iot_device] device_uuid: ~p, auth: true, state_name from ~p, to: ~p", [DeviceUUID, ?STATE_DENIED, ?STATE_ACTIVATED]),
{next_state, ?STATE_ACTIVATED, State#state{status = ?DEVICE_STATUS_OFFLINE}};
false ->
lager:debug("[iot_device] device_uuid: ~p, auth: false, will keep state_name: ~p", [DeviceUUID, ?STATE_DENIED]),
{keep_state, State}
end;
handle_event(cast, {auth, Auth}, ?STATE_ACTIVATED, State = #state{device_id = DeviceId, device_uuid = DeviceUUID}) ->
case Auth of
true ->
lager:debug("[iot_device] device_uuid: ~p, auth: true, will keep state_name: ~p", [DeviceUUID, ?STATE_ACTIVATED]),
{keep_state, State};
false ->
%% status字段的值
{ok, _} = device_bo:change_status(DeviceId, ?DEVICE_STATUS_OFFLINE),
lager:debug("[iot_device] device_uuid: ~p, auth: false, state_name from: ~p, to: ~p", [DeviceUUID, ?STATE_ACTIVATED, ?STATE_DENIED]),
{next_state, ?STATE_DENIED, State#state{status = ?DEVICE_STATUS_OFFLINE}}
end;
handle_event(info, {timeout, _, reload_ticker}, _, State) ->
erlang:start_timer(?RELOAD_TICKER + rand:uniform(10) * 1000, self(), reload_ticker),
reload_database(State).
%% @private
%% @doc This function is called by a gen_statem 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_statem terminates with
%% Reason. The return value is ignored.
terminate(Reason, StateName, #state{device_uuid = DeviceUUID}) ->
lager:notice("[iot_device] device_uuid: ~p, state_name: ~p, terminate with reason: ~p", [DeviceUUID, StateName, Reason]),
ok.
%% @private
%% @doc Convert process state when code is changed
code_change(_OldVsn, StateName, State = #state{}, _Extra) ->
{ok, StateName, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
reload_database(State = #state{device_uuid = DeviceUUID}) ->
case device_bo:get_device_by_uuid(DeviceUUID) of
{ok, #{<<"authorize_status">> := AuthorizeStatus, <<"status">> := Status, <<"id">> := DeviceId}} ->
StateName = case AuthorizeStatus =:= ?DEVICE_AUTH_AUTHED of
false -> ?STATE_DENIED;
true -> ?STATE_ACTIVATED
end,
{next_state, StateName, State#state{device_id = DeviceId, status = Status}};
undefined ->
lager:warning("[iot_device] device uuid: ~p, loaded from mysql failed", [DeviceUUID]),
{keep_state, State}
end.

View File

@ -21,7 +21,7 @@
-define(HOST_AUTHED, 1). -define(HOST_AUTHED, 1).
%% API %% API
-export([start_link/2, get_name/1, get_pid/1, handle/2, reload/1, activate/2]). -export([start_link/2, get_name/1, get_pid/1, handle/2, reload/1, activate/2, reload_device/2, delete_device/2, auth_device/3]).
-export([get_metric/1, publish_message/4, get_aes/1]). -export([get_metric/1, publish_message/4, get_aes/1]).
-export([create_session/2, attach_channel/2]). -export([create_session/2, attach_channel/2]).
@ -32,6 +32,8 @@
host_id :: integer(), host_id :: integer(),
%% %%
uuid :: binary(), uuid :: binary(),
%% device之间的映射关系
device_map = #{},
%% aes的key, %% aes的key,
aes = <<>> :: binary(), aes = <<>> :: binary(),
@ -65,6 +67,20 @@ handle(Pid, Packet) when is_pid(Pid) ->
reload(Pid) when is_pid(Pid) -> reload(Pid) when is_pid(Pid) ->
gen_statem:call(Pid, reload). gen_statem:call(Pid, reload).
%%
-spec reload_device(Pid :: pid(), DeviceUUID :: binary()) -> ok | {error, Reason :: any()}.
reload_device(Pid, DeviceUUID) when is_pid(Pid), is_binary(DeviceUUID) ->
gen_statem:call(Pid, {reload_device, DeviceUUID}).
%%
-spec delete_device(Pid :: pid(), DeviceUUID :: binary()) -> ok | {error, Reason :: any()}.
delete_device(Pid, DeviceUUID) when is_pid(Pid), is_binary(DeviceUUID) ->
gen_statem:call(Pid, {delete_device, DeviceUUID}).
-spec auth_device(Pid :: pid(), DeviceUUID :: binary(), Auth :: boolean()) -> ok | {error, Reason :: any()}.
auth_device(Pid, DeviceUUID, Auth) when is_pid(Pid), is_binary(DeviceUUID), is_boolean(Auth) ->
gen_statem:call(Pid, {auth_device, DeviceUUID, Auth}).
-spec get_aes(Pid :: pid()) -> {ok, Aes :: binary()}. -spec get_aes(Pid :: pid()) -> {ok, Aes :: binary()}.
get_aes(Pid) when is_pid(Pid) -> get_aes(Pid) when is_pid(Pid) ->
gen_statem:call(Pid, get_aes). gen_statem:call(Pid, get_aes).
@ -130,7 +146,15 @@ init([UUID]) ->
true -> activated true -> activated
end, end,
{ok, StateName, #state{host_id = HostId, uuid = UUID, aes = Aes}}; %% device
{ok, Devices} = device_bo:get_host_devices(HostId),
lager:debug("[iot_host] uuid: ~p, loaded devices: ~p", [UUID, Devices]),
DeviceMap = lists:foldl(fun(#{<<"device_uuid">> := DeviceUUID}, M) ->
{ok, Pid} = iot_device:start_link(self(), DeviceUUID),
maps:put(DeviceUUID, Pid, M)
end, #{}, Devices),
{ok, StateName, #state{host_id = HostId, device_map = DeviceMap, uuid = UUID, aes = Aes}};
undefined -> undefined ->
lager:warning("[iot_host] host uuid: ~p, loaded from mysql failed", [UUID]), lager:warning("[iot_host] host uuid: ~p, loaded from mysql failed", [UUID]),
ignore ignore
@ -231,39 +255,56 @@ handle_event({call, From}, {create_session, PubKey}, StateName, State = #state{u
{next_state, session, State, [{reply, From, {ok, <<10:8, EncReply/binary>>}}]}; {next_state, session, State, [{reply, From, {ok, <<10:8, EncReply/binary>>}}]};
%%
handle_event({call, From}, {reload_device, DeviceUUID}, _, State = #state{device_map = DeviceMap}) ->
case maps:get(DeviceUUID, DeviceMap, undefined) of
undefined ->
{ok, Pid} = iot_device:start_link(self(), DeviceUUID),
NDeviceMap = maps:put(DeviceUUID, Pid, DeviceMap),
{keep_state, State#state{device_map = NDeviceMap}, [{reply, From, ok}]};
DevicePid ->
iot_device:reload(DevicePid),
{keep_state, State, [{reply, From, ok}]}
end;
handle_event({call, From}, {auth_device, DeviceUUID, Auth}, _, State = #state{device_map = DeviceMap}) ->
case maps:get(DeviceUUID, DeviceMap, undefined) of
undefined ->
{ok, Pid} = iot_device:start_link(self(), DeviceUUID),
iot_device:auth(Pid, Auth),
NDeviceMap = maps:put(DeviceUUID, Pid, DeviceMap),
{keep_state, State#state{device_map = NDeviceMap}, [{reply, From, ok}]};
DevicePid ->
iot_device:auth(DevicePid, Auth),
{keep_state, State, [{reply, From, ok}]}
end;
handle_event({call, From}, {delete_device, DeviceUUID}, _, State = #state{uuid = UUID, device_map = DeviceMap}) ->
case maps:take(DeviceUUID, DeviceMap) of
error ->
lager:notice("[iot_host] uuid: ~p, delete device: ~p, not found", [UUID, DeviceUUID]),
{keep_state, State, [{reply, From, ok}]};
{DevicePid, NDeviceMap} ->
iot_device:stop(DevicePid),
{keep_state, State#state{device_map = NDeviceMap}, [{reply, From, ok}]}
end;
%% json格式然后再处理, host进程里面处理, props %% json格式然后再处理, host进程里面处理, props
handle_event(cast, {handle, {data, Data}}, session, State = #state{uuid = UUID, aes = AES}) -> handle_event(cast, {handle, {data, Data}}, session, State = #state{aes = AES}) ->
PlainData = iot_cipher_aes:decrypt(AES, Data), PlainData = iot_cipher_aes:decrypt(AES, Data),
case catch jiffy:decode(PlainData, [return_maps]) of case catch jiffy:decode(PlainData, [return_maps]) of
Info = #{<<"service_name">> := ServiceName, <<"at">> := Timestamp, <<"fields">> := FieldsList, <<"tags">> := Tags} when is_binary(ServiceName) -> Info when is_map(Info) ->
%% handle_data(Info, State);
RouterUUID = router_uuid(Info, UUID),
lager:debug("[iot_host] host: ~p, router_uuid: ~p, get data: ~p", [UUID, RouterUUID, Info]),
case mnesia_kv:hget(RouterUUID, <<"location_code">>) of
none ->
lager:debug("[iot_host] the north_data hget location_code uuid: ~p, router_uuid: ~p, not found", [UUID, RouterUUID]);
{error, Reason} ->
lager:debug("[iot_host] the north_data hget location_code uuid: ~p, router_uuid: ~p, get error: ~p", [UUID, RouterUUID, Reason]);
{ok, LocationCode} ->
iot_router:route(LocationCode, FieldsList, Timestamp)
end,
%% influxdb
NTags = with_device_uuid(Tags#{<<"uuid">> => UUID, <<"service_name">> => ServiceName}, Info),
%% uuid进行分组
Points = lists:map(fun(Fields) ->
NFields = convert_fields(Fields),
influx_point:new(RouterUUID, NTags, NFields, Timestamp)
end, FieldsList),
Precision = influx_client:get_precision(Timestamp),
poolboy:transaction(influx_pool, fun(Pid) -> influx_client:write(Pid, <<"metric">>, <<"nannong">>, Precision, Points) end);
Other -> Other ->
lager:debug("[iot_host] the data is invalid json: ~p", [Other]) lager:debug("[iot_host] the data is invalid json: ~p", [Other])
end, end,
{keep_state, State}; {keep_state, State};
%%
handle_event(cast, {handle, {data, _}}, _, State) ->
{keep_state, State};
%% ping %% ping
handle_event(cast, {handle, {ping, CipherMetric}}, _, State = #state{uuid = UUID, aes = AES}) -> handle_event(cast, {handle, {ping, CipherMetric}}, _, State = #state{uuid = UUID, aes = AES}) ->
MetricsInfo = iot_cipher_aes:decrypt(AES, CipherMetric), MetricsInfo = iot_cipher_aes:decrypt(AES, CipherMetric),
@ -335,8 +376,21 @@ handle_event(cast, {handle, {feedback_result, Info0}}, session, State = #state{a
end, end,
{keep_state, State}; {keep_state, State};
%% handle_event(cast, {handle, {event, Event0}}, session, State = #state{aes = AES, device_map = DeviceMap}) ->
handle_event(cast, {handle, {data, _}}, _, State) -> EventText = iot_cipher_aes:decrypt(AES, Event0),
case catch jiffy:decode(EventText, [return_maps]) of
#{<<"event_type">> := ?EVENT_DEVICE, <<"params">> := #{<<"device_uuid">> := DeviceUUID, <<"status">> := Status}} ->
case maps:get(DeviceUUID, DeviceMap, undefined) of
undefined ->
ok;
Pid ->
iot_device:change_status(Pid, Status)
end;
Event when is_map(Event) ->
lager:warning("[iot_host] event: ~p, not supported", [Event]);
Other ->
lager:warning("[iot_host] event error: ~p", [Other])
end,
{keep_state, State}; {keep_state, State};
%% websocket断开的时候线; %% websocket断开的时候线;
@ -352,6 +406,25 @@ handle_event(info, {'DOWN', Ref, process, ChannelPid, Reason}, StateName, State
{keep_state, State#state{channel_pid = undefined}} {keep_state, State#state{channel_pid = undefined}}
end; end;
%% device进程的退出
handle_event(info, {'EXIT', Pid, Reason}, StateName, State = #state{uuid = UUID, device_map = DeviceMap}) ->
lager:warning("[iot_host] uuid: ~p, device pid: ~p, exit with reason: ~p, state name: ~p, state: ~p", [UUID, Pid, Reason, StateName, State]),
case lists:search(fun({_, Pid0}) -> Pid =:= Pid0 end, maps:to_list(DeviceMap)) of
false ->
{keep_state, State};
{value, {DeviceUUID, _}} ->
%% device进程不一定能成功
case iot_device:start_link(self(), DeviceUUID) of
{ok, DevicePid} ->
%% DeviceUUID
NDeviceMap = maps:put(DeviceUUID, DevicePid, DeviceMap),
{keep_state, State#state{device_map = NDeviceMap}};
Error ->
lager:warning("[iot_host] uuid: ~p, restart device: ~p, get error: ~p", [UUID, DeviceUUID, Error]),
{keep_state, State}
end
end;
handle_event(EventType, EventContent, StateName, State) -> handle_event(EventType, EventContent, StateName, State) ->
lager:warning("[iot_host] unknown event_type: ~p, event: ~p, state name: ~p, state: ~p", [EventType, EventContent, StateName, State]), lager:warning("[iot_host] unknown event_type: ~p, event: ~p, state name: ~p, state: ~p", [EventType, EventContent, StateName, State]),
@ -376,19 +449,33 @@ code_change(_OldVsn, StateName, State = #state{}, _Extra) ->
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
convert_fields(#{<<"key">> := Key, <<"value">> := Value, <<"unit">> := Unit}) -> %%
#{Key => jiffy:encode(#{<<"value">> => Value, <<"unit">> => Unit}, [force_utf8])}; handle_data(#{<<"device_uuid">> := DeviceUUID, <<"service_name">> := ServiceName, <<"at">> := Timestamp, <<"fields">> := FieldsList, <<"tags">> := Tags}, #state{uuid = UUID, device_map = DeviceMap})
convert_fields(#{<<"key">> := Key, <<"value">> := Value}) -> when is_binary(DeviceUUID), DeviceUUID /= <<>> ->
#{Key => #{<<"value">> => Value}}.
%% case maps:get(DeviceUUID, DeviceMap, undefined) of
router_uuid(#{<<"device_uuid">> := DeviceUUID}, _) when is_binary(DeviceUUID), DeviceUUID /= <<>> -> undefined ->
DeviceUUID; ok;
router_uuid(_, UUID) -> Pid ->
UUID. case iot_device:is_activated(Pid) of
true ->
%%
iot_router:route_uuid(DeviceUUID, FieldsList, Timestamp),
-spec with_device_uuid(Tags :: #{}, Info :: #{}) -> #{}. %% influxdb
with_device_uuid(Tags, #{<<"device_uuid">> := DeviceUUID}) when DeviceUUID /= <<>> -> NTags = Tags#{<<"uuid">> => UUID, <<"service_name">> => ServiceName, <<"device_uuid">> => DeviceUUID},
Tags#{<<"device_uuid">> => DeviceUUID}; influx_client:write_data(DeviceUUID, NTags, FieldsList, Timestamp),
with_device_uuid(Tags, _) ->
Tags. iot_device:change_status(Pid, 1);
false ->
lager:notice("[iot_host] uuid: ~p, device_uuid: ~p, device is not activated", [UUID, DeviceUUID])
end
end;
handle_data(#{<<"service_name">> := ServiceName, <<"at">> := Timestamp, <<"fields">> := FieldsList, <<"tags">> := Tags}, #state{uuid = UUID}) ->
%%
iot_router:route_uuid(UUID, FieldsList, Timestamp),
%% influxdb
NTags = Tags#{<<"uuid">> => UUID, <<"service_name">> => ServiceName},
influx_client:write_data(UUID, NTags, FieldsList, Timestamp).

View File

@ -11,7 +11,19 @@
-include("iot.hrl"). -include("iot.hrl").
%% API %% API
-export([route/3]). -export([route/3, route_uuid/3]).
-spec route_uuid(RouterUUID :: binary(), Fields :: list(), Timestamp :: integer()) -> no_return().
route_uuid(RouterUUID, Fields, Timestamp) when is_binary(RouterUUID), is_list(Fields), is_integer(Timestamp) ->
%%
case mnesia_kv:hget(RouterUUID, <<"location_code">>) of
none ->
lager:debug("[iot_host] the north_data hget location_code, uuid: ~p, not found", [RouterUUID]);
{error, Reason} ->
lager:debug("[iot_host] the north_data hget location_code uuid: ~p, get error: ~p", [RouterUUID, Reason]);
{ok, LocationCode} ->
route(LocationCode, Fields, Timestamp)
end.
-spec route(LocationCode :: binary(), Fields :: list(), Timestamp :: integer()) -> ok. -spec route(LocationCode :: binary(), Fields :: list(), Timestamp :: integer()) -> ok.
route(LocationCode, Fields, Timestamp) when is_binary(LocationCode), is_list(Fields), is_integer(Timestamp) -> route(LocationCode, Fields, Timestamp) when is_binary(LocationCode), is_list(Fields), is_integer(Timestamp) ->

View File

@ -100,6 +100,10 @@ websocket_handle({binary, <<?PACKET_REQUEST, _PacketId:32, ?METHOD_FEEDBACK_RESU
iot_host:handle(HostPid, {feedback_result, CipherInfo}), iot_host:handle(HostPid, {feedback_result, CipherInfo}),
{ok, State}; {ok, State};
websocket_handle({binary, <<?PACKET_REQUEST, _PacketId:32, ?METHOD_EVENT:8, CipherEvent/binary>>}, State = #state{host_pid = HostPid}) when is_pid(HostPid) ->
iot_host:handle(HostPid, {event, CipherEvent}),
{ok, State};
%% %%
websocket_handle({binary, <<?PACKET_PUBLISH_RESPONSE, PacketId:32, Body/binary>>}, State = #state{uuid = UUID, inflight = Inflight}) when PacketId > 0 -> websocket_handle({binary, <<?PACKET_PUBLISH_RESPONSE, PacketId:32, Body/binary>>}, State = #state{uuid = UUID, inflight = Inflight}) when PacketId > 0 ->
lager:debug("[ws_channel] uuid: ~p, get publish response message: ~p, packet_id: ~p", [UUID, Body, PacketId]), lager:debug("[ws_channel] uuid: ~p, get publish response message: ~p, packet_id: ~p", [UUID, Body, PacketId]),

View File

@ -67,12 +67,25 @@ Body: 公钥信息
PacketId: 4字节整数, 值为0; PacketId: 4字节整数, 值为0;
Body: 公钥信息 Body: 公钥信息
### 主机上传终端设备的在线或离线事件 ### 主机上传终端设备的相关事件
<<0x01, PacketId:4, 0x07, DeviceUUID:32/binary, Status:1>> <<0x01, PacketId:4, 0x07, Body:任意长度>>
PacketId: 4字节整数, 值为0; PacketId: 4字节整数, 值为0;
DeviceUUID: 设备32位UUID Body: 事件内容AES加密
Status: 1字节整数1表示在线0表示离线
```text
设备的离在线状态
{
"event_type": 1,
"params": {
"device_uuid": "",
"status": 0 // 1在线 0离线
}
}
```
### data北向数据上传 (无响应) ### data北向数据上传 (无响应)