Files
mars3142_collection/plugin/actions/ModelRailwayAction.js
T
2026-04-11 20:00:42 +02:00

177 lines
4.6 KiB
JavaScript

class ModelRailwayAction extends ActionBase {
constructor($UD, context) {
super($UD, context);
this.config = { host: '' };
this.ws = null;
this.wsReconnectTimer = null;
this.clockTimer = null;
this.lightOn = false;
this.mode = '';
this.color = { r: 0, g: 0, b: 0 };
this.simulationClock = null;
this.$UD.onDidReceiveSettings(jsn => {
if (jsn.context !== this.context) return;
const s = jsn.settings || {};
const prevHost = this.config.host;
this.config = Object.assign(this.config, s);
if (this.config.host !== prevHost) {
this.connectWebSocket();
}
});
}
setActive() {}
setParams(jsn) {
this.config = Object.assign(this.config, (jsn && jsn.param) || {});
this.$UD.getSettings(this.context);
this.connectWebSocket();
this.startClockTimer();
}
onRun() {
if (!this.config.host) return;
fetch(`http://${this.config.host}/api/light/power`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ on: !this.lightOn }),
}).catch(() => {});
}
onClear() {
super.onClear();
this.stopClockTimer();
this.disconnectWebSocket();
}
startClockTimer() {
this.stopClockTimer();
this.clockTimer = setInterval(() => this.renderButton(), 1000);
}
stopClockTimer() {
if (this.clockTimer) {
clearInterval(this.clockTimer);
this.clockTimer = null;
}
}
disconnectWebSocket() {
if (this.wsReconnectTimer) {
clearTimeout(this.wsReconnectTimer);
this.wsReconnectTimer = null;
}
if (this.ws) {
this.ws.onclose = null;
this.ws.close();
this.ws = null;
}
}
connectWebSocket() {
this.disconnectWebSocket();
if (!this.config.host) {
this.renderButton();
return;
}
try {
this.ws = new WebSocket(`ws://${this.config.host}/ws`);
this.ws.onopen = () => {
this.ws.send(JSON.stringify({ type: 'getStatus' }));
this.renderButton();
};
this.ws.onmessage = (event) => {
try {
const msg = JSON.parse(event.data);
if (msg.type === 'status') {
this.lightOn = msg.on;
this.mode = msg.mode || '';
if (msg.color) this.color = msg.color;
this.simulationClock = msg.clock || null;
} else if (msg.type === 'color') {
this.color = { r: msg.r, g: msg.g, b: msg.b };
}
this.renderButton();
} catch (e) {}
};
this.ws.onerror = () => {
this.renderButton();
};
this.ws.onclose = () => {
this.ws = null;
this.renderButton();
this.wsReconnectTimer = setTimeout(() => this.connectWebSocket(), 3000);
};
} catch (e) {
this.ws = null;
this.renderButton();
}
}
formatTime() {
const now = new Date();
const h = String(now.getHours()).padStart(2, '0');
const m = String(now.getMinutes()).padStart(2, '0');
return `${h}:${m}`;
}
modeLabel(mode) {
switch (mode) {
case 'day': return this.$UD.t('Day');
case 'night': return this.$UD.t('Night');
case 'simulation': return this.$UD.t('Simulation');
default: return mode || '---';
}
}
modeColor(mode) {
switch (mode) {
case 'day': return '#f0c040';
case 'night': return '#7ec8e3';
case 'simulation': return '#6bff6b';
default: return '#888888';
}
}
renderButton() {
const { canvas, ctx } = this.createCanvas();
const connected = this.ws && this.ws.readyState === WebSocket.OPEN;
// Color strip at top
const { r, g, b } = this.color;
ctx.fillStyle = (connected && this.lightOn) ? `rgb(${r},${g},${b})` : '#2a2a2a';
ctx.fillRect(0, 0, 196, 46);
// Time (center) — simulation clock from ESP32, otherwise local time
const timeText = (this.mode === 'simulation' && this.simulationClock)
? this.simulationClock
: this.formatTime();
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 54px "Source Han Sans SC"';
ctx.fillText(timeText, 98, 110);
// Status / Mode (bottom)
if (!connected) {
ctx.fillStyle = '#ff6b6b';
ctx.font = '22px "Source Han Sans SC"';
ctx.fillText(this.config.host ? this.$UD.t('Connecting…') : this.$UD.t('No Host'), 98, 162);
} else if (!this.lightOn) {
ctx.fillStyle = '#888888';
ctx.font = '26px "Source Han Sans SC"';
ctx.fillText(this.$UD.t('Off'), 98, 162);
} else {
ctx.fillStyle = this.modeColor(this.mode);
ctx.font = '24px "Source Han Sans SC"';
ctx.fillText(this.modeLabel(this.mode), 98, 162);
}
this.setIcon(canvas);
}
}