Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# General
.env
.envrc
*~

# Build artifacts
bin/
target/
*.jar
*.zip
*.war

# Downloaded JRE and packaging staging directory (re-created by make download-jre / make package)
workshop/04-build/java/hello-extension/install/

# Java
*.class
*.log
.classpath
.project
.settings/
.idea/
*.iml

# Node (if any tooling added)
node_modules/
dist/

# macOS
.DS_Store

# Editor
.vscode/
454 changes: 454 additions & 0 deletions CLAUDE.md

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
EXTENSION_DIR := workshop/04-build/java/hello-extension
INSTALL_DIR := $(EXTENSION_DIR)/install
COMMON_SCRIPTS := ../extension-template/examples/common-scripts
JAR_NAME := hello-extension-1.0.0.jar
EXTENSION_NAME := hello_extension

.PHONY: help build download-jre package test-local clean

help: ## Print available targets
@grep -E '^[a-zA-Z_-]+:.*##' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*##"}; {printf " %-14s %s\n", $$1, $$2}'

build: ## Build the extension JAR
$(MAKE) -C $(EXTENSION_DIR) build

download-jre: ## Download Temurin JRE 11 for linux/aarch64 (required for packaging)
$(MAKE) -C $(EXTENSION_DIR) download-jre

package: download-jre ## Build JAR, bundle JRE, and produce the deployable extension ZIP
@if [ ! -d "$(COMMON_SCRIPTS)" ]; then \
echo ""; \
echo "ERROR: common-scripts not found at $(COMMON_SCRIPTS)"; \
echo " Run: git clone https://github.com/brightsign/extension-template"; \
echo " alongside this repository (one level up), then retry."; \
echo ""; \
exit 1; \
fi
$(MAKE) -C $(EXTENSION_DIR) package

test-local: build ## Build, run the extension locally, verify the HTTP endpoint, then stop
$(MAKE) -C $(EXTENSION_DIR) test-local

clean: ## Remove build artifacts, install directory, and ZIP files
$(MAKE) -C $(EXTENSION_DIR) clean
58 changes: 58 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
FROM debian:bookworm-slim

LABEL org.opencontainers.image.source=https://github.com/BrightSign-Playground/bs-extension-workshop
LABEL org.opencontainers.image.description="BrightSign Extension Workshop — development environment"
LABEL org.opencontainers.image.licenses=Apache-2.0

