Move project from bordanlage/ to repo root

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-26 14:31:08 +01:00
parent 946c0a5377
commit 77123a0df5
56 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,118 @@
// Simulates a SignalK WebSocket delta stream with realistic Baltic Sea boat data.
const INTERVAL_MS = 1000
// Starting position: Kiel Fjord, Baltic Sea
const BASE_LAT = 54.3233
const BASE_LON = 10.1394
function randomWalk(value, min, max, step) {
const delta = (Math.random() - 0.5) * step * 2
return Math.min(max, Math.max(min, value + delta))
}
function degToRad(d) { return d * Math.PI / 180 }
export function createSignalKMock() {
const listeners = {}
let timer = null
let running = false
// Initial state
const state = {
sog: 5.2,
cog: 215,
heading: 217,
depth: 12.4,
windSpeed: 13.5,
windAngle: 42,
rpm: 1800,
battery1: 12.6, // starter
battery2: 25.1, // house (24V)
waterTemp: 17.8,
lat: BASE_LAT,
lon: BASE_LON,
routeIndex: 0,
rudder: 2.5,
airTemp: 14.2,
fuel: 68,
}
// Simulate boat moving along a rough course
function advancePosition() {
const speedMs = state.sog * 0.514 // knots to m/s
const headRad = degToRad(state.heading)
const dLat = (speedMs * Math.cos(headRad) * INTERVAL_MS / 1000) / 111320
const dLon = (speedMs * Math.sin(headRad) * INTERVAL_MS / 1000) / (111320 * Math.cos(degToRad(state.lat)))
state.lat += dLat
state.lon += dLon
}
function buildDelta() {
state.sog = randomWalk(state.sog, 3.5, 8, 0.15)
state.cog = randomWalk(state.cog, 200, 235, 1.5)
state.heading = randomWalk(state.heading, 198, 237, 1.2)
state.depth = randomWalk(state.depth, 6, 25, 0.3)
state.windSpeed = randomWalk(state.windSpeed, 8, 22, 0.8)
state.windAngle = randomWalk(state.windAngle, 25, 70, 2)
state.rpm = Math.round(randomWalk(state.rpm, 1500, 2100, 40))
state.battery1 = randomWalk(state.battery1, 12.2, 12.9, 0.02)
state.battery2 = randomWalk(state.battery2, 24.5, 25.6, 0.04)
state.waterTemp = randomWalk(state.waterTemp, 16, 20, 0.05)
state.rudder = randomWalk(state.rudder, -15, 15, 1.5)
advancePosition()
return {
updates: [{
source: { label: 'mock', type: 'NMEA2000' },
timestamp: new Date().toISOString(),
values: [
{ path: 'navigation.speedOverGround', value: state.sog * 0.514 },
{ path: 'navigation.courseOverGroundTrue', value: degToRad(state.cog) },
{ path: 'navigation.headingTrue', value: degToRad(state.heading) },
{ path: 'navigation.position', value: { latitude: state.lat, longitude: state.lon } },
{ path: 'environment.depth.belowKeel', value: state.depth },
{ path: 'environment.wind.speedApparent', value: state.windSpeed * 0.514 },
{ path: 'environment.wind.angleApparent', value: degToRad(state.windAngle) },
{ path: 'environment.water.temperature', value: state.waterTemp + 273.15 },
{ path: 'environment.outside.temperature', value: state.airTemp + 273.15 },
{ path: 'propulsion.main.revolutions', value: state.rpm / 60 },
{ path: 'electrical.batteries.starter.voltage', value: state.battery1 },
{ path: 'electrical.batteries.house.voltage', value: state.battery2 },
{ path: 'steering.rudderAngle', value: degToRad(state.rudder) },
{ path: 'tanks.fuel.0.currentLevel', value: state.fuel / 100 },
]
}]
}
}
function emit(event, data) {
if (listeners[event]) listeners[event].forEach(fn => fn(data))
}
function start() {
if (running) return
running = true
// Send initial delta immediately
emit('delta', buildDelta())
timer = setInterval(() => emit('delta', buildDelta()), INTERVAL_MS)
}
function stop() {
if (timer) clearInterval(timer)
running = false
}
return {
on(event, fn) {
if (!listeners[event]) listeners[event] = []
listeners[event].push(fn)
if (event === 'delta' && !running) start()
},
off(event, fn) {
if (listeners[event]) listeners[event] = listeners[event].filter(l => l !== fn)
},
getSnapshot: () => ({ ...state }),
disconnect: stop,
}
}