fix parse
This commit is contained in:
parent
737cc9fbab
commit
df3e7c4346
@ -79,3 +79,10 @@
|
|||||||
unit :: string() | undefined,
|
unit :: string() | undefined,
|
||||||
poll = true :: boolean()
|
poll = true :: boolean()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
|
-record(ast, {
|
||||||
|
modbus,
|
||||||
|
devices = [],
|
||||||
|
processors = [],
|
||||||
|
alarms = []
|
||||||
|
}).
|
||||||
@ -19,9 +19,42 @@
|
|||||||
%% 主解析函数
|
%% 主解析函数
|
||||||
parse(Input) when is_binary(Input) ->
|
parse(Input) when is_binary(Input) ->
|
||||||
Tokens = lexer(Input),
|
Tokens = lexer(Input),
|
||||||
{ok, AST} = parser(Tokens),
|
{ok, Blocks} = parser(Tokens),
|
||||||
try validate(AST) of
|
Trees = parse_ast(Blocks),
|
||||||
|
try validate(Trees) of
|
||||||
ok ->
|
ok ->
|
||||||
|
%% 分组
|
||||||
|
Modbus = lists:filter(fun(E) ->
|
||||||
|
case E of
|
||||||
|
#modbus{} -> true;
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
|
end, Trees),
|
||||||
|
Devices = lists:filter(fun(E) ->
|
||||||
|
case E of
|
||||||
|
#modbus_device{} ->
|
||||||
|
true;
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
|
end, Trees),
|
||||||
|
Processors = lists:filter(fun(E) ->
|
||||||
|
case E of
|
||||||
|
#modbus_processor{} ->
|
||||||
|
true;
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end, Trees),
|
||||||
|
Alarms = lists:filter(fun(E) ->
|
||||||
|
case E of
|
||||||
|
#modbus_alarm{} ->
|
||||||
|
true;
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end, Trees),
|
||||||
|
|
||||||
|
AST = #ast{modbus = Modbus, devices = Devices, processors = Processors, alarms = Alarms },
|
||||||
{ok, AST}
|
{ok, AST}
|
||||||
catch throw:Reason ->
|
catch throw:Reason ->
|
||||||
{error, Reason}
|
{error, Reason}
|
||||||
@ -124,11 +157,11 @@ parse_ast0(#block{ident = <<"device", Name0/binary>>, props = Props}) ->
|
|||||||
MapProps = parse_ast1(Props),
|
MapProps = parse_ast1(Props),
|
||||||
#modbus_device {
|
#modbus_device {
|
||||||
name = string:trim(Name0),
|
name = string:trim(Name0),
|
||||||
slave_id = maps:get(<<"slave_id">>, MapProps, undefined),
|
slave_id = map_of_integer(<<"slave_id">>, MapProps, 0),
|
||||||
model = maps:get(<<"model">>, MapProps, undefined),
|
model = maps:get(<<"model">>, MapProps, undefined),
|
||||||
description = maps:get(<<"description">>, MapProps, undefined),
|
description = maps:get(<<"description">>, MapProps, undefined),
|
||||||
poll_interval = maps:get(<<"poll_interval">>, MapProps, undefined),
|
poll_interval = maps:get(<<"poll_interval">>, MapProps, undefined),
|
||||||
retries = maps:get(<<"retries">>, MapProps, undefined),
|
retries = map_of_integer(<<"retries">>, MapProps, 0),
|
||||||
retry_timeout = maps:get(<<"retry_timeout">>, MapProps, undefined),
|
retry_timeout = maps:get(<<"retry_timeout">>, MapProps, undefined),
|
||||||
variables = maps:get(<<"variables">>, MapProps, undefined),
|
variables = maps:get(<<"variables">>, MapProps, undefined),
|
||||||
controls = maps:get(<<"controls">>, MapProps, undefined)
|
controls = maps:get(<<"controls">>, MapProps, undefined)
|
||||||
@ -213,3 +246,17 @@ validate([#modbus_device{variables = _Vars}|T]) ->
|
|||||||
validate(T);
|
validate(T);
|
||||||
validate([_|T]) ->
|
validate([_|T]) ->
|
||||||
validate(T).
|
validate(T).
|
||||||
|
|
||||||
|
-spec starts_with(Bin :: binary(), Prefix :: binary()) -> boolean().
|
||||||
|
starts_with(Bin, Prefix) when is_binary(Bin), is_binary(Prefix) ->
|
||||||
|
binary:part(Bin, 0, byte_size(Prefix)) == Prefix.
|
||||||
|
|
||||||
|
map_of_integer(Key, M, Def) ->
|
||||||
|
case maps:is_key(Key, M) of
|
||||||
|
true ->
|
||||||
|
[Val0] = maps:get(Key, M),
|
||||||
|
binary_to_integer(Val0);
|
||||||
|
false ->
|
||||||
|
Def
|
||||||
|
end.
|
||||||
|
|
||||||
|
|||||||
100
apps/modbus/src/modbus_service.erl
Normal file
100
apps/modbus/src/modbus_service.erl
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% @author anlicheng
|
||||||
|
%%% @copyright (C) 2025, <COMPANY>
|
||||||
|
%%% @doc
|
||||||
|
%%%
|
||||||
|
%%% @end
|
||||||
|
%%% Created : 13. 6月 2025 09:46
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
-module(modbus_service).
|
||||||
|
-author("anlicheng").
|
||||||
|
-include("modbus_ast.hrl").
|
||||||
|
|
||||||
|
-behaviour(gen_server).
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([start_link/1]).
|
||||||
|
|
||||||
|
%% 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, {
|
||||||
|
|
||||||
|
}).
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% API
|
||||||
|
%%%===================================================================
|
||||||
|
|
||||||
|
%% @doc Spawns the server and registers the local name (unique)
|
||||||
|
-spec(start_link(AST :: #ast{}) ->
|
||||||
|
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
|
||||||
|
start_link(AST) ->
|
||||||
|
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, #state{}}.
|
||||||
|
|
||||||
|
%% @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(_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(_Request, State = #state{}) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
%% @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
|
||||||
|
%%%===================================================================
|
||||||
66
modbus.conf
66
modbus.conf
@ -86,6 +86,72 @@ device boiler_controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
device xyz {
|
||||||
|
# 设备标识
|
||||||
|
slave_id 1;
|
||||||
|
model "Siemens S7-1200";
|
||||||
|
description "Main boiler controller";
|
||||||
|
|
||||||
|
# 轮询间隔
|
||||||
|
poll_interval 5s;
|
||||||
|
|
||||||
|
# 重试策略
|
||||||
|
retries 3;
|
||||||
|
retry_timeout 2s;
|
||||||
|
|
||||||
|
# 数据点定义
|
||||||
|
variables {
|
||||||
|
# 温度读取(保持寄存器)
|
||||||
|
temperature {
|
||||||
|
address 40001; # Modbus地址表示法
|
||||||
|
type int16;
|
||||||
|
scale 0.1;
|
||||||
|
unit "°C";
|
||||||
|
poll on;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 压力传感器(输入寄存器)
|
||||||
|
pressure {
|
||||||
|
address 30001;
|
||||||
|
type uint16;
|
||||||
|
scale 0.01;
|
||||||
|
unit "kPa";
|
||||||
|
poll on;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 状态位(线圈)
|
||||||
|
alarm_status {
|
||||||
|
address 00001;
|
||||||
|
type bool;
|
||||||
|
bits {
|
||||||
|
0 "overheat";
|
||||||
|
1 "low_pressure";
|
||||||
|
2 "pump_failure";
|
||||||
|
}
|
||||||
|
poll on;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 写入控制
|
||||||
|
controls {
|
||||||
|
# 启停控制
|
||||||
|
power_switch {
|
||||||
|
address 00010;
|
||||||
|
type bool;
|
||||||
|
safe_value off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# PID设定值
|
||||||
|
pid_setpoint {
|
||||||
|
address 40010;
|
||||||
|
type float32;
|
||||||
|
min 0.0;
|
||||||
|
max 100.0;
|
||||||
|
precision 0.1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
processor temperature_processor {
|
processor temperature_processor {
|
||||||
# 输入源
|
# 输入源
|
||||||
input $boiler_controller.temperature;
|
input $boiler_controller.temperature;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user