fix devic
This commit is contained in:
parent
6c80d8d16c
commit
d7b6f5eb32
177
apps/aircon/src/aircon_device.erl
Normal file
177
apps/aircon/src/aircon_device.erl
Normal file
@ -0,0 +1,177 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author anlicheng
|
||||
%%% @copyright (C) 2024, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 04. 9月 2024 16:13
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aircon_device).
|
||||
-author("anlicheng").
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/2]).
|
||||
-export([get_pid/1, get_name/1]).
|
||||
-export([metric_data/2, poll_status/1]).
|
||||
|
||||
-define(UNIT0, 16#10).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
-record(state, {
|
||||
device_uuid :: binary(),
|
||||
heartbeat_ticker :: integer(),
|
||||
%% 消息计数器
|
||||
data_counter = 0,
|
||||
%% 状态,1: 在线,0: 离线
|
||||
status = 0
|
||||
}).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
|
||||
-spec get_pid(DeviceUUID :: binary()) -> undefined | pid().
|
||||
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(<<"aircon_device:", DeviceUUID/binary>>).
|
||||
|
||||
-spec metric_data(Pid :: pid(), Message :: binary()) -> no_return().
|
||||
metric_data(Pid, Message) when is_pid(Pid), is_binary(Message) ->
|
||||
gen_server:cast(Pid, {metric_data, Message}).
|
||||
|
||||
%% 查询设备的当前状态, 1: 在线,0: 离线
|
||||
-spec poll_status(Pid :: pid()) -> {ok, Status :: integer()}.
|
||||
poll_status(Pid) when is_pid(Pid) ->
|
||||
gen_server:call(Pid, poll_status).
|
||||
|
||||
%% @doc Spawns the server and registers the local name (unique)
|
||||
-spec(start_link(Name :: atom(), DeviceUUID :: binary()) ->
|
||||
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
|
||||
start_link(Name, DeviceUUID) when is_atom(Name), is_binary(DeviceUUID) ->
|
||||
gen_server:start_link({local, get_name(DeviceUUID)}, ?MODULE, [DeviceUUID], []).
|
||||
|
||||
%%%===================================================================
|
||||
%%% 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([DeviceUUID]) ->
|
||||
{ok, HeartbeatTicker0} = application:get_env(aircon, heartbeat_ticker),
|
||||
HeartbeatTicker = HeartbeatTicker0 * 1000,
|
||||
|
||||
erlang:start_timer(HeartbeatTicker, self(), heartbeat_ticker),
|
||||
{ok, #state{device_uuid = DeviceUUID, heartbeat_ticker = HeartbeatTicker, status = 0}}.
|
||||
|
||||
%% @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(poll_status, _From, State = #state{status = Status}) ->
|
||||
{reply, {ok, Status}, 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({metric_data, Message}, State = #state{device_uuid = DeviceUUID, data_counter = DataCounter, status = Status}) ->
|
||||
case catch jiffy:decode(Message, [return_maps]) of
|
||||
#{<<"properties">> := Props0} ->
|
||||
Props = lists:map(fun(Fields) -> transform(Fields#{<<"device_uuid">> => DeviceUUID}) end, Props0),
|
||||
Info = iolist_to_binary(jiffy:encode(Props, [force_utf8])),
|
||||
case catch efka_client:send_metric_data(Props, #{}) of
|
||||
{ok, _} ->
|
||||
aircon_logger:write([<<"OK">>, Info]);
|
||||
_ ->
|
||||
aircon_logger:write([<<"ERROR">>, Info])
|
||||
end,
|
||||
|
||||
%% 如果设备当前是离线状态,则需要发送上线消息
|
||||
Status == 0 andalso efka_client:device_online(DeviceUUID),
|
||||
|
||||
{noreply, State#state{data_counter = DataCounter + 1, status = 1}};
|
||||
M when is_map(M) ->
|
||||
lager:notice("[power_device] invalid map: ~p", [M]);
|
||||
Error ->
|
||||
lager:notice("[power_device] jiffy decode error: ~p", [Error]),
|
||||
{noreply, State}
|
||||
end.
|
||||
|
||||
%% @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({timeout, _, heartbeat_ticker}, State = #state{device_uuid = DeviceUUID, heartbeat_ticker = HeartbeatTicker, data_counter = DataCounter, status = Status}) ->
|
||||
erlang:start_timer(HeartbeatTicker, self(), heartbeat_ticker),
|
||||
case DataCounter > 0 of
|
||||
true ->
|
||||
{noreply, State};
|
||||
false ->
|
||||
Status == 1 andalso efka_client:device_offline(DeviceUUID),
|
||||
{noreply, State#state{status = 0}}
|
||||
end.
|
||||
|
||||
%% @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
|
||||
%%%===================================================================
|
||||
|
||||
%% 网关上来的数据写反了: value是类型,unit才是数值
|
||||
%% [{"value":"int","unit":44081,"type":"AI","timestamp":1730799797,"name":"使用次数","label":"次","key":"use_times","device_uuid":"30409239002349977617259608405456"}]
|
||||
-spec transform(Prop :: map()) -> map().
|
||||
transform(Prop = #{<<"key">> := <<"total_power">>, <<"unit">> := Unit}) ->
|
||||
Prop#{<<"name">> => <<"总能耗"/utf8>>, <<"value">> => Unit, <<"unit">> => 16#02};
|
||||
transform(Prop = #{<<"key">> := <<"total_runtime">>, <<"unit">> := Unit}) ->
|
||||
Prop#{<<"name">> => <<"总运行时间"/utf8>>, <<"value">> => Unit, <<"unit">> => ?UNIT0};
|
||||
transform(Prop = #{<<"key">> := <<"use_times">>, <<"unit">> := Unit}) ->
|
||||
Prop#{<<"name">> => <<"使用次数"/utf8>>, <<"label">> => <<"次"/utf8>>, <<"value">> => Unit, <<"unit">> => ?UNIT0};
|
||||
transform(Prop = #{<<"key">> := <<"light_switch">>, <<"unit">> := Unit}) ->
|
||||
Prop#{<<"name">> => <<"开关"/utf8>>, <<"value">> => Unit, <<"unit">> => ?UNIT0};
|
||||
transform(Prop = #{<<"key">> := <<"light_brightness">>, <<"unit">> := Unit}) when is_integer(Unit) ->
|
||||
Label = iolist_to_binary([<<"亮度设置为:"/utf8>>, integer_to_binary(Unit)]),
|
||||
Prop#{<<"name">> => <<"亮度"/utf8>>, <<"label">> => Label, <<"value">> => Unit, <<"unit">> => ?UNIT0};
|
||||
transform(Prop = #{<<"key">> := <<"light_change_time">>, <<"unit">> := Unit}) ->
|
||||
Prop#{<<"name">> => <<"变亮变暗时间"/utf8>>, <<"value">> => Unit, <<"unit">> => ?UNIT0};
|
||||
transform(Prop = #{<<"key">> := <<"light_status">>, <<"unit">> := Unit}) ->
|
||||
Prop#{<<"name">> => <<"是否损坏"/utf8>>, <<"value">> => Unit, <<"unit">> => ?UNIT0};
|
||||
transform(Prop = #{<<"unit">> := Unit}) ->
|
||||
Prop#{<<"value">> => Unit, <<"unit">> => ?UNIT0}.
|
||||
80
apps/aircon/src/aircon_device_sup.erl
Normal file
80
apps/aircon/src/aircon_device_sup.erl
Normal file
@ -0,0 +1,80 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author anlicheng
|
||||
%%% @copyright (C) 2024, <COMPANY>
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 04. 9月 2024 15:39
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aircon_device_sup).
|
||||
-author("anlicheng").
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
%% API
|
||||
-export([start_link/0]).
|
||||
-export([ensure_device_started/1, delete_device/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.
|
||||
init([]) ->
|
||||
SupFlags = #{strategy => one_for_one, intensity => 1000, period => 3600},
|
||||
|
||||
{ok, {SupFlags, []}}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
|
||||
-spec ensure_device_started(DeviceUUID :: binary()) -> {ok, Pid :: pid()} | {error, Reason :: any()}.
|
||||
ensure_device_started(DeviceUUID) when is_binary(DeviceUUID) ->
|
||||
case aircon_device:get_pid(DeviceUUID) of
|
||||
DevicePid when is_pid(DevicePid) ->
|
||||
{ok, DevicePid};
|
||||
undefined ->
|
||||
case supervisor:start_child(?MODULE, child_spec(DeviceUUID)) of
|
||||
{ok, Pid} when is_pid(Pid) ->
|
||||
{ok, Pid};
|
||||
{error, {'already_started', Pid}} when is_pid(Pid) ->
|
||||
{ok, Pid};
|
||||
{error, Error} ->
|
||||
{error, Error}
|
||||
end
|
||||
end.
|
||||
|
||||
delete_device(DeviceUUID) when is_binary(DeviceUUID) ->
|
||||
Id = aircon_device:get_name(DeviceUUID),
|
||||
ok = supervisor:terminate_child(?MODULE, Id),
|
||||
supervisor:delete_child(?MODULE, Id).
|
||||
|
||||
child_spec(DeviceUUID) when is_binary(DeviceUUID) ->
|
||||
Name = aircon_device:get_name(DeviceUUID),
|
||||
#{
|
||||
id => Name,
|
||||
start => {aircon_device, start_link, [Name, DeviceUUID]},
|
||||
restart => permanent,
|
||||
shutdown => 2000,
|
||||
type => worker,
|
||||
modules => ['aircon_device']
|
||||
}.
|
||||
@ -63,14 +63,14 @@ init([]) ->
|
||||
modules => ['aircon_args']
|
||||
},
|
||||
|
||||
%#{
|
||||
% id => 'power_device_sup',
|
||||
% start => {'power_device_sup', start_link, []},
|
||||
% restart => permanent,
|
||||
% shutdown => 2000,
|
||||
% type => worker,
|
||||
% modules => ['power_device_sup']
|
||||
%},
|
||||
#{
|
||||
id => 'aircon_device_sup',
|
||||
start => {'aircon_device_sup', start_link, []},
|
||||
restart => permanent,
|
||||
shutdown => 2000,
|
||||
type => worker,
|
||||
modules => ['aircon_device_sup']
|
||||
},
|
||||
|
||||
#{
|
||||
id => 'aircon_mqtt_subscriber',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user