How to implement an RPC-able class?

24 Nov 2009 . Edited: 24 Nov 2009

I want to do something like this:

 

#include "mbed.h"
Serial pc(USBTX, USBRX);
DigitalOut led1(LED1, "led1");
class myrpc : public Base {
public: 
  blink(int n);
}
myrpc::blink(int n) {
  for (int i=0; i<n; i++) {
    led1=1; wait(0.2);
    led1=0; wait(0.2);
  }
}
main() {
  Base::add_rpc_class<myrpc>();
  char buf[256], outbuf[256];
  while(1) {
    pc.gets(buf, 256);
    rpc(buf, outbuf); 
    pc.printf("%s\n", outbuf);
  }
}

 

Is there a sample code that can be used to provide RPC for custom classes / functions?

24 Nov 2009

Looks like you need to implement virtual methods rpc() and get_rpc_methods() described in Base.h.

24 Nov 2009 . Edited: 24 Nov 2009

Hi Ilya,

Yeah, the implementation isn't advertised as I'd like to be able to do better. Introspection in C/C++ just isn't pretty, and what we've managed so far is just a bit ugly. We were looking at some wrapper auto-generators, but as yet not found an ideal solution.

If you *really* want to play, take a look at this...

#include "rpc.h"

const rpc_method *DigitalOut::get_rpc_methods() {
    static const rpc_method rpc_methods[] = {
        { "write", rpc_method_caller<DigitalOut, int, &DigitalOut::write> },
        { "read", rpc_method_caller<int, DigitalOut, &DigitalOut::read> },
        RPC_METHOD_SUPER(Base),
    };
    return rpc_methods;
}

rpc_class *DigitalOut::get_rpc_class() {
    static const rpc_function funcs[] = {
        { "new", rpc_function_caller<const char*, PinName, const char*, &Base::construct<DigitalOut,PinName,const char*> > },
        RPC_METHOD_END
    };
    static rpc_class c = { "DigitalOut", funcs, NULL };
    return &c;
}

...but I give you absolutely no guarantees we wont change all of this. Be warned!

Simon

24 Nov 2009

Hi Ilya,

If you keep in mind that you get ...

Simon Ford wrote:
absolutely no guarantees we wont change all of this. Be warned!

And would like to see a working example have a look at the Servo class in the cookbook:

http://mbed.org/projects/cookbook/svn/Servo/trunk

 

Cheers

Rolf

 

24 Nov 2009 . Edited: 08 Dec 2009

Simon, Rolf,

Thanks for quick response and great pointers. Note taken on the "absolutely no guarantees we wont change all of this. Be warned!".

 

Here's the code that works:

 

myrpc.h

 

#ifndef MYRPC_H
#define MYRPC_H

#include "mbed.h"

namespace mbed {

class myrpc : public Base {
public:
  myrpc(PinName pin, const char* name = NULL);
  void blink(int n);
#ifdef MBED_RPC
  virtual const struct rpc_method *get_rpc_methods();
  static struct rpc_class *get_rpc_class();
#endif    // MBED_RPC

protected:
    DigitalOut _pin;
};

}    // namespace mbed
#endif    // MYRPC_H

 

 

myrpc.cpp
#include "myrpc.h"
#ifdef MBED_RPC
#include "rpc.h"
#endif

namespace mbed {

myrpc::myrpc(PinName pin, const char *name) : Base(name), _pin(pin) {}

void myrpc::blink(int n) {
  for (int i=0; i<n; i++) {
    _pin = 1;
    wait(0.2);
    _pin = 0;
    wait(0.2);
  }
}

#ifdef MBED_RPC
const rpc_method *myrpc::get_rpc_methods() {
  static const rpc_method rpc_methods[] = {
    { "blink", rpc_method_caller<myrpc, int, &myrpc::blink> },
    RPC_METHOD_SUPER(Base)
  };
  return rpc_methods;
}       
rpc_class *myrpc::get_rpc_class() {
    static const rpc_function funcs[] = {
        "new", rpc_function_caller<const char*, PinName, const char*, &Base::construct<myrpc,PinName,const char*> >,
        RPC_METHOD_END
    };
    static rpc_class c = { "myrpc", funcs, NULL };
    return &c;
}
#endif

}    // namespace mbed

Obviously "blink" function is not that exciting, but it is easy to see if code works with it.
25 Nov 2009 . Edited: 25 Nov 2009

