System Bridge
  • 21 Feb 2025
  • 7 Minutes to read

System Bridge


Article summary

System level scripting with System Bridge

PRELIMINARY INFORMATION - SUBJECT TO CHANGE

The Dise One Player offers rich, secure, high-performance play-out running in a Chromium browser engine, with an app-based architecture, where HTML5 apps (templates) are managed through the CMS.  These apps have access to all web engine features and run in the browser’s sandbox environment - with some extensions such as COM-port access.

Using web technologies, highly advanced solutions and applications can be built and maintained in a secure environment.

However, certain applications and integrations require running scripts with operating system level access and capabilities on the player device. These scripts need to run outside of the Dise One Player, and can communicate with apps/templates running inside the Dise One Player through the built-in websocket.

While this can be managed completely outside of Dise, using the Dise CMS to manage the complete solution - including external scripts - gives many benefits.

What is the System Bridge?

  • The System Bridge is a separate installation package, currently only available for Windows.

  • Installed on the player device, runs in the background as a Windows service

  • Hosts and runs JavaScript and Python scripts in the context of the local Windows account.

  • The scripts themselves (and any other files required) are distributed as part of Dise One app packages (template zip files).

  • API for starting, stopping, and monitoring Python and JS scripts from within a Dise One app.

  • API for exchange of data between the Python/JS script and the Dise One Player via websocket; script can get and set attributes in Dise One Player.

  • The System Bridge itself can be updated as a software addon package from the Dise CMS.

Prerequisites

Installation

SystemBridge

  • Extract the SystemBridge program files in C:\Program Files (x86)\Dise\SystemBridge.

  • Install SystemBridgeService.exe as a Windows service with the command
    sc create "Dise SystemBridge" binPath= "C:\Program Files (x86)\Dise\SystemBridgeService.exe"

Python:

  • Winpython64-3.13.1.0dot.exe in C:\Python\WPy64-31310.

  • Edit C:\Program Files (x86)\Dise\SystemBridge\appsettings.json.

  • Change script launcher for ".py" from "python" into "C:\\Python\\WPy64-31310\\scripts\\python.bat":

{
    "webSocketURL": "ws://localhost:6556/SystemBridge",
    "diseWebRoot": "%PUBLIC%\\Dise",
    "scriptLaunchers": {
        ".js": "node",
        ".mjs": "node",
        ".py": "C:\\Python\\WPy64-31310\\scripts\\python.bat"
    }
}
  • Ensure C:\Python has permissions set to the current user (or the user that will run the player)

  • From command line:

    • C:\Python\WPy64-31310\scripts\env.bat

    • pip install websockets


Launching a JavaScript script from a Dise One template

import * as Dise from "../../../TemplateFramework/esm/index.js";
import { SystemBridge } from "../../../templateframework/esm/AddIns/SystemBridge.js";

class View extends Dise.TemplateViewBase {

    bridge?: SystemBridge;
    duration?: number;

    restartTemplate: boolean = false;
    exitTemplate?: (...anything: unknown[]) => void;

    async onPreloadWithDataHelper(helper: Dise.IDataHelper): Promise<Dise.OnPreloadReadyReturn | void | boolean> {
        const isVirgin = !this.bridge;
        this.bridge ||= new SystemBridge('BridgeTestTemplate');

        this.duration = helper.getDuration(12);

        // On initial startup, the SystemBridge needs some
        // time to connect to the WebSocket:
        if (isVirgin) {
            this.bridge.onMessage = (msg) => {
                console.log(msg);
                if (msg.canReply)
                    msg.reply('ACK, ACK!');
            };
            this.bridge.onScriptEnded = (localPath, details) => {
                console.log('Ended: ' + localPath, details);
            };
            await this.controller.delay(1000);
        }

        let remoteName: string;
        try {
            remoteName = await this.bridge.startScript('HostScripts/myScript.js');
        }
        catch (err) {
            console.log(err?.message || 'startScript failed');
        }

        this.controller.setAttribute({
            name: 'test.FooBar',
            active: false,
            data: 'Good golly Miss Molly!',
        });

        return {
            duration: this.duration,
            suggestedNextPreload: this.duration + 2,
        };
    }

    async onShift(): Promise<boolean | void | Dise.OnTemplateEndedReturn> {
        this.exitTemplate?.();
        const endPromise = new Promise((resolve) => this.exitTemplate = resolve);

        this.controller.setAttribute({
            name: 'test.FooBar',
            active: true,
            data: "Don't be lazy Miss Daisy!",
        });
        //this.bridge.sendMessage("TestPage", "Hello Test Page!");
        const responseMsg = await this.bridge.sendMessage("TestPage", "Hello Test Page!", true);
        const whoIsOutThere = await this.bridge.ping();
        const isRunning = await this.bridge.isScriptServiceRunning();

        await this.controller.delay(5000);

        await this.bridge.stopScript('HostScripts/myScript.js');

        await endPromise;
        if (this.restartTemplate)
            return {
                forceNew: true,
            };
    }

    onTemplateUpdate(update: Dise.TemplateUpdate): void {
        this.restartTemplate = true;
        this.exitTemplate?.();
    }
}

new Dise.TemplateController(new View());

Launching a Python script from a Dise One template

const bridge = new SystemBridge('BridgeTestTemplate');

