diff --git a/apps/iot/src/iot_device.erl b/apps/iot/src/iot_device.erl index 3a09fed..f6af9c3 100644 --- a/apps/iot/src/iot_device.erl +++ b/apps/iot/src/iot_device.erl @@ -9,14 +9,8 @@ -author("aresei"). -include("iot.hrl"). --behaviour(gen_statem). - %% API --export([get_name/1, get_pid/1]). --export([start_link/2, is_activated/1, is_alive/1, change_status/2, reload/1, auth/2]). - -%% gen_statem callbacks --export([init/1, handle_event/4, terminate/3, code_change/4, callback_mode/0]). +-export([new/1, is_activated/1, change_status/2, reload/1, auth/2]). %% 终端是否授权 -define(DEVICE_AUTH_DENIED, 0). @@ -26,8 +20,9 @@ -define(STATE_DENIED, denied). -define(STATE_ACTIVATED, activated). --record(state, { +-record(device, { device_uuid :: binary(), + auth_state = ?STATE_DENIED, status = ?DEVICE_OFFLINE }). @@ -35,170 +30,79 @@ %%% API %%%=================================================================== --spec is_alive(DeviceUUID :: binary()) -> error | {ok, Pid :: pid()}. -is_alive(DeviceUUID) when is_binary(DeviceUUID) -> - case iot_device:get_pid(DeviceUUID) of - undefined -> - error; - DevicePid when is_pid(DevicePid) -> - case iot_device:is_activated(DevicePid) of - true -> - {ok, DevicePid}; - false -> - error - end - end. - --spec get_pid(DeviceUUID :: binary()) -> Pid :: pid() | undefined. -get_pid(DeviceUUID) when is_binary(DeviceUUID) -> - whereis(get_name(DeviceUUID)). - --spec get_name(DeviceUUID :: binary()) -> atom(). -get_name(DeviceUUID) when is_binary(DeviceUUID) -> - binary_to_atom(<<"iot_device:", DeviceUUID/binary>>). - --spec is_activated(Pid :: pid() | undefined) -> boolean(). -is_activated(undefined) -> - false; -is_activated(Pid) when is_pid(Pid) -> - gen_statem:call(Pid, is_activated). - --spec change_status(Pid :: pid() | undefined, NewStatus :: integer()) -> no_return(). -change_status(undefined, _) -> - ok; -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}). - -%% @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(Name, DeviceUUID) when is_atom(Name), is_binary(DeviceUUID) -> - gen_statem:start_link({local, Name}, ?MODULE, [DeviceUUID], []); -start_link(Name, DeviceInfo) when is_atom(Name), is_map(DeviceInfo) -> - gen_statem:start_link({local, Name}, ?MODULE, [DeviceInfo], []). - -%%%=================================================================== -%%% 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([DeviceUUID]) when is_binary(DeviceUUID) -> +-spec new(DeviceUUID :: binary()) -> error | {ok, Device :: #device{}}. +new(DeviceUUID) when is_binary(DeviceUUID) -> case device_bo:get_device_by_uuid(DeviceUUID) of - {ok, DeviceInfo} -> - init([DeviceInfo]); + {ok, #{<<"device_uuid">> := DeviceUUID, <<"authorize_status">> := AuthorizeStatus, <<"status">> := Status}} -> + {ok, #device{device_uuid = DeviceUUID, status = Status, auth_state = auth_state(AuthorizeStatus)}}; undefined -> lager:warning("[iot_device] device uuid: ~p, loaded from mysql failed", [DeviceUUID]), - ignore - end; -init([DeviceInfo = #{<<"device_uuid">> := DeviceUUID, <<"authorize_status">> := AuthorizeStatus, <<"status">> := Status}]) when is_map(DeviceInfo) -> - case AuthorizeStatus =:= ?DEVICE_AUTH_AUTHED of - true -> - {ok, ?STATE_ACTIVATED, #state{device_uuid = DeviceUUID, status = Status}}; - false -> - {ok, ?STATE_DENIED, #state{device_uuid = DeviceUUID, status = Status}} + error end. -%% @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. +-spec is_activated(Device :: #device{}) -> boolean(). +is_activated(#device{auth_state = AuthState}) -> + AuthState =:= ?STATE_ACTIVATED. -%% @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_ONLINE}, _, State = #state{status = ?DEVICE_ONLINE}) -> - {keep_state, State}; -%% 改变数据库的状态, 其他情况下执行次数都很少 -handle_event(cast, {change_status, ?DEVICE_ONLINE}, _, State = #state{device_uuid = DeviceUUID}) -> +-spec change_status(Device :: #device{}, NewStatus :: integer()) -> NDevice :: #device{}. +change_status(Device = #device{status = Status}, NewStatus) when is_integer(NewStatus), Status =:= NewStatus -> + Device; +change_status(Device = #device{device_uuid = DeviceUUID}, ?DEVICE_ONLINE) -> {ok, _} = device_bo:change_status(DeviceUUID, ?DEVICE_ONLINE), report_event(DeviceUUID, ?DEVICE_ONLINE), - {keep_state, State#state{status = ?DEVICE_ONLINE}}; - -handle_event(cast, {change_status, ?DEVICE_OFFLINE}, _, State = #state{device_uuid = DeviceUUID}) -> + Device#device{status = ?DEVICE_ONLINE}; +change_status(Device = #device{device_uuid = DeviceUUID}, ?DEVICE_OFFLINE) -> {ok, #{<<"status">> := Status}} = device_bo:get_device_by_uuid(DeviceUUID), case Status of ?DEVICE_NOT_JOINED -> lager:debug("[iot_device] device: ~p, device_maybe_offline, not joined, can not change to offline", [DeviceUUID]), - {keep_state, State#state{status = ?DEVICE_NOT_JOINED}}; + Device#device{status = ?DEVICE_NOT_JOINED}; ?DEVICE_OFFLINE -> lager:debug("[iot_device] device: ~p, device_maybe_offline, is offline, do nothing", [DeviceUUID]), - {keep_state, State#state{status = ?DEVICE_OFFLINE}}; + Device#device{status = ?DEVICE_OFFLINE}; ?DEVICE_ONLINE -> {ok, _} = device_bo:change_status(DeviceUUID, ?DEVICE_OFFLINE), report_event(DeviceUUID, ?DEVICE_OFFLINE), - {keep_state, State#state{status = ?DEVICE_OFFLINE}} - end; + Device#device{status = ?DEVICE_OFFLINE} + end. -%% 重新加载数据库数据 -handle_event(cast, reload, _, State = #state{device_uuid = DeviceUUID}) -> +-spec reload(Device :: #device{}) -> error | {ok, NDevice :: #device{}}. +reload(Device = #device{device_uuid = DeviceUUID}) -> lager:debug("[iot_device] will reload: ~p", [DeviceUUID]), case device_bo:get_device_by_uuid(DeviceUUID) of {ok, #{<<"authorize_status">> := AuthorizeStatus, <<"status">> := Status}} -> - case AuthorizeStatus =:= ?DEVICE_AUTH_AUTHED of - true -> - {next_state, ?STATE_ACTIVATED, State#state{status = Status}}; - false -> - {next_state, ?STATE_DENIED, State#state{status = Status}} - end; + {ok, Device#device{device_uuid = DeviceUUID, status = Status, auth_state = auth_state(AuthorizeStatus)}}; undefined -> lager:warning("[iot_device] device uuid: ~p, loaded from mysql failed", [DeviceUUID]), - {stop, normal, State} - end; + error + end. -%% 处理授权 -handle_event(cast, {auth, Auth}, StateName, State = #state{device_uuid = DeviceUUID}) -> +-spec auth(Device :: #device{}, Auth :: boolean()) -> NDevice :: #device{}. +auth(Device = #device{auth_state = StateName, device_uuid = DeviceUUID}, Auth) when is_boolean(Auth) -> case {StateName, Auth} of {?STATE_DENIED, false} -> lager:debug("[iot_device] device_uuid: ~p, auth: false, will keep state_name: ~p", [DeviceUUID, ?STATE_DENIED]), - {keep_state, State}; + Device; {?STATE_DENIED, true} -> - {next_state, ?STATE_ACTIVATED, State}; - + Device#device{auth_state = ?STATE_ACTIVATED}; {?STATE_ACTIVATED, false} -> 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}; + Device#device{auth_state = ?STATE_DENIED}; {?STATE_ACTIVATED, true} -> lager:debug("[iot_device] device_uuid: ~p, auth: true, will keep state_name: ~p", [DeviceUUID, ?STATE_ACTIVATED]), - {keep_state, State} + Device end. -%% @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 %%%=================================================================== +-spec auth_state(integer()) -> atom(). +auth_state(?DEVICE_AUTH_AUTHED) -> + ?STATE_ACTIVATED; +auth_state(?DEVICE_AUTH_DENIED) -> + ?STATE_DENIED. + -spec report_event(DeviceUUID :: binary(), NewStatus :: integer()) -> no_return(). report_event(DeviceUUID, NewStatus) when is_binary(DeviceUUID), is_integer(NewStatus) -> TextMap = #{ diff --git a/apps/iot/src/iot_device1.erl b/apps/iot/src/iot_device1.erl deleted file mode 100644 index f67a492..0000000 --- a/apps/iot/src/iot_device1.erl +++ /dev/null @@ -1,124 +0,0 @@ -%%%------------------------------------------------------------------- -%%% @copyright (C) 2023, -%%% @doc -%%% -%%% @end -%%% Created : 14. 8月 2023 11:40 -%%%------------------------------------------------------------------- --module(iot_device1). --author("aresei"). --include("iot.hrl"). - -%% API --export([new/1, is_activated/1, change_status/2, reload/1, auth/2]). - -%% 终端是否授权 --define(DEVICE_AUTH_DENIED, 0). --define(DEVICE_AUTH_AUTHED, 1). - -%% 状态 --define(STATE_DENIED, denied). --define(STATE_ACTIVATED, activated). - --record(device, { - device_uuid :: binary(), - auth_state = ?STATE_DENIED, - status = ?DEVICE_OFFLINE -}). - -%%%=================================================================== -%%% API -%%%=================================================================== - --spec new(DeviceUUID :: binary()) -> error | {ok, Device :: #device{}}. -new(DeviceUUID) when is_binary(DeviceUUID) -> - case device_bo:get_device_by_uuid(DeviceUUID) of - {ok, #{<<"device_uuid">> := DeviceUUID, <<"authorize_status">> := AuthorizeStatus, <<"status">> := Status}} -> - {ok, #device{device_uuid = DeviceUUID, status = Status, auth_state = auth_state(AuthorizeStatus)}}; - undefined -> - lager:warning("[iot_device] device uuid: ~p, loaded from mysql failed", [DeviceUUID]), - error - end. - --spec is_activated(Device :: #device{}) -> boolean(). -is_activated(#device{auth_state = AuthState}) -> - AuthState =:= ?STATE_ACTIVATED. - --spec change_status(Device :: #device{}, NewStatus :: integer()) -> NDevice :: #device{}. -change_status(Device = #device{status = Status}, NewStatus) when is_integer(NewStatus), Status =:= NewStatus -> - Device; -change_status(Device = #device{device_uuid = DeviceUUID}, ?DEVICE_ONLINE) -> - {ok, _} = device_bo:change_status(DeviceUUID, ?DEVICE_ONLINE), - report_event(DeviceUUID, ?DEVICE_ONLINE), - Device#device{status = ?DEVICE_ONLINE}; -change_status(Device = #device{device_uuid = DeviceUUID}, ?DEVICE_OFFLINE) -> - {ok, #{<<"status">> := Status}} = device_bo:get_device_by_uuid(DeviceUUID), - case Status of - ?DEVICE_NOT_JOINED -> - lager:debug("[iot_device] device: ~p, device_maybe_offline, not joined, can not change to offline", [DeviceUUID]), - Device#device{status = ?DEVICE_NOT_JOINED}; - ?DEVICE_OFFLINE -> - lager:debug("[iot_device] device: ~p, device_maybe_offline, is offline, do nothing", [DeviceUUID]), - Device#device{status = ?DEVICE_OFFLINE}; - ?DEVICE_ONLINE -> - {ok, _} = device_bo:change_status(DeviceUUID, ?DEVICE_OFFLINE), - report_event(DeviceUUID, ?DEVICE_OFFLINE), - Device#device{status = ?DEVICE_OFFLINE} - end. - --spec reload(Device :: #device{}) -> error | {ok, NDevice :: #device{}}. -reload(Device = #device{device_uuid = DeviceUUID}) -> - lager:debug("[iot_device] will reload: ~p", [DeviceUUID]), - case device_bo:get_device_by_uuid(DeviceUUID) of - {ok, #{<<"authorize_status">> := AuthorizeStatus, <<"status">> := Status}} -> - {ok, Device#device{device_uuid = DeviceUUID, status = Status, auth_state = auth_state(AuthorizeStatus)}}; - undefined -> - lager:warning("[iot_device] device uuid: ~p, loaded from mysql failed", [DeviceUUID]), - error - end. - --spec auth(Device :: #device{}, Auth :: boolean()) -> NDevice :: #device{}. -auth(Device = #device{auth_state = StateName, device_uuid = DeviceUUID}, Auth) when is_boolean(Auth) -> - case {StateName, Auth} of - {?STATE_DENIED, false} -> - lager:debug("[iot_device] device_uuid: ~p, auth: false, will keep state_name: ~p", [DeviceUUID, ?STATE_DENIED]), - Device; - {?STATE_DENIED, true} -> - Device#device{auth_state = ?STATE_ACTIVATED}; - {?STATE_ACTIVATED, false} -> - lager:debug("[iot_device] device_uuid: ~p, auth: false, state_name from: ~p, to: ~p", [DeviceUUID, ?STATE_ACTIVATED, ?STATE_DENIED]), - Device#device{auth_state = ?STATE_DENIED}; - {?STATE_ACTIVATED, true} -> - lager:debug("[iot_device] device_uuid: ~p, auth: true, will keep state_name: ~p", [DeviceUUID, ?STATE_ACTIVATED]), - Device - end. - -%%%=================================================================== -%%% Internal functions -%%%=================================================================== - --spec auth_state(integer()) -> atom(). -auth_state(?DEVICE_AUTH_AUTHED) -> - ?STATE_ACTIVATED; -auth_state(?DEVICE_AUTH_DENIED) -> - ?STATE_DENIED. - --spec report_event(DeviceUUID :: binary(), NewStatus :: integer()) -> no_return(). -report_event(DeviceUUID, NewStatus) when is_binary(DeviceUUID), is_integer(NewStatus) -> - TextMap = #{ - 0 => <<"离线"/utf8>>, - 1 => <<"在线"/utf8>> - }, - %% 设备的状态信息上报给中电 - Timestamp = iot_util:timestamp_of_seconds(), - FieldsList = [#{ - <<"key">> => <<"device_status">>, - <<"value">> => NewStatus, - <<"value_text">> => maps:get(NewStatus, TextMap), - <<"unit">> => 0, - <<"type">> => <<"DI">>, - <<"name">> => <<"设备状态"/utf8>>, - <<"timestamp">> => Timestamp - }], - iot_router:route_uuid(DeviceUUID, FieldsList, Timestamp), - lager:debug("[iot_device] device_uuid: ~p, route fields: ~p", [DeviceUUID, FieldsList]). \ No newline at end of file diff --git a/apps/iot/src/iot_host.erl b/apps/iot/src/iot_host.erl index a0f5783..dc2cf27 100644 --- a/apps/iot/src/iot_host.erl +++ b/apps/iot/src/iot_host.erl @@ -40,6 +40,10 @@ heartbeat_counter = 0 :: integer(), %% websocket相关 channel_pid :: undefined | pid(), + + %% 设备的关系, #{device_uuid => Device} + device_map = #{}, + %% 主机的相关信息 metrics = #{} :: map() }). @@ -153,7 +157,19 @@ init([UUID]) -> true -> ?STATE_ACTIVATED; false -> ?STATE_DENIED end, - {ok, StateName, #state{host_id = HostId, uuid = UUID, has_session = false}}; + + %% 加载设备信息 + {ok, DeviceItems} = device_bo:get_host_devices(HostId), + Devices = lists:flatmap(fun(DeviceUUID) -> + case iot_device:new(DeviceUUID) of + error -> + []; + {ok, Device} -> + [{DeviceUUID, Device}] + end + end, DeviceItems), + + {ok, StateName, #state{host_id = HostId, uuid = UUID, device_map = maps:from_list(Devices), has_session = false}}; undefined -> lager:warning("[iot_host] host uuid: ~p, loaded from mysql failed", [UUID]), ignore @@ -175,23 +191,12 @@ handle_event({call, From}, get_metric, _, State = #state{metrics = Metrics}) -> %% 获取主机的状态 handle_event({call, From}, get_status, _, State = #state{host_id = HostId, channel_pid = ChannelPid, heartbeat_counter = HeartbeatCounter, metrics = Metrics, has_session = HasSession}) -> - %% 启动主机相关的devices - {ok, Devices} = device_bo:get_host_devices(HostId), - DeviceInfos = lists:map(fun(DeviceUUID) -> - DevicePid = iot_device:get_pid(DeviceUUID), - case iot_device:is_activated(DevicePid) of - true -> {DeviceUUID, <<"activated">>}; - false -> {DeviceUUID, <<"denied">>} - end - end, Devices), - HasChannel = (ChannelPid /= undefined), Reply = #{ <<"has_channel">> => HasChannel, <<"has_session">> => HasSession, <<"heartbeat_counter">> => HeartbeatCounter, - <<"metrics">> => Metrics, - <<"device_infos">> => DeviceInfos + <<"metrics">> => Metrics }, {keep_state, State, [{reply, From, {ok, Reply}}]};