I used the above example in HTTPServer, and it works! Pretty cool stuff.

just add this to the main.cpp:

 

...
#include "myrpc.h"
...
main() {
...
Base::add_rpc_class<myrpc>();
// before http.addHandler(new HTTPRPC());

 

Then in a browser do these:

 

http://mbeddevice/rpc/myrpc/new+LED1+bled1

http://mbeddevice/rpc/bled1/blink+10

> MBED blinks led1 10 times

 

http://mbeddevice/rpc/bled1/delete

 

I have one more question:

Is it possible to use full-formed URI in HTTPServer's RPC? That would allow using regular HTML forms with input elements to fill in multiple arguments, e.g. GET will result in this:
http://mbeddevice/rpc/led1/blink?n=1&t=0.2

I tried it to no avail...

25 Nov 2009 . Edited: 25 Nov 2009

Hi Ilya,

Ilya I wrote:
Is it possible to use full-formed URI in HTTPServer's RPC? That would allow using regular HTML forms with input elements to fill in multiple arguments, e.g. GET will result in this: http://mbeddevice/rpc/led1/blink?n=1&t=0.2

The format needs to be something like e.g. /rpc/obj/func?5,12, so not quite straight forward. You could probably use an onSubmit JS function to mangle the URL appropriately.

The other way is AJAX :) Have a look at something I experimented with once (may not be exactly aligned now):

This aims to basically map the C++ interface exactly to Javascript over the HTTP/AJAX transport mechanism. This allows something cool like:

// javascript (backed by AJAX -> C++ RPC)

x = new PwmOut(2);
x.write(value);

Have a look. It might give you what you need.

Simon

27 Nov 2009

Thanks Simon!

I used your AJAX FudgeIt/javascript/mbed.js and got it to work right away. I'm good for now, though I still consider AJAX a workaround, since it leaves URI format incompatible with forms, which will cause some headaches. There will be RPC use cases for clients/browsers without javascript.

While fiddling with ajax, I noticed some errors in FireFox error console: "no element found http:/mbeddev/rpc/led1/write,1". I did some googling, and found out that Content-Type headers are not set in rpc responses. This causes FireFox to default to text/xml and it tries to translate as XML the response which comes empty from the RPC.

I came up with a fix that worked for me (not that there are no other ways to fix it - I think it is better to explicitly set Content-Type header in HTTPServer.HTTPRPC.h). Here's the post() js function (I also added optional outgoing message):

function post(url, message) {
    http = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
    if (!http) return false;
    http.open("POST", url, false);
http.overrideMimeType('text/plain');   // this resolves FireFox javascript errors
    http.send(typeof(message) != 'undefined' ? message : null);
    return http.responseText;
}

 

Back to URI format... I have an idea on how to address it without rpc redesign. Architecturally, it is HTTPServer.HTTPRPC that calls mbed.rpc, and URI format is specific to HTTPRPC only. It looks like URI in HTML GET format can be converted in HTTPRPC::clean() function, which already takes care of %20, ',' '+', etc. It actually seems quite easy, it does replacements in place. So the recipe is: 1. deal with '?' and '&', and 2. put spaces where the argument names are between '?'/'&' and '='. What do you think of that, Simon, Rolf?

28 Nov 2009 . Edited: 28 Nov 2009

Hi Ilya,

Ilya I wrote:
Back to URI format... I have an idea on how to address it without rpc redesign. Architecturally, it is HTTPServer.HTTPRPC that calls mbed.rpc, and URI format is specific to HTTPRPC only. It looks like URI in HTML GET format can be converted in HTTPRPC::clean() function, which already takes care of %20, ',' '+', etc. It actually seems quite easy, it does replacements in place. So the recipe is: 1. deal with '?' and '&', and 2. put spaces where the argument names are between '?'/'&' and '='. What do you think of that, Simon, Rolf?

As you've said you would like to have RPC calles over HTTP in a more URL like way I was thinking about that. But I was wondering what will happen if for example you've declared a function like:

int MyRPCClass::calc(int y, float z, int normalize);

and now you make your RPC call like:

http://mymbed.example.org/rpc/myobject/calc?y=5&z=6.4&normalize=360

You would get an RPC call like:

/myobject/calc   5   6.4           360
myobject.calc(5,6.4,360);

which would work fine. But everybody who would see the first call would think he can do something like this as well:

