b5561729f7
Signed-off-by: Peter Siegmund <developer@mars3142.org>
222 lines
6.2 KiB
JavaScript
222 lines
6.2 KiB
JavaScript
class RailwayAction extends ActionBase {
|
|
constructor($UD, context) {
|
|
super($UD, context);
|
|
this.config = { uri: '', username: '', password: '', deviceId: '' };
|
|
this.mqttClient = null;
|
|
this.clockTimer = null;
|
|
this.connected = false;
|
|
this.lastError = 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 prevKey = this.connectionKey();
|
|
this.config = Object.assign(this.config, s);
|
|
if (this.connectionKey() !== prevKey) {
|
|
this.connectMqtt();
|
|
}
|
|
});
|
|
}
|
|
|
|
connectionKey() {
|
|
return `${this.config.uri}|${this.config.username}|${this.config.deviceId}`;
|
|
}
|
|
|
|
buildTopic() {
|
|
const last4 = this.config.deviceId.replace(/:/g, '').slice(-4).toLowerCase();
|
|
return `device/system_control/${last4}/status`;
|
|
}
|
|
|
|
setActive() {}
|
|
|
|
setParams(jsn) {
|
|
this.config = Object.assign(this.config, (jsn && jsn.param) || {});
|
|
this.$UD.getSettings(this.context);
|
|
this.connectMqtt();
|
|
this.startClockTimer();
|
|
}
|
|
|
|
onRun() {}
|
|
|
|
onClear() {
|
|
super.onClear();
|
|
this.stopClockTimer();
|
|
this.disconnectMqtt();
|
|
}
|
|
|
|
startClockTimer() {
|
|
this.stopClockTimer();
|
|
this.clockTimer = setInterval(() => this.renderButton(), 1000);
|
|
}
|
|
|
|
stopClockTimer() {
|
|
if (this.clockTimer) {
|
|
clearInterval(this.clockTimer);
|
|
this.clockTimer = null;
|
|
}
|
|
}
|
|
|
|
disconnectMqtt() {
|
|
if (this.mqttClient) {
|
|
this.mqttClient.end(true);
|
|
this.mqttClient = null;
|
|
}
|
|
this.connected = false;
|
|
}
|
|
|
|
connectMqtt() {
|
|
this.disconnectMqtt();
|
|
if (!this.config.uri || !this.config.deviceId) {
|
|
this.renderButton();
|
|
return;
|
|
}
|
|
|
|
const log = (msg) => this.$UD.logMessage(`[Railway] ${msg}`, 'info');
|
|
|
|
if (typeof mqtt === 'undefined') {
|
|
this.lastError = 'mqtt lib missing';
|
|
log('ERROR: mqtt.min.js not loaded');
|
|
this.renderButton();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const opts = {
|
|
reconnectPeriod: 3000,
|
|
rejectUnauthorized: false,
|
|
};
|
|
if (this.config.username) opts.username = this.config.username;
|
|
if (this.config.password) opts.password = this.config.password;
|
|
|
|
const topic = this.buildTopic();
|
|
log(`Connecting to ${this.config.uri}, topic: ${topic}`);
|
|
|
|
this.mqttClient = mqtt.connect(this.config.uri, opts);
|
|
|
|
this.mqttClient.on('connect', () => {
|
|
this.connected = true;
|
|
this.lastError = null;
|
|
log(`Connected, subscribing to ${topic}`);
|
|
this.mqttClient.subscribe(topic, (err) => {
|
|
if (err) log(`Subscribe error: ${err.message}`);
|
|
else log(`Subscribed to ${topic}`);
|
|
});
|
|
this.renderButton();
|
|
});
|
|
|
|
this.mqttClient.on('message', (t, payload) => {
|
|
try {
|
|
const outer = JSON.parse(payload.toString());
|
|
const msg = outer.message || outer;
|
|
this.lightOn = msg.on ?? this.lightOn;
|
|
this.mode = msg.mode || this.mode;
|
|
if (msg.color) this.color = msg.color;
|
|
this.simulationClock = msg.clock || null;
|
|
this.renderButton();
|
|
} catch (e) {
|
|
log(`Parse error: ${e.message}`);
|
|
}
|
|
});
|
|
|
|
this.mqttClient.on('reconnect', () => {
|
|
this.connected = false;
|
|
log('Reconnecting…');
|
|
this.renderButton();
|
|
});
|
|
|
|
this.mqttClient.on('error', (err) => {
|
|
this.connected = false;
|
|
this.lastError = err ? err.message : 'unknown';
|
|
log(`Error: ${this.lastError}`);
|
|
this.renderButton();
|
|
});
|
|
|
|
this.mqttClient.on('close', () => {
|
|
this.connected = false;
|
|
log('Connection closed');
|
|
this.renderButton();
|
|
});
|
|
|
|
this.renderButton();
|
|
} catch (e) {
|
|
this.lastError = e.message;
|
|
this.$UD.logMessage(`[Railway] Exception: ${e.message}`, 'error');
|
|
this.mqttClient = 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();
|
|
|
|
// Color strip at top
|
|
const { r, g, b } = this.color;
|
|
ctx.fillStyle = (this.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 (!this.connected) {
|
|
ctx.fillStyle = '#ff6b6b';
|
|
const missing = !this.config.uri || !this.config.deviceId;
|
|
if (this.lastError) {
|
|
ctx.font = '16px "Source Han Sans SC"';
|
|
const short = this.lastError.length > 20 ? this.lastError.slice(0, 19) + '…' : this.lastError;
|
|
ctx.fillText(short, 98, 155);
|
|
ctx.font = '14px "Source Han Sans SC"';
|
|
ctx.fillStyle = '#888888';
|
|
ctx.fillText(this.$UD.t('See log'), 98, 175);
|
|
} else {
|
|
ctx.font = '22px "Source Han Sans SC"';
|
|
ctx.fillText(missing ? this.$UD.t('Not configured') : this.$UD.t('Connecting…'), 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);
|
|
}
|
|
}
|