Skip to content

Commit ebcc9d2

Browse files
committed
feat: add sample code for node
- Add registration and deregistration examples. - Add vehicle, speed monitor, and loopback examples. - Add CI support to release examples. - Update documentation for node examples.
1 parent 4c075ce commit ebcc9d2

28 files changed

Lines changed: 3165 additions & 85 deletions

.github/workflows/ci-release.yml

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,21 +173,70 @@ jobs:
173173
if [ -d "$SAMPLE_DIR" ]; then
174174
cp LICENSE "$SAMPLE_DIR/"
175175
cp NOTICE "$SAMPLE_DIR/"
176+
177+
# Inject the language wrapper (the wheel)
178+
mkdir -p "$SAMPLE_DIR/j2735codec"
179+
cp "$STAGING"/*.whl "$SAMPLE_DIR/j2735codec/"
180+
176181
cd "$SAMPLE_DIR"
182+
VERSION=$(cat "$GITHUB_WORKSPACE/VERSION")
177183
ZIP_NAME="python-etx-samples-$VERSION.zip"
178184
zip -r "$STAGING/$ZIP_NAME" . \
179185
-x "*config.json" -x "*/.venv/*" -x "*.venv*" -x "*/.env*" -x "*/__pycache__/*"
180186
cd "$GITHUB_WORKSPACE"
181187
fi
182188
183-
# --- 5. Finalize Staging ---
189+
# --- 5. Package Node/TS Samples ---
190+
NODE_SAMPLE_DIR="etx/examples/node"
191+
if [ -d "$NODE_SAMPLE_DIR" ]; then
192+
VERSION=$(cat "$GITHUB_WORKSPACE/VERSION")
193+
PKG_STAGING="$GITHUB_WORKSPACE/node_pkg_temp"
194+
mkdir -p "$PKG_STAGING"
195+
196+
# 1. Copy the source (leaves your repo untouched)
197+
cp -r "$NODE_SAMPLE_DIR"/. "$PKG_STAGING/"
198+
199+
# 2. Rewrite the path to the PORTABLE local path
200+
# This changes the monorepo path to the ZIP-friendly path
201+
sed -i "s|\"j2735codec\": \".*\"|\"j2735codec\": \"file:./j2735codec/j2735codec-$VERSION.tgz\"|g" "$PKG_STAGING/package.json"
202+
203+
# 3. Inject the tarball into the staging area
204+
mkdir -p "$PKG_STAGING/j2735codec"
205+
cp "$STAGING"/j2735codec-$VERSION.tgz "$PKG_STAGING/j2735codec/"
206+
207+
# 4. GENERATE THE LOCKFILE HERE
208+
# This lockfile will now contain the correct, portable reference to the codec
209+
cd "$PKG_STAGING"
210+
npm install --package-lock-only --no-workspaces
211+
212+
# 5. ZIP EVERYTHING
213+
# Now the ZIP contains a package.json and a package-lock.json
214+
# that both point to the local ./j2735codec folder.
215+
ZIP_NAME="node-etx-samples-$VERSION.zip"
216+
zip -r "$STAGING/$ZIP_NAME" . -x "node_modules/*" "dist/*"
217+
218+
# We zip EVERYTHING (*) in this folder, excluding ONLY the junk
219+
zip -r "$STAGING/$ZIP_NAME" . \
220+
-x "certs/*" "dist/*" "node_modules/*" "config.json" ".env*" "npm-debug.log*"
221+
222+
cd "$GITHUB_WORKSPACE"
223+
rm -rf "$PKG_STAGING"
224+
fi
225+
226+
# --- 6. Finalize Staging ---
184227
cp "$PY_DIR/src/j2735codec/generated/j2735codec.wasm" "$STAGING/j2735codec-$VERSION.wasm"
185228
cp LICENSE "$STAGING/"
186229
cp NOTICE "$STAGING/"
187230
188-
# Cleanup
189-
find "$STAGING" -name "default.gitignore" -delete
190-
find "$STAGING" -name ".*" -not -name "." -delete
231+
# --- 7. FIXED Safe Cleanup ---
232+
echo "🧹 Performing final cleanup..."
233+
# Do NOT use -delete on a broad glob. Delete specific files.
234+
rm -f "$STAGING"/.DS_Store
235+
rm -f "$STAGING"/.env
236+
rm -f "$STAGING"/default.gitignore
237+
238+
echo "✅ Contents of $STAGING for release:"
239+
ls -la "$STAGING"
191240
192241
- name: Create GitHub Release
193242
uses: softprops/action-gh-release@v2

Makefile

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ J2735_REV ?= j2735_202409
4747
THREADS ?= $(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
4848

4949
SAMPLE_PY = etx/examples/python
50+
51+
SAMPLE_JS = etx/examples/node
52+
5053
VERSION ?= $(shell cat VERSION 2>/dev/null || echo "0.1.0")
5154

5255
# --- Targets ---
@@ -206,14 +209,15 @@ sync-version:
206209
@echo "🔄 Syncing version $(VERSION) to manifests..."
207210
@# Update Root CMake
208211
@perl -i -pe 's/VERSION\s+"[0-9.]+"/VERSION "$(VERSION)"/gi' j2735codec/CMakeLists.txt
212+
209213
@# Update Node manifests
210214
@npm version $(VERSION) --no-git-tag-version --allow-same-version || true
211215
@cd $(BINDING_JS) && npm version $(VERSION) --no-git-tag-version --allow-same-version || true
212-
@# Update Python manifests
216+
@cd $(SAMPLE_JS) && npm version $(VERSION) --no-git-tag-version --allow-same-version || true
217+
218+
@# Update Python manifests (root, binding, sample)
213219
@perl -i -pe 's/^version\s*=\s*"[0-9.]*"/version = "$(VERSION)"/m' pyproject.toml
214220
@perl -i -pe 's/^version\s*=\s*"[0-9.]*"/version = "$(VERSION)"/m' $(BINDING_PY)/pyproject.toml
215-
216-
@# Update Sample Python (etx-samples/python/pyproject.toml)
217221
@perl -i -pe 's/^version\s*=\s*"[0-9.]*"/version = "$(VERSION)"/m' $(SAMPLE_PY)/pyproject.toml
218222

219-
@echo "✅ Version $(VERSION) synced across 4 manifests and CMake."
223+
@echo "✅ Version $(VERSION) synced across 6 manifests and CMake."

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ For specific installation steps and examples for Python or Node, please view the
7373

7474
### Language Binding Installation
7575

76-
There are two ways to install the language binding:
76+
It is possible to just install the langauge bindings for encoding and decoding messages.
7777

7878
1. Install the bindings using the link directly from the project release page ([View Latest Releases](https://github.com/5GRealityLab/etx-starter-kit/releases/latest)).
7979

@@ -101,7 +101,7 @@ pip install "https://github.com/5GRealityLab/etx-starter-kit/releases/download/v
101101
npm install "https://github.com/5GRealityLab/etx-starter-kit/releases/download/v0.0.0-alpha/node-j2735codec-0.2.0.tgz"
102102
```
103103

104-
**Note**: The release links above are not valid. Please see release page in this repo for proper links to to use.
104+
**Note**: The release links above are not valid. Please see release page for proper links to to use. You can also download the files from the release page and install locally as well.
105105

106106
### 2. Source Install
107107

@@ -147,6 +147,7 @@ The first step is to clone this repository first. All of the following commands
147147

148148
```bash
149149
git clone https://github.com/5GRealityLab/etx-starter-kit.git
150+
cd etx-starter-kit
150151
```
151152

152153
The following commands will perform a full installation outlined below:

etx/examples/node/.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v22.16.0

etx/examples/node/README.md

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
# Node ETX Client Sample
2+
3+
This repository serves as a practical guide for developers looking to get started with the Verizon ETX platform in Node. It offers a selection of concise, self-contained examples designed to help you quickly understand core V2X concepts—such as mTLS authentication, J2735 message transcoding, and geographic topic routing.
4+
5+
## Running Examples
6+
7+
This version of the client sample has been tested against **v22.16.0**. It utilizes the `j2735codec` for transcoding between JER (JSON) and UPER (Binary) formats.
8+
9+
**NOTE**: All references to the project root refer to `etx/examples/node/`. This is where **ALL** the examples should be executed from.
10+
11+
### Prerequisite - Setup
12+
13+
The fastest way to get started is to download the packages from release. The latest release can be [found here](https://github.com/5GRealityLab/etx-starter-kit/releases/latest).
14+
15+
The file to download will look like: `node-etx-sample-x.x.x.zip`, where x.x.x is the version fo the project.
16+
17+
1. **Install Dependencies**:
18+
19+
**Install With Pip**
20+
21+
```bash
22+
curl -L -O https://github.com/5GRealityLab/etx-starter-kit/releases/latest/download/node-etx-sample-x.x.x.zip
23+
unzip node-etx-sample-x.x.x.zip -d node-etx-sample
24+
cd node-etx-sample
25+
npm install ./j2735codec/j2735codec-*.tgz
26+
27+
# Ready to Run!
28+
```
29+
30+
2. **Environment Configuration**: Create a `config.json` file to start using the examples. The [`config.json.example`](./config.json.example) file provides a good starting point.
31+
32+
Here are some pointers on what to fill out for the fields in the JSON.
33+
34+
| Section | Field | Type | Description |
35+
| :--- | :--- | :--- | :--- |
36+
| **Root** | `logLevel` | String | Controls the verbosity of the console output (e.g., `DEBUG`, `INFO`, `WARN`, `ERROR`). |
37+
| | `certDir` | String | The local directory where security certificates and keys are generated after registration completes. |
38+
| **identity** | `user` | String | Your Verizon ThingSpace account username. |
39+
| | `password` | String | Your Verizon ThingSpace account password. |
40+
| | `token` | String | The Base64 encoded Application Token (Client ID:Client Secret) from ThingSpace. |
41+
| **attributes**| `clientType` | String | The broad category of the client (e.g., `Vehicle`, `VulnerableRoadUser`, etc). |
42+
| | `clientSubType` | String | The specific type of vehicle (e.g., `PassengerCar`, `Truck`, `Radar`, etc). |
43+
| | `vendorId` | String | An identifier for the software or hardware provider. |
44+
| **location** | `lat` | Float | The initial latitude for the device (used during registration/geofencing). |
45+
| | `lon` | Float | The initial longitude for the device. |
46+
47+
**Note: The latitude and longitude determines which edge server you will conenct to.**
48+
49+
**Note: The `Endpoints` section in the config is optional and does not have to be filled out or included in the final `config.json`.**
50+
```json
51+
"endpoints": {
52+
"oauthUrl": "https://thingspace.verizon.com/api/ts/v1/oauth2/token",
53+
"sessionUrl": "https://thingspace.verizon.com/api/m2m/v1/session/login",
54+
"oauthTimeout": 30000,
55+
"registrationUrl": "https://imp.thingspace.verizon.com",
56+
"registrationTimeout": 45000
57+
}
58+
```
59+
60+
#### Supported Arguments
61+
62+
The following command line arguments are supported by all examples in the next section.
63+
64+
| Arg | Required | Default | Description |
65+
| :--- | :--- | :--- | :--- |
66+
| --log-level | False | DEBUG | Controls the verbosity of the console output (e.g., `DEBUG`, `INFO`, `WARN`, `ERROR`). |
67+
68+
---
69+
70+
### 0. Registration Example
71+
72+
The registration example is a precursor to performing any other operations on ETX. This step retrieves a unique **Device ID** and the **mTLS certificates** (CA, Certificate, and Private Key) required to connect.
73+
74+
**IMPORTANT**: Each vendor ID has a fixed quota of device IDs. Always keep track of your registrations and deregister when finished.
75+
76+
#### How to Run
77+
```bash
78+
npm run registration -- --config config.json --cert-dir ./certs
79+
```
80+
81+
**Output**: The above command generates a JSON "Identity Artifact" in the `./certs/` folder. This file contains everything needed to "re-hydrate" your identity in the following examples.
82+
83+
#### Supported Arguments
84+
85+
| Arg | Required | Default | Description |
86+
| :--- | :--- | :--- | :--- |
87+
| --cert-dir| False | ./cert/ | The local directory where security certificates and keys are generated after registration completes. |
88+
| --config | False | config.json | The configuration file to use for registration. |
89+
90+
---
91+
92+
### 1. Deregistration Examples
93+
94+
The deregistration example removes the registered device from ETX. Every vendor ID is has a limited number of device IDs. The deregistration process removes a unique device ID from circulation. This will also remove the files in the folder that contain the registration information.
95+
96+
#### How to Run
97+
98+
The command format to run this example (from the project root):
99+
```bash
100+
npm run deregistration -- --device-files ./certs/20260119_0101_f355703d.json
101+
```
102+
103+
#### Supported Arguments
104+
105+
| Arg | Required | Default | Description |
106+
| :--- | :--- | :--- | :--- |
107+
| --device-file | False | config.json | The configuration file to use for registration. |
108+
109+
---
110+
111+
### 2. Private Loopback Example
112+
113+
A diagnostic tool to verify bidirectional private communication.
114+
115+
1. Connects to the ETX platform and acquires a Session ID.
116+
2. Subscribes to its own unique **Private Topic**.
117+
3. Publishes a BSM to itself to verify the mTLS loop is closed.
118+
119+
#### How to Run
120+
```bash
121+
npm run loopback -- --device-file ./certs/20260119_0101_f355703d.json --count 5
122+
```
123+
#### Supported Arguments
124+
125+
| Arg | Required | Default | Description |
126+
| :--- | :--- | :--- | :--- |
127+
| --device-file | True | - | The device file to use for ETX connection. |
128+
| --count | False | 5 | The number of loopback messages to send. |
129+
130+
---
131+
132+
### 3. Speed Monitor Example (RSU/App)
133+
134+
Demonstrates a "Virtual Speed Trap" application.
135+
1. **Geohashing**: Subscribes to regional topics by delimiting geohashes (e.g., `d/r/5/r/9/y`).
136+
2. **Real-time Analytics**: Decodes BSMs from all vehicles in the neighborhood.
137+
3. **Targeted Advisory**: If a vehicle exceeds the `--limit`, it sends a **TIM (Traveler Information Message)** warning directly to that specific vehicle's private topic.
138+
139+
#### How to Run
140+
```bash
141+
npm run speedmon -- --device-file ./certs/20260119_0101_f355703d.json --limit 25
142+
```
143+
144+
#### Supported Arguments
145+
146+
| Arg | Required | Default | Description |
147+
| :--- | :--- | :--- | :--- |
148+
| --device-file | True | - | The device file to use for ETX connection. |
149+
| --lat | False | - | The default value is defined in the registration config file. |
150+
| --lon | False | - | The default value is defined in the registraiton config file. |
151+
| --limit | False | 25 | The speed limit in mph to send out warnings. |
152+
153+
---
154+
155+
### 4. Vehicle Example (OBU)
156+
157+
Simulates a mobile entity (On-Board Unit). It demonstrates the "Public Broadcast" pattern used in real-world V2X.
158+
1. **Session Handshake**: Connects and retrieves a Session ID from `vzimp/1/ClientInfo`.
159+
2. **Periodic Broadcast**: Runs a background thread to publish BSMs to the `GeoRelevance` topic at 1Hz.
160+
3. **Regional Listening**: Subscribes to regional safety messages (TIM, SPaT, MAP).
161+
162+
#### How to Run
163+
```bash
164+
npm run vehicle -- --device-file ./certs/20260119_0101_f355703d.json --speed 25
165+
```
166+
167+
**Note**: You can try running both the speed monitor and vehicle example together to see when TIM messages are sent to warn the vehicle of speed limit violations. **Remember to use two different registration profiles! One for the monitor and one for the vehicle.**
168+
169+
#### Supported Arguments
170+
171+
| Arg | Required | Default | Description |
172+
| :--- | :--- | :--- | :--- |
173+
| --device-file | True | - | The device file to use for ETX connection. |
174+
| --lat | False | - | The default value is defined in the registration config file. |
175+
| --lon | False | - | The default value is defined in the registraiton config file. |
176+
| --speed | False | 30 | The speed in which the vehicle is traveling at. |
177+
---
178+
179+
### Install and Running From Source (For Project Contributors)
180+
181+
It is possible to run the examples directly from this project as well. Check the prequisites for the building the [language bindings here](/README.md#2-source-install).
182+
183+
```bash
184+
git clone https://github.com/5GRealityLab/etx-starter-kit.git
185+
cd etx-starter-kit
186+
make install-js
187+
cd etx/examples/node
188+
189+
# Run examples above
190+
191+
```
192+
193+
---
194+
195+
### FAQ
196+
197+
**What is the difference between Public and Private topics?** Public topics (`GeoRelevance` or `Regional`) are for one-to-many broadcasts based on location. Private topics are for one-to-one communication targeted at a specific `SessionID`.
198+
199+
**Why use two separate Codecs?** V2X clients are multi-threaded. The MQTT library handles incoming messages on a background thread while your application publishes on the main thread. Using separate `Codec` instances prevents race conditions.
200+
201+
**How do I clean up my device?** Please refer to the `0_deregistration_example` to remove your device ID from the system and reclaim your quota.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"logLevel": "DEBUG",
3+
"certDir": "./certs",
4+
"endpoints": {
5+
"oauthUrl": "https://thingspace.verizon.com/api/ts/v1/oauth2/token",
6+
"sessionUrl": "https://thingspace.verizon.com/api/m2m/v1/session/login",
7+
"oauthTimeout": 30000,
8+
"registrationUrl": "https://imp.thingspace.verizon.com",
9+
"registrationTimeout": 45000
10+
},
11+
"identity": {
12+
"user": "YOUR_THINGSPACE_USERNAME",
13+
"password": "YOUR_THINGSPACE_PASSWORD",
14+
"token": "YOUR_BASE64_ENCODED_APP_TOKEN",
15+
"attributes": {
16+
"clientType": "YOUR_ASSIGNED_CLIENT_TYPE",
17+
"clientSubType": "YOUR_ASSIGNED_SUBTYPE",
18+
"vendorId": "YOUR_ASSIGNED_VENDOR_ID",
19+
"location": {
20+
"lat": 0.0,
21+
"lon": 0.0
22+
}
23+
}
24+
}
25+
}

etx/examples/node/package.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "examples",
3+
"version": "0.2.0",
4+
"type": "module",
5+
"description": "Reference implementations and sample code for Verizon ETX",
6+
"scripts": {
7+
"build": "tsc",
8+
"registration": "node dist/0_registration_example.js",
9+
"preregistration": "npm run build",
10+
"deregistration": "node dist/1_deregistration_example.js",
11+
"prederegistration": "npm run build",
12+
"loopback": "node dist/2_loopback_example.js",
13+
"preloopback": "npm run build",
14+
"speedmon": "node dist/3_speedmon_example.js",
15+
"prespeedmon": "npm run build",
16+
"vehicle": "node dist/4_vehicle_example.js",
17+
"prevehicle": "npm run build"
18+
},
19+
"author": "Sean Hsu",
20+
"license": "Apache-2.0",
21+
"devDependencies": {
22+
"@types/ngeohash": "^0.6.8",
23+
"@types/node": "^25.0.9",
24+
"ts-node": "^1.7.1",
25+
"typescript": "^5.9.3"
26+
},
27+
"dependencies": {
28+
"j2735codec": "^0.2.0",
29+
"mqtt": "^5.14.1",
30+
"ngeohash": "^0.6.3"
31+
}
32+
}

0 commit comments

Comments
 (0)