Remote Procedure Call (RPC) over Websockets (uses MbedJSONValue)

Dependents:   RPC_mbed_client RPC_Wifly_HelloWorld RPC_Ethernet_HelloWorld

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