Cool ! - Running a Python script - watching the IK3D Render with Logging statements scrolling by ! 
On the online demo ! - http://demo.myrobotlab.org:8888/#/main

 


(build was chatty as a 3 headed dog! - now its tiny and cute !)

Hello !

This is to describe some framework additions I recently put into the WebGUI.  MaVo and kwatters probably would be the most interested, but I would welcome anyones comments or questions

The goal was to reduce the amount of necessary "binding" code in the Controller among other refactoring chores.

The only Service GUI currently refactored is the PythonGUI.  The changes in the framework can live side by side with the previous way we have done things. But my preference is to quickly refactor the other existing service guis (Clock, ProgramAB, etc) - it will mostly be deleting uneeded code, and making sure method names on the html == method names on the Java service (a good thing) :)

Ok, 
lets start with the first half of PythonGUI.js (oh names have changed for the js controllers in that they == names of the classes in Java - there is no more lowercasing the names)

First, you'll notice most of the "code" is just comments.
The important part is the creation of a message interface - line 11.
This effectively gives you a direct connection to your service.  Any method it has you can call it in the same manner as you might have in the Swing gui.  e.g. msg.send('foo','param bar');

All the rest is comments, or Python specific stuff until line 30.
updateState(service) - is a method which should take the services recently changed state and update the appropriate display components.  In this particular example, $scope.editor.setVaule(service.currentScript.code) - puts the code currently in the Java service into the display editor.  And $scope.tabName = service.currentScript.name will display the filename.

The controllers primary purpose is to surface relevant data regarding the service to the person viewing it.
You might be wondering when updateState gets called...   lets look at the other half of the file.

Just as before the onMsg() method is the "callback point" for all data sent from MRL to the Angular client. This is where this services data all comes in.  Any subscribed method's data when call will appear in this onMsg() method.

A service typically returns two types of data.  It can return data from some resource it maintains, like a serial port, or a video stream, OR it can return itself.  Returning itself can be useful in displaying various state information.  For example, is a serial service connected or not?  Is a servo connected or not ? etc...

When a service thinks its internal state has changed, and its important to tell everyone, it fires a method called broadcastState this broadcasts itself to anyone who has subscribed to the 'publishState' method.
This is so common and structured, you (as a coder of a service gui) does not have to put those parts in.  Its automatically done by the framework.

Another significant change is related to what I call "binding code" - which is controller code created which binds control methods to their equivalent Java counterparts.  The  above commented out section on line 82 is an example.  As the PythonGui code grew in functionality there would be many more of these bindings.  Implementing them would be laborious, manual, and troublesome work.  Developers would change names slightly, or parameters, or the Java method would change and break and this code would break .. and it would just get messy !  Some methods would be created, and others left .. overall code drudgery...

Now, all of the Java's public methods are created and available immediately after the controller is created ! .. Yes ALL of the methods ! :)  Its all auto-generated by magic framework elves, so NO binding code is necessary.  Instead you can go directly to the html and do this.

Ya - there is a Python.loadScriptFromFile(String) method in MRL and that's all the code it takes to call it with a parameter.  Granted the parameter is 'hardcoded' to 'test.py' - but don't get hung up by that - it could just as easily be a scope parameter which the user selects.  There is autogenerated scafolding underneath which gets this event to send it appropriately to MRL.

The  only thing needed for all this to work are just 2 framework method calls:

1. At the beginning of your controller call this

This initializes the messaging interface for this controller - you can use it as soon as you create it to send commands to your service.

2. and at the end call this

It binds the necessary objects together, and begins to construct the auto-generated message bindings.

 

Here are some other developer friendly tricks :

This URL will give you a quick description of all the methods of a service - would be nice to have it surface at a button click in the gui, but if you have a JSON formatting browser it works as is pretty well.
http://localhost:8888/api/services/runtime/getMethodMap 

Both the service & all its methods descriptions are "available" in the html
Most of you are already familiar with {{service}} , now you can get the method map too {{methodMap}}

Additionally the service api is mostly working - to get current service state you can look at it in JSON this way

http://localhost:8888/api/services/runtime/  for any running service 

 

MaVo

8 years 6 months ago

All in all I think this makes a huge step in the right direction.

I still have some notes on it:

The biggest one is:

mrl.createMsgInterface() requires the scope to be injected - this is not a great strategy in Angular as you make the whole system a bit more fragile & you can't easily seperate the two parts (also under the aspect of code-reusement & -maintenance).

By roughly looking through createMsgInterface(), I think it stores the scope and puts the java-methods on it. If this is all of the reason to get the scope in there, I think it may be better to leave the scope in the controller and just use two lines for it (scope.msg = mrl.createMsgInterface(...); & var msg = scope.msg; ). This also makes it a little bit more stronger, but this can be neglected.

It's more a design thing than something else.

 

Some smaller notes (not so important ones):

It may be useful to move msg.subscribe(this); out of [Python]Gui.js, altough this would mean that this isn't compatible to the "old way" anymore. Also it looks like "this" is needed just for this.onMsg which makes it seem more than what is needed.

If actually using scope.msg = mrl.createMsgInterface(...);, this also can be moved out of the controller, but not if just using var msg = ... - right now createMsgInterface put's it on scope (decribed above).

Mebbe it may (under certain circumstances : ) ) also be useful to split msg up a bit, but I'm not going to suggest that  ... (yet).

 

But I like that idea a lot, reduces the controller code by quite a bit and removes "more or less" unneccessary wrapper methods.

Please have more ideas like this!

Interesting idea - scope.msg = mrl.createMsgInterface(...); & var msg = scope.msg; 

With the eval I decided to use actual method names in the method which were built ... e.g. msg._interface.someMethod('param0', ...) , I felt it probably would be better if they were 'exactly' the same .. so the dynamically built ones would  have 1 more parameter and be exactly the same as the ones in the controller .. e.g. msg._interface.send('someMethod', 'param0' ..)   

I think there was some excitement on being able to proxy the method as it actually existed in java SomeService.someMethod('param0',...)    

I'll look deeper into this..

(smaller notes)

msg.subscribe(this) - was the idea that message subscriptions would be handled here .. additionally the onMethodMap needs to process to build out the dynamic methods - a subscription is needed before that - so it has to be done at some point....

Initially I really wanted the msg.subscribe(this); & msg = mrl.createMsgInterface(..) out of the controller - but doing some research I did not see where you could get a handle on the controller as soon as it was created...    maybe you know how.  I gave up, supposing Angular has control when the controller is created - and your never given the opportunity to get a reference to it, nor subscribe something to the controllers this.onMsg.  I asked a guy at work work and he said, it could be done with JQuery but it would be a hack ... so I basically gave up and left the 2 statements in...

 

 

 

"Interesting idea - scope.msg = mrl.createMsgInterface(...); & var msg = scope.msg;"

-> was a suggestion to avoid to pass in the scope (I really want that !)

-> requires two lines (one could be moved outside of the controller)

 

I don't completly understand what you're trying to say in the second & third paragraph, but I figure you will do the right thing!

 

It is possible (at least I think that, not tried), to put msg on scope before the controller ( will still need a "var msg = scope.msg;" ) and execute something like "msg.subscribe(this)" after the controller has run (maybe even only "msg.subscribe(this.onMsg)", not going hell on that for now).

Flow would look something like this:

scope.msg = mrl.createMsgInterface(...)

[controller start]

     var msg = scope.msg;

     <other controller stuff>

[controller end]

msg.subscribe(this)

 

If scope isn't passed in createMsgInterface, createMsgInterface can be moved out of the controller.

msg.subscribe doesn't require any special changes to be moved.