# WASM Client Development
Browser-based Modbus client using WebAssembly and WebSocket.
---
## Overview
The `mbus-ffi` crate compiles to WebAssembly for browser-based Modbus communication over WebSocket.
**Use cases:**
- Web HMI dashboards
- Browser-based SCADA interfaces
- IoT monitoring applications
- No server-side installation required
---
## Prerequisites
1. Install wasm-pack:
```bash
cargo install wasm-pack
```
2. Build the WASM package:
```bash
cd mbus-ffi
wasm-pack build --target web
```
3. Find the output in `mbus-ffi/pkg/`:
- `mbus_ffi.js` — JavaScript bindings
- `mbus_ffi_bg.wasm` — WebAssembly module
- `mbus_ffi.d.ts` — TypeScript declarations
---
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Browser │
│ ┌─────────────────┐ ┌────────────────────────────┐ │
│ │ Your Web App │─────▶│ mbus_ffi.js │ │
│ │ (HTML/JS/TS) │ │ (WASM wrapper) │ │
│ └─────────────────┘ └────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────┐ │
│ │ WebSocket Transport │ │
│ │ ws://proxy:8080 │ │
│ └────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────┐
│ WebSocket Proxy │
└────────────────────┘
│
▼
┌────────────────────┐
│ Modbus Device │
│ (TCP port 502) │
└────────────────────┘
```
**Note:** Browsers cannot make raw TCP connections. A WebSocket-to-TCP proxy is required.
---
## Quick Start
### 1. Include WASM Module
```html
Modbus WASM Client
```
### 2. Run WebSocket Proxy
Use the included proxy script:
```bash
node mbus-ffi/examples/proxy.js
```
Or use any WebSocket-to-TCP proxy. Example nginx config:
```nginx
stream {
upstream modbus {
server 192.168.1.10:502;
}
server {
listen 8080;
proxy_pass modbus;
}
}
http {
server {
listen 80;
location /ws {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
}
```
---
## TypeScript Usage
```typescript
import init, { WasmModbusClient } from './pkg/mbus_ffi';
async function main() {
await init();
const client = new WasmModbusClient("ws://localhost:8080", 1, 2000, 1, 20);
// Periodic polling
setInterval(async () => {
try {
const regs = await client.read_holding_registers(0, 10);
console.log("holding registers:", Array.from(regs));
} catch (err) {
console.error("request failed:", err);
}
}, 1000);
}
main();
```
---
## Web Serial (Browser Serial API)
In addition to WebSocket/TCP proxy mode, the wasm bindings also expose Web Serial
support through:
- `request_serial_port()`
- `WasmSerialPortHandle`
- `WasmSerialModbusClient`
Typical flow:
1. Call `request_serial_port()` from a user gesture (button click).
2. Construct `WasmSerialModbusClient` with the returned handle.
3. Use the same Promise-based request methods (`read_coils`, `read_holding_registers`, etc.).
Web Serial is currently supported in Chromium-based browsers under secure contexts
(`https://` or `http://localhost`).
---
## WASM Server Bindings (Phase 1/2 Surface)
`mbus-ffi` now exposes browser-facing server binding types:
- `WasmTcpServer` + `WasmTcpGatewayConfig`
- `WasmSerialServer` + `WasmSerialServerConfig`
Current scope:
- Lifecycle controls: `start()`, `stop()`, `is_running()`
- Request bridge: `dispatch_request(...)` through JS callback handler
- Adapter passthrough helpers: `send_frame(...)`, `recv_frame(...)`
- WebSocket handshake helpers on `WasmTcpServer`: `transport_connecting()` and `transport_connected()` (`transport_connected()` is true only after websocket OPEN)
### Supported Protocol Surface (Contract)
Current contractual server binding support:
- Lifecycle APIs are stable (`start`, `stop`, `is_running`)
- `dispatch_request(...)` callback bridge is stable (sync return or Promise return)
- Raw frame transport passthrough helpers are stable (`send_frame`, `recv_frame`)
Not currently contractual at server binding level:
- Built-in Modbus FC request parsing/routing by `WasmTcpServer` / `WasmSerialServer`
- Guaranteed typed FC helper APIs on server bindings
- End-to-end managed protocol loop in `mbus-ffi` server layer
Planned expansion (non-contractual roadmap intent):
- Incremental typed protocol helpers and FC mapping on top of current bridge
- More managed request/response orchestration while preserving transport ownership boundaries
### Transport Ownership Boundary
WASM transport implementations are not reimplemented in `mbus-ffi` server bindings:
- `mbus-network` owns websocket WASM transport implementation
- `mbus-serial` owns Web Serial WASM transport implementation
`mbus-ffi` owns only binding orchestration (lifecycle + JS bridge + adapter wiring).
Note:
- Any behavior implemented only in example pages (for smoke/demo convenience) is not part of the stable binding contract unless documented in the API sections above.
---
## API Reference
### Constructor
```typescript
new WasmModbusClient(
ws_url: string,
unit_id: number,
response_timeout_ms: number,
retry_attempts: number,
tick_interval_ms: number
)
```
### Methods
| Method | Description |
|--------|-------------|
| `is_connected()` | Check connection status |
| `has_pending_requests()` | Returns `true` while requests are in flight |
| `reconnect()` | Reconnect underlying transport and fail in-flight requests |
| `read_coils(address, quantity)` | FC01 |
| `read_discrete_inputs(address, quantity)` | FC02 |
| `read_holding_registers(address, quantity)` | FC03 |
| `read_input_registers(address, quantity)` | FC04 |
| `write_single_coil(address, value)` | FC05 |
| `write_single_register(address, value)` | FC06 |
| `write_multiple_coils(address, quantity, values)` | FC0F |
| `write_multiple_registers(address, quantity, values)` | FC10 |
### Promise-based Responses
```typescript
const regs = await client.read_holding_registers(0, 10);
console.log(Array.from(regs));
if (client.has_pending_requests()) {
console.log("requests still in flight");
}
```
Errors are surfaced by Promise rejection (for example: connection loss, timeout/retry exhaustion, or protocol-level failures).
---
## Example HTML Smoke Test
A complete example is available at:
[mbus-ffi/examples/network_smoke.html](../../mbus-ffi/javascript/examples/wasm_client/network_smoke.html)
```bash
# Serve the example
cd mbus-ffi/examples
python3 -m http.server 8000
# Open browser
open http://localhost:8000/network_smoke.html
```
---
## Build Variants
### Standard (TCP via WebSocket)
```bash
wasm-pack build --target web
```
### With All Features
```bash
wasm-pack build --target web --features "coils,registers,discrete-inputs,diagnostics"
```
---
## Security Considerations
1. **WebSocket URL** — Use `wss://` in production
2. **Proxy Authentication** — Implement at proxy level
3. **CORS** — Configure proxy for allowed origins
4. **CSP** — Allow `'wasm-unsafe-eval'` for WASM
---
## Troubleshooting
### WASM Module Fails to Load
```
CompileError: WebAssembly.instantiate()
```
Ensure your server sends correct MIME types:
- `.wasm` → `application/wasm`
- `.js` → `application/javascript`
### WebSocket Connection Refused
Check that:
1. Proxy is running on the correct port
2. No firewall blocking connection
3. Correct WebSocket URL (ws:// vs wss://)
### Responses Not Arriving
1. Check browser DevTools Network tab for WebSocket frames
2. Verify Modbus device is responding (test with a TCP client first)
3. Check proxy logs for errors
---
## See Also
- [C/FFI Bindings](c_bindings.md) — Native C integration
- [Feature Flags](feature_flags.md) — Build options
- [Sync Development](sync.md) — Rust sync client guide