User Guide for the PSLab Remote-Access Framework

The remote-lab framework of the pocket science lab has been designed to enable user to access their devices remotely via the internet. The pslab-remote repository includes an API server built with Python-Flask and a webapp that uses EmberJS. This post is a guide for users who wish to test the framework. A series of blog posts have been previously written which have explored and elaborated various aspect of the remote-lab such as designing the API server, remote execution of function strings, automatic deployment on various domains etc. In this post, we shall explore how to execute function strings, execute example scripts, and write a script ourselves.

A live demo is hosted at pslab-remote.surge.sh . The API server is hosted at pslab-stage.herokuapp.com, and an API reference which is being developed can be accessed at pslab-stage.herokuapp.com/apidocs . A screencast of the remote lab is also available

Create an account

Signing up at this point is very straightforward, and does not include any third party verification tools since the framework is under active development, and cannot be claimed to be ready for release yet.

Click on the sign-up button, and provide a username, email, and password. The e-mail will be used as the login-id, and needs to be unique.

Login to the remote lab

Use the email-id used for signing up, enter the password, and the app will redirect you to your new home-page, where you will be greeted with a similar screen.

Your home-page

On the home-page, you will find that the first section includes a text box for entering a function string, and an execute button. Here, you can enter any valid PSLab function such as `get_resistance()` , and click on the execute button in order to run the function on the PSLab device connected to the API server, and view the results. A detailed blog post on this process can be found here.

Since this is a new account, no saved scripts are present in the Your Scripts section. We will come to that shortly, but for now, there are some pre-written example scripts that will let you test them as well as view their source code in order to copy into your own collection, and modify them.

Click on the play icon next to `multimeter.py` in order to run the script. The eye icon to the right of the row enables you to view the source code, but this can also be done while the app is running. The multimeter app looks something like this, and you can click on the various buttons to try them out.

You may also click on the Source Code tab in order to view the source

Create and execute a small python script

We can now try to create a simple script of our own. Click on the `New Python Script` button in the top-bar to navigate to a page that will allow you to create and save your own scripts. We shall write a small 3-line code to print some sinusoidal coordinates, save it, and test it. Copy the following code for a sine wave with 30 points, and publish your script.

import numpy as np
x=np.linspace(0,2*np.pi,30)
print (x, np.sin(x))

Create a button widget and associate a callback to the get_voltage function

A small degree of object oriented capabilities have also been added, and the pslab-remote allows you to create button widgets and associate their targets with other widgets and labels.
The multimeter demo script uses this feature, and a single line of code suffices to demonstrate this feature.

button('Voltage on CH1 >',"get_voltage('CH1')","display_number")

You can copy the above line into a new script in order to try it out.

Associate a button’s callback to the capture routines, and set the target as a plot

The callback target for a button can be set to point to a plot. This is useful if the callback involves arrays such as those returned by the capture routines.

Example code to show a sine wave in a plot, and make button which will replace it with captured data from the oscilloscope:

import numpy as np
x=np.linspace(0,2*np.pi,30)
plt = plot(x, np.sin(x))
button('capture 1',"capture1('CH1',100,10)","update-plot",target=plt)
Figure: Demo animation from the plot_test example. Capture1 is connected to the plot shown.
Resources

Continue Reading

Creating an Elementary Oscilloscope in PSLab’s Remote Framework

The last couple of blog posts explained how we could put together the versatility of ember components, the visual appeal of jqplot, the flexibility of Python Flask, and the simplicity of Python itself in order to make simple scripts for PSLab that would could be run on a server by a remote client anywhere on the web. We have also seen how callbacks could be assigned to widgets created in these scripts in order to make object oriented applications. In this blog post, we shall see how to assign a capture method to a button, and update a plot with the received data. It will also demonstrate how to use ember-lodash to perform array manipulations.

Specifying the return data type in the callback success routine

For a more instructive write-up on assigning callbacks, please refer to these posts .

Whenever the callback assigned to a button is a function that returns an array of elements, and the target for the resultant data is a plot, the stacking order of the returned array must be specified in order to change its shape to suit the plotting library. The default return data from a capture routine (oscilloscope) is made up of separate arrays for X coordinate and Y coordinate values. Since JQplot requires [X,Y] pairs , we must specify a stacking order of ‘xy’ so that the application knows that it must convert them to pairs (using lodash/zip)  before passing the result to the plot widget. Similarly, different stacking orders for capture2, and capture4 must also be defined.

Creating an action that performs necessary array manipulations and plots the received data

It can be seen from the excerpt below, that if the onSuccess target for a callback is specified to be a plot in the actionDefinition object, then the stacking order is checked, and the returned data is modified accordingly

Relevant excerpt from controllers/user-home.js/runButtonAction

if (actionDefinition.success.type === 'update-plot') {
  if (actionDefinition.success.stacking === 'xy') {
    $.jqplot(actionDefinition.success.target, [zip(...resultValue)]).replot();
  } else if (actionDefinition.success.stacking === 'xyy') {
    $.jqplot(actionDefinition.success.target, [zip(...[resultValue[0], resultValue[1]]), zip(...[resultValue[0], resultValue[2]])]).replot();
  } else if (actionDefinition.success.stacking === 'xyyyy') {
    $.jqplot(actionDefinition.success.target, [zip(...[resultValue[0], resultValue[1]]), zip(...[resultValue[0], resultValue[2]]), zip(...[resultValue[0], resultValue[3]]), zip(...[resultValue[0], resultValue[4]])]).replot();
  } else {
    $.jqplot(actionDefinition.success.target, resultValue).replot();
  }
}

 

With the above framework in place, we can add a plot with the line plt = plot(x, np.sin(x)) , and associate a button with a capture routine that will update its contents with a single line of code: button(‘capture1’,”capture1(‘CH1’,100,10)”,”update-plot”,target=plt)

Final Result

The following script created on the pslab-remote platform makes three buttons and plots, and sets the buttons to invoke capture1, capture2, and capture4 respectively when clicked.

import numpy as np
x=np.linspace(0,2*np.pi,30)
plt = plot(x, np.sin(x))
button('capture 1',"capture1('CH1',100,10)","update-plot",target=plt)

plt2 = plot(x, np.sin(x))
button('capture 2',"capture2(50,10)","update-plot",target=plt2,stacking='xyy')

plt3 = plot(x, np.sin(x))
button('capture 4',"capture4(50,10)","update-plot",target=plt3,stacking='xyyyy')

 

 

 

 

 

 

 

 

 

 

 

 

Resources

 

Continue Reading

Enhancing the Functionality of User Submitted Scripts in the PSLab-remote framework

The remote-lab framework of the pocket science lab enables users to access their devices remotely via the internet. Its design involves an API server built with Python-Flask and a webapp that uses EmberJS. This post is the latest in a series of blog posts which have explored and elaborated various aspect of the remote-lab such as designing the API server and testing with Postman, remote execution of function strings, automatic deployment on various domains etc. It also supports creating and submitting python scripts which will be run on the remote server, and the console output relayed to the webapp.

In this post, we shall take a look at how we can extend the functionality by providing support for object oriented code in user submitted scripts.

Let’s take an example of a Python script where the user wishes to create a button which when clicked will read a voltage via the API server, and display the value to the remote user. Clearly, an interpreter that only provides the console output is not enough for this task. We need the interpreter to generate an app structure that also includes callbacks for widgets such as buttons, and JSON objects are an obvious choice for relaying such a structure to the webapp.

In a nutshell, we had earlier created an API method that could execute a python script and return a string output, and now we will modify this method to return a JSON encoded structure which will be parsed by the webapp in order to display an output.

Let’s elaborate this with an example : Example.py

print ('testing')
print ('testing some changes..... ')
print_('highlighted print statement')

 

JSON returned by the API [localhost:8000/runScriptById] , for the above script:

{"Date": "Tue, 01 Aug 2017 21:39:12 GMT", "Filename": "example.py", "Id": 4,
 "result": [
  {"name": "print", "type": "span", "value": "('testing',)"},
  {"name": "print", "type": "span", "value": "('testing some changes..... ',)"},
  {"class": "row well", "name": "print", "type": "span", "value": "highlighted print statement"}
  ],
"status": true}
Screenshot of the EmberJS webapp showing the output rendered with the above JSON

Adding Support for Widgets

In the previous section, we laid the groundwork for a flexible platform. Instead of returning a string, the webapp accepts a JSON object and parses it. We shall now add support for a clickable button which can be associated with a valid PSLab function.

An elementary JS twiddle has been made by Niranjan Rajendran which will help newbies to understand how to render dynamic templates via JSON objects retrieved from APIs. The twiddle uses two API endpoints; one to retrieve the compiled JSON output, and another to act as a voltmeter method which returns a voltage value.

To understand how this works in pslab-remote, consider a one line script called button.py:

button('get voltage',"get_voltage('CH1')")

The objective is to create a button with the text ‘get voltage’ on it , and which when clicked will run the command ‘get_voltage(‘CH1’)’ on the API server, and display the result.

When this script is run on the API server, it returns a JSON object with the following structure:

{"Date": "Tue, 01 Aug 2017 21:39:12 GMT", "Filename": "example.py", "Id": 4,
 "result": [  {"type":"button","name":"button-id0","label":"get_voltage","fetched_value":"","action":{"type":"POST","endpoint":"get_voltage('CH1')","success":{"datapoint":'result',"type":"display_number", "target":"button-id0-label"}}},
  {"name": "button-id0label", "type": "label", "value": ""},
  ],
"status": true}

The above JSON object is parsed by the webapp’s user-home template, and a corresponding button and label are generated. The following section of code from user-home.hbs renders the JSON object

{{#each codeResults as |element|}}
  {{#if (eq element.type 'label')}}
    <label  id="{{element.name}}" class="{{element.class}}">{{element.value}}</label>
  {{/if}}
  {{#if (eq element.type 'button')}}
    <button id="{{element.name}}" {{action 'runButtonAction' element.action}}>{{element.label}}</button>
  {{/if}}
{{/each}}    

An action was also associated with the the created button, and this is the “get_voltage(‘CH1’)” string which we had specified in our one line script.

For the concluding section, we shall see how this action is invoked when the button is clicked, and how the returned value is used to update the contents of the label that was generated as part of this button.

Action defined in controllers/user-home.js :

runButtonAction(actionDefinition) {
  if(actionDefinition.type === 'POST') {
    Ember.$.post('/evalFunctionString',{'function':actionDefinition.endpoint},this,"json")
      .then(response => {
        const resultValue = Ember.get(response, actionDefinition.success.datapoint);
        if (actionDefinition.success.type === 'display_number') {
           Ember.$('#' + actionDefinition.success.target).text(resultValue.toFixed(3));
        }
      });
  }
}

The action string is passed to the evalFunctionString endpoint of the API, and the contents are mapped to the display label.

Screencast of the above process
Resources:

Continue Reading
Close Menu