ACE2 Drying Card v2, Dryer Presets,
This commit is contained in:
@@ -32,3 +32,29 @@ auto_leveling = 1
|
||||
[bridge]
|
||||
# Poll-Intervall in Sekunden
|
||||
poll_interval = 3
|
||||
|
||||
[ace_dry_presets]
|
||||
# Vordefinierte Dry-Set Presets (Temp in °C, Dauer in Sekunden)
|
||||
pla_temp = 45
|
||||
pla_duration_sec = 14400
|
||||
pla_plus_temp = 45
|
||||
pla_plus_duration_sec = 14400
|
||||
petg_temp = 50
|
||||
petg_duration_sec = 14400
|
||||
tpu_temp = 55
|
||||
tpu_duration_sec = 14400
|
||||
abs_asa_temp = 45
|
||||
abs_asa_duration_sec = 28800
|
||||
pa_pc_temp = 55
|
||||
pa_pc_duration_sec = 43200
|
||||
|
||||
# Custom Presets (Name + Temp + Dauer)
|
||||
custom_1_name = Custom 1
|
||||
custom_1_temp = 45
|
||||
custom_1_duration_sec = 14400
|
||||
custom_2_name = Custom 2
|
||||
custom_2_temp = 45
|
||||
custom_2_duration_sec = 14400
|
||||
custom_3_name = Custom 3
|
||||
custom_3_temp = 45
|
||||
custom_3_duration_sec = 14400
|
||||
|
||||
@@ -34,3 +34,29 @@ auto_leveling = 1
|
||||
[bridge]
|
||||
# Poll-Intervall in Sekunden
|
||||
poll_interval = 3
|
||||
|
||||
[ace_dry_presets]
|
||||
# Vordefinierte Dry-Set Presets (Temp in °C, Dauer in Sekunden)
|
||||
pla_temp = 45
|
||||
pla_duration_sec = 14400
|
||||
pla_plus_temp = 45
|
||||
pla_plus_duration_sec = 14400
|
||||
petg_temp = 50
|
||||
petg_duration_sec = 14400
|
||||
tpu_temp = 55
|
||||
tpu_duration_sec = 14400
|
||||
abs_asa_temp = 45
|
||||
abs_asa_duration_sec = 28800
|
||||
pa_pc_temp = 55
|
||||
pa_pc_duration_sec = 43200
|
||||
|
||||
# Custom Presets (Name + Temp + Dauer)
|
||||
custom_1_name = Custom 1
|
||||
custom_1_temp = 45
|
||||
custom_1_duration_sec = 14400
|
||||
custom_2_name = Custom 2
|
||||
custom_2_temp = 45
|
||||
custom_2_duration_sec = 14400
|
||||
custom_3_name = Custom 3
|
||||
custom_3_temp = 45
|
||||
custom_3_duration_sec = 14400
|
||||
|
||||
@@ -422,32 +422,32 @@ class KobraXBridge:
|
||||
self.ws_clients: set[web.WebSocketResponse] = set()
|
||||
self._last_state: dict = {}
|
||||
self._state = {
|
||||
"nozzle_temp": 0.0,
|
||||
"nozzle_target": 0.0,
|
||||
"bed_temp": 0.0,
|
||||
"bed_target": 0.0,
|
||||
"print_state": "standby",
|
||||
"kobra_state": "free",
|
||||
"filename": "",
|
||||
"slicer_time": 0,
|
||||
"progress": 0.0,
|
||||
"print_duration": 0,
|
||||
"remain_time": 0,
|
||||
"curr_layer": 0,
|
||||
"total_layers": 0,
|
||||
"printer_name": env_loader.get("BRIDGE_PRINTER_NAME", "Anycubic Kobra X"),
|
||||
"firmware_version": "unknown",
|
||||
"upload_url": "",
|
||||
"camera_url": "",
|
||||
"fan_speed": 0,
|
||||
"light_on": False,
|
||||
"light_brightness": 80,
|
||||
"taskid": "-1",
|
||||
"print_speed_mode": 2,
|
||||
"connection_error": "",
|
||||
"file_ready": "",
|
||||
"filament_mode": "toolhead",
|
||||
"ace_drying": {"status": 0, "target_temp": 0, "duration": 0, "remain_time": 0, "humidity": None, "current_temp": None},
|
||||
"nozzle_temp": 0.0,
|
||||
"nozzle_target": 0.0,
|
||||
"bed_temp": 0.0,
|
||||
"bed_target": 0.0,
|
||||
"print_state": "standby",
|
||||
"kobra_state": "free",
|
||||
"filename": "",
|
||||
"slicer_time": 0,
|
||||
"progress": 0.0,
|
||||
"print_duration": 0,
|
||||
"remain_time": 0,
|
||||
"curr_layer": 0,
|
||||
"total_layers": 0,
|
||||
"printer_name": env_loader.get("BRIDGE_PRINTER_NAME", "Anycubic Kobra X"),
|
||||
"firmware_version": "unknown",
|
||||
"upload_url": "",
|
||||
"camera_url": "",
|
||||
"fan_speed": 0,
|
||||
"light_on": False,
|
||||
"light_brightness": 80,
|
||||
"taskid": "-1",
|
||||
"print_speed_mode": 2,
|
||||
"connection_error": "",
|
||||
"file_ready": "",
|
||||
"filament_mode": "toolhead",
|
||||
"ace_drying": {"status": 0, "target_temp": 0, "duration": 0, "remain_time": 0, "humidity": None, "current_temp": None},
|
||||
}
|
||||
self._ams_slots: list[dict] = [] # flat global list; each entry has global_index + box_id
|
||||
self._ams_loaded_slot: int = -1 # global slot index of currently loaded slot
|
||||
@@ -461,6 +461,7 @@ class KobraXBridge:
|
||||
self._current_job_id: str = ""
|
||||
|
||||
self._thumbnail_b64: str = ""
|
||||
self._ace_dry_presets: dict[str, dict] = self._load_ace_dry_presets_config()
|
||||
|
||||
# Part-Skip: zuletzt vom Drucker gemeldete Skip-Liste (v0.9.10)
|
||||
self._skip_state: dict = {"objects": [], "skipped": [], "ts": 0}
|
||||
@@ -474,6 +475,73 @@ class KobraXBridge:
|
||||
client.callbacks["light/report"] = self._on_light
|
||||
client.callbacks["skip/report"] = self._on_skip
|
||||
|
||||
def _default_ace_dry_presets(self) -> dict[str, dict]:
|
||||
return {
|
||||
"pla": {"temp": 45, "duration_sec": 4 * 3600},
|
||||
"pla_plus": {"temp": 45, "duration_sec": 4 * 3600},
|
||||
"petg": {"temp": 50, "duration_sec": 4 * 3600},
|
||||
"tpu": {"temp": 55, "duration_sec": 4 * 3600},
|
||||
"abs_asa": {"temp": 45, "duration_sec": 8 * 3600},
|
||||
"pa_pc": {"temp": 55, "duration_sec": 12 * 3600},
|
||||
"custom_1": {"name": "Custom 1", "temp": 45, "duration_sec": 4 * 3600},
|
||||
"custom_2": {"name": "Custom 2", "temp": 45, "duration_sec": 4 * 3600},
|
||||
"custom_3": {"name": "Custom 3", "temp": 45, "duration_sec": 4 * 3600},
|
||||
}
|
||||
|
||||
def _sanitize_ace_dry_presets(self, presets: dict) -> dict[str, dict]:
|
||||
out = self._default_ace_dry_presets()
|
||||
for key in list(out.keys()):
|
||||
src = presets.get(key) if isinstance(presets, dict) else None
|
||||
if not isinstance(src, dict):
|
||||
continue
|
||||
try:
|
||||
t = int(src.get("temp", out[key]["temp"]))
|
||||
except Exception:
|
||||
t = out[key]["temp"]
|
||||
try:
|
||||
d = int(src.get("duration_sec", out[key]["duration_sec"]))
|
||||
except Exception:
|
||||
d = out[key]["duration_sec"]
|
||||
out[key]["temp"] = max(30, min(80, t))
|
||||
out[key]["duration_sec"] = max(10 * 60, min(24 * 3600, d))
|
||||
if key.startswith("custom_"):
|
||||
name = str(src.get("name", out[key].get("name", key.replace("_", " ").title()))).strip()
|
||||
out[key]["name"] = name or out[key].get("name", "Custom")
|
||||
return out
|
||||
|
||||
def _load_ace_dry_presets_config(self) -> dict[str, dict]:
|
||||
import configparser
|
||||
defaults = self._default_ace_dry_presets()
|
||||
cfg_path = self._find_config_path()
|
||||
if not cfg_path.is_file():
|
||||
return defaults
|
||||
cfg = configparser.ConfigParser()
|
||||
cfg.read(cfg_path, encoding="utf-8")
|
||||
sec = "ace_dry_presets"
|
||||
if not cfg.has_section(sec):
|
||||
return defaults
|
||||
out = {}
|
||||
for key, d in defaults.items():
|
||||
temp_k = f"{key}_temp"
|
||||
dur_k = f"{key}_duration_sec"
|
||||
try:
|
||||
temp = int(cfg.get(sec, temp_k, fallback=str(d["temp"])))
|
||||
except Exception:
|
||||
temp = d["temp"]
|
||||
try:
|
||||
dur = int(cfg.get(sec, dur_k, fallback=str(d["duration_sec"])))
|
||||
except Exception:
|
||||
dur = d["duration_sec"]
|
||||
out[key] = {
|
||||
"temp": max(30, min(80, temp)),
|
||||
"duration_sec": max(10 * 60, min(24 * 3600, dur)),
|
||||
}
|
||||
if key.startswith("custom_"):
|
||||
name_k = f"{key}_name"
|
||||
name = cfg.get(sec, name_k, fallback=str(d.get("name", key.replace("_", " ").title()))).strip()
|
||||
out[key]["name"] = name or str(d.get("name", "Custom"))
|
||||
return out
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# MQTT callbacks (called from reader thread)
|
||||
# -------------------------------------------------------------------------
|
||||
@@ -804,6 +872,17 @@ class KobraXBridge:
|
||||
def _current_temp_from(src: dict, default=None):
|
||||
return _num_from(src, ("current_temp", "cur_temp", "temperature", "temp", "drying_temp", "chamber_temp"), default)
|
||||
|
||||
def _minutes_from(src: dict, key: str, default=0):
|
||||
raw = src.get(key, default)
|
||||
try:
|
||||
value = int(float(raw))
|
||||
except Exception:
|
||||
return int(default)
|
||||
# Some firmware payloads report dryer times in seconds while the UI uses minutes.
|
||||
if value > (24 * 60):
|
||||
return max(0, int(round(value / 60.0)))
|
||||
return max(0, value)
|
||||
|
||||
per_unit: list[dict] = []
|
||||
for box in boxes:
|
||||
bid = int(box.get("id", -1))
|
||||
@@ -820,8 +899,8 @@ class KobraXBridge:
|
||||
"id": bid,
|
||||
"status": int(bs.get("status", 0)),
|
||||
"target_temp": int(bs.get("target_temp", 0)),
|
||||
"duration": int(bs.get("duration", 0)),
|
||||
"remain_time": int(bs.get("remain_time", 0)),
|
||||
"duration": _minutes_from(bs, "duration", 0),
|
||||
"remain_time": _minutes_from(bs, "remain_time", 0),
|
||||
"humidity": hu,
|
||||
"current_temp": ct,
|
||||
})
|
||||
@@ -843,8 +922,8 @@ class KobraXBridge:
|
||||
self._state["ace_drying"] = {
|
||||
"status": int(src.get("status", cur.get("status", 0))),
|
||||
"target_temp": int(src.get("target_temp", cur.get("target_temp", 0))),
|
||||
"duration": int(src.get("duration", cur.get("duration", 0))),
|
||||
"remain_time": int(src.get("remain_time", cur.get("remain_time", 0))),
|
||||
"duration": _minutes_from(src, "duration", cur.get("duration", 0)),
|
||||
"remain_time": _minutes_from(src, "remain_time", cur.get("remain_time", 0)),
|
||||
"humidity": _humidity_from(src, primary.get("humidity", cur.get("humidity"))),
|
||||
"current_temp": _current_temp_from(src, primary.get("current_temp", cur.get("current_temp"))),
|
||||
"units": per_unit,
|
||||
@@ -2438,12 +2517,82 @@ var S={nozzle_temp:0,nozzle_target:0,bed_temp:0,bed_target:0,
|
||||
print_state:'standby',filename:'',progress:0,print_duration:0,remain_time:0,
|
||||
curr_layer:0,total_layers:0,printer_name:'Kobra X',firmware_version:'–',
|
||||
camera_url:'',fan_speed:0,print_speed_mode:2,light_on:false,light_brightness:80,
|
||||
ams_slots:[],filament_mode:'toolhead',ace_units:[],ace_drying:{status:0,target_temp:0,duration:0,remain_time:0,humidity:null,current_temp:null,units:[]}};
|
||||
ams_slots:[],filament_mode:'toolhead',ace_units:[],ace_dry_presets:null,ace_drying:{status:0,target_temp:0,duration:0,remain_time:0,humidity:null,current_temp:null,units:[]}};
|
||||
var tempHistory={n:[],b:[]};
|
||||
var aceDryHistory={0:{t:[],h:[]},1:{t:[],h:[]},2:{t:[],h:[]},3:{t:[],h:[]}};
|
||||
var camOn=false;
|
||||
var currentStep=1;
|
||||
var currentPanel='dashboard';
|
||||
var aceAutoRefillPrefs=(function(){
|
||||
try{return JSON.parse(localStorage.getItem('aceAutoRefillPrefs')||'{}')||{};}catch(_){return {};}
|
||||
})();
|
||||
var aceDryProfiles=(function(){
|
||||
try{return JSON.parse(localStorage.getItem('aceDryProfiles')||'{}')||{};}catch(_){return {};}
|
||||
})();
|
||||
var _aceDryDialogAceId=-1;
|
||||
var _aceDryDialogPresetKey='';
|
||||
var _aceDryDialogPresetOriginals={};
|
||||
var ACE_DRY_PRESET_DEFAULTS={
|
||||
pla:{temp:45,duration_sec:4*3600},
|
||||
pla_plus:{temp:45,duration_sec:4*3600},
|
||||
petg:{temp:50,duration_sec:4*3600},
|
||||
tpu:{temp:55,duration_sec:4*3600},
|
||||
abs_asa:{temp:45,duration_sec:8*3600},
|
||||
pa_pc:{temp:55,duration_sec:12*3600}
|
||||
};
|
||||
var ACE_DRY_PRESETS={
|
||||
pla:{temp:45,duration_sec:4*3600},
|
||||
pla_plus:{temp:45,duration_sec:4*3600},
|
||||
petg:{temp:50,duration_sec:4*3600},
|
||||
tpu:{temp:55,duration_sec:4*3600},
|
||||
abs_asa:{temp:45,duration_sec:8*3600},
|
||||
pa_pc:{temp:55,duration_sec:12*3600},
|
||||
custom_1:{name:'Custom 1',temp:45,duration_sec:4*3600},
|
||||
custom_2:{name:'Custom 2',temp:45,duration_sec:4*3600},
|
||||
custom_3:{name:'Custom 3',temp:45,duration_sec:4*3600}
|
||||
};
|
||||
|
||||
function _aceAutoRefillGet(aceId){return !!aceAutoRefillPrefs[String(aceId)];}
|
||||
function _aceAutoRefillSet(aceId,on){
|
||||
aceAutoRefillPrefs[String(aceId)]=!!on;
|
||||
localStorage.setItem('aceAutoRefillPrefs',JSON.stringify(aceAutoRefillPrefs));
|
||||
}
|
||||
function _aceDryProfileGet(aceId){
|
||||
var p=aceDryProfiles[String(aceId)]||{};
|
||||
var temp=parseInt(p.temp,10);
|
||||
var dur=parseInt(p.duration_sec,10);
|
||||
if(!Number.isFinite(temp))temp=45;
|
||||
if(!Number.isFinite(dur))dur=4*3600;
|
||||
temp=Math.max(30,Math.min(80,temp));
|
||||
dur=Math.max(10*60,Math.min(24*3600,dur));
|
||||
return {temp:temp,duration_sec:dur,preset:p.preset||''};
|
||||
}
|
||||
function _aceDryProfileSet(aceId,temp,durationSec,preset){
|
||||
aceDryProfiles[String(aceId)]={
|
||||
temp:Math.max(30,Math.min(80,parseInt(temp,10)||45)),
|
||||
duration_sec:Math.max(10*60,Math.min(24*3600,parseInt(durationSec,10)||4*3600)),
|
||||
preset:preset||''
|
||||
};
|
||||
localStorage.setItem('aceDryProfiles',JSON.stringify(aceDryProfiles));
|
||||
}
|
||||
function _aceDryDurationMinFromSec(sec){
|
||||
var minutes=Math.round((parseInt(sec,10)||0)/60);
|
||||
return Math.max(10,Math.min(1440,minutes));
|
||||
}
|
||||
function _syncAceDryPresetsFromServer(raw){
|
||||
if(!raw||typeof raw!=='object')return;
|
||||
Object.keys(ACE_DRY_PRESETS).forEach(function(k){
|
||||
var p=raw[k];
|
||||
if(!p||typeof p!=='object')return;
|
||||
var t=parseInt(p.temp,10);
|
||||
var d=parseInt(p.duration_sec,10);
|
||||
if(Number.isFinite(t))ACE_DRY_PRESETS[k].temp=Math.max(30,Math.min(80,t));
|
||||
if(Number.isFinite(d))ACE_DRY_PRESETS[k].duration_sec=Math.max(10*60,Math.min(24*3600,d));
|
||||
if(/^custom_[123]$/.test(k)&&typeof p.name==='string'){
|
||||
var n=p.name.trim();
|
||||
ACE_DRY_PRESETS[k].name=n||('Custom '+k.slice(-1));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── Theme ──
|
||||
function toggleTheme(){
|
||||
@@ -2461,7 +2610,7 @@ var LANG_DE={
|
||||
card_progress:'Fortschritt',card_temps:'Temperaturen',card_light_fan:'Lüfter',card_speed:'Druckgeschwindigkeit',card_cam:'Kamera',lbl_elapsed:'Verstrichen:',lbl_remaining:'Restzeit:',lbl_slicer_time:'Slicer-Schätzung:',lbl_layers:'Layer',
|
||||
speed_silent:'🐢 Leise',speed_normal:'⚡ Normal',speed_sport:'🚀 Sport',
|
||||
lbl_light:'💡 Licht',lbl_feed:'Einziehen',lbl_unload:'Ausziehen',
|
||||
card_ace_dry:'ACE Trocknung',ace_dry_dryer:'Trockner',ace_dry_status_off:'Status: Aus',ace_dry_status_on:'Status: Aktiv',ace_dry_status_remaining:'Rest',ace_dry_humidity:'Luftfeuchte',ace_dry_current_temp:'Aktuelle Temperatur',ace_dry_chart:'Verlauf (Temp/Feuchte)',ace_dry_temp:'Temperatur (°C)',ace_dry_duration:'Dauer (Min)',ace_dry_start:'▶ Start',ace_dry_stop:'■ Stop',
|
||||
card_ace_dry:'ACE Trocknung',ace_dry_dryer:'Trockner',ace_dry_status_off:'Status: Aus',ace_dry_status_on:'Status: Aktiv',ace_dry_status_remaining:'Rest',ace_dry_humidity:'Luftfeuchte',ace_dry_current_temp:'Temperatur',ace_dry_chart:'Verlauf (Temp/Feuchte)',ace_dry_temp:'Temperatur (°C)',ace_dry_duration:'Dauer (Min)',ace_dry_start:'▶ Start',ace_dry_stop:'■ Stop',ace_dry_auto_refill:'Auto Refill',ace_dry_enable:'Enable Drying',ace_dry_temp_line:'Trocknungstemperatur',ace_dry_time_line:'Trocknungszeit',ace_dry_ui_pending:'(nur UI, Backend folgt)',ace_dry_dialog_title:'Dryer Temp/Time Settings',ace_dry_dialog_temp:'Temperature (30-80°C)',ace_dry_dialog_time:'Rem. Time (h:m:s)',ace_dry_dialog_confirm:'Confirm',ace_dry_dialog_cancel:'Cancel',ace_dry_dialog_save_restart:'Speichern & Neustart',ace_dry_dialog_custom_name:'Custom Name',
|
||||
cam_placeholder:'📷 Kamera nicht gestartet',btn_cam_start:'▶ Kamera',btn_cam_stop:'◼ Kamera',
|
||||
btn_pause:'⏸ Pause',btn_resume:'▶ Weiter',btn_cancel:'✕ Stopp',
|
||||
label_nozzle:'Nozzle',label_bed:'Bett',label_fan:'🌀 Lüfter',label_light:'💡 Licht',label_on_off:'Ein / Aus',label_speed:'Geschwindigkeit',
|
||||
@@ -2524,7 +2673,7 @@ var LANG_EN={
|
||||
card_progress:'Progress',card_temps:'Temperatures',card_light_fan:'Fan',card_speed:'Print Speed',card_cam:'Camera',lbl_elapsed:'Elapsed:',lbl_remaining:'Remaining:',lbl_slicer_time:'Slicer estimate:',lbl_layers:'Layer',
|
||||
speed_silent:'🐢 Silent',speed_normal:'⚡ Normal',speed_sport:'🚀 Sport',
|
||||
lbl_light:'💡 Light',lbl_feed:'Load',lbl_unload:'Unload',
|
||||
card_ace_dry:'ACE Drying',ace_dry_dryer:'Dryer',ace_dry_status_off:'Status: Off',ace_dry_status_on:'Status: Active',ace_dry_status_remaining:'Remaining',ace_dry_humidity:'Humidity',ace_dry_current_temp:'Current Temp',ace_dry_chart:'History (Temp/Humidity)',ace_dry_temp:'Temperature (°C)',ace_dry_duration:'Duration (min)',ace_dry_start:'▶ Start',ace_dry_stop:'■ Stop',
|
||||
card_ace_dry:'ACE Drying',ace_dry_dryer:'Dryer',ace_dry_status_off:'Status: Off',ace_dry_status_on:'Status: Active',ace_dry_status_remaining:'Remaining',ace_dry_humidity:'Humidity',ace_dry_current_temp:'Temperature',ace_dry_chart:'History (Temp/Humidity)',ace_dry_temp:'Temperature (°C)',ace_dry_duration:'Duration (min)',ace_dry_start:'▶ Start',ace_dry_stop:'■ Stop',ace_dry_auto_refill:'Auto Refill',ace_dry_enable:'Enable Drying',ace_dry_temp_line:'Drying Temperature',ace_dry_time_line:'Drying Time',ace_dry_ui_pending:'(UI only, backend next)',ace_dry_dialog_title:'Dryer Temp/Time Settings',ace_dry_dialog_temp:'Temperature (30-80°C)',ace_dry_dialog_time:'Rem. Time (h:m:s)',ace_dry_dialog_confirm:'Confirm',ace_dry_dialog_cancel:'Cancel',ace_dry_dialog_save_restart:'Save & Restart',ace_dry_dialog_custom_name:'Custom Name',
|
||||
cam_placeholder:'📷 Camera not started',btn_cam_start:'▶ Camera',btn_cam_stop:'◼ Camera',
|
||||
btn_pause:'⏸ Pause',btn_resume:'▶ Resume',btn_cancel:'✕ Stop',
|
||||
label_nozzle:'Nozzle',label_bed:'Bed',label_fan:'🌀 Fan',label_light:'💡 Light',label_on_off:'On / Off',label_speed:'Speed',
|
||||
@@ -2739,14 +2888,25 @@ function applyLang(){
|
||||
document.querySelectorAll('.lbl-unload').forEach(e=>e.textContent=T.lbl_unload);
|
||||
for(var i=0;i<4;i++){
|
||||
setText('d-card-ace-dry-'+i,'ACE '+(i+1)+' - '+(T.ace_dry_dryer||'Dryer'));
|
||||
setText('ace-dry-start-'+i,T.ace_dry_start);
|
||||
setText('ace-dry-stop-'+i,T.ace_dry_stop);
|
||||
setText('d-ace-auto-refill-label-'+i,T.ace_dry_auto_refill||'Auto Refill');
|
||||
setText('d-ace-drying-enable-label-'+i,T.ace_dry_enable||'Enable Drying');
|
||||
setText('d-ace-dry-humidity-label-'+i,(T.ace_dry_humidity||'Humidity')+':');
|
||||
setText('d-ace-dry-current-temp-label-'+i,(T.ace_dry_current_temp||'Current Temp')+':');
|
||||
setText('d-ace-dry-target-label-'+i,(T.ace_dry_temp_line||'Drying Temperature')+':');
|
||||
setText('d-ace-dry-time-label-'+i,(T.ace_dry_time_line||'Drying Time')+':');
|
||||
setText('d-ace-dry-chart-label-'+i,T.ace_dry_chart||'History (Temp/Humidity)');
|
||||
var adTemp=document.getElementById('ace-dry-temp-'+i);if(adTemp)adTemp.setAttribute('placeholder',T.ace_dry_temp);
|
||||
var adDur=document.getElementById('ace-dry-duration-'+i);if(adDur)adDur.setAttribute('placeholder',T.ace_dry_duration);
|
||||
}
|
||||
setText('ace-dry-dialog-title',T.ace_dry_dialog_title||'Dryer Temp/Time Settings');
|
||||
setText('ace-dry-dialog-temp-label',T.ace_dry_dialog_temp||'Temperature (30-80°C)');
|
||||
setText('ace-dry-dialog-time-label',T.ace_dry_dialog_time||'Rem. Time (h:m:s)');
|
||||
setText('ace-dry-dialog-custom-name-label',T.ace_dry_dialog_custom_name||'Custom Name');
|
||||
setText('ace-dry-dialog-cancel',T.ace_dry_dialog_cancel||'Cancel');
|
||||
setText('ace-dry-dialog-confirm',T.ace_dry_dialog_confirm||'Confirm');
|
||||
setText('ace-dry-dialog-reset-default',T.ace_dry_dialog_reset_default||'Reset to Default');
|
||||
setText('ace-dry-dialog-save-preset',T.ace_dry_dialog_save_restart||'Save & Restart');
|
||||
aceDryDialogSyncCustomButtonNames();
|
||||
// conn-btn text (nur wenn nicht im Übergangszustand)
|
||||
updateConnBtn();
|
||||
// Slot-Edit-Dialog
|
||||
@@ -2770,21 +2930,35 @@ function ensureAceDryCards(){
|
||||
for(var i=0;i<4;i++){
|
||||
html+='<div class="card" id="d-ace-dry-card-'+i+'" style="display:none">'
|
||||
+'<div class="card-title"><span>♨</span> <span id="d-card-ace-dry-'+i+'">ACE '+(i+1)+' - Dryer</span></div>'
|
||||
+'<div style="font-size:12px;color:var(--txt2);margin-bottom:10px"><span id="d-ace-dry-status-'+i+'">Status: Off</span></div>'
|
||||
+'<div style="font-size:12px;color:var(--txt2);margin:-6px 0 10px 0"><span id="d-ace-dry-humidity-label-'+i+'">Humidity:</span> <span id="d-ace-dry-humidity-'+i+'">-</span></div>'
|
||||
+'<div style="font-size:12px;color:var(--txt2);margin:-6px 0 10px 0"><span id="d-ace-dry-current-temp-label-'+i+'">Current Temp:</span> <span id="d-ace-dry-current-temp-'+i+'">-</span></div>'
|
||||
+'<div style="display:flex;gap:8px;margin-bottom:10px">'
|
||||
+'<input type="number" class="temp-input" id="ace-dry-temp-'+i+'" min="30" max="80" step="1" value="55" style="flex:1" placeholder="Temp (°C)">'
|
||||
+'<input type="number" class="temp-input" id="ace-dry-duration-'+i+'" min="10" max="1440" step="10" value="240" style="flex:1" placeholder="Min">'
|
||||
+'<div style="display:flex;justify-content:space-between;gap:10px;font-size:14px;color:var(--txt2);margin-bottom:10px">'
|
||||
+'<span><span id="d-ace-dry-current-temp-label-'+i+'">Temperature:</span> <span id="d-ace-dry-current-temp-'+i+'" style="font-size:30px;font-weight:700;color:#ff8c2f;line-height:1">-</span></span>'
|
||||
+'<span><span id="d-ace-dry-humidity-label-'+i+'">Humidity:</span> <span id="d-ace-dry-humidity-'+i+'" style="font-size:30px;font-weight:700;color:#3aa8ff;line-height:1">-</span></span>'
|
||||
+'</div>'
|
||||
+'<div class="ctrl-btns">'
|
||||
+'<button class="btn btn-sm btn-accent" id="ace-dry-start-'+i+'" onclick="aceDryStart('+i+')">▶ Start</button>'
|
||||
+'<button class="btn btn-sm btn-cancel" id="ace-dry-stop-'+i+'" onclick="aceDryStop('+i+')">■ Stop</button>'
|
||||
+'<div style="height:1px;background:var(--border);margin-bottom:10px"></div>'
|
||||
+'<div style="display:flex;justify-content:space-between;gap:10px;font-size:14px;color:var(--txt2);margin-bottom:10px">'
|
||||
+'<span><span id="d-ace-dry-target-label-'+i+'">Drying Temperature:</span> <span id="d-ace-dry-target-'+i+'" style="font-size:30px;font-weight:700;color:#ff8c2f;line-height:1">-</span></span>'
|
||||
+'<span><span id="d-ace-dry-time-label-'+i+'">Drying Time:</span> <span id="d-ace-dry-time-'+i+'" style="font-size:30px;font-weight:700;color:#fff;line-height:1">-</span></span>'
|
||||
+'</div>'
|
||||
+'<div style="margin-bottom:10px">'
|
||||
+'<button onclick="openAceDryDialog('+i+')" title="Edit Dryer Temp/Time Settings" style="width:100%;padding:8px 10px;border-radius:8px;border:1px solid var(--accent);background:var(--accent);color:#000;cursor:pointer;font-size:13px;font-weight:600;line-height:1.2">Set Temp/Time</button>'
|
||||
+'</div>'
|
||||
+'<div style="height:1px;background:var(--border);margin-bottom:10px"></div>'
|
||||
+'<div class="toggle-row" style="margin-bottom:10px">'
|
||||
+'<span class="toggle-label" id="d-ace-auto-refill-label-'+i+'">Auto Refill</span>'
|
||||
+'<label class="toggle">'
|
||||
+'<input type="checkbox" id="ace-auto-refill-toggle-'+i+'" onchange="aceAutoRefillToggle('+i+')">'
|
||||
+'<span class="toggle-track"></span>'
|
||||
+'<span class="toggle-thumb"></span>'
|
||||
+'</label>'
|
||||
+'</div>'
|
||||
+'<div class="toggle-row" style="margin-bottom:8px">'
|
||||
+'<span class="toggle-label" id="d-ace-drying-enable-label-'+i+'">Enable Drying</span>'
|
||||
+'<label class="toggle">'
|
||||
+'<input type="checkbox" id="ace-dry-enable-toggle-'+i+'" onchange="aceDryToggle('+i+',this.checked)">'
|
||||
+'<span class="toggle-track"></span>'
|
||||
+'<span class="toggle-thumb"></span>'
|
||||
+'</label>'
|
||||
+'</div>'
|
||||
+'<div style="margin-top:10px">'
|
||||
+'<div style="font-size:10px;color:var(--txt2);margin-bottom:4px" id="d-ace-dry-chart-label-'+i+'">History (Temp/Humidity)</div>'
|
||||
+'<canvas id="d-ace-dry-chart-'+i+'" width="600" height="90" style="width:100%;height:90px;background:var(--raised);border-radius:8px"></canvas>'
|
||||
+'</div>'
|
||||
+'</div>';
|
||||
}
|
||||
grid.innerHTML=html;
|
||||
@@ -2914,12 +3088,20 @@ function escHtml(s){return s.replace(/&/g,'&').replace(/</g,'<').replace(
|
||||
|
||||
// ── Helpers ──
|
||||
function fmtTime(s){if(!s||s<0)return'–';var m=Math.floor(s/60),h=Math.floor(m/60);m%=60;return h>0?h+'h '+m+'m':m+'m'}
|
||||
function fmtHmsFromSec(total){
|
||||
total=Math.max(0,parseInt(total||0,10));
|
||||
var h=Math.floor(total/3600);
|
||||
var mm=Math.floor((total%3600)/60);
|
||||
var ss=total%60;
|
||||
return h+':'+String(mm).padStart(2,'0')+':'+String(ss).padStart(2,'0');
|
||||
}
|
||||
function post(url,body){return fetch(_apiUrl(url),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)})}
|
||||
function clamp(v,lo,hi){return Math.min(hi,Math.max(lo,v))}
|
||||
|
||||
// ── Apply state to DOM ──
|
||||
function applyState(){
|
||||
var s=S;
|
||||
_syncAceDryPresetsFromServer(s.ace_dry_presets);
|
||||
// connection error banner – nur wenn überhaupt ein Drucker konfiguriert ist
|
||||
var banner=document.getElementById('conn-error-banner');
|
||||
if(banner){if(s.connection_error&&_printers.length>0){banner.textContent='⚠ '+(T.lbl_conn_error||'Connection error:')+' '+s.connection_error;banner.style.display='block';}else{banner.style.display='none';}}
|
||||
@@ -3035,15 +3217,10 @@ function applyState(){
|
||||
card.style.display=show?'':'none';
|
||||
if(!show)continue;
|
||||
var ud=unitMap[i]||dry;
|
||||
var st=document.getElementById('d-ace-dry-status-'+i);
|
||||
if(st){
|
||||
if(Number(ud.status||0)){
|
||||
var rem=(Number(ud.remain_time)||0);
|
||||
st.textContent=(T.ace_dry_status_on||'Status: Active')+(rem>0?(' - '+(T.ace_dry_status_remaining||'Remaining')+': '+rem+' min'):'' );
|
||||
}else{
|
||||
st.textContent=T.ace_dry_status_off||'Status: Off';
|
||||
}
|
||||
}
|
||||
var refillToggle=document.getElementById('ace-auto-refill-toggle-'+i);
|
||||
if(refillToggle)refillToggle.checked=_aceAutoRefillGet(i);
|
||||
var dryToggle=document.getElementById('ace-dry-enable-toggle-'+i);
|
||||
if(dryToggle)dryToggle.checked=Number(ud.status||0)>0;
|
||||
var hh=document.getElementById('d-ace-dry-humidity-'+i);
|
||||
if(hh){
|
||||
var hv=(ud.humidity===null||ud.humidity===undefined||ud.humidity==='')?null:Number(ud.humidity);
|
||||
@@ -3054,10 +3231,15 @@ function applyState(){
|
||||
var ct=(ud.current_temp===null||ud.current_temp===undefined||ud.current_temp==='')?null:Number(ud.current_temp);
|
||||
ht.textContent=(ct===null||Number.isNaN(ct))?'-':(ct.toFixed(1)+'°C');
|
||||
}
|
||||
var dryTemp=document.getElementById('ace-dry-temp-'+i);
|
||||
if(dryTemp&&Number(ud.target_temp)>0&&String(dryTemp.value||'')==='55')dryTemp.value=Number(ud.target_temp);
|
||||
var dryDur=document.getElementById('ace-dry-duration-'+i);
|
||||
if(dryDur&&Number(ud.duration)>0&&String(dryDur.value||'')==='240')dryDur.value=Number(ud.duration);
|
||||
var prof=_aceDryProfileGet(i);
|
||||
var useSec=(Number(ud.status||0)>0&&Number(ud.remain_time)>0)
|
||||
?Number(ud.remain_time||0)*60
|
||||
:prof.duration_sec;
|
||||
var showTemp=(Number(ud.status||0)>0&&Number(ud.target_temp)>0)?Number(ud.target_temp):prof.temp;
|
||||
var dryTempEl=document.getElementById('d-ace-dry-target-'+i);
|
||||
if(dryTempEl)dryTempEl.textContent=showTemp+'°C';
|
||||
var dryTimeEl=document.getElementById('d-ace-dry-time-'+i);
|
||||
if(dryTimeEl)dryTimeEl.textContent=fmtHmsFromSec(useSec);
|
||||
}
|
||||
|
||||
// AMS
|
||||
@@ -3158,25 +3340,6 @@ function updateHistory(){
|
||||
if(tempHistory.n.length>60)tempHistory.n.shift();
|
||||
if(tempHistory.b.length>60)tempHistory.b.shift();
|
||||
drawChart('d-chart',tempHistory,[{data:tempHistory.n,color:'#00c8ff',max:300},{data:tempHistory.b,color:'#ff6b35',max:120}]);
|
||||
|
||||
var dry=S.ace_drying||{};
|
||||
var units=(dry.units||[]);
|
||||
var unitMap={};
|
||||
units.forEach(function(u){var id=Number(u.id);if(id>=0&&id<=3)unitMap[id]=u;});
|
||||
for(var i=0;i<4;i++){
|
||||
var u=unitMap[i];
|
||||
var t=u&&u.current_temp!=null?Number(u.current_temp):null;
|
||||
var h=u&&u.humidity!=null?Number(u.humidity):null;
|
||||
if((t===null||Number.isNaN(t))&&(h===null||Number.isNaN(h)))continue;
|
||||
if(t!==null&&!Number.isNaN(t))aceDryHistory[i].t.push(t);
|
||||
if(h!==null&&!Number.isNaN(h))aceDryHistory[i].h.push(h);
|
||||
if(aceDryHistory[i].t.length>60)aceDryHistory[i].t.shift();
|
||||
if(aceDryHistory[i].h.length>60)aceDryHistory[i].h.shift();
|
||||
drawChart('d-ace-dry-chart-'+i,aceDryHistory[i],[
|
||||
{data:aceDryHistory[i].t,color:'#ff8c2f',max:100},
|
||||
{data:aceDryHistory[i].h,color:'#3aa8ff',max:100}
|
||||
]);
|
||||
}
|
||||
}
|
||||
function drawChart(id,_,series){
|
||||
var canvas=document.getElementById(id);if(!canvas)return;
|
||||
@@ -3577,8 +3740,9 @@ function camStop(){
|
||||
|
||||
function aceDryStart(aceId){
|
||||
aceId=(typeof aceId==='number'&&aceId>=0)?aceId:0;
|
||||
var t=parseInt((document.getElementById('ace-dry-temp-'+aceId)||{}).value||55);
|
||||
var d=parseInt((document.getElementById('ace-dry-duration-'+aceId)||{}).value||240);
|
||||
var prof=_aceDryProfileGet(aceId);
|
||||
var t=parseInt(prof.temp,10);
|
||||
var d=_aceDryDurationMinFromSec(prof.duration_sec);
|
||||
t=Math.max(30,Math.min(80,t));
|
||||
d=Math.max(10,Math.min(1440,d));
|
||||
return post('/api/ace/dry',{action:'start',target_temp:t,duration:d,ace_id:aceId})
|
||||
@@ -3591,6 +3755,226 @@ function aceDryStart(aceId){
|
||||
.catch(function(e){clog('ACE-Fehler: '+e,'msg-err');});
|
||||
}
|
||||
|
||||
function aceAutoRefillToggle(aceId){
|
||||
aceId=(typeof aceId==='number'&&aceId>=0)?aceId:0;
|
||||
var on=!!((document.getElementById('ace-auto-refill-toggle-'+aceId)||{}).checked);
|
||||
_aceAutoRefillSet(aceId,on);
|
||||
clog('ACE '+(aceId+1)+' - '+(T.ace_dry_auto_refill||'Auto Refill')+': '+(on?'ON':'OFF')+' '+(T.ace_dry_ui_pending||'(UI only, backend next)'),'msg-info');
|
||||
}
|
||||
|
||||
function openAceDryDialog(aceId){
|
||||
aceId=(typeof aceId==='number'&&aceId>=0)?aceId:0;
|
||||
_aceDryDialogAceId=aceId;
|
||||
_syncAceDryPresetsFromServer(S.ace_dry_presets);
|
||||
_aceDryDialogPresetOriginals=JSON.parse(JSON.stringify(ACE_DRY_PRESETS));
|
||||
aceDryDialogSyncCustomButtonNames();
|
||||
var hasStored=Object.prototype.hasOwnProperty.call(aceDryProfiles,String(aceId));
|
||||
var prof=_aceDryProfileGet(aceId);
|
||||
if(hasStored&&prof.preset&&ACE_DRY_PRESETS[prof.preset]){
|
||||
aceDryDialogPreset(prof.preset);
|
||||
}else if(hasStored){
|
||||
var sec=prof.duration_sec;
|
||||
document.getElementById('ace-dry-dialog-temp').value=prof.temp;
|
||||
document.getElementById('ace-dry-dialog-h').value=Math.floor(sec/3600);
|
||||
document.getElementById('ace-dry-dialog-m').value=Math.floor((sec%3600)/60);
|
||||
document.getElementById('ace-dry-dialog-s').value=sec%60;
|
||||
aceDryDialogHighlightPreset('');
|
||||
}else{
|
||||
aceDryDialogPreset('pla');
|
||||
}
|
||||
aceDryDialogUpdateSaveButton();
|
||||
aceDryDialogUpdateResetButton();
|
||||
var sb=document.getElementById('ace-dry-dialog-save-preset');
|
||||
if(sb){sb.disabled=false;sb.textContent=T.ace_dry_dialog_save_restart||'Save & Restart';}
|
||||
document.getElementById('ace-dry-dialog').classList.add('open');
|
||||
}
|
||||
|
||||
function closeAceDryDialog(){
|
||||
_aceDryDialogAceId=-1;
|
||||
_aceDryDialogPresetOriginals={};
|
||||
var sb=document.getElementById('ace-dry-dialog-save-preset');
|
||||
if(sb)sb.style.display='none';
|
||||
var rb=document.getElementById('ace-dry-dialog-reset-default');
|
||||
if(rb)rb.style.display='none';
|
||||
document.getElementById('ace-dry-dialog').classList.remove('open');
|
||||
}
|
||||
|
||||
function aceDryDialogIsCustomPreset(key){
|
||||
return /^custom_[123]$/.test(String(key||''));
|
||||
}
|
||||
|
||||
function aceDryDialogSyncCustomButtonNames(){
|
||||
['custom_1','custom_2','custom_3'].forEach(function(k){
|
||||
var b=document.querySelector('.ace-dry-preset-btn[data-preset="'+k+'"]');
|
||||
if(b)b.textContent=(ACE_DRY_PRESETS[k]&&ACE_DRY_PRESETS[k].name)||('Custom '+k.slice(-1));
|
||||
});
|
||||
}
|
||||
|
||||
function aceDryDialogUpdateCustomNameUi(){
|
||||
var row=document.getElementById('ace-dry-dialog-custom-name-row');
|
||||
var input=document.getElementById('ace-dry-dialog-custom-name');
|
||||
if(!row||!input)return;
|
||||
if(!aceDryDialogIsCustomPreset(_aceDryDialogPresetKey)){
|
||||
row.style.display='none';
|
||||
return;
|
||||
}
|
||||
row.style.display='flex';
|
||||
input.value=(ACE_DRY_PRESETS[_aceDryDialogPresetKey]&&ACE_DRY_PRESETS[_aceDryDialogPresetKey].name)||'';
|
||||
}
|
||||
|
||||
function aceDryDialogCurrentValues(){
|
||||
var t=parseInt(document.getElementById('ace-dry-dialog-temp').value||45,10);
|
||||
var h=parseInt(document.getElementById('ace-dry-dialog-h').value||0,10);
|
||||
var m=parseInt(document.getElementById('ace-dry-dialog-m').value||0,10);
|
||||
var s=parseInt(document.getElementById('ace-dry-dialog-s').value||0,10);
|
||||
t=Math.max(30,Math.min(80,t));
|
||||
h=Math.max(0,Math.min(24,h));
|
||||
m=Math.max(0,Math.min(59,m));
|
||||
s=Math.max(0,Math.min(59,s));
|
||||
var totalSec=(h*3600)+(m*60)+s;
|
||||
totalSec=Math.max(10*60,Math.min(24*3600,totalSec));
|
||||
return {temp:t,duration_sec:totalSec};
|
||||
}
|
||||
|
||||
function aceDryDialogUpdateSaveButton(){
|
||||
var btn=document.getElementById('ace-dry-dialog-save-preset');
|
||||
if(!btn)return;
|
||||
var key=_aceDryDialogPresetKey||'';
|
||||
if(!key||!ACE_DRY_PRESETS[key]){btn.style.display='none';return;}
|
||||
var p=_aceDryDialogPresetOriginals[key]||ACE_DRY_PRESETS[key];
|
||||
var cur=aceDryDialogCurrentValues();
|
||||
var changed=(cur.temp!==Number(p.temp)||cur.duration_sec!==Number(p.duration_sec));
|
||||
if(aceDryDialogIsCustomPreset(key)){
|
||||
var nameInp=document.getElementById('ace-dry-dialog-custom-name');
|
||||
var n=((nameInp&&nameInp.value)||'').trim();
|
||||
var old=(p&&p.name?String(p.name):('Custom '+key.slice(-1))).trim();
|
||||
if((n||old)!==old)changed=true;
|
||||
}
|
||||
btn.style.display=changed?'':'none';
|
||||
}
|
||||
|
||||
function aceDryDialogUpdateResetButton(){
|
||||
var btn=document.getElementById('ace-dry-dialog-reset-default');
|
||||
if(!btn)return;
|
||||
var key=_aceDryDialogPresetKey||'';
|
||||
var d=ACE_DRY_PRESET_DEFAULTS[key];
|
||||
if(!key||!d){btn.style.display='none';return;}
|
||||
var cur=aceDryDialogCurrentValues();
|
||||
var changed=(cur.temp!==Number(d.temp)||cur.duration_sec!==Number(d.duration_sec));
|
||||
btn.style.display=changed?'':'none';
|
||||
}
|
||||
|
||||
function aceDryDialogInputsChanged(){
|
||||
if(aceDryDialogIsCustomPreset(_aceDryDialogPresetKey)){
|
||||
var b=document.querySelector('.ace-dry-preset-btn[data-preset="'+_aceDryDialogPresetKey+'"]');
|
||||
var i=document.getElementById('ace-dry-dialog-custom-name');
|
||||
if(b&&i){
|
||||
var t=(i.value||'').trim();
|
||||
b.textContent=t||((ACE_DRY_PRESETS[_aceDryDialogPresetKey]&&ACE_DRY_PRESETS[_aceDryDialogPresetKey].name)||('Custom '+_aceDryDialogPresetKey.slice(-1)));
|
||||
}
|
||||
}
|
||||
aceDryDialogUpdateSaveButton();
|
||||
aceDryDialogUpdateResetButton();
|
||||
}
|
||||
|
||||
function aceDryDialogHighlightPreset(presetKey){
|
||||
_aceDryDialogPresetKey=presetKey||'';
|
||||
document.querySelectorAll('.ace-dry-preset-btn').forEach(function(btn){
|
||||
var on=(btn.getAttribute('data-preset')===presetKey);
|
||||
btn.style.background=on?'var(--accent)':'var(--raised)';
|
||||
btn.style.color=on?'#fff':'var(--txt2)';
|
||||
btn.style.borderColor=on?'var(--accent)':'var(--border)';
|
||||
});
|
||||
aceDryDialogUpdateCustomNameUi();
|
||||
}
|
||||
|
||||
function aceDryDialogPreset(presetKey){
|
||||
var p=ACE_DRY_PRESETS[presetKey];
|
||||
if(!p)return;
|
||||
var sec=p.duration_sec;
|
||||
document.getElementById('ace-dry-dialog-temp').value=p.temp;
|
||||
document.getElementById('ace-dry-dialog-h').value=Math.floor(sec/3600);
|
||||
document.getElementById('ace-dry-dialog-m').value=Math.floor((sec%3600)/60);
|
||||
document.getElementById('ace-dry-dialog-s').value=sec%60;
|
||||
aceDryDialogHighlightPreset(presetKey);
|
||||
aceDryDialogSyncCustomButtonNames();
|
||||
aceDryDialogUpdateSaveButton();
|
||||
aceDryDialogUpdateResetButton();
|
||||
}
|
||||
|
||||
function resetAceDryPresetToDefault(){
|
||||
var key=_aceDryDialogPresetKey||'';
|
||||
var d=ACE_DRY_PRESET_DEFAULTS[key];
|
||||
if(!key||!d)return;
|
||||
var sec=Number(d.duration_sec)||0;
|
||||
document.getElementById('ace-dry-dialog-temp').value=Number(d.temp)||45;
|
||||
document.getElementById('ace-dry-dialog-h').value=Math.floor(sec/3600);
|
||||
document.getElementById('ace-dry-dialog-m').value=Math.floor((sec%3600)/60);
|
||||
document.getElementById('ace-dry-dialog-s').value=sec%60;
|
||||
aceDryDialogInputsChanged();
|
||||
}
|
||||
|
||||
function saveAceDryPresetAndRestart(){
|
||||
var key=_aceDryDialogPresetKey||'';
|
||||
var btn=document.getElementById('ace-dry-dialog-save-preset');
|
||||
if(!key||!ACE_DRY_PRESETS[key]||!btn)return;
|
||||
var cur=aceDryDialogCurrentValues();
|
||||
if(!ACE_DRY_PRESETS[key])ACE_DRY_PRESETS[key]={};
|
||||
ACE_DRY_PRESETS[key].temp=cur.temp;
|
||||
ACE_DRY_PRESETS[key].duration_sec=cur.duration_sec;
|
||||
if(aceDryDialogIsCustomPreset(key)){
|
||||
var nameInp=document.getElementById('ace-dry-dialog-custom-name');
|
||||
var nm=((nameInp&&nameInp.value)||'').trim();
|
||||
ACE_DRY_PRESETS[key].name=nm||('Custom '+key.slice(-1));
|
||||
}
|
||||
btn.disabled=true;
|
||||
btn.textContent='…';
|
||||
fetch(_apiUrl('/api/settings')).then(function(r){return r.json();}).then(function(d){
|
||||
d.ace_dry_presets={
|
||||
pla:{temp:ACE_DRY_PRESETS.pla.temp,duration_sec:ACE_DRY_PRESETS.pla.duration_sec},
|
||||
pla_plus:{temp:ACE_DRY_PRESETS.pla_plus.temp,duration_sec:ACE_DRY_PRESETS.pla_plus.duration_sec},
|
||||
petg:{temp:ACE_DRY_PRESETS.petg.temp,duration_sec:ACE_DRY_PRESETS.petg.duration_sec},
|
||||
tpu:{temp:ACE_DRY_PRESETS.tpu.temp,duration_sec:ACE_DRY_PRESETS.tpu.duration_sec},
|
||||
abs_asa:{temp:ACE_DRY_PRESETS.abs_asa.temp,duration_sec:ACE_DRY_PRESETS.abs_asa.duration_sec},
|
||||
pa_pc:{temp:ACE_DRY_PRESETS.pa_pc.temp,duration_sec:ACE_DRY_PRESETS.pa_pc.duration_sec},
|
||||
custom_1:{name:ACE_DRY_PRESETS.custom_1.name,temp:ACE_DRY_PRESETS.custom_1.temp,duration_sec:ACE_DRY_PRESETS.custom_1.duration_sec},
|
||||
custom_2:{name:ACE_DRY_PRESETS.custom_2.name,temp:ACE_DRY_PRESETS.custom_2.temp,duration_sec:ACE_DRY_PRESETS.custom_2.duration_sec},
|
||||
custom_3:{name:ACE_DRY_PRESETS.custom_3.name,temp:ACE_DRY_PRESETS.custom_3.temp,duration_sec:ACE_DRY_PRESETS.custom_3.duration_sec}
|
||||
};
|
||||
return post('/api/settings',d);
|
||||
}).then(function(){
|
||||
clog('ACE preset '+key+' '+(T.settings_save||'Save & Restart'),'msg-ok');
|
||||
closeAceDryDialog();
|
||||
}).catch(function(e){
|
||||
btn.disabled=false;
|
||||
btn.textContent=T.ace_dry_dialog_save_restart||'Save & Restart';
|
||||
clog('ACE-Preset Fehler: '+e,'msg-err');
|
||||
});
|
||||
}
|
||||
|
||||
function confirmAceDryDialog(){
|
||||
if(_aceDryDialogAceId<0)return;
|
||||
var t=parseInt(document.getElementById('ace-dry-dialog-temp').value||45,10);
|
||||
var h=parseInt(document.getElementById('ace-dry-dialog-h').value||0,10);
|
||||
var m=parseInt(document.getElementById('ace-dry-dialog-m').value||0,10);
|
||||
var s=parseInt(document.getElementById('ace-dry-dialog-s').value||0,10);
|
||||
t=Math.max(30,Math.min(80,t));
|
||||
h=Math.max(0,Math.min(24,h));
|
||||
m=Math.max(0,Math.min(59,m));
|
||||
s=Math.max(0,Math.min(59,s));
|
||||
var totalSec=(h*3600)+(m*60)+s;
|
||||
totalSec=Math.max(10*60,Math.min(24*3600,totalSec));
|
||||
var preset=_aceDryDialogPresetKey||'';
|
||||
_aceDryProfileSet(_aceDryDialogAceId,t,totalSec,preset);
|
||||
closeAceDryDialog();
|
||||
applyState();
|
||||
}
|
||||
|
||||
function aceDryToggle(aceId,on){
|
||||
if(on)return aceDryStart(aceId);
|
||||
return aceDryStop(aceId);
|
||||
}
|
||||
|
||||
function toggleCam(){if(camOn)camStop();else camStart()}
|
||||
|
||||
function aceDryStop(aceId){
|
||||
@@ -4195,6 +4579,60 @@ function loadPrinterTab(){
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ACE Dryer Temp/Time Settings Dialog -->
|
||||
<div class="modal-overlay" id="ace-dry-dialog" onclick="if(event.target===this)closeAceDryDialog()">
|
||||
<div class="modal-box" style="max-width:560px;width:100%">
|
||||
<div class="modal-header" style="margin-bottom:10px">
|
||||
<span class="modal-title" id="ace-dry-dialog-title">Dryer Temp/Time Settings</span>
|
||||
<button onclick="closeAceDryDialog()" style="background:none;border:none;font-size:18px;cursor:pointer;color:var(--txt2)">✕</button>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px">
|
||||
<label id="ace-dry-dialog-temp-label" style="min-width:190px;font-size:12px;color:var(--txt)">Temperature (30-80°C)</label>
|
||||
<input id="ace-dry-dialog-temp" type="number" min="30" max="80" step="1"
|
||||
oninput="aceDryDialogInputsChanged()"
|
||||
style="width:130px;padding:8px;background:var(--raised);border:1px solid var(--border);border-radius:6px;color:var(--txt);text-align:center" value="45">
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:12px;margin-bottom:16px">
|
||||
<label id="ace-dry-dialog-time-label" style="min-width:190px;font-size:12px;color:var(--txt)">Rem. Time (h:m:s)</label>
|
||||
<div style="display:flex;align-items:center;gap:8px">
|
||||
<input id="ace-dry-dialog-h" type="number" min="0" max="24" step="1" value="4"
|
||||
oninput="aceDryDialogInputsChanged()"
|
||||
style="width:70px;padding:8px;background:var(--raised);border:1px solid var(--border);border-radius:6px;color:var(--txt);text-align:center">
|
||||
<span style="color:var(--txt2)">:</span>
|
||||
<input id="ace-dry-dialog-m" type="number" min="0" max="59" step="1" value="0"
|
||||
oninput="aceDryDialogInputsChanged()"
|
||||
style="width:70px;padding:8px;background:var(--raised);border:1px solid var(--border);border-radius:6px;color:var(--txt);text-align:center">
|
||||
<span style="color:var(--txt2)">:</span>
|
||||
<input id="ace-dry-dialog-s" type="number" min="0" max="59" step="1" value="0"
|
||||
oninput="aceDryDialogInputsChanged()"
|
||||
style="width:70px;padding:8px;background:var(--raised);border:1px solid var(--border);border-radius:6px;color:var(--txt);text-align:center">
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:8px">
|
||||
<button class="ace-dry-preset-btn" data-preset="pla" onclick="aceDryDialogPreset('pla')" style="padding:8px;border-radius:8px;border:1px solid var(--border);background:var(--raised);color:var(--txt2);cursor:pointer">PLA</button>
|
||||
<button class="ace-dry-preset-btn" data-preset="pla_plus" onclick="aceDryDialogPreset('pla_plus')" style="padding:8px;border-radius:8px;border:1px solid var(--border);background:var(--raised);color:var(--txt2);cursor:pointer">PLA+</button>
|
||||
<button class="ace-dry-preset-btn" data-preset="petg" onclick="aceDryDialogPreset('petg')" style="padding:8px;border-radius:8px;border:1px solid var(--border);background:var(--raised);color:var(--txt2);cursor:pointer">PETG</button>
|
||||
<button class="ace-dry-preset-btn" data-preset="tpu" onclick="aceDryDialogPreset('tpu')" style="padding:8px;border-radius:8px;border:1px solid var(--border);background:var(--raised);color:var(--txt2);cursor:pointer">TPU</button>
|
||||
<button class="ace-dry-preset-btn" data-preset="abs_asa" onclick="aceDryDialogPreset('abs_asa')" style="padding:8px;border-radius:8px;border:1px solid var(--border);background:var(--raised);color:var(--txt2);cursor:pointer">ABS / ASA</button>
|
||||
<button class="ace-dry-preset-btn" data-preset="pa_pc" onclick="aceDryDialogPreset('pa_pc')" style="padding:8px;border-radius:8px;border:1px solid var(--border);background:var(--raised);color:var(--txt2);cursor:pointer">PA / PC</button>
|
||||
<button class="ace-dry-preset-btn" data-preset="custom_1" onclick="aceDryDialogPreset('custom_1')" style="padding:8px;border-radius:8px;border:1px solid var(--border);background:var(--raised);color:var(--txt2);cursor:pointer">Custom 1</button>
|
||||
<button class="ace-dry-preset-btn" data-preset="custom_2" onclick="aceDryDialogPreset('custom_2')" style="padding:8px;border-radius:8px;border:1px solid var(--border);background:var(--raised);color:var(--txt2);cursor:pointer">Custom 2</button>
|
||||
<button class="ace-dry-preset-btn" data-preset="custom_3" onclick="aceDryDialogPreset('custom_3')" style="padding:8px;border-radius:8px;border:1px solid var(--border);background:var(--raised);color:var(--txt2);cursor:pointer">Custom 3</button>
|
||||
</div>
|
||||
<div id="ace-dry-dialog-custom-name-row" style="display:none;align-items:center;gap:12px;margin-bottom:14px">
|
||||
<label id="ace-dry-dialog-custom-name-label" style="min-width:190px;font-size:12px;color:var(--txt)">Custom Name</label>
|
||||
<input id="ace-dry-dialog-custom-name" type="text" maxlength="32" oninput="aceDryDialogInputsChanged()"
|
||||
style="width:220px;padding:8px;background:var(--raised);border:1px solid var(--border);border-radius:6px;color:var(--txt)">
|
||||
</div>
|
||||
<div style="display:flex;justify-content:flex-end;gap:8px">
|
||||
<button id="ace-dry-dialog-reset-default" onclick="resetAceDryPresetToDefault()" style="display:none;padding:8px 14px;background:var(--raised);border:1px solid var(--border);border-radius:8px;color:var(--txt);cursor:pointer">Reset to Default</button>
|
||||
<button id="ace-dry-dialog-save-preset" onclick="saveAceDryPresetAndRestart()" style="display:none;padding:8px 14px;background:var(--warn);border:1px solid transparent;border-radius:8px;color:#fff;cursor:pointer">Save & Restart</button>
|
||||
<button id="ace-dry-dialog-cancel" onclick="closeAceDryDialog()" style="padding:8px 14px;background:var(--raised);border:1px solid var(--border);border-radius:8px;color:var(--txt);cursor:pointer">Cancel</button>
|
||||
<button id="ace-dry-dialog-confirm" onclick="confirmAceDryDialog()" style="padding:8px 16px;background:var(--accent);color:#fff;border:none;border-radius:8px;cursor:pointer;font-weight:600">Confirm</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer style="text-align:center;padding:12px;font-size:11px;color:var(--txt2);border-top:1px solid var(--border);margin-top:auto">
|
||||
© ViewIT 2026
|
||||
</footer>
|
||||
@@ -4649,6 +5087,7 @@ function loadPrinterTab(){
|
||||
"filament_mode": s.get("filament_mode", self._filament_mode),
|
||||
"ace_drying": s.get("ace_drying", {"status": 0, "target_temp": 0, "duration": 0, "remain_time": 0, "humidity": None, "current_temp": None}),
|
||||
"ace_units": list(self._ace_box_ids),
|
||||
"ace_dry_presets": self._ace_dry_presets,
|
||||
"thumbnail": self._thumbnail_b64,
|
||||
"connection_error": s["connection_error"],
|
||||
"file_ready": s["file_ready"],
|
||||
@@ -4731,6 +5170,7 @@ function loadPrinterTab(){
|
||||
"device_id": self._args.device_id,
|
||||
"default_ams_slot": getattr(self._args, "default_ams_slot", "auto"),
|
||||
"auto_leveling": getattr(self._args, "auto_leveling", 1),
|
||||
"ace_dry_presets": self._ace_dry_presets,
|
||||
})
|
||||
|
||||
async def handle_api_settings_post(self, request):
|
||||
@@ -4745,7 +5185,7 @@ function loadPrinterTab(){
|
||||
cfg.read(config_path, encoding="utf-8")
|
||||
|
||||
# Sections sicherstellen
|
||||
for section in ("connection", "print", "bridge"):
|
||||
for section in ("connection", "print", "bridge", "ace_dry_presets"):
|
||||
if not cfg.has_section(section):
|
||||
cfg.add_section(section)
|
||||
|
||||
@@ -4766,6 +5206,15 @@ function loadPrinterTab(){
|
||||
elif cfg.has_option("bridge", "printer_name"):
|
||||
cfg.remove_option("bridge", "printer_name")
|
||||
|
||||
incoming_presets = data.get("ace_dry_presets") if isinstance(data, dict) else None
|
||||
presets = self._sanitize_ace_dry_presets(incoming_presets if isinstance(incoming_presets, dict) else self._ace_dry_presets)
|
||||
for key, val in presets.items():
|
||||
cfg.set("ace_dry_presets", f"{key}_temp", str(val["temp"]))
|
||||
cfg.set("ace_dry_presets", f"{key}_duration_sec", str(val["duration_sec"]))
|
||||
if key.startswith("custom_"):
|
||||
cfg.set("ace_dry_presets", f"{key}_name", str(val.get("name", key.replace("_", " ").title())))
|
||||
self._ace_dry_presets = presets
|
||||
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
f.write("# KX-Bridge Konfigurationsdatei\n\n")
|
||||
cfg.write(f)
|
||||
|
||||
Reference in New Issue
Block a user