%%-------------------------------------------------------------------- %% Copyright (c) 2023-2024 EMQ Technologies Co., Ltd. All Rights Reserved. %%-------------------------------------------------------------------- -module(emqx_gbt32960_frame). -behaviour(emqx_gateway_frame). -include("emqx_gbt32960.hrl"). -include_lib("emqx/include/logger.hrl"). %% emqx_gateway_frame callbacks -export([ initial_parse_state/1, serialize_opts/0, serialize_pkt/2, parse/2, format/1, type/1, is_message/1 ]). -define(FLAG, 1 / binary). -define(BYTE, 8 / big - integer). -define(WORD, 16 / big - integer). -define(DWORD, 32 / big - integer). %% CMD: 1, ACK: 1, VIN: 17, Enc: 1, Len: 2 -define(HEADER_SIZE, 22). -define(IS_RESPONSE(Ack), Ack == ?ACK_SUCCESS orelse Ack == ?ACK_ERROR orelse Ack == ?ACK_VIN_REPEAT ). -type phase() :: search_heading | parse. -type parser_state() :: #{ data := binary(), phase := phase() }. -ifdef(TEST). -export([serialize/1]). -endif. %%-------------------------------------------------------------------- %% Init a Parser %%-------------------------------------------------------------------- -spec initial_parse_state(map()) -> parser_state(). initial_parse_state(_) -> #{data => <<>>, phase => search_heading}. -spec serialize_opts() -> emqx_gateway_frame:serialize_options(). serialize_opts() -> #{}. %%-------------------------------------------------------------------- %% Parse Message %%-------------------------------------------------------------------- parse(Bin, State) -> case enter_parse(Bin, State) of {ok, Message, Rest} -> {ok, Message, Rest, State#{data => <<>>, phase => search_heading}}; {error, Error} -> {error, Error}; {more_data_follow, Partial} -> {more, State#{data => Partial, phase => parse}} end. enter_parse(Bin, #{phase := search_heading}) -> case search_heading(Bin) of {ok, Rest} -> parse_msg(Rest); Error -> Error end; enter_parse(Bin, #{data := Data}) -> parse_msg(<>). search_heading(<<16#23, 16#23, Rest/binary>>) -> {ok, Rest}; search_heading(<<_, Rest/binary>>) -> search_heading(Rest); search_heading(<<>>) -> {error, invalid_frame}. parse_msg(Binary) -> case byte_size(Binary) >= ?HEADER_SIZE of true -> {Frame, Rest2} = parse_header(Binary), case byte_size(Rest2) >= Frame#frame.length + 1 of true -> parse_body(Rest2, Frame); false -> {more_data_follow, Binary} end; false -> {more_data_follow, Binary} end. parse_header(<> = Binary) -> Check = cal_check(Binary, ?HEADER_SIZE, undefined), { #frame{cmd = Cmd, ack = Ack, vin = VIN, encrypt = Encrypt, length = Length, check = Check}, Rest2 }. parse_body(Binary, Frame = #frame{length = Length, check = OldCheck, encrypt = Encrypt}) -> <> = Binary, Check = cal_check(Binary, Length, OldCheck), case CheckByte == Check of true -> RawData = decipher(Data, Encrypt), {ok, Frame#frame{data = parse_data(Frame, RawData), rawdata = RawData}, Rest}; false -> {error, frame_check_error} end. % Algo: ?ENCRYPT_NONE, ENCRYPT_RSA, ENCRYPT_AES128 decipher(Data, _Algo) -> % TODO: decypher data Data. % Algo: ?ENCRYPT_NONE, ENCRYPT_RSA, ENCRYPT_AES128 encipher(Data, _Algo) -> % TODO: encipher data Data. parse_data( #frame{cmd = ?CMD_VIHECLE_LOGIN}, <> ) -> #{ <<"Time">> => #{ <<"Year">> => Year, <<"Month">> => Month, <<"Day">> => Day, <<"Hour">> => Hour, <<"Minute">> => Minute, <<"Second">> => Second }, <<"Seq">> => Seq, <<"ICCID">> => ICCID, <<"Num">> => Num, <<"Length">> => Length, <<"Id">> => Id }; parse_data( #frame{cmd = ?CMD_INFO_REPORT}, <> ) -> #{ <<"Time">> => #{ <<"Year">> => Year, <<"Month">> => Month, <<"Day">> => Day, <<"Hour">> => Hour, <<"Minute">> => Minute, <<"Second">> => Second }, <<"Infos">> => parse_info(Infos, []) }; parse_data( #frame{cmd = ?CMD_INFO_RE_REPORT}, <> ) -> #{ <<"Time">> => #{ <<"Year">> => Year, <<"Month">> => Month, <<"Day">> => Day, <<"Hour">> => Hour, <<"Minute">> => Minute, <<"Second">> => Second }, <<"Infos">> => parse_info(Infos, []) }; parse_data( #frame{cmd = ?CMD_VIHECLE_LOGOUT}, <> ) -> #{ <<"Time">> => #{ <<"Year">> => Year, <<"Month">> => Month, <<"Day">> => Day, <<"Hour">> => Hour, <<"Minute">> => Minute, <<"Second">> => Second }, <<"Seq">> => Seq }; parse_data( #frame{cmd = ?CMD_PLATFORM_LOGIN}, <> ) -> #{ <<"Time">> => #{ <<"Year">> => Year, <<"Month">> => Month, <<"Day">> => Day, <<"Hour">> => Hour, <<"Minute">> => Minute, <<"Second">> => Second }, <<"Seq">> => Seq, <<"Username">> => Username, <<"Password">> => Password, <<"Encrypt">> => Encrypt }; parse_data( #frame{cmd = ?CMD_PLATFORM_LOGOUT}, <> ) -> #{ <<"Time">> => #{ <<"Year">> => Year, <<"Month">> => Month, <<"Day">> => Day, <<"Hour">> => Hour, <<"Minute">> => Minute, <<"Second">> => Second }, <<"Seq">> => Seq }; parse_data(#frame{cmd = ?CMD_HEARTBEAT}, <<>>) -> #{}; parse_data(#frame{cmd = ?CMD_SCHOOL_TIME}, <<>>) -> #{}; parse_data( #frame{cmd = ?CMD_PARAM_QUERY}, <> ) -> %% XXX: need check ACK field? #{ <<"Time">> => #{ <<"Year">> => Year, <<"Month">> => Month, <<"Day">> => Day, <<"Hour">> => Hour, <<"Minute">> => Minute, <<"Second">> => Second }, <<"Total">> => Total, <<"Params">> => parse_params(Rest) }; parse_data( #frame{cmd = ?CMD_PARAM_SETTING}, <> ) -> ?SLOG(debug, #{msg => "rest", data => Rest}), #{ <<"Time">> => #{ <<"Year">> => Year, <<"Month">> => Month, <<"Day">> => Day, <<"Hour">> => Hour, <<"Minute">> => Minute, <<"Second">> => Second }, <<"Total">> => Total, <<"Params">> => parse_params(Rest) }; parse_data( #frame{cmd = ?CMD_TERMINAL_CTRL}, <> ) -> #{ <<"Time">> => #{ <<"Year">> => Year, <<"Month">> => Month, <<"Day">> => Day, <<"Hour">> => Hour, <<"Minute">> => Minute, <<"Second">> => Second }, <<"Command">> => Command, <<"Param">> => parse_ctrl_param(Command, Rest) }; parse_data(Frame, Data) -> ?SLOG(error, #{msg => "invalid_frame", frame => Frame, data => Data}), error(invalid_frame). %%-------------------------------------------------------------------- %% Parse Report Data Info %%-------------------------------------------------------------------- parse_info(<<>>, Acc) -> lists:reverse(Acc); parse_info(<>, Acc) -> <> = Body, parse_info(Rest, [ #{ <<"Type">> => <<"Vehicle">>, <<"Status">> => Status, <<"Charging">> => Charging, <<"Mode">> => Mode, <<"Speed">> => Speed, <<"Mileage">> => Mileage, <<"Voltage">> => Voltage, <<"Current">> => Current, <<"SOC">> => SOC, <<"DC">> => DC, <<"Gear">> => Gear, <<"Resistance">> => Resistance, <<"AcceleratorPedal">> => AcceleratorPedal, <<"BrakePedal">> => BrakePedal } | Acc ]); parse_info(<>, Acc) -> % 12 is packet len of per drive motor Len = Number * 12, <> = Rest, parse_info(Rest1, [ #{ <<"Type">> => <<"DriveMotor">>, <<"Number">> => Number, <<"Motors">> => parse_drive_motor(Bodys, []) } | Acc ]); parse_info(<>, Acc) -> <> = Rest, <> = Rest1, <> = Rest2, parse_info(Rest3, [ #{ <<"Type">> => <<"FuelCell">>, <<"CellVoltage">> => CellVoltage, <<"CellCurrent">> => CellCurrent, <<"FuelConsumption">> => FuelConsumption, <<"ProbeNum">> => ProbeNum, <<"ProbeTemps">> => binary_to_list(ProbeTemps), <<"H_MaxTemp">> => HMaxTemp, <<"H_TempProbeCode">> => HTempProbeCode, <<"H_MaxConc">> => HMaxConc, <<"H_ConcSensorCode">> => HConcSensorCode, <<"H_MaxPress">> => HMaxPress, <<"H_PressSensorCode">> => HPressSensorCode, <<"DCStatus">> => DCStatus } | Acc ]); parse_info( <>, Acc ) -> parse_info(Rest, [ #{ <<"Type">> => <<"Engine">>, <<"Status">> => Status, <<"CrankshaftSpeed">> => CrankshaftSpeed, <<"FuelConsumption">> => FuelConsumption } | Acc ]); parse_info( <>, Acc ) -> parse_info(Rest, [ #{ <<"Type">> => <<"Location">>, <<"Status">> => Status, <<"Longitude">> => Longitude, <<"Latitude">> => Latitude } | Acc ]); parse_info(<>, Acc) -> <> = Body, parse_info(Rest, [ #{ <<"Type">> => <<"Extreme">>, <<"MaxVoltageBatterySubsysNo">> => MaxVoltageBatterySubsysNo, <<"MaxVoltageBatteryCode">> => MaxVoltageBatteryCode, <<"MaxBatteryVoltage">> => MaxBatteryVoltage, <<"MinVoltageBatterySubsysNo">> => MinVoltageBatterySubsysNo, <<"MinVoltageBatteryCode">> => MinVoltageBatteryCode, <<"MinBatteryVoltage">> => MinBatteryVoltage, <<"MaxTempSubsysNo">> => MaxTempSubsysNo, <<"MaxTempProbeNo">> => MaxTempProbeNo, <<"MaxTemp">> => MaxTemp, <<"MinTempSubsysNo">> => MinTempSubsysNo, <<"MinTempProbeNo">> => MinTempProbeNo, <<"MinTemp">> => MinTemp } | Acc ]); parse_info(<>, Acc) -> <> = Rest, N1 = FaultChargeableDeviceNum * 4, <> = Rest1, N2 = FaultDriveMotorNum * 4, <> = Rest2, N3 = FaultEngineNum * 4, <> = Rest3, N4 = FaultOthersNum * 4, <> = Rest4, parse_info(Rest5, [ #{ <<"Type">> => <<"Alarm">>, <<"MaxAlarmLevel">> => MaxAlarmLevel, <<"GeneralAlarmFlag">> => GeneralAlarmFlag, <<"FaultChargeableDeviceNum">> => FaultChargeableDeviceNum, <<"FaultChargeableDeviceList">> => tune_fault_codelist(FaultChargeableDeviceList), <<"FaultDriveMotorNum">> => FaultDriveMotorNum, <<"FaultDriveMotorList">> => tune_fault_codelist(FaultDriveMotorList), <<"FaultEngineNum">> => FaultEngineNum, <<"FaultEngineList">> => tune_fault_codelist(FaultEngineList), <<"FaultOthersNum">> => FaultOthersNum, <<"FaultOthersList">> => tune_fault_codelist(FaultOthersList) } | Acc ]); parse_info(<>, Acc) -> {Rest1, SubSystems} = parse_chargeable_voltage(Rest, Number, []), parse_info(Rest1, [ #{ <<"Type">> => <<"ChargeableVoltage">>, <<"Number">> => Number, <<"SubSystems">> => SubSystems } | Acc ]); parse_info(<>, Acc) -> {Rest1, SubSystems} = parse_chargeable_temp(Rest, Number, []), parse_info(Rest1, [ #{ <<"Type">> => <<"ChargeableTemp">>, <<"Number">> => Number, <<"SubSystems">> => SubSystems } | Acc ]); parse_info(Rest, Acc) -> ?SLOG(error, #{msg => "invalid_info_feild", rest => Rest, acc => Acc}), error(invalid_info_feild). parse_drive_motor(<<>>, Acc) -> lists:reverse(Acc); parse_drive_motor( <>, Acc ) -> parse_drive_motor(Rest, [ #{ <<"No">> => No, <<"Status">> => Status, <<"CtrlTemp">> => CtrlTemp, <<"Rotating">> => Rotating, <<"Torque">> => Torque, <<"MotorTemp">> => MotorTemp, <<"InputVoltage">> => InputVoltage, <<"DCBusCurrent">> => DCBusCurrent } | Acc ]). parse_chargeable_voltage(Rest, 0, Acc) -> {Rest, lists:reverse(Acc)}; parse_chargeable_voltage( <>, Num, Acc ) -> Len = FrameCellsCount * 2, <> = Rest, parse_chargeable_voltage(Rest1, Num - 1, [ #{ <<"ChargeableSubsysNo">> => ChargeableSubsysNo, <<"ChargeableVoltage">> => ChargeableVoltage, <<"ChargeableCurrent">> => ChargeableCurrent, <<"CellsTotal">> => CellsTotal, <<"FrameCellsIndex">> => FrameCellsIndex, <<"FrameCellsCount">> => FrameCellsCount, <<"CellsVoltage">> => tune_voltage(CellsVoltage) } | Acc ]). parse_chargeable_temp(Rest, 0, Acc) -> {Rest, lists:reverse(Acc)}; parse_chargeable_temp(<>, Num, Acc) -> <> = Rest, parse_chargeable_temp(Rest1, Num - 1, [ #{ <<"ChargeableSubsysNo">> => ChargeableSubsysNo, <<"ProbeNum">> => ProbeNum, <<"ProbesTemp">> => binary_to_list(ProbesTemp) } | Acc ]). tune_fault_codelist(<<>>) -> []; tune_fault_codelist(Data) -> lists:flatten([list_to_binary(io_lib:format("~4.16.0B", [X])) || <> <= Data]). tune_voltage(Bin) -> tune_voltage_(Bin, []). tune_voltage_(<<>>, Acc) -> lists:reverse(Acc); tune_voltage_(<>, Acc) -> tune_voltage_(Rest, [V | Acc]). parse_params(Bin) -> parse_params_(Bin, []). parse_params_(<<>>, Acc) -> lists:reverse(Acc); parse_params_(<<16#01, Val:?WORD, Rest/binary>>, Acc) -> parse_params_(Rest, [#{<<"0x01">> => Val} | Acc]); parse_params_(<<16#02, Val:?WORD, Rest/binary>>, Acc) -> parse_params_(Rest, [#{<<"0x02">> => Val} | Acc]); parse_params_(<<16#03, Val:?WORD, Rest/binary>>, Acc) -> parse_params_(Rest, [#{<<"0x03">> => Val} | Acc]); parse_params_(<<16#04, Val:?BYTE, Rest/binary>>, Acc) -> parse_params_(Rest, [#{<<"0x04">> => Val} | Acc]); parse_params_(<<16#05, Rest/binary>>, Acc) -> case [V || #{<<"0x04">> := V} <- Acc] of [Len] -> <> = Rest, parse_params_(Rest1, [#{<<"0x05">> => Val} | Acc]); _ -> ?SLOG(error, #{ msg => "invalid_data", reason => "cmd_0x04 must appear ahead of cmd_0x05" }), lists:reverse(Acc) end; parse_params_(<<16#06, Val:?WORD, Rest/binary>>, Acc) -> parse_params_(Rest, [#{<<"0x06">> => Val} | Acc]); parse_params_(<<16#07, Val:5/binary, Rest/binary>>, Acc) -> parse_params_(Rest, [#{<<"0x07">> => Val} | Acc]); parse_params_(<<16#08, Val:5/binary, Rest/binary>>, Acc) -> parse_params_(Rest, [#{<<"0x08">> => Val} | Acc]); parse_params_(<<16#09, Val:?BYTE, Rest/binary>>, Acc) -> parse_params_(Rest, [#{<<"0x09">> => Val} | Acc]); parse_params_(<<16#0A, Val:?WORD, Rest/binary>>, Acc) -> parse_params_(Rest, [#{<<"0x0A">> => Val} | Acc]); parse_params_(<<16#0B, Val:?WORD, Rest/binary>>, Acc) -> parse_params_(Rest, [#{<<"0x0B">> => Val} | Acc]); parse_params_(<<16#0C, Val:?BYTE, Rest/binary>>, Acc) -> parse_params_(Rest, [#{<<"0x0C">> => Val} | Acc]); parse_params_(<<16#0D, Val:?BYTE, Rest/binary>>, Acc) -> parse_params_(Rest, [#{<<"0x0D">> => Val} | Acc]); parse_params_(<<16#0E, Rest/binary>>, Acc) -> case [V || #{<<"0x0D">> := V} <- Acc] of [Len] -> <> = Rest, parse_params_(Rest1, [#{<<"0x0E">> => Val} | Acc]); _ -> ?SLOG(error, #{ msg => "invalid_data", reason => "cmd_0x0D must appear ahead of cmd_0x0E" }), lists:reverse(Acc) end; parse_params_(<<16#0F, Val:?WORD, Rest/binary>>, Acc) -> parse_params_(Rest, [#{<<"0x0F">> => Val} | Acc]); parse_params_(<<16#10, Val:?BYTE, Rest/binary>>, Acc) -> parse_params_(Rest, [#{<<"0x10">> => Val} | Acc]); parse_params_(Cmd, Acc) -> ?SLOG(error, #{msg => "unexcepted_param_identifier", cmd => Cmd}), lists:reverse(Acc). parse_ctrl_param(16#01, Param) -> parse_upgrade_feild(Param); parse_ctrl_param(16#02, _) -> <<>>; parse_ctrl_param(16#03, _) -> <<>>; parse_ctrl_param(16#04, _) -> <<>>; parse_ctrl_param(16#05, _) -> <<>>; parse_ctrl_param(16#06, <>) -> #{<<"Level">> => Level, <<"Message">> => Msg}; parse_ctrl_param(16#07, _) -> <<>>; parse_ctrl_param(Cmd, Param) -> ?SLOG(error, #{msg => "unexcepted_param", param => Param, cmd => Cmd}), <<>>. parse_upgrade_feild(Param) -> [ DialingName, Username, Password, <<0, 0, I1, I2, I3, I4>>, <>, ManufacturerId, HardwareVer, SoftwareVer, UpgradeUrl, <> ] = re:split(Param, ";", [{return, binary}]), #{ <<"DialingName">> => DialingName, <<"Username">> => Username, <<"Password">> => Password, <<"Ip">> => list_to_binary(inet:ntoa({I1, I2, I3, I4})), <<"Port">> => Port, <<"ManufacturerId">> => ManufacturerId, <<"HardwareVer">> => HardwareVer, <<"SoftwareVer">> => SoftwareVer, <<"UpgradeUrl">> => UpgradeUrl, <<"Timeout">> => Timeout }. %%-------------------------------------------------------------------- %% serialize_pkt %%-------------------------------------------------------------------- serialize_pkt(Frame, _Opts) -> serialize(Frame). serialize(#frame{cmd = Cmd, ack = Ack, vin = Vin, encrypt = Encrypt, data = Data, rawdata = RawData}) -> Encrypted = encipher(serialize_data(Cmd, Ack, RawData, Data), Encrypt), Len = byte_size(Encrypted), Stream = <>, Crc = cal_check(Stream, byte_size(Stream), undefined), <<"##", Stream/binary, Crc:?BYTE>>. serialize_data(?CMD_PARAM_QUERY, ?ACK_IS_CMD, _, #{ <<"Time">> := Time, <<"Total">> := Total, <<"Ids">> := Ids }) when length(Ids) == Total -> T = tune_time(Time), Ids1 = tune_ids(Ids), <>; serialize_data(?CMD_PARAM_SETTING, ?ACK_IS_CMD, _, #{ <<"Time">> := Time, <<"Total">> := Total, <<"Params">> := Params }) when length(Params) == Total -> T = tune_time(Time), Params1 = tune_params(Params), <>; serialize_data(?CMD_TERMINAL_CTRL, ?ACK_IS_CMD, _, #{ <<"Time">> := Time, <<"Command">> := Cmd, <<"Param">> := Param }) -> T = tune_time(Time), Param1 = tune_ctrl_param(Cmd, Param), <>; serialize_data(_Cmd, Ack, RawData, #{<<"Time">> := Time}) when ?IS_RESPONSE(Ack) -> Rest = case byte_size(RawData) > 6 of false -> <<>>; true -> binary:part(RawData, 6, byte_size(RawData) - 6) end, T = tune_time(Time), <>. tune_time(#{ <<"Year">> := Year, <<"Month">> := Month, <<"Day">> := Day, <<"Hour">> := Hour, <<"Minute">> := Min, <<"Second">> := Sec }) -> <>. tune_ids(Ids) -> lists:foldr( fun (Id, Acc) when is_integer(Id) -> <>; (Id, Acc) when is_binary(Id) -> <> end, <<>>, Ids ). tune_params(Params) -> tune_params_(lists:reverse(Params), <<>>). tune_params_([], Bin) -> Bin; tune_params_([#{16#01 := Val} | Rest], Bin) -> tune_params_(Rest, <<16#01:?BYTE, Val:?WORD, Bin/binary>>); tune_params_([#{16#02 := Val} | Rest], Bin) -> tune_params_(Rest, <<16#02:?BYTE, Val:?WORD, Bin/binary>>); tune_params_([#{16#03 := Val} | Rest], Bin) -> tune_params_(Rest, <<16#03:?BYTE, Val:?WORD, Bin/binary>>); tune_params_([#{16#04 := Val} | Rest], Bin) -> {Val05, Rest1} = take_param(16#05, Rest), tune_params_(Rest1, <<16#04:?BYTE, Val:?BYTE, 16#05, Val05:Val/binary, Bin/binary>>); tune_params_([#{16#05 := Val} | Rest], Bin) -> tune_params_(Rest ++ [#{16#05 => Val}], Bin); tune_params_([#{16#06 := Val} | Rest], Bin) -> tune_params_(Rest, <<16#06:?BYTE, Val:?WORD, Bin/binary>>); tune_params_([#{16#07 := Val} | Rest], Bin) when byte_size(Val) == 5 -> tune_params_(Rest, <<16#07:?BYTE, Val/binary, Bin/binary>>); tune_params_([#{16#08 := Val} | Rest], Bin) when byte_size(Val) == 5 -> tune_params_(Rest, <<16#08:?BYTE, Val/binary, Bin/binary>>); tune_params_([#{16#09 := Val} | Rest], Bin) -> tune_params_(Rest, <<16#09:?BYTE, Val:?BYTE, Bin/binary>>); tune_params_([#{16#0A := Val} | Rest], Bin) -> tune_params_(Rest, <<16#0A:?BYTE, Val:?WORD, Bin/binary>>); tune_params_([#{16#0B := Val} | Rest], Bin) -> tune_params_(Rest, <<16#0B:?BYTE, Val:?WORD, Bin/binary>>); tune_params_([#{16#0C := Val} | Rest], Bin) -> tune_params_(Rest, <<16#0C:?BYTE, Val:?BYTE, Bin/binary>>); tune_params_([#{16#0D := Val} | Rest], Bin) -> {Val0E, Rest1} = take_param(16#0E, Rest), tune_params_(Rest1, <<16#0D:?BYTE, Val:?BYTE, 16#0E, Val0E:Val/binary, Bin/binary>>); tune_params_([#{16#0E := Val} | Rest], Bin) -> tune_params_(Rest ++ [#{16#0E => Val}], Bin); tune_params_([#{16#0F := Val} | Rest], Bin) -> tune_params_(Rest, <<16#0F:?BYTE, Val:?WORD, Bin/binary>>); tune_params_([#{16#10 := Val} | Rest], Bin) -> tune_params_(Rest, <<16#10:?BYTE, Val:?BYTE, Bin/binary>>). tune_ctrl_param(16#00, _) -> <<>>; tune_ctrl_param(16#01, Param) -> tune_upgrade_feild(Param); tune_ctrl_param(16#02, _) -> <<>>; tune_ctrl_param(16#03, _) -> <<>>; tune_ctrl_param(16#04, _) -> <<>>; tune_ctrl_param(16#05, _) -> <<>>; tune_ctrl_param(16#06, #{<<"Level">> := Level, <<"Message">> := Msg}) -> <>; tune_ctrl_param(16#07, _) -> <<>>; tune_ctrl_param(Cmd, Param) -> ?SLOG(error, #{msg => "unexcepted_cmd", cmd => Cmd, param => Param}), <<>>. tune_upgrade_feild(Param) -> TuneBin = fun (Bin, Len) when is_binary(Bin), byte_size(Bin) =:= Len -> Bin; (undefined, _) -> undefined; (Bin, _) -> error({invalid_param_length, Bin}) end, TuneWrd = fun (Val) when is_integer(Val), Val < 65535 -> <>; (undefined) -> undefined; (_) -> error(invalid_param_word_value) end, TuneAdr = fun (Ip) when is_binary(Ip) -> {ok, {I1, I2, I3, I4}} = inet:parse_address(binary_to_list(Ip)), <<0, 0, I1, I2, I3, I4>>; (undefined) -> undefined; (_) -> error(invalid_ip_address) end, L = [ maps:get(<<"DialingName">>, Param, undefined), maps:get(<<"Username">>, Param, undefined), maps:get(<<"Password">>, Param, undefined), TuneAdr(maps:get(<<"Ip">>, Param, undefined)), TuneWrd(maps:get(<<"Port">>, Param, undefined)), TuneBin(maps:get(<<"ManufacturerId">>, Param, undefined), 4), TuneBin(maps:get(<<"HardwareVer">>, Param, undefined), 5), TuneBin(maps:get(<<"SoftwareVer">>, Param, undefined), 5), maps:get(<<"UpgradeUrl">>, Param, undefined), TuneWrd(maps:get(<<"Timeout">>, Param, undefined)) ], list_to_binary([I || I <- lists:join(";", L), I /= undefined]). take_param(K, Params) -> V = search_param(K, Params), {V, Params -- [#{K => V}]}. search_param(16#05, [#{16#05 := V} | _]) -> V; search_param(16#0E, [#{16#0E := V} | _]) -> V; search_param(K, [_ | Rest]) -> search_param(K, Rest). cal_check(_, 0, Check) -> Check; cal_check(<>, Size, undefined) -> cal_check(Rest, Size - 1, C); cal_check(<>, Size, Check) -> cal_check(Rest, Size - 1, Check bxor C). format(Msg) -> io_lib:format("~p", [Msg]). type(_) -> %% TODO: gbt32960. is_message(#frame{}) -> %% TODO: true; is_message(_) -> false.