From 2c53f00fae60af39c704cce91b9f22b173e8ef51 Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Fri, 9 Jan 2026 15:30:33 +0800 Subject: [PATCH] add docker env --- .args.yaml | 26 ++++++++++ Dockerfile | 3 ++ apps/aircon/src/aircon.app.src | 2 +- apps/aircon/src/aircon_app.erl | 7 +-- apps/aircon/src/aircon_args.erl | 4 +- apps/aircon/src/aircon_device.erl | 60 +++++++++------------- apps/aircon/src/aircon_mqtt_publisher.erl | 9 ++-- apps/aircon/src/aircon_mqtt_subscriber.erl | 17 +++--- apps/aircon/src/efka_client.erl | 18 ++++--- boot.sh | 3 ++ build.sh | 4 ++ config/sys-dev.config | 3 ++ config/sys-prod.config | 34 +++++++++++- stop.sh | 3 ++ 14 files changed, 129 insertions(+), 64 deletions(-) create mode 100644 .args.yaml create mode 100644 Dockerfile create mode 100755 boot.sh create mode 100755 build.sh create mode 100755 stop.sh diff --git a/.args.yaml b/.args.yaml new file mode 100644 index 0000000..a2b0c0e --- /dev/null +++ b/.args.yaml @@ -0,0 +1,26 @@ +metrics: + - + key: serial + type: string + label: "空调设备ID" + config: + default: "" + maxlen: 20 + - + key: device_uuid + type: string + label: "设备UUID" + config: + default: "" + maxlen: 50 + +boot: "./boot.sh" +# stop can be used for execute stop action +stop: "./stop.sh" + +# coll / collect for collection, has data upload +# stream has transform +# up / upload for data upload +service_type: "coll" + +run_as_root: true \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d15319b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,3 @@ +FROM erlang:25.3 + +CMD /data/aircon/bin/aircon foreground \ No newline at end of file diff --git a/apps/aircon/src/aircon.app.src b/apps/aircon/src/aircon.app.src index 7bbef3e..0fcaeca 100644 --- a/apps/aircon/src/aircon.app.src +++ b/apps/aircon/src/aircon.app.src @@ -1,5 +1,5 @@ {application, aircon, - [{description, "An OTP application"}, + [{description, "aircon gateway application"}, {vsn, "0.1.0"}, {registered, []}, {mod, {aircon_app, []}}, diff --git a/apps/aircon/src/aircon_app.erl b/apps/aircon/src/aircon_app.erl index 388258a..88f27b8 100644 --- a/apps/aircon/src/aircon_app.erl +++ b/apps/aircon/src/aircon_app.erl @@ -11,7 +11,6 @@ start(_StartType, _StartArgs) -> set_logger_level(), - logger:warning("call me here start logger"), aircon_sup:start_link(). stop(_State) -> @@ -22,6 +21,8 @@ stop(_State) -> set_logger_level() -> logger:set_application_level(kernel, notice), logger:set_application_level(stdlib, notice), - logger:set_application_level(emqtt, debug), - logger:set_application_level(aircon, debug), + logger:set_application_level(emqtt, notice), + + {ok, Level} = application:get_env(aircon, logger_level), + logger:set_application_level(aircon, Level), ok. \ No newline at end of file diff --git a/apps/aircon/src/aircon_args.erl b/apps/aircon/src/aircon_args.erl index 07e1df1..3605e3b 100644 --- a/apps/aircon/src/aircon_args.erl +++ b/apps/aircon/src/aircon_args.erl @@ -69,7 +69,7 @@ init([]) -> {ok, Metrics} = efka_client:request_metric(), try convert_metric(Metrics) of {ok, MetricMap} -> - logger:debug("[aircon_args] init load metric_map: ~p", [MetricMap]), + logger:info("[aircon_args] init load metric_map: ~p", [MetricMap]), {ok, Param} = efka_client:request_param(), {ok, #state{metrics = MetricMap, param = Param}} @@ -111,7 +111,7 @@ handle_cast({push_param, Param}, State = #state{}) -> handle_cast({push_metric, Metrics}, State = #state{}) -> try convert_metric(Metrics) of {ok, MetricMap} -> - logger:debug("[aircon_args] push metric_map: ~p", [MetricMap]), + logger:info("[aircon_args] push metric_map: ~p", [MetricMap]), {noreply, State#state{metrics = MetricMap}} catch _:_ -> {noreply, State} diff --git a/apps/aircon/src/aircon_device.erl b/apps/aircon/src/aircon_device.erl index eb22c73..c7297d8 100644 --- a/apps/aircon/src/aircon_device.erl +++ b/apps/aircon/src/aircon_device.erl @@ -96,24 +96,27 @@ handle_call(_Request, _From, State = #state{}) -> {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, + Props0 when is_list(Props0) -> + case is_valid_props(Props0) of + true -> + Props = lists:map(fun(Fields) -> 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), + %% 如果设备当前是离线状态,则需要发送上线消息 + Status == 0 andalso efka_client:device_online(DeviceUUID), - {noreply, State#state{data_counter = DataCounter + 1, status = 1}}; - M when is_map(M) -> - logger:notice("[power_device] invalid map: ~p", [M]); + {noreply, State#state{data_counter = DataCounter + 1, status = 1}}; + false -> + logger:notice("[aircon_device] invalid map: ~p", [Props0]) + end; Error -> - logger:notice("[power_device] jiffy decode error: ~p", [Error]), + logger:notice("[aircon_device] jiffy decode error: ~p", [Error]), {noreply, State} end. @@ -155,23 +158,10 @@ code_change(_OldVsn, State = #state{}, _Extra) -> %%% 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}. \ No newline at end of file +-spec is_valid_props(Props :: list()) -> boolean(). +is_valid_props(Props) when is_list(Props) -> + lists:all(fun is_valid_props0/1, Props). +is_valid_props0(#{<<"key">> := Key, <<"value">> := _, <<"unit">> := Unit, <<"type">> := Type, <<"name">> := Name, <<"label">> := Label}) -> + is_binary(Key) andalso is_integer(Unit) andalso is_binary(Name) andalso is_binary(Label) andalso is_binary(Type); +is_valid_props0(_) -> + false. \ No newline at end of file diff --git a/apps/aircon/src/aircon_mqtt_publisher.erl b/apps/aircon/src/aircon_mqtt_publisher.erl index 220884c..0143f7e 100644 --- a/apps/aircon/src/aircon_mqtt_publisher.erl +++ b/apps/aircon/src/aircon_mqtt_publisher.erl @@ -57,18 +57,17 @@ start_link() -> init([]) -> %% 建立到emqx服务器的连接 Opts = emqx_opts(<<"aircon-data-publisher">>), - logger:debug("[mqtt_publisher] opts is: ~p", [Opts]), + logger:info("[mqtt_publisher] start with opts is: ~p", [Opts]), case emqtt:start_link(Opts) of {ok, ConnPid} -> - logger:debug("[mqtt_publisher] start conntecting, pid: ~p", [ConnPid]), {ok, _} = emqtt:connect(ConnPid), - logger:debug("[mqtt_publisher] connect success"), + logger:info("[mqtt_publisher] connect success, pid: ~p", [ConnPid]), {ok, #state{conn_pid = ConnPid}}; ignore -> - logger:debug("[mqtt_publisher] connect emqx get ignore"), + logger:warning("[mqtt_publisher] connect emqx get ignore"), {stop, ignore}; {error, Reason} -> - logger:debug("[mqtt_publisher] connect emqx get error: ~p", [Reason]), + logger:warning("[mqtt_publisher] connect emqx get error: ~p", [Reason]), {stop, Reason} end. diff --git a/apps/aircon/src/aircon_mqtt_subscriber.erl b/apps/aircon/src/aircon_mqtt_subscriber.erl index 8108959..e23bfb8 100644 --- a/apps/aircon/src/aircon_mqtt_subscriber.erl +++ b/apps/aircon/src/aircon_mqtt_subscriber.erl @@ -51,22 +51,21 @@ start_link() -> init([]) -> %% 建立到emqx服务器的连接 Opts = emqx_opts(<<"aircon-data-subscriber">>), - logger:debug("[mqtt_subscriber] opts is: ~p", [Opts]), + logger:info("[mqtt_subscriber] start with opts is: ~p", [Opts]), case emqtt:start_link(Opts) of {ok, ConnPid} -> - logger:debug("[mqtt_subscriber] start conntecting, pid: ~p", [ConnPid]), {ok, _} = emqtt:connect(ConnPid), - logger:debug("[mqtt_subscriber] connect success"), + logger:info("[mqtt_subscriber] connect success, pid: ~p", [ConnPid]), %% 监听和设备的全部事件 SubscribeResult = emqtt:subscribe(ConnPid, ?Topics), - logger:debug("[mqtt_subscriber] subscribe topics: ~p, result is: ~p", [?Topics, SubscribeResult]), + logger:info("[mqtt_subscriber] subscribe topics: ~p, result is: ~p", [?Topics, SubscribeResult]), {ok, #state{conn_pid = ConnPid}}; ignore -> - logger:debug("[mqtt_subscriber] connect emqx get ignore"), + logger:warning("[mqtt_subscriber] connect emqx get ignore"), {stop, ignore}; {error, Reason} -> - logger:debug("[mqtt_subscriber] connect emqx get error: ~p", [Reason]), + logger:warning("[mqtt_subscriber] connect emqx get error: ~p", [Reason]), {stop, Reason} end. @@ -99,7 +98,7 @@ handle_cast(_Request, State = #state{}) -> {noreply, NewState :: #state{}, timeout() | hibernate} | {stop, Reason :: term(), NewState :: #state{}}). handle_info({disconnect, ReasonCode, Properties}, State = #state{}) -> - logger:debug("[mqtt_subscriber] Recv a DISONNECT packet - ReasonCode: ~p, Properties: ~p", [ReasonCode, Properties]), + logger:warning("[mqtt_subscriber] Recv a DISONNECT packet - ReasonCode: ~p, Properties: ~p", [ReasonCode, Properties]), {stop, disconnected, State}; %% 必须要做到消息的快速分发,数据的json反序列需要在host进程进行 handle_info({publish, #{packet_id := _PacketId, payload := Payload, qos := Qos, topic := Topic}}, State = #state{conn_pid = _ConnPid}) -> @@ -187,12 +186,12 @@ emqx_opts(ClientSuffix) when is_binary(ClientSuffix) -> dispatch(DeviceMac, Message) when is_binary(DeviceMac), is_binary(Message) -> case aircon_args:get_device_uuid(DeviceMac) of error -> - logger:notice("[mqtt_subscriber] device_mac: ~p, device_uuid not found", [DeviceMac]); + logger:warning("[mqtt_subscriber] device_mac: ~p, device_uuid not found", [DeviceMac]); {ok, DeviceUUID} -> case aircon_device_sup:ensure_device_started(DeviceUUID) of {ok, DevicePid} -> aircon_device:metric_data(DevicePid, Message); {error, Reason} -> - logger:notice("[mqtt_subscriber] start device get error: ~p", [Reason]) + logger:warning("[mqtt_subscriber] start device get error: ~p", [Reason]) end end. \ No newline at end of file diff --git a/apps/aircon/src/efka_client.erl b/apps/aircon/src/efka_client.erl index 1f9e402..a1f6425 100644 --- a/apps/aircon/src/efka_client.erl +++ b/apps/aircon/src/efka_client.erl @@ -6,6 +6,7 @@ %%% Created : 28. 8月 2023 15:39 %%%------------------------------------------------------------------- -module(efka_client). +-feature(maybe_expr, enable). -author("aresei"). -behaviour(gen_server). @@ -144,12 +145,15 @@ start_link(RegisterName, Host, Port) when is_binary(RegisterName), is_list(Host) {ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} | {stop, Reason :: term()} | ignore). init([RegisterName, Host, Port]) -> - {ok, Socket} = gen_tcp:connect(Host, Port, [binary, {packet, 4}, {active, true}]), - ok = gen_tcp:controlling_process(Socket, self()), - case do_register(RegisterName, Socket) of - ok -> - {ok, #state{packet_id = 1, host = Host, port = Port, socket = Socket}}; - {error, Reason} -> + maybe + logger:info("[efka_client] connect ~p:~p, register name: ~p", [Host, Port, RegisterName]), + {ok, Socket} ?= gen_tcp:connect(Host, Port, [binary, {packet, 4}, {active, true}]), + ok = gen_tcp:controlling_process(Socket, self()), + ok ?= do_register(RegisterName, Socket), + {ok, #state{packet_id = 1, host = Host, port = Port, socket = Socket}} + else + Reason -> + logger:notice("[efka_client] init get error: ~p", [Reason]), {stop, Reason} end. @@ -173,7 +177,7 @@ do_register(RegisterName, Socket) -> end after ?EFKA_REQUEST_TIMEOUT -> - {error, timeout} + {error, register_timeout} end. %% @private diff --git a/boot.sh b/boot.sh new file mode 100755 index 0000000..28cf5d1 --- /dev/null +++ b/boot.sh @@ -0,0 +1,3 @@ +#! /bin/bash + +docker run -e TZ=Asia/Shanghai --hostname=aircon --net=host --restart=always -v /data/docker/aircon/:/data/aircon/ aircon:latest \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..87c07a3 --- /dev/null +++ b/build.sh @@ -0,0 +1,4 @@ +#!/bin/bash +rebar3 compile && rebar3 release && rebar3 tar + +tar -czvf aircon_gateway.tgz boot.sh stop.sh Dockerfile .args.yaml \ No newline at end of file diff --git a/config/sys-dev.config b/config/sys-dev.config index c0c0c45..198d3b2 100644 --- a/config/sys-dev.config +++ b/config/sys-dev.config @@ -3,6 +3,9 @@ %% 离线判断时间间隔,单位:(秒) {heartbeat_ticker, 120}, + %% 设置当前系统的日志级别 + {logger_level, debug}, + {emqx_server, [ {host, "118.178.229.213"}, {port, 1883}, diff --git a/config/sys-prod.config b/config/sys-prod.config index a16c10f..7802ef1 100644 --- a/config/sys-prod.config +++ b/config/sys-prod.config @@ -3,6 +3,9 @@ %% 离线判断时间间隔,单位:(秒) {heartbeat_ticker, 120}, + %% 设置当前系统的日志级别 + {logger_level, debug}, + {emqx_server, [ {host, "172.30.37.212"}, {port, 1883}, @@ -14,8 +17,35 @@ ]}, {efka_server, [ - {host, "39.98.184.67"}, + {host, "localhost"}, {port, 3361} ]} + ]}, + + {kernel, [ + %% 设置 Logger 的 primary log level + {logger_level, debug}, + {logger, [ + {handler, default, logger_std_h, + #{ + level => debug, + formatter => {logger_formatter, #{template => [time, " [", level, "] ", msg, "\n"]}} + } + }, + + {handler, disk, logger_disk_log_h, + #{ + level => debug, + config => #{ + file => "log/debug.log", + max_no_files => 10, + max_no_bytes => 524288000 + }, + formatter => {logger_formatter, #{template => [time, " [", level, "] ", msg, "\n"]}} + } + } + + ]} ]} -]. + +]. \ No newline at end of file diff --git a/stop.sh b/stop.sh new file mode 100755 index 0000000..0aeeda9 --- /dev/null +++ b/stop.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +docker ps -a | grep "aircon:latest" | awk -F " " '{print $1}' | xargs docker stop | xargs docker rm \ No newline at end of file