http://mymbed.example.org/rpc/myobject/calc?normalize=360&y=5&z=6.4

Which will not work as expected:

/myobject/calc           360   6.4   5
myobject.calc(360,6.4,5);

 

Therefore I think that our URLs look a little bit weired and you cannot see what the specific parameter are doing but you can have a look inside the class declaration and find out what they mean. Furthermore the RPC call over Serial, Zigbee, Bluetooth or what ever could carry a RPC call, is the same. So it might look a little bit blemish but it is working as it and you need no special knowledge about the RPC transport mechanism to get it working. I think this is more powerful than having a nice looking HTTP RPC interface.

But as Simon said, we are looking for an nice tool, which might bring a more powerful RPC implementation with proper reflection and this could introduce named parameter and the HTTP RPC calls would follow a more URL like scheme. Unfortunately I don't know what it will be and when ;-)

 

Cheers

Rolf

28 Nov 2009 . Edited: 28 Nov 2009

Hello again,

Ilya I wrote:
While fiddling with ajax, I noticed some errors in FireFox error console: "no element found http:/mbeddev/rpc/led1/write,1". I did some googling, and found out that Content-Type headers are not set in rpc responses. This causes FireFox to default to text/xml and it tries to translate as XML the response which comes empty from the RPC. I came up with a fix that worked for me (not that there are no other ways to fix it - I think it is better to explicitly set Content-Type header in HTTPServer.HTTPRPC.h). Here's the post() js function (I also added optional outgoing message):

Yeah, I've noticed that as well. I have a changed the type of all outgoing RPC "pages" to text/plain that should help in future.

 

Cheers

Rolf

29 Nov 2009

Rolf,

I did think about the order of arguments problem as well, and I totally agree with you that it will be not a complete solution to just "clean" URLs with spaces. In my question I'm not chasing expressive side of standard URLs. I'm after support of browsers without javascript, which you will find often in the wild (anyone germofobic or not can disable javascript on their PC for whatever reason), and I'm after use of regular HTML forms as GUI to MBED RPC.

But it is not a pressing issue for me (I have js enabled) - I just wanted to get it out there for discussion. Good to know that you are considering other RPC tools. I did not find simple but powerful and platform independent RPC tools when I was looking few years ago (did not want to go with M$ IDL) - I hope it changed recently. My other observation is that how the RPC calls are formatted is completely up to the HTTPRPC.h, so it can be anything, without overburdening base RPC classes. I can even craft my own version when I really need to. So you don't need to worry ;-)

 

Re: text/plain - great! Hope to see it in SVN soon. Will go check now.

 

Best,

 

 

08 Dec 2009

I was pondering on one more RPC encoding issue: How do I pass an arbitrary string?

It looks like any non-alphanumeric characters turn into delimiters... Can you give an example or a recipe of how to encode URLs so RPC function will get a complete, but fully decoded string?

 

Regards,

08 Dec 2009

Hi llya,

 

I am very interested in your myRPC software. On my navigator, the code you inserted (myrpc.h and myrcp.cpp) is unreadable because some char like '>' and '&' were transformed to html stuff. Could you publish your program so we get it ?

 

Thanks in advance !!

 

Stéphane

08 Dec 2009

Ilya,

I tried compiling your "myrpc.cpp" and it comes back with "lt" and "gt" not defined.

...kevin

08 Dec 2009

Stephane, Kevin,

I edited the earlier post with code (http://mbed.org/forum/post/1067/), so the code should be clean now. Sorry, I don't have a project to publish.

 

--

Ilya

08 Dec 2009

Getting closer Ilya,

It now compiles without error.  Now, I'm trying to get it to work.  I took the following from above:

http://mbeddevice/rpc/myrpc/new+LED1+bled1

http://mbeddevice/rpc/bled1/blink+10

> MBED blinks led1 10 times

 http://mbeddevice/rpc/bled1/delete

It does nothing for me.  I know that the HHTPserver works because I am successfully able to:  http://mbed-c3p0/rpc/led3/write+1

09 Dec 2009

I heard your requests, and spent few minutes resurrecting my test case program for publishing. Here it is:

 

http://mbed.org/users/iva2k/programs/gpdz3x


There are step-by-step instructions in main.cpp


Enjoy!

09 Dec 2009

It all makes sense now Llya

thanks,

...kevin

09 Dec 2009 . Edited: 09 Dec 2009

Thanks Llya,

I was interested in your source code to create my own RPC class. I must admit my C++ syntax is null. I am trying to modify your source to add a new gizmo on the HTTP Live Demo : a text zone in the web page asking the visitor's first name. I want to display it on the LCD screen with a URL like :

http://public_address/rpc/LCDWrite,visitorname

The html is ready and I modified the class like this :

 

myrpc::myrpc(PinName pin, const char *name) : Base(name), _pin(pin) {}

 

void myrpc::LCDWrite(char nom[16]) {

 

lcd.cls();

lcd.locate(0,0);

lcd.printf("Hello ! dear");

lcd.locate(0,1);

lcd.printf("%s",nom);

}

}

