The application note titled "Basic Interaction from a Custom Web Page" desicribes a simple form of interaction (starting a named task). However, some applications ned a tighter integration, with direct read/write. and rea-ltime access to the inner workings of Blocls. This application note shows how such advanced integration can be accomplished, including:
As a bonus point, this application note also shows how you can make a call out to another web service (here a Google charts service for generating a QR code), returning the result to your custom Blocks application.
You need a computer with a running blocks server and a web browser to run this application note. Here's the Blocks root for this application note. See the the general setup section for details on downloading and installation. Once that general setup is done, follow these steps to run the application note:
Now choose "Block1" or "Block2" on the "Play block on" dropdown. You test spot window will show the selected block.
The text under the dropdown, titled "Current playing block" shows the name of the currently playing block. Note that this information is obtained "live" from Blocks, which you can see by dragging the other Block to the Spot in the editor window, which then immediately updates the block name on the custom web page.
Type some text into the text area titled "Text to append", then click the "Append" button. Now look into the script/files directory inside the blocks root installed above, and you'll find a text file named test.txt, with the text you just typed appended.
Finally, press the "Generate" button. After a brief pause, a QR code appears below the field with the text entered into the field next to the Generate button. This image is stored in the file located at public/custom/qr.png, from where the custom web page retrieves it.
This example consists of the following components:
The server-side user script performs a number of functions:
The HTML web page as well as all the scripts have ample comments, explaining what's going on, so please refer to those for full details.
While the Websocket API provides a very efficient method for observing and manipulating individual property values, you sometimes need to pass more complex data to the server or back to the client. There are two mechanisms for doing so:
Both of these methods are described in detail below.
As mentioned above, the file at public/pubsubpeer-test/js/pub-sub-peer.js implements the websocket API allowing a custom web page to talk directly to Blocks. This uses the same bidirectional, high-performance method as used by Blocks itself for all buttons, sliders and other controls.
Following is a description of each public API function. See examples and more details of how these calls are used in the main.js script file.
Top level function for establishing a connection to Blocks. Use it like this:
/** * Instantiate a PubSubPeer that auto-connects to the blocks server * using a websocket. * * The PIXILAB Websocket API is defined in the pub-sub-peer.js script, which * attaches its root object as a global (window) variable named PIXILAB_BLOCKS. */ const pubSubPeer = new PIXILAB_BLOCKS.PubSubPeer( onServerConnectionChange );
This call returns a pubSubPeer that manages the websocket connection to the server.
The example shown above assumes that the custom web page is served by the Blocks server. If it is served from some other server, you must add a second parameter that is the URL for accessing the websocket endpoint on your Blocks server, like this;
const pubSubPeer = new PIXILAB_BLOCKS.PubSubPeer( onServerConnectionChange, "ws://<nameOrIp><:nonStandardPort>/rpc/pub-sub" );
Replace <nameOrIp> with the IP number or resolvable name to your Blocks server. If you're running on a non-standard port, append the port number separated by a colon. Hence, if your Blocks server rus on 10.1.0.10 using the standard port, the call would look like this:
const pubSubPeer = new PIXILAB_BLOCKS.PubSubPeer( onServerConnectionChange, "ws://10.1.0.10/rpc/pub-sub" );
The first parameter of the PubSubPeer constructor call is an optional callback function, in the example above called onServerConnectionChange, which in the sample code looks like this:
/** * The server connection state has changed. Here we just log that event, but you * may want to use it to indicate the server is temporarily offline, or similar. */ function onServerConnectionChange(connected) { console.log(connected ? 'Connected to server' : 'Disconnected from server'); }
The argument passed to this function is true when the connection is established and false if the connection is lost. The example code merely logs a message when this function is called.
This function allows you to set any property in your Blocks server (unless it's protected by a role restriction). it takes two parameters:
The type of the second parameter must match the value type of the property. For instance, if the property is numeric (such as the volume level of a Spot), you must pass a number here.
This is similar to the set function, and takes the same two parameters. When used with a numeric property, the value you specify will be added to the current value. Pass a negative value to subtract. When used with a string property, the value will be appended (concatenated) to the current string value.
The add methid is often simpler for such incremental adjustments than maintaining a subscription to the value in order to do the calculation on the client side based on the current value and then using the set function to change the value.
Subscribe to the value of the specified property. It accepts the following parameters:
The subscribe function returns the current value of the property if already known. If not, it returns undefined and you'll be notified by the dataReceived function being called once the value arrives.
You should pay attention to any initial value returned by the subscribe function, treating it the same way as any value change provided through the callback function. See main.js for an example of how this can be accomplished.
This function allows you to terminate any value subscription initiated by the subscribe function if you're no longer interested in changes of a property. It takes the following parameters:
The main.js example shows how to use this function, as triggered by the Unscubscribe button.
The example script shows how to use a named websocket channel to pass a JSON data object consisting of a filename and a message to the server. The server then writes the message to a file with the specified name. Such a mechanism could be used to implement some custom logging mechanism, for instance.
The receiving end of the channel is in this example managed by the server side script. This is specified in main.js as 'Script.user.CustomWebPageCommunicator', which indicates that the channel is owned by the user script named CustomWebPageCommunicator, which is the server-side user script mentioned above. The specific channel name used in the example is 'append-to-file'. You can see this channel being established by the CustomWebPageCommunicator user script like this:
/** * Establish a websocket channel named "append-to-file", accepting * JSON data representing a AppendToFileData object from the web peer. * Note that such channels aren't restricted to JSON data, but can handle * any text. */ this.establishChannel( "append-to-file", data => { /* Parse the data as it is from its JSON string which is expected to be a AppendToFileData object. */ const appendToFileData: AppendToFileData = JSON.parse(data); // Pass the data to doAppendTextToFile, appending it to a file. return this.doAppendTextToFile(appendToFileData); }, );
To send data to this channel, the custom web page first calls PubSubPeer.channelName to obtain the full system name of the channel and then calls pubSubPeer.send to send the data through the channel.
This function takes the following parameters:
The function returns the full system name of the channel which is then passed to pubSubPeer.send to send the data to the channel.
This function takes the following parameters:
You can also use a plain HTTP POST request to make calls from a custom web page to a server-side script. This is often simpler, as it doesn't use a websocket connection, and therefore doesn't require the use of the more complex pub-sub-peer.js API. To use this method, implement a public function in your user script marked by a @resource() annotation. Here's the relevant snippet from the CustomWebPageCommunicator user script:
/** * Accept an HTTP POST at /rest/script/invoke/CustomWebPageCommunicator/generateQRImage. * This "screenshots" a web page containing a QR code and then moves the image to a folder * accessible from our custom app. * * See @resource() for details on how the endpoint URL is structured. */ @resource() generateQRImage(data: QRData): Promise<string>
Note the following details:
The generateQRImage function used in the example CustomWebPageCommunicator script shows an example of how a deferred result can be returned by means of a promise. Here required since the Blocks server in its turn requires a number of time consuming steps:
Once these three steps are done, the promise is resolved with the name of the generated image file, now available under /public on the Blocks server. If any error occurs, the promise is instead rejected, passing an error code back as the HTTP POST request's result. Upon receiving the result, the custom web page's main.js script proceeds by displaying the resulting image. Note that the fetch function, called to make the HTTP request from the client side, also returns a Promise object, eventually resolved with the result of the operation once its then function is called.