26 |
27 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-app/template/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "BrightSign React App",
3 | "name": "Create React App for BrightSign Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-dashboard/template/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "BrightSign React App",
3 | "name": "Create React App for BrightSign Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-dashboard/template/src/server/index.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 |
3 | // Path: `__dirname` on player == /storage/sd/dist
4 | const path = __dirname;
5 | const app = express();
6 |
7 | app.use(express.json());
8 | app.use(express.static(path));
9 |
10 | let text = "BrightSign React Web App Dashboard Template";
11 |
12 | // POST endpoint to receive updates
13 | app.post("/text", (req, res) => {
14 | if (req === undefined) {
15 | res.status(400).send("Bad request: no body provided.");
16 | }
17 |
18 | text = req.body.text;
19 |
20 | res.status(200).send("done");
21 | });
22 |
23 | app.get("/text", (req, res) => {
24 | res.status(200).json({ text });
25 | });
26 |
27 | const PORT = 8020;
28 | app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve dev-cookbook
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **BrightSign Device (please complete the following information):**
27 | - OS Version: [e.g. 9.0.123]
28 | - Device model: [e.g. XC4055...]
29 |
30 | **Additional context**
31 | Add any other context about the problem here.
32 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-app/template/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "@testing-library/jest-dom";
3 | import { render, screen } from "@testing-library/react";
4 | import App from "./App";
5 |
6 | const DeviceInfo = require("@brightsign/deviceinfo");
7 |
8 | describe("Device Info Mock", () => {
9 | it("should return mocked device info", () => {
10 | const deviceInfo = new DeviceInfo();
11 |
12 | expect(deviceInfo.model).toBe("MockModel");
13 | expect(deviceInfo.osVersion).toBe("MockOSVersion");
14 | expect(deviceInfo.serialNumber).toBe("MockSerialNumber");
15 | });
16 | });
17 |
18 | test("renders expected body text", () => {
19 | render();
20 | const linkElement = screen.getByText(/MockOSVersion/i);
21 | expect(linkElement).toBeInTheDocument();
22 | });
23 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-app/template/src/App.css:
--------------------------------------------------------------------------------
1 | body,
2 | html {
3 | margin: 0;
4 | height: 100%;
5 | }
6 |
7 | body {
8 | font-family: Arial, sans-serif;
9 | background: rgb(16, 9, 34);
10 | background: linear-gradient(
11 | 90deg,
12 | rgba(16, 9, 34, 1) 0%,
13 | rgba(48, 27, 105, 1) 38%
14 | );
15 | color: white;
16 | display: flex;
17 | align-items: center;
18 | flex-direction: column;
19 | justify-content: center;
20 | padding: 16px;
21 | }
22 |
23 | h1 {
24 | font-size: 48px;
25 | text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
26 | }
27 |
28 | #info {
29 | margin-top: 32px;
30 | display: flex;
31 | flex-direction: column;
32 | gap: 16px;
33 | font-size: 32px;
34 | }
35 |
36 | .label {
37 | font-weight: bold;
38 | margin-right: 8px;
39 | }
40 |
--------------------------------------------------------------------------------
/templates/html5-app-template/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "prettier"
4 | ],
5 | "plugins": ["prettier", "@typescript-eslint"],
6 | "parser": "@typescript-eslint/parser",
7 | "parserOptions": {
8 | "ecmaVersion": 2020,
9 | "sourceType": "module"
10 | },
11 | "rules": {
12 | "prettier/prettier": ["warn"],
13 | "import/no-unresolved": ["off"],
14 | "import/no-extraneous-dependencies": ["off"],
15 | "no-console": ["off"],
16 | "import/prefer-default-export": ["off"],
17 | "import/extensions": ["off"],
18 | "import/order": ["off"],
19 | "import/newline-after-import": ["off"],
20 | "no-use-before-define": ["off"],
21 | "arrow-body-style": ["off"],
22 | "eqeqeq": ["off"],
23 | "prefer-destructuring": ["off"],
24 | "@typescript-eslint/no-unused-vars": ["error"]
25 | },
26 | "env": {
27 | "browser": true,
28 | "node": true
29 | }
30 | }
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for dev-cookbook
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | Describe the problem you're trying to solve. Ex. I'm always frustrated when [...] or, I have this [...] use case that I want to use a BrightSign device for.
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of the template or tool you'd like to have. For example, "a template using XYZ framework" or "an example of streaming video from a URL"
15 |
16 | **Describe alternatives you've considered**
17 | Suggest other projects that might be similar to what you'd like to see built, or another solution that we might consider.
18 |
19 | **Additional context**
20 | Add any other context, links, videos, or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/examples/cec-interface/javascript/autorun.brs:
--------------------------------------------------------------------------------
1 | function main()
2 | ' Create directory to store crash-dumps (optional)
3 | dir = CreateDirectory("SD:/brightsign-dumps")
4 | if not dir then
5 | print "Could not create directory"
6 | end if
7 |
8 | mp = CreateObject("roMessagePort")
9 | r = CreateObject("roRectangle", 0, 0, 1920, 1080)
10 |
11 | config = {
12 | nodejs_enabled: true,
13 | security_params: {
14 | websecurity: false
15 | },
16 | url: "file:///sd:/index.html",
17 | brightsign_js_objects_enabled: true,
18 | javascript_enabled: true,
19 | inspector_server: {
20 | port: 2999
21 | }
22 | }
23 |
24 | htmlWidget = CreateObject("roHtmlWidget", r, config)
25 | htmlWidget.SetPort(mp)
26 | htmlWidget.Show()
27 |
28 | while true
29 | msg = wait(0, mp)
30 | if type(msg) = "roMessagePortEvent"
31 | ? "Message received: ";msg
32 | end if
33 | end while
34 |
35 | end function
--------------------------------------------------------------------------------
/examples/bs-self-updater/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bs-self-updater",
3 | "description": "A simple self-updater example using TypeScript to run on BrightSign players.",
4 | "version": "1.0.0",
5 | "main": "dist/index.js",
6 | "scripts": {
7 | "format": "prettier . --write --config ../../.prettierrc.js --cache --cache-location=../../prettiercache && yarn lint --fix",
8 | "lint": "eslint --no-error-on-unmatched-pattern --config ../../.eslintrc template/src/**/*.{js,jsx}",
9 | "build": "webpack"
10 | },
11 | "dependencies": {
12 | "@types/decompress": "^4.2.7",
13 | "@types/node-fetch": "^2.6.12",
14 | "decompress": "^4.2.1",
15 | "md5-file": "^5.0.0",
16 | "node-fetch": "2.7.0"
17 | },
18 | "devDependencies": {
19 | "ts-loader": "^9.5.2",
20 | "typescript": "^5.0.0",
21 | "webpack": "^5.0.0",
22 | "webpack-cli": "^5.0.0"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/html-starter/README.md:
--------------------------------------------------------------------------------
1 | # Example HTML App
2 |
3 | ## Introduction
4 |
5 | This example is the first stepping stone of the starter examples to get familiar with developing with a BrightSign. The intention of this example is to show how to get a simple HTML application running on your player.
6 |
7 | The HTML application is defined in `index.html` and is a simplified version of the html templates we offer. In addition to the html file, there is an `autorun.brs` file which is what tells your player to run the application and a `static` directory which contains images displayed by the html.
8 |
9 | ## How to run on a player
10 |
11 | Since this example is simplified, there are only two files and one directory which you will need to copy to the root of the player's sd card. Upon the player booting successfully, it will run the `autorun.brs` file. This file instantiates an html object which points the player to the `index.html` file to display the images in the `static` directory.
12 |
--------------------------------------------------------------------------------
/examples/enable-ldws/autorun.brs:
--------------------------------------------------------------------------------
1 | Sub Main()
2 | ' Create network configuration object (0 = eth0, 1 = wlan0, 2 = ppp0, usb0 and usb1 can be input as well)
3 | nc = CreateObject("roNetworkConfiguration", 0)
4 |
5 | ' Configure LDWS: enable on port 80 with password
6 | dwsConfig = { port: 80, open: "your_password_here" }
7 |
8 | print "Enabling LDWS..."
9 | rebootRequired = nc.SetupDWS(dwsConfig)
10 | nc.Apply()
11 |
12 | ' Get device IP address to show user where to connect
13 | currentConfig = nc.GetCurrentConfig()
14 | if type(currentConfig) = "roAssociativeArray" then
15 | ipAddress$ = currentConfig.ip4_address
16 | else
17 | ipAddress$ = ""
18 | endif
19 |
20 | ' Some configurations require restart to take effect
21 | if rebootRequired then
22 | print "Restarting device to apply changes..."
23 | RebootSystem()
24 | else
25 | print "LDWS enabled! Access at: http://"; ipAddress$; "/"
26 | print "Password: your_password_here"
27 | end if
28 | End Sub
29 |
--------------------------------------------------------------------------------
/examples/node-simple-server/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 |
3 | const isProduction = process.env.NODE_ENV === "production";
4 |
5 | module.exports = {
6 | entry: "./src/index",
7 | target: "node",
8 | output: {
9 | filename: "bundle.js",
10 | path: path.resolve(__dirname, "dist"),
11 | },
12 | mode: isProduction ? "production" : "development",
13 | plugins: [],
14 | module: {
15 | rules: [
16 | {
17 | test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
18 | type: "asset/resource",
19 | },
20 | ],
21 | },
22 | resolve: {
23 | extensions: [".js", ".jsx"],
24 | },
25 | externals: ({ request }, callback) => {
26 | if (/^@brightsign\//.test(request)) {
27 | return callback(null, "commonjs " + request);
28 | }
29 | callback();
30 | },
31 | devtool: isProduction ? "source-map" : "eval-source-map",
32 | };
33 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Node.js Packages Publishing
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | jobs:
8 | publish-gpr:
9 | runs-on: ubuntu-latest
10 | permissions:
11 | packages: write
12 | contents: read
13 | steps:
14 | - uses: actions/checkout@v4
15 | with:
16 | fetch-depth: 1
17 | - name: Use Node.js
18 | uses: actions/setup-node@v4
19 | with:
20 | node-version: '14.17.6'
21 | registry-url: 'https://registry.npmjs.org'
22 | cache: 'yarn'
23 | always-auth: 'true'
24 | token: ${{ secrets.NPM_TOKEN }}
25 | scope: '@brightsign'
26 | - run: yarn --frozen-lockfile
27 | - run: echo '//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}' > ${{ github.workspace }}/.npmrc
28 | - run: ./scripts/bump_version.sh
29 | env:
30 | GH_REF_VERSION: ${{ github.ref_name }}
31 | - run: yarn workspaces run publish-package
--------------------------------------------------------------------------------
/examples/bs-self-updater/server/index.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const fs = require("fs");
3 | const path = require("path");
4 |
5 | const app = express();
6 | const PORT = process.env.PORT || 7000;
7 |
8 | app.get("/autorun.zip", (req, res) => {
9 | const zipPath = path.join(__dirname, "autorun.zip");
10 | fs.access(zipPath, fs.constants.F_OK, (err) => {
11 | if (err) {
12 | return res.status(404).json({ error: "autorun.zip not found" });
13 | }
14 | res.sendFile(zipPath, (err) => {
15 | if (err) {
16 | res.status(500).json({ error: "Failed to send file" });
17 | }
18 | });
19 | });
20 | });
21 |
22 | // Catch-all error handler
23 | app.use((err, req, res, next) => {
24 | res.status(err.status || 500).json({
25 | error: err.message || "Internal Server Error",
26 | });
27 | });
28 |
29 | app.listen(PORT, () => {
30 | console.log(`Server running on port ${PORT}`);
31 | });
32 |
--------------------------------------------------------------------------------
/examples/enable-ldws/registry-config/autorun.brs:
--------------------------------------------------------------------------------
1 | function Main()
2 | print "Enabling LDWS via registry..."
3 |
4 | ' Access the networking section of device registry
5 | registrySection = CreateObject("roRegistrySection", "networking")
6 |
7 | if type(registrySection) = "roRegistrySection" then
8 |
9 | ' Check if LDWS is already enabled
10 | if registrySection.read("http_server") <> "" and registrySection.read("http_server") <> "0" then
11 | print "HTTP server port already set. LDWS may already be enabled."
12 | else if registrySection.read("dwse") = "yes" then
13 | print "LDWS already enabled."
14 | else
15 | ' Set HTTP server to enable LDWS on the specified port
16 | registrySection.write("http_server", "80")
17 | ' Enable Local DWS on the default port 80
18 | ' registrySection.write("dwse", "yes")
19 | print "Registry updated - restart device"
20 | print "After restart: http:///"
21 | RebootSystem()
22 | end if
23 | else
24 | print "Error: Could not access registry"
25 | end if
26 | end function
--------------------------------------------------------------------------------
/templates/html5-app-template/src-js/index.js:
--------------------------------------------------------------------------------
1 | import { displayCurrentNetwork, displayDeviceInfo } from "./info";
2 | import { getConfig } from "./config";
3 | import express from "express";
4 | import path from "path";
5 | const spawn = require("child_process").spawn;
6 |
7 | const app = express();
8 | const port = 9000;
9 | const config = getConfig();
10 |
11 | const start = async () => {
12 | console.log(
13 | `config.env: desktop mode - ${config.isDesktop}, environment - ${config.nodeEnv}`
14 | );
15 |
16 | if (!config.isDesktop) {
17 | displayCurrentNetwork();
18 | await displayDeviceInfo();
19 | } else {
20 | // open default browser
21 | app.use(express.static(path.join(__dirname)));
22 | app.listen(port, () => {
23 | console.log(`Server running at http://localhost:${port}/`);
24 | });
25 | spawn("open", [`http://localhost:${port}/`]);
26 | }
27 | };
28 |
29 | if (config.isDesktop) {
30 | start();
31 | } else {
32 | window.main = start;
33 | }
34 |
--------------------------------------------------------------------------------
/templates/html5-app-template/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 |
4 | const isProduction = process.env.NODE_ENV === 'production';
5 |
6 | module.exports = {
7 | entry: './src/index.ts',
8 | module: {
9 | rules: [
10 | {
11 | test: /\.tsx?$/,
12 | use: 'ts-loader',
13 | exclude: /node_modules/,
14 | },
15 | ],
16 | },
17 | resolve: {
18 | extensions: ['.tsx', '.ts', '.js', '.jsx'],
19 | },
20 | output: {
21 | filename: 'bundle.js',
22 | path: path.resolve(__dirname, 'dist'),
23 | },
24 | target: 'node',
25 | plugins: [
26 | new HtmlWebpackPlugin({
27 | template: './src/index.html',
28 | }),
29 | ],
30 | externals: ({ request }, callback) => {
31 | if (/^@brightsign\//.test(request)) {
32 | return callback(null, 'commonjs ' + request);
33 | }
34 | callback();
35 | },
36 | mode: isProduction ? 'production' : 'development',
37 | devtool: isProduction ? 'source-map' : 'eval-source-map',
38 | };
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 BrightSign
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/templates/html5-app-template/src/index.ts:
--------------------------------------------------------------------------------
1 | import { displayCurrentNetwork, displayDeviceInfo } from "./info";
2 | import { getConfig } from "./config";
3 | import express from "express";
4 | import path from "path";
5 | const spawn = require("child_process").spawn;
6 |
7 | const app = express();
8 | const port = 9000;
9 | const config = getConfig();
10 |
11 | const start = async () => {
12 | console.log(
13 | `config.env: desktop mode - ${config.isDesktop}, environment - ${config.nodeEnv}`
14 | );
15 |
16 | if (!config.isDesktop) {
17 | displayCurrentNetwork();
18 | await displayDeviceInfo();
19 | } else {
20 | // open default browser
21 | app.use(express.static(path.join(__dirname)));
22 | app.listen(port, () => {
23 | console.log(`Server running at http://localhost:${port}/`);
24 | });
25 | spawn("open", [`http://localhost:${port}/`]);
26 | }
27 | };
28 |
29 | declare global {
30 | interface Window {
31 | main: () => void;
32 | }
33 | }
34 |
35 | if (config.isDesktop) {
36 | start();
37 | } else {
38 | window.main = start;
39 | }
40 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-app/template/src/server/index.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 |
3 | // Path: `__dirname` on player == /storage/sd/dist
4 | const path = __dirname;
5 | const app = express();
6 |
7 | app.use(express.json());
8 | app.use(express.static(path));
9 | app.use((req, res, next) => {
10 | res.setHeader("Access-Control-Allow-Origin", "*");
11 | res.setHeader("Access-Control-Allow-Headers", "Content-Type");
12 | res.setHeader(
13 | "Access-Control-Allow-Methods",
14 | "GET, POST, PUT, DELETE, OPTIONS"
15 | );
16 | next();
17 | });
18 |
19 | let text = "BrightSign React Web App Template";
20 |
21 | // POST endpoint to receive updates
22 | app.post("/text", (req, res) => {
23 | if (req === undefined) {
24 | res.status(400).send("Bad request: no body provided.");
25 | }
26 |
27 | text = req.body.text;
28 |
29 | res.status(200).send("done");
30 | });
31 |
32 | app.get("/text", (req, res) => {
33 | res.status(200).json({ text });
34 | });
35 |
36 | const port = process.env.REACT_APP_PORT || 8020;
37 | app.listen(port, () => console.log(`Server running on port ${port}`));
38 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-dashboard/template/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "@testing-library/jest-dom";
3 | import { render, screen, act, waitFor } from "@testing-library/react";
4 | import App from "./App";
5 |
6 | const DeviceInfo = require("@brightsign/deviceinfo");
7 |
8 | describe("Device Info Mock", () => {
9 | it("should return mocked device info", () => {
10 | const deviceInfo = new DeviceInfo();
11 |
12 | expect(deviceInfo.model).toBe("MockModel");
13 | expect(deviceInfo.osVersion).toBe("MockOSVersion");
14 | expect(deviceInfo.serialNumber).toBe("MockSerialNumber");
15 | });
16 | });
17 |
18 | test("renders expected dashboard properties text", async () => {
19 | await act(async () => {
20 | render();
21 | });
22 |
23 | const linkElement = screen.getByText(/MockOSVersion/i);
24 | expect(linkElement).toBeInTheDocument();
25 |
26 | await waitFor(() => {
27 | expect(screen.getByText(/mockMonitor/i)).toBeInTheDocument();
28 | expect(screen.getByText(/1080x1920@60hz/i)).toBeInTheDocument();
29 | expect(screen.getByText(/1000b/i)).toBeInTheDocument();
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cra-template-brightsign-web-app",
3 | "version": "1.0.0",
4 | "description": "Create and publish a web app onto a BrightSign player.",
5 | "main": "index.js",
6 | "keywords": [
7 | "BrightSign"
8 | ],
9 | "author": "Diego Benitez ",
10 | "license": "SEE LICENSE IN License.txt",
11 | "repository": {
12 | "url": "https://github.com/brightsign/dev-cookbook.git",
13 | "type": "git",
14 | "directory": "cra-template-brightsign-app/"
15 | },
16 | "publishConfig": {
17 | "registry": "https://registry.npmjs.org"
18 | },
19 | "scripts": {
20 | "test": "jest --config jest.config.js template/src/",
21 | "format:check": "prettier . --check --config ../.prettierrc.js --cache --cache-location=../prettiercache",
22 | "format": "prettier . --write --config ../.prettierrc.js --cache --cache-location=../prettiercache && yarn lint --fix",
23 | "lint": "eslint --no-error-on-unmatched-pattern --config ../../.eslintrc template/src/**/*.{js,jsx}",
24 | "publish-package": "npm publish --access public"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/templates/html5-app-template/src-js/player.js:
--------------------------------------------------------------------------------
1 | import { getConfig } from "./config";
2 |
3 | const config = getConfig();
4 |
5 | const initializePlayer = async () => {
6 | console.log("initializePlayer()");
7 |
8 | return {
9 | DeviceInfo: (await import("@brightsign/deviceinfo")).default,
10 | RegistryClass: (await import("@brightsign/registry")).default,
11 | };
12 | };
13 |
14 | const initializeDesktop = async () => {
15 | console.log("initializeDesktop()");
16 |
17 | return {
18 | DeviceInfo: (await import("./device-info")).default,
19 | mockPlayer: (await import("./__mocks__/@brightsign/raptor")).info,
20 | };
21 | };
22 |
23 | export async function bsPlayer() {
24 | const player = {};
25 |
26 | if (config.isDesktop) {
27 | const playerModule = await initializeDesktop();
28 | player.BSDeviceInfo = new playerModule.DeviceInfo(
29 | playerModule.mockPlayer
30 | );
31 | } else {
32 | const playerModule = await initializePlayer();
33 | player.BSDeviceInfo = new playerModule.DeviceInfo();
34 | player.registry = new playerModule.RegistryClass();
35 | }
36 | return player;
37 | }
38 |
--------------------------------------------------------------------------------
/examples/self-signed-certs/index.js:
--------------------------------------------------------------------------------
1 | const { Agent } = require('undici');
2 |
3 | // Create an agent that accepts self-signed certificates
4 | const httpsAgent = new Agent({
5 | connect: {
6 | rejectUnauthorized: false,
7 | },
8 | });
9 |
10 | // Example: Making a request to a BrightSign player with self-signed cert
11 | async function communicateWithPlayer(playerIP, endpoint = '/api/v1/status') {
12 | const url = `https://${playerIP}:8443${endpoint}`;
13 |
14 | try {
15 | const response = await fetch(url, {
16 | dispatcher: httpsAgent
17 | });
18 |
19 | const data = await response.json();
20 | console.log('Player response:', data);
21 | return data;
22 | } catch (error) {
23 | console.error('Communication failed:', error.message);
24 | throw error;
25 | }
26 | }
27 |
28 | // Usage example
29 | if (require.main === module) {
30 | const playerIP = '192.168.1.100'; // Replace with your player's IP
31 | communicateWithPlayer(playerIP)
32 | .then(data => console.log('Success:', data))
33 | .catch(err => console.error('Error:', err));
34 | }
35 |
36 | module.exports = { httpsAgent, communicateWithPlayer };
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-dashboard/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cra-template-brightsign-dashboard",
3 | "version": "1.0.0",
4 | "description": "Create and publish a rich dashboard onto a BrightSign player.",
5 | "main": "index.js",
6 | "keywords": [
7 | "BrightSign"
8 | ],
9 | "author": "Diego Benitez ",
10 | "license": "SEE LICENSE IN License.txt",
11 | "repository": {
12 | "url": "https://github.com/brightsign/dev-cookbook.git",
13 | "type": "git",
14 | "directory": "cra-template-brightsign-dashboard/"
15 | },
16 | "publishConfig": {
17 | "registry": "https://registry.npmjs.org"
18 | },
19 | "scripts": {
20 | "test": "jest --config jest.config.js template/src/",
21 | "format:check": "prettier . --check --config ../.prettierrc.js --cache --cache-location=../prettiercache",
22 | "format": "prettier . --write --config ../.prettierrc.js --cache --cache-location=../prettiercache && yarn lint --fix",
23 | "lint": "eslint --no-error-on-unmatched-pattern --config ../../.eslintrc template/src/**/*.{js,jsx}",
24 | "publish-package": "npm publish --access public"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/node-starter/index.js:
--------------------------------------------------------------------------------
1 | const http = require('http');
2 | const port = 3000;
3 | // Define the hostname as '0.0.0.0', which means the server will bind to all available network interfaces
4 | const hostname = '0.0.0.0';
5 |
6 | // Create the HTTP server
7 | const server = http.createServer((req, res) => {
8 | // Check if the request method is 'GET' and the requested URL is '/'
9 | if (req.method === 'GET' && req.url === '/') {
10 | res.statusCode = 200;
11 | res.setHeader('Content-Type', 'text/plain');
12 | // Send the response body with a congratulatory message
13 | res.end('Congratulations on interacting with your BrightSign running a simple node http application!');
14 |
15 | } else {
16 | res.statusCode = 404;
17 | res.setHeader('Content-Type', 'text/plain');
18 | res.end('Route not found. Try a GET request to /');
19 | }
20 | });
21 |
22 | // The callback function logs a message to the console when the server starts successfully
23 | server.listen(port, hostname, () => {
24 | // Log a message to indicate that the server setup was successful
25 | console.log('Congratulations on setting up a simple node http application with your BrightSign!');
26 | console.log(`Server is running at http://${hostname}:${port}`);
27 | });
28 |
--------------------------------------------------------------------------------
/examples/htmlwidget-iframes/autorun.brs:
--------------------------------------------------------------------------------
1 | Sub main()
2 | x = 0
3 | y = 0
4 | width = 1920
5 | height = 1080
6 | url = "https://www.brightsign.biz"
7 |
8 | rect = CreateObject("roRectangle", x, y, width, height)
9 |
10 | securityParams = {
11 | trusted_iframes_enabled: true ' This option is available after OS 9.1.75.3
12 | }
13 | config = {
14 | url: url,
15 | nodejs_enabled: true,
16 | brightsign_js_objects_enabled: true,
17 | security_params: securityParams,
18 | storage_path: "/local/",
19 | }
20 | html = CreateObject("roHtmlWidget", rect, config)
21 |
22 | ' Fallback in case the above fails due to unsupported security parameter
23 | if html = invalid then
24 | config = {
25 | url: url,
26 | nodejs_enabled: true,
27 | brightsign_js_objects_enabled: true,
28 | storage_path: "/local/",
29 | }
30 | html = CreateObject("roHtmlWidget", rect, config)
31 | end if
32 |
33 | html.Show()
34 |
35 | m.msgPort = CreateObject("roMessagePort")
36 | syslog = CreateObject("roSystemLog")
37 | while true
38 | event = wait(0, m.msgPort)
39 | syslog.sendline("@@ type(event)="+ type(event))
40 | end while
41 | End Sub
--------------------------------------------------------------------------------
/examples/local-storage/autorun.brs:
--------------------------------------------------------------------------------
1 | Sub Main()
2 | width = 1920
3 | height = 1080
4 |
5 | vidmode = CreateObject("roVideoMode")
6 | if type(vidmode) = "roVideoMode"
7 | width = vidmode.GetResX()
8 | height = vidmode.GetResY()
9 | end if
10 |
11 | msgPort = CreateObject("roMessagePort")
12 | rect = CreateObject("roRectangle", 0, 0, width, height)
13 |
14 | 'inspector disabled currently
15 | 'inspectorServer = {
16 | ' port: 3010
17 | '}
18 |
19 | config = {
20 | url: "file:/SD:/index.html", ' you can use a server or website URL here - "https://example.com/index.html"
21 | mouse_enabled: false, ' set to true to enable mouse/keyboard
22 | 'inspector_server: inspectorServer ' uncomment to enable inspector server
23 | security_params: { websecurity: true }
24 | javascript_enabled: true, ' Else set to false to disable JavaScript
25 | port: msgPort,
26 | }
27 |
28 | html = CreateObject("roHtmlWidget", rect, config)
29 | html.Show()
30 |
31 | syslog = CreateObject("roSystemLog")
32 |
33 | while true
34 | event = wait(0, msgPort)
35 | syslog.sendline("@@ type(event)="+ type(event))
36 | end while
37 | End Sub
38 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-app/template.json:
--------------------------------------------------------------------------------
1 | {
2 | "package": {
3 | "scripts": {
4 | "build": "webpack --mode=development",
5 | "build:prod": "webpack --mode=production --node-env=production",
6 | "clean": "rm -rf dist",
7 | "reinstall": "npm run clean && rm -rf node_modules && npm install",
8 | "watch": "webpack --watch",
9 | "put": "npm run build && ./scripts/put",
10 | "put:prod": "npm run build:prod && ./scripts/put"
11 | },
12 | "devDependencies": {
13 | "@babel/core": "^7.23.0",
14 | "@babel/preset-env": "^7.22.20",
15 | "@babel/preset-react": "^7.23.3",
16 | "@webpack-cli/generators": "^3.0.2",
17 | "babel-loader": "^9.1.3",
18 | "html-webpack-plugin": "^5.5.3",
19 | "css-loader": "^6.10.0",
20 | "style-loader": "^3.3.4",
21 | "webpack": "^5.82.0",
22 | "webpack-cli": "^5.1.0",
23 | "@testing-library/jest-dom": "^6.4.2",
24 | "@testing-library/react": "^14.2.2"
25 | },
26 | "engines": {
27 | "node": "<=14.17.6"
28 | },
29 | "dependencies": {
30 | "node": "^14.17.6",
31 | "nvm": "^0.39.1"
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/examples/node-simple-server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-simple-server-example",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "jest --config jest.config.js src/",
8 | "build:prod": "webpack --mode=production --node-env=production",
9 | "build": "webpack --mode=development",
10 | "format:check": "prettier . --check --config ../.prettierrc.js --cache --cache-location=../prettiercache",
11 | "format": "prettier . --write --config ../.prettierrc.js --cache --cache-location=../prettiercache && yarn lint --fix",
12 | "lint": "eslint --no-error-on-unmatched-pattern --config ../../.eslintrc src/**/*.{js,jsx}",
13 | "publish-package": "npm publish --access public"
14 | },
15 | "repository": {
16 | "url": "https://github.com/brightsign/dev-cookbook.git",
17 | "type": "git",
18 | "directory": "node-simple-server-example/"
19 | },
20 | "publishConfig": {
21 | "registry": "https://registry.npmjs.org"
22 | },
23 | "keywords": [],
24 | "author": "",
25 | "license": "ISC",
26 | "devDependencies": {
27 | "webpack": "^5.94.0",
28 | "webpack-cli": "^5.1.0"
29 | },
30 | "dependencies": {
31 | "http": "^0.0.1-security",
32 | "node": "14.17.6"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-app/template/scripts/put:
--------------------------------------------------------------------------------
1 | # List of required and optional environment variables
2 | : '
3 |
4 | required_vars=("PLAYER")
5 | optional_vars=("PLAYER_PW")
6 |
7 | # Iterate over the required variables
8 | for var in "${required_vars[@]}"; do
9 | if [[ -z "${!var}" ]]; then
10 | echo "ERROR: Environment variable $var is not set."
11 | exit 1
12 | fi
13 | done
14 |
15 | # Iterate over the required variables
16 | for var in "${optional_vars[@]}"; do
17 | if [[ -z "${!var}" ]]; then
18 | echo "INFO: Environment variable $var is not set. Consider whether $var needs to be set."
19 | fi
20 | done
21 |
22 | '
23 |
24 | echo "copying files to $PLAYER"
25 |
26 | # add digest auth when DWS password is set
27 | if [ -z $PLAYER_PW ]; then
28 | AUTH=""
29 | else
30 | AUTH="--digest --user admin:$PLAYER_PW"
31 | fi
32 | echo "AUTH: $AUTH"
33 |
34 | # fi
35 | for f in src/autorun.brs
36 | do
37 | echo $f
38 | curl $AUTH --location --request PUT "http://$PLAYER/api/v1/files/sd" --form "=@"$f"" | jq -r .data
39 | done
40 |
41 | for f in dist/*
42 | do
43 | echo $f
44 | curl $AUTH --location --request PUT "http://$PLAYER/api/v1/files/sd/dist" --form "=@"$f"" | jq -r .data
45 | done
46 |
47 | # reboot the player to restart the application
48 | curl $AUTH --location --request PUT "http://$PLAYER/api/v1/control/reboot/" | jq -r .data
49 |
--------------------------------------------------------------------------------
/examples/cec-interface/brightscript/README.md:
--------------------------------------------------------------------------------
1 | # BrightScript CEC Example
2 |
3 | This example demonstrates how to send CEC (Consumer Electronics Control) commands using BrightScript on BrightSign devices.
4 |
5 | ## What it does
6 | - Sends an "Image View On" (display on) CEC command
7 | - Waits 15 seconds
8 | - Sends a "Standby" (display off) CEC command
9 |
10 | ## How to use
11 | 1. Copy the `autorun.brs` and `index.html` files to the root of the SD card of your BrightSign player.
12 | 2. Reboot the player.
13 | 3. The script will automatically run and send the CEC commands as described.
14 |
15 | ## Troubleshooting
16 |
17 | A common error that you may see when using CEC is the 133 error which indicates "destination address not acknowledged" or in plainer English "the device you are trying to talk to is not listening":
18 | ```
19 | { 238.703} [INFO] [source file:///sd:/index.html:52]: Error while sending CEC command: {"error":133}
20 | ```
21 |
22 | To resolve this:
23 | - Ensure that your BrightSign device is connected to a CEC-compatible display via HDMI.
24 | - Make sure that CEC is enabled on your display device.
25 | - Try replacing the HDMI cable if CEC commands are still not being received.
26 | - Try running your code on a different display device to rule out compatibility issues.
27 |
28 | ## Reference
29 | - [BrightSign roCecInterface documentation](https://docs.brightsign.biz/developers/rocecinterface)
30 |
--------------------------------------------------------------------------------
/templates/html5-app-template/src-js/info.js:
--------------------------------------------------------------------------------
1 | import os from "os";
2 | import { bsPlayer } from "./player";
3 |
4 | const displayCurrentNetwork = () => {
5 | const networkInterfaces = os.networkInterfaces() || {};
6 | let ipAddress = "";
7 |
8 | Object.keys(networkInterfaces).forEach((interfaceName) => {
9 | networkInterfaces[interfaceName]?.forEach((interfaceInfo) => {
10 | if (interfaceInfo.family === "IPv4") {
11 | ipAddress += `${interfaceName}: ${interfaceInfo.address} `;
12 | console.log(
13 | `Network Interface - ${interfaceName}: ${interfaceInfo.address}`
14 | );
15 | }
16 | });
17 | });
18 |
19 | document.querySelector("#ipAddress .value").textContent = ipAddress;
20 | };
21 |
22 | const displayDeviceInfo = async () => {
23 | const player = await bsPlayer();
24 | const deviceInfo = player.BSDeviceInfo;
25 | const { model, osVersion, serialNumber } = deviceInfo;
26 |
27 | document.querySelector("#model .value").textContent = model;
28 | document.querySelector("#osVersion .value").textContent = osVersion;
29 | document.querySelector("#serialNumber .value").textContent = serialNumber;
30 |
31 | console.log(
32 | `DeviceInfo - Model: ${model}, OS Version: ${osVersion}, Serial Number: ${serialNumber}`
33 | );
34 | };
35 |
36 | export { displayCurrentNetwork, displayDeviceInfo };
37 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: Node.js CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | install_and_test:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v4
10 | with:
11 | fetch-depth: 1
12 | - name: Fetch latest main branch commit
13 | run: git fetch --no-tags --depth=1 origin +refs/heads/main:refs/remotes/origin/main
14 | - name: Use Node.js
15 | uses: actions/setup-node@v4
16 | with:
17 | node-version: '14.17.6'
18 | cache: 'yarn'
19 | - run: yarn --frozen-lockfile
20 | - run: node ./scripts/workspace_actions.js test
21 |
22 | format_all:
23 | runs-on: ubuntu-latest
24 | needs: install_and_test
25 | steps:
26 | - uses: actions/checkout@v4
27 | with:
28 | fetch-depth: 1
29 | - name: Fetch latest main branch commit
30 | run: git fetch --no-tags --depth=1 origin +refs/heads/main:refs/remotes/origin/main
31 | - name: Use Node.js
32 | uses: actions/setup-node@v4
33 | with:
34 | node-version: '14.17.6'
35 | cache: 'yarn'
36 | - run: yarn --frozen-lockfile
37 | - run: node ./scripts/workspace_actions.js format
38 | - name: Commit formatting changes
39 | uses: stefanzweifel/git-auto-commit-action@v5
40 | with:
41 | commit_message: Applied automatic formatting changes
42 | branch: ${{ github.head_ref }}
--------------------------------------------------------------------------------
/examples/enable-ldws/javascript/index.js:
--------------------------------------------------------------------------------
1 | const DWSConfiguration = require("@brightsign/dwsconfiguration");
2 | const systemClass = require("@brightsign/system");
3 | const registryClass = require("@brightsign/registry");
4 |
5 | async function configureLDWS() {
6 | console.log("Enabling LDWS...");
7 | const system = new systemClass();
8 | const registry = new registryClass();
9 |
10 | // Create DWS configuration instance
11 | const dwsConfig = new DWSConfiguration();
12 | const config = {
13 | port: 80, // HTTP port for web interface
14 | password: {
15 | value: "your_password_here",
16 | obfuscated: false // Password stored as plain text
17 | },
18 | authenticationList: ["digest"] // Use digest HTTP authentication
19 | };
20 |
21 | try {
22 | const httpServerPort = await registry.read("networking", "http_server");
23 | // Check if LDWS is already enabled
24 | if (httpServerPort !== "" && httpServerPort !== "0") {
25 | // LDWS is already enabled
26 | console.log(`HTTP server port already set to ${httpServerPort}. LDWS may already be enabled.`);
27 | return;
28 | }
29 |
30 | // Apply LDWS configuration to device
31 | dwsConfig.applyConfig(config);
32 | console.log("LDWS enabled!");
33 |
34 | // Reboot device to apply changes
35 | console.log("Rebooting device to apply changes...");
36 | system.reboot();
37 | } catch (error) {
38 | console.error("Configuration failed:", error.message);
39 | }
40 | }
41 |
42 | configureLDWS();
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-dashboard/template.json:
--------------------------------------------------------------------------------
1 | {
2 | "package": {
3 | "scripts": {
4 | "build": "webpack --mode=development",
5 | "build:prod": "webpack --mode=production --node-env=production",
6 | "clean": "rm -rf dist",
7 | "reinstall": "npm run clean && rm -rf node_modules && npm install",
8 | "watch": "webpack --watch",
9 | "put": "npm run build && ./scripts/put",
10 | "put:prod": "npm run build:prod && ./scripts/put",
11 | "put:watch": "nodemon -e js,brs,css --watch src --watch package.json --exec 'npm put:prod'"
12 | },
13 | "devDependencies": {
14 | "@babel/core": "^7.23.0",
15 | "@babel/preset-env": "^7.22.20",
16 | "@babel/preset-react": "^7.23.3",
17 | "@webpack-cli/generators": "^3.0.2",
18 | "babel-loader": "^9.1.3",
19 | "html-webpack-plugin": "^5.5.3",
20 | "webpack": "^5.82.0",
21 | "webpack-cli": "^5.1.0",
22 | "nodemon": "^3.0.3",
23 | "css-loader": "^6.10.0",
24 | "style-loader": "^3.3.4",
25 | "@testing-library/jest-dom": "^6.4.2",
26 | "@testing-library/react": "^14.2.2"
27 | },
28 | "engines": {
29 | "node": "<=14.17.6"
30 | },
31 | "dependencies": {
32 | "node": "^14.17.6",
33 | "nvm": "^0.39.1"
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/examples/send-plugin-message/pluginMessageApp.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Html Messages
7 |
8 |
9 |
10 |
11 |
12 |
43 |
44 |
--------------------------------------------------------------------------------
/examples/cec-interface/javascript/README.md:
--------------------------------------------------------------------------------
1 | # JavaScript CEC Example
2 |
3 | This example demonstrates how to send and receive CEC (Consumer Electronics Control) commands using JavaScript on BrightSign devices.
4 |
5 | ## What it does
6 | - Uses the `@brightsign/cec` package to send CEC commands
7 | - Listens for CEC receive events
8 | - Sends an "Image View On" (display on) command, waits 15 seconds, then sends a "Standby" (display off) command
9 |
10 | ## How to use
11 | 1. Copy the `autorun.brs`, `index.html` and `index.js` files to the root of the SD card of your BrightSign player.
12 | 2. Reboot the player.
13 | 3. The script will automatically run and send the CEC commands as described.
14 |
15 | ## Troubleshooting
16 |
17 | A common error that you may see when using CEC is the 133 error which indicates "destination address not acknowledged" or in plainer English "the device you are trying to talk to is not listening":
18 | ```
19 | { 238.703} [INFO] [source file:///sd:/index.html:52]: Error while sending CEC command: {"error":133}
20 | ```
21 |
22 | To resolve this:
23 | - Ensure that your BrightSign device is connected to a CEC-compatible display via HDMI.
24 | - Make sure that CEC is enabled on your display device.
25 | - Try replacing the HDMI cable if CEC commands are still not being received.
26 | - Try running your code on a different display device to rule out compatibility issues.
27 |
28 | ## Reference
29 | - [BrightSign @brightsign/cec documentation](https://docs.brightsign.biz/developers/cec)
30 |
--------------------------------------------------------------------------------
/examples/htmlwidget-iframes/README.md:
--------------------------------------------------------------------------------
1 | # HTML Widget Iframes Example
2 |
3 | To enhance security, the BrightSign OS has disabled the access to JavaScript APIs, BS-JS objects, and Node.js within iframes in BOS v9.1. However, a security parameter `trusted_iframes_enabled` has been added to the `roHtmlWidget` configuration, which will enable iframes to have access to that functionality. We DO NOT recommend this configuration as the content in these iframes can gain access to core player APIs, and this content is not always under the application's control.
4 |
5 | See the following pages for details:
6 |
7 | - `trusted_iframes_enabled` in the [roHtmlWidget documentation](https://docs.brightsign.biz/developers/rohtmlwidget#23vJS)
8 | - `trustedIframesEnabled` in the [JavaScript htmlwidget documentation](https://docs.brightsign.biz/developers/htmlwidget)
9 |
10 | ## Introduction
11 |
12 | This example demonstrates how to use the `roHtmlWidget` component on a BrightSign player to display a fullscreen web page with support for trusted iframes and enhanced security options (available in OS `v9.1.75.3` and later).
13 |
14 | ## How to Run on a Player
15 |
16 | 1. Copy the `autorun.brs` file to the root of an SD card.
17 | 2. Insert the SD card into your BrightSign player.
18 | 3. Power on the player. The player will automatically run the `autorun.brs` file and display the configured web page.
19 |
20 | ## File Structure
21 |
22 | - `autorun.brs`: BrightScript file that initializes the HTML widget with iframe and security options.
23 |
--------------------------------------------------------------------------------
/examples/bluetooth-scan/autorun.brs:
--------------------------------------------------------------------------------
1 | function main()
2 | ' This interface is the preferred way for JavaScript content to communicate with its parent application.
3 | ' https://brightsign.atlassian.net/wiki/x/-gAeG
4 | mp = CreateObject("roMessagePort")
5 |
6 | ' Create HTML Widget which is defined below in its own function
7 | widget = CreateHTMLWidget(mp)
8 | widget.Show()
9 |
10 | ' Event Loop to view the events that are being sent from the HTML content.
11 | ' The roHtmlWidgetEvent object is sent to the message port when an event occurs in the HTML content.
12 | while true
13 | msg = wait(0,mp)
14 | print "msg received - type=";type(msg)
15 | if type(msg) = "roHtmlWidgetEvent" then
16 | print "msg: ";msg
17 | end if
18 | end while
19 | end function
20 |
21 | function CreateHTMLWidget(mp as object) as object
22 | ' Get Screen Resolution
23 | ' https://brightsign.atlassian.net/wiki/x/SQUYFg
24 | vidmode = CreateObject("roVideoMode")
25 | width = vidmode.GetResX()
26 | height = vidmode.GetResY()
27 |
28 | ' https://brightsign.atlassian.net/wiki/x/HwUYFg
29 | r = CreateObject("roRectangle",0,0,width,height)
30 |
31 | ' Create HTML Widget config
32 | ' https://brightsign.atlassian.net/wiki/spaces/DOC/pages/370672896/roHtmlWidget#Initialization-Parameters
33 | config = {
34 | nodejs_enabled: true
35 | url: "file:///sd:/index.html"
36 | port: mp
37 | }
38 |
39 | ' Create HTML Widget
40 | ' https://brightsign.atlassian.net/wiki/x/AAUYFg
41 | h = CreateObject("roHtmlWidget",r,config)
42 | return h
43 | end function
--------------------------------------------------------------------------------
/templates/html5-app-template/src/info.ts:
--------------------------------------------------------------------------------
1 | import os from "os";
2 |
3 | import { DeviceInfo } from "./device-info";
4 | import { bsPlayer, BrightSignPlayer } from "./player";
5 |
6 | const displayCurrentNetwork = () => {
7 | const networkInterfaces = os.networkInterfaces() || {};
8 | let ipAddress = "";
9 |
10 | Object.keys(networkInterfaces).forEach((interfaceName) => {
11 | networkInterfaces[interfaceName]?.forEach((interfaceInfo) => {
12 | if (interfaceInfo.family === "IPv4") {
13 | ipAddress += `${interfaceName}: ${interfaceInfo.address} `;
14 | console.log(
15 | `Network Interface - ${interfaceName}: ${interfaceInfo.address}`
16 | );
17 | }
18 | });
19 | });
20 |
21 | document.querySelector("#ipAddress .value")!.textContent = ipAddress;
22 | };
23 |
24 | const displayDeviceInfo = async () => {
25 | const player: BrightSignPlayer = await bsPlayer();
26 | const deviceInfo: DeviceInfo = player.BSDeviceInfo;
27 | const { model, osVersion, serialNumber } = deviceInfo;
28 |
29 | document.querySelector("#model .value")!.textContent = model;
30 | document.querySelector("#osVersion .value")!.textContent = osVersion;
31 | document.querySelector("#serialNumber .value")!.textContent = serialNumber;
32 |
33 | console.log(
34 | `DeviceInfo - Model: ${model}, OS Version: ${osVersion}, Serial Number: ${serialNumber}`
35 | );
36 | };
37 |
38 | export { displayCurrentNetwork, displayDeviceInfo };
39 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-dashboard/template/scripts/put:
--------------------------------------------------------------------------------
1 | # List of required and optional environment variables
2 | : '
3 |
4 | required_vars=("PLAYER")
5 | optional_vars=("PLAYER_PW")
6 |
7 | # Iterate over the required variables
8 | for var in "${required_vars[@]}"; do
9 | if [[ -z "${!var}" ]]; then
10 | echo "ERROR: Environment variable $var is not set."
11 | exit 1
12 | fi
13 | done
14 |
15 | # Iterate over the required variables
16 | for var in "${optional_vars[@]}"; do
17 | if [[ -z "${!var}" ]]; then
18 | echo "INFO: Environment variable $var is not set. Consider whether $var needs to be set."
19 | fi
20 | done
21 |
22 | '
23 |
24 | echo "copying files to $PLAYER"
25 |
26 | # add digest auth when DWS password is set
27 | if [ -z $PLAYER_PW ]; then
28 | AUTH=""
29 | else
30 | AUTH="--digest --user admin:$PLAYER_PW"
31 | fi
32 | echo "AUTH: $AUTH"
33 |
34 | # fi
35 | for f in src/autorun.brs
36 | do
37 | echo $f
38 | curl $AUTH --location --request PUT "http://$PLAYER/api/v1/files/sd" --form "=@"$f"" | jq -r .data
39 | done
40 |
41 | for f in dist/*
42 | do
43 | echo $f
44 | curl $AUTH --location --request PUT "http://$PLAYER/api/v1/files/sd/dist" --form "=@"$f"" | jq -r .data
45 | done
46 |
47 | for f in public/*
48 | do
49 | echo $f
50 | curl $AUTH --location --request PUT "http://$PLAYER/api/v1/files/sd/dist" --form "=@"$f"" | jq -r .data
51 | done
52 |
53 |
54 | # reboot the player to restart the application
55 | curl $AUTH --location --request PUT "http://$PLAYER/api/v1/control/reboot/" | jq -r .data
56 |
--------------------------------------------------------------------------------
/examples/indexeddb-caching/autorun.brs:
--------------------------------------------------------------------------------
1 | Sub Main()
2 | width = 1920
3 | height = 1080
4 |
5 | vidmode = CreateObject("roVideoMode")
6 | if type(vidmode) = "roVideoMode"
7 | width = vidmode.GetResX()
8 | height = vidmode.GetResY()
9 | end if
10 |
11 | msgPort = CreateObject("roMessagePort")
12 | rect = CreateObject("roRectangle", 0, 0, width, height)
13 |
14 | 'inspector disabled currently
15 | 'inspectorServer = {
16 | ' port: 3010
17 | '}
18 |
19 | config = {
20 | url: "file:/SD:/index.html", ' you can use a server or website URL here - "https://example.com/index.html"
21 | mouse_enabled: false, ' set to true to enable mouse/keyboard
22 | 'inspector_server: inspectorServer ' uncomment to enable inspector server
23 | security_params: { websecurity: true }
24 | javascript_enabled: true, ' Else set to false to disable JavaScript
25 | nodejs_enabled: true, ' Else set to false to disable Node.js
26 | storage_path: "/cache", ' Name of the directory for local storage cache
27 | storage_quota: "2147483648", ' 2GB
28 | port: msgPort,
29 | }
30 |
31 | html = CreateObject("roHtmlWidget", rect, config)
32 | html.Show()
33 |
34 | syslog = CreateObject("roSystemLog")
35 |
36 | while true
37 | event = wait(0, msgPort)
38 | syslog.sendline("@@ type(event)="+ type(event))
39 | end while
40 | End Sub
41 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-dashboard/template/src/App.css:
--------------------------------------------------------------------------------
1 | body,
2 | html {
3 | margin: 0;
4 | height: 100%;
5 | }
6 |
7 | body {
8 | font-family: Arial, sans-serif;
9 | background: rgb(16, 9, 34);
10 | background: linear-gradient(
11 | 90deg,
12 | rgba(48, 27, 105, 1) 0%,
13 | rgba(16, 9, 34, 1) 38%
14 | );
15 | color: white;
16 | padding: 16px;
17 | }
18 |
19 | h1 {
20 | font-size: 48px;
21 | text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
22 | }
23 |
24 | #info {
25 | margin-top: 32px;
26 | display: flex;
27 | flex-direction: column;
28 | gap: 16px;
29 | font-size: 32px;
30 | }
31 |
32 | .label {
33 | font-weight: bold;
34 | margin-right: 8px;
35 | }
36 |
37 | .container {
38 | display: flex;
39 | padding: 16px;
40 | margin-left: 50%;
41 | margin-right: 5%;
42 | height: 100vh;
43 | }
44 |
45 | .column {
46 | width: 100%;
47 | margin: 10px;
48 | padding: 15px;
49 | display: flex;
50 | flex-direction: column;
51 | }
52 |
53 | .row {
54 | background-color: rgba(48, 27, 105, 0.5);
55 | border-radius: 15px;
56 | display: flex;
57 | justify-content: flex-start;
58 | align-items: center;
59 | width: 100%;
60 | height: max-content;
61 | margin: 10px;
62 | }
63 |
64 | .header {
65 | font-weight: bold;
66 | }
67 |
68 | .column img {
69 | height: auto;
70 | margin-right: 10px;
71 | }
72 |
73 | .background-img {
74 | position: absolute;
75 | top: 25%;
76 | left: 20%;
77 | z-index: -1;
78 | }
79 |
--------------------------------------------------------------------------------
/templates/html5-app-template/src/player.ts:
--------------------------------------------------------------------------------
1 | import { getConfig } from "./config";
2 |
3 | const config = getConfig();
4 |
5 | const initializePlayer = async () => {
6 | console.log("initializePlayer()");
7 |
8 | return {
9 | // @ts-expect-error: Should accept import
10 | DeviceInfo: (await import("@brightsign/deviceinfo")).default,
11 | // @ts-expect-error: Should accept import
12 | RegistryClass: (await import("@brightsign/registry")).default,
13 | };
14 | };
15 |
16 | const initializeDesktop = async () => {
17 | console.log("initializeDesktop()");
18 |
19 | return {
20 | DeviceInfo: (await import("./device-info")).default,
21 | mockPlayer: (await import("./__mocks__/@brightsign/raptor")).info,
22 | };
23 | };
24 |
25 | export interface BrightSignPlayer {
26 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
27 | BSDeviceInfo: any;
28 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
29 | registry?: any;
30 | }
31 |
32 | export async function bsPlayer() {
33 | const player = {};
34 |
35 | if (config.isDesktop) {
36 | const playerModule = await initializeDesktop();
37 | player.BSDeviceInfo = new playerModule.DeviceInfo(
38 | playerModule.mockPlayer
39 | );
40 | } else {
41 | const playerModule = await initializePlayer();
42 | player.BSDeviceInfo = new playerModule.DeviceInfo();
43 | player.registry = new playerModule.RegistryClass();
44 | }
45 | return player;
46 | }
47 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-app/template/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | BrightSign Test App
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-dashboard/template/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | BrightSign Test App
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## 📝 Description
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | **Issue**: [Url to Github Issue](https://github.com/brightsign/dev-cookbook/issues/-1)
10 |
11 | ## 📋 List of Changes
12 |
13 |
14 |
15 | - Please provide a concise list of the main changes.
16 |
17 | ## 🧪 Steps to Test
18 |
19 |
20 | 1. Step 1
21 |
22 | ## Notes to the Reviewer
23 |
24 |
25 |
26 | ## 📸 Screenshots
27 |
28 |
29 |
30 |
31 | ## ✔️ Dev Complete Checklist
32 |
33 | - [ ] PR template filled out
34 | - [ ] Change is tested by submitter
35 | - [ ] PR follows all linting and coding standards
36 | - [ ] Github Issue exists (if applicable)
37 | - [ ] Team member has been assigned
38 | - [ ] At least one commit message is in [Conventional Commit](https://www.conventionalcommits.org/) format
39 |
40 |
--------------------------------------------------------------------------------
/examples/cec-interface/javascript/index.js:
--------------------------------------------------------------------------------
1 | const CecClass = require('@brightsign/cec');
2 | const cec1 = new CecClass('HDMI-1'); // Modify this to be 'HDMI-1', 'HDMI-2', 'HDMI-3', or 'HDMI-4' as needed
3 |
4 | cec1.addEventListener("receive", function(packet) {
5 | const frame = packet.data;
6 | console.log("frame from HDMI-1: " + toHexString(frame));
7 | });
8 |
9 | const toHexString = (cecBytes) => {
10 | return Array.from(cecBytes)
11 | .map((byte) => byte.toString(16).padStart(2, '0'))
12 | .join(' ');
13 | };
14 |
15 | async function sendCecCommand(buffer) {
16 | try {
17 | console.log("Sending CEC command ..." + toHexString(buffer));
18 | await cec1.send(buffer);
19 | } catch (error) {
20 | console.log("Error while sending CEC command: " + JSON.stringify(error));
21 | }
22 | }
23 |
24 | function main() {
25 | // * Developer Note: Varying display manufacturers require direct addressing.
26 | // * There may be settings to configure direct address or not.
27 |
28 | // Turn display "on" - image's view to on
29 | const bufferImageViewOn = new Uint8Array(2);
30 | bufferImageViewOn[0] = 0x4f;
31 | bufferImageViewOn[1] = 0x0D;
32 |
33 | // Turn display "off". Some displays will go to standby mode.
34 | const bufferStandby = new Uint8Array(2);
35 | bufferStandby[0] = 0x4f;
36 | bufferStandby[1] = 0x36;
37 |
38 | sendCecCommand(Array.from(bufferImageViewOn));
39 |
40 | // Wait 15 seconds, then turn display off
41 | setTimeout(() => {
42 | sendCecCommand(Array.from(bufferStandby));
43 |
44 | // Uncomment below to repeat the cycle every 15 seconds
45 | // setTimeout(main, 15000);
46 | }, 15000);
47 | }
48 |
49 | window.main = main;
--------------------------------------------------------------------------------
/examples/html-starter/autorun.brs:
--------------------------------------------------------------------------------
1 | function main()
2 | ' Create directory to store crash-dumps (optional)
3 | dir = CreateDirectory("SD:/brightsign-dumps")
4 | if not dir then
5 | print "Could not create directory"
6 | end if
7 |
8 | ' This interface is the preferred way for JavaScript content to communicate with its parent application.
9 | ' https://brightsign.atlassian.net/wiki/x/-gAeG
10 | mp = CreateObject("roMessagePort")
11 |
12 | ' Create HTML Widget which is defined below in its own function
13 | widget = CreateHTMLWidget(mp)
14 | widget.Show()
15 |
16 | ' Event Loop to view the events that are being sent from the HTML content.
17 | ' The roHtmlWidgetEvent object is sent to the message port when an event occurs in the HTML content.
18 | while true
19 | msg = wait(0,mp)
20 | print "msg received - type=";type(msg)
21 | if type(msg) = "roHtmlWidgetEvent" then
22 | print "msg: ";msg
23 | end if
24 | end while
25 | end function
26 |
27 | function CreateHTMLWidget(mp as object) as object
28 | ' Get Screen Resolution
29 | ' https://brightsign.atlassian.net/wiki/x/SQUYFg
30 | vidmode = CreateObject("roVideoMode")
31 | width = vidmode.GetResX()
32 | height = vidmode.GetResY()
33 | ' https://brightsign.atlassian.net/wiki/x/HwUYFg
34 | r = CreateObject("roRectangle",0,0,width,height)
35 |
36 | ' Create HTML Widget config
37 | ' https://brightsign.atlassian.net/wiki/spaces/DOC/pages/370672896/roHtmlWidget#Initialization-Parameters
38 | config = {
39 | nodejs_enabled: true
40 | url: "file:///sd:/index.html"
41 | port: mp
42 | }
43 |
44 | ' Create HTML Widget
45 | ' https://brightsign.atlassian.net/wiki/x/AAUYFg
46 | h = CreateObject("roHtmlWidget",r,config)
47 | return h
48 | end function
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-app/template/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const HtmlWebpackPlugin = require("html-webpack-plugin");
3 |
4 | const isProduction = process.env.NODE_ENV === "production";
5 |
6 | module.exports = {
7 | entry: {
8 | frontend: "./src/index",
9 | backend: "./src/server/index",
10 | },
11 | target: "node",
12 | output: {
13 | filename: "[name].js",
14 | path: path.resolve(__dirname, "dist"),
15 | },
16 | mode: isProduction ? "production" : "development",
17 | plugins: [
18 | new HtmlWebpackPlugin({
19 | template: "./public/index.html",
20 | }),
21 | ],
22 | module: {
23 | rules: [
24 | {
25 | test: /\.(js|jsx)$/i,
26 | exclude: /node_modules/,
27 | use: {
28 | loader: "babel-loader",
29 | options: {
30 | presets: ["@babel/preset-react"],
31 | },
32 | },
33 | },
34 | {
35 | test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
36 | type: "asset/resource",
37 | },
38 | {
39 | test: /\.css$/i,
40 | use: ["style-loader", "css-loader"],
41 | },
42 | ],
43 | },
44 | resolve: {
45 | extensions: [".js", ".jsx"],
46 | },
47 | externals: ({ request }, callback) => {
48 | if (/^@brightsign\//.test(request)) {
49 | return callback(null, "commonjs " + request);
50 | }
51 | callback();
52 | },
53 | devtool: isProduction ? "source-map" : "eval-source-map",
54 | };
55 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-dashboard/template/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const HtmlWebpackPlugin = require("html-webpack-plugin");
3 |
4 | const isProduction = process.env.NODE_ENV === "production";
5 |
6 | module.exports = {
7 | entry: {
8 | frontend: "./src/index",
9 | backend: "./src/server/index",
10 | },
11 | target: "node",
12 | output: {
13 | filename: "[name].js",
14 | path: path.resolve(__dirname, "dist"),
15 | },
16 | mode: isProduction ? "production" : "development",
17 | plugins: [
18 | new HtmlWebpackPlugin({
19 | template: "./public/index.html",
20 | }),
21 | ],
22 | module: {
23 | rules: [
24 | {
25 | test: /\.(js|jsx)$/i,
26 | exclude: /node_modules/,
27 | use: {
28 | loader: "babel-loader",
29 | options: {
30 | presets: ["@babel/preset-react"],
31 | },
32 | },
33 | },
34 | {
35 | test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
36 | type: "asset/resource",
37 | },
38 | {
39 | test: /\.css$/i,
40 | use: ["style-loader", "css-loader"],
41 | },
42 | ],
43 | },
44 | resolve: {
45 | extensions: [".js", ".jsx"],
46 | },
47 | externals: ({ request }, callback) => {
48 | if (/^@brightsign\//.test(request)) {
49 | return callback(null, "commonjs " + request);
50 | }
51 | callback();
52 | },
53 | devtool: isProduction ? "source-map" : "eval-source-map",
54 | };
55 |
--------------------------------------------------------------------------------
/examples/html-starter/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Simple Example HTML App
8 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
Congratulations on setting up a simple html application with your BrightSign!
36 |
37 |
38 |
39 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/examples/provisioning-server/content/autorun.brs:
--------------------------------------------------------------------------------
1 | ' BrightSign Example Autorun Script
2 | ' The main content autorun script, this is the script which runs on BrightSign startup after provisioning is complete.
3 | function main()
4 | ' Create directory to store crash-dumps (optional)
5 | dir = CreateDirectory("SD:/brightsign-dumps")
6 | if not dir then
7 | print "Could not create directory"
8 | end if
9 |
10 | ' This interface is the preferred way for JavaScript content to communicate with its parent application.
11 | ' https://docs.brightsign.biz/developers/romessageport
12 | mp = CreateObject("roMessagePort")
13 |
14 | ' Create HTML Widget which is defined below in its own function
15 | widget = CreateHTMLWidget(mp)
16 | widget.Show()
17 |
18 | ' Event Loop to view the events that are being sent from the HTML content.
19 | ' The roHtmlWidgetEvent object is sent to the message port when an event occurs in the HTML content.
20 | while true
21 | msg = wait(0,mp)
22 | print "msg received - type=";type(msg)
23 | if type(msg) = "roHtmlWidgetEvent" then
24 | print "msg: ";msg
25 | end if
26 | end while
27 | end function
28 |
29 | function CreateHTMLWidget(mp as object) as object
30 | ' Get Screen Resolution
31 | ' https://docs.brightsign.biz/developers/rovideomode
32 | vidmode = CreateObject("roVideoMode")
33 | width = vidmode.GetResX()
34 | height = vidmode.GetResY()
35 | ' https://docs.brightsign.biz/developers/rorectangle
36 | r = CreateObject("roRectangle",0,0,width,height)
37 |
38 | config = {
39 | nodejs_enabled: true
40 | url: "file:///sd:/index.html"
41 | port: mp
42 | }
43 |
44 | ' Create HTML Widget
45 | ' https://docs.brightsign.biz/developers/rohtmlwidget
46 | h = CreateObject("roHtmlWidget",r,config)
47 | return h
48 | end function
49 |
--------------------------------------------------------------------------------
/examples/send-plugin-message/README.md:
--------------------------------------------------------------------------------
1 | # How to send a plugin message to your html application
2 | This example demonstrates how to set up a presentation that sends plugin messages to your HTML application. Once received, these messages can be forwarded to your backend for processing.
3 |
4 | The setup consists of three components:
5 |
6 | 1. **Presentation File (`plugin-message-transfer.bpfx`)**: This file supports sending plugin messages from the included widgets.
7 | 2. **Script File (`pluginMessageTransfer.brs`)**: Sends and receives the plugin messages between the BrightAuthor:connected Presentation.
8 | 3. **HTML Application (`pluginMessageApp.html`)**: Receives the messages and handles further processing.
9 |
10 | The plugin message format enforced in the `pluginMessageTransfer.brs` file is: `pluginMessage!!!!`. You can modify this format to suit the information you need to communicate from your presentations to your backend. In this case, the plugin messages are triggered by the TimeoutEvents attached to each example video in the presentation. The messages include the variable serial number of the player and the text that indicates if the 1stVideo or 2ndVideo executed.
11 |
12 | ## How to run and edit the example
13 | The `bpfx` presentation file can be opened in BrightAuthor:connected by clicking the dropdown next to "Presentations" and then selecting "Open..."
14 | Choose this `bpfx` file and if it asks for the location of the `html` and `brs` files, point it to the folder in which they are contained.
15 |
16 | Once open, the widgets can individually be selected and the "advanced" tab on the right side can be opened to view related plugin messages.
17 |
18 | Widgets and plugin messages can be altered in the presentation. Once complete, the presentation should be saved and published to your player.
--------------------------------------------------------------------------------
/templates/html5-app-template/src-js/autorun.brs:
--------------------------------------------------------------------------------
1 | function main()
2 |
3 | mp = CreateObject("roMessagePort")
4 | 'Enable lDWS
5 | EnableLDWS()
6 | ' Enable SSH
7 | EnableSSH()
8 | ' Create HTML Widget
9 | widget = CreateHTMLWidget(mp)
10 | widget.Show()
11 |
12 | 'Event Loop
13 | while true
14 | msg = wait(0,mp)
15 | print "msg received - type=";type(msg)
16 |
17 | if type(msg) = "roHtmlWidgetEvent" then
18 | print "msg: ";msg
19 | end if
20 | end while
21 |
22 | end function
23 |
24 | function CreateHTMLWidget(mp as object) as object
25 | ' Enable Web Inspector
26 | reg = CreateObject("roRegistrySection","html")
27 | reg.Write("enable_web_inspector","1")
28 | reg.Flush()
29 |
30 | ' Get Screen Resolution
31 | vidmode = CreateObject("roVideoMode")
32 | width = vidmode.GetResX()
33 | height = vidmode.GetResY()
34 |
35 | r = CreateObject("roRectangle",0,0,width,height)
36 |
37 | ' Create HTML Widget config
38 | config = {
39 | nodejs_enabled: true
40 | inspector_server: {
41 | port: 3000
42 | }
43 | url: "file:///sd:/dist/index.html"
44 | port: mp
45 | }
46 |
47 | ' Create HTML Widget
48 | h = CreateObject("roHtmlWidget",r,config)
49 | return h
50 |
51 | end function
52 |
53 | function EnableLDWS()
54 |
55 | registrySection = CreateObject("roRegistrySection", "networking")
56 |
57 | if type(registrySection) = "roRegistrySection" then
58 |
59 | registrySection.Write("http_server", "80")
60 |
61 | end if
62 |
63 | registrySection.Flush()
64 |
65 | end function
66 |
67 | function EnableSSH()
68 |
69 | regSSH = CreateObject("roRegistrySection", "networking")
70 |
71 | if type(regSSH) = "roRegistrySection" then
72 |
73 | regSSH.Write("ssh","22")
74 |
75 | endif
76 |
77 | n = CreateObject("roNetworkConfiguration", 0)
78 | n.SetLoginPassword("password")
79 | n.Apply()
80 |
81 | regSSH.Flush()
82 |
83 | end function
--------------------------------------------------------------------------------
/templates/html5-app-template/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | html5-app-template
7 |
43 |
44 |
45 |
46 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/examples/local-storage/README.md:
--------------------------------------------------------------------------------
1 | # Local Storage Image Slideshow Example
2 |
3 | ## Introduction
4 |
5 | This example demonstrates how to create an image slideshow application that caches images in the browser's localStorage and displays them in sequence. The application downloads and stores images persistently for smooth playback and implements a looping slideshow that automatically advances to the next image after a specified duration.
6 |
7 | ## How it Works
8 |
9 | 1. **Initial Load**: The application checks localStorage for cached images and displays the first image (cached or streamed)
10 | 2. **Persistent Caching**: Images are downloaded, converted to base64, and stored in browser localStorage
11 | 3. **Background Caching**: While the first image is displayed, other images are downloaded and cached in the background
12 | 4. **Smooth Transitions**: When an image's display timer expires, the next cached image loads instantly from localStorage
13 | 5. **Session Persistence**: Cached images persist across browser sessions and page reloads
14 | 6. **Loop Behavior**: After the last image, the slideshow loops back to the first image
15 |
16 | **Note**: Browser localStorage has a storage limit of approximately 5MB. For caching larger amounts of content, consider using IndexedDB which offers much larger storage capacity. Check out our [IndexedDB caching example](https://github.com/brightsign/dev-cookbook/tree/main/examples/indexeddb-caching-example) for an alternative approach.
17 |
18 | ## How to Run on a Player
19 |
20 | 1. Copy the `autorun.brs` and `index.html` files to the root of an SD card
21 | 2. Insert the SD card into your BrightSign player
22 | 3. Power on the player - it will automatically run the `autorun.brs` file which then loads the HTML/JS application
23 |
24 | ## Files Structure
25 |
26 | - autorun.brs: BrightScript file that initializes the HTML widget
27 | - index.html: Main HTML/JS application with localStorage image caching
28 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-app/README.md:
--------------------------------------------------------------------------------
1 | ## Using the template
2 |
3 | Ensure that you have the right version of Node.js installed (`14.17.6`) as this is what is best supported by BrightSign devices at the moment.
4 |
5 | This project is a custom `create-react-app` template. Clone the repo and then run the following command in the root of your project directory to instantiate the project. Be sure to set the `file:/` path to the location where `/template` and `/template.json` reside.
6 |
7 | ```
8 | npx create-react-app bs-app --template file:/path/to/dev-cookbook/cra-template-brightsign-app
9 | ```
10 |
11 | Once the app is ready, simply follow the instructions in the project `readme` to deploy to your device. When you run the app locally using `yarn start`, mocked `@brightsign/` JavaScript APIs will be used.
12 |
13 | **For more info**, see [/template/README.md](./template/README.md)
14 |
15 | ## Contributing to the template
16 |
17 | To contribute enhancements or fixes to the template, please follow these steps:
18 |
19 | 1. Fork and Clone: Start by forking the `dev-cookbook` repository. Then, clone your fork locally to make changes.
20 |
21 | 2. Make Changes: Navigate to the specific template you wish to improve within the dev-cookbook directory. Apply your changes there.
22 |
23 | 3. Test Locally: Test the template by instantiating using the command mentioned in the "Using the Template" section. This step ensures your changes work as expected within the create-react-app workflow.
24 |
25 | 4. Commit Changes: After testing your changes, commit them to your fork. Ensure your commit messages clearly describe the enhancements or fixes made. Push your commits to GitHub.
26 |
27 | 5. Submit a Pull Request (PR): Submit a pull request to the main dev-cookbook repository. Be sure to fill out the predefined PR sections as fully as possible.
28 |
29 | 6. Code Review: Once your PR is submitted, it will be reviewed by a BrightSign team member. Be open to feedback and ready to make further adjustments based on their suggestions.
30 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-dashboard/README.md:
--------------------------------------------------------------------------------
1 | ## Using the template
2 |
3 | Ensure that you have the right version of Node.js installed (`14.17.6`) as this is what is best supported by BrightSign devices at the moment.
4 |
5 | This project is a custom `create-react-app` template. Clone the repo and then run the following command in the root of your project directory to instantiate the project. Be sure to set the `file:/` path to the location as where `/template` and `/template.json` reside.
6 |
7 | ```
8 | npx create-react-app bs-dashboard --template file:/path/to/dev-cookbook/cra-template-brightsign-dashboard
9 | ```
10 |
11 | Once the app is ready, simply follow the instructions in the project `readme` to deploy to your device. When you run the app locally using `yarn start`, mocked `@brightsign/` JavaScript APIs will be used.
12 |
13 | **For more info**, see [/template/README.md](./template/README.md)
14 |
15 | ## Contributing to the template
16 |
17 | To contribute enhancements or fixes to the template, please follow these steps:
18 |
19 | 1. Fork and Clone: Start by forking the `dev-cookbook` repository. Then, clone your fork locally to make changes.
20 |
21 | 2. Make Changes: Navigate to the specific template you wish to improve within the dev-cookbook directory. Apply your changes there.
22 |
23 | 3. Test Locally: Test the template by instantiating using the command mentioned in the "Using the Template" section. This step ensures your changes work as expected within the create-react-app workflow.
24 |
25 | 4. Commit Changes: After testing your changes, commit them to your fork. Ensure your commit messages clearly describe the enhancements or fixes made. Push your commits to GitHub.
26 |
27 | 5. Submit a Pull Request (PR): Submit a pull request to the main dev-cookbook repository. Be sure to fill out the predefined PR sections as fully as possible.
28 |
29 | 6. Code Review: Once your PR is submitted, it will be reviewed by a BrightSign team member. Be open to feedback and ready to make further adjustments based on their suggestions.
30 |
--------------------------------------------------------------------------------
/templates/html5-app-template/src/autorun.brs:
--------------------------------------------------------------------------------
1 | function main()
2 |
3 | ' Create directory to store crash-dumps (optional)
4 | dir = CreateDirectory("SD:/brightsign-dumps")
5 | if not dir then
6 | print "Could not create directory"
7 | end if
8 |
9 | mp = CreateObject("roMessagePort")
10 | 'Enable lDWS
11 | EnableLDWS()
12 | ' Enable SSH
13 | EnableSSH()
14 | ' Create HTML Widget
15 | widget = CreateHTMLWidget(mp)
16 | widget.Show()
17 |
18 | 'Event Loop
19 | while true
20 | msg = wait(0,mp)
21 | print "msg received - type=";type(msg)
22 |
23 | if type(msg) = "roHtmlWidgetEvent" then
24 | print "msg: ";msg
25 | end if
26 | end while
27 |
28 | end function
29 |
30 | function CreateHTMLWidget(mp as object) as object
31 | ' Enable Web Inspector
32 | reg = CreateObject("roRegistrySection","html")
33 | reg.Write("enable_web_inspector","1")
34 | reg.Flush()
35 |
36 | ' Get Screen Resolution
37 | vidmode = CreateObject("roVideoMode")
38 | width = vidmode.GetResX()
39 | height = vidmode.GetResY()
40 |
41 | r = CreateObject("roRectangle",0,0,width,height)
42 |
43 | ' Create HTML Widget config
44 | config = {
45 | nodejs_enabled: true
46 | inspector_server: {
47 | port: 3000
48 | }
49 | url: "file:///sd:/dist/index.html"
50 | port: mp
51 | }
52 |
53 | ' Create HTML Widget
54 | h = CreateObject("roHtmlWidget",r,config)
55 | return h
56 |
57 | end function
58 |
59 | function EnableLDWS()
60 |
61 | registrySection = CreateObject("roRegistrySection", "networking")
62 |
63 | if type(registrySection) = "roRegistrySection" then
64 |
65 | registrySection.Write("http_server", "80")
66 |
67 | end if
68 |
69 | registrySection.Flush()
70 |
71 | end function
72 |
73 | function EnableSSH()
74 |
75 | regSSH = CreateObject("roRegistrySection", "networking")
76 |
77 | if type(regSSH) = "roRegistrySection" then
78 |
79 | regSSH.Write("ssh","22")
80 |
81 | endif
82 |
83 | n = CreateObject("roNetworkConfiguration", 0)
84 | n.SetLoginPassword("password")
85 | n.Apply()
86 |
87 | regSSH.Flush()
88 |
89 | end function
--------------------------------------------------------------------------------
/examples/node-simple-server/src/app.test.js:
--------------------------------------------------------------------------------
1 | const http = require("http");
2 | const main = require("./app.js");
3 |
4 | describe("HTTP Server Response", () => {
5 | let server;
6 | const port = 13131;
7 |
8 | beforeAll(async () => {
9 | server = await main();
10 | });
11 |
12 | afterAll(() => {
13 | if (server) {
14 | server.close();
15 | }
16 | });
17 |
18 | it("should respond with JSON containing mocked device info", async () => {
19 | const response = await new Promise((resolve, reject) => {
20 | http.get(`http://localhost:${port}/api/device-info`, (res) => {
21 | expect(res.statusCode).toBe(200);
22 | expect(res.headers["content-type"]).toBe("application/json");
23 |
24 | let data = "";
25 | res.on("data", (chunk) => {
26 | data += chunk;
27 | });
28 | res.on("end", () => {
29 | const receivedData = JSON.parse(data);
30 |
31 | // Verify that the response contains the mocked device info
32 | expect(receivedData.model).toBe("MockModel");
33 | expect(receivedData.osVersion).toBe("MockOSVersion");
34 | expect(receivedData.serialNumber).toBe("MockSerialNumber");
35 | resolve(receivedData);
36 | });
37 | }).on("error", (err) => {
38 | reject(err);
39 | });
40 | });
41 | expect(response).toBeDefined();
42 | });
43 |
44 | it("should return 404 for non-existent files", async () => {
45 | await new Promise((resolve, reject) => {
46 | http.get(
47 | `http://localhost:${port}/non-existent-file.txt`,
48 | (res) => {
49 | expect(res.statusCode).toBe(404);
50 | resolve();
51 | }
52 | ).on("error", (err) => {
53 | reject(err);
54 | });
55 | });
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/templates/README.md:
--------------------------------------------------------------------------------
1 | # Templates
2 |
3 | Welcome to the templates directory! This guide will help you understand the purpose of the templates and where to find specific instructions for each template.
4 |
5 | ## Purpose of Templates
6 |
7 | The templates in this directory are designed to provide you with a starting point for various types of applications. They include pre-configured setups and best practices to help you quickly bootstrap your projects.
8 |
9 | ## Available Templates
10 |
11 | ### cra-template-brightsign-app
12 |
13 | This template helps you create a React application tailored for BrightSign devices.
14 |
15 | - **Location**: [`templates/cra-template-brightsign-app`](./cra-template-brightsign-app/)
16 | - **Instructions**: Refer to the README file within the template directory for detailed setup and usage instructions.
17 |
18 | ### cra-template-brightsign-dashboard
19 |
20 | This template helps you create a React application tailored for BrightSign devices.
21 |
22 | - **Location**: [`templates/cra-template-brightsign-dashboard`](./cra-template-brightsign-dashboard/)
23 | - **Instructions**: Refer to the README file within the template directory for detailed setup and usage instructions.
24 |
25 | ### html5-app-template
26 |
27 | This template helps you create an html application on your BrightSign devices.
28 |
29 | - **Location**: [`templates/html5-app-template`](./html5-app-template/)
30 | - **Instructions**: Refer to the README file within the template directory for detailed setup and usage instructions.
31 |
32 | ### Other Templates
33 |
34 | Explore other templates in the `templates` directory based on your application needs. Each template comes with its own README file with specific instructions on how to set up and run the project.
35 |
36 | ## Conclusion
37 |
38 | By exploring the templates directory, you will find various templates tailored for different types of applications. Each template is designed to help you quickly set up and run your projects with minimal configuration. Refer to the README file within each template directory for detailed instructions.
39 |
40 | Happy coding!
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "workspaces": [
4 | "examples/bluetooth-scan",
5 | "examples/bs-self-updater",
6 | "examples/bs-sqlite-db",
7 | "examples/enable-ldws",
8 | "examples/html-starter",
9 | "examples/htmlwidget-iframes",
10 | "examples/indexeddb-caching",
11 | "examples/local-storage",
12 | "examples/node-simple-server",
13 | "examples/node-starter",
14 | "examples/nodejs-web-app",
15 | "examples/self-signed-certs",
16 | "examples/send-plugin-message",
17 | "templates/**"
18 | ],
19 | "dependencies": {
20 | "@babel/plugin-syntax-jsx": "^7.24.1",
21 | "@babel/preset-env": "^7.24.3",
22 | "@babel/preset-react": "^7.24.1",
23 | "@testing-library/jest-dom": "^6.4.2",
24 | "@testing-library/react": "^14.2.2",
25 | "babel-jest": "^29.7.0",
26 | "jest": "^29.7.0",
27 | "jest-environment-jsdom": "^29.7.0",
28 | "jest-transform-css": "^6.0.1",
29 | "jsdom": "20.0",
30 | "react": "^18.2.0",
31 | "react-dom": "^18.2.0"
32 | },
33 | "scripts": {
34 | "commit": "cz",
35 | "prepare": "husky install",
36 | "test": "yarn workspaces run test",
37 | "format": "yarn workspaces run format",
38 | "lint": "yarn workspaces run lint"
39 | },
40 | "license": "SEE LICENSE IN LICENSE.txt",
41 | "devDependencies": {
42 | "commitizen": "^4.3.0",
43 | "cz-conventional-changelog": "^3.3.0",
44 | "eslint": "8.57.0",
45 | "eslint-config-airbnb": "19.0.4",
46 | "eslint-config-prettier": "9.1.0",
47 | "eslint-plugin-import": "2.29.1",
48 | "eslint-plugin-jest": "27.9.0",
49 | "eslint-plugin-jsx-a11y": "6.8.0",
50 | "eslint-plugin-prettier": "4",
51 | "eslint-plugin-react": "7.34.1",
52 | "husky": "8.0.3",
53 | "prettier": "2.8.8"
54 | },
55 | "config": {
56 | "commitizen": {
57 | "path": "./node_modules/cz-conventional-changelog"
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/examples/node-simple-server/src/app.js:
--------------------------------------------------------------------------------
1 | const DIClass = require("@brightsign/deviceinfo");
2 | const http = require("http");
3 | const fs = require("fs");
4 | const path = require("path");
5 |
6 | function app() {
7 | return new Promise((resolve, reject) => {
8 | const di = new DIClass();
9 |
10 | const server = http.createServer((req, res) => {
11 | // Serve device info on /api/device-info
12 | if (req.url === "/api/device-info") {
13 | res.setHeader("Content-Type", "application/json");
14 | const jsonResponse = JSON.stringify(di);
15 | res.end(jsonResponse);
16 | return;
17 | }
18 |
19 | // Serve static files from /storage/sd/
20 | const filePath = path.join(
21 | "/storage/sd",
22 | req.url === "/" ? "index.html" : req.url
23 | );
24 | fs.readFile(filePath, (err, content) => {
25 | if (err) {
26 | res.writeHead(404);
27 | res.end("File not found");
28 | return;
29 | }
30 |
31 | // Set content type based on file extension
32 | const ext = path.extname(filePath);
33 | const contentType =
34 | {
35 | ".html": "text/html",
36 | ".js": "text/javascript",
37 | ".css": "text/css",
38 | ".json": "application/json",
39 | ".png": "image/png",
40 | ".jpg": "image/jpeg",
41 | }[ext] || "text/plain";
42 |
43 | res.writeHead(200, { "Content-Type": contentType });
44 | res.end(content);
45 | });
46 | });
47 |
48 | const port = 13131;
49 | server.listen(port, () => {
50 | console.log(`Server running at http://localhost:${port}`);
51 | resolve(server);
52 | });
53 |
54 | server.on("error", reject);
55 | });
56 | }
57 |
58 | module.exports = app;
59 |
--------------------------------------------------------------------------------
/examples/cec-interface/brightscript/autorun.brs:
--------------------------------------------------------------------------------
1 | sub Main()
2 | print "BrightScript CEC Example: Display On/Off"
3 |
4 | mp = createobject("roMessagePort")
5 | cec = CreateObject("roCecInterface", "HDMI-1") ' Modify this to be 'HDMI-1', 'HDMI-2', 'HDMI-3', or 'HDMI-4' as needed
6 | if cec = invalid then
7 | print "Failed to create roCecInterface object."
8 | return
9 | end if
10 |
11 | ' Create a HTML widget to display something on the screen
12 | r = CreateObject("roRectangle", 0, 0, 1920, 1080)
13 | config = {
14 | url: "file:///sd:/index.html",
15 | inspector_server: {
16 | port: 2999
17 | }
18 | }
19 | htmlWidget = CreateObject("roHtmlWidget", r, config)
20 | htmlWidget.SetPort(mp)
21 | htmlWidget.Show()
22 |
23 | cec.SetPort(mp)
24 |
25 | ' Optionally enable debugging for CEC messages
26 | ' cec.EnableCecDebug("SD:/cec_debug.log")
27 |
28 | ' Send Image View On (4f0d)
29 | bufferImageViewOn = CreateObject("roByteArray")
30 | bufferImageViewOn.FromHexString("4f0d")
31 | cec.SendRawMessage(bufferImageViewOn)
32 | print "Sent Image View On: " + bufferImageViewOn.ToHexString()
33 |
34 | ' Wait 15 seconds, then send Standby
35 | sleep(15000)
36 | bufferStandby = CreateObject("roByteArray")
37 | bufferStandby.FromHexString("4f36")
38 | cec.SendRawMessage(bufferStandby)
39 | print "Sent Standby: " + bufferStandby.ToHexString()
40 |
41 | ' Optionally, uncomment the block below to repeat the cycle every 15 seconds
42 | ' while true
43 | ' sleep(15000)
44 | ' cec.SendRawMessage(bufferImageViewOn)
45 | ' print "Sent Image View On: " + bufferImageViewOn.ToHexString()
46 |
47 | ' sleep(15000)
48 | ' cec.SendRawMessage(bufferStandby)
49 | ' print "Sent Standby: " + bufferStandby.ToHexString()
50 | ' end while
51 |
52 | ' Message loop to handle any incoming messages (if needed)
53 | while true
54 | msg = wait(0, mp)
55 | if type(msg) = "roMessagePortEvent"
56 | ? "Message received: ";msg
57 | end if
58 | end while
59 | end sub
60 |
--------------------------------------------------------------------------------
/examples/provisioning-server/content/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Simple Example HTML App
7 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | Congratulations on setting up a Provisioning & Recovery server
38 | with BrightSign!
39 |
40 |
41 |
42 |
43 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/examples/bs-self-updater/README.md:
--------------------------------------------------------------------------------
1 | # bs-self-updater
2 |
3 | A simple self-updating application written using TypeScript and designed to run on BrightSign players.
4 |
5 | ## Overview
6 |
7 | `bs-app-updater` is a lightweight utility that allows an application running on BrightSign players to update itself by downloading and applying a new `autorun.zip` package. This enables remote updates and maintenance of deployed applications with minimal effort.
8 |
9 | ## Features
10 |
11 | - Calls a configurable server endpoint to download the `autorun.zip` application to run on the player.
12 | - See `SERVER_URL` in `index.ts`.
13 | - Unzips the downloaded package and executes the `autorun.brs` file from the unzipped package.
14 | - Makes periodic calls to the server to check for any updates to the `autorun.zip` file.
15 |
16 | ## Getting Started
17 |
18 | ### Prerequisites
19 |
20 | - Node.js (v18 or later recommended)
21 | - Yarn (for package management)
22 |
23 | ### Installation
24 |
25 | 1. Clone this repository or copy the code to your project directory.
26 | 2. Install dependencies (if any):
27 | ```sh
28 | yarn install
29 | ```
30 |
31 | ### Build
32 |
33 | Run the following command to compile the TypeScript code into JavaScript:
34 |
35 | ```sh
36 | yarn build
37 | ```
38 |
39 | ### Deploy and run on player
40 |
41 | 1. Copy the `autorun.brs` file and `dist/index.js` files to the root of an SD card.
42 | - /storage/sd/autorun.brs
43 | - /storage/sd/index.js
44 | 2. Insert the SD card into the BrightSign player.
45 | 3. Boot up the player.
46 |
47 | ## Optional: Local Node.js Server for Testing
48 |
49 | You can run a simple Node.js server locally to serve the `autorun.zip` file expected by the JS app. This is useful for development and testing.
50 |
51 | ### To start the server:
52 |
53 | 1. Navigate to the `server` directory:
54 | ```sh
55 | cd server
56 | ```
57 | 2. Install dependencies:
58 | ```sh
59 | yarn install
60 | ```
61 | 3. Start the server:
62 | ```sh
63 | yarn start
64 | ```
65 | 4. The server will listen on port 7000 by default and has a single endpoint to serve the `autorun.zip` file:
66 | ```
67 | http://localhost:7000/autorun.zip
68 | ```
69 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-dashboard/template/src/autorun.brs:
--------------------------------------------------------------------------------
1 | function main()
2 |
3 | ' Create directory to store crash-dumps (optional)
4 | dir = CreateDirectory("SD:/brightsign-dumps")
5 | if not dir then
6 | print "Could not create directory"
7 | end if
8 |
9 | ' Create Message Port
10 | mp = CreateObject("roMessagePort")
11 | ' Enable lDWS
12 | enableLDWS()
13 | ' Enable SSH
14 | enableSSH()
15 |
16 | ' Initialize roNodeJs with path or correct filename, whether webpack is used or not.
17 | node_js = CreateObject("roNodeJs", "sd:/dist/backend.js", {message_port: mp, node_arguments: ["--inspect=0.0.0.0:2999"], arguments: []})
18 |
19 | ' Create HTML Widget
20 | widget = createHTMLWidget(mp)
21 | widget.Show()
22 |
23 | ' Event Loop
24 | while true
25 | msg = wait(0,mp)
26 | print "msg received - type=";type(msg)
27 |
28 | if type(msg) = "roHtmlWidgetEvent" then
29 | print "msg: ";msg
30 | end if
31 | end while
32 |
33 | end function
34 |
35 | function createHTMLWidget(mp as object) as object
36 | ' Enable Web Inspector
37 | reg = CreateObject("roRegistrySection","html")
38 | reg.Write("enable_web_inspector","1")
39 | reg.Flush()
40 |
41 | ' Get Screen Resolution
42 | vidmode = CreateObject("roVideoMode")
43 | width = vidmode.GetResX()
44 | height = vidmode.GetResY()
45 |
46 | r = CreateObject("roRectangle",0,0,width,height)
47 |
48 | ' Create HTML Widget config
49 | config = {
50 | nodejs_enabled: true
51 | inspector_server: {
52 | port: 3000
53 | }
54 | url: "http://localhost:8020"
55 | port: mp
56 | }
57 |
58 | ' Create HTML Widget
59 | h = CreateObject("roHtmlWidget",r,config)
60 | return h
61 |
62 | end function
63 |
64 | function enableLDWS()
65 |
66 | registrySection = CreateObject("roRegistrySection", "networking")
67 |
68 | if type(registrySection) = "roRegistrySection" then
69 |
70 | registrySection.Write("http_server", "80")
71 |
72 | end if
73 |
74 | registrySection.Flush()
75 |
76 | end function
77 |
78 | function enableSSH()
79 |
80 | regSSH = CreateObject("roRegistrySection", "networking")
81 |
82 | if type(regSSH) = "roRegistrySection" then
83 |
84 | regSSH.Write("ssh","22")
85 |
86 | endif
87 |
88 | n = CreateObject("roNetworkConfiguration", 0)
89 | n.SetLoginPassword("password")
90 | n.Apply()
91 |
92 | regSSH.Flush()
93 |
94 | end function
--------------------------------------------------------------------------------
/examples/self-signed-certs/README.md:
--------------------------------------------------------------------------------
1 | # Self-Signed Certificate Handling Example
2 |
3 | ## Introduction
4 |
5 | This example demonstrates how to handle self-signed certificates when communicating with BrightSign players via the local Diagnostic Web Server (DWS). BrightSign players use self-signed certificates for HTTPS communication, which requires configuring your HTTP client to accept these certificates for successful player communication and management.
6 |
7 | ## How it Works
8 |
9 | 1. **Certificate Generation**: BrightSign players automatically generate self-signed certificates for secure HTTPS communication
10 | 2. **Client Configuration**: Standard HTTP clients reject self-signed certificates by default for security reasons
11 | 3. **Agent Setup**: The example creates an HTTPS agent with `rejectUnauthorized: false` to accept self-signed certificates
12 | 4. **API Communication**: Uses the configured agent to make secure requests to the player's DWS endpoints
13 |
14 | ## How to Run the Example
15 |
16 | ### Prerequisites
17 |
18 | 1. **Node.js Environment**: Ensure you have Node.js installed on your development machine
19 | 2. **Network Connection**: Your computer and BrightSign player should be on the same network
20 | 3. **Player IP Address**: Know the IP address of your BrightSign player
21 |
22 | ### Steps to Run
23 |
24 | 1. **Install Dependencies**:
25 | ```bash
26 | npm install undici
27 | ```
28 |
29 | 2. **Update Player IP**:
30 | - Open `index.js`
31 | - Replace `192.168.1.100` with your player's actual IP address
32 |
33 | 3. **Run the Example**:
34 | ```bash
35 | node index.js
36 | ```
37 |
38 | 4. **Expected Output**:
39 | - If successful, you'll see the player's status response in JSON format
40 | - Any communication errors will be displayed with descriptive error messages
41 |
42 | ## Files Structure
43 |
44 | - **index.js**: Main example file showing how to configure undici Agent for self-signed certificates
45 | - **README.md**: This documentation file
46 |
47 | ## Important Security Notes
48 |
49 | - Only disable certificate verification (`rejectUnauthorized: false`) for trusted BrightSign player communication
50 | - Never use this configuration for external or untrusted HTTPS endpoints
51 | - This approach is specifically designed for local development and player management scenarios
52 | - Default DWS port is `8443` for HTTPS communication
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-app/template/src/autorun.brs:
--------------------------------------------------------------------------------
1 | function main()
2 |
3 | ' Create directory to store crash-dumps (optional)
4 | dir = CreateDirectory("SD:/brightsign-dumps")
5 | if not dir then
6 | print "Could not create directory"
7 | end if
8 |
9 | mp = CreateObject("roMessagePort")
10 |
11 | 'Enable lDWS
12 | enableLDWS()
13 |
14 | ' Enable SSH
15 | enableSSH()
16 |
17 | ' Initialize roNodeJs with path or correct filename, whether webpack is used or not.
18 | node_js = CreateObject("roNodeJs", "sd:/dist/backend.js", {message_port: mp, node_arguments: ["--inspect=0.0.0.0:2999"], arguments: []})
19 |
20 | ' Create HTML Widget
21 | widget = createHTMLWidget(mp)
22 | widget.Show()
23 |
24 | 'Event Loop test
25 | while true
26 | msg = wait(0,mp)
27 | print "msg received - type=";type(msg)
28 |
29 | if type(msg) = "roHtmlWidgetEvent" then
30 | print "msg: ";msg
31 | end if
32 | end while
33 |
34 | end function
35 |
36 |
37 | function createHTMLWidget(mp as object) as object
38 |
39 | ' Enable Web Inspector
40 | reg = CreateObject("roRegistrySection","html")
41 | reg.Write("enable_web_inspector","1")
42 | reg.Flush()
43 |
44 | ' Get Screen Resolution
45 | vidmode = CreateObject("roVideoMode")
46 | width = vidmode.GetResX()
47 | height = vidmode.GetResY()
48 |
49 | r = CreateObject("roRectangle",0,0,width,height)
50 |
51 | ' Create HTML Widget config
52 | config = {
53 | nodejs_enabled: true
54 | inspector_server: {
55 | port: 3000
56 | }
57 | url: "http://localhost:8020"
58 | port: mp
59 | }
60 |
61 | ' Create HTML Widget
62 | h = CreateObject("roHtmlWidget",r,config)
63 | return h
64 |
65 | end function
66 |
67 |
68 | function enableLDWS()
69 |
70 | registrySection = CreateObject("roRegistrySection", "networking")
71 |
72 | if type(registrySection) = "roRegistrySection" then
73 | registrySection.Write("http_server", "80")
74 | end if
75 |
76 | registrySection.Flush()
77 |
78 | end function
79 |
80 |
81 | function enableSSH()
82 | regSSH = CreateObject("roRegistrySection", "networking")
83 |
84 | if type(regSSH) = "roRegistrySection" then
85 | regSSH.Write("ssh","22")
86 | endif
87 |
88 | n = CreateObject("roNetworkConfiguration", 0)
89 | n.SetLoginPassword("password")
90 | n.Apply()
91 |
92 | regSSH.Flush()
93 |
94 | end function
--------------------------------------------------------------------------------
/templates/html5-app-template/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bs-html5-app-template",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "src/index.ts",
6 | "scripts": {
7 | "start": "IS_DESKTOP=true NODE_PATH=./dist node dist/bundle.js",
8 | "build:dev": "npm run clean && webpack --mode development",
9 | "build:prod": "npm run clean && webpack --mode production --node-env=production",
10 | "clean": "rm -rf dist",
11 | "reinstall": "npm run clean && rm -rf node_modules && npm install",
12 | "lint": "eslint --no-error-on-unmatched-pattern --config .eslintrc src/**/*.{ts,tsx}",
13 | "lint:fix": "eslint --fix src --ext js,jsx,ts,tsx,json",
14 | "format:check": "prettier 'src/**/*.{js,jsx,ts,tsx,css,md,json}' --check --config ../.prettierrc.js --cache --cache-location=../prettiercache",
15 | "format": "prettier --write 'src/**/*.{js,jsx,ts,tsx,css,md,json}' --config ../.prettierrc.js && npm run lint",
16 | "test": "jest --config jest.config.js src/",
17 | "publish-package": "npm publish --access public"
18 | },
19 | "repository": {
20 | "url": "https://github.com/brightsign/dev-cookbook.git",
21 | "type": "git",
22 | "directory": "html5-app-template/"
23 | },
24 | "publishConfig": {
25 | "registry": "https://registry.npmjs.org"
26 | },
27 | "author": "BrightSign LLC",
28 | "license": "ISC",
29 | "dependencies": {
30 | "digest-fetch": "^3.1.1",
31 | "express": "^4.20.0",
32 | "form-data": "^4.0.0",
33 | "http-status-codes": "^2.3.0",
34 | "lodash": "^4.17.21",
35 | "node-fetch": "^2.7.0",
36 | "socket.io-client": "^4.7.5"
37 | },
38 | "devDependencies": {
39 | "@babel/cli": "^7.24.5",
40 | "@babel/core": "^7.24.5",
41 | "@babel/preset-typescript": "^7.24.1",
42 | "@types/express": "^4.17.21",
43 | "@types/lodash": "^4.17.0",
44 | "@types/node": "^14.18.63",
45 | "@types/node-fetch": "^2.6.11",
46 | "@typescript-eslint/eslint-plugin": "^5.62.0",
47 | "@typescript-eslint/parser": "^5.62.0",
48 | "eslint": "^7.24.0",
49 | "eslint-config-prettier": "^9.1.0",
50 | "html-webpack-plugin": "^5.6.0",
51 | "jest": "^29.7.0",
52 | "prettier": "^3.2.5",
53 | "ts-loader": "^9.5.1",
54 | "ts-node": "^10.9.2",
55 | "typescript": "5.1.6",
56 | "webpack": "^5.96.1",
57 | "webpack-cli": "^4.10.0"
58 | },
59 | "engine": {
60 | "node": "14.17.6"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-dashboard/template/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-app/template/src/logo.svg:
--------------------------------------------------------------------------------
1 | ;
8 |
--------------------------------------------------------------------------------
/examples/node-starter/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Starter Node.js HTTP Server on BrightSign Device
3 |
4 | ## Introduction
5 |
6 | This example is the second stepping stone of the starter examples to get familiar with developing with a BrightSign. The intention of this example is to show how to get a simple node application running on your player. Once the server is running, you’ll be able to access a congratulatory message by making a request to the server's `/` endpoint.
7 |
8 | The node application is defined in `index.js` and is a simplified version of the html templates we offer. In addition to the JavaScript file, there is an `autorun.brs` file which is what tells your player to run the application.
9 |
10 | ## Prerequisites
11 |
12 | 1. **Wi-Fi or Ethernet Network**: Both your BrightSign device and the computer should be connected to the same network to fully interact with this example.
13 |
14 | ## Steps to Set Up and Run the Server
15 |
16 | ### 1. Prepare the SD Card
17 |
18 | 1. Insert the SD card into your computer.
19 | 2. Since this example is simplified, there are only two files which you will need to copy to the root of the player's sd card. Copy the `index.js` and `autorun.brs` files from this directory to the root of the SD card.
20 |
21 | ### 2. Set Up the BrightSign Device
22 |
23 | 1. Eject the SD card from your computer and insert it into your BrightSign device.
24 | 2. Power on the BrightSign device and allow it to boot up.
25 | 3. The device will automatically run the `autorun.brs` file which executes the `index.js` node server.
26 |
27 | ### 3. Accessing the Server
28 |
29 | Once the device has booted and the server has started:
30 |
31 | 1. **Find the IP Address**:
32 | - Identify the IP address of your BrightSign device. This can typically be found in the device's network settings or via BrightAuthor:connected or by booting the player without the Micro SD card inserted.
33 |
34 | 2. **Interact with the Server**:
35 | - Open a terminal or command prompt on your computer.
36 | - Use the `curl` command to make a request to the server's `/` endpoint:
37 | ```bash
38 | curl http://:3000/
39 | ```
40 | Replace `` with the actual IP address of your BrightSign device.
41 |
42 | - Alternatively, you can open a web browser and navigate to:
43 | ```
44 | http://:3000/
45 | ```
46 |
47 | 3. **Expected Response**:
48 | - If the server is running correctly, you should see the following message in your terminal or browser:
49 | ```
50 | Congratulations on interacting with your BrightSign running a simple node http application!
51 | ```
52 |
53 | ### 4. Troubleshooting
54 |
55 | - **No Response**: If you don't receive a response from the server, verify that:
56 | - The BrightSign device is properly connected to the network.
57 | - The IP address is correct.
58 | - The server script is correctly copied to the SD card.
59 | - The device allows incoming connections on port `3000`.
60 |
--------------------------------------------------------------------------------
/examples/indexeddb-caching/README.md:
--------------------------------------------------------------------------------
1 | # IndexedDB Video Caching Example
2 |
3 | This example demonstrates how to use IndexedDB for caching video content in a BrightSign HTML5 application. The application creates a video playlist that intelligently caches videos in the background for smooth playback.
4 |
5 | ## File Structure
6 |
7 | - `index.html`: Main HTML application with video player and caching logic
8 | - `autorun.brs`: BrightSign autorun script which loads the HTML application
9 |
10 | ## Usage
11 |
12 | 1. Copy the `autorun.brs` and `index.html` files to the root of an SD card
13 | 2. Insert the SD card in the BrightSign player and power it on
14 | 3. The `autorun.brs` script will automatically launch the HTML application
15 |
16 | ## How It Works
17 |
18 | 1. **Database Setup**: Creates an IndexedDB database (`videos_db`) with an object store (`videos_os`) to store video blobs
19 | 2. **Playlist Loading**: Loads a predefined playlist of sample videos from Google's test video collection
20 | 3. **Smart Caching Strategy**:
21 | - First video: Downloads and plays immediately, then starts background caching
22 | - Subsequent videos: Plays from cache if available, otherwise downloads from network
23 | - Background caching: Downloads remaining videos with 1-second delays between requests
24 | 4. **Automatic Playback**: Videos play continuously with automatic progression to the next video
25 |
26 | ## Configuration Options
27 |
28 | ### BrightSign Configuration (autorun.brs)
29 | - `url`: Path to the HTML file. This can be modified to point to a remote server URL that serves the HTML file.
30 | - `mouse_enabled`: Enable/disable mouse interaction (default: false)
31 | - `javascript_enabled`: Enable/disable JavaScript (default: true)
32 | - `nodejs_enabled`: Enable/disable Node.js (default: false)
33 | - `storage_path`: Directory for local storage cache
34 | - `storage_quota`: Maximum cache size
35 |
36 | See the [roHtmlWidget](https://docs.brightsign.biz/developers/rohtmlwidget#23vJS) page for more details on configuration options.
37 |
38 | ### Video Configuration (index.html)
39 | - Modify the `fetchPlaylist()` function to add your own video URLs
40 | - Adjust the background caching delay by changing the `TIMEOUT_DELAY` value
41 | - Customize video display properties in the `displayVideo()` function
42 |
43 | ## Error Handling
44 |
45 | The application includes comprehensive error handling for:
46 | - Network connectivity issues
47 | - IndexedDB operation failures
48 | - Duplicate cache entries (ConstraintError)
49 | - Video loading and playback errors
50 |
51 | ## Performance Considerations
52 |
53 | - **Storage**: Videos are stored as blobs in IndexedDB, which uses available disk space
54 | - **Network**: Background caching uses staggered requests (1-second delays) to avoid overwhelming the network
55 | - **Memory**: Video blobs are created using `URL.createObjectURL()` for efficient memory usage
56 |
57 | ## Troubleshooting
58 |
59 | 1. **Videos not caching**: Check console for IndexedDB errors
60 | 2. **Network errors**: Verify internet connectivity and video URLs
61 | 3. **Storage full**: Check available disk space and storage quota settings
62 | 4. **Playback issues**: Ensure video formats are supported by the player
63 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-app/template/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import "./App.css";
3 |
4 | let DeviceInfo;
5 | let os;
6 |
7 | // Import mocked @brightsign modules when developing locally
8 | // When running on a device, these modules are pre-installed globally
9 | // Feel free to modify the mocks as needed for your use case.
10 | if (process.env.NODE_ENV === "development") {
11 | DeviceInfo = require("./__mocks__/@brightsign/deviceinfo");
12 | os = require("./__mocks__/os");
13 | } else {
14 | DeviceInfo = require("@brightsign/deviceinfo");
15 | os = require("os");
16 | }
17 |
18 | function App() {
19 | const networkInterfaces = os.networkInterfaces() || {};
20 | const [ipAddress, setIpAddress] = useState("");
21 | const [header, setHeader] = useState("");
22 |
23 | useEffect(() => {
24 | const interval = setInterval(async () => {
25 | const port = process.env.REACT_APP_PORT || 8020;
26 | const rawText = await fetch(`http://localhost:${port}/text`);
27 | const { text } = await rawText.json();
28 |
29 | if (text) {
30 | setHeader(text);
31 | }
32 |
33 | // Get network interface data
34 | Object.keys(networkInterfaces).forEach((interfaceName) => {
35 | networkInterfaces[interfaceName].forEach((interfaceInfo) => {
36 | if (interfaceInfo.family === "IPv4") {
37 | setIpAddress(
38 | `${interfaceName}: ${interfaceInfo.address} `
39 | );
40 | console.log(
41 | `Network Interface - ${interfaceName}: ${interfaceInfo.address}`
42 | );
43 | }
44 | });
45 | });
46 | }, 5000);
47 |
48 | return () => clearInterval(interval);
49 | });
50 |
51 | const deviceInfo = new DeviceInfo();
52 | const { model, osVersion, serialNumber } = deviceInfo;
53 |
54 | return (
55 |
79 | );
80 | }
81 |
82 | export default App;
83 |
--------------------------------------------------------------------------------
/examples/bs-sqlite-db/index.js:
--------------------------------------------------------------------------------
1 | // index.js - BrightSign JavaScript app to receive messages from BrightScript via MessagePort
2 |
3 | const MESSAGE_PORT = require("@brightsign/messageport");
4 | const bsMessage = new MESSAGE_PORT();
5 |
6 | if (bsMessage) {
7 | bsMessage.addEventListener('bsmessage', function(msg) {
8 | if (msg && typeof msg === 'object') {
9 | switch(msg.action) {
10 | case 'dbCreated': {
11 | console.log('Database created at:', msg.path);
12 | bsMessage.PostBSMessage({
13 | action: 'create',
14 | command: "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER);"
15 | });
16 | break;
17 | }
18 | case 'create': {
19 | console.log('Created table:', msg);
20 | bsMessage.PostBSMessage({
21 | action: 'insert',
22 | command: "INSERT INTO users (name, age) VALUES ('Alice', 25), ('Bob', 30), ('Charlie', 35);"
23 | });
24 | break;
25 | }
26 | case 'insert': {
27 | console.log('Inserted record:', msg);
28 | bsMessage.PostBSMessage({
29 | action: 'select',
30 | command: "SELECT id, name, age FROM users;"
31 | });
32 | break;
33 | }
34 | case 'select': {
35 | console.log('Retrieved records:', msg);
36 | if (typeof msg.result === 'string') {
37 | msg.result = JSON.parse(msg.result);
38 | }
39 | if (Array.isArray(msg.result) && msg.result.length > 0) {
40 | for (const record of msg.result) {
41 | bsMessage.PostBSMessage({
42 | action: 'delete',
43 | command: `DELETE FROM users WHERE id = ${record.id};`
44 | });
45 | }
46 | }
47 | break;
48 | }
49 | case 'delete': {
50 | console.log('Deleted record:', msg);
51 | break;
52 | }
53 | default: {
54 | console.log('Unknown message type:', msg);
55 | break;
56 | }
57 | }
58 | } else {
59 | console.log('Received non-object message:', msg);
60 | }
61 | });
62 |
63 | // Send an initial message to indicate the JS app has started.
64 | // This will be received by the BrightScript side
65 | // which will then create the database.
66 | bsMessage.PostBSMessage({
67 | action: 'ready',
68 | message: 'SQLite DB Example app has started.'
69 | });
70 |
71 | // Uncomment the line below to keep the JS app running indefinitely.
72 | // setInterval(function(){}, 10000);
73 |
74 | } else {
75 | console.log('@brightsign/messageport API not available.');
76 | }
77 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 | Welcome to the examples directory! This guide will help you get started with the provided examples. Each example is independent and can be used based on your specific needs.
4 |
5 | ## Available Examples
6 |
7 | Note that some starter examples include the creation of a `brightsign-dumps` folder, which is used by the BrightSign OS to store crash information and is very useful to BrightSign Support when troubleshooting. The creation of this folder is an optional but recommended step to help with diagnosing application issues.
8 |
9 | ### HTML & Web Storage Examples
10 |
11 | #### HTML Starter Example
12 |
13 | - **Location**: `examples/html-starter`
14 | - **Features**: Simple HTML application for BrightSign, demonstrates running HTML and displaying images using a static directory. Great for getting started with HTML on BrightSign.
15 |
16 | #### IndexedDB Caching Example
17 |
18 | - **Location**: `examples/indexeddb-caching`
19 | - **Features**: Demonstrates video caching using IndexedDB in a BrightSign HTML5 app. Implements a smart playlist and background caching for smooth video playback.
20 |
21 | #### Local Storage Example
22 |
23 | - **Location**: `examples/local-storage`
24 | - **Features**: Image slideshow that caches images in browser localStorage for persistent, smooth playback and looping.
25 |
26 | ### Node.js Examples
27 |
28 | #### Node Starter Example
29 |
30 | - **Location**: `examples/node-starter`
31 | - **Features**: Minimal Node.js HTTP server for BrightSign. Boots a simple server and responds to requests at the root endpoint.
32 |
33 | #### Node Simple Server Example
34 |
35 | - **Location**: `examples/node-simple-server`
36 | - **Features**: Advanced Node.js server with static file serving, device info REST API, Jest tests, and webpack config. Good for learning about full-featured Node.js deployments on BrightSign.
37 |
38 | ### Device & Plugin Integration Examples
39 |
40 | #### Bluetooth Scan Example
41 |
42 | - **Location**: `examples/bluetooth-scan`
43 | - **Features**: HTML+JS app for scanning Bluetooth devices on BrightSign. Requires a compatible Bluetooth adapter and uses BrightSign's proprietary JS API.
44 |
45 | #### BS Self Updater Example
46 |
47 | - **Location**: `examples/bs-self-updater`
48 | - **Features**: TypeScript utility for self-updating BrightSign apps by downloading and applying new `autorun.zip` packages from a server.
49 |
50 | #### BS SQLite DB Example
51 |
52 | - **Location**: `examples/bs-sqlite-db`
53 | - **Features**: Demonstrates SQLite database usage on BrightSign, including table creation, data insertion, querying, and cleanup via BrightScript and JavaScript communication.
54 |
55 | #### Send Plugin Message Example
56 |
57 | - **Location**: `examples/send-plugin-message`
58 | - **Features**: Shows how to send plugin messages between BrightScript and HTML/JavaScript apps, useful for integrating BrightAuthor:connected presentations with custom logic.
59 |
60 | ## Next Steps
61 |
62 | After exploring these examples, you can:
63 |
64 | 1. Combine concepts from different examples to build more complex applications
65 | 2. Add testing to your applications following the `node-simple-server` example
66 | 3. Implement plugin message communication for advanced BrightScript integration
67 |
--------------------------------------------------------------------------------
/scripts/workspace_actions.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This script runs the specifed yarn commands only on workspaces that have changes
3 | * against the `main` branch.
4 | *
5 | * Usage: node ./workspace_actions.js test format
6 | *
7 | */
8 | const { execSync } = require("child_process");
9 | const { existsSync } = require("fs");
10 | const path = require("path");
11 |
12 | const mainBranchName = "origin/main";
13 | const commandsToRun = process.argv.slice(2); // Commands passed as arguments
14 |
15 | const repoRoot = path.resolve(__dirname, "..");
16 |
17 | function exec(command, captureOutput = false) {
18 | if (captureOutput) {
19 | return execSync(command).toString().trim();
20 | } else {
21 | execSync(command, { stdio: "inherit" });
22 | }
23 | }
24 |
25 | // Get a list of all workspaces and their paths
26 | function getWorkspacesInfo() {
27 | const workspacesInfoRaw = exec(`yarn --cwd ${repoRoot} workspaces --json info`, true);
28 | try {
29 | // First, parse the JSON output to get the "data" field.
30 | const parsedOutput = JSON.parse(workspacesInfoRaw);
31 | // Then, parse the "data" field to get the actual workspaces info.
32 | const workspacesInfo = JSON.parse(parsedOutput.data);
33 | return Object.keys(workspacesInfo).reduce((acc, key) => {
34 | acc[key] = path.resolve(repoRoot, workspacesInfo[key].location);
35 | return acc;
36 | }, {});
37 | } catch (error) {
38 | console.error('Failed to parse workspaces info:', error);
39 | return {};
40 | }
41 | }
42 |
43 | // Get a list of changed files compared to the main branch
44 | function getChangedFiles() {
45 | return exec(`git -C ${repoRoot} diff --name-only ${mainBranchName}`, true).split('\n');
46 | }
47 |
48 | // Determine if a path is part of a workspace
49 | function isPathInWorkspace(filePath, workspacePath) {
50 | const fullFilePath = path.resolve(repoRoot, filePath);
51 | const isInWorkspace = fullFilePath.startsWith(workspacePath);
52 |
53 | return isInWorkspace;
54 | }
55 |
56 | // Main function to run configurable commands in changed workspaces
57 | function runCommandsInChangedWorkspaces() {
58 | const workspaces = getWorkspacesInfo();
59 | const changedFiles = getChangedFiles();
60 | const changedWorkspaces = {};
61 |
62 | // Determine which workspaces have changed
63 | for (const [workspace, workspacePath] of Object.entries(workspaces)) {
64 | if (
65 | changedFiles.some((file) => isPathInWorkspace(file, workspacePath))
66 | ) {
67 | changedWorkspaces[workspace] = workspacePath;
68 | }
69 | }
70 |
71 | // Run configurable commands in changed workspaces
72 | for (const [workspace, workspacePath] of Object.entries(
73 | changedWorkspaces
74 | )) {
75 | if (existsSync(`${workspacePath}/package.json`)) {
76 | for (const command of commandsToRun) {
77 | try {
78 | exec(`cd ${workspacePath} && yarn ${command}`);
79 | } catch (error) {
80 | console.error(
81 | `Failed to run '${command}' in workspace: ${workspace}\n${error}`
82 | );
83 | }
84 | }
85 | }
86 | }
87 | }
88 |
89 | runCommandsInChangedWorkspaces();
90 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Welcome
2 |
3 | Thank you for contributing to the BrightSign Dev Cookbook!
4 |
5 | ## Contributing Guidelines
6 |
7 | - **Code Style:** Please adhere to the coding conventions as defined in `.eslintrc`. Run `yarn format` before submitting.
8 | - **Commit Messages:** Write clear, concise commit messages that explain the changes made. Use [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/) style messages.
9 | - **Pull Requests:** For substantial changes, it's best to open an issue for discussion before submitting a pull request.
10 |
11 | We look forward to your contributions and suggestions!
12 |
13 |
14 | # How to submit changes
15 |
16 | To contribute enhancements or fixes to `dev-cookbook`, please follow these steps:
17 |
18 | 1. Fork and Clone: Start by forking the `dev-cookbook` repository. Then, clone your fork locally to make changes.
19 |
20 | 2. Make Changes: Navigate to the specific template or example you wish to improve within the `dev-cookbook` directory. Apply your changes there.
21 |
22 | 3. Test Locally: Test the template by instantiating using the command mentioned in the "Using the Template" section. This step ensures your changes work as expected within the `create-react-app` workflow. If it is an `example-*`, test your changes according to the instructions in the example Readme.
23 |
24 | 4. Commit Changes: After testing your changes, commit them to your fork. Ensure your commit messages clearly describe the enhancements or fixes made. Push your commits to GitHub.
25 |
26 | 5. Submit a Pull Request (PR): Submit a pull request to the main dev-cookbook repository. Be sure to fill out the predefined PR sections as fully as possible.
27 |
28 | 6. Code Review: Once your PR is submitted, it will be reviewed by a BrightSign team member. Be open to feedback and ready to make further adjustments based on their suggestions.
29 |
30 | # Modifying cra-template-* examples
31 |
32 | `cra-template-*` examples can be run on your development machine (via `yarn start`) for rapid iteration, with some caveats. The full functionality of `@brightsign` API modules are only available on the device, but we have added partial mock implementations for you to develop with locally. Please refer to the [JavaScript API Documentation](https://brightsign.atlassian.net/wiki/spaces/DOC/pages/370678188/JavaScript+APIs) for further details on each API so that you may extend the existing mocks.
33 |
34 | You can also extend or create new tests (`App.test.js`) and run them with `yarn run test` in each example directory `dev-cookbook`, or in every workspace by running `yarn run test` at the root of the project.
35 |
36 | If you want to contribute improvements back to `dev-cookbook`, either make your changes directly in the repo or copy them from your `create-react-app` project after you have verified them locally and on a device. Be sure to maintain the same directory structure.
37 |
38 |
39 | # Git hooks
40 |
41 | `dev-cookbook` uses [husky](https://typicode.github.io/husky/) to automatically run `eslint` and `prettier` on commit to maintain code quality. There is also a hook to run tests before pushing to a remote branch. If you would like to suggest improvements, take a look at the hooks configuration in the `.husky` directory.
42 |
43 |
44 | # How to report a bug
45 |
46 | Create a new issue. Be sure to describe the bug and provide clear repro steps.
47 |
48 | # How to request new examples
49 |
50 | Create a new issue. Describe the problem you are trying to solve and why a new example would be useful. A BrightSign team member will review.
51 |
52 | # Copyright
53 |
54 | By contributing to this repo, you agree to license any code submitted under the MIT license. For larger contributions, we may request a Contributor License Agreement.
--------------------------------------------------------------------------------
/examples/bs-sqlite-db/README.md:
--------------------------------------------------------------------------------
1 | # BrightSign SQLite Database Example
2 |
3 | This example demonstrates how to use SQLite database functionality in a BrightSign application, showing the communication between BrightScript and JavaScript for database operations.
4 |
5 | ## Overview
6 |
7 | The example showcases:
8 | - Creating a SQLite database
9 | - Creating tables with SQL commands
10 | - Inserting records into the database
11 | - Querying records from the database
12 | - Deleting records from the database
13 | - Proper database cleanup and connection management
14 |
15 | ## Files
16 |
17 | - `autorun.brs` - BrightScript application that handles database operations
18 | - `index.js` - JavaScript application that sends SQL commands via MessagePort
19 |
20 | ## How It Works
21 |
22 | 1. **Initialization**: The BrightScript application (`autorun.brs`) starts and creates a Node.js instance running `index.js`
23 | 2. **Database Creation**: When the JavaScript app signals it's ready, BrightScript creates a SQLite database at `SD:/example.db`
24 | 3. **Table Creation**: JavaScript sends a command to create a `users` table with columns for ID, name, and age
25 | 4. **Data Insertion**: Sample user records are inserted into the database
26 | 5. **Data Retrieval**: All records are queried from the database
27 | 6. **Data Deletion**: Each retrieved record is deleted from the database
28 | 7. **Cleanup**: The database connection is properly closed when the application exits
29 |
30 | ## Database Schema
31 |
32 | The example creates a simple `users` table:
33 |
34 | ```sql
35 | CREATE TABLE IF NOT EXISTS users (
36 | id INTEGER PRIMARY KEY AUTOINCREMENT,
37 | name TEXT,
38 | age INTEGER
39 | );
40 | ```
41 |
42 | ## Sample Data
43 |
44 | The example inserts the following sample records:
45 | - Alice, age 25
46 | - Bob, age 30
47 | - Charlie, age 35
48 |
49 | ## Key Features
50 |
51 | ### BrightScript Side (`autorun.brs`)
52 | - **Database Management**: Creates and manages SQLite database connection
53 | - **SQL Statement Execution**: Handles CREATE, INSERT, SELECT, and DELETE operations
54 | - **Result Processing**: Processes query results and formats them for JavaScript consumption
55 | - **Error Handling**: Includes error checking for database operations
56 | - **Resource Cleanup**: Properly closes database connections on exit
57 |
58 | ### JavaScript Side (`index.js`)
59 | - **MessagePort Communication**: Uses `@brightsign/messageport` for BrightScript communication
60 | - **Command Orchestration**: Sends SQL commands in logical sequence
61 | - **Data Processing**: Parses and processes database results
62 | - **Event-Driven Architecture**: Responds to database operation completion events
63 |
64 | ## Running the Example
65 |
66 | 1. Copy the `autorun.brs` and `index.js` files to the root of your BrightSign player's SD card
67 | 2. Power on or restart your BrightSign player
68 | 3. The application will automatically start and demonstrate the database operations
69 | 4. Check the device logs to see the SQL operations being performed
70 |
71 | ## Expected Output
72 |
73 | The application will log information about each database operation:
74 | - Database creation confirmation
75 | - Table creation success
76 | - Record insertion confirmations
77 | - Retrieved records display
78 | - Record deletion confirmations
79 |
80 | ## Notes
81 |
82 | - The database file is created at `SD:/example.db`
83 | - The application demonstrates a complete CRUD (Create, Read, Update, Delete) cycle
84 | - Error handling is included for robust database operations
85 | - The database connection is automatically closed when the Nodejs application exits
86 | - Results from SELECT queries are formatted as JSON strings for JavaScript processing
87 |
88 | This example serves as a foundation for building more complex database-driven BrightSign applications.
--------------------------------------------------------------------------------
/examples/bluetooth-scan/README.md:
--------------------------------------------------------------------------------
1 | # Bluetooth scan example on BrightSign players
2 |
3 | ## Introduction
4 |
5 | The intention of this example is to show how to get a Bluetooth scanning application (HTML+JS) running on your BrightSign player.
6 |
7 | The application is defined in `index.html` which also includes a `script` section for the JavaScript bits. There is an `autorun.brs` file which tells the player to run the application.
8 |
9 | Note that this example cannot be ran on a browser as it uses BrightSign's proprietary JavaScript API.
10 |
11 | ## Prerequisites
12 |
13 | 1. **Bluetooth adapter**: The BrightSign player should have a Bluetooth adapter to scan for nearby devices. See [this page](https://www.brightsign.biz/wp-content/uploads/2023/04/Wifi-Modules-Bluetooth-Datasheet.pdf) for more information on BrightSign Wi-Fi and Bluetooth modules.
14 |
15 | 2. [optional] **Wi-Fi or Ethernet Network**: ONLY applicable if using the [Automated Transfer](#2-automated-transfer-using-brightsign-cli-for-dws) method below. Both your BrightSign player and the computer should be connected to the same Wi-Fi or Ethernet network.
16 |
17 | ## Steps to Set Up and Run the Application
18 |
19 | After the application is ready to be deployed, you need to transfer the required files to your BrightSign player. There are 2 ways of doing this:
20 |
21 | ### 1. Manual Transfer
22 | 1. Copy the `autorun.brs` and `index.html` files at the root of the SD card.
23 | 2. Insert the SD card into the player.
24 | 3. Reboot the player.
25 |
26 | ### 2. Automated Transfer Using BrightSign CLI for DWS
27 | BrightSign's player CLI: [player-CLI](https://www.npmjs.com/package/@brightsign/bsc). To deploy this app with the CLI:
28 |
29 | Configure the CLI by choosing a name for your player and passing your player's information:
30 |
31 | `bsc local player --add --player playerName --ip ip-address --user username --pass password --storage sd`
32 |
33 | **Note**: The "username" and "password" are the credentials for the [Local DWS](#3-local-dws-ui) of the BrightSign player.
34 |
35 | This is an example command for pushing files to your player:
36 |
37 | `bsc local file --upload --player playerName --file ./path-to-your-file --destination sd/path-on-player`
38 |
39 | ### 3. Local DWS UI
40 | To access the Local DWS, go to the IP address of your player in a web browser. The default username is `"admin"` and the password is the player serial number.
41 |
42 | **Note**: If you don't know the IP address of the player, you can boot up the player WITHOUT an SD card, and after a few seconds, the splash screen will display it, along with the player's serial number and OS version.
43 |
44 | 1. Once logged in to the DWS, go to the `SD` tab.
45 | 2. Click on the `Browse` button in the `Upload Files` section.
46 | 3. Select the `autorun.brs` and `index.html` files from your computer.
47 | 4. Click on the `Upload` button to transfer the files to the player.
48 | 5. Once the files are uploaded, you can reboot the player to run the application.
49 | 6. You can also reboot the player using the `Reboot` button under the `Control` tab.
50 |
51 | ## Troubleshooting
52 |
53 | - **No HTML content**: If you don't see anything on the display attached to the player, verify that:
54 | - The BrightSign player is connected to a display via an HDMI cable.
55 | - The required files are correctly copied to the root of the SD card.
56 |
57 | - **Bluetooth scanning not working**: If the Bluetooth scanning is not working, verify that:
58 | - The BrightSign player has a Bluetooth adapter attached.
59 | - The player is at least on OS v9.0.199.
60 |
61 | In order to troubleshoot any issues, it is recommended to use the BrightSign Shell. The shell can be accessed by connecting a serial cable to the BrightSign player and using a terminal emulator (e.g. PuTTY) to connect to the player. More details can be found [here](https://docs.brightsign.biz/space/DOC/1988100153/BrightSign+Shell).
62 |
63 | If a serial cable is not available, you can make use of [telnet or SSH](https://docs.brightsign.biz/space/DOC/370673607/Telnet+and+SSH) as well.
--------------------------------------------------------------------------------
/docs/node-js-notes.md:
--------------------------------------------------------------------------------
1 | # Using Node.js with your project
2 |
3 | As you work with the sample code in this repo, you will find references to `@brightsign` JavaScript modules. These modules provide BrightSign specific functionality and are built in to the device firmware. These modules can be integrated with your code to provide rich experiences on the device.
4 |
5 | There are two main ways to run Node.js on your Brightsign device.
6 |
7 | # roHtmlWidget
8 |
9 | Create an [roHtmlWidget](https://brightsign.atlassian.net/wiki/spaces/DOC/pages/370672896/roHtmlWidget) object using BrightScript in your `autorun.brs` with the `nodejs_enabled` flag set to `true`. This creates a Chromium HTML renderer instance that can access the BrightSign JS modules. Set the URL of the widget to be an externally hosted page or to a file on your local filesystem. You can then set the size and position of the widget to be displayed on your device. This essentially creates a floating browser window that displays content of your choice.
10 |
11 | ```
12 | aa=createobject("roassociativearray")
13 | aa.url="http://test-server/index.html"
14 | aa.nodejs_enabled=true
15 |
16 | r=createobject("rorectangle",0,0,1920,1080)
17 | h=createobject("rohtmlwidget",r,aa)
18 | h.show()
19 | ```
20 |
21 | Keep in mind that any JavaScript code in your `roHtmlWidget` will only be run as long as the widget is active. You can create and display multiple instances at once.
22 |
23 | If you created a widget and it is not visible, the following could be happening:
24 |
25 | 1. `show()` is `false`
26 | 2. `roRectangle` has x and y values which are off the screen,
27 | 3. Your `index.html` doesn't render anything and contains a "visual passthrough"
28 | 4. Your content is at 100% opacity.
29 |
30 | 
31 |
32 |
33 | # roNodeJs
34 |
35 | Launch a Node.js process in the background in BrightScript using [roNodeJs]( https://brightsign.atlassian.net/wiki/spaces/DOC/pages/404619466/roNodeJs). Supply any arguments you might need. Your code will need to be on your local filesystem.
36 |
37 | ```
38 | node = CreateObject("roNodeJs", "index.js", {message_port:my_message_port, node_arguments: ["arg"], arguments: ["arg1", "arg2"]})
39 | ```
40 |
41 | Unlike `roHtmlWidget`, an `roNodeJs` object will run in the background uninterrupted. You can use this for long running processes like gathering metrics or running a web server. You can even pass messages between this process and code running in an `roHtmlWidget` using an `roNodeJsEvent`. The message would be received in by the OS layer's `roMessagePort` as an `roNodeJsEvent`. To send it to an `roHtmlWidget` you must execute `roMessagePort.SendJsMessage()` in your widget.
42 |
43 | The `cra-template-` examples use an `roNodeJs` to host a static server (see `template/src/server/index.js`) which serves the frontend HTML and other files. In `autorun.brs` you can see how the config is actually pointed at the Node.js process running the server, not an HTML file.
44 |
45 | You can also inspect the running process by setting `"--inspect=0.0.0.0:2999"` in `node_arguments`. In Chrome, open DevTools at `chrome://inspect` and configure your network targets.
46 |
47 | ```
48 | config = {
49 | nodejs_enabled: true
50 | inspector_server: {
51 | port: 3000
52 | }
53 | url: "http://localhost:8020"
54 | port: mp
55 | }
56 | ```
57 |
58 | 
59 |
60 |
61 | # Conclusion
62 |
63 | Use `roNodeJs` if you need a long running background process or have more complex needs. Use `roHtmlWidget` with Node.js enabled for browser-based apps. Both have access to BrightSign [JavaScript APIs](https://brightsign.atlassian.net/wiki/spaces/DOC/pages/370678188/JavaScript+APIs).
64 |
65 | ### References / Further Reading
66 |
67 | roNodeJs: https://brightsign.atlassian.net/wiki/spaces/DOC/pages/404619466/roNodeJs
68 | roHtmlWidget: https://brightsign.atlassian.net/wiki/spaces/DOC/pages/370672896/roHtmlWidget
69 | JavaScript APIs: https://brightsign.atlassian.net/wiki/spaces/DOC/pages/370678188/JavaScript+APIs
70 | roNodeJsEvent: https://brightsign.atlassian.net/wiki/spaces/DOC/pages/404621871/roNodeJsEvent
71 |
72 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-dashboard/template/README.md:
--------------------------------------------------------------------------------
1 | ## Intro
2 |
3 | This sample shows how you can run a dashboard that hosts an API and a React frontend on the same port. This displays device information, API data, and can be extended for your use case.
4 |
5 | ## How it works
6 |
7 | The app builds two bundles via Webpack: `frontend.js` and `backend.js`.
8 |
9 | - `backend.js` runs an Express server that serves `index.html` and other static files as well as any API endpoints that you wish to build. This is built from the code in `src/server/index.js`.
10 | - `frontend.js` is built from `src/index.js` and contains all the React dependencies and code. `index.html` loads it from the statically hosted files.
11 |
12 | When the project is built, generated code is placed in the `/dist` directory. This code and everything in the `public` directory needs to be pushed to the device at `/sd/dist` and run by the `autorun.brs` script.
13 |
14 | ## Using the sample
15 |
16 | To deploy your code, you will need the device to be configured for DWS access, the device's IP address, and its serial number. Simply navigate to the root of the directory and run the following command to push the code and restart the device.
17 |
18 | ```
19 | PLAYER=your.device.ip.address PLAYER_PW=XCG31D001234 npm run put:prod
20 | ```
21 |
22 | Updating the header text
23 |
24 | ```
25 | curl -d '{"text": "hello world" }' -H 'Content-Type: application/json' -X POST your.device.ip.address:8020/text
26 | ```
27 |
28 | ## Debugging
29 |
30 | To debug your web application you can enable the `Inspector Server` allowing the Chrome DevTools to connect over the local network. See the _Debugging Webpages_ section in [HTML Best Practices](https://brightsign.atlassian.net/wiki/x/ngIYFg) for more info.
31 |
32 | ## Bundling
33 |
34 | For most use cases, leveraging a bundling tool like [webpack](https://webpack.js.org/) is recommended to minimize the dependency graph from one or more entry points and many modules, into either 1 or a few entry points.
35 |
36 | ## Deployment for Local Development
37 |
38 | There are many means of deploying software to a BrightSign player. Common methods include:
39 |
40 | 1. Push your software to the Local Diagnostic Web Server (DWS) either through the Local DWS UI or a REST Client Tool.
41 | 1. The `~/scripts/put` shell script could be leveraged.
42 | 2. Coming soon: Improved tooling to push software to the Player independent of BrightAuthor:connected
43 |
44 | ## Deploy through an Authoring Application
45 |
46 | 1. Leverage a CMS to run HTML, CSS, JS and / or Node.js managing the application as content is running.
47 | 2. Author a BrightAuthor:connected Presentation to load your local application(s).
48 |
49 | HTML 5 Widget in a Presentation loads locally provided .html file. The .html file is the entry file for HTML, CSS, JavaScript. To execute within the Node.js runtime, then _Enable Node.js_.
50 |
51 | A Node.js Zone in a Presentation is used to execute within the Node.js runtime when the entry file is JavaScript.
52 |
53 | ## How to check for logs
54 |
55 | Using the BrightSign CLI:
56 |
57 | ```
58 | bsc getlogs playerName | grep "my message" | tail
59 | ```
60 |
61 | If you are not using the BrightSign CLI, you can check for `console.log` messages in your device log file.
62 |
63 | 1. Find your device in BrightAuthor: Connected and click the gear icon.
64 | 2. Go to the "LOG" tab and click "Download Log"
65 |
66 | Search for a string like the following:
67 |
68 | `[ 12.858] [INFO] [source file:///sd:/dist/bundle.js:2]: console log message...`
69 |
70 | You can also use SSH to access the device and view log messages in realtime.
71 |
72 | ### Building on Mac M1
73 |
74 | You might see an error like `npm ERR! Error: Cannot find module 'node-bin-darwin-arm64/package.json'`
75 |
76 | Run the following commands
77 |
78 | ```
79 | > node -v
80 | v14.17.6
81 | > node -p process.arch
82 | arm64
83 | > arch -x86_64 zsh
84 | > nvm remove 14.17.6 && nvm install 14.17.6
85 | ```
86 |
87 | You might need to do this each time you restart your terminal.
88 |
89 | https://stackoverflow.com/questions/68896696/having-trouble-installing-npm-on-mac-m1
90 |
--------------------------------------------------------------------------------
/templates/cra-template-brightsign-app/template/README.md:
--------------------------------------------------------------------------------
1 | ## Intro
2 |
3 | This sample shows how you can run a simple Node.js service that hosts an API and a React frontend on the same port. You can update the displayed text using the `/text` endpoint.
4 |
5 | ## How it works
6 |
7 | The app builds two bundles via Webpack: `frontend.js` and `backend.js`.
8 |
9 | - `backend.js` runs an Express server that serves `index.html` and other static files as well as any API endpoints that you wish to build. This is built from the code in `src/server/index.js`.
10 | - `frontend.js` is built from `src/index.js` and contains all the React dependencies and code. `index.html` loads it from the statically hosted files.
11 |
12 | When the project is built, generated code is placed in the `/dist` directory. This code and everything in the `public` directory needs to be pushed to the device at `/sd/dist` and run by the `autorun.brs` script.
13 |
14 | ## Using the sample
15 |
16 | You can run the React app locally using mocked device data with `yarn start`. Once the code is pushed to the device it will use the built-in @brightsign modules for real data.
17 |
18 | To deploy your code, you will need the device to be configured for DWS access, the device's IP address, and its serial number. Simply navigate to the root of the directory and run the following command to push the code and restart the device.
19 |
20 | ```
21 | PLAYER=your.device.ip.address PLAYER_PW=XCG31D001234 npm run put:prod
22 | ```
23 |
24 | Updating the header text
25 |
26 | ```
27 | curl -d '{"text": "hello world" }' -H 'Content-Type: application/json' -X POST your.device.ip.address:8020/text
28 | ```
29 |
30 | ## Debugging
31 |
32 | To debug your web application, you can enable the`Inspector Server`, allowing the Chrome DevTools to connect over the local network. See the _Debugging Webpages_ section in [HTML Best Practices](https://brightsign.atlassian.net/wiki/x/ngIYFg) for more info.
33 |
34 | ## Bundling
35 |
36 | For most use cases, leveraging a bundling tool like [webpack](https://webpack.js.org/) is recommended to minimize the dependency graph from one or more entry points and many modules, into either 1 or a few entry points.
37 |
38 | ## Deployment for Local Development
39 |
40 | There are many means of deploying software to a BrightSign player. Common methods include:
41 |
42 | 1. Push your software to the Local Diagnostic Web Server (DWS) either through the Local DWS UI or a REST Client Tool.
43 | 1. The `~/scripts/put` shell script could be leveraged.
44 | 2. Coming soon: Improved tooling to push software to the Player independent of BrightAuthor:connected
45 |
46 | ## Deploy through an Authoring Application
47 |
48 | 1. Leverage a CMS to run HTML, CSS, JS and / or Node.js managing the application as content running.
49 | 2. Author a BrightAuthor:connected Presentation to load your local application(s).
50 |
51 | HTML 5 Widget in a Presentation loads locally provided .html file. The .html file is the entry file for HTML, CSS, JavaScript. To execute within the Node.js runtime, the _Enable Node.js_.
52 |
53 | A Node.js Zone in a Presentation is used to execute within the Node.js runtime when the entry file is JavaScript.
54 |
55 | ## How to check for logs
56 |
57 | Using the BrightSign CLI:
58 |
59 | ```
60 | bsc getlogs playerName | grep "my message" | tail
61 | ```
62 |
63 | If you are not using the BrightSign CLI, you can check for `console.log` messages in your device log file.
64 |
65 | 1. Find your device in BrightAuthor: Connected and click the gear icon.
66 | 2. Go to the "LOG" tab and click "Download Log"
67 |
68 | Search for a string like the following:
69 |
70 | `[ 12.858] [INFO] [source file:///sd:/dist/bundle.js:2]: console log message...`
71 |
72 | You can also use SSH to access the device and view log messages in realtime.
73 |
74 | ### Building on Mac M1
75 |
76 | You might see an error like `npm ERR! Error: Cannot find module 'node-bin-darwin-arm64/package.json'`
77 |
78 | Run the following commands
79 |
80 | ```
81 | > node -v
82 | v14.17.6
83 | > node -p process.arch
84 | arm64
85 | > arch -x86_64 zsh
86 | > nvm remove 14.17.6 && nvm install 14.17.6
87 | ```
88 |
89 | You might need to do this each time you restart your terminal.
90 |
91 | https://stackoverflow.com/questions/68896696/having-trouble-installing-npm-on-mac-m1
92 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [1.2.20](https://github.com/brightsign/dev-cookbook/compare/v1.2.19...v1.2.20) (2024-06-26)
4 |
5 |
6 | ### Bug Fixes
7 |
8 | * **package.json:** rename publish command ([#55](https://github.com/brightsign/dev-cookbook/issues/55)) ([11b6f73](https://github.com/brightsign/dev-cookbook/commit/11b6f7398cf086537b35399045b143e0ab96610e))
9 |
10 | ## [1.2.19](https://github.com/brightsign/dev-cookbook/compare/v1.2.18...v1.2.19) (2024-06-26)
11 |
12 |
13 | ### Bug Fixes
14 |
15 | * renaming due to package name conflict ([#53](https://github.com/brightsign/dev-cookbook/issues/53)) ([6c940de](https://github.com/brightsign/dev-cookbook/commit/6c940de7e2c57d16248293e2c75b080dff78a77c))
16 |
17 | ## [1.2.18](https://github.com/brightsign/dev-cookbook/compare/v1.2.17...v1.2.18) (2024-06-26)
18 |
19 |
20 | ### Bug Fixes
21 |
22 | * .npmrc token overrides env var token ([#51](https://github.com/brightsign/dev-cookbook/issues/51)) ([9d70660](https://github.com/brightsign/dev-cookbook/commit/9d7066052c342a8816099d78be42fb08618459e5))
23 |
24 | ## [1.2.17](https://github.com/brightsign/dev-cookbook/compare/v1.2.16...v1.2.17) (2024-06-26)
25 |
26 |
27 | ### Bug Fixes
28 |
29 | * set public package publishing flag ([#49](https://github.com/brightsign/dev-cookbook/issues/49)) ([b223e09](https://github.com/brightsign/dev-cookbook/commit/b223e09a34e40f0ff3e7f09809ccccab2d6f4841))
30 |
31 | ## [1.2.16](https://github.com/brightsign/dev-cookbook/compare/v1.2.15...v1.2.16) (2024-06-11)
32 |
33 |
34 | ### Bug Fixes
35 |
36 | * add publishconfig and repository info for npm package publish ([c64f728](https://github.com/brightsign/dev-cookbook/commit/c64f728b7dc6f92b3464833350f079d7084d9a6e))
37 |
38 | ## [1.2.15](https://github.com/brightsign/dev-cookbook/compare/v1.2.14...v1.2.15) (2024-06-10)
39 |
40 |
41 | ### Bug Fixes
42 |
43 | * rename packages for npm publishing ([9347f66](https://github.com/brightsign/dev-cookbook/commit/9347f66ff3aa02a1d5c34b4789a4c2925b84a8c9))
44 |
45 | ## [1.2.14](https://github.com/brightsign/dev-cookbook/compare/v1.2.13...v1.2.14) (2024-06-07)
46 |
47 |
48 | ### Miscellaneous Chores
49 |
50 | * release 1.2.14 ([c555268](https://github.com/brightsign/dev-cookbook/commit/c555268edba2a443cff6486c4dbfeb3655c64f3b))
51 |
52 | ## [1.2.7](https://github.com/brightsign/dev-cookbook/compare/v1.2.6...v1.2.7) (2024-05-31)
53 |
54 |
55 | ### Bug Fixes
56 |
57 | * quick publish fix ([2cc4a65](https://github.com/brightsign/dev-cookbook/commit/2cc4a65ef4fbb1e1f671aa890e37f5ae62f96faf))
58 |
59 | ## [1.2.0](https://github.com/brightsign/dev-cookbook/compare/v1.1.2...v1.2.0) (2024-05-08)
60 |
61 |
62 | ### Features
63 |
64 | * Husky Pre-commit hooks ([#18](https://github.com/brightsign/dev-cookbook/issues/18)) ([de35ee0](https://github.com/brightsign/dev-cookbook/commit/de35ee051e119fe0fa8f133abfcc83e6a72840b1))
65 |
66 | ## [1.1.2](https://github.com/brightsign/dev-cookbook/compare/v1.1.1...v1.1.2) (2024-04-19)
67 |
68 |
69 | ### Bug Fixes
70 |
71 | * update publish config and release PAT ([7985339](https://github.com/brightsign/dev-cookbook/commit/79853392c84093c5d449affdf828fc1f3b2a6fa1))
72 |
73 | ## [1.1.0](https://github.com/brightsign/dev-cookbook/compare/v1.0.2...v1.1.0) (2024-04-16)
74 |
75 |
76 | ### Features
77 |
78 | * publish to github package registry ([aef07e5](https://github.com/brightsign/dev-cookbook/commit/aef07e5f2cd9a154baff02ea3b2d9f9f28eb0868))
79 |
80 | ## 1.0.0 (2024-04-16)
81 |
82 | ### Features
83 |
84 | * Add tests for templates and nodejs examples ([#10](https://github.com/brightsign/dev-cookbook/issues/10)) ([3fe286c](https://github.com/brightsign/dev-cookbook/commit/3fe286c92a32e841b1900403d3f783565a1e9407))
85 | * issue templates ([4c1f93f](https://github.com/brightsign/dev-cookbook/commit/4c1f93f52069aa52bf7fadd44dfa3d85b415c56c))
86 |
87 |
88 | ### Bug Fixes
89 |
90 | * change from port 80 to 8020 ([f3c0196](https://github.com/brightsign/dev-cookbook/commit/f3c019603be8d13660f02d02b228382d35c6e999))
91 | * remove console ([19b507c](https://github.com/brightsign/dev-cookbook/commit/19b507c67686ffffd90f98bea36021b3881b1b4b))
92 | * template updates ([f515e9b](https://github.com/brightsign/dev-cookbook/commit/f515e9b57efa6df97daa1d8f5c3b568c9db62fe6))
93 | * update the directory path for static files ([375c879](https://github.com/brightsign/dev-cookbook/commit/375c8793fb1f77020d7b4e6f85383ac88f97ff13))
94 |
--------------------------------------------------------------------------------
/examples/send-plugin-message/pluginMessageTransfer.brs:
--------------------------------------------------------------------------------
1 | ' Note: The plugin's filename can be anything, as long as the presentation the plugin is added to has the
2 | ' appropriate filename (e.g. 'pluginMessageTransfer.brs') assigned.
3 |
4 | ' Note: the name of the plugin must be defined by the suffix '_Initialize', for example, pluginMessage
5 | ' is the name of the plugin defined by the function, pluginMessage_Initialize
6 | Function pluginMessage_Initialize(msgPort As Object, userVariables As Object, bsp as Object)
7 | 'no spaces in names
8 |
9 | print "=== pluginMessage_Initialize() - entry"
10 | pluginMessage = newPluginMessage(msgPort, userVariables, bsp)
11 | print "=== pluginMessage_Initialize() - exit"
12 |
13 | return pluginMessage
14 | End Function
15 |
16 |
17 | Function newPluginMessage(msgPort As Object, userVariables As Object, bsp as Object)
18 | print "=== newPluginMessage() - entry"
19 |
20 | s = {}
21 | s.version = 0.1
22 | s.msgPort = msgPort
23 | s.userVariables = userVariables
24 | s.bsp = bsp
25 | s.ProcessEvent = pluginMessage_ProcessEvent
26 | s.objectName = "pluginMessage_object"
27 | s.systemLog = CreateObject("roSystemLog")
28 |
29 | print "=== newPluginMessage() - exit"
30 | return s
31 | End Function
32 |
33 | ' Note: the suffix '_ProcessEvent', for example, pluginMessage
34 | ' is the name of the plugin defined by the function, pluginMessage_ProcessEvent
35 | Function pluginMessage_ProcessEvent(event As Object) as boolean
36 |
37 | ' retval should be false if the autorun should handle the event, even if the plugin also handled the event
38 | ' retval should be true if the autorun should NOT handle the event, the plugin instead handled the event
39 | retval = false
40 | m.systemLog.SendLine("=== pluginMessage_ProcessEvent() - event:")
41 | m.systemLog.SendLine(type(event))
42 | if type(event) = "roAssociativeArray" then ' Receive a message from BA
43 | if type(event["EventType"]) = "roString"
44 | if event["EventType"] = "SEND_PLUGIN_MESSAGE" then
45 | if event["PluginName"] = "pluginMessage" then
46 | pluginMessage$ = event["PluginMessage"]
47 | retval = ParsePluginMessage(pluginMessage$, m)
48 | end if
49 | end if
50 | end if
51 | else if type(event) = "roHtmlWidgetEvent" then
52 | payload = event.GetData()
53 | if payload.reason = "message" then
54 | m.systemLog.SendLine("=== Received Node message: " + payload.message.result)
55 | end if
56 | end if
57 | return retval
58 | End Function
59 |
60 |
61 | Function ParsePluginMessage(origMsg as String, h as Object) as boolean
62 |
63 | retval = false
64 | msg = lcase(origMsg)
65 | h.systemLog.SendLine("=== Received Plugin message: " + msg)
66 | r = CreateObject("roRegex", "^plugin", "i")
67 | match = r.IsMatch(msg)
68 | if match then
69 | retval = true
70 | ' split the string, !! is the field seperator
71 | ' pluginMessage!!!!
72 | r2 = CreateObject("roRegex", "!!", "i")
73 | fields = r2.split(msg)
74 | command = fields[0]
75 |
76 | if h.html = invalid then
77 | h.html = FindHTMLWidget(h.bsp)
78 | if h.html = invalid then
79 | return true
80 | end if
81 | end if
82 |
83 | if command = "pluginMessage" then
84 | serialNumber = fields[1]
85 | filename = fields[2]
86 | h.systemLog.SendLine("=== File " + filename + " showing ended and reported from player " + serialNumber)
87 | h.html.PostJsMessage({ serialNumber: serialNumber, filename: filename })
88 | end if
89 | if command = "plugin" then
90 | serialNumber = fields[2]
91 | filename = fields[3]
92 | h.systemLog.SendLine("=== File " + filename + " showing ended and reported from player 222 " + serialNumber)
93 | h.html.PostJsMessage({ serialNumber: serialNumber, filename: filename })
94 | end if
95 | end if
96 |
97 | return retval
98 | End Function
99 |
100 | Function ParseNodeMessage(origMsg as Object, h as Object) as boolean
101 | retval = false
102 | if origMsg.reason = "message" then
103 | h.systemLog.SendLine("=== Received Node message complete: " + origMsg.message.complete + " ; message: " + origMsg.message.result)
104 | end if
105 | return retval
106 | End Function
107 |
108 | ' Find the HTML widget in the bsp to ensure there is an app to receive messages
109 | Function FindHTMLWidget(bsp)
110 | for each baZone in bsp.sign.zonesHSM
111 | print baZone.loadingHtmlWidget
112 | if baZone.loadingHtmlWidget <> invalid then
113 | print "=== Found HTML!"
114 | return baZone.loadingHtmlWidget
115 | end if
116 | end for
117 |
118 | print "=== Couldn't find htmlwidget"
119 | return invalid
120 | End Function
121 |
--------------------------------------------------------------------------------
/examples/node-simple-server/README.md:
--------------------------------------------------------------------------------
1 | # Hosting a Node.js Server Example
2 |
3 | ## Overview
4 |
5 | This example is designed to help you get familiarized with running Node.js on a BrightSign player. It demonstrates how to run a Node.js server directly on the player that can serve both static files and fetch device information. Additionally, it introduces tools and configurations that go beyond the basics covered in starter examples.
6 |
7 | The core Node.js application is defined in `app.js`, which:
8 | - Serves static files from the `/storage/sd/` directory
9 | - Provides device information via a REST API endpoint
10 | - Includes proper content-type handling for common file types
11 |
12 | ## Static File Serving
13 |
14 | This example includes a built-in static file server that serves files from the `/storage/sd/` directory. The server:
15 | - Automatically serves `index.html` when accessing the root URL (`/`)
16 | - Handles common file types with appropriate content-type headers (HTML, CSS, JavaScript, images)
17 | - Returns 404 errors for files that don't exist
18 |
19 | ### Example Usage
20 |
21 | 1. Create an `index.html` file in your SD card root:
22 | ```html
23 |
24 |
25 |
26 | My BrightSign App
27 |
28 |
29 |
Hello from BrightSign!
30 |
31 |
41 |
42 |
43 | ```
44 |
45 | 2. Access your content:
46 | - Main page: `http://:13131/`
47 | - Individual files: `http://:13131/styles.css`, `http://:13131/images/logo.png`, etc.
48 |
49 | 3. Supported file types:
50 | - HTML (`.html`): `text/html`
51 | - JavaScript (`.js`): `text/javascript`
52 | - CSS (`.css`): `text/css`
53 | - JSON (`.json`): `application/json`
54 | - Images (`.png`, `.jpg`): `image/png`, `image/jpeg`
55 | - Other files: `text/plain`
56 |
57 | ## Building and Running the Application
58 |
59 | ### Step 1: Install Dependencies
60 | Ensure that Node.js is installed on your machine. From your project directory, install the necessary dependencies by running the following command:
61 | ```bash
62 | npm install
63 | ```
64 |
65 | Next, build and bundle the application:
66 | ```bash
67 | npm run build
68 | ```
69 |
70 | ### Step 2: Transfer Files to the Player
71 |
72 | #### Manual Transfer
73 | After the application is bundled, you need to transfer the required files to your BrightSign player:
74 | 1. Copy the `dist` folder to the SD card
75 | 2. Place the `autorun.brs` file at the root of the SD card
76 | 3. Place any static files (HTML, CSS, images) you want to serve in the root of the SD card
77 | 4. Insert the SD card into the player
78 | 5. Reboot the player
79 |
80 | #### Automated Transfer Using BrightSign CLI for DWS
81 | BrightSign's player CLI: [player-CLI](https://www.npmjs.com/package/@brightsign/bsc). To deploy this app with the CLI:
82 |
83 | Configure the CLI by choosing a name for your player and passing your player's information:
84 | ```sh
85 | bsc local player --add --player playerName --ip ip-address --user username --pass password --storage sd
86 | ```
87 |
88 | This is an example command for pushing files to your player:
89 | ```sh
90 | bsc local file --upload --player playerName --file ./path-to-your-file --destination sd/path-on-player
91 | ```
92 |
93 | Alternatively, there is a script that can be run that is configured for this application:
94 | ```sh
95 | npm run upload --playerName=playerName
96 | ```
97 |
98 | ### Step 3: Access the Server
99 |
100 | Once the player is running, you can access the Node.js server from a web browser. Make sure your computer is connected to the same network as the BrightSign player.
101 |
102 | #### Available Endpoints
103 |
104 | 1. Static Files:
105 | ```
106 | http://:13131/
107 | ```
108 | Serves files from the `/storage/sd/` directory. The root URL (`/`) will serve `index.html` if present.
109 |
110 | 2. Device Info API:
111 | ```
112 | http://:13131/api/device-info
113 | ```
114 | Returns JSON with device information like model, OS version, and serial number.
115 |
116 | ## Testing and mocking
117 |
118 | This example is equipped with basic mocking to make testing easier with local development. In the `__mocks__` directory, there is a `deviceinfo.js` file which defines mock values when the application is run in a development environment.
119 |
120 | To run the test(s) located in `App.test.js`, execute the following:
121 | ```bash
122 | npm run test
--------------------------------------------------------------------------------
/examples/bs-self-updater/index.ts:
--------------------------------------------------------------------------------
1 | import fetch from "node-fetch";
2 | import decompress from "decompress";
3 | import md5File from "md5-file";
4 | import fs from "fs";
5 | import path from "path";
6 |
7 | // @ts-ignore
8 | import { System } from "@brightsign/system";
9 |
10 | // Configurable values
11 | const CHECK_INTERVAL_MS = 15 * 60 * 1000; // 15 minutes
12 | const SERVER_URL = "http://localhost:7000/autorun.zip"; // Update to your server URL
13 | const STORAGE_PATH = "/storage/sd";
14 | const TMP_PATH = "/storage/tmp";
15 | const extensionsToCheck = [".brs", ".html", ".js", ".json"];
16 |
17 | async function sleep(ms: number) {
18 | return new Promise((resolve) => setTimeout(resolve, ms));
19 | }
20 |
21 | async function doesFileExist(filePath: string): Promise {
22 | try {
23 | await fs.promises.access(filePath, fs.constants.F_OK);
24 | return true;
25 | } catch {
26 | return false;
27 | }
28 | }
29 |
30 | async function downloadAndUnzipFile(
31 | url: string,
32 | dest: string
33 | ): Promise {
34 | try {
35 | const res = await fetch(url);
36 | if (res.status === 200) {
37 | const buffer = Buffer.from(await res.arrayBuffer());
38 | await decompress(buffer, dest);
39 | console.log(`Downloaded zip and unzipped contents to ${dest}`);
40 | return true;
41 | } else {
42 | const err = await res.json();
43 | console.error(`Server error: ${JSON.stringify(err)}`);
44 | return false;
45 | }
46 | } catch (e) {
47 | console.error(`Download failed: ${e}`);
48 | return false;
49 | }
50 | }
51 |
52 | async function backupFiles(storagePath: string): Promise {
53 | const filesToBackup = await findFiles(storagePath, extensionsToCheck);
54 | for (const file of filesToBackup) {
55 | const filename = file.replace(/^.*[\\/]/, "");
56 | const backupFile = path.join(TMP_PATH, filename);
57 | await fs.promises.copyFile(file, backupFile);
58 | console.log(`Backed up ${file} to ${backupFile}`);
59 | }
60 | }
61 |
62 | async function restoreFiles(storagePath: string): Promise {
63 | const filesToRestore = await findFiles(TMP_PATH, extensionsToCheck);
64 | for (const file of filesToRestore) {
65 | const filename = file.replace(/^.*[\\/]/, "");
66 | const originalFile = path.join(storagePath, filename);
67 | await fs.promises.copyFile(file, originalFile);
68 | console.log(`Restored ${file} to ${originalFile}`);
69 | }
70 | }
71 |
72 | async function findFiles(dir: string, exts: string[]): Promise {
73 | let results: string[] = [];
74 | const files = await fs.promises.readdir(dir);
75 | for (const file of files) {
76 | const fullPath = path.join(dir, file);
77 | const stat = await fs.promises.stat(fullPath);
78 | if (stat.isDirectory()) {
79 | results = results.concat(await findFiles(fullPath, exts));
80 | } else if (exts.some((ext) => file.endsWith(ext))) {
81 | results.push(fullPath);
82 | }
83 | }
84 | return results;
85 | }
86 |
87 | async function checksum(filePath: string): Promise {
88 | try {
89 | return await md5File(filePath);
90 | } catch {
91 | return null;
92 | }
93 | }
94 |
95 | async function reboot() {
96 | try {
97 | console.log("Rebooting device...");
98 | new System().reboot();
99 | } catch (e: any) {
100 | console.error("Failed to reboot:", e.message);
101 | }
102 | }
103 |
104 | async function processUpdate() {
105 | await backupFiles(STORAGE_PATH);
106 |
107 | const downloaded = await downloadAndUnzipFile(SERVER_URL, STORAGE_PATH);
108 | if (!downloaded) return;
109 |
110 | const autorunPath = path.join(STORAGE_PATH, "autorun.brs");
111 | if (!(await doesFileExist(autorunPath))) {
112 | console.error(
113 | "No autorun.brs script found after unzip. Restoring backup and rebooting."
114 | );
115 | await restoreFiles(STORAGE_PATH);
116 | await reboot();
117 | return;
118 | }
119 |
120 | const tmpFiles = await findFiles(TMP_PATH, extensionsToCheck);
121 | for (const file of tmpFiles) {
122 | const filename = file.replace(/^.*[\\/]/, "");
123 | const newFile = path.join(STORAGE_PATH, filename);
124 | if (await doesFileExist(newFile)) {
125 | const oldSum = await checksum(file);
126 | const newSum = await checksum(newFile);
127 | if (newSum !== oldSum) {
128 | console.log(`${file} changed. Rebooting.`);
129 | await reboot();
130 | return;
131 | }
132 | }
133 | }
134 | }
135 |
136 | async function main() {
137 | while (true) {
138 | try {
139 | await processUpdate();
140 | } catch (e) {
141 | console.error("Error in update loop:", e);
142 | }
143 | await sleep(CHECK_INTERVAL_MS);
144 | }
145 | }
146 |
147 | main();
148 |
--------------------------------------------------------------------------------
/examples/bluetooth-scan/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Bluetooth Device Scanning App
8 |
23 |
24 |
25 |
26 |
27 |
Success!
28 |
29 | Scanning for Bluetooth devices...
30 | Found 0 unique devices.
31 | Found 0 unique advertising reports.
32 |