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