bridge.onMessage = (message) => {
    console.log(`Received message: ${message.sendMessage}`);
    if (message.canReply) {
        message.reply('Response to message');
    }
}

bridge.onScriptEnded = (localPath, info) => {
    console.log(`Script ${path} ended with info: ${JSON.stringify(info)}`);
}

// Wait for the script to start
await this.bridge.waitForScriptServiceConnection(6000);

// Start script included in the template package
const remoteName = await bridge.startScript('Python/sampleScript.py');

// Just send a message to the remote script
await bridge.sendMessage(remoteName, "Hello!");

// Send message and wait for response
const response = await bridge.sendMessage(remoteName, "Hello! Please answer...", true);

// Ping remote script
const whoIsOutThere = await bridge.ping(remoteName);

// Ping remote script and System Bridge
const whoIsOutThere = await bridge.ping();

// Check if script service is running
const isRunning = await bridge.isScriptServiceRunning();

// setAttribute (from any template) is broadcasted through system bridge
this.controller.setAttribute({
            name: 'attributeName',
            active: false,
            data: 'Attribute value',
        });

// Stop the script
await this.bridge.stopScript(remoteName);

// Stop named script
await this.bridge.stopScript('Python/sampleScript.py');

Dise System Bridge Python functions

Below is a list of available Python commands:

get_logger

def get_logger()

Description:

Get logger instance. Will log messages to DbgView if running on Windows.

Returns:

Logger instance.

Example:

from SystemBridge.client import get_logger
logger = get_logger()
logger.info('This is a test log message.')

ConnectTimeoutMS

4 second connection timeout.

ReceiveTimeoutMS

4 second receive timeout.


DiseAPIClientObjects

class DiseAPIClient()

Description:

A client for Dise template inter-communication.

Attributes:

  • onAttribute - Callable[[Attribute], None] - A callback function to be called when an attribute is received.

  • onMessage - Callable[[any, str], None] - A callback function to be called when a custom message is received.

Example:

from SystemBridge.client import Attribute, DiseAPIClient
import asyncio
import sys
args = sys.argv[1:]
SCRIPT_SENDER_NAME = args[0] if len(args) > 0 else ""
TEMPLATE_SENDER_NAME = args[1] if len(args) > 1 else ""
async def main():
    DiseAPI = DiseAPIClient(SCRIPT_SENDER_NAME, TEMPLATE_SENDER_NAME)
    
    def handle_attribute_broadcast(attribute: Attribute):
        print(f"Got attribute broadcast: {attribute.name} = {attribute.data}")

    def handle_message_broadcast(data: any, msgId: str):
        print(f"Got message broadcast: {data} MsgId: {msgId}")

    DiseAPI.onMessage = handle_message_broadcast
    DiseAPI.onAttribute = handle_attribute_broadcast

    await DiseAPI.connect()
    try:
        connected = await DiseAPI.waitForConnection()
        ping_reply = await DiseAPI.ping()
        example_attribute = await DiseAPI.getAttribute("exampleAttribute")
        await DiseAPI.setAttribute(Attribute("sampleAttributeName", True, "Another example value!"))
        await DiseAPI.sendMessage("Message")
    finally:
        await DiseAPI.disconnect()

if __name__ == "__main__":
    try:
        logger.debug("Start application")
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        # Run async code
        loop.run_until_complete(main())
    except KeyboardInterrupt:
        logger.debug("Interrupted by user. Exiting cleanly.")
    finally:
        loop.close()

__init__

def __init__(senderName: str,
             templateName: str,
             url: str = SystemBridgeServerURL)

Description:

Initialize the DiseAPIClient.

Arguments:

  • senderName - str - The name of the sender. Normally the first argument of the host script.

  • templateName - str - The name of the template. Normally the second argument of the host script.

  • url - str - Optional. The URL of the System Bridge server.


waitForConnection

async def waitForConnection(timeoutMS: int = ConnectTimeoutMS) -> bool

Description:

Wait for the WebSocket connection to be established.

Arguments:

  • timeoutMS - int - Optional. The maximum time to wait for the connection, in milliseconds.

Returns:

  • bool - True if the connection was established within the specified timeout, False otherwise.


setAttribute

async def setAttribute(attribute: Attribute)

Description:

Set an attribute on the template.

Arguments:

  • attribute - Attribute - The attribute to set.

Returns:

  • None - If the attribute was successfully sent.


getAttribute

async def getAttribute(name: str) -> Attribute | None

Description:

Request an attribute from the template.

Arguments:

  • name - str - The name of the attribute to request.

Returns:

  • Attribute - The requested attribute, or None if the request timed out.


sendMessage

async def sendMessage(data: any)

Description:

Send a custom message to the template.

Arguments:

  • data - any - The custom message data.

Returns:

  • None - If the message was sent successfully.


ping

async def ping() -> bool

Description:

Send a ping message to the template.

Returns:

  • bool - True if a pong message was received within the timeout period, False otherwise.


Was this article helpful?

Changing your password will log you out immediately. Use the new password to log back in.
First name must have atleast 2 characters. Numbers and special characters are not allowed.
Last name must have atleast 1 characters. Numbers and special characters are not allowed.
Enter a valid email
Enter a valid password
Your profile has been successfully updated.