railway over mqtt
change copilot to mqtt Signed-off-by: Peter Siegmund <developer@mars3142.org>
This commit is contained in:
@@ -1,32 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<!-- Locomotive body -->
|
||||
<rect x="12" y="32" width="62" height="34" rx="5" fill="#ffffff"/>
|
||||
<!-- Cab section -->
|
||||
<rect x="12" y="22" width="28" height="13" rx="4" fill="#ffffff"/>
|
||||
<!-- Cab window -->
|
||||
<rect x="16" y="25" width="20" height="8" rx="2" fill="#87ceeb"/>
|
||||
<!-- Chimney -->
|
||||
<rect x="64" y="18" width="10" height="16" rx="3" fill="#ffffff"/>
|
||||
<!-- Smoke puff -->
|
||||
<circle cx="69" cy="14" r="5" fill="#cccccc" opacity="0.7"/>
|
||||
<circle cx="75" cy="11" r="3.5" fill="#cccccc" opacity="0.5"/>
|
||||
<!-- Front light -->
|
||||
<circle cx="74" cy="46" r="5" fill="#f0c040"/>
|
||||
<!-- Wheels -->
|
||||
<circle cx="27" cy="68" r="9" fill="#444444" stroke="#ffffff" stroke-width="2"/>
|
||||
<circle cx="27" cy="68" r="3" fill="#ffffff"/>
|
||||
<circle cx="52" cy="68" r="9" fill="#444444" stroke="#ffffff" stroke-width="2"/>
|
||||
<circle cx="52" cy="68" r="3" fill="#ffffff"/>
|
||||
<circle cx="72" cy="68" r="6" fill="#444444" stroke="#ffffff" stroke-width="2"/>
|
||||
<circle cx="72" cy="68" r="2" fill="#ffffff"/>
|
||||
<!-- Rails -->
|
||||
<rect x="5" y="77" width="90" height="4" rx="2" fill="#aaaaaa"/>
|
||||
<rect x="5" y="85" width="90" height="4" rx="2" fill="#aaaaaa"/>
|
||||
<!-- Sleepers -->
|
||||
<rect x="12" y="76" width="6" height="14" fill="#888888"/>
|
||||
<rect x="26" y="76" width="6" height="14" fill="#888888"/>
|
||||
<rect x="40" y="76" width="6" height="14" fill="#888888"/>
|
||||
<rect x="54" y="76" width="6" height="14" fill="#888888"/>
|
||||
<rect x="68" y="76" width="6" height="14" fill="#888888"/>
|
||||
<rect x="82" y="76" width="6" height="14" fill="#888888"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
+7
-1
@@ -29,6 +29,12 @@
|
||||
"Off": "Aus",
|
||||
"Day": "Tag",
|
||||
"Night": "Nacht",
|
||||
"Simulation": "Simulation"
|
||||
"Simulation": "Simulation",
|
||||
"MQTT URI": "MQTT URI",
|
||||
"Username": "Benutzername",
|
||||
"Password": "Passwort",
|
||||
"Device ID": "Geräte-ID",
|
||||
"Topic": "Topic",
|
||||
"Not configured": "Nicht konfiguriert"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,12 @@
|
||||
"Off": "Off",
|
||||
"Day": "Day",
|
||||
"Night": "Night",
|
||||
"Simulation": "Simulation"
|
||||
"Simulation": "Simulation",
|
||||
"MQTT URI": "MQTT URI",
|
||||
"Username": "Username",
|
||||
"Password": "Password",
|
||||
"Device ID": "Device ID",
|
||||
"Topic": "Topic",
|
||||
"Not configured": "Not configured"
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+19
File diff suppressed because one or more lines are too long
+5
-5
@@ -65,16 +65,16 @@
|
||||
},
|
||||
{
|
||||
"Name": "Model Railway",
|
||||
"Icon": "assets/icons/modelrailway.svg",
|
||||
"PropertyInspectorPath": "property-inspector/modelrailway/inspector.html",
|
||||
"Icon": "assets/icons/railway.png",
|
||||
"PropertyInspectorPath": "property-inspector/railway/inspector.html",
|
||||
"States": [
|
||||
{
|
||||
"Name": "Default",
|
||||
"Image": "assets/icons/modelrailway.svg"
|
||||
"Image": "assets/icons/railway.png"
|
||||
}
|
||||
],
|
||||
"Tooltip": "Shows light color, time and mode from an ESP32 model railway controller via WebSocket",
|
||||
"UUID": "dev.mars3142.ulanzideck.collection.modelrailway"
|
||||
"Tooltip": "Shows light color, time and mode from an ESP32 model railway controller via MQTT",
|
||||
"UUID": "dev.mars3142.ulanzideck.collection.railway"
|
||||
}
|
||||
],
|
||||
"OS": [
|
||||
|
||||
@@ -1,57 +1,105 @@
|
||||
class CopilotAction extends ActionBase {
|
||||
constructor($UD, context) {
|
||||
super($UD, context);
|
||||
this.config = {
|
||||
url: '',
|
||||
refreshRate: '4',
|
||||
};
|
||||
this.config = { uri: '', username: '', password: '', topic: '' };
|
||||
this.mqttClient = null;
|
||||
this.value = null;
|
||||
this.isError = false;
|
||||
|
||||
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.topic}`;
|
||||
}
|
||||
|
||||
setActive() {}
|
||||
|
||||
setParams(jsn) {
|
||||
this.config = Object.assign(this.config, (jsn && jsn.param) || {});
|
||||
this.startTimer(() => this.fetchData(), this.config.refreshRate);
|
||||
this.$UD.getSettings(this.context);
|
||||
this.connectMqtt();
|
||||
}
|
||||
|
||||
onRun() {
|
||||
this.fetchData();
|
||||
if (!this.mqttClient || !this.mqttClient.connected) {
|
||||
this.connectMqtt();
|
||||
}
|
||||
}
|
||||
|
||||
fetchData() {
|
||||
this.debounce(async () => {
|
||||
if (!this.config.url) {
|
||||
onClear() {
|
||||
super.onClear();
|
||||
this.disconnectMqtt();
|
||||
}
|
||||
|
||||
disconnectMqtt() {
|
||||
if (this.mqttClient) {
|
||||
this.mqttClient.end(true);
|
||||
this.mqttClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
connectMqtt() {
|
||||
this.disconnectMqtt();
|
||||
if (!this.config.uri || !this.config.topic) {
|
||||
this.renderGauge(null, false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(this.config.url);
|
||||
if (!response.ok) {
|
||||
this.renderGauge(null, true);
|
||||
return;
|
||||
const opts = {
|
||||
reconnectPeriod: 3000,
|
||||
rejectUnauthorized: false,
|
||||
};
|
||||
if (this.config.username) opts.username = this.config.username;
|
||||
if (this.config.password) opts.password = this.config.password;
|
||||
|
||||
this.mqttClient = mqtt.connect(this.config.uri, opts);
|
||||
|
||||
this.mqttClient.on('connect', () => {
|
||||
this.isError = false;
|
||||
this.mqttClient.subscribe(this.config.topic);
|
||||
this.renderGauge(this.value, false);
|
||||
});
|
||||
|
||||
this.mqttClient.on('message', (topic, payload) => {
|
||||
try {
|
||||
const msg = JSON.parse(payload.toString());
|
||||
if (msg.usage != null) {
|
||||
this.value = parseFloat(msg.usage);
|
||||
this.isError = false;
|
||||
} else {
|
||||
this.isError = true;
|
||||
}
|
||||
const text = await response.text();
|
||||
const value = this.parsePrometheus(text);
|
||||
this.renderGauge(value !== null ? parseFloat(value) : null, false);
|
||||
this.renderGauge(this.value, this.isError);
|
||||
} catch (e) {
|
||||
this.renderGauge(null, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
parsePrometheus(text) {
|
||||
for (const line of text.split('\n')) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||
if (trimmed.startsWith('github_copilot_usage_percentage')) {
|
||||
const closingBrace = trimmed.indexOf('}');
|
||||
if (closingBrace !== -1) return trimmed.slice(closingBrace + 1).trim();
|
||||
const parts = trimmed.split(/\s+/);
|
||||
if (parts.length >= 2) return parts[1];
|
||||
this.mqttClient.on('reconnect', () => {
|
||||
this.renderGauge(this.value, false);
|
||||
});
|
||||
|
||||
this.mqttClient.on('error', () => {
|
||||
this.isError = true;
|
||||
this.renderGauge(null, true);
|
||||
});
|
||||
|
||||
this.renderGauge(this.value, false);
|
||||
} catch (e) {
|
||||
this.mqttClient = null;
|
||||
this.renderGauge(null, true);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderGauge(value, isError) {
|
||||
const { canvas, ctx } = this.createCanvas();
|
||||
@@ -74,7 +122,9 @@ class CopilotAction extends ActionBase {
|
||||
ctx.lineCap = 'round';
|
||||
ctx.stroke();
|
||||
|
||||
if (value !== null) {
|
||||
const connected = this.mqttClient && this.mqttClient.connected;
|
||||
|
||||
if (value !== null && !isError && connected) {
|
||||
const pct = Math.max(0, Math.min(100, value)) / 100;
|
||||
const valueAngle = startAngle + pct * totalSweep;
|
||||
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
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('(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);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -10,13 +10,14 @@
|
||||
<script src="../libs/js/timers.js"></script>
|
||||
<script src="../libs/js/utils.js"></script>
|
||||
<script src="../libs/js/ulanzideckApi.js"></script>
|
||||
<script src="../libs/js/mqtt.min.js"></script>
|
||||
|
||||
<script src="./ActionBase.js"></script>
|
||||
<script src="./actions/PetrolAction.js"></script>
|
||||
<script src="./actions/CopilotAction.js"></script>
|
||||
<script src="./actions/GiteaAction.js"></script>
|
||||
<script src="./actions/GiteaPRAction.js"></script>
|
||||
<script src="./actions/ModelRailwayAction.js"></script>
|
||||
<script src="./actions/RailwayAction.js"></script>
|
||||
<script src="./app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+2
-2
@@ -22,8 +22,8 @@ $UD.onAdd(jsn => {
|
||||
ACTION_CACHES[context] = new GiteaAction($UD, context);
|
||||
} else if (name === 'giteapr') {
|
||||
ACTION_CACHES[context] = new GiteaPRAction($UD, context);
|
||||
} else if (name === 'modelrailway') {
|
||||
ACTION_CACHES[context] = new ModelRailwayAction($UD, context);
|
||||
} else if (name === 'railway') {
|
||||
ACTION_CACHES[context] = new RailwayAction($UD, context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,20 +10,20 @@
|
||||
<div class="uspi-wrapper">
|
||||
<form id="property-inspector">
|
||||
<div class="uspi-item">
|
||||
<div class="uspi-item-label" data-localize>URL</div>
|
||||
<input class="uspi-item-value" type="text" name="url" placeholder="https://..." />
|
||||
<div class="uspi-item-label" data-localize>MQTT URI</div>
|
||||
<input class="uspi-item-value" type="text" name="uri" placeholder="wss://mqtt.example.com" />
|
||||
</div>
|
||||
<div class="uspi-item">
|
||||
<div class="uspi-item-label" data-localize>Refresh Rate</div>
|
||||
<select class="uspi-item-value" name="refreshRate">
|
||||
<option value="0" data-localize>On Press</option>
|
||||
<option value="1" data-localize>Every 1 min</option>
|
||||
<option value="2" data-localize>Every 2 min</option>
|
||||
<option value="3" data-localize>Every 5 min</option>
|
||||
<option value="4" selected data-localize>Every 10 min</option>
|
||||
<option value="5" data-localize>Every 30 min</option>
|
||||
<option value="6" data-localize>Every Hour</option>
|
||||
</select>
|
||||
<div class="uspi-item-label" data-localize>Username</div>
|
||||
<input class="uspi-item-value" type="text" name="username" />
|
||||
</div>
|
||||
<div class="uspi-item">
|
||||
<div class="uspi-item-label" data-localize>Password</div>
|
||||
<input class="uspi-item-value" type="password" name="password" />
|
||||
</div>
|
||||
<div class="uspi-item">
|
||||
<div class="uspi-item-label" data-localize>Topic</div>
|
||||
<input class="uspi-item-value" type="text" name="topic" placeholder="copilot/usage" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
|
||||
<title>Model Railway</title>
|
||||
<link rel="stylesheet" href="../../libs/css/uspi.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="uspi-wrapper">
|
||||
<form id="property-inspector">
|
||||
<div class="uspi-item">
|
||||
<div class="uspi-item-label" data-localize>Server</div>
|
||||
<input class="uspi-item-value" type="text" name="host" placeholder="system-control.local" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script src="../../libs/js/constants.js"></script>
|
||||
<script src="../../libs/js/eventEmitter.js"></script>
|
||||
<script src="../../libs/js/utils.js"></script>
|
||||
<script src="../../libs/js/ulanzideckApi.js"></script>
|
||||
<script src="./inspector.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
|
||||
<title>Model Railway</title>
|
||||
<link rel="stylesheet" href="../../libs/css/uspi.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="uspi-wrapper">
|
||||
<form id="property-inspector">
|
||||
<div class="uspi-item">
|
||||
<div class="uspi-item-label" data-localize>MQTT URI</div>
|
||||
<input class="uspi-item-value" type="text" name="uri" placeholder="wss://broker.example.com:8883" />
|
||||
</div>
|
||||
<div class="uspi-item">
|
||||
<div class="uspi-item-label" data-localize>Username</div>
|
||||
<input class="uspi-item-value" type="text" name="username" placeholder="user" />
|
||||
</div>
|
||||
<div class="uspi-item">
|
||||
<div class="uspi-item-label" data-localize>Password</div>
|
||||
<input class="uspi-item-value" type="password" name="password" />
|
||||
</div>
|
||||
<div class="uspi-item">
|
||||
<div class="uspi-item-label" data-localize>Device ID</div>
|
||||
<input class="uspi-item-value" type="text" name="deviceId" placeholder="DC:54:75:D6:09:AC" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script src="../../libs/js/constants.js"></script>
|
||||
<script src="../../libs/js/eventEmitter.js"></script>
|
||||
<script src="../../libs/js/utils.js"></script>
|
||||
<script src="../../libs/js/ulanzideckApi.js"></script>
|
||||
<script src="./inspector.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
let ACTION_SETTING = {};
|
||||
let form = '';
|
||||
|
||||
$UD.connect('dev.mars3142.ulanzideck.collection.modelrailway');
|
||||
$UD.connect('dev.mars3142.ulanzideck.collection.railway');
|
||||
|
||||
$UD.onConnected(() => {
|
||||
form = document.querySelector('#property-inspector');
|
||||
Reference in New Issue
Block a user