# System packages: base tools + squashfs-tools (required for Module 5 packaging)
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
curl \
git \
jq \
lvm2 \
maven \
openssh-client \
openjdk-11-jdk \
python3 \
squashfs-tools \
unzip \
wget \
&& rm -rf /var/lib/apt/lists/*

# Node.js 14.x (required for HTML app module — matches BrightSign player runtime)
RUN curl -fsSL https://deb.nodesource.com/setup_14.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \
&& rm -rf /var/lib/apt/lists/*

# Go 1.21 (required for future Go extension module)
ARG GO_VERSION=1.21.13
ARG TARGETARCH=amd64
RUN wget -q https://go.dev/dl/go${GO_VERSION}.linux-${TARGETARCH}.tar.gz \
&& tar -C /usr/local -xzf go${GO_VERSION}.linux-${TARGETARCH}.tar.gz \
&& rm go${GO_VERSION}.linux-${TARGETARCH}.tar.gz

ENV PATH="/usr/local/go/bin:${PATH}"
ENV GOPATH="/go"
ENV PATH="${GOPATH}/bin:${PATH}"

# Maven local repo pre-warm: pull the shade plugin and java 11 settings so
# the first `mvn clean package` inside the workshop does not require internet.
# This runs a dummy build that forces download of the shade plugin.
RUN mkdir -p /tmp/mvn-warmup/src/main/java/warmup && \
echo '<project><modelVersion>4.0.0</modelVersion><groupId>warmup</groupId><artifactId>warmup</artifactId><version>1.0</version><properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target></properties><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.5.1</version><executions><execution><phase>package</phase><goals><goal>shade</goal></goals></execution></executions></plugin></plugins></build></project>' \
> /tmp/mvn-warmup/pom.xml && \
echo 'package warmup; public class Main { public static void main(String[] a){} }' \
> /tmp/mvn-warmup/src/main/java/warmup/Main.java && \
mvn -f /tmp/mvn-warmup/pom.xml clean package -q 2>/dev/null || true && \
rm -rf /tmp/mvn-warmup

# Non-root user for workshop work
RUN useradd -m -s /bin/bash workshop
USER workshop
WORKDIR /workspace

# Verify tools
RUN java -version && mvn -version && node --version && npm --version && go version && mksquashfs -version 2>&1 | head -1

CMD ["/bin/bash"]
152 changes: 152 additions & 0 deletions facilitator-guide/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Facilitator Guide

This guide is for BrightSign engineers running the workshop. Participants do not need to read this.

---

## Pre-Workshop Setup (Day Before)

### Travel Router and Network (Set Up at the Venue)

The travel router bridges the venue internet to the local workshop LAN. Players connect via Ethernet through a switch; participants connect via the router's WiFi AP.

**Equipment checklist:**
- [ ] Travel router (GL.iNet [GL-MT3000](https://www.amazon.com/dp/B09N72FMH5) recommended, or equivalent)
- [ ] Ethernet switch (one port per player + one uplink to router)
- [ ] Ethernet cables: one per player from switch, one from router LAN port to switch uplink

**Router configuration:**
- [ ] Set WAN mode to **WiFi Repeater / Extender** — connect to the venue WiFi as the WAN uplink
- [ ] Confirm the router's LAN subnet does not conflict with the venue network (change if needed, e.g. `192.168.8.0/24`)
- [ ] Set DHCP reservations: one static IP per player MAC address
- [ ] Confirm internet access through the router before the session: `curl https://api.github.com`

**Player cabling:**
- [ ] Each player connected via Ethernet to the switch
- [ ] Switch uplink connected to the router LAN port

**Verification:**
- [ ] From a workstation on the router WiFi: ping each player IP
- [ ] From a workstation: `curl http://<player_ip>/api/v1/info` returns JSON
- [ ] From a workstation: internet access works (GitHub, Docker Hub reachable)

**Labels and handouts:**
- [ ] IP address label printed and affixed to each player — participants use this in Module 1.2
- [ ] SSID and password written on a card or slide to hand out in Module 1.1

> **If you cannot pre-assign IPs:** participants boot their player without an SD card and the player shows its IP on screen. Add ~5 minutes to Module 1.2.

### Players — Insecuring (Critical — Must Be Done Before the Workshop)

Insecuring a player is **irreversible**. Do this only on dedicated development units.

For each participant player:
- [ ] Connect serial cable (115200 baud, 8N1)
- [ ] Remove SD card, hold SVC button, apply power, press Ctrl-C at bootloader countdown
- [ ] At bootloader prompt: `disable_secure_boot` (or `setenv SECURE_CHECKS 0` + `saveenv` as fallback)
- [ ] Reboot, then at BrightSign shell: `script debug on` + `reboot`
- [ ] Boot with the development `autorun.brs` on SD card (see Module 1.3 in the workshop for the script)
- [ ] After player displays "Setup complete", remove SD card and reboot
- [ ] Verify: SSH in, type `exit` twice — second `exit` should drop to `#` Linux shell (not reboot)
- [ ] Affix IP label to player

Reference: [BrightSign dev environment setup docs](https://github.com/BrightDevelopers/technical-documentation/blob/main/howto-articles/01-setting-up-development-environment.md)

### Facilitator Demo Player

- [ ] Separate pre-insecured player with the finished extension and HTML app already deployed
- [ ] Connected to the travel router switch; verify the extension responds on port 8080 before the session
- [ ] Used for the Module 0 demo — show `curl` output and the HTML app on screen before participants start
- [ ] Do not use it for participant exercises

### Workstations (Container Path — Recommended)

- [ ] Docker Desktop installed and running (macOS/Windows) or Docker Engine (Linux)
- [ ] Container image pre-pulled to avoid download time during Module 1:
```
docker pull ghcr.io/brightsign-playground/bs-extension-workshop-devenv:latest
```
- [ ] Test the container run command on each workstation OS before the day
- [ ] `$HOME/workshop` (macOS/Linux) or `%USERPROFILE%\workshop` (Windows) directory exists as the volume mount point
- [ ] Network access to GitHub (for `git clone` inside the container), OR pre-cloned repos staged in the volume mount directory

### Workstations (Manual Install Fallback)

If Docker is unavailable, ensure these are installed on the workstation:
- [ ] JDK 11+, Maven 3.6+, Node 14.x, Git, curl, unzip
- [ ] `squashfs-tools` (Linux only — this is the hard blocker on macOS/Windows without container)
- [ ] On macOS/Windows without squashfs-tools: pre-run Module 5 packaging for participants as a fallback, provide the ZIP directly

---

## Timing Guide

| Module | Expected | Watch for |
|---|---|---|
| 0 — Introduction | 15 min | Demo can run long; cut Q&A if needed |
| 1 — Environment Setup | 30 min | Travel router connectivity is the most common blocker; verify all players are reachable before starting |
| 2 — Understand Template | 20 min | Often sparks questions; 5-minute buffer built in |
| 3 — Player API | 15 min | Network issues here signal problems ahead; fix before proceeding |
| Break | 10 min | After Module 3 |
| 4 — Build Java Extension | 45 min | Maven download on slow networks; pre-cache `.m2` or use a local mirror |
| 5 — Package | 15 min | Usually smooth |
| 6 — Deploy | 20 min | Most common failure: wrong `manifest.json` |
| 7 — Verify | 15 min | Usually fast |
| Break | 10 min | After Module 7 |
| 8 — Iterate | 20 min | Let fast finishers add a `/health` endpoint |
| 9 — HTML App | 30 min | Node/webpack issues on Windows; have a Linux fallback ready |
| 10 — Production | 15 min | Conceptual; keep it crisp |
| Cleanup | 5 min | |
| **Total** | **~3.5 hours** | |

---

## Common Failures and Fixes

### Module 1: Player not reachable

- Check that the cable is in the correct Ethernet port.
- The player may still be booting — wait 90 seconds and retry.
- Run `arp -a` to scan the network for the player's MAC address.

### Module 1: `curl` to port 8008 returns nothing

- Dev mode may not be enabled yet — enable it manually for that participant via the player's web UI.
- A firewall on the workstation may be blocking outbound connections; check Windows Defender rules.

### Module 4: Maven download hangs

- Use a local `.m2` mirror if one is available on the network.
- Pre-stage the fat JAR on a USB drive as a fallback so participants can skip the build and continue.

### Module 6: Extension fails to start after install

- Most common cause: wrong `mainClass` in `manifest.json` — verify the exact fully-qualified `package.ClassName`.
- Second most common cause: the JAR is not a fat JAR — the shaded JAR with all dependencies bundled is required.
- Pull the extension logs to diagnose:

```bash
curl http://$PLAYER_IP:8008/api/v1/extensions/hello-extension/logs
```

### Module 9: webpack fails on Windows

- Use WSL2 or a Linux VM — the simple-gaze-detection-html repo has known issues on macOS and Windows.
- A pre-built `sd/` directory is available on USB as a fallback so participants can continue to Module 10.

---

## Fast Finisher Extensions

Optional exercises for participants who finish ahead of schedule.

**Finished Module 4 early:**

- Add a `GET /health` endpoint that returns `{ "status": "ok" }`.
- Add a request counter to the JSON response body.
- Return the player hostname in the JSON response.

**Finished Module 8 early:**

- Use the `curl`-based deploy flow instead of the web UI to redeploy the extension.
- Explore what other endpoints port 8008 exposes on the player.
117 changes: 117 additions & 0 deletions workshop/00-introduction/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<!-- instructor: demo the finished product on a live player before starting — show curl output and the HTML app on screen. This sets expectations and motivates the steps. -->

# Module 0: Introduction

**Duration:** 15 minutes
**Learning Objectives:**
- Explain what a BrightSign extension is and how it runs on the player
- Describe the three-process architecture: extension, HTML app, and Control API
- Identify what the extension-template provides and why it accelerates development
- State what you will build and deploy by the end of this workshop

**Prerequisites:** None — this is the first module.

---

## 1. What Is a BrightSign Extension?

A BrightSign extension is a **separate process** that runs alongside BrightSign OS on the player. It is not a plugin or a scripting hook — it is a standalone executable that the OS starts and manages.

Key properties:

- Runs as its own process on the player hardware
- Exposes HTTP endpoints on **port 8080** that other processes (and remote clients) can call
- Has full access to hardware, the filesystem, and the network — anything the player supports
- Is **language-agnostic**: Java, Go, C++, or any language that produces a runnable binary

> **Note:** The extension HTTP server runs on port 8080 by convention. BrightSign OS does not enforce this — your extension can bind any available port — but the workshop and the template use 8080 throughout.

---

## 2. Why Teams Build Extensions

| Problem | Extension solution |
|---|---|
| BrightSign OS does not expose a capability natively | Implement it yourself and expose it over HTTP |
| Custom compute needed (AI inference, sensor fusion, proprietary protocols) | Run the compute on the player; no external server required |
| Round-trip latency to a backend server is unacceptable | Co-locate business logic with the display device |

The extension model lets you treat the BrightSign player as a general-purpose Linux compute node that also drives a display.

---

## 3. System Architecture

```mermaid
graph TB
subgraph player["BrightSign Player"]
ext["Extension Process\nport 8080\nyour code"]
html["HTML App\ndisplay layer\nBrightScript + JS"]
api["Control API\nport 8008"]
ext -->|"HTTP GET /"| html
end
ws["Your Workstation\ncurl / CI / management tool"] -->|"install · start · stop"| api
```

**How the pieces interact:**

1. You upload a packaged extension to the player via the **Control API (port 8008)**.
2. The Control API installs and starts the extension process.
3. The **extension process** binds port 8080 and begins serving HTTP.
4. The **HTML app** running in the display layer fetches data from `http://localhost:8080` and renders it on screen.

---

## 4. What the Extension Template Gives You

The `extension-template` repository in this workshop provides:

- **Packaging structure** — `manifest.json`, compiled binary, and a ZIP archive in the layout BrightSign OS expects
- **Deploy pipeline** — a scripted sequence: upload → install → start → verify
- **Consistent pattern** — the template works for any language; teams replace the binary with their own without changing the packaging or deploy steps

> **Tip:** The template's core value is the workflow, not the code. Once you understand how packaging and deployment work, swapping in a real extension (AI inference engine, sensor reader, etc.) is a matter of replacing the binary and updating `manifest.json`.

---

## 5. What We Will Build Today

By the end of this workshop you will have deployed a fully functional extension to a live BrightSign player.

**The extension ("Hello BrightSign"):**
- HTTP server on port 8080
- Single endpoint `GET /` returning JSON with server uptime
- Intentionally trivial — the workflow is the point, not the logic

**The HTML app:**
- Polls the extension endpoint every few seconds
- Displays the JSON response on screen

**The full cycle:**
- Package → deploy → verify with `curl` → see output on the display

---

## 6. Architecture of the Finished Product

```mermaid
graph TB
subgraph player["BrightSign Player"]
ext["hello-brightsign\nport 8080\nGET / → {message, uptime_seconds}"]
html["index.html\nrenders message and uptime on screen"]
ext -->|"fetch every 1s"| html
end
ws["Your Workstation"] -.->|"curl http://player-ip:8080/"| ext
```

---

## 7. What This Workshop Is Not

> **Note:** This is not a Java, Go, or C++ tutorial. The extension binary is intentionally trivial — a minimal HTTP server that returns a static JSON response. The purpose of this workshop is to teach the **packaging and deployment workflow**, not application development. Once you know the workflow, the language and complexity of your actual extension are separate concerns.

---

## Next Step

Proceed to **[Module 1: Environment Setup](../01-environment-setup/README.md)** to install the required tools and verify connectivity to your player.
Loading