Declare required capabilities in package.json:
| Capability | Description | Status |
|---|---|---|
dataRead |
Read Signal K data model | Supported |
dataWrite |
Emit delta messages | Supported |
storage |
Write to VFS (vfs-only) |
Supported |
httpEndpoints |
Register custom HTTP endpoints | Supported |
staticFiles |
Serve HTML/CSS/JS from public/ folder |
Supported |
network |
HTTP requests (via as-fetch) | Supported (AssemblyScript only) |
putHandlers |
Register PUT handlers for vessel control | Supported |
rawSockets |
UDP socket access for radar, NMEA, etc. | Supported |
serialPorts |
Serial port access | Planned |
AssemblyScript plugins can make HTTP requests using the as-fetch library integrated into the SDK.
Requirements:
"network": true in manifest"transform": ["as-fetch/transform"] to asconfig.json options"exportRuntime": true in asconfig.json optionsExample: HTTP GET Request
import {
httpGet,
hasNetworkCapability
} from '@signalk/assemblyscript-plugin-sdk/assembly/network'
import { debug, setError } from '@signalk/assemblyscript-plugin-sdk/assembly'
class MyPlugin extends Plugin {
start(config: string): i32 {
// Always check capability first
if (!hasNetworkCapability()) {
setError('Network capability not granted')
return 1
}
// Make HTTP GET request
const response = httpGet('https://api.example.com/data')
if (response === null) {
setError('HTTP request failed')
return 1
}
debug('Received: ' + response)
return 0
}
}
Available Network Functions:
// Check if network capability is granted
hasNetworkCapability(): boolean
// HTTP GET request - returns response body or null on error
httpGet(url: string): string | null
// HTTP POST request - returns status code or -1 on error
httpPost(url: string, body: string): i32
// HTTP POST with response - returns response body or null
httpPostWithResponse(url: string, body: string): string | null
// HTTP PUT request - returns status code or -1 on error
httpPut(url: string, body: string): i32
// HTTP DELETE request - returns status code or -1 on error
httpDelete(url: string): i32
// Advanced HTTP request with full control
httpRequest(
url: string,
method: string,
body: string | null,
contentType: string | null
): HttpResponse | null
Build Configuration (asconfig.json):
For plugins using network capability:
{
"targets": {
"release": {
"outFile": "build/plugin.wasm",
"optimize": true,
"shrinkLevel": 2,
"runtime": "stub"
}
},
"options": {
"bindings": "esm",
"exportRuntime": true,
"transform": ["as-fetch/transform"]
}
}
Manifest Configuration:
{
"name": "my-plugin",
"wasmCapabilities": {
"network": true
},
"dependencies": {
"@signalk/assemblyscript-plugin-sdk": "^0.2.0",
"as-fetch": "^2.1.4"
}
}
The rawSockets capability enables direct UDP socket access for plugins that need to communicate with devices like:
Requirements:
"rawSockets": true in manifestManifest Configuration:
{
"name": "my-radar-plugin",
"wasmManifest": "plugin.wasm",
"wasmCapabilities": {
"rawSockets": true,
"dataWrite": true
}
}
FFI Functions Available:
| Function | Signature | Description |
|---|---|---|
sk_udp_create |
(type: i32) -> i32 |
Create socket (0=udp4, 1=udp6). Returns socket_id or -1 |
sk_udp_bind |
(socket_id, port) -> i32 |
Bind to port (0=any). Returns 0 or -1 |
sk_udp_join_multicast |
(socket_id, addr_ptr, addr_len, iface_ptr, iface_len) -> i32 |
Join multicast group |
sk_udp_leave_multicast |
(socket_id, addr_ptr, addr_len, iface_ptr, iface_len) -> i32 |
Leave multicast group |
sk_udp_set_multicast_ttl |
(socket_id, ttl) -> i32 |
Set multicast TTL |
sk_udp_set_multicast_loopback |
(socket_id, enabled) -> i32 |
Enable/disable loopback |
sk_udp_set_broadcast |
(socket_id, enabled) -> i32 |
Enable/disable broadcast |
sk_udp_send |
(socket_id, addr_ptr, addr_len, port, data_ptr, data_len) -> i32 |
Send datagram |
sk_udp_recv |
(socket_id, buf_ptr, buf_max_len, addr_out_ptr, port_out_ptr) -> i32 |
Receive datagram (non-blocking) |
sk_udp_pending |
(socket_id) -> i32 |
Get number of buffered datagrams |
sk_udp_close |
(socket_id) -> void |
Close socket |
Rust Example:
#[link(wasm_import_module = "env")]
extern "C" {
fn sk_udp_create(socket_type: i32) -> i32;
fn sk_udp_bind(socket_id: i32, port: u16) -> i32;
fn sk_udp_join_multicast(
socket_id: i32,
addr_ptr: *const u8, addr_len: usize,
iface_ptr: *const u8, iface_len: usize
) -> i32;
fn sk_udp_recv(
socket_id: i32,
buf_ptr: *mut u8, buf_max_len: usize,
addr_out_ptr: *mut u8, port_out_ptr: *mut u16
) -> i32;
fn sk_udp_close(socket_id: i32);
}
// Example: Radar discovery
fn start_radar_locator() -> i32 {
// Create UDP socket
let socket_id = unsafe { sk_udp_create(0) }; // udp4
if socket_id < 0 {
return -1;
}
// Bind to radar discovery port
if unsafe { sk_udp_bind(socket_id, 6878) } < 0 {
return -1;
}
// Join radar multicast group
let group = "239.254.2.0";
let iface = "";
if unsafe { sk_udp_join_multicast(socket_id, group.as_ptr(), group.len(), iface.as_ptr(), iface.len()) } < 0 {
return -1;
}
socket_id
}
Important Notes:
sk_udp_pending() to check if data is available before calling sk_udp_recv()The rawSockets capability also enables TCP socket access for plugins that need persistent connections to devices:
TCP sockets support both line-buffered mode (for text protocols with \r\n terminators) and raw mode (for binary protocols).
FFI Functions Available:
| Function | Signature | Description |
|---|---|---|
sk_tcp_create |
() -> i32 |
Create TCP socket. Returns socket_id or -1 |
sk_tcp_connect |
(socket_id, addr_ptr, addr_len, port) -> i32 |
Initiate connection (non-blocking). Returns 0 or -1 |
sk_tcp_connected |
(socket_id) -> i32 |
Check connection status. Returns 1 if connected, 0 otherwise |
sk_tcp_set_line_buffering |
(socket_id, enabled) -> i32 |
Set buffering mode (1=line, 0=raw). Default: line |
sk_tcp_send |
(socket_id, data_ptr, data_len) -> i32 |
Send data. Returns bytes sent or -1 |
sk_tcp_recv_line |
(socket_id, buf_ptr, buf_max_len) -> i32 |
Receive complete line (line mode). Returns len or 0 |
sk_tcp_recv_raw |
(socket_id, buf_ptr, buf_max_len) -> i32 |
Receive raw data (raw mode). Returns len or 0 |
sk_tcp_pending |
(socket_id) -> i32 |
Get buffered item count |
sk_tcp_close |
(socket_id) -> void |
Close socket |
Rust Example:
#[link(wasm_import_module = "env")]
extern "C" {
fn sk_tcp_create() -> i32;
fn sk_tcp_connect(socket_id: i32, addr_ptr: *const u8, addr_len: usize, port: u16) -> i32;
fn sk_tcp_connected(socket_id: i32) -> i32;
fn sk_tcp_set_line_buffering(socket_id: i32, enabled: i32) -> i32;
fn sk_tcp_send(socket_id: i32, data_ptr: *const u8, data_len: usize) -> i32;
fn sk_tcp_recv_line(socket_id: i32, buf_ptr: *mut u8, buf_max_len: usize) -> i32;
fn sk_tcp_recv_raw(socket_id: i32, buf_ptr: *mut u8, buf_max_len: usize) -> i32;
fn sk_tcp_pending(socket_id: i32) -> i32;
fn sk_tcp_close(socket_id: i32);
}
// Example: Furuno radar control connection
fn connect_furuno_radar(ip: &str, port: u16) -> i32 {
// Create TCP socket
let socket_id = unsafe { sk_tcp_create() };
if socket_id < 0 {
return -1;
}
// Initiate connection (non-blocking)
if unsafe { sk_tcp_connect(socket_id, ip.as_ptr(), ip.len(), port) } < 0 {
return -1;
}
socket_id
}
fn poll_connection(socket_id: i32) {
// Check if connected
if unsafe { sk_tcp_connected(socket_id) } != 1 {
return; // Still connecting
}
// Send command with \r\n terminator
let cmd = "$S69,2,0,0,60,300,0\r\n";
unsafe { sk_tcp_send(socket_id, cmd.as_ptr(), cmd.len()) };
// Receive response line
let mut buf = [0u8; 256];
let len = unsafe { sk_tcp_recv_line(socket_id, buf.as_mut_ptr(), buf.len()) };
if len > 0 {
// Process response
}
}
Important Notes:
sk_tcp_connected() until connected\r\n or \nsk_tcp_pending() to check if data is availableWASM plugins can register PUT handlers to respond to PUT requests from clients, enabling vessel control and configuration management.
Requirements:
"putHandlers": true in manifestplugin_start()Manifest Configuration:
{
"name": "my-plugin",
"wasmManifest": "plugin.wasm",
"wasmCapabilities": {
"putHandlers": true
}
}
Handler Naming Convention:
Format: handle_put_{context}_{path}
.) with underscores (_)Examples:
| Context | Path | Handler Function Name |
|---|---|---|
vessels.self |
navigation.anchor.position |
handle_put_vessels_self_navigation_anchor_position |
vessels.self |
steering.autopilot.target.headingTrue |
handle_put_vessels_self_steering_autopilot_target_headingTrue |
Response Format:
{
"state": "COMPLETED",
"statusCode": 200,
"message": "Operation successful"
}
state - Request state: COMPLETED or PENDINGstatusCode - HTTP status code (200, 400, 403, 500, 501)message - Human-readable message (optional)Plugins have access to isolated virtual filesystem:
use std::fs;
fn save_state() {
// Plugin sees "/" as its VFS root
fs::write("/data/state.json", state_json).unwrap();
}
fn load_state() -> String {
fs::read_to_string("/data/state.json").unwrap_or_default()
}
VFS Structure:
/ (VFS root)
├── data/ # Persistent storage
├── config/ # Plugin-managed config
└── tmp/ # Temporary files
Emit delta messages to update Signal K data:
fn emit_position_delta() {
let delta = r#"{
"context": "vessels.self",
"updates": [{
"source": {
"label": "example-wasm",
"type": "plugin"
},
"timestamp": "2025-12-01T10:00:00.000Z",
"values": [{
"path": "navigation.position",
"value": {
"latitude": 60.1,
"longitude": 24.9
}
}]
}]
}"#;
handle_message(&delta);
}