feat(printer): add smart-plug power switch button (Issue #103); fix(update): stable update check missing behind prereleases (Issue #104)
Issue #103: the printer has no MQTT-level command to power off or enter standby, so users on a separate-room setup have to physically walk over or use a smart plug (e.g. Tasmota) manually. Added per-printer power_on_url/power_off_url/power_status_url config (Settings > Power Switch) and a power button with a live on/off indicator on each printer's card in the Printers grid - plain HTTP GET calls, no Moonraker device_power dependency. Issue #104: the stable-release update check only ever requested the single newest Gitea release (limit=1) regardless of type. Since nightly/dev prereleases publish far more often than stable ones, that newest release is almost always a prerelease, so the "not a prerelease" filter found nothing and reported "no stable releases found" even though a newer stable release existed further back in the list. Fixed by requesting enough releases (limit=20) to look past a run of prereleases. Also fixes a related pre-existing bug surfaced while testing #103's config: configparser's default string interpolation rejected any config.ini value containing a literal '%' (ValueError: invalid interpolation syntax) - this broke saving Tasmota-style power URLs (cmnd=Power%20on) and would have broken any other value with a '%' character. Fixed globally with interpolation=None on every ConfigParser() instantiation in config_loader.py and kobrax_moonraker_bridge.py.
This commit is contained in:
@ -450,6 +450,11 @@ function applyLang(){
|
||||
setText('lbl-password',T.settings_password);
|
||||
setText('lbl-device-id',T.settings_device_id);
|
||||
setText('lbl-mode-id',T.settings_mode_id);
|
||||
setText('modal-sec-power',T.settings_power||'Power Switch');
|
||||
setText('lbl-power-on-url',T.settings_power_on_url||'Power-On URL');
|
||||
setText('lbl-power-off-url',T.settings_power_off_url||'Power-Off URL');
|
||||
setText('lbl-power-status-url',T.settings_power_status_url||'Status URL');
|
||||
setText('lbl-power-hint',T.settings_power_hint||'Optional: plain HTTP GET URLs for a smart plug (e.g. Tasmota) controlling the printer\'s mains power. Leave empty to hide the power button.');
|
||||
setText('lbl-default-slot',T.settings_default_slot);
|
||||
setText('opt-slot-auto',T.settings_slot_auto);
|
||||
setText('lbl-auto-leveling',T.settings_auto_leveling);
|
||||
@ -1127,6 +1132,9 @@ function openSettings(){
|
||||
document.getElementById('s-password').value=d.password||'';
|
||||
document.getElementById('s-device-id').value=d.device_id||'';
|
||||
document.getElementById('s-mode-id').value=d.mode_id||'';
|
||||
var pon=document.getElementById('s-power-on-url');if(pon)pon.value=d.power_on_url||'';
|
||||
var poff=document.getElementById('s-power-off-url');if(poff)poff.value=d.power_off_url||'';
|
||||
var pstat=document.getElementById('s-power-status-url');if(pstat)pstat.value=d.power_status_url||'';
|
||||
document.getElementById('s-default-slot').value=d.default_ams_slot||'auto';
|
||||
document.getElementById('s-auto-leveling').checked=(d.auto_leveling===undefined?true:!!d.auto_leveling);
|
||||
var vc=document.getElementById('s-vibration-compensation');if(vc)vc.checked=!!d.vibration_compensation;
|
||||
@ -1895,6 +1903,9 @@ function saveSettings(){
|
||||
password: document.getElementById('s-password').value,
|
||||
device_id: document.getElementById('s-device-id').value,
|
||||
mode_id: document.getElementById('s-mode-id').value,
|
||||
power_on_url: (document.getElementById('s-power-on-url')||{}).value||'',
|
||||
power_off_url: (document.getElementById('s-power-off-url')||{}).value||'',
|
||||
power_status_url: (document.getElementById('s-power-status-url')||{}).value||'',
|
||||
default_ams_slot: document.getElementById('s-default-slot').value,
|
||||
auto_leveling: document.getElementById('s-auto-leveling').checked?1:0,
|
||||
vibration_compensation: (document.getElementById('s-vibration-compensation')||{}).checked?1:0,
|
||||
@ -3984,6 +3995,7 @@ function loadPrinterTab(){
|
||||
'<span style="font-weight:700;font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">🖨 '+p.name+'</span>'+
|
||||
'<span style="display:flex;align-items:center;gap:8px;flex-shrink:0">'+
|
||||
(isActive?'<span style="font-size:11px;color:var(--accent);font-weight:600">'+T.printers_active+'</span>':'')+
|
||||
(p.has_power_control?'<button id="power-btn-'+printerNum+'" onclick="togglePrinterPower(\''+printerNum+'\')" title="'+T.printers_power+'" style="background:none;border:none;color:var(--txt2);font-size:16px;cursor:pointer;line-height:1;padding:0">🔌</button>':'')+
|
||||
'<button onclick="removePrinter(\''+printerNum+'\',\''+nameEsc+'\')" title="'+T.printers_remove+'" style="background:none;border:none;color:var(--txt2);font-size:16px;cursor:pointer;line-height:1;padding:0">✕</button>'+
|
||||
'</span>'+
|
||||
'</div>'+
|
||||
@ -4000,8 +4012,40 @@ function loadPrinterTab(){
|
||||
(!isActive?'<a href="'+url+'/printer'+printerNum+'" style="display:block;text-align:center;padding:7px;background:var(--accent);color:#fff;border-radius:7px;font-size:13px;font-weight:600;text-decoration:none;margin-top:4px">'+T.printers_switch+'</a>':'<div style="text-align:center;padding:7px;font-size:12px;color:var(--txt2)">'+T.printers_current+'</div>')+
|
||||
'</div>';
|
||||
}).join('');
|
||||
results.forEach(function(res){
|
||||
if(res.printer.has_power_control)_refreshPrinterPowerIcon(res.printer.id,(res.printer.bridge_url||'').replace(/\/+$/,''));
|
||||
});
|
||||
});
|
||||
}).catch(function(e){
|
||||
if(grid)grid.innerHTML='<div style="color:var(--err);font-size:13px;padding:20px">Fehler: '+e+'</div>';
|
||||
});
|
||||
}
|
||||
|
||||
function _refreshPrinterPowerIcon(pid,bridgeUrl){
|
||||
fetch((bridgeUrl||'')+'/kx/printers/'+encodeURIComponent(pid)+'/power-status',{signal:AbortSignal.timeout(5000)})
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(d){
|
||||
var btn=document.getElementById('power-btn-'+pid);
|
||||
if(!btn)return;
|
||||
if(d.state==='on'){btn.style.color='var(--ok)';btn.title=T.printers_power_on||'Power: On';}
|
||||
else if(d.state==='off'){btn.style.color='var(--txt2)';btn.title=T.printers_power_off||'Power: Off';}
|
||||
})
|
||||
.catch(function(){/* status endpoint optional - icon just stays neutral */});
|
||||
}
|
||||
|
||||
function togglePrinterPower(pid){
|
||||
var btn=document.getElementById('power-btn-'+pid);
|
||||
var currentlyOn=btn&&btn.style.color&&btn.style.color.indexOf('var(--ok)')!==-1;
|
||||
// Without a known current state, default to "on" - turning an already-off
|
||||
// switch on is harmless, whereas guessing "off" on a printer mid-print is not.
|
||||
var action=currentlyOn?'off':'on';
|
||||
if(action==='off'&&!confirm(T.printers_power_off_confirm||'Turn printer power off? Make sure no print is running.'))return;
|
||||
if(btn)btn.style.opacity='0.5';
|
||||
post('/kx/printers/'+encodeURIComponent(pid)+'/power',{action:action}).then(function(){
|
||||
if(btn)btn.style.opacity='1';
|
||||
setTimeout(function(){loadPrinterTab();},1500);
|
||||
}).catch(function(e){
|
||||
if(btn)btn.style.opacity='1';
|
||||
clog('Power-Fehler: '+e,'msg-err');
|
||||
});
|
||||
}
|
||||
|
||||
@ -548,6 +548,22 @@
|
||||
<input type="text" id="s-mode-id" placeholder="20030">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-title"><span>🔌</span> <span id="modal-sec-power">Power Switch</span></div>
|
||||
<div class="modal-field">
|
||||
<label id="lbl-power-on-url">Power-On URL</label>
|
||||
<input type="text" id="s-power-on-url" placeholder="http://192.168.x.x/cm?cmnd=Power%20on">
|
||||
</div>
|
||||
<div class="modal-field">
|
||||
<label id="lbl-power-off-url">Power-Off URL</label>
|
||||
<input type="text" id="s-power-off-url" placeholder="http://192.168.x.x/cm?cmnd=Power%20off">
|
||||
</div>
|
||||
<div class="modal-field">
|
||||
<label id="lbl-power-status-url">Status URL</label>
|
||||
<input type="text" id="s-power-status-url" placeholder="http://192.168.x.x/cm?cmnd=Power">
|
||||
<small id="lbl-power-hint" style="color:var(--txt2)"></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Drucker -->
|
||||
|
||||
@ -224,6 +224,10 @@
|
||||
"printers_loading": "Lade…",
|
||||
"printers_none": "Keine Drucker konfiguriert.",
|
||||
"printers_remove": "Drucker entfernen",
|
||||
"printers_power": "Drucker-Stromversorgung schalten",
|
||||
"printers_power_on": "Strom: An",
|
||||
"printers_power_off": "Strom: Aus",
|
||||
"printers_power_off_confirm": "Drucker-Strom ausschalten? Stelle sicher, dass kein Druck läuft.",
|
||||
"printers_remove_confirm": "Drucker \"{name}\" entfernen? Die Bridge startet neu.",
|
||||
"printers_switch": "Wechseln →",
|
||||
"progress_action_clear": "Leeren",
|
||||
@ -257,6 +261,11 @@
|
||||
"settings_language": "Sprache",
|
||||
"settings_mode_id": "Mode-ID",
|
||||
"settings_mode_id_placeholder": "20030",
|
||||
"settings_power": "Steckdose (Ein/Aus)",
|
||||
"settings_power_on_url": "Einschalt-URL",
|
||||
"settings_power_off_url": "Ausschalt-URL",
|
||||
"settings_power_status_url": "Status-URL",
|
||||
"settings_power_hint": "Optional: einfache HTTP-GET-URLs für eine Steckdose (z.B. Tasmota), die den Drucker per Netzstrom schaltet. Leer lassen blendet den Power-Button aus.",
|
||||
"settings_mqtt_port": "MQTT-Port",
|
||||
"settings_mqtt_username_placeholder": "userXXXXXXXX",
|
||||
"settings_orca_profiles_import": "Profile importieren",
|
||||
|
||||
@ -224,6 +224,10 @@
|
||||
"printers_loading": "Loading…",
|
||||
"printers_none": "No printers configured.",
|
||||
"printers_remove": "Remove printer",
|
||||
"printers_power": "Toggle printer power",
|
||||
"printers_power_on": "Power: On",
|
||||
"printers_power_off": "Power: Off",
|
||||
"printers_power_off_confirm": "Turn printer power off? Make sure no print is running.",
|
||||
"printers_remove_confirm": "Remove printer \"{name}\"? The bridge will restart.",
|
||||
"printers_switch": "Switch →",
|
||||
"progress_action_clear": "Clear",
|
||||
@ -257,6 +261,11 @@
|
||||
"settings_language": "Language",
|
||||
"settings_mode_id": "Mode ID",
|
||||
"settings_mode_id_placeholder": "20030",
|
||||
"settings_power": "Power Switch",
|
||||
"settings_power_on_url": "Power-On URL",
|
||||
"settings_power_off_url": "Power-Off URL",
|
||||
"settings_power_status_url": "Status URL",
|
||||
"settings_power_hint": "Optional: plain HTTP GET URLs for a smart plug (e.g. Tasmota) controlling the printer's mains power. Leave empty to hide the power button.",
|
||||
"settings_mqtt_port": "MQTT Port",
|
||||
"settings_mqtt_username_placeholder": "userXXXXXXXX",
|
||||
"settings_orca_profiles_import": "Import profiles",
|
||||
|
||||
@ -224,6 +224,10 @@
|
||||
"printers_loading": "Cargando…",
|
||||
"printers_none": "No hay impresoras configuradas.",
|
||||
"printers_remove": "Eliminar impresora",
|
||||
"printers_power": "Alternar alimentación de la impresora",
|
||||
"printers_power_on": "Alimentación: Encendida",
|
||||
"printers_power_off": "Alimentación: Apagada",
|
||||
"printers_power_off_confirm": "¿Apagar la alimentación de la impresora? Asegúrate de que no haya ninguna impresión en curso.",
|
||||
"printers_remove_confirm": "¿Eliminar impresora \"{name}\"? El bridge se reiniciará.",
|
||||
"printers_switch": "Cambiar →",
|
||||
"progress_action_clear": "Vaciar",
|
||||
@ -257,6 +261,11 @@
|
||||
"settings_language": "Idioma",
|
||||
"settings_mode_id": "ID de modo",
|
||||
"settings_mode_id_placeholder": "20030",
|
||||
"settings_power": "Enchufe inteligente",
|
||||
"settings_power_on_url": "URL de encendido",
|
||||
"settings_power_off_url": "URL de apagado",
|
||||
"settings_power_status_url": "URL de estado",
|
||||
"settings_power_hint": "Opcional: URLs HTTP GET para un enchufe inteligente (p.ej. Tasmota) que controla la alimentación de la impresora. Déjalo vacío para ocultar el botón de encendido.",
|
||||
"settings_mqtt_port": "MQTT Port",
|
||||
"settings_mqtt_username_placeholder": "userXXXXXXXX",
|
||||
"settings_orca_profiles_import": "Importar perfiles",
|
||||
|
||||
@ -212,6 +212,10 @@
|
||||
"printers_loading": "Chargement…",
|
||||
"printers_none": "Aucune imprimante configurée.",
|
||||
"printers_remove": "Supprimer l'imprimante",
|
||||
"printers_power": "Basculer l'alimentation de l'imprimante",
|
||||
"printers_power_on": "Alimentation : Allumée",
|
||||
"printers_power_off": "Alimentation : Éteinte",
|
||||
"printers_power_off_confirm": "Éteindre l'alimentation de l'imprimante ? Assurez-vous qu'aucune impression n'est en cours.",
|
||||
"printers_remove_confirm": "Supprimer l'imprimante \"{name}\" ? Le bridge va redémarrer.",
|
||||
"printers_switch": "Changer →",
|
||||
"progress_action_clear": "Vider",
|
||||
@ -245,6 +249,11 @@
|
||||
"settings_language": "Langue",
|
||||
"settings_mode_id": "ID du mode",
|
||||
"settings_mode_id_placeholder": "20030",
|
||||
"settings_power": "Prise électrique",
|
||||
"settings_power_on_url": "URL d'allumage",
|
||||
"settings_power_off_url": "URL d'extinction",
|
||||
"settings_power_status_url": "URL de statut",
|
||||
"settings_power_hint": "Optionnel : URL HTTP GET pour une prise connectée (ex. Tasmota) contrôlant l'alimentation secteur de l'imprimante. Laisser vide masque le bouton d'alimentation.",
|
||||
"settings_mqtt_port": "Port MQTT",
|
||||
"settings_mqtt_username_placeholder": "userXXXXXXXX",
|
||||
"settings_orca_profiles_import": "Importer des profils",
|
||||
|
||||
@ -212,6 +212,10 @@
|
||||
"printers_loading": "Caricamento in corso…",
|
||||
"printers_none": "Nessuna stampante configurata.",
|
||||
"printers_remove": "Rimuovi stampante",
|
||||
"printers_power": "Attiva/disattiva alimentazione stampante",
|
||||
"printers_power_on": "Alimentazione: Accesa",
|
||||
"printers_power_off": "Alimentazione: Spenta",
|
||||
"printers_power_off_confirm": "Spegnere l'alimentazione della stampante? Assicurati che non sia in corso alcuna stampa.",
|
||||
"printers_remove_confirm": "Rimuovere la stampante \"{name}\"? Il bridge si riavvierà.",
|
||||
"printers_switch": "Cambia →",
|
||||
"progress_action_clear": "Cancella",
|
||||
@ -245,6 +249,11 @@
|
||||
"settings_language": "Lingua",
|
||||
"settings_mode_id": "ID modalità",
|
||||
"settings_mode_id_placeholder": "20030",
|
||||
"settings_power": "Presa elettrica",
|
||||
"settings_power_on_url": "URL accensione",
|
||||
"settings_power_off_url": "URL spegnimento",
|
||||
"settings_power_status_url": "URL stato",
|
||||
"settings_power_hint": "Opzionale: URL HTTP GET per una presa smart (es. Tasmota) che controlla l'alimentazione della stampante. Lasciare vuoto per nascondere il pulsante di accensione.",
|
||||
"settings_mqtt_port": "Porta MQTT",
|
||||
"settings_mqtt_username_placeholder": "userXXXXXXXX",
|
||||
"settings_orca_profiles_import": "Importa profili",
|
||||
|
||||
@ -224,6 +224,10 @@
|
||||
"printers_loading": "加载中…",
|
||||
"printers_none": "未配置打印机。",
|
||||
"printers_remove": "移除打印机",
|
||||
"printers_power": "切换打印机电源",
|
||||
"printers_power_on": "电源:开",
|
||||
"printers_power_off": "电源:关",
|
||||
"printers_power_off_confirm": "关闭打印机电源?请确认当前没有正在进行的打印任务。",
|
||||
"printers_remove_confirm": "移除打印机 \"{name}\"? Bridge 将重启。",
|
||||
"printers_switch": "切换 →",
|
||||
"progress_action_clear": "清除",
|
||||
@ -257,6 +261,11 @@
|
||||
"settings_language": "语言",
|
||||
"settings_mode_id": "模式 ID",
|
||||
"settings_mode_id_placeholder": "20030",
|
||||
"settings_power": "电源插座",
|
||||
"settings_power_on_url": "开机 URL",
|
||||
"settings_power_off_url": "关机 URL",
|
||||
"settings_power_status_url": "状态 URL",
|
||||
"settings_power_hint": "可选:智能插座(如 Tasmota)的 HTTP GET 控制 URL,用于控制打印机的电源。留空则隐藏电源按钮。",
|
||||
"settings_mqtt_port": "MQTT 端口",
|
||||
"settings_mqtt_username_placeholder": "userXXXXXXXX",
|
||||
"settings_orca_profiles_import": "导入配置文件",
|
||||
|
||||
Reference in New Issue
Block a user