Spaces:
Paused
Paused
fleet simulator
Browse files- server/simulator.js +23 -23
server/simulator.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
| 1 |
-
// Simulated fleet
|
| 2 |
-
//
|
| 3 |
-
//
|
| 4 |
-
// cd server && npm run sim
|
| 5 |
-
// SERVER_URL=http://localhost:8080 SIM_CENTER=48.8566,2.3522 node simulator.js
|
| 6 |
import WebSocket from 'ws';
|
| 7 |
|
| 8 |
const BASE = process.env.SERVER_URL || 'http://localhost:8080';
|
| 9 |
-
const
|
| 10 |
-
const
|
|
|
|
| 11 |
|
| 12 |
async function main() {
|
| 13 |
const login = await fetch(`${BASE}/api/login`, {
|
|
@@ -17,23 +16,23 @@ async function main() {
|
|
| 17 |
});
|
| 18 |
if (!login.ok) throw new Error(`login failed: ${login.status}`);
|
| 19 |
const { token } = await login.json();
|
| 20 |
-
const
|
| 21 |
|
| 22 |
-
const groups = await (await fetch(`${BASE}/api/groups`, { headers })).json();
|
| 23 |
-
if (!groups.length) throw new Error('no talkgroups exist');
|
| 24 |
const groupId = groups[0].id;
|
| 25 |
|
| 26 |
-
const existing = await (await fetch(`${BASE}/api/members`, { headers })).json();
|
| 27 |
const units = [];
|
| 28 |
for (let i = 1; i <= N; i++) {
|
| 29 |
const deviceId = `sim-${i}`;
|
| 30 |
let m = existing.find((x) => x.device_id === deviceId);
|
| 31 |
if (!m) {
|
| 32 |
const res = await fetch(`${BASE}/api/members`, {
|
| 33 |
-
method: 'POST',
|
|
|
|
| 34 |
body: JSON.stringify({ name: `SIM Unit ${i}`, callsign: `SIM-${i}`, device_id: deviceId, group_id: groupId }),
|
| 35 |
});
|
| 36 |
-
if (!res.ok) throw new Error(`could not create ${deviceId}: ${await res.text()}`);
|
| 37 |
m = await res.json();
|
| 38 |
}
|
| 39 |
units.push(m);
|
|
@@ -41,19 +40,21 @@ async function main() {
|
|
| 41 |
|
| 42 |
const states = units.map((m) => ({
|
| 43 |
m,
|
| 44 |
-
lat: clat + (Math.random() - 0.5) * 0.08,
|
| 45 |
-
lng: clng + (Math.random() - 0.5) * 0.12,
|
| 46 |
-
battery: 60 + Math.floor(Math.random() * 40),
|
| 47 |
ws: null,
|
|
|
|
|
|
|
|
|
|
| 48 |
}));
|
| 49 |
|
| 50 |
for (const s of states) {
|
| 51 |
-
const ws = new WebSocket(
|
| 52 |
ws.on('open', () => ws.send(JSON.stringify({ type: 'auth', token, memberId: s.m.id })));
|
| 53 |
ws.on('message', (d) => {
|
| 54 |
const msg = JSON.parse(d.toString());
|
| 55 |
-
if (msg.type === 'ptt_denied') console.log(`[${s.m.name}]
|
|
|
|
| 56 |
});
|
|
|
|
| 57 |
s.ws = ws;
|
| 58 |
}
|
| 59 |
|
|
@@ -68,19 +69,18 @@ async function main() {
|
|
| 68 |
}
|
| 69 |
}, 5000);
|
| 70 |
|
| 71 |
-
// one unit raises an SOS shortly after start
|
| 72 |
setTimeout(() => {
|
| 73 |
const s = states[0];
|
| 74 |
if (s.ws.readyState === WebSocket.OPEN) {
|
| 75 |
s.ws.send(JSON.stringify({ type: 'sos' }));
|
| 76 |
-
console.log(
|
| 77 |
}
|
| 78 |
}, 25000);
|
| 79 |
|
| 80 |
-
// units take turns requesting the PTT floor
|
| 81 |
let turn = 0;
|
| 82 |
setInterval(() => {
|
| 83 |
-
const s = states[turn
|
|
|
|
| 84 |
if (s.ws.readyState !== WebSocket.OPEN) return;
|
| 85 |
s.ws.send(JSON.stringify({ type: 'ptt_start' }));
|
| 86 |
setTimeout(() => {
|
|
@@ -88,7 +88,7 @@ async function main() {
|
|
| 88 |
}, 4000);
|
| 89 |
}, 30000);
|
| 90 |
|
| 91 |
-
console.log(`
|
| 92 |
}
|
| 93 |
|
| 94 |
main().catch((e) => { console.error(e); process.exit(1); });
|
|
|
|
| 1 |
+
// Simulated fleet for the dispatch console.
|
| 2 |
+
// Usage (server running on :8080): node simulator.js
|
| 3 |
+
// Env: SERVER_URL, SIM_UNITS, SIM_CENTER="lat,lng", ADMIN_USER, ADMIN_PASS
|
|
|
|
|
|
|
| 4 |
import WebSocket from 'ws';
|
| 5 |
|
| 6 |
const BASE = process.env.SERVER_URL || 'http://localhost:8080';
|
| 7 |
+
const WS_URL = BASE.replace(/^http/, 'ws') + '/ws';
|
| 8 |
+
const N = Number(process.env.SIM_UNITS || 5);
|
| 9 |
+
const CENTER = (process.env.SIM_CENTER || '48.8566,2.3522').split(',').map(Number);
|
| 10 |
|
| 11 |
async function main() {
|
| 12 |
const login = await fetch(`${BASE}/api/login`, {
|
|
|
|
| 16 |
});
|
| 17 |
if (!login.ok) throw new Error(`login failed: ${login.status}`);
|
| 18 |
const { token } = await login.json();
|
| 19 |
+
const auth = { authorization: `Bearer ${token}`, 'content-type': 'application/json' };
|
| 20 |
|
| 21 |
+
const groups = await (await fetch(`${BASE}/api/groups`, { headers: auth })).json();
|
| 22 |
+
if (!groups.length) throw new Error('no talkgroups exist — create one first');
|
| 23 |
const groupId = groups[0].id;
|
| 24 |
|
| 25 |
+
const existing = await (await fetch(`${BASE}/api/members`, { headers: auth })).json();
|
| 26 |
const units = [];
|
| 27 |
for (let i = 1; i <= N; i++) {
|
| 28 |
const deviceId = `sim-${i}`;
|
| 29 |
let m = existing.find((x) => x.device_id === deviceId);
|
| 30 |
if (!m) {
|
| 31 |
const res = await fetch(`${BASE}/api/members`, {
|
| 32 |
+
method: 'POST',
|
| 33 |
+
headers: auth,
|
| 34 |
body: JSON.stringify({ name: `SIM Unit ${i}`, callsign: `SIM-${i}`, device_id: deviceId, group_id: groupId }),
|
| 35 |
});
|
|
|
|
| 36 |
m = await res.json();
|
| 37 |
}
|
| 38 |
units.push(m);
|
|
|
|
| 40 |
|
| 41 |
const states = units.map((m) => ({
|
| 42 |
m,
|
|
|
|
|
|
|
|
|
|
| 43 |
ws: null,
|
| 44 |
+
lat: CENTER[0] + (Math.random() - 0.5) * 0.08,
|
| 45 |
+
lng: CENTER[1] + (Math.random() - 0.5) * 0.12,
|
| 46 |
+
battery: 60 + Math.floor(Math.random() * 40),
|
| 47 |
}));
|
| 48 |
|
| 49 |
for (const s of states) {
|
| 50 |
+
const ws = new WebSocket(WS_URL);
|
| 51 |
ws.on('open', () => ws.send(JSON.stringify({ type: 'auth', token, memberId: s.m.id })));
|
| 52 |
ws.on('message', (d) => {
|
| 53 |
const msg = JSON.parse(d.toString());
|
| 54 |
+
if (msg.type === 'ptt_denied') console.log(`[${s.m.name}] ptt denied (held by ${msg.holder})`);
|
| 55 |
+
if (msg.type === 'floor' && msg.memberId === s.m.id) console.log(`[${s.m.name}] holding the floor on group ${msg.groupId}`);
|
| 56 |
});
|
| 57 |
+
ws.on('error', (e) => console.error(`[${s.m.name}] ws error:`, e.message));
|
| 58 |
s.ws = ws;
|
| 59 |
}
|
| 60 |
|
|
|
|
| 69 |
}
|
| 70 |
}, 5000);
|
| 71 |
|
|
|
|
| 72 |
setTimeout(() => {
|
| 73 |
const s = states[0];
|
| 74 |
if (s.ws.readyState === WebSocket.OPEN) {
|
| 75 |
s.ws.send(JSON.stringify({ type: 'sos' }));
|
| 76 |
+
console.log('[SIM Unit 1] SOS sent');
|
| 77 |
}
|
| 78 |
}, 25000);
|
| 79 |
|
|
|
|
| 80 |
let turn = 0;
|
| 81 |
setInterval(() => {
|
| 82 |
+
const s = states[turn % states.length];
|
| 83 |
+
turn++;
|
| 84 |
if (s.ws.readyState !== WebSocket.OPEN) return;
|
| 85 |
s.ws.send(JSON.stringify({ type: 'ptt_start' }));
|
| 86 |
setTimeout(() => {
|
|
|
|
| 88 |
}, 4000);
|
| 89 |
}, 30000);
|
| 90 |
|
| 91 |
+
console.log(`Simulating ${N} units against ${BASE} — Ctrl+C to stop.`);
|
| 92 |
}
|
| 93 |
|
| 94 |
main().catch((e) => { console.error(e); process.exit(1); });
|