fix parse

This commit is contained in:
anlicheng 2025-06-13 11:27:16 +08:00
parent 737cc9fbab
commit df3e7c4346
4 changed files with 225 additions and 5 deletions

View File

@ -78,4 +78,11 @@
scale = 1.0 :: float(),
unit :: string() | undefined,
poll = true :: boolean()
}).
-record(ast, {
modbus,
devices = [],
processors = [],
alarms = []
}).

View File

@ -19,9 +19,42 @@
%%
parse(Input) when is_binary(Input) ->
Tokens = lexer(Input),
{ok, AST} = parser(Tokens),
try validate(AST) of
{ok, Blocks} = parser(Tokens),
Trees = parse_ast(Blocks),
try validate(Trees) of
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}
catch throw:Reason ->
{error, Reason}
@ -124,11 +157,11 @@ parse_ast0(#block{ident = <<"device", Name0/binary>>, props = Props}) ->
MapProps = parse_ast1(Props),
#modbus_device {
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),
description = maps:get(<<"description">>, 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),
variables = maps:get(<<"variables">>, MapProps, undefined),
controls = maps:get(<<"controls">>, MapProps, undefined)
@ -212,4 +245,18 @@ validate([#modbus_device{slave_id = Id}|_]) when Id < 1 orelse Id > 247 ->
validate([#modbus_device{variables = _Vars}|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.

View 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
%%%===================================================================

View File

@ -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 {
# 输入源
input $boiler_controller.temperature;