No edit summary |
|||
(2 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
ProB contains external functions to read and write JSON data and to communicate via sockets using JSON: | == Introduction == | ||
ProB contains external functions to read and write JSON data and to communicate via sockets using JSON-RPC: | |||
* <tt>LibraryJSON.def</tt> and <tt>LibraryJSON.mch</tt> providing the functions <tt>READ_JSON</tt>, <tt>READ_JSON_FROM_STRING</tt>, <tt>WRITE_JSON</tt>, <tt>WRITE_JSON_TO_STRING</tt> and the freetype <tt>JsonValue</tt> to read and write JSON data. | * <tt>LibraryJSON.def</tt> and <tt>LibraryJSON.mch</tt> providing the functions <tt>READ_JSON</tt>, <tt>READ_JSON_FROM_STRING</tt>, <tt>WRITE_JSON</tt>, <tt>WRITE_JSON_TO_STRING</tt> and the freetype <tt>JsonValue</tt> to read and write JSON data. | ||
* <tt>LibraryZMQ_RPC.def</tt> and <tt>LibraryZMQ_RPC.mch</tt>: providing access to a JSON RPC (Remote Procedure Call) | * <tt>LibraryZMQ_RPC.def</tt> and <tt>LibraryZMQ_RPC.mch</tt>: providing access to a JSON-RPC (Remote Procedure Call) protocol implementation either over regular sockets or ZMQ sockets. | ||
We have used this library to control Crazyflie drones via ProB: | |||
[[File:prob_drone.jpeg||800px]] | |||
== JSON Library == | |||
<tt>LibraryJSON.mch</tt> defines a FREETYPE to represent JSON data in a type-safe way: | <tt>LibraryJSON.mch</tt> defines a FREETYPE to represent [https://json.org/ JSON] data in a type-safe way: | ||
<pre> | <pre> | ||
JsonValue = JsonNull, | JsonValue = JsonNull, | ||
Line 18: | Line 24: | ||
They are not standard B, but stem from Z and Rodin's theory plug-in. | They are not standard B, but stem from Z and Rodin's theory plug-in. | ||
<tt>JsonValue</tt> maps quite naturally to the specification of JSON itself. | |||
<tt>LibraryJSON.def</tt> contains the definitions for the the external functions to interact with JSON text data: | |||
* <tt>READ_JSON(file)</tt> | |||
** Type Signature: <tt>STRING --> JsonValue</tt> | |||
** Reads a file path on the disk and parses the contained JSON data into the freetype representation. | |||
** <i>Warning: this function does external IO and should be guarded by a <tt>MAX_OPERATIONS == 0</tt> definition</i> | |||
* <tt>READ_JSON_FROM_STRING(contents)</tt> | |||
** Type Signature: <tt>STRING --> JsonValue</tt> | |||
** Parses the given JSON text into the freetype representation. | |||
* <tt>WRITE_JSON (json, file)</tt> | |||
** Type Signature: <tt>JsonValue * STRING</tt> (predicate, always returns <tt>true</tt>) | |||
** Converts the given JSON data from the freetype representation into text and writes it to the given file path. | |||
** <i>Warning: this function does external IO and should be guarded by a <tt>MAX_OPERATIONS == 0</tt> definition</i> | |||
* <tt>WRITE_JSON_TO_STRING(json)</tt> | |||
** Type Signature: <tt>JsonValue --> STRING</tt> | |||
** Converts the given JSON data from the freetype representation into text. | |||
You need to include <b>both</b> <tt>LibraryJson.def</tt> and <tt>LibraryJson.mch</tt> to interact with JSON. One contains the freetype definition, the other one the external function definitions. | |||
== JSON-RPC Library == | |||
Implements the [http://jsonrpc.org/spec JSON RPC] protocol. | |||
Despite their names the <tt>LibraryZMQ_RPC</tt> libraries can use both [https://zeromq.org/ ZeroMQ] library and native sockets. | |||
<tt> LibraryZMQ_RPC.mch </tt> defines a FREETYPE to represent RPC in a type-safe way: | <tt>LibraryZMQ_RPC.mch</tt> defines a FREETYPE to represent JSON-RPC response object in a type-safe way: | ||
<pre> | <pre> | ||
RpcResult = RpcSuccess(JsonValue), RpcError(STRING) | RpcResult = RpcSuccess(JsonValue), RpcError(STRING) | ||
</pre> | </pre> | ||
<tt>LibraryZMQ_RPC.def</tt> contains the definitions for the the external functions to interact with JSON RPC as a client or server: | |||
[ | * <tt>ZMQ_RPC_INIT(endpoint)</tt> | ||
** Type Signature: <tt>STRING --> SOCKET</tt> | |||
** Creates a ZMQ socket for RPC with the ZMQ library and binds it to the given endpoint. | |||
** <i>Warning: this function does external IO and should be guarded by a <tt>MAX_OPERATIONS == 0</tt> definition</i> | |||
* <tt>SOCKET_RPC_INIT(port)</tt> | |||
** Type Signature: <tt>INTEGER --> SOCKET</tt> | |||
** Creates a native socket for RPC and binds it to the given port. | |||
** <i>Warning: this function does external IO and should be guarded by a <tt>MAX_OPERATIONS == 0</tt> definition</i> | |||
* <tt>ZMQ_RPC_DESTROY(socket)</tt> | |||
** Type Signature: <tt>SOCKET</tt> (substitution, no return value) | |||
** Closes the given ZMQ or native socket. | |||
** <i>Note: despite its name this function works with both ZMQ and native sockets</i> | |||
** <i>Warning: this substitution does external IO and should be guarded by a <tt>MAX_OPERATIONS == 0</tt> definition</i> | |||
* <tt>ZMQ_RPC_SEND(socket, name, args)</tt> | |||
** Type Signature: <tt>(SOCKET*STRING*(STRING+->JsonValue)) --> RpcResult</tt> | |||
** Sends a JSON-RPC request object over the given ZMQ or native socket. | |||
** <i>Note: despite its name this function works with both ZMQ and native sockets</i> | |||
** <i>Warning: this function does external IO and should be guarded by a <tt>MAX_OPERATIONS == 0</tt> definition</i> | |||
* <tt>SOCKET_RPC_ACCEPT(port)</tt> | |||
** Type Signature: <tt>INTEGER --> JsonValue</tt> | |||
** Open a native server socket, wait for a client to connect, receive a JSON-RPC request object and return it. | |||
** <i>Warning: this function does external IO and should be guarded by a <tt>MAX_OPERATIONS == 0</tt> definition</i> | |||
* <tt>SOCKET_RPC_REPLY(port,rpcresult)</tt> | |||
** Type Signature: <tt>(INTEGER * RpcResult) --> BOOL</tt> | |||
** Respond to a previously received JSON-RPC request object with the given JSON-RPC response object. Will always return <tt>TRUE</tt>. | |||
** <i>Note: must be called right after <tt>SOCKET_RPC_ACCEPT</tt></i> | |||
** <i>Warning: this function does external IO and should be guarded by a <tt>MAX_OPERATIONS == 0</tt> definition</i> | |||
<tt>LibraryZMQ_RPC.def</tt> will transitively include <tt>LibraryJson.def</tt>. | |||
You need to include <b>all</b> of <tt>LibraryJson.mch</tt>, <tt>LibraryZMQ_RPC.def</tt> and <tt>LibraryZMQ_RPC.mch</tt> to interact with JSON-RPC, because the freetype definitions have to be separate from the external function definitions. | |||
== Example Machine == | |||
This is an excerpt from the B machine used to communicate with the Crazyflie drone. | |||
Parallel to this a Python script is running, which implements all the JSON-RPC methods and uses [https://pypi.org/project/cflib/ <tt>cflib</tt>] to communicate with the drone over radio. | |||
<pre> | |||
MACHINE DroneCommunicator USES LibraryJSON, LibraryZMQ_RPC | |||
DEFINITIONS | |||
"LibraryZMQ_RPC.def"; | |||
"LibraryReals.def"; | |||
SET_PREF_MAX_OPERATIONS == 0; | |||
CONSTANTS DRONE_URL | |||
PROPERTIES DRONE_URL:STRING & DRONE_URL = "radio://0/80/2M/5700B500F7" | |||
VARIABLES init, socket, cycle | |||
INVARIANT | |||
init:BOOL & | |||
socket:SOCKET & | |||
cycle:INTEGER | |||
INITIALISATION | |||
init := FALSE; | |||
socket := 0; | |||
cycle := 1 | |||
OPERATIONS | |||
Init = | |||
SELECT init = FALSE THEN | |||
socket := ZMQ_RPC_INIT("tcp://localhost:22272"); | |||
init := TRUE | |||
END; | |||
Destroy = | |||
SELECT init = TRUE THEN | |||
ZMQ_RPC_DESTROY(socket); | |||
init := FALSE | |||
END; | |||
register_sensors = | |||
SELECT init = TRUE THEN | |||
VAR res IN | |||
res := RpcSuccess~(ZMQ_RPC_SEND(socket, "register_log", { | |||
("url" |-> JsonString(DRONE_URL)), | |||
("name" |-> JsonString("Telemetry")), | |||
("variables" |-> JsonArray([ | |||
JsonString("stateEstimate.x"), | |||
JsonString("stateEstimate.y"), | |||
JsonString("stateEstimate.z"), | |||
JsonString("stateEstimate.roll"), | |||
JsonString("stateEstimate.pitch"), | |||
JsonString("stateEstimate.yaw") | |||
]))})); | |||
END | |||
END; | |||
Drone_Takeoff = | |||
SELECT init = TRUE THEN | |||
VAR res IN | |||
res := RpcSuccess~(ZMQ_RPC_SEND(socket, "takeoff", {("url" |-> JsonString(DRONE_URL))})) | |||
END | |||
END; | |||
Drone_Land = | |||
SELECT init = TRUE THEN | |||
VAR res IN | |||
res := RpcSuccess~(ZMQ_RPC_SEND(socket, "land", {("url" |-> JsonString(DRONE_URL))})) | |||
END | |||
END; | |||
Drone_Left(dist) = | |||
SELECT | |||
init = TRUE & | |||
dist : 0..2000 | |||
THEN | |||
VAR res IN | |||
res := RpcSuccess~(ZMQ_RPC_SEND(socket, "left", {("url" |-> JsonString(DRONE_URL)), ("distance" |-> JsonNumber(RDIV(real(dist), real(1000))))})) | |||
END | |||
END; | |||
Drone_Right(dist) = | |||
SELECT | |||
init = TRUE & | |||
dist : 0..2000 | |||
THEN | |||
VAR res IN | |||
res := RpcSuccess~(ZMQ_RPC_SEND(socket, "right", {("url" |-> JsonString(DRONE_URL)), ("distance" |-> JsonNumber(RDIV(real(dist), real(1000))))})) | |||
END | |||
END; | |||
Drone_Up(dist) = | |||
SELECT | |||
init = TRUE & | |||
dist : 0..2000 | |||
THEN | |||
VAR res IN | |||
res := RpcSuccess~(ZMQ_RPC_SEND(socket, "up", {("url" |-> JsonString(DRONE_URL)), ("distance" |-> JsonNumber(RDIV(real(dist), real(1000))))})) | |||
END | |||
END; | |||
Drone_Down(dist) = | |||
SELECT | |||
init = TRUE & | |||
dist : 0..2000 | |||
THEN | |||
VAR res IN | |||
res := RpcSuccess~(ZMQ_RPC_SEND(socket, "down", {("url" |-> JsonString(DRONE_URL)), ("distance" |-> JsonNumber(RDIV(real(dist), real(1000))))})) | |||
END | |||
END; | |||
Drone_Forward(dist) = | |||
SELECT | |||
init = TRUE & | |||
dist : 0..2000 | |||
THEN | |||
VAR res IN | |||
res := RpcSuccess~(ZMQ_RPC_SEND(socket, "forward", {("url" |-> JsonString(DRONE_URL)), ("distance" |-> JsonNumber(RDIV(real(dist), real(1000))))})) | |||
END | |||
END; | |||
Drone_Backward(dist) = | |||
SELECT | |||
init = TRUE & | |||
dist : 0..2000 | |||
THEN | |||
VAR res IN | |||
res := RpcSuccess~(ZMQ_RPC_SEND(socket, "backward", {("url" |-> JsonString(DRONE_URL)), ("distance" |-> JsonNumber(RDIV(real(dist), real(1000))))})) | |||
END | |||
END; | |||
out <-- Drone_GetX = | |||
SELECT init = TRUE THEN | |||
out := floor(1000.0 * JsonNumber~(RpcSuccess~(ZMQ_RPC_SEND(socket, "get_log_var", {("url" |-> JsonString(DRONE_URL)), ("name" |-> JsonString("stateEstimate.x"))})))); | |||
cycle := cycle + 1 | |||
END | |||
END | |||
</pre> |
ProB contains external functions to read and write JSON data and to communicate via sockets using JSON-RPC:
We have used this library to control Crazyflie drones via ProB:
LibraryJSON.mch defines a FREETYPE to represent JSON data in a type-safe way:
JsonValue = JsonNull, JsonBoolean(BOOL), JsonNumber(FLOAT), JsonString(STRING), JsonArray(seq(JsonValue)), JsonObject(STRING +-> JsonValue)
Free Types are inductive data types. They are not standard B, but stem from Z and Rodin's theory plug-in.
JsonValue maps quite naturally to the specification of JSON itself.
LibraryJSON.def contains the definitions for the the external functions to interact with JSON text data:
You need to include both LibraryJson.def and LibraryJson.mch to interact with JSON. One contains the freetype definition, the other one the external function definitions.
Implements the JSON RPC protocol.
Despite their names the LibraryZMQ_RPC libraries can use both ZeroMQ library and native sockets.
LibraryZMQ_RPC.mch defines a FREETYPE to represent JSON-RPC response object in a type-safe way:
RpcResult = RpcSuccess(JsonValue), RpcError(STRING)
LibraryZMQ_RPC.def contains the definitions for the the external functions to interact with JSON RPC as a client or server:
LibraryZMQ_RPC.def will transitively include LibraryJson.def.
You need to include all of LibraryJson.mch, LibraryZMQ_RPC.def and LibraryZMQ_RPC.mch to interact with JSON-RPC, because the freetype definitions have to be separate from the external function definitions.
This is an excerpt from the B machine used to communicate with the Crazyflie drone. Parallel to this a Python script is running, which implements all the JSON-RPC methods and uses cflib to communicate with the drone over radio.
MACHINE DroneCommunicator USES LibraryJSON, LibraryZMQ_RPC DEFINITIONS "LibraryZMQ_RPC.def"; "LibraryReals.def"; SET_PREF_MAX_OPERATIONS == 0; CONSTANTS DRONE_URL PROPERTIES DRONE_URL:STRING & DRONE_URL = "radio://0/80/2M/5700B500F7" VARIABLES init, socket, cycle INVARIANT init:BOOL & socket:SOCKET & cycle:INTEGER INITIALISATION init := FALSE; socket := 0; cycle := 1 OPERATIONS Init = SELECT init = FALSE THEN socket := ZMQ_RPC_INIT("tcp://localhost:22272"); init := TRUE END; Destroy = SELECT init = TRUE THEN ZMQ_RPC_DESTROY(socket); init := FALSE END; register_sensors = SELECT init = TRUE THEN VAR res IN res := RpcSuccess~(ZMQ_RPC_SEND(socket, "register_log", { ("url" |-> JsonString(DRONE_URL)), ("name" |-> JsonString("Telemetry")), ("variables" |-> JsonArray([ JsonString("stateEstimate.x"), JsonString("stateEstimate.y"), JsonString("stateEstimate.z"), JsonString("stateEstimate.roll"), JsonString("stateEstimate.pitch"), JsonString("stateEstimate.yaw") ]))})); END END; Drone_Takeoff = SELECT init = TRUE THEN VAR res IN res := RpcSuccess~(ZMQ_RPC_SEND(socket, "takeoff", {("url" |-> JsonString(DRONE_URL))})) END END; Drone_Land = SELECT init = TRUE THEN VAR res IN res := RpcSuccess~(ZMQ_RPC_SEND(socket, "land", {("url" |-> JsonString(DRONE_URL))})) END END; Drone_Left(dist) = SELECT init = TRUE & dist : 0..2000 THEN VAR res IN res := RpcSuccess~(ZMQ_RPC_SEND(socket, "left", {("url" |-> JsonString(DRONE_URL)), ("distance" |-> JsonNumber(RDIV(real(dist), real(1000))))})) END END; Drone_Right(dist) = SELECT init = TRUE & dist : 0..2000 THEN VAR res IN res := RpcSuccess~(ZMQ_RPC_SEND(socket, "right", {("url" |-> JsonString(DRONE_URL)), ("distance" |-> JsonNumber(RDIV(real(dist), real(1000))))})) END END; Drone_Up(dist) = SELECT init = TRUE & dist : 0..2000 THEN VAR res IN res := RpcSuccess~(ZMQ_RPC_SEND(socket, "up", {("url" |-> JsonString(DRONE_URL)), ("distance" |-> JsonNumber(RDIV(real(dist), real(1000))))})) END END; Drone_Down(dist) = SELECT init = TRUE & dist : 0..2000 THEN VAR res IN res := RpcSuccess~(ZMQ_RPC_SEND(socket, "down", {("url" |-> JsonString(DRONE_URL)), ("distance" |-> JsonNumber(RDIV(real(dist), real(1000))))})) END END; Drone_Forward(dist) = SELECT init = TRUE & dist : 0..2000 THEN VAR res IN res := RpcSuccess~(ZMQ_RPC_SEND(socket, "forward", {("url" |-> JsonString(DRONE_URL)), ("distance" |-> JsonNumber(RDIV(real(dist), real(1000))))})) END END; Drone_Backward(dist) = SELECT init = TRUE & dist : 0..2000 THEN VAR res IN res := RpcSuccess~(ZMQ_RPC_SEND(socket, "backward", {("url" |-> JsonString(DRONE_URL)), ("distance" |-> JsonNumber(RDIV(real(dist), real(1000))))})) END END; out <-- Drone_GetX = SELECT init = TRUE THEN out := floor(1000.0 * JsonNumber~(RpcSuccess~(ZMQ_RPC_SEND(socket, "get_log_var", {("url" |-> JsonString(DRONE_URL)), ("name" |-> JsonString("stateEstimate.x"))})))); cycle := cycle + 1 END END