Skip to content

JSON generator and parser for NX/NJ in Sysmac Studio

License

Notifications You must be signed in to change notification settings

kmu2030/JSONLib

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

JSONLib (JSON generator and parser fo NX/NJ)

JSONLib is a JSON generator/parser for OMRON's NX/NJ controllers. The JSON parser focuses on parsing JSON structures and only inspects values ​​as necessary for parsing. This is because the target is a PLC (NX/NJ) and its processing capacity is limited. The JSON parser is tested using test data from JSONTestSuite to confirm quality. For other information, please check this article (Japanese).

The numeric value is the value most needed by the PLC, so it is inspected during parsing. Also, even if the value is in a range that cannot be handled by the basic type, a value that is valid in format will pass the check. Because you can use it if you have your own numerical library. On the other hand, checking on string values ​​is minimal since it is unlikely that you will be building an application that primarily processes strings.

FUN, which retrieves string values, does not unescape escaped ASCII and Unicode escape sequences. Inspection of string values ​​is delegated to the user. The JSON parser does not convert bytes to strings for values ​​of either type without prompting from the user. This is intended to reduce risks associated with processing UTF-8 byte strings and Unicode escape sequences, and to avoid processing that is unnecessary for the target system's primary use.

For keys, convert them to string type when parsing for JSON path search. However, unescape is not performed. Therefore, if a key contains an escape character, it will not be equivalent to its unescaped value.

JSON generator

The JSON generator consists of a series of output FUNs. For any output FUN, by specifying an empty string for Key, only the value will be output. When generating key-value pairs for an object, be sure to specify a value that is not an empty string for Key. The Key string is not escaped, so you need to escape it first.

The JSON generator has the following limitations:

  • JSON size is limited to 65534 bytes
  • Collection nesting is limited to 16 levels

The following will output JSON where the root is an array.

iNow :=GetTime();
JSONContext_init(iJson);
JSON_ARRAY(Context:=iJson);
FOR i := 0 TO 2 DO
    JSON_OBJECT(Context:=iJson);
        JSON_STRING(Context:=iJson, Key:='product_id', Value:=UINT_TO_STRING(i));
        JSON_UINT(Context:=iJson, Key:='amount', Value:=(i + 1) * 100);
        // Since DT output is expensive (more than 50 us),
        // epoch seconds is preferable if possible.
        JSON_DATE_AND_TIME(Context:=iJson, Key:='timestamp', Value:=iNow, Timezone:='+09:00');
    JSON_OBJECT_CLOSE(Context:=iJson);
END_FOR;
JSON_ARRAY_CLOSE(iJson);
// [{"product_id":0,"amount":100,"timestamp":"YYYY-MM-DDThh:mm:ss+09:00"},...]
JSONContext_toString(iJson, iJsonStr);

JSON parser

The JSON parser parses a UTF-8 byte string, which is a JSON string, as JSON.

The JSON parser has the following characteristics:

  • Do not un-escape escaped ASCII and Unicode escape sequences for either keys or values.
  • Numbers are allowed outside the range of the basic type as long as there are no errors in the format (parsing succeeds)
  • Value evaluation is done when executing FUN, which is obtained as an arbitrary type.

The JSON parser has the following restrictions:

  • JSON size is limited to 65534 bytes
  • Up to 4096 elements (values ​​or value-key pairs)
  • The maximum size of the path that a key or index consists of is 63 characters.
  • Collection nesting is limited to 16 levels

The following parses the JSON data that is an object and retrieves the value.

iJsonStr := '{"command":10, "payload":{"product_id":"xxxxx-xxx", "amount":500, "start_at":"2025-02-25T13:00:00+09:00"}}';
iJsonBinSize := StringToAry(In:=iJsonStr, AryOut:=iJsonBin[0]);
JSONContext_init(iJson);
// JSONParser finishes parsing in one cycle.
// Therefore, large JSON sizes may put pressure on task time.
iParser(Execute:=TRUE,
        Context:=iJson,
        Data:=iJsonBin,
        // Specify the starting position of Data.
        Head:=0,
        // Specify the data size.
        Size:=iJsonBinSize);

// Getter FUN returns TRUE if the key exists and the value type is valid for the type of get FUN.
// Also, the default value can be specified for the FUN.
// The default value is to output Value when the key does not exist or is null.
JSON_TO_STRING(Context:=iJson,
               Key:='payload.product_id',
               Value=>iProductId,
               Default:='');
JSON_TO_UINT(Context:=iJson,
             Key:='payload.amount',
             Value=>iAmount,
             Default:=0);
JSON_TO_DATE_AND_TIME(Context:=iJson,
                      Key:='payload.start_at',
                      Value=>iStartAt,
                      Default:=DT#1970-01-01-00:00:00);	

Collection operations with iterators

You can use iterators as collection operations. The JsonElement you get in the iteration holds the value as STRING type, so it has the side effect of converting the value to STRING type. In the future, I will standardize iterations that do not convert values ​​to STRING types.

iJsonStr := '{"command":10, "payload":{"product_id":"xxxxx-xxx", "amount":500, "start_at":"2025-02-25T13:00:00+09:00"}}';
iJsonBinSize := StringToAry(In:=iJsonStr, AryOut:=iJsonBin[0]);
JSONContext_init(iJson);
iParser(Execute:=TRUE,
        Context:=iJson,
        Data:=iJsonBin,
        Head:=0,
        Size:=iJsonBinSize);

IF JSON_TO_UINT(Context:=iJson,
                Key:='command',
                Value=>iCommand,
                Default:=1000)
THEN
    CASE iCommand OF
        // Schedule production.
        10:
            IF JSON_ITERATOR(Context:=iJson, Iterator:=iIterator, Key:='payload') THEN
                iOk := TRUE;
                Clear(iProductId); Clear(iAmount); Clear(iStartAt);
                WHILE JSON_ITERATOR_NEXT(Context:=iJson,
                                         Iterator:=iIterator,
                                         Element:=iElement)
                DO
                    IF iElement.Key = 'product_id' THEN
                        iProductId := iElement.Value;
                    ELSIF iElement.Key = 'amount' THEN
                        iAmount := STRING_TO_UINT(iElement.Value);
                    ELSIF iElement.Key = 'start_at' THEN
                        // DT utility is not yet implemented.
                        JSON_TO_DATE_AND_TIME(Context:=iJson,
                                              Key:=iElement.Path,
                                              Value=>iStartAt);
                    ELSE
                        iOk := FALSE;
                        EXIT;
                    END_IF;
                END_WHILE;
                // Validate parameters.
                // ...
                // Do schedule procedure.
                // ...
            ELSE
                // Invalid payload.
                iError := TRUE;
            END_IF;
    ELSE
        // Invalid command.
        iError := TRUE;
    END_CASE;
ELSE
    // Unknown json.
    iError := TRUE;
END_IF;

Environment

Usage environment

The following environment is required to use this project

Controller NX1 or NX5
Sysmac Studio I always recommend the latest version.

Built environment

This project was built in the following environment.

Controller NX102-9000 Ver 1.64
Sysmac Studio Ver.1.61

JSONLib.slr

JSONLib.slr is the library itself. By referencing this library in your project you will be able to use the JSON generator/parser. Please check JSONLibExmaple.smc2 for usage. By checking the unit test of JSONLibExample.smc2, you can get a rough idea of ​​how each POU is used. The documentation is now ready.

JSONLibExample.smc2

SONLibExample.smc2 contains the following POUs. Also, JSONLib.slr and STUnit.slr are included, so there is no need to refer to the library.

  • POU/Program/Test_UnitTest
    Unit tests for JSONLib and tests for JSONTestSuite. Run it in the simulator.

  • POU/Program/Main
    This is an example of using JSONLib. You can run it on a simulator, but I recommend running it on a real machine.

Test execution steps

Run the test using the following steps.

  1. Preparing test data
    Copy the directories under the sd directory of this repository to the virtual SD card folder (C:\OMRON\Data\SimulatorData\CARD\Memory001).

  2. Run Sysmac simulator
    Open the project in Sysmac Studio, run the simulator, and wait for the test to complete. The test will take approximately 5 minutes. The test is complete when Done of Test_JSONTestSuite becomes True and the test output to the file is finished (creating or updating JSONTestSuite_Test.txt).

  3. Check the test output
    Once the test is complete, check the test output in the virtual SD card folder. Since there are many output files, grep the file containing "❌ Test suite". If there are no corresponding files, all tests have passed. You can also create a simple script to monitor folders.

Example execution steps

Run the example using the following steps.

  1. Change Sysmac project
    Change the controller model of the Sysmac project to the controller model you plan to use.

  2. Transfer Sysmac project to controller
    Transfer the Sysmac project to the controller, put the controller in operation mode, and run the program.

  3. Check in watch window
    Connect to the controller in Sysmac Studio and check the output in the watch window. You can switch between use cases by changing the iState value.

Testing with JSONTestSuite test data

The JSON parser has been tested with its own test data and JSONTestSuite test data. A series of tests are included in JSONLibExample.smc2, so you can run the tests and check the results by preparing test data.The test results using JSONTestSuite test data are below.

🟩 Returns the expected result (n_* is failure, y_* is success)
🟦 Succeed in parsing
🟪 Parsing fails
⬛ Parsing result is indeterminate
🟥 Task hangs

test json result note
i_number_double_huge_neg_exp 🟦 Because it can be used if you have your own numerical library.
i_number_huge_exp 🟦 Because it can be used if you have your own numerical library.
i_number_neg_int_huge_exp 🟦 Because it can be used if you have your own numerical library.
i_number_pos_double_huge_exp 🟦 Because it can be used if you have your own numerical library.
i_number_real_neg_overflow 🟦 Because it can be used if you have your own numerical library.
i_number_real_pos_overflow 🟦 Because it can be used if you have your own numerical library.
i_number_real_underflow 🟦 Because it can be used if you have your own numerical library.
i_number_too_big_neg_int 🟦 Because it can be used if you have your own numerical library.
i_number_too_big_pos_int 🟦 Because it can be used if you have your own numerical library.
i_number_very_big_negative_int 🟦 Because it can be used if you have your own numerical library.
i_object_key_lone_2nd_surrogate 🟦 To delegate strict validation of strings to the user.
i_string_1st_surrogate_but_2nd_missing 🟦 To delegate strict validation of strings to the user.
i_string_1st_valid_surrogate_2nd_invalid 🟦 To delegate strict validation of strings to the user.
i_string_UTF-16LE_with_BOM 🟪 UTF-16 is not supported.
i_string_UTF-8_invalid_sequence 🟦 To delegate strict validation of strings to the user.
i_string_UTF8_surrogate_U+D800 🟦 To delegate strict validation of strings to the user.
i_string_incomplete_surrogate_and_escape_valid 🟦 To delegate strict validation of strings to the user.
i_string_incomplete_surrogate_pair 🟦 To delegate strict validation of strings to the user.
i_string_incomplete_surrogates_escape_valid 🟦 To delegate strict validation of strings to the user.
i_string_invalid_lonely_surrogate 🟦 To delegate strict validation of strings to the user.
i_string_invalid_surrogate 🟦 To delegate strict validation of strings to the user.
i_string_invalid_utf-8 🟦 To delegate strict validation of strings to the user.
i_string_inverted_surrogates_U+1D11E 🟦 To delegate strict validation of strings to the user.
i_string_iso_latin_1 🟪 Latin-1 is not supported.
i_string_lone_second_surrogate 🟦 To delegate strict validation of strings to the user.
i_string_lone_utf8_continuation_byte 🟪 Unable to read JSON structure.
i_string_not_in_unicode_range 🟦 To delegate strict validation of strings to the user.
i_string_overlong_sequence_2_bytes Success or failure depends on the value.
i_string_overlong_sequence_6_bytes Success or failure depends on the value.
i_string_overlong_sequence_6_bytes_null Success or failure depends on the value.
i_string_truncated-utf-8 🟪 Unable to read JSON structure.
i_string_utf16BE_no_BOM 🟪 UTF-16 is not supported.
i_string_utf16LE_no_BOM 🟪 UTF-16 is not supported.
i_structure_500_nested_arrays 🟪 The maximum nesting level is 16 levels.
i_structure_UTF-8_BOM_empty_object 🟪 UTF-16 is not supported.
n_array_1_true_without_comma 🟩
n_array_a_invalid_utf8 🟩
n_array_colon_instead_of_comma 🟩
n_array_comma_after_close 🟩
n_array_comma_and_number 🟩
n_array_double_comma 🟩
n_array_double_extra_comma 🟩
n_array_extra_close 🟩
n_array_extra_comma 🟩
n_array_incomplete 🟩
n_array_incomplete_invalid_value 🟩
n_array_inner_array_no_comma 🟩
n_array_invalid_utf8 🟩
n_array_items_separated_by_semicolon 🟩
n_array_just_comma 🟩
n_array_just_minus 🟩
n_array_missing_value 🟩
n_array_newlines_unclosed 🟩
n_array_number_and_comma 🟩
n_array_number_and_several_commas 🟩
n_array_spaces_vertical_tab_formfeed 🟩
n_array_star_inside 🟩
n_array_unclosed 🟩
n_array_unclosed_trailing_comma 🟩
n_array_unclosed_with_new_lines 🟩
n_array_unclosed_with_object_inside 🟩
n_incomplete_false 🟩 Ensure that only "false" is accepted.
n_incomplete_null 🟩 Ensure that only "null" is accepted.。
n_incomplete_true 🟩 Ensure that only "true" is accepted.
n_multidigit_number_then_00 🟩
n_number_++ 🟩
n_number_+1 🟩
n_number_+Inf 🟩
n_number_-01 🟩
n_number_-1.0. 🟩
n_number_-2. 🟩
n_number_-NaN 🟩
n_number_.-1 🟩
n_number_.2e-3 🟩
n_number_0.1.2 🟩
n_number_0.3e+ 🟩
n_number_0.3e 🟩
n_number_0.e1 🟩
n_number_0_capital_E+ 🟩
n_number_0_capital_E 🟩
n_number_0e+ 🟩
n_number_0e 🟩
n_number_1.0e+ 🟩
n_number_1.0e- 🟩
n_number_1.0e 🟩
n_number_1_000 🟩
n_number_1eE2 🟩
n_number_2.e+3 🟩
n_number_2.e-3 🟩
n_number_2.e3 🟩
n_number_9.e+ 🟩
n_number_Inf 🟩
n_number_NaN 🟩
n_number_U+FF11_fullwidth_dig 🟩
n_number_expression 🟩
n_number_hex_1_digit 🟩
n_number_hex_2_digits 🟩
n_number_infinity 🟩
n_number_invalid+- 🟩
n_number_invalid-negative-real 🟩
n_number_invalid-utf-8-in-bigger-int 🟩
n_number_invalid-utf-8-in-exponent 🟩
n_number_invalid-utf-8-in-int 🟩
n_number_minus_infinity 🟩
n_number_minus_sign_with_trailing_garbag 🟩
n_number_minus_space_1 🟩
n_number_neg_int_starting_with_zero 🟩
n_number_neg_real_without_int_part 🟩
n_number_neg_with_garbage_at_end 🟩
n_number_real_garbage_after_e 🟩
n_number_real_with_invalid_utf8_after_e 🟩
n_number_real_without_fractional_part 🟩
n_number_starting_with_dot 🟩
n_number_with_alpha 🟩
n_number_with_alpha_char 🟩
n_number_with_leading_zero 🟩
n_object_bad_value 🟩
n_object_bracket_key 🟩
n_object_comma_instead_of_colon 🟩
n_object_double_colon 🟩
n_object_emoji 🟩
n_object_garbage_at_end 🟩
n_object_key_with_single_quotes 🟩
n_object_lone_continuation_byte_in_key_and_trailing_comma 🟩
n_object_missing_colon 🟩
n_object_missing_key 🟩
n_object_missing_semicolon 🟩
n_object_missing_value 🟩
n_object_no-colon 🟩
n_object_non_string_key 🟩
n_object_non_string_key_but_huge_number_instead 🟩
n_object_repeated_null_null 🟩
n_object_several_trailing_commas 🟩
n_object_single_quote 🟩
n_object_trailing_comma 🟩
n_object_trailing_comment 🟩
n_object_trailing_comment_open 🟩
n_object_trailing_comment_slash_open 🟩
n_object_trailing_comment_slash_open_incomplete 🟩
n_object_two_commas_in_a_row 🟩
n_object_unquoted_key 🟩
n_object_unterminated-value 🟩
n_object_with_single_string 🟩
n_object_with_trailing_garbage 🟩
n_single_space 🟩
n_string_1_surrogate_then_escape 🟩
n_string_1_surrogate_then_escape_u 🟩
n_string_1_surrogate_then_escape_u1 🟩
n_string_1_surrogate_then_escape_u1x 🟩
n_string_accentuated_char_no_quotes 🟩
n_string_backslash_00 🟩
n_string_escape_x 🟩
n_string_escaped_backslash_bad 🟩
n_string_escaped_ctrl_char_tab 🟩
n_string_escaped_emoji 🟩
n_string_incomplete_escape 🟩
n_string_incomplete_escaped_character 🟩
n_string_incomplete_surrogate 🟩
n_string_incomplete_surrogate_escape_invalid 🟩
n_string_invalid-utf-8-in-escape 🟩
n_string_invalid_backslash_esc 🟩
n_string_invalid_unicode_escape 🟩
n_string_invalid_utf8_after_escape 🟩
n_string_leading_uescaped_thinspace 🟩
n_string_no_quotes_with_bad_escape 🟩
n_string_single_doublequote 🟩
n_string_single_quote 🟩
n_string_single_string_no_double_quotes 🟩
n_string_start_escape_unclosed 🟩
n_string_unescaped_ctrl_char 🟩
n_string_unescaped_newline 🟩
n_string_unescaped_tab 🟩
n_string_unicode_CapitalU 🟩
n_string_with_trailing_garbage 🟩
n_structure_100000_opening_arrays 🟩 The maximum nesting level is 16 levels.
n_structure_U+2060_word_joined 🟩
n_structure_UTF8_BOM_no_data 🟩 UTF-8 with BOM is not supported.
n_structure_angle_bracket_. 🟩
n_structure_angle_bracket_null 🟩
n_structure_array_trailing_garbage 🟩
n_structure_array_with_extra_array_close 🟩
n_structure_array_with_unclosed_string 🟩
n_structure_ascii-unicode-identifier 🟩
n_structure_capitalized_True 🟩
n_structure_close_unopened_array 🟩
n_structure_comma_instead_of_closing_brace 🟩
n_structure_double_array 🟩
n_structure_end_array 🟩
n_structure_incomplete_UTF8_BOM 🟩
n_structure_lone-invalid-utf-8 🟩
n_structure_lone-open-bracket 🟩
n_structure_no_data 🟩
n_structure_null-byte-outside-string 🟩
n_structure_number_with_trailing_garbage 🟩
n_structure_object_followed_by_closing_object 🟩
n_structure_object_unclosed_no_value 🟩
n_structure_object_with_comment 🟩
n_structure_object_with_trailing_garbage 🟩
n_structure_open_array_apostrophe 🟩
n_structure_open_array_comma 🟩
n_structure_open_array_object 🟩
n_structure_open_array_open_object 🟩
n_structure_open_array_open_string 🟩
n_structure_open_array_string 🟩
n_structure_open_object 🟩
n_structure_open_object_close_array 🟩
n_structure_open_object_comma 🟩
n_structure_open_object_open_array 🟩
n_structure_open_object_open_string 🟩
n_structure_open_object_string_with_apostrophes 🟩
n_structure_open_open 🟩
n_structure_single_eacute 🟩
n_structure_single_star 🟩
n_structure_trailing_# 🟩
n_structure_uescaped_LF_before_string 🟩
n_structure_unclosed_array 🟩
n_structure_unclosed_array_partial_null 🟩
n_structure_unclosed_array_unfinished_false 🟩
n_structure_unclosed_array_unfinished_true 🟩
n_structure_unclosed_object 🟩
n_structure_unicode-identifier 🟩
n_structure_whitespace_U+2060_word_joiner 🟩
n_structure_whitespace_formfeed 🟩
y_array_arraysWithSpaces 🟩
y_array_empty-string 🟩
y_array_empty 🟩
y_array_ending_with_newline 🟩
y_array_false 🟩
y_array_heterogeneous 🟩
y_array_null 🟩
y_array_with_1_and_newline 🟩
y_array_with_leading_space 🟩
y_array_with_several_null 🟩
y_array_with_trailing_space 🟩
y_number 🟩
y_number_0e+1 🟩
y_number_0e1 🟩
y_number_after_space 🟩
y_number_double_close_to_zero 🟩
y_number_int_with_exp 🟩
y_number_minus_zero 🟩
y_number_negative_int 🟩
y_number_negative_one 🟩
y_number_negative_zero 🟩
y_number_real_capital_e 🟩
y_number_real_capital_e_neg_exp 🟩
y_number_real_capital_e_pos_exp 🟩
y_number_real_exponent 🟩
y_number_real_fraction_exponent 🟩
y_number_real_neg_exp 🟩
y_number_real_pos_exponent 🟩
y_number_simple_int 🟩
y_number_simple_real 🟩
y_object 🟩
y_object_basic 🟩
y_object_duplicated_key 🟩
y_object_duplicated_key_and_value 🟩
y_object_empty 🟩
y_object_empty_key 🟩
y_object_escaped_null_in_key 🟩
y_object_extreme_numbers 🟩
y_object_long_strings 🟩
y_object_simple 🟩
y_object_string_unicode 🟩
y_object_with_newlines 🟩
y_string_1_2_3_bytes_UTF-8_sequences 🟩
y_string_accepted_surrogate_pair 🟩 Just check the number of bytes.
y_string_accepted_surrogate_pairs 🟩 Just check the number of bytes.
y_string_allowed_escapes 🟩
y_string_backslash_and_u_escaped_zero 🟩
y_string_backslash_doublequotes 🟩
y_string_comments 🟩
y_string_double_escape_a 🟩
y_string_double_escape_n 🟩
y_string_escaped_control_character 🟩
y_string_escaped_noncharacter 🟩
y_string_in_array 🟩
y_string_in_array_with_leading_space 🟩
y_string_last_surrogates_1_and_2 🟩
y_string_nbsp_uescaped 🟩
y_string_nonCharacterInUTF-8_U+10FFFF 🟩
y_string_nonCharacterInUTF-8_U+FFFF 🟩
y_string_null_escape 🟩
y_string_one-byte-utf-8 🟩
y_string_pi 🟩
y_string_reservedCharacterInUTF-8_U+1BFFF 🟩
y_string_simple_ascii 🟩
y_string_space 🟩
y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF 🟩
y_string_three-byte-utf-8 🟩
y_string_two-byte-utf-8 🟩
y_string_u+2028_line_sep 🟩
y_string_u+2029_par_sep 🟩
y_string_uEscape 🟩
y_string_uescaped_newline 🟩
y_string_unescaped_char_delete 🟩
y_string_unicode 🟩
y_string_unicodeEscapedBackslash 🟩
y_string_unicode_2 🟩
y_string_unicode_U+10FFFE_nonchar 🟩
y_string_unicode_U+1FFFE_nonchar 🟩
y_string_unicode_U+200B_ZERO_WIDTH_SPACE 🟩
y_string_unicode_U+2064_invisible_plus 🟩
y_string_unicode_U+FDD0_nonchar 🟩
y_string_unicode_U+FFFE_nonchar 🟩
y_string_unicode_escaped_double_quote 🟩
y_string_utf8 🟩
y_string_with_del_character 🟩
y_structure_lonely_false 🟩
y_structure_lonely_int 🟩
y_structure_lonely_negative_real 🟩
y_structure_lonely_null 🟩
y_structure_lonely_string 🟩
y_structure_lonely_true 🟩
y_structure_string_empty 🟩
y_structure_trailing_newline 🟩
y_structure_true_in_array 🟩
y_structure_whitespace_array 🟩

About

JSON generator and parser for NX/NJ in Sysmac Studio

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published