Remote Procedure Call (RPC) over Websockets (uses MbedJSONValue)
Dependents: RPC_mbed_client RPC_Wifly_HelloWorld RPC_Ethernet_HelloWorld
MbedJSONRpc.h
- Committer:
- samux
- Date:
- 2011-09-22
- Revision:
- 5:88e1902947e5
- Parent:
- 4:2f83c922c234
- Child:
- 6:0ec91dd0e80e
File content as of revision 5:88e1902947e5:
/** * @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 "MbedJSONValue.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 MbedJSONValue)*/ 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(MbedJSONValue& val, MbedJSONValue& res) = 0; }; template <class T> class Obj : public ObjBase { public: Obj(T * ptr, void (T::*fn_ptr)(MbedJSONValue& , MbedJSONValue& )) { obj_ptr = ptr; fn = fn_ptr; }; virtual void execute(MbedJSONValue& val, MbedJSONValue& res) { ((*obj_ptr).*fn)(val, res); } //obj T * obj_ptr; //method void (T::*fn)(MbedJSONValue& , MbedJSONValue& ); }; /** 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 "MbedJSONValue.h" * #include "Websocket.h" * * #define WIFI * * #ifdef WIFI * #include "Wifly.h" * #endif * * DigitalOut led1(LED1); * Serial pc(USBTX, USBRX); * * * class Test { * public: * Test() {}; * void fn(MbedJSONValue& val, MbedJSONValue& 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, "NETGEAR", "hello", 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(&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 "MbedJSONValue.h" * #include "Websocket.h" * * #define ETHERNET * * #ifdef WIFI * #include "Wifly.h" * #endif * * Serial pc(USBTX, USBRX); * * #ifdef WIFI * //wifi and websocket * Wifly wifly(p9, p10, p30, "NETGEAR", "hello", 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(&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; * MbedJSONValue 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(MbedJSONValue& val, MbedJSONValue& 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)(MbedJSONValue& val, MbedJSONValue& res)) { char json[100]; RPC_TYPE t; Timer tmr; int id = rand() % 100; MbedJSONValue 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(MbedJSONValue& val, MbedJSONValue& res) * @return if REGISTER_OK, the procedure is registered, otherwise, there is an error * */ RPC_TYPE registerMethod(const char * public_name, void (*fn)(MbedJSONValue& val, MbedJSONValue& 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, MbedJSONValue& val, MbedJSONValue& 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)(MbedJSONValue& , MbedJSONValue& ); int methodAlreadyRegistered(char *); int procAlreadyRegistered(char *); RPC_TYPE decodeMsg(MbedJSONValue& , int); RPC_TYPE waitAnswer(MbedJSONValue& , 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