but run into loads of compiler errors. Is it mandatory to link a class to a pin (like LED1 in your code) ? I going to buy some C++ books this week-end ;-)

10 Dec 2009

First, you should realize that there is no straightforward way (as of today) to pass text/strings via RPC. Any space, comma or other non-alphanumerical character will terminate the string. See ticket #17.

I'd try to answer your question first, though I think it is not the right approach, and I explain second:

The "myrpc" example blinks the LED, so yes, it is needed for that function, and no, it is not needed for yours. Since you obviously want to implement another function, my guess is that you don't need a pin for LED, so it can be removed.

From what you posted it appears that you did not declare lcd object, hence all the compile errors. I assume you would want your lcd hooked up to some mbed pins, right? Then you probably want to let your object instantiation know which pin(s) to use, and the class should create the actual object somewhere. But to me it is a more difficult way... Here's why:

I guess that you have lcd class taken from some other example. I suggest it would be easier to start with working lcd example, then incrementally add rpc capability to it. It will be easier because "myrpc" class just demonstrates how to hook up an existing function to RPC. Nothing prevents you from making a good working lcd class RPC-able.

Here's how I recommend attacking the problem of RPC:

First look at the code between #ifdef MBED_RPC and #endif, make sense out of it. (Hint: code between #ifdef MBED_RPC and #endif describes class constructor and functions/arguments that are accessible via RPC, but it uses different syntax which fills in data structures. You will have to map C++ to these descriptions)

Then copy-paste that code into your lcd class .cpp and .h files as a template, then replace "myrpc" with "lcd" everywhere (your class name), remove "blink" function description since you don't need it and insert function(s) that you want, following the syntax of the "blink" example.

 

I realize that it will be tremendously difficult for a newbie to figure it all out before even understanding C++ (RPC and C++ introspection limitations are by far the most complicated concepts), so give C++ book a good study. You should fill very good about yourself when you'd make it work.

10 Dec 2009 . Edited: 10 Dec 2009

Hello Llya, I know I was not playing in my League ;-) Thanks for the homework, I know what I have to look for.

 

I was looking at RPC because I think it might be a good way to communicate orders between two mbeds for my SurveyorBot project. The live http demo was a prof of concept of how to send orders through a web page. I want to have one mbed on my network with HTTPServer to receive robot movement orders and display the Wifi webcam image. On the web page, there is a "move forward button" and I could sent with RPC a "MOVEFO" string that would be resent via XBee to the second mbed on the robot (I guess it will be stuck on a old roomba base, as you showed me). The second mbed would receive the string via the second Xbee Pro and process the order. As the webcam is on the robot, I could watch the robot move on the enbedded image in the web page on the first mbed.

I am sure there are several better ways to do it but I tryed to figure out a solution using the tools provided in the cookbook as I am far from creating my own. I guess if I can overcome the RPC stuff, the rest will be cakewalk. Or not ;-)

11 Jan 2010

I attacked the RPC & strings problem here HTTPServer and RPC

With the sample code from that notebook one can pass strings to RPC functions. It has a second example for dealing with URI queries.

03 Aug 2010 . Edited: 03 Aug 2010

I am having trouble with this project.  It works great, but only once.  After serving the page request (either file or  RPC) it does not work again.

I am talking about Ilya's RPC example project: http://mbed.org/users/iva2k/programs/gpdz3x

It responds to having the cable removed and reinserted, but even this does not allow you to request another page from it.  The browser returns (on the 2nd try):

 

Oops! Google Chrome could not connect to 192.168.0.3

Even still this project is a great help - Thanks, Ilya!

03 Aug 2010

Hi Cam, That's an old network stack implementation which had unresolved problems, and apparently it is not supported anymore. Try Donatien's new network stack.