Contents |
[Back to TinyG main page]
[Back to TinyG developer info page]
[Back to TinyG communications page]
NOTE: THIS PAGE DESCRIBES JSON HANDLING IN RELEASE 0.95 AND LATER (Updated 9/7/12)
For documentation on JSON in 0.93 and 0.94 see TinyG-JSON-deprecated
TinyG can use JavaScript Object Notation (JSON) as an exchange format for commands and responses. Using JSON dramatically simplifies writing upper-level consoles in languages such as Python, Ruby, JavaScript, Java, Processing and other languages that support dictionaries / hashmaps. [Almost] All commands are also available in plaion old text mode as well. The ASCII communications overhead is somewhat higher in JSON than text mode but is still quite efficient and manageable.
The JSON language definition is here. A handy validator can be found here.
TinyG implements a subset of JSON with the following limitations:
TinyG can operate in either text mode (command line mode) or JSON mode. In text mode TinyG accepts Gcode blocks per usual and $ config lines, and returns responses in human-friendly ASCII. Text mode responses start with "ok:" or "error:" to maintain compatibility with grbl. In text mode it's up the user whether character echo is enabled or not ($ee=0 or 1).
In JSON mode TinyG expects well structured JSON (see the JSON validator if you have any doubt). It returns well-structured JSON wrapped in a response header, which is described below. In JSON mode it usually makes sense to disable character echo by sending {"ee":"0"}. JSON requests are generally just curly braces and formatting around what would otherwise be a command line request. The method (verb) for the command is implied by convention and is not present in the object itself. Most JSON objects only contain a single request but multiple request objects in a JSON line is supported - up the the limits mentioned above.
TinyG starts up in either JSON or text mode depending on the setting of $ej parameter (enable_json). Set $ej=0 for text mode, $ej=1 for JSON. (Note: The first string returned on bootup will be in JSON format, regardless of the mode set)
JSON operation will also be invoked by sending any line starting with an opening curly with no leading whitespace. All commands will then be expected in JSON format and responses will be returned in JSON format. The system will stay in JSON mode until a line with a leading $, ? or 'h' is received, which reverts it back to text mode. Note that Gcode commands entered without JSON wrapping will not return the system to text mode - responses will still come back wrapped in JSON format.
All this JSON stuff is an attempt at a RESTful interface, albeit not one based on HTTP. Data is treated as resources and state is transfered in and out. Methods are implied by convention as there is no request header in which to declare them. There are two main resource models: the static model which has all the settings and parameters, and the dynamic model which is the Gcode state and the state of the machine. These models are composites made of various resources such as motor resources, axis resources, coordinate system resources, system settings, gcode state and a few others. The intention is to (1) model the system in a way that's familiar to web programmers, and (2) to close the gap to putting an actual HTTP/REST interface on the thing.
TinyG accepts either friendly names or short mnemonic tokens as input. Friendly names (names) are strings up to 24 characters. Tokens are shorthand for the name and can be up to 4 characters in length. Axis and motor tokens are typically 3 characters in length including their axis or motor prefix; Non-axis and non-motor (general) tokens are 2 to 4 characters. Both friendly names and tokens are case insensitive and cannot contain whitespace or the separator characters: quote("), comma (,), colon (:), equal (=), or pipe(|). Requests may contain either friendly names or tokens, but responses will always be in token form. See TinyG Configuration for a complete list of the tokens and friendly-names used for settings. Some examples are provided below:
| Token | Name |
| xfr | x_feedrate |
| cjm | c_jerk_maximum |
| ea | enable_acceleration |
| ic | ignore_cr |
| 1mi | m1_microsteps |
| sr | status_report |
JSON requests are used to perform the following actions:
Generally speaking, JSON requests are exactly like text mode requests except that they have a JSON wrapper. This is explained in the next sections.
To get a parameter pass an object with a null value. The value is returned in the response. Some examples of valid requests and responses are:
| Request | Response | Comments |
| {"xfr":""} | {"r":{"bd":{"xfr":1200.000},"sc":0,"sm":"OK","cks":"2593896578"}} | return x max feed rate |
| {"x_feedrate":""} | {"r":{"bd":{"xfr":1200.000},"sc":0,"sm":"OK","cks":"2593896578"}} | Same as above using friendly-name |
| {"xfr":null} | {"r":{"bd":{"xfr":1200.000},"sc":0,"sm":"OK","cks":"2593896578"}} | Same as above |
| {"xfr":null, "yfr":null, "zfr":null} | {"r":{"bd":{"xfr":1200.000,"yfr":1200.000,"zfr":1200.000},"sc":0,"sm":"OK","cks":"2593896578"}} | Multiple gets may be presented in a single object |
To set a parameter pass an object with the value to be set. The value taken is returned in the response. The response value may be different than the requested valued in some cases. For example, an attempt to set a status interval less than the minimum will return the minimum interval. Trying to set a read-only value will return that value; for example, firmware version. In some other cases a value of 'false' will be returned. The following are examples of valid set commands.
| Request | Response | Comments |
| {"xfr":1200} | {"r":{"bd":{"xfr":1200.000},"sc":0,"sm":"OK","cks":"2593896578"}} | Set x max feed rate to 1200 mm/min (assumes mm units set) |
| {"x_feedrate":1200} | {"r":{"bd":{"xfr":1200.000},"sc":0,"sm":"OK","cks":"2593896578"}} | Same as above using friendly-name |
| {"si":250} | {"r":{"bd":{"si":250.000},"sc":0,"sm":"OK","cks":"2593896578"}} | Set status interval to 250 milliseconds |
| {"si":10} | {"r":{"bd":{"si":200.000},"sc":0,"sm":"OK","cks":"2593896578"}} | Attempt to set status interval to 10 ms, but minimum was 200, so that's what stuck |
| {"fv":2.0} | {"r":{"bd":{"fv":0.930},"sc":0,"sm":"OK","cks":"2593896578"}} | Whilst you may want a version 2.0 to magically appear the firmware remains at version 0.93 :( |
| {"xvm":2000, "yvm":2000, "zvm":2000, "avm":36000} | {"r":{"bd":{"xvm":2000.000,"yvm":2000.000,"zvm":2000.000,"avm":36000.000},"sc":0,"sm":"OK","cks":"2593896578"}} | Setting max velocity for 4 axes |
All responses to JSON request are returned in a response header. The response format provides an outer wrapper (header) for response details and an inner "body" which is the object being returned. The response wrapper plays a similar role to an HTTP response header. This simplifies parsing and makes the whole thing one step closer to an actual RESTful HTTP implementation.
The response wrapper has the following format:
{ Comments:
"r": { response object
"bd": { body of response
<returned object> returned object, e.g. {"xvm":600.00"}
},
"sc": 0, status code
"sm": "OK", status message (for end-user display)
"buf": 255, bytes available in input buffer
"ln": 0, gcode line number N just received, or auto-increment if no N word provided
"cks": 123456789 checksum
}
}
Note: The actual output is on one line with a trailing <CR>. The format above is just for clarity
Note: The letter 'q' is rereserved for reQuest, should we decide to wrap that, too.
"r" Response Header
Responses can be up to 256 characters long but we try to keep them shorter in the interest of communications overhead.
The entire JSON object is terminated by CR, LF or CR/LF depending on the board configuration settings ($ec, $el)
'bd' {returned body object} This is a JSON object that is one of:
"sc" Status Code
A status code of '0' is OK. All others are errors. Check tinyg.h for the most up-to-date list, and see controller.c for corresponding end-user status messages.
// OS, communications and low-level status (must align with XIO_xxxx codes in xio.h) #define TG_OK 0 // function completed OK #define TG_ERROR 1 // generic error return (EPERM) #define TG_EAGAIN 2 // function would block here (call again) #define TG_NOOP 3 // function had no-operation #define TG_COMPLETE 4 // operation is complete #define TG_TERMINATE 5 // operation terminated (gracefully) #define TG_ABORT 6 // operaation aborted #define TG_EOL 7 // function returned end-of-line #define TG_EOF 8 // function returned end-of-file #define TG_FILE_NOT_OPEN 9 #define TG_FILE_SIZE_EXCEEDED 10 #define TG_NO_SUCH_DEVICE 11 #define TG_BUFFER_EMPTY 12 #define TG_BUFFER_FULL_FATAL 13 #define TG_BUFFER_FULL_NON_FATAL 14 // NOTE: XIO codes align to here // Internal errors and startup messages #define TG_INTERNAL_ERROR 20 // unrecoverable internal error #define TG_INTERNAL_RANGE_ERROR 21 // number range other than by user input #define TG_FLOATING_POINT_ERROR 22 // number conversion error #define TG_DIVIDE_BY_ZERO 23 // Input errors (400's, if you will) #define TG_UNRECOGNIZED_COMMAND 40 // parser didn't recognize the command #define TG_EXPECTED_COMMAND_LETTER 41 // malformed line to parser #define TG_BAD_NUMBER_FORMAT 42 // number format error #define TG_INPUT_EXCEEDS_MAX_LENGTH 43 // input string is too long #define TG_INPUT_VALUE_TOO_SMALL 44 // input error: value is under minimum #define TG_INPUT_VALUE_TOO_LARGE 45 // input error: value is over maximum #define TG_INPUT_VALUE_RANGE_ERROR 46 // input error: value is out-of-range #define TG_INPUT_VALUE_UNSUPPORTED 47 // input error: value is not supported #define TG_JSON_SYNTAX_ERROR 48 // JSON string is not well formed #define TG_JSON_TOO_MANY_PAIRS 49 // JSON string or has too many JSON pairs // Gcode and machining errors #define TG_ZERO_LENGTH_MOVE 60 // move is zero length #define TG_GCODE_BLOCK_SKIPPED 61 // block is too short - was skipped #define TG_GCODE_INPUT_ERROR 62 // general error for gcode input #define TG_GCODE_FEEDRATE_ERROR 63 // move has no feedrate #define TG_GCODE_AXIS_WORD_MISSING 64 // command requires at least one axis present #define TG_MODAL_GROUP_VIOLATION 65 // gcode modal group error #define TG_HOMING_CYCLE_FAILED 66 // homing cycle did not complete #define TG_MAX_TRAVEL_EXCEEDED 67 #define TG_MAX_SPINDLE_SPEED_EXCEEDED 68 #define TG_ARC_SPECIFICATION_ERROR 69 // arc specification error
At some point we may want to distinguish between fatal and non fatal errors.
"sm" Status Message
Status messages are end-user text associated with each error code (see controller.c). Not all commands are required to return messages (for example, status reports may not). Parsers should work to the status codes, NOT the messages, as messages may change or be omitted.
Note: these are distinct from application messages that may be returned from Gcode MSG fields, or from system startup operations.
"buf" RX buffer bytes available
This value is how many bytes are available in the serial input buffer (RX buffer). An empty buffer has 254 bytes. As the RX buffer fill up this number goes down until a command is read out of the buffer. Then it goes back up by the length of the line read. This value is used for synchronization with host drivers.
"ln" Last line number processed
This is the line number of the line that was most recently pulled out of the buffer, or the "gcode model" line number. This number will be populate with the N word sent in the Gcode block - e.g. N300. If no N word is populated the line number will advance by 1 for each new line sent in. Since Gcode does not require every line to have a number (or any!), this can lead to some odd behaviors. Say you set N300, then followed with 20 lines without N words. Then sent N310. The line number will advance to 320, then reset to 310. You are your own master. Note that the line number reported in "ln" (the *model* line number) is different than the *runtime* line number reported as "line" in a status report. "Line" is the line currently being executed by the steppers. The model line number may lag the runtime line number by as many as 24 moves, depending on how deep the move planner is.
Be aware that BX buffer space is being under-reported in most cases. The controller module is continuously pulling characters off the RX buffer into a separate input buffer (string). When the input string has a full command ready for processing it's another 100 microseconds or so before the model line number is updated and available for return (e.g. on a status report or some other asynchronous response). Then typically it will be another 3000 microseconds (+ or -) before the command is actually processed and the response to the command is returned with the new model line number. This under-reporting should not cause problems for command synchronization with the host, but it's good to know it's happening.
"cks" Checksum
The checksum is always the last item on the line. The checksum is the decimal Java hashCode of the string starting with the opening curly (in front the "r") to the "cks" name. For example:
for this string ---------> {"r.:{"bd":{"gc":"g0x100"},"sc":0,"sm":"OK","cks":"3756753382"}}
take checksum of --> {"r.:{"bd":{"gc":"g0x100"},"sc":0,"sm":"OK","cks"
Do not include a CR or line terminator in the checksum.
See en.wikipedia.org/wiki/Java_hashCode() or the algorithm
In JSON mode Gcode blocks may be provided either as native gcode text or wrapped in JSON as a "gc" object. In either case responses will be returned in JSON format. A JSON wrapper may only contain a single gcode block.
Gcode requests
{"gc":"g0 x100"} wrapped input
g0 x100 unwrapped input
{"gc":"g0 x100 (Initial move)"} wrapped input with a Gcode comment (see note)
{"gc":"m0 (MSGChange tool and hit Cycle Start when done)"} stop with a message
Gcode responses
{"r":{bd:{"gc":"G0X100"},"st":0,"sm":"OK","cks":123456789}}
{"r":{bd:{"gc":"G0X100 (Initial move)"},"st":0,"sm":"OK","cks":123456789}}
{"r":{bd:{"gc":"M0","msg":"Change tool and hit Cycle Start when done"},"st":0,"sm":"OK","cks":123456789}}
The first response is pretty normal. The second has a comment in it. The third is what would happen if a MSG were communicated in the Gcode comment.
Note: We are still undecided on how to handle comments in the Gcode. These could be returned in the gcode response as illustrataed, but currently they are stripped.
Note: We are still debating if line numbers should be returned as a separate JSON element, e.g. "gc":"G0X100","line":2245. See issue #23 on the TinyG github
Status reports are parent/child objects with a "sr" parent and one or more child NV pairs, as in the example below.
{"sr": {"line":1245, "xpos":23.4352, "ypos":-9.4386, "zpos":0.125, "vel":600, "unit":"mm", "stat":"run"}}
The following use-cases are supported:
{"sr":{"line":true,"xpos":true, "ypos":true,"zpos":true, "vel":true, "unit":true, "stat":true}}
{"status_report":{"line":true,"xpos":true, "ypos":true,"zpos":true, "vel":true, "unit":true, "stat":true}} - this is the same as the above but using the friendly name
{"status_report":""}
{"status_report":null} the 'null' value is used instead of "" in this case. Either are accepted.
{"sr":""}
{"sr":null}
{"line":"","xpos":"","ypos":"","zpos":"","vel":"","unit":"","stat":""} request GETS for the listed values
{"line":1245, "xpos":23.4352, "ypos":-9.4386, "zpos":0.125, "vel":600, "unit":"mm", "stat":"run"} returns the NV pairs. Note the absence of the SR parent object.
{"status_interval":0} disable automatic status reports
{"status_interval":200} request status reports every 200 ms during movement
{"si":200} same as above
A status report may contain one or more of the following attributes. The [token] and the friendly name are listed. Values are returned as numbers. (note: A CSV string is returned when in text mode)
[vel] velocity - actual velocity - may be different than programmed feed rate [line] line_number - either the Gcode line number (N word), or the auto-generated line count if N's are no present [feed] feed_rate - gcode programmed feed rate (F word) [stat] machine_state - 0=reset, 2=stop, 3=end, 4=run, 5=hold, 6=homing [unit] units_mode - 0=inch, 1=mm [coor] coordinate_system - 0=g53, 1=g54, 2=g55, 3=g56, 4=g57, 5=g58, 6=g59 [momo] motion_mode - 0=traverse, 1=straight feed, 2=cw arc, 3=ccw arc [plan] plane_select - 0=XY plane, 1=XZ plane, 2=YZ plane [path] path_control_mode - 0=exact stop, 1=exact path, 2=continuous [dist] distance_mode - 0=absolute distance, 1=incremental distance [frmo] feed_rate_mode - 0=units-per-minute-mode, 1=inverse-time-mode [gc] gcode_block - gcode block currently being run [mpox] mpox (x absolute position) - X machine position in absolute coordinate system in prevailing units (mm or inch) [mpoy] mpoy (y absolute position) [mpoz] mpoz (z_absolute position) [mpoa] mpoa (a absolute position) [mpob] mpob (b absolute position) [mpoc] mpoc (c absolute position) [posx] posx (x position) - X work position in prevailing units (mm or inch) [posy] posy (y position) [posz] posz (z position) [posa] posa (a position) [posb] posb (b position) [posc] posc (c position) [g92x] g92x - G92 origin offset for X axis [g92y] g92y [g92z] g92z [g29a] g92a [g92b] g92b [g92c] g92c Additionally, any valid token may be listed in a status report. For example, "g54x" will return the X offset in the G54 coordiante system (coordinate system #1). "fv" would return the firmware version.
"Groups" are related parameters or settings. The following groups are defined:
See [Configuration] for details about the settings available in groups.
Groups are treated specially in JSON. The group prefix is "pulled out" and used as the parent object ID for the child NV pairs. For example, here is a standard (non-group) JSON line with all motor parameters for motor #2:
{"2ma":1,"2sa":1.8,"2tr":1.275,"2mi":2,"2po":0,"2pm":1}
where:
[2ma] m2_map_to_axis
[2sa] m2_step_angle
[2tr] m2_travel_per_revolution
[2mi] m2_microsteps
[2po] m2_polarity
[2pm] m2_power_management
Here is the same set of parameters represented as a group resource:
{"2":{"ma":1,"sa":1.8,"tr":1.275,"mi":2,"po":0,"pm":1}}
These are handled identically by the JSON parser. Either line above would have set all the values in the list. If null's had been provided all values would have been returned.
A group resource can also be retrieved by its parent name alone:
{"2":""} returns:
{"2":{"ma":1,"sa":1.8,"tr":1.275,"mi":2,"po":0,"pm":1}}
Another advantage of the group form is that all motors will serialize / deserialize in host code to well-defined objects, making for convenient host coding. Essentially a resource can be instantiated in the host program, serialized to JSON, then sent to the the firmware to set all values in the resource. The same is true in reverse. You can think of r attributes as being in an implicit dotted notation: e.g. x.fr, 1.sa, or g55.x
Here is an short Python Script that illustrates how easy it is to get data back from TinyG in a programatic way.
#!/usr/bin/python
import json
motor_settings = '{"2":{"ma":1,"sa":1.8,"tr":1.275,"mi":2,"po":0,"pm":1}}'
#motor_settings is an example response you would get from TG if you issued a {"2":""}\n command
motors=json.loads(motor_settings) #load the response into a python dict.
#Break out individual values into vars.
microSteps, travelRevs, stepAngle, polarity, powerManagement = (motors["2"]["ma"], p["2"]["tr"], p["2"]["sa"], p["2"]["po"], p["2"]["pm"])
Some other examples of group resoruces:
{"x":{"am":1,"fr":1000,"vm":1000,"tm":100,"jm":100000000,"jd":0.05,"sm":1,"sv":1000,"lv":100,"zo":4,"abs":"","pos":""}}
{"y":{"am":1,"fr":1000,"vm":1000,"tm":100,"jm":100000000,"jd":0.05,"sm":1,"sv":1000,"lv":100,"zo":4,"abs":"","pos":""}}
{"z":{"am":1,"fr":1000,"vm":1000,"tm":100,"jm":100000000,"jd":0.05,"sm":1,"sv":1000,"lv":100,"zo":4,"abs":"","pos":""}}
{"g54":{"x":0.000,""y":0.000,"z":0.000,"a",0.000,"b":0.000,"c":0.000}}
{"g55":{"x":50.000,""y":50.000,"z":-10.000,"a",0.000,"b":0.000,"c":0.000}}
The System Group is a special case that handles system and global values that don't have have a common prefix. The system group is identified by "sys". Here are the members of the system group listed as a plain list and as a group resource.
{"fv":"","fb":"","si":"","gpl":"","gun":"","gco":"","gpa":"","gdi":"",ea":"","ja":"","ml":"","ma":"","mt":"","ic":"","il":"","ec":"","ee":"","ex":""} - as a plain list
{"sys":{"fv":"","fb":"","si":"","gpl":"","gun":"","gco":"","gpa":"","gdi":"",ea":"","ja":"","ml":"","ma":"","mt":"","ic":"","il":"","ec":"","ee":"","ex":""}} - as a system group
{"sys":""} - returns all values in the system group
[fv] firmware_version
[fb] firmware_build
[si] status_interval
[gpl] gcode_select_plane - power-on and reset default, not equivalent to G17, G18, G19
[gun] gcode_units_mode - power-on and reset default, not equivalent to G20, G21
[gco] gcode_coord_system - power-on and reset default, not equivalent to G54 - G59
[gpa] gcode_path_control - power-on and reset default, not equivalent to G61, G61.1, G64
[gdi] gcode_distance_mode - power-on and reset default, not equivalent to G90, G91
[ea] enable_acceleration
[ja] junction_acceleration
[ml] min_line_segment
[ma] min_arc_segment
[mt] min_segment_time
[ic] ignore_CR/LF (on RX)
[ec] enable_CR (on TX)
[ee] enable_echo
[ex] enable_xon_xoff
[ej] enable_json_mode on startup