This is an old revision of the document!
User Script Case Study
Here's a rather interesting case study that shows how a user script can be utilized as a bridge between a position tracking sensor and the scroll position of a scroll block. The project involves a 5 meter long wall with a printed "wallpaper". In front of this wall hangs a vertically oriented display. This display run on a track, allowing it to be moved back and forth along the wall. The display shows the section of the wall it's placed in front of, and allows a visitor to interact with the content at that position.
To interact with another part of the wall, the visitor can push the screen to the desired position. Hence, the screen act as an interactive window or looking glass onto the static image behind it. The position tracker reads a barcode along the track, sending the position readout to Blocks. Blocks translates this to a scroll position, applying the result to the Display Spot connected to the display.
The sensor is a rather small device, mounted behind one of the brackets used to attach the display on the track. There are many types of position sensors that work in similar ways. This one – a Leuze ODSL 8 – reads the position with millimeter accuracy from a barcode strip taped to the track.
The sensor uses a laser to read the barcode strip, streaming the resulting data over a serial (RS-232) connection, which in its turn connects to Blocks through a MOXA nPort serial-to-ethernet adapter. The MOXA nPort is added to Blocks under Manage as a Network TCP device. To save some time in getting the complete solution working, no driver was written for this device. Instead, the code that received data from the sensor was rolled into the user script. Another option could have been to write a driver that published a property corresponding to the most recent position readout. That would have simplified the user script at the expense of writing both a driver and a user script.
Here's the complete script used to read the position data, scale it as appropriate and apply the result to the scroll position of the display spot. The script is saved in a file named LeuzeScript.ts in the script/user directory of the Blocks root. It compiles to a file named LeuzeScript.js, in that same directory. This is handled automatically by the editor used to create and edit scripts.
/* Blocks user script reading data from a Leuze ODSL 8 barcode distance sensor. The device has an RS232 comms port, which is connected to the network through a MOXA nPort interface. */ // Import referenced system objects import {Script, ScriptEnv} from "system_lib/Script"; import {NetworkTCP, Network} from "system/Network"; import {Spot, DisplaySpot} from "system/Spot"; /** A user script must have a class with the same name as its containing file (minus the .ts suffix). */ export class LeuzeScript extends Script { networkPort: NetworkTCP; // Network TCP connection to device lastPosition: number; // Last positionreceived form device timeout: CancelablePromise<void>; // Used to re-init stalled comms /** A user script must have a constructor that takes a single ScriptEnv parameter, which is passed to super. */ public constructor(env : ScriptEnv) { super(env); // Get the network TCP connection used to talk to the device. this.networkPort = <NetworkTCP>Network['Leuze over Moxa']; if (this.networkPort.connected) // Handle connected BEFORE script starts this.doWhenConnected(); // Handle connection state changes AFTER script started this.networkPort.subscribe('connect', (sender, message) => { // console.log("Connection state", message.type); if (message.type === 'Connection' && this.networkPort.connected) this.doWhenConnected(); }); // console.log("LeuzeScript instantiated"); } /** Hook up event handler to receive data form reader. Get comms going. */ doWhenConnected() { this.networkPort.subscribe('textReceived', (sender, message) => this.dataReceived(message.text) ); this.initComms(); this.resetTimeout(); } /** Tell the barcode reader to please start sending data. These command strings are according to the device's documentation. */ initComms() { if (this.networkPort.connected) {} this.networkPort.sendText('\x02M+', '\r\n'); this.networkPort.sendText('\x02MMT0100', '\r\n'); } } /** Look for silence from the device, and re-init communication if that happens. Call when data is received form the device to reset and restart this timer. */ resetTimeout() { if (this.timeout) // Had a pending timer this.timeout.cancel(); // Kill it this.timeout = wait(2000); // Wait at most this many mS this.timeout.then(() => { // Re-init comms if ever times out this.timeout = undefined; // console.log("Re-inited comms"); this.initComms(); this.resetTimeout(); // Restart timer }); } /** New data received from device. Attempt to parse out number and scroll to that position. */ dataReceived(posData: string) { // console.log(posData); // Skip leading STX character, then parse remainder as a number var position = parseInt(posData.substr(1)); /* Device sends other data occasionally, causing the parsing to fail. So we need to verify that parseInt indeed returned a Number. */ const posType = typeof position; if (posType === 'Number' && this.lastPosition !== position) { // Was indeed a number, and different from last position received this.lastPosition = position; // console.log("New position", position); // Normalize maximum position readout to 1, for scroller use position = position / 5000; // Lookup the Spot by name and scroll it to that position const spot = <DisplaySpot>Spot['43inch Portrait']; spot.scrollTo(position); } this.resetTimeout(); // Got data, so reset silence timeout } }
The script has plenty of comments explaining what's going on, but here are a few additional highlights.