No edit summary |
No edit summary |
||
| Line 86: | Line 86: | ||
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. | 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. | |||
<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.
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