General JavaScript extensions

Device status information

okular.DevicesStatus()

The function okular.DevicesStatus() will return a map of currently available client devices and their current status maps.

Example:

{
    "c04addea-54c2-4982-b27e-476d34e225ce":{ "RSSI" : 60, "Charger": 3, "BatteryLevel": 91, "Temperature": 24},
    "32021db4-74ba-5983-f27a-157d44e235ac":{ "RSSI" : 70, "Charger": 1, "BateryLevel": 13, "Temperature": 25},
  "27003600-0d47-3130-3830-333200000000":{"Battery":93,"Charger":3, "RSSI":61,"SleepMilis":8870,"Temperature":25,"TouchLastRelease":0}
}

Access information about the Visionect Server

var okular.nm_host

Stores the IP address of the Network Manager. This is important if you wish to access services from the Network Manager.


var okular.session_uuid

Stores the device UUID. The device UUIDs are globally unique (based on a device’s micro-controller UUID).


Trigger beep sounds from JavaScript

Warning

This feature only works with certain Visionect client devices, when coupled with an appropriate version of firmware.

Application developers can trigger different beep sounds directly from JavaScript by using the following function call:

okular.Beep(argument)

The following arguments are valid:

  • okular.BeepSoft
  • okular.BeepHard

The first one releases a silent, soft beep and the second one a louder beep.

//Example use:
okular.Beep(okular.BeepSoft)

Controlling the frontlight

Warning

This feature only works with certain frontlight-equipped client devices. Some devices might require additional configuration via the CLI to enable remote control of LEDs.

If the device has frontlight LEDs installed, it’s possible to control their brightness from JavaScript by using:

okular.SetFrontlight(value) // Valid argument values are 0 - 100

// Example use:
okular.SetFrontlight(60);

This will set the frontlight brightness to 60%. To switch off the frontlight, set the brightness to 0.

Onscreen keyboard

Warning

This feature only works on first generation Visionect 6” Signs. Newer client devices should implement the onscreen keyboard by using the JavaScript library.

A Visionect client device has the capability to display an on-screen keyboard if requested by the HTML rendering backend (formerly known as Okular). The backend will request the virtual keyboard to be displayed if:

  • The user clicked on a regular HTML input element of typed in a text or password.
  • The user clicked on a regular HTML textarea element.

The message that was typed by the user will be assigned to the input or textarea element value. It is also possible to apply a callback function to elements to be called after a successful text entry on the on-screen keyboard. This is done by providing an element with onKeyboardConfirm="callback_function" attribute.

<!--HTML EXAMPLE:-->
<!DOCTYPE html>
<html>
    <head>
        <script type="text/javascript">
            function kbd_check(el_id) {
                el = document.getElementById(el_id);
                    if (el) {
                        kbd_data = el.value;
                        console.log('Keyboard data:' + kbd_data);
                    } else
                        console.log('Element not found.');
                    }
        </script>
    </head>
    <body>
        <!-- input displays onscreen keyboard by default -->
        <input id = "test1" type="text" name="something" size=50 keyboard_layout="p"/>
        <!-- text area uses the callback function -->
        <textarea id = "test2" name="comments" onKeyboardConfirm='kbd_check("test2")'>
            Enter your comments here...
        </textarea>
    </body>
</html>

Keyboard layout

Warning

This feature only works on first generation Visionect 6” Signs.

To change the layout of the keyboard (portrait or landscape), add a keyboard_layout attribute to the HTML element. The valid values for the keyboard_layout attribute are l for landscape or p for portrait view. If the keyboard_layout attribute is not set, then a default layout will be used. The default layout is predicted from the display rotation setting.

<!-- HTML EXAMPLE: -->
<input type="text" name="something" size=50 keyboard_layout="l" />

Disabling the onscreen keyboard

Warning

This feature only works on first generation Visionect 6” Signs.

If you want to prevent the onscreen keyboard from popping up when a user registers a touch command, add a disabled attribute to the input element.

<!-- HTML EXAMPLE: -->
<form action="demo_form.asp">
  First name: <input type="text" name="fname"><br>
  Last name: <input type="text" name="lname" disabled><br>
  <!-- First name will display the keyboard popup, Last name will not. -->
  <input type="submit" value="Submit">
</form>

Button push event

Note

Supported on Visionect Software Suite 3.4 and newer.

If one or more physical buttons are pressed on the device, then the webkit service will dispatch a “button-push” custom js event. The event provides us with source device ID and with state of all buttons.

e.detail.device - source device uuid.

e.detail.state - bit-mask, where each bit represents the ON or OFF state for up to 32 different buttons. The actual hardware will probably support a much lower number of buttons. The least significant bit represents the first button.

e.detail.active - undefined. Reserved for future use.

Following example checks whether button is pressed or released. Button index is then printed on the screen.

<body>
    <div class="content">
        <h1 align="center" id="title"><b>Checking button state: </b></h1>
        <p class="printout" id="pressed" align="center">/</p>
        <p class="printout" id="released" align="center">/</p>
    </div>
    <script type="text/javascript">
        document.addEventListener("button-push", checkButton, true);
        function checkButton(e) {
            var pt1 = document.querySelector("#pressed");
            var pt2 = document.querySelector("#released");
            for (var i=0; i<31; i++) {
                if((e.detail.active >> i) & 1 > 0) { //button index
                    if(((e.detail.state >> i) & 1) == 0) { //index if pressed
                        console.log("Button pressed: ", i);
                        pt1.innerHTML = "Button pressed: " + i.toString();
                    }
                    if (((e.detail.state >> i) & 1) == 1) { // index if released
                        console.log("Button released: ",i);
                        pt2.innerHTML = "Button released:" + i.toString();
                    }
                }
            }
        }
    </script>
</body>

Fine-grained device configuration

The following API is designed to change client device configuration - these are the things that usually require you to connect the device to the Micro USB configuration cable and configure by using CLI.

Warning

Changing some of the following settings may lead to a dysfunctional device. Use this function only if you know what you are doing.

Syntax:

okular.NewCommand(device-id, "Param", {"Data": param-json})

The param-json must be a JSON object with the following content:

{
    "Data": [
            {
                    "Type"   : paramID,
                    "Control": paramControl,
                    "Value"  : paramValue
            },
            ...
    ]
}

The JavaScript developer should define the following constants:

  • Constants for the parameter Control key:
var ParamRead       = 0, // Create a read request
    ParamWrite      = 1, // Create a write request
    ParamReadErrro  = 2, // Error reading the requested
    ParamWriteError = 3; // Error writing requested
  • Constants for the Type key:

TCLV list

Note

TCLV (Type-Control-Length-Value) is an encoding scheme used for device configuration.

Flag Description
Constant Value can’t be changed
Remote read only Value can only be read remotely
Command Sending TCLV with this flag triggers function on device
Remote disabled TCLV can’t be sent remotely
   

 
Type ID Description Flag
0 TCLV Magic number: 0xDEADBEEF Constant
1 TCLV table version: 2 Constant
2 Connectivity type: 0=None, 1=Wifi_PandaDS, 2=Mobile, 3=Ethernet, 4=Wifi_VTab2+Quad, 5=N/A, 6=Wifi_new Command, remote read only
3 Wifi ACK or NACK timeout and Waiting for next block timeout [ms]  
4 Wifi power saving timeout [ms] Remote read only
5 WiFi DTIM skip interval [ms]  
6 Mobile ACK or NACK timeout and Waiting for next block timeout [ms]  
7 Mobile power saving timeout [ms] Remote read only
8 Ethernet MAC address Remote read only
9 Ethernet TCP retry count Remote read only
10 Ethernet TCP retry timeout [100ms] Remote read only
11 Ethernet ACK or NACK timeout and Waiting for next block timeout [ms]  
12 Ethernet power saving timeout [ms] Remote read only
13 IPv4 static IP Remote read only
14 IPv4 static Netmask Remote read only
15 IPv4 static Gateway Remote read only
16 IPv4 static DNS server Remote read only
17 IPv4 mode: 0=Static IP, 1=DHCP Remote read only
18 Server IP Remote read only
19 Server port Remote read only
20 VCOM of Display 0 [mV]  
21 VCOM of Display 1 [mV]  
22 VCOM of Display 2 [mV]  
23 VCOM of Display 3 [mV]  
24 VCOM of Display 4 [mV]  
25 VCOM of Display 5 [mV]  
26 VCOM of Display 6 [mV]  
27 VCOM of Display 7 [mV]  
28 Display Type  
29 Heartbeat interval [minute]  
30 Network error retry interval [ms] Remote read only
31 Proximity: offset calibration parameter  
32 Proximity: threshold [%]  
33 Proximity: diode current [10mA]  
34 Accelerometer threshold count  
35 Accelerometer debounce count  
36 Battery OFF threshold [mV]  
37 Battery ON threshold [mV]  
38 Battery threshold count  
39 Front light map 0: PWM+LDR  
40 Front light map 1: PWM+LDR  
41 Front light map 2: PWM+LDR  
42 Front light map 3: PWM+LDR  
43 Front light map 4: PWM+LDR  
44 Front light map 5: PWM+LDR  
45 Front light map 6: PWM+LDR  
46 Front light map 7: PWM+LDR  
47 Front light threshold  
48 Front light mode  
49 System screens: 1=Battery, 2=Not connected, 3=Battery+Not connected  
50 Touch mode: 0=OFF, 1=ON, 3=ON+Beep, 5=ON+NoCalib, 7=ON+Beep+NoCalib Command
51 Shipping mode: 0=OFF, 1=ON (Accelerometer ON), 2=ON (Accelerometer OFF) Remote disabled
52 Sleep mode disable: 0=Sleep mode enabled, 1=Sleep mode disabled  
53 Command to save parameters to flash Command
54 Roaming mode: 0=Roaming disabled, 1=Roaming enabled  
55 Roaming threshold [dBm]  
56 Roaming hysteresis [dBm]  
57 Writes EAP certificate chunk Command
58 Writes EAP certificate descriptor2 Command
59 Erases EAP certificate Command
60 EAP certificate 0 info Constant
61 EAP certificate 1 info Constant
62 EAP certificate 2 info Constant
63 EAP certificate 3 info Constant
64 EAP certificate 4 info Constant
65 WiFi SSID Remote read only
66 WiFi security mode: open, wpa2, wpae, wpa2e, wpa2eu Remote read only
67 WiFi password Command, remote read only
68 WiFi: not used. Set to 0 Remote read only
69 Mobile APN string Remote read only
70 Mobile security mode: none, pap, chap Remote read only
71 Mobile username Remote read only
72 Mobile password Remote read only
73 Mobile: not used. Set to 0 Remote read only
74 Set WiFi EAP method: 0=PEAP/MSCHAPV2, 2=TLS, 4=TTLS/MSCHAPV2, 5=FAST Remote read only
75 Set WiFi EAP password Remote read only
76 Set WiFi EAP username Remote read only
77 Enable feature Command
78 Disable feature Command
79 Write: Play RTTTL song [text]. Read: play status: 0=Idle, 1=Playing Command
80 Force connection establish. Read requested connection status Command, remote disabled
81 Read connection status Constant, command, remote disabled
82 Application name Constant
83 Connectivity support: bit0=Wifi, bit1=Ethernet, bit2=Mobile Constant
84 Connectivity drivers: see CLI command conn_type_list Constant, remote disabled
85 Disable connectivity for N minutes Constant, remote disabled
86 HW version: ID, Major, Minor, BOM Constant, command
87 Application version: Major, Minor, Revision Constant, command
88 Bootloader version: Major, Minor, Revision Constant, command
89 Device UUID Constant, command
90 Application ready status: 0:Not ready, 1:Ready Constant, remote disabled
91 Reboot device Command
92 Jump to application Command, remote disabled
93 Connectivity FW version Constant, command
95 Touch FW version Constant, command
96 If 1, device successfully sent status packet to server, 0 otherwise Constant, command, remote disabled
97 Frontlight LDR filter coefficient  
98 Frontlight PWM filter coefficient  
99 Front light sensor timeout  
100 Heater low temperature limit  
101 Heater high temperature limit  
102 Heater operation mode  
103 Extension battery threshold  
104 Extension operation minimal temperature  
105 Extension operation maximal temperature  
106 Extension operation mode  
107 Used (compiled) display type Constant, command
108 GTIN code Command, remote read only
109 EPD border mode: 0=server, 1=white, 2=black Remote disabled
110 WiFi MAC Remote read only
111 Text2speech enable  
112 Text2speech voice ID  
113 Text2speech speaking rate in words/minute  
114 Text2speech speaking volume in dB  
115 Text2speech timeout between texts in seconds  
116 Converts text to speech Command
1000 TCLV table version Constant
1001 TCLV SCPU Frontlight Mode  
1002 TCLV SCPU Frontlight map 0: PWM+LDR  
1003 TCLV SCPU Frontlight map 1: PWM+LDR  
1004 TCLV SCPU Frontlight map 2: PWM+LDR  
1005 TCLV SCPU Frontlight map 3: PWM+LDR  
1006 TCLV SCPU Frontlight map 4: PWM+LDR  
1007 TCLV SCPU Frontlight map 5: PWM+LDR  
1008 TCLV SCPU Frontlight map 6: PWM+LDR  
1009 TCLV SCPU Frontlight map 7: PWM+LDR  
1010 TCLV SCPU Frontlight PWM frequency in Hz  
1011 TCLV SCPU Frontlight samplerate in sec  
1012 TCLV SCPU Frontlight prefilter [0..100]  
1013 TCLV SCPU Frontlight postfilter [0..100]  
1014 TCLV SCPU Frontlight sensor timeout in sec  
1015 TCLV SCPU Heater Mode  
1016 TCLV SCPU Heater OFF temperature in °C  
1017 TCLV SCPU Heater ON temperature in °C  
1018 TCLV SCPU Device Mode  
1019 TCLV SCPU Minimal OFF temperature in °C  
1020 TCLV SCPU Maximal OFF temperature in °C  
1021 TCLV SCPU Battery threshold in mV  
1022 TCLV SCPU Frontlight PWM limit in %  
1300 Command to push parameters to SCPU Command
1301 Command to pull parameters from SCPU Command
1302 Command to save parameters to SCPU Command
2000 TCLV table version Command
2001 TCLV DPU mode  
2002 TCLV DPU VSPOS rail voltage in mV  
2003 TCLV DPU VSNEG rail voltage in mV  
2004 TCLV DPU VGPOS rail voltage in mV  
2005 TCLV DPU VGNEG rail voltage in mV  
2006 TCLV DPU VCOM1 rail voltage in mV  
2007 TCLV DPU VCOM2 rail voltage in mV  
2008 TCLV DPU VCOM3 rail voltage in mV  
2009 TCLV DPU VCOM4 rail voltage in mV  
2010 TCLV DPU sequence 0 ON: delay in ms + rail ID  
2011 TCLV DPU sequence 1 ON: delay in ms + rail ID  
2012 TCLV DPU sequence 2 ON: delay in ms + rail ID  
2013 TCLV DPU sequence 3 ON: delay in ms + rail ID  
2014 TCLV DPU sequence 4 ON: delay in ms + rail ID  
2015 TCLV DPU sequence 5 ON: delay in ms + rail ID  
2016 TCLV DPU sequence 6 ON: delay in ms + rail ID  
2017 TCLV DPU sequence 7 ON: delay in ms + rail ID  
2018 TCLV DPU sequence 0 OFF: delay in ms + rail ID  
2019 TCLV DPU sequence 1 OFF: delay in ms + rail ID  
2020 TCLV DPU sequence 2 OFF: delay in ms + rail ID  
2021 TCLV DPU sequence 3 OFF: delay in ms + rail ID  
2022 TCLV DPU sequence 4 OFF: delay in ms + rail ID  
2023 TCLV DPU sequence 5 OFF: delay in ms + rail ID  
2024 TCLV DPU sequence 6 OFF: delay in ms + rail ID  
2025 TCLV DPU sequence 7 OFF: delay in ms + rail ID  
2300 Command to push parameters to SCPU Command
2301 Command to pull parameters from SCPU Command
2302 Command to save parameters to SCPU Command

Note

The commands up to 999 are supported by all devices.

The commands from 1000 up to 2000 are SCPU related and are supported by System Board 32’’ ver 0.5, 0.6 and 1.0B0

The commands from 2000 onward are in addition to SCPU also related to the DPU. These are supported by System Board 32’’ ver 1.0B1 and 2.0.

  • the Value key is a string value.

Example 1:

Get device IP address and server (gateway) IP address:

var paramPromise = okular.NewCommand("47003500-1351-3432-3434-373300000000", "Param", {"Data": [ {"Type":13, "Control":0, "Value": ""}, {"Type":18, "Control":0, "Value": ""}]})
paramPromise.then(function(data) {
    obj = JSON.parse(data);
    if (obj[0].Control == 2) {
        console.log('Error reading device IP address');
    } else {
        console.log('Device IP address is ', obj[0].Value); // convert from base64
    }
    if (obj[1].Control == 2) {
        console.log('Error reading server IP address');
    } else {
        console.log('Server IP address is ', obj[1].Value); // convert from base64
    }
});

Example 2:

Setting the device heartbeat:

changeHb = function(minutes) {
        console.log('setting heartbeat to ', minutes.toString(), ' minute(s)');
        var paramPromise = okular.NewCommand("47003500-1351-3432-3434-373300000000", "Param",
                        {"Data": [{"Type":29, "Control":1, "Value": minutes.toString()}]})
        paramPromise.then(function(data) {
                var obj = JSON.parse(data);
                if (Object.keys(obj).length == 0) {
                        document.getElementById("heartbeat").innerHTML = minutes.toString();
                        return;
                }
                var ctrl = obj[0].Control;
                var hb = obj[0].Value;
                console.log('Control:', ctrl);
                if (ctrl == 3) {
                        document.getElementById("heartbeat").innerHTML = '(write error ' + hb + ')';
                }
        });
}

Access the live view image

Note

This section applies to the Visionect Software Suite version 2.12.4 and above.

Since Visionect Software Suite version 2.12.4, the user application can fetch live view images using the following API:

  • The same image as one seen on device display(s):
okular.image.device(id, encoder, parameters);

  • The latest rendered image for a device. The image might or might not be the same as seen on device displays. When changing content, it takes some time for a device to synchronize its displays with the Software Suite. This is the reason why the display content and the cached image might differ.
okular.image.cached(id, encoder, parameters);

  • The latest image as seen by webkit backend viewport. If back-end is in a virtual screen mode, this will show merged view for all devices:
okular.image.backend(encoder, parameters);

Parameters:

  • id Target device UUID. Typically on a single device configuration (no virtual screen), the device UUID is implicitly known and the value can be safely set to null or an empty string “”,
  • parameters Optional parameters {[scale: scale], [display: id]},
    • scale Optional number parameter for image scaling 0<scale<=4.0
    • display Optional display ID (0, 1, 2, 3, ... NumDisplays-1). With okular.image.device() function, you can target a specific display.

Returns: promise

  • A resolved promise will contain:
{
      type,
      dataEncoding,
      data
};

Where:

  • type is a MIME content type, used to encode the image. The list below shows the valid type values and how they relate to the encoder function parameter value:

    • “bmp” -> “image/bmp”
    • “bmp-lz4_compress” -> “application/octet-stream” This is a normal bmp image additionally compressed with lz4 algorithm (see https://github.com/pierrec/node-lz4/)
    • “png” -> “image/png”
    • “jpeg”-> “image/jpeg”
  • dataEncoding string with value “base64”

  • data base64 string encoded image data

Should something fail, a promise is rejected with Error.

Example:

Let’s send a captured image to some server:

<!DOCTYPE html>
  <html>
  <head>
  <script>
  function postData(json) {
      var http = new XMLHttpRequest();
      var url = "http://localhost:8666";
      http.open("POST", url, true);
      http.setRequestHeader("Content-type", "application/json");

      http.onreadystatechange = function() {//Call a function when the state changes.
          if(http.readyState == XMLHttpRequest.DONE && http.status == 200) {
               console.log(http.responseText);
          }
      }
      http.send(json);
  }

  function render() {
          okular.image.device(null, "png").then(function(img) {
              img.origin="device";
              postData(JSON.stringify(img));
           }, function (err) {
              postData(JSON.stringify({err:err.toString(), origin: "device"}));
           })
   }
   window.onload=function() {
       setTimeout(function(){
           render()
       }, 10000)
   }
   </script>

  </head>
  <body>
  <h1>My Liveview Test</h1>
  <p>
   Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum
  </p>
  </body>
  </html>

Access Live-View image

Since release 2.12.4 user application can fetch live view images using the following API:

  • okular.image.device(id, encoder, parameters); the same image as seen on device displays,
  • okular.image.cached(id, encoder, parameters); latest rendered image for device. The image might or might not be the same as seen on device displays. On content change, it takes some time for device to synchronize its displays with the server. The latter is the reason why the display content and the cached image might differ,
  • okular.image.backend(encoder, parameters); latest image as seen by the HTML backend viewport. If the backend is in virtual screen mode, this will show a merged view for all devices.
Parameters:
  • id target device UUID. Typically on a single device configuration (no virtual screen), device UUID is implicitly known and the value can be safely set to null or an empty string "",

  • parameters optional parameters {[scale: scale], [display: id]},

    • scale optional number parameter for image scaling 0<scale<=4.0
    • display optional display ID (0, 1, 2, 3, ... NumDisplays-1). With okular.image.device() function, one can optionally target specific display.
Returns: promise
  • a resolved promise will contain

    {
     type,
    
         dataEncoding,
         data
    };
    
where:
  • type is a MIME content type, used to encode the image. The list below shows valid type values and how they relate to the encoder function parameter value:

    • "bmp" -> "impage/bmp"
    • "bmp-lz4_compress" -> "application/octet-stream"- This is is a normal bmp image additionally compressed with lz4 algorithm see https://github.com/pierrec/node-lz4/
    • "pmg" -> "image/png"
    • "jpeg"-> "image/jpeg"
  • dataEncoding string with value "base64"

  • data base64 string encoded image data

or, if something fails, promise is rejected with Error

Examples

The following app will take its own “screenshot” and will append to its DOM tree:

okular.image.backend("jpeg", {scale: 0.5}).then(function(img) {
    // img holds base64 encoded image.

    // convert base64 string to a binary string
    var binary = atob(img.data);
    // copy binary string to ArrayBuffer
    var buffer = new ArrayBuffer(img.data.length);
    var view = new Uint8Array(buffer);
    for (var i = 0; i < img.data.length; i++) {
        view[i] = binary.charCodeAt(i);
    }
    // create blob
    var blob = new Blob( [view], { type: img.type });
    // blob URL
    var objectURL = URL.createObjectURL(blob);

   // create img element and attach blob URL to it
   var image = document.createElement('img');
   image.src = objectURL;
   //append img to DOM
   document.body.appendChild(image);

   // release blob URL
   URL.revokeObjectURL(objectURL);
}, function (err) {
   console.log(err);
})

Perhaps a more practical case on how to POST a captured image to a remote server using JSON API is as follows:

<!DOCTYPE html>
<html>
<head>
<script>
function postData(json) {
    var http = new XMLHttpRequest();
    var url = "http://localhost:8666";
    http.open("POST", url, true);
    http.setRequestHeader("Content-type", "application/json");

    http.onreadystatechange = function() {//Call a function when the state changes.
        if(http.readyState == XMLHttpRequest.DONE && http.status == 200) {
             console.log(http.responseText);
        }
    }
    http.send(json);
}

function render() {
        okular.image.device(null, "png").then(function(img) {
            img.origin="device";
            postData(JSON.stringify(img));
         }, function (err) {
            postData(JSON.stringify({err:err.toString(), origin: "device"}));
         })
 }
 window.onload=function() {
     setTimeout(function(){
         render()
     }, 10000)
 }
 </script>

</head>
<body>
<h1>My Liveview Test</h1>
<p>
 Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum
</p>
</body>
</html>

Text to speech

Note

Added in version 4.1.6

Text to speech function sends arbitrary text to a specific device and triggers the speech module.

Sending multiple speech requests will be queued.

Usage:

okular.Say([device id], [arbitrary 255-byte text]);