Remote Procedure Call (RPC) over Websockets (uses MbedJSONValue)
Dependents: RPC_mbed_client RPC_Wifly_HelloWorld RPC_Ethernet_HelloWorld
Diff: MbedJSONRpc.h
- Revision:
- 0:a53d1c86196c
- Child:
- 2:af408b5cae75
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MbedJSONRpc.h Thu Sep 22 10:14:52 2011 +0000 @@ -0,0 +1,377 @@ +/** +* @author Samuel Mokrani +* +* @section LICENSE +* +* Copyright (c) 2011 mbed +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +* +* @section DESCRIPTION +* Main part of MbedJSONRpc: you can register methods or procedures, call a registered method or procedure +* on a client over a websocket communication, see all methods available on a certain client, listen for +* CALL incoming messages to execute a method or procedure. +* +*/ + + +#ifndef _MbedJSONRPC_H_ +#define _MbedJSONRPC_H_ + +#include "Websocket.h" +#include "Wifly.h" +#include "mbed.h" +#include "MbedJSONRpcValue.h" +#include <string> + +#define NB_METH 30 + +#define MSG_CALL "{\"from\": \"%s\",\"to\": \"%s\",\"msg\": \"CALL\",\"id_msg\": %d,\"method\": \"%s\",\"params\":%s}" +/**< Message skeleton which will be use for a call */ +#define MSG_RESULT "{\"from\": \"%s\",\"to\": \"%s\",\"msg\": \"RESULT\",\"id_msg\": %d,\"res\":%s}" +/**< Message skeleton which will be use for a result */ +#define MSG_REGISTER "{\"from\": \"%s\",\"to\": \"gateway\",\"msg\": \"REGISTER\",\"id_msg\": %d,\"fn\":\"%s\"}" +/**< Message skeleton which will be use for register a method or a procedure */ +#define MSG_INFO_METHODS "{\"from\": \"%s\",\"to\": \"%s\",\"msg\": \"INFO_METHODS\",\"id_msg\": %d}" +/**< Message skeleton which will be send to see all methods available of a certain client */ + + +/** +* \enum RPC_TYPE +* \brief Type of an RPC transaction +*/ +enum RPC_TYPE { + JSON_PARSE_ERROR, /*!< Json parse error */ + RPC_PARSE_ERROR, /*!< rpc parse error (the message doesn't contain good identifiers in a TypeObject MbedRpcValue)*/ + PROC_NOT_FOUND, /*!< The user wants to call an inexisting method or procedure */ + CLIENT_NOT_CONNECTED, /*!< The user wants to call a method or procedure on a client not connected (no response from the client within 3s) */ + SERVER_NOT_CONNECTED, /*!< No response from the server within 5s */ + ERR_ID, /*!< Each messages have an id, if the id of the answer doesn't match with the id of the request, there is an id error */ + CALL_OK, /*!< A call has been successfully executed */ + REGISTER_OK /*!< A request to register a method or procedure is successful */ +}; + + + +class ObjBase { +public: + virtual void execute(MbedRpcValue& val, MbedRpcValue& res) = 0; +}; + +template <class T> +class Obj : public ObjBase { +public: + + Obj(T * ptr, void (T::*fn_ptr)(MbedRpcValue& , MbedRpcValue& )) { + obj_ptr = ptr; + fn = fn_ptr; + }; + + virtual void execute(MbedRpcValue& val, MbedRpcValue& res) { + ((*obj_ptr).*fn)(val, res); + } + + //obj + T * obj_ptr; + + //method + void (T::*fn)(MbedRpcValue& , MbedRpcValue& ); + +}; + + +/** MbedJSONRpc class + * + * Warning: you must use a wifi module (Wifly RN131-C) or an ethernet network to use this class + * + * Example (client which registers one method): + * @code + * #include "mbed.h" + * #include "MbedJSONRpc.h" + * #include "MbedJSONRpcValue.h" + * #include "Websocket.h" + * + * #define ETHERNET + * + * #ifdef WIFI + * #include "Wifly.h" + * #endif + * + * DigitalOut led1(LED1); + * Serial pc(USBTX, USBRX); + * + * + * class Test { + * public: + * Test() {}; + * void fn(JSONRpcValue& val, JSONRpcValue& res) { + * printf("first arg: %s\r\n", val[0].get<string>().c_str()); + * res[0] = "coucou"; + * led1 = 1; + * } + * }; + * + * + * #ifdef WIFI + * //wifi and websocket + * Wifly wifly(p9, p10, p30, "mbed", "mbedapm2011", true); + * Websocket webs("ws://sockets.mbed.org:888/ws/samux/server",&wifly); + * #endif + * + * #ifdef ETHERNET + * Websocket webs("ws://sockets.mbed.org:888/ws/samux/server"); + * #endif + * + * //RPC object identified with the name "server" on the network and attached to the websocket server + * MbedJSONRpc rpc("server", &webs); + * + * Test test; + * + * int main() { + * + * RPC_TYPE t; + * + * + * //connection to the router and to the websocket server + * #ifdef WIFI + * while (1) { + * + * while (!wifly.Join()) //we connect to the network + * wifly.reset(); + * + * if (!webs.connect()) //we connect to the server + * wifly.reset(); + * else + * break; + * } + * #endif + * + * #ifdef ETHERNET + * while(!webs.connect()) + * pc.printf("cannot connect websocket, retrying\r\n"); + * #endif + * + * //register the method Test::fn as "fn1" + * if((t = rpc.registerMethod("fn1", &test, &Test::fn)) == REGISTER_OK) + * printf("register ok\r\n"); + * else + * printType(t); + * + * //Listen CALL requests + * rpc.work(); + * } + * @endcode + * + * + * + * Example (client which calls the previous registered method): + * @code + * #include "mbed.h" + * #include "MbedJSONRpc.h" + * #include "MbedJSONRpcValue.h" + * #include "Websocket.h" + * + * #define WIFI + * + * #ifdef WIFI + * #include "Wifly.h" + * #endif + * + * Serial pc(USBTX, USBRX); + * + * #ifdef WIFI + * //wifi and websocket + * Wifly wifly(p9, p10, p21, "mbed", "mbedapm2011", true); + * Websocket webs("ws://sockets.mbed.org:888/ws/samux/client",&wifly); + * #endif + * + * #ifdef ETHERNET + * Websocket webs("ws://sockets.mbed.org:888/ws/samux/client"); + * #endif + * + * //RPC object identified by the name "client" on the network and attached to the websocket server + * MbedJSONRpc rpc("client", &webs); + * + * int main() { + * + * //connection to the router and to the websocket server + * #ifdef WIFI + * while (1) { + * + * while (!wifly.Join()) //we connect to the network + * wifly.reset(); + * + * if (!webs.connect()) //we connect to the server + * wifly.reset(); + * else + * break; + * } + * #endif + * + * #ifdef ETHERNET + * while(!webs.connect()) + * pc.printf("cannot connect websocket, retrying\r\n"); + * #endif + * + * RPC_TYPE t; + * MbedRpcValue arg, resp; + * arg[0] = "Hello"; + * + * // print all methods and procedure available on the client "server" + * rpc.checkMethods("server"); + * + * // Try to call the function "fn1" registered by server previously + * if((t = rpc.call("fn1", "server", arg, resp)) == CALL_OK) + * { + * printf("call success\r\n"); + * printf("res: %s\r\n", resp[0].get<string>().c_str()); + * } + * else + * printType(t); + * } + * @endcode + */ +class MbedJSONRpc { +public: + + /** + * Constructor + * + * @param id Name of the client on the network + * @param ws All communication between clients will be established over this websocket + */ + MbedJSONRpc(Websocket * webs) : webs(webs), index(0), index_proc(0) { + std::string path = webs->getPath(); + std::string token = "/"; + size_t found; + found = path.find(token, 3); + if (found != std::string::npos) + path = path.substr(found + 1, std::string::npos); + strcpy(my_id, path.c_str()); + }; + + /** + * Register a method of an object + * + * @param public_name the method will be seen and called by this name + * @param obj_ptr a pointeur on the object which contains the method to register + * @param fn the method to register (this method must have this signature: void fn(MbedRpcValue& val, MbedRpcValue& res) + * @return if REGISTER_OK, the method is registered, otherwise, there is an error + * + */ + template<typename T> RPC_TYPE registerMethod(const char * public_name, T * obj_ptr, void (T::*fn)(MbedRpcValue& val, MbedRpcValue& res)) { + char json[100]; + RPC_TYPE t; + Timer tmr; + int id = rand() % 100; + MbedRpcValue tmp; + + sprintf(json, (const char *)MSG_REGISTER, my_id, id, public_name); + webs->Send(json); + tmr.start(); + t = waitAnswer(tmp, id, json); + if (t != REGISTER_OK) + return t; + if( index == NB_METH ) + index = NB_METH - 1; + obj[index] = new Obj<T>(obj_ptr, fn); + name[index++] = public_name; + return REGISTER_OK; + } + + + /** + * Register a procedure + * + * @param public_name the method will be seen and called by this name + * @param fn the procedure to register (this procedure must have this signature: void fn(MbedRpcValue& val, MbedRpcValue& res) + * @return if REGISTER_OK, the procedure is registered, otherwise, there is an error + * + */ + RPC_TYPE registerMethod(const char * public_name, void (*fn)(MbedRpcValue& val, MbedRpcValue& res) ); + + + /** + * Call a method or procedure on a client + * + * @param fn name of the method or procedure to be called + * @param dest name of the client where will be executed the method or procedure + * @param val Input parameters of the method or procedure "fn" + * @param resp Once the method or procedure executed, the result will be stored in the "resp" variable + * @return If CALL_OK, the method has been executed and you can access the result with the "resp" variable. + * If CLIENT_NOT_CONNECTED, means that the client hasn't answered within 3s. + * If SERVER_NOT_CONNECTED, means that the server hasn't answered within 5s. + * Refer to the RPC_TYPE description + * + */ + RPC_TYPE call(char * fn, char * dest, MbedRpcValue& val, MbedRpcValue& resp); + + + /** + * Listen for CALL requests + * Warning: infinite loop + */ + void work(); + + /** + * Print by the usb serial port all methods or procedure available on "dest" client + */ + void checkMethods(char * dest); + +private: + + typedef void (*FNPTR)(MbedRpcValue& , MbedRpcValue& ); + + int methodAlreadyRegistered(char *); + int procAlreadyRegistered(char *); + RPC_TYPE decodeMsg(MbedRpcValue& , int); + RPC_TYPE waitAnswer(MbedRpcValue& , int, char *); + + Websocket * webs; + + ObjBase * obj[NB_METH]; + const char * name[NB_METH]; + int index; + + FNPTR proc[NB_METH]; + const char * name_proc[NB_METH]; + int index_proc; + + + char my_id[20]; + +}; + + +inline void printType(RPC_TYPE t) +{ + switch(t) + { + case JSON_PARSE_ERROR: printf("json parse error\r\n"); break; + case RPC_PARSE_ERROR: printf("rpc parse error\r\n"); break; + case PROC_NOT_FOUND: printf("proc or method not found\r\n"); break; + case CLIENT_NOT_CONNECTED: printf("client not connected\r\n"); break; + case SERVER_NOT_CONNECTED: printf("server not connected\r\n"); break; + case ERR_ID: printf("id error\r\n"); break; + case CALL_OK: printf("call ok\r\n"); break; + case REGISTER_OK: printf("register ok\r\n"); break; + } +} + +#endif \ No newline at end of file