FUNCTION_BLOCK JSON_READER
VAR CONSTANT
MAX_LEVEL : UINT := 19;
END_VAR
VAR_INPUT
RST : BOOL := TRUE; (* reset *)
WATCHDOG : TIME := t#1ms; (* watchdog time *)
START_POS : UINT := 0; (* first byte in network buffer to be processed *)
STOP_POS : UINT := 0; (* last byte in network buffer to be processed *)
BUF : NW_BUF_LONG; (* network buffer that contains the JSON string as byte stream *)
END_VAR
VAR_OUTPUT
COUNT : UINT := 0; (* number of parsed JSON elements *)
LEVEL : UINT := 0; (* nesting level of current JSON element *)
ARRAY_INDEX : ARRAY[1..MAX_LEVEL] OF INT := MAX_LEVEL(-1); (* current array index value (-1: element is not part of an array) *)
PATH : STRING(STRING_LENGTH); (* full path of current JSON element *)
ELEMENT : STRING(STRING_LENGTH); (* name of current JSON element *)
VALUE : STRING(STRING_LENGTH); (* value of current JSON element *)
NEW_DATA : BOOL := FALSE; (* new data is available (next element has been parsed *)
DONE : BOOL := FALSE; (* last element has been parsed *)
ERROR : BOOL := FALSE; (* error occured, see status for details; reset is required *)
STATUS : BYTE := 0; (* ESR compatible status output ( 0=ok,
1=error: dot not found in path string,
2=error: closing square bracket not found in path string,
3=max. nesting level exceeded,
100=element has been parsed,
101=watchdog timer expired *)
END_VAR
VAR
CurrentByte : UINT; (* index of currently scanned byte in network buffer *)
ObjectEmpty : BOOL := FALSE; (* current object is still empty *)
LookForValue : BOOL := FALSE; (* colon has been found, now look for value *)
FirstByte : UINT := 0; (* first byte of string to be extracted from buffer *)
LastByte : UINT := 0; (* last byte of string to be extracted from buffer *)
ElementParsed : BOOL := FALSE; (* element has been parsed *)
PathOverflow : BOOL := FALSE; (* maximum string length exceeded for path variable *)
StringFound : INT := 0; (* position of string within another string *)
LoopCount : INT := 0; (* loop counter *)
WatchdogTimer : TON; (* watchdog timer *)
_Level : UINT := 0; (* nesting level of current JSON element *)
_Path : STRING(STRING_LENGTH); (* full path of current JSON element *)
_ArrayIndex : ARRAY[1..MAX_LEVEL] OF INT := MAX_LEVEL(-1); (* current array index value (-1: element is not part of an array) *)
_Element : STRING(STRING_LENGTH); (* name of current JSON element *)
_Value : STRING(STRING_LENGTH); (* value of current JSON element *)
pValueString : POINTER TO ARRAY[0..STRING_LENGTH] OF BYTE;
END_VAR
(*
version 1.1 11 oct 2022
programmer md
tested by md
JSON_READER allows to parse a JSON string stored in a byte array (buffer) and extract the JSON elements one after another.
The parsing is monitored by a watchdog timer such that the block does cause a delay of the PLC program of more than the
configured time. The block further automatically interrupts the parsing as soon as an element and its value have been found.
It then provides both at the CTRL structured variable for external processing and continues to search for the next element
when it is called again (next cycle).
Values are always provided as strings, conversion to numeric data types needs to be done by the calling program.
*)
(* reset/initialize *)
IF RST THEN
CurrentByte := START_POS;
ObjectEmpty := FALSE;
LookForValue := FALSE;
FirstByte := 0;
LastByte := 0;
FOR LoopCount := 1 TO MAX_LEVEL DO
_ArrayIndex[LoopCount] := -1;
END_FOR
ElementParsed := FALSE;
PathOverflow := FALSE;
_Level := 0;
_Element := '';
_Path := '';
_Value := '';
COUNT := 0;
LEVEL := 0;
ELEMENT := '';
PATH := '';
VALUE := '';
NEW_DATA := FALSE;
DONE := FALSE;
ERROR := FALSE;
STATUS := 0;
END_IF;
(* reset watchdog timer *)
WatchdogTimer(IN:=FALSE , PT:= WATCHDOG, Q=> , ET=> );
(* reset new data flag *)
NEW_DATA := FALSE;
(* loop through buffer until buffer end reached OR element has been parsed OR watchdog timer has expired *)
WHILE (CurrentByte <= STOP_POS AND NOT ElementParsed AND NOT WatchdogTimer.Q AND NOT ERROR AND NOT RST AND NOT DONE) DO
WatchdogTimer(IN:=TRUE , PT:= WATCHDOG, Q=> , ET=> ); (* call watchdog timer *)
CASE BUF[CurrentByte] OF (* evaluate current character *)
0: (* end of string *)
DONE := TRUE;
123: (* '{' object start *)
IF _Level < MAX_LEVEL THEN
_Level := _Level + 1;
ObjectEmpty := TRUE;
LookForValue := FALSE;
FirstByte := 0;
_Element := '';
_Value := '';
ELSE
ERROR := TRUE;
STATUS := 3;
END_IF
125: (* '}' object end *)
IF LookForValue AND _Value <> '' THEN (* element has been parsed *)
ElementParsed := TRUE;
EXIT; (* exit loop immediately in order to first copy the element details to the output parameters *)
END_IF
IF _Level > 1 AND NOT PathOverflow THEN
_Element := '';
_Value := '';
_Level := _Level - 1; (* up one level *)
LookForValue := FALSE; (* next string must be a key, not a value *)
FirstByte := 0;
IF NOT ObjectEmpty THEN (* don't change path if current object is still empty, i. e. nothing has been appended to the path before *)
StringFound := FINDB(_Path, '.');
IF StringFound > 1 THEN
_Path := LEFT(_Path, StringFound - 1); (* remove last element from path string *)
ELSE (* error: dot has not been found *)
ERROR := TRUE;
STATUS := 1;
END_IF
END_IF
ObjectEmpty := FALSE;
END_IF
91: (* '[' array start *)
IF LEN(_Path) + 3 < STRING_LENGTH AND NOT PathOverflow THEN
_Path := CONCAT(_Path, '[0]'); (* append array index to path *)
ELSE
_Path := 'OVERFLOW';
PathOverflow := TRUE;
END_IF
_Element := CONCAT(_Element, '[0]'); (* append array index to path *)
_ArrayIndex[_Level] := 0; (* array index *)
93: (* ']' array end *)
IF LookForValue AND _Value <> '' THEN (* element has been parsed *)
ElementParsed := TRUE;
EXIT; (* exit loop immediately in order to first copy the element details to the output parameters *)
END_IF
IF NOT PathOverflow THEN
StringFound := FINDB(_Path, '[');
IF StringFound > 1 THEN (* remove array index from path string *)
_Path := LEFT(_Path, StringFound - 1);
_Element := LEFT(_Element, FINDB(_Path, '[') - 1);
_Value := '';
_ArrayIndex[_Level] := -1; (* reset array index *)
ELSE (* error: opening square bracket has not been found *)
ERROR := TRUE;
STATUS := 2;
END_IF
END_IF
58: (* ':' key delimiter *)
LookForValue := TRUE;
44: (* ',' element delimiter *)
IF LookForValue AND _Value <> '' THEN (* element has been parsed *)
ElementParsed := TRUE;
EXIT; (* exit loop immediately in order to first copy the element details to the output parameters *)
END_IF
IF RIGHT(_Path, 1) = ']' THEN (* previous element is part of an array *)
IF LEN(LEFT(_Path, FINDB(_Path, '['))) + LEN(INT_TO_STRING(_ArrayIndex[_Level] + 1)) + 1 <= STRING_LENGTH THEN
_ArrayIndex[_Level] := _ArrayIndex[_Level] + 1; (* increment array index *)
_Path := LEFT(_Path, FINDB(_Path, '['));
_Path := CONCAT(_Path, INT_TO_STRING(_ArrayIndex[_Level]));
_Path := CONCAT(_Path, ']');
ELSE
_Path := 'OVERFLOW';
PathOverflow := TRUE;
END_IF
ELSE (* previous element is not part of an array *)
StringFound := FINDB(_Path, '.');
IF StringFound > 1 THEN (* remove last element from path string *)
_Path := LEFT(_Path, StringFound - 1);
ELSE (* previous element was root, empty path variable *)
_Path := '';
END_IF
_Element := '';
_Value := '';
END_IF
34: (* '"' quotation mark *)
IF FirstByte = 0 THEN
FirstByte := CurrentByte + 1;
ELSE
IF NOT LookForValue THEN (* key has been parsed *)
ObjectEmpty := FALSE;
_Element := BUFFER_TO_STRING(PT:=ADR(BUF),SIZE:=STOP_POS + 1,START:=FirstByte,STOP:=CurrentByte - 1);
IF _Path = '' THEN (* path empty, use element as root for path *)
_Path := _Element;
ELSE (* append key to path *)
IF LEN(_Path) + LEN(_Element) + 1 <= STRING_LENGTH THEN
_Path := CONCAT(_Path, '.');
_Path := CONCAT(_Path, _Element);
ELSE
_Path := 'OVERFLOW';
PathOverflow := TRUE;
END_IF
END_IF
ELSE (* value has been parsed *)
_Value := BUFFER_TO_STRING(PT:=ADR(BUF),SIZE:=STOP_POS + 1,START:=FirstByte,STOP:=CurrentByte - 1);
ElementParsed := TRUE;
END_IF
FirstByte := 0;
END_IF
45, 48..57, 46, 110, 117, 108: (* minus, numbers and dot + characters for "null" (not as string but as null value *)
IF LookForValue AND FirstByte = 0 THEN
pValueString := ADR(_Value);
pValueString^[LEN(_Value) + 1] := 0;
pValueString^[LEN(_Value)] := BUF[CurrentByte];
END_IF
END_CASE
CurrentByte := CurrentByte + 1; (* proceed to next byte *)
END_WHILE
IF ElementParsed THEN
ElementParsed := FALSE;
LookForValue := FALSE;
COUNT := COUNT + 1; (* copy output values to interface *)
LEVEL := _Level;
PATH := _Path;
ARRAY_INDEX := _ArrayIndex;
ELEMENT := _Element;
VALUE := _Value;
NEW_DATA := TRUE;
STATUS := 100;
END_IF
IF CurrentByte > STOP_POS THEN (* end of string to be parsed reached *)
DONE := TRUE;
ELSIF WatchdogTimer.Q THEN
STATUS := 101;
END_IF
(* revision history
md 10 Jul 2019 rev 1.0
original version
md 11 Oct 2022 rev 1.1
fixed negative values not being recognized
fixed empty objects breaking path handling
*)
Seite erstellt in 0.028 Sekunden mit 15 Abfragen.