diff --git a/apps/modbus/src/modbus_service.erl b/apps/modbus/src/modbus_service.erl index a1dbad5..abab248 100644 --- a/apps/modbus/src/modbus_service.erl +++ b/apps/modbus/src/modbus_service.erl @@ -14,7 +14,7 @@ %% API -export([start_link/1]). --export([test/0]). +-export([test/0, frame_delay/4]). %% gen_statem callbacks -export([init/1, handle_event/4, terminate/3, code_change/4, callback_mode/0]). @@ -29,6 +29,15 @@ -record(state, { port, + packet_id = 1, + queue = queue:new(), + is_pending = false :: boolean(), + %% 延迟的毫秒数 + delay_ms = 0, + + %% 未确认的消息 + inflight = #{}, + access_log_pid :: pid() | undefined, error_log_pid :: pid() | undefined }). @@ -67,10 +76,11 @@ init([AST = #ast{modbus = Modbus, devices = Devices}]) -> {ok, DevicePid} = modbus_device:start_link(hd(Devices)), lager:debug("device pid: ~p", [DevicePid]), + %{ok, AccessLogPid} = modbus_logger:start_link(AccessLog), %{ok, ErrorLogPid} = modbus_logger:start_link(ErrorLog), - {ok, ?DISCONNECTED, #state{}}. + {ok, ?DISCONNECTED, #state{queue = queue:new(), packet_id = 1}}. %% @private %% @doc This function is called by a gen_statem when it needs to find out @@ -83,15 +93,40 @@ callback_mode() -> %% gen_statem receives an event from call/2, cast/2, or as a normal %% process message, this function is called. -handle_event(info, {read, SlaveId, Address}, ?CONNECTED, State = #state{port = Port}) -> - ReadCmd = <>, - Port ! {self(), {command, ReadCmd}}, - {keep_state, State}; +handle_event(info, {read, SlaveId, Address}, ?CONNECTED, State = #state{packet_id = PacketId, queue = Q, is_pending = IsPending}) -> + %% 基于队列处理, modbus不是全双工的模式 + NQ = queue:in({PacketId, SlaveId, Address}, Q), + Actions = case IsPending of + true -> [{next_event, info, read_next}]; + false -> [] + end, + {keep_state, State#state{queue = NQ, packet_id = PacketId + 1, is_pending = false}, Actions}; -%% 从port读取数据 -handle_event(info, {Port, {data, Data}}, ?CONNECTED, State = #state{port = Port}) -> - lager:debug("port data is: ~p", [Data]), - {keep_state, State}; +%% 读取下一个任务 +handle_event(info, read_next, ?CONNECTED, State = #state{queue = Q, port = Port}) -> + case queue:out(Q) of + {{value, {PacketId, SlaveId, Address}}, Q2} -> + ReadCmd = <>, + Port ! {self(), {command, ReadCmd}}, + {keep_state, State#state{queue = Q2}}; + {empty, Q1} -> + {keep_state, State#state{queue = Q1}} + end; + +%% 从port读取数据, todo 获取到的数据表示为32位整数 +handle_event(info, {Port, {data, <>}}, ?CONNECTED, State = #state{port = Port, delay_ms = DelayMs, inflight = Inflight}) -> + NInflight = case maps:take(PacketId, Inflight) of + error -> + Inflight; + {{ReceiverPid, Ref}, NMap} -> + erlang:is_process_alive(ReceiverPid) andalso ReceiverPid ! {request_reply, Ref, Val}, + NMap + end, + lager:debug("port data is: ~p", [{PacketId, Val}]), + %% 读取下一条 + erlang:start_timer(DelayMs, self(), read_next), + + {keep_state, State#state{inflight = NInflight}}; handle_event(_EventType, _EventContent, _StateName, State = #state{}) -> NextStateName = the_next_state_name, @@ -114,12 +149,15 @@ code_change(_OldVsn, StateName, State = #state{}, _Extra) -> %%% Internal functions %%%=================================================================== -connect(#modbus{transport = #modbus_transport_rtu{port = Port, baudrate = Baudrate, stopbits = Stopbits, parity = Parity, timeout = Timeout}}) -> +%% databits为8位 +connect(#modbus{transport = #modbus_transport_rtu{port = Port, baudrate = BaudRate, stopbits = StopBits, parity = Parity, timeout = Timeout}}) -> RealExecCmd = "", Port = erlang:open_port({spawn_executable, RealExecCmd}, [binary, {packet, 2}, exit_status]), + DelayMs = frame_delay(BaudRate, 8, Parity, StopBits), + Len0 = byte_size(Port), - ConnectCmd = <>, + ConnectCmd = <>, %% 建立连接 Port ! {self(), {command, ConnectCmd}}, ok; @@ -131,4 +169,11 @@ connect(#modbus{transport = #modbus_transport_tcp{host = Host, port = Port, time false -> 2000 end, - gen_tcp:connect(Host, Port, [binary], Timeout). \ No newline at end of file + gen_tcp:connect(Host, Port, [binary], Timeout). + +%% 计算3.5个字符的静默时间 +frame_delay(BaudRate, DataBits, Parity, StopBits) when is_integer(BaudRate), is_integer(DataBits), is_integer(Parity), is_integer(StopBits) -> + %% 计算1个字符的总位数 + CharBits = 1 + DataBits + case Parity of 0 -> 0; _ -> 1 end + StopBits, + %% 3.5字符时间(毫秒), 预留20%的时间 + erlang:ceil((3500 * CharBits) / BaudRate * 1.2). \ No newline at end of file