Virtual Script Port is the unique and powerful capability offered by Virtual Serial Port Tools. It allows the user to create a virtual serial port, “backed up” by a custom script, written in TypeScript or JavaScript.
A script that implements a virtual serial device logic (called device script further in this document) must be written in TypeScript or JavaScript (ES6) and must define a class that implements the IScriptDevice interface and define a createDevice or createDeviceAsync global function. This global function is called each time a virtual script port is opened by an application. This function must create an instance of the port class and return a reference to it.
Device creation function is allowed to perform some initialization and is passed an optional initialization value string. If it needs to perform an asynchronous initialization, use createDeviceAsync
function, otherwise, use createDevice
function.
VSPT API ISerialPortLibrary.createScriptPort method is used to create a virtual serial script port. Set an optional script initialization parameter using the IScriptPortDevice.initializationValue. Change the default script execution logging folder by setting the IScriptPortDevice.logPath property.
Once you have your port script ready, set it by calling IScriptPortDevice.setScriptFile or IScriptPortDevice.setScriptText method.
The library compiles and validates the script and stores it internally. If setScriptFile
method is successful, the original script file will never be read again.
This is done for security reasons: by storing the script internally, VSPT protects the created virtual script port from unwanted script updates.
If you make changes to a script file and want those changes applied, call setScriptFile
method again.
If this method returns false
, observe the IScriptPortDevice.validationErrors property to get a list of validation errors. This method may also throw an exception which usually means that while there were no syntax errors in a script, it still failed additional validation, like missing createDevice
or createDeviceAsync
global functions.
Below is the minimal device script:
///<reference path="hhdscriptport.d.ts" />
class MyDevice implements Port.IScriptDevice { // 1
// The following method is optional, see below
public async onSend(data: Uint8Array): Promise<void> { // 2
// ...
}
// The following method is optional, see below
public setParam(name: string, value: string): void { // 3
// ...
}
}
// Either createDevice or createDeviceAsync global functions must be defined. If both are defined, createDeviceAsync will be used
async function createDeviceAsync(initializationValue?: string): Promise<Port.IScriptDevice> { // 4
return new MyDevice;
}
// Either createDevice or createDeviceAsync global functions must be defined. If both are defined, createDeviceAsync will be used
function createDevice(initializationValue?: string): Port.IScriptDevice { // 5
return new MyDevice;
}
The following list describes the numbered elements in the sample above:
Script logic must be incorporated into a class which implements a Port.IScriptDevice interface.
An optional IScriptDevice.onSend method is used in the push model.
An optional IScriptDevice.setParam method, if present, allows the external code to send arbitrary data to the script, either by calling IScriptPortDevice.setScriptParam method or by sending a custom IOCTL_SCRIPTPORT_SET_PARAM device I/O request to the opened port.
createDeviceAsync
global function, if present, will be called each time the virtual script port is opened. It is passed a copy of the IScriptPortDevice.initializationValue. This function must create an instance of a device class and return it.
At least one of createDeviceAsync
or createDevice
global functions must be defined, otherwise, a validation error occurs. If both functions are defined, only createDeviceAsync
will be used.
createDevice
global function, if present, will be called each time the virtual script port is opened. It is passed a copy of the IScriptPortDevice.initializationValue. This function must create an instance of a device class and return it.
At least one of createDeviceAsync
or createDevice
global functions must be defined, otherwise, a validation error occurs. If both functions are defined, only createDeviceAsync
will be used.
This script execution context is created after the port is opened and is destroyed as soon as it is closed. Any global variables you create will only live this long.
That is, you cannot store any state between port opening attempts. However, the execution context is guaranteed to exist until the port is closed by the application. Anyway, prefer storing any state in the created device script object, not on a global scope.
Device script can use one of the following data flow models:
onSend
method.The virtual serial port driver chooses the model by determining the presence of the IScriptDevice.onSend method in the device script. The sections below provide detailed explanation on flow models:
Whenever an application sends any data to the virtual serial port, device script's IScriptDevice.onSend method is called with a copy of data sent. onSend
method returns a promise. Once this promise is resolved, virtual serial port immediately discards sent data and completes the original application's Write request.
Loopback Serial Port (Push Model).ts
sample script, included in the default product installation, illustrates the usage of a push model:
///<reference path="hhdscriptport.d.ts" />
/**
* This script implements a simple "loopback" virtual serial device
* It immediately returns everything that has been sent to it
*
* This version illustrates the usage of so-called "push" model
* In this mode, virtual serial port driver calls an onSend method with data sent by application to a serial port
*/
class LoopbackSerialDevicePush implements Port.IScriptDevice {
public async onSend(data: Uint8Array): Promise<void> {
// This script emulates an echo device, we simply provide the same data back
port.provideReceivedData(data);
}
}
function createDevice(): Port.IScriptDevice {
return new LoopbackSerialDevicePush;
}
Whenever an application sends any data to the virtual serial port, it is put into the internal output queue. Application's Write request is completed immediately. At any time, a device script may query for the whole or part of the output queue by calling the IPort.getSentData method. This method returns a promise that is resolved with a copy of the output queue, or a part of it.
Loopback Serial Port (Pull Model).ts
sample script, included in the default product installation, illustrates the usage of a push model:
///<reference path="hhdscriptport.d.ts" />
/**
* This script implements a simple "loopback" virtual serial device
* It immediately returns everything that has been sent to it
*
* This version illustrates the usage of so-called "pull" model
* In this mode, a port implementation calls the virtual serial port driver's getSentData function that returns a Promise.
* This promise is resolved with a byte array sent by application to a serial port
*/
class LoopbackSerialDevicePull implements Port.IScriptDevice {
constructor() {
this.run();
}
async run() {
while (true) {
var sentData = await port.getSentData();
port.provideReceivedData(sentData);
}
}
}
function createDevice(): Port.IScriptDevice {
return new LoopbackSerialDevicePull;
}
Data model only governs the processing of the data sent by an application to a port. Whenever the device script needs to emulate received data, it calls the IPort.provideReceivedData method with the data it wants to make available for application. Next time application attempts to read from the serial port it gets a copy of those data.
Virtual script port provides the following functionality to the device script:
port
which implements the IPort interface. It represents the virtual serial port itself and provides methods for reading and writing data.fs
which implements the FS.IFileManager interface. It allows the device script to create and delete folders and files, open files for reading and writing and read and write data from them. Device script may also enumerate the contents of any folder.net
which implements the Net.INetworkManager interface. It allows the device script to create TCP and UDP sockets and use them to perform network communication.http
which implements the Http.IHttpClient interface. It allows the device script to send all kinds of HTTP requests.Device script runs in a controlled and secure environment. Therefore, external access to a running script is limited. The only debugging facility available for a device script is a global log method.
By default, all log files are created in a %TEMP%\vspt_script_logs
folder. Note that %TEMP%
environment variable is expanded under the context of a System
account and is usually resolved into c:\Windows\TEMP
. Log file folder location may also be changed by setting the IScriptPortDevice.logPath property.
A log file is only created if a device script makes a call to a log
method, or throws an unhandled exception. The log file name follows this scheme:
vspt.YYYY-MM-dd.hh-mm-ss.PID.log
Abbr | Description |
---|---|
YYYY | Year |
MM | Month |
dd | Day |
hh | Hour in 24-hours format |
mm | Minutes |
ss | Seconds |
PID | Process ID of a port opening process |