diff --git a/apps/iot/src/endpoint/iot_donghuoliren_endpoint.erl b/apps/iot/src/endpoint/iot_donghuoliren_endpoint.erl new file mode 100644 index 0000000..9303df6 --- /dev/null +++ b/apps/iot/src/endpoint/iot_donghuoliren_endpoint.erl @@ -0,0 +1,186 @@ +%%%------------------------------------------------------------------- +%%% @author anlicheng +%%% @copyright (C) 2024, +%%% @doc +%%% +%%% @end +%%% Created : 13. 12月 2024 16:20 +%%%------------------------------------------------------------------- +-module(iot_donghuoliren_endpoint). +-author("anlicheng"). + +-behaviour(gen_server). + +%% API +-export([start_link/0]). +-export([forward/4, get_status/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). + +-record(state, { + %% 签名用的token值 + token :: binary(), + url :: string(), + succ_counter = 0, + fail_counter = 0, + logger_pid :: pid() +}). + +%%%=================================================================== +%%% API +%%%=================================================================== + +test() -> + forward(<<"1234">>, <<"1234">>, 17, #{ + <<"datetime">> => <<"2024-12-12 03:10:29">>, + <<"attachments">> => [] + }). + +-spec forward(LocationCode :: binary(), DynamicLocationCode :: binary(), EventType :: integer(), Params :: map()) -> no_return(). +forward(LocationCode, DynamicLocationCode, EventType, Params) when is_binary(LocationCode), is_binary(DynamicLocationCode), is_integer(EventType), is_map(Params) -> + gen_server:cast(?MODULE, {forward, LocationCode, DynamicLocationCode, EventType, Params}). + +-spec get_status() -> {ok, map()}. +get_status() -> + gen_server:call(?MODULE, get_status). + +%% @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, ?SERVER}, ?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([]) -> + {ok, Props} = application:get_env(iot, donghuoliren), + Token = proplists:get_value(token, Props), + Url = proplists:get_value(url, Props), + + {ok, LoggerPid} = iot_logger:start_link("donghuoliren_data"), + + {ok, #state{token = Token, url = Url, logger_pid = LoggerPid}}. + +%% @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(get_status, _From, State = #state{succ_counter = SuccCounter, fail_counter = FailCounter}) -> + {reply, {ok, #{succ => SuccCounter, fail => FailCounter}}, 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({forward, LocationCode, DynamicLocationCode, EventType, Params}, + State = #state{url = Url, token = Token, logger_pid = LoggerPid, succ_counter = SuccCounter, fail_counter = FailCounter}) -> + + Body = format_event(LocationCode, DynamicLocationCode, EventType, Params), + case do_post(Url, Token, Body) of + {ok, RespBody} -> + %% 记录日志 + iot_logger:write(LoggerPid, [<<"OK">>, Body, RespBody]), + {noreply, State#state{succ_counter = SuccCounter + 1}}; + {error, Reason} -> + NReason = iolist_to_binary(io_lib:format("~p", Reason)), + iot_logger:write(LoggerPid, [<<"ERROR">>, Body, NReason]), + {noreply, State#state{fail_counter = FailCounter + 1}} + 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(_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 +%%%=================================================================== + +-spec do_post(Url :: string(), Token :: binary(), Body :: binary()) -> {ok, Resp :: binary()} | {error, any()}. +do_post(Url, Token, Body) when is_binary(Body) -> + Headers = [ + {<<"content-type">>, <<"application/json">>} + ], + Sign = iot_util:md5(iolist_to_binary([Token, Body, Token])), + + Url1 = Url ++ "?sign=" ++ binary_to_list(Sign), + case hackney:request(post, Url1, Headers, Body) of + {ok, 200, _, ClientRef} -> + {ok, RespBody} = hackney:body(ClientRef), + hackney:close(ClientRef), + {ok, RespBody}; + {ok, HttpCode, _, ClientRef} -> + {ok, RespBody} = hackney:body(ClientRef), + hackney:close(ClientRef), + {error, {HttpCode, RespBody}}; + {error, Reason} -> + {error, Reason} + end. + +%% 格式话要发送的数据,避免多次格式化处理 +-spec format_event(LocationCode :: binary(), DynamicLocationCode :: binary(), EventType :: integer(), Params :: map()) -> Body :: binary(). +format_event(LocationCode, DynamicLocationCode, EventType, + #{<<"datetime">> := Datetime, <<"attachments">> := Attachments0}) + when is_binary(LocationCode), is_binary(DynamicLocationCode), is_integer(EventType) -> + + %Attachments = lists:map(fun(#{<<"filename">> := Filename}) -> + % {ok, FileUrl} = iot_util:file_uri(Filename), + % Name = filename:basename(FileUrl), + % #{<<"name">> => Name, <<"url">> => FileUrl} + % end, Attachments0), + + Attachments = [ + <<"https://lgsiot.njau.edu.cn/upload/2024/11/29/2024-11-29-1732842080-1732842100.mp4">>, + <<"https://lgsiot.njau.edu.cn/upload/2024/11/29/2024-11-29-1732842080-1732842100.jpg">> + ], + + Params = #{ + <<"eventLocation">> => <<"事件地点测试值"/utf8>>, + <<"eventType">> => <<"动火离人"/utf8>>, + <<"eventTime">> => Datetime, + <<"videoJkAddr">> => <<"rtsp://admin:admin@123@192.168.111.147/cam/realmonitor?channel=1&subtype=0">>, + <<"attachments">> => Attachments + }, + + iolist_to_binary(jiffy:encode(Params, [force_utf8])). \ No newline at end of file diff --git a/apps/iot/src/endpoint/iot_jinzhi_endpoint.erl b/apps/iot/src/endpoint/iot_jinzhi_endpoint.erl index 9b3014b..8b42397 100644 --- a/apps/iot/src/endpoint/iot_jinzhi_endpoint.erl +++ b/apps/iot/src/endpoint/iot_jinzhi_endpoint.erl @@ -93,16 +93,13 @@ handle_call(_Request, _From, State = #state{}) -> {noreply, NewState :: #state{}, timeout() | hibernate} | {stop, Reason :: term(), NewState :: #state{}}). handle_cast({forward, LocationCode, DynamicLocationCode, EventType, Params}, State = #state{id = Id, timer_map = TimerMap, pri_key = PriKey, url = Url}) -> - case format_event(LocationCode, DynamicLocationCode, EventType, Params, PriKey) of - error -> - {noreply, State}; - {ok, ReqBody} -> - Res = catch do_post(Url, Id, ReqBody), - lager:debug("[iot_jinzhi_endpoint] format_data: ~p, post result: ~p", [ReqBody, Res]), - TimerRef = erlang:start_timer(?RETRY_INTERVAL, self(), {repost_ticker, Id, ReqBody}), + ReqBody = format_event(LocationCode, DynamicLocationCode, EventType, Params, PriKey), - {noreply, State#state{id = Id + 1, timer_map = maps:put(Id, TimerRef, TimerMap)}} - end. + Res = catch do_post(Url, Id, ReqBody), + lager:debug("[iot_jinzhi_endpoint] format_data: ~p, post result: ~p", [ReqBody, Res]), + TimerRef = erlang:start_timer(?RETRY_INTERVAL, self(), {repost_ticker, Id, ReqBody}), + + {noreply, State#state{id = Id + 1, timer_map = maps:put(Id, TimerRef, TimerMap)}}. %% @private %% @doc Handling all non call/cast messages @@ -180,42 +177,36 @@ do_post(Url, Id, Body) when is_list(Url), is_integer(Id), is_binary(Body) -> end). %% 格式话要发送的数据,避免多次格式化处理 --spec format_event(LocationCode :: binary(), DynamicLocationCode :: binary(), EventType :: integer(), Params :: map(), PriKey :: public_key:private_key()) -> error | {ok, binary()}. +-spec format_event(LocationCode :: binary(), DynamicLocationCode :: binary(), EventType :: integer(), Params :: map(), PriKey :: public_key:private_key()) -> binary(). format_event(LocationCode, DynamicLocationCode, EventType, #{<<"event_code">> := EventCode, <<"description">> := Description, <<"datetime">> := Datetime, <<"attachments">> := Attachments0}, PriKey) when is_binary(LocationCode), is_binary(DynamicLocationCode), is_integer(EventType) -> - %% 动火离人不推送给金智 2024-12-02 - case lists:member(EventCode, [<<"23104">>]) of - true -> - error; - false -> - Attachments = lists:map(fun(#{<<"filename">> := Filename}) -> - {ok, FileUrl} = iot_util:file_uri(Filename), - Name = filename:basename(FileUrl), - #{<<"name">> => Name, <<"url">> => FileUrl} - end, Attachments0), + Attachments = lists:map(fun(#{<<"filename">> := Filename}) -> + {ok, FileUrl} = iot_util:file_uri(Filename), + Name = filename:basename(FileUrl), + #{<<"name">> => Name, <<"url">> => FileUrl} + end, Attachments0), - % <<"occurrenceTime">> => <<"2023-06-10 12:00:00">>, + % <<"occurrenceTime">> => <<"2023-06-10 12:00:00">>, - LocationCode1 = fake_location_code(EventCode, LocationCode), - DeviceInfo = #{ - % <<"location">> => LocationCode, - <<"location">> => LocationCode1, - <<"category">> => EventCode, - <<"description">> => Description, - <<"occurrenceTime">> => Datetime, - <<"attachments">> => Attachments - }, + LocationCode1 = fake_location_code(EventCode, LocationCode), + DeviceInfo = #{ + % <<"location">> => LocationCode, + <<"location">> => LocationCode1, + <<"category">> => EventCode, + <<"description">> => Description, + <<"occurrenceTime">> => Datetime, + <<"attachments">> => Attachments + }, - ReqData = #{ - <<"sign">> => sign(DeviceInfo, PriKey), - <<"sysId">> => ?SYS_ID, - <<"taskId">> => generate_task_id(LocationCode1, EventCode), - <<"count">> => 1, - <<"deviceInfo">> => DeviceInfo - }, - {ok, iolist_to_binary(jiffy:encode(ReqData, [force_utf8]))} - end. + ReqData = #{ + <<"sign">> => sign(DeviceInfo, PriKey), + <<"sysId">> => ?SYS_ID, + <<"taskId">> => generate_task_id(LocationCode1, EventCode), + <<"count">> => 1, + <<"deviceInfo">> => DeviceInfo + }, + iolist_to_binary(jiffy:encode(ReqData, [force_utf8])). -spec generate_task_id(LocationCode :: binary(), EventCode :: binary()) -> binary(). generate_task_id(LocationCode, EventCode) when is_binary(LocationCode), is_binary(EventCode) -> diff --git a/apps/iot/src/iot_ai_router.erl b/apps/iot/src/iot_ai_router.erl index 42075e1..689a22c 100644 --- a/apps/iot/src/iot_ai_router.erl +++ b/apps/iot/src/iot_ai_router.erl @@ -18,7 +18,14 @@ route_uuid(RouterUUID, EventType, Params) when is_binary(RouterUUID), is_integer %% 查找终端设备对应的点位信息 case redis_client:hgetall(RouterUUID) of {ok, #{<<"location_code">> := LocationCode, <<"dynamic_location_code">> := DynamicLocationCode}} when is_binary(LocationCode), is_binary(DynamicLocationCode) -> - iot_jinzhi_endpoint:forward(LocationCode, DynamicLocationCode, EventType, Params); + %% 动火离人不推送给金智 2024-12-02 + case lists:member(EventType, [17]) of + true -> + lager:debug("[iot_ai_router] donghuoliren: ~p", [RouterUUID]); + %iot_donghuoliren_endpoint:forward(LocationCode, DynamicLocationCode, EventType, Params); + false -> + iot_jinzhi_endpoint:forward(LocationCode, DynamicLocationCode, EventType, Params) + end; {ok, _} -> lager:debug("[iot_ai_router] the event_data hget location_code, uuid: ~p, not found", [RouterUUID]); {error, Reason} -> diff --git a/apps/iot/src/iot_sup.erl b/apps/iot/src/iot_sup.erl index aa11084..4c32dd3 100644 --- a/apps/iot/src/iot_sup.erl +++ b/apps/iot/src/iot_sup.erl @@ -82,6 +82,15 @@ init([]) -> modules => ['iot_zd_endpoint'] }, + #{ + id => 'iot_donghuoliren_endpoint', + start => {'iot_donghuoliren_endpoint', start_link, []}, + restart => permanent, + shutdown => 2000, + type => worker, + modules => ['iot_donghuoliren_endpoint'] + }, + #{ id => 'iot_zd_consumer', start => {'iot_zd_consumer', start_link, []}, diff --git a/config/sys-dev.config b/config/sys-dev.config index 8eaa5e4..66d0381 100644 --- a/config/sys-dev.config +++ b/config/sys-dev.config @@ -71,6 +71,12 @@ ]} ]}, + %% 智慧监控平台 + {donghuoliren, [ + {url, "https://xsdc.njau.edu.cn/hq-cyaqjg/rest/rgkSmart/push"}, + {token, <<"aB3$dEfGhiJkLmNoPqRsTuVwXyZ!@#4f5e6d7c8b9a0f1e2d">>} + ]}, + {pools, [ %% mysql连接池配置 {mysql_iot, diff --git a/config/sys-prod.config b/config/sys-prod.config index 5704247..4992105 100644 --- a/config/sys-prod.config +++ b/config/sys-prod.config @@ -58,6 +58,12 @@ {pool_size, 10} ]}, + %% 智慧监控平台 + {donghuoliren, [ + {url, "https://xsdc.njau.edu.cn/hq-cyaqjg/rest/rgkSmart/push"}, + {token, <<"aB3$dEfGhiJkLmNoPqRsTuVwXyZ!@#4f5e6d7c8b9a0f1e2d">>} + ]}, + %% influxdb数据库配置 {influx_pool, [ {pool_size, 100}, diff --git a/docs/jinzhi.md b/docs/jinzhi.md index 5bfe939..d712c90 100644 --- a/docs/jinzhi.md +++ b/docs/jinzhi.md @@ -1,12 +1,12 @@ # 事件分类 ```text -23103 鼠类监测 event_code: 11 -20407 异物占道 event_code: 13 -10404 垃圾溢满 event_code: 14 -22503 后厨非标着装 event_code: 12 -23104 动火离人 event_code: 15 -22706 强弱电间违规进入 event_code: 16 +event_code: 23103 鼠类监测 event_type: 11 +event_code: 20407 异物占道 event_type: 13 +event_code: 10404 垃圾溢满 event_type: 14 +event_code: 22503 后厨非标着装 event_type: 12 +event_code: 23104 动火离人 event_type: 15 +event_code: 22706 强弱电间违规进入 event_type: 16 ``` 1、鼠类检测 0508103010001050300001 朴苑餐厅