/***********************
- BambuLab AMS 0 + TEMP + PRINT + MEROSS
- Komplett-Skript ohne AMS 1
- Enthält:
-
-
-
- Werte: Materialtyp;Filament-Untertyp
-
-
-
-
- Meross Adaptersteuerung
***********************/
const printerId = '01S00C321500283';
const baseBambu = bambulab.0.${printerId};
const baseAMS = '0_userdata.0.Bambulab.AMS';
const baseTemp = '0_userdata.0.Bambulab.Temp';
const basePrint = '0_userdata.0.Bambulab.Print';
const merossId = 'meross.0.2303167546755151200348e1e9beebff.0';
const adapterId = 'system.adapter.bambulab.0';
const trayNowId = ${baseBambu}.ams.tray_now;
const trayCount = 4;
const defaultColor = '#D3D3D3';
const refreshMs = 10000;
const tempSources = {
bedActual: ${baseBambu}.bed_temper,
bedTarget: ${baseBambu}.bed_target_temper,
nozzleActual: ${baseBambu}.nozzle_temper,
nozzleTarget: ${baseBambu}.nozzle_target_temper
};
const printSources = {
percent: ${baseBambu}.mc_percent,
file: ${baseBambu}.gcode_file,
state: ${baseBambu}.gcode_state,
remaining: ${baseBambu}.mc_remaining_time
};
// ------------------------------------------------------------
// Hilfsfunktionen
// ------------------------------------------------------------
function create(id, def, type = null, role = 'state') {
if (!existsState(id)) {
createState(id, {
name: id.split('.').pop(),
type: type || typeof def,
role,
read: true,
write: true,
def
}, () => setState(id, def, true));
}
}
function get(id) {
const state = getState(id);
return state ? state.val : undefined;
}
function set(id, val) {
const state = getState(id);
if (!state || state.val !== val) {
setState(id, val, true);
}
}
function normalizeColor(raw) {
let c = String(raw ?? '').replace('#', '').trim();
c = c.replace(/[^0-9A-Fa-f]/g, '');
if (c.length >= 6) {
return `#${c.substring(0, 6)}`.toUpperCase();
}
return defaultColor;
}
function getTextColor(color) {
return String(color).toLowerCase() === '#000000' ? '#FFFFFF' : '#000000';
}
function formatTemp(v) {
const n = Number(v);
return isNaN(n) ? '-- °C' : ${Math.floor(n)} °C;
}
function parseRemaining(value) {
if (value === null || value === undefined || value === '') return 0;
const s = String(value).trim();
const hm = s.match(/^(\d+):(\d+)$/);
if (hm) return Number(hm[1]) * 60 + Number(hm[2]);
if (!isNaN(Number(s))) return Number(s);
return 0;
}
function formatRemaining(value) {
const minutes = parseRemaining(value);
if (minutes <= 0) return '--';
const h = Math.floor(minutes / 60);
const m = Math.floor(minutes % 60);
return h > 0 ? `${h}h ${m}m` : `${m} min`;
}
function finishTime(value) {
const minutes = parseRemaining(value);
if (minutes <= 0) return '--';
const end = new Date(Date.now() + minutes * 60000);
return end.toLocaleTimeString('de-DE', {
hour: '2-digit',
minute: '2-digit'
});
}
// ------------------------------------------------------------
// Datenpunkte erstellen
// ------------------------------------------------------------
function createStates() {
// AMS allgemein
create(`${baseAMS}.Aktiver_Tray`, 255, 'number', 'value');
create(`${baseAMS}.Farbe_aktiv`, defaultColor, 'string', 'value.color');
create(`${baseAMS}.AMS_Farbe_aktiv`, defaultColor, 'string', 'value.color');
// Aktive Tray-Boolean-Datenpunkte nur AMS 0
create(`${baseAMS}.AMS0_Tray0`, false, 'boolean', 'indicator');
create(`${baseAMS}.AMS0_Tray1`, false, 'boolean', 'indicator');
create(`${baseAMS}.AMS0_Tray2`, false, 'boolean', 'indicator');
create(`${baseAMS}.AMS0_Tray3`, false, 'boolean', 'indicator');
// AMS 0 Tray 0 bis 3
for (let tray = 0; tray < trayCount; tray++) {
// Struktur mit .0.
create(`${baseAMS}.0.Farbe_Tray_${tray}`, defaultColor, 'string', 'value.color');
create(`${baseAMS}.0.Schriftfarbe_Tray_${tray}`, '#000000', 'string', 'value.color');
create(`${baseAMS}.0.Werte_Tray_${tray}`, '', 'string', 'text');
// Einfache Struktur
create(`${baseAMS}.Farbe_Tray_${tray}`, defaultColor, 'string', 'value.color');
create(`${baseAMS}.Schriftfarbe_Tray_${tray}`, '#000000', 'string', 'value.color');
create(`${baseAMS}.Werte_Tray_${tray}`, '', 'string', 'text');
}
// Temperaturen
create(`${baseTemp}.Ist_Druckbett`, '-- °C', 'string', 'text');
create(`${baseTemp}.Soll_Druckbett`, '-- °C', 'string', 'text');
create(`${baseTemp}.Ist_Duese`, '-- °C', 'string', 'text');
create(`${baseTemp}.Soll_Duese`, '-- °C', 'string', 'text');
// Print
create(`${basePrint}.Fortschritt`, 0, 'number', 'value');
create(`${basePrint}.Datei`, '', 'string', 'text');
create(`${basePrint}.Status`, 'BEREIT', 'string', 'text');
create(`${basePrint}.Restzeit`, '--', 'string', 'text');
create(`${basePrint}.Fertig_um`, '--', 'string', 'text');
}
// ------------------------------------------------------------
// AMS Farben + Schriftfarben + Werte
// ------------------------------------------------------------
function updateAMS() {
for (let tray = 0; tray < trayCount; tray++) {
updateAmsTrayColor(tray);
updateAmsTrayWerte(tray);
}
updateActiveAmsTray();
}
function updateAmsTrayColor(tray) {
const sourceColorId = `${baseBambu}.ams.ams.0.tray.${tray}.tray_color`;
const color = normalizeColor(get(sourceColorId));
const textColor = getTextColor(color);
// Struktur mit .0.
set(`${baseAMS}.0.Farbe_Tray_${tray}`, color);
set(`${baseAMS}.0.Schriftfarbe_Tray_${tray}`, textColor);
// Einfache Struktur
set(`${baseAMS}.Farbe_Tray_${tray}`, color);
set(`${baseAMS}.Schriftfarbe_Tray_${tray}`, textColor);
}
function updateAmsTrayWerte(tray) {
const trayType = get(`${baseBambu}.ams.ams.0.tray.${tray}.tray_type`) ?? '';
const subBrands = get(`${baseBambu}.ams.ams.0.tray.${tray}.tray_sub_brands`) ?? '';
// Nur Materialtyp und Filament-Untertyp
const wert = `${trayType};${subBrands}`;
// Struktur mit .0.
set(`${baseAMS}.0.Werte_Tray_${tray}`, wert);
// Einfache Struktur
set(`${baseAMS}.Werte_Tray_${tray}`, wert);
}
// ------------------------------------------------------------
// Aktives AMS / aktiver Tray
// ------------------------------------------------------------
function updateActiveAmsTray() {
const trayNow = Number(get(trayNowId));
const trayStateNames = [
'AMS0_Tray0',
'AMS0_Tray1',
'AMS0_Tray2',
'AMS0_Tray3'
];
// Erst alle Trays auf false setzen
for (const name of trayStateNames) {
set(`${baseAMS}.${name}`, false);
}
// Kein aktiver Tray oder ungültiger Wert
if (isNaN(trayNow) || trayNow === 255 || trayNow < 0 || trayNow > 3) {
set(`${baseAMS}.Aktiver_Tray`, 255);
set(`${baseAMS}.Farbe_aktiv`, defaultColor);
set(`${baseAMS}.AMS_Farbe_aktiv`, defaultColor);
return;
}
// Aktiven Tray setzen
set(`${baseAMS}.Aktiver_Tray`, trayNow);
set(`${baseAMS}.${trayStateNames[trayNow]}`, true);
const activeColor = get(`${baseAMS}.0.Farbe_Tray_${trayNow}`) || defaultColor;
set(`${baseAMS}.Farbe_aktiv`, activeColor);
set(`${baseAMS}.AMS_Farbe_aktiv`, activeColor);
}
// ------------------------------------------------------------
// Temperaturen
// ------------------------------------------------------------
function updateTemp() {
set(${baseTemp}.Ist_Druckbett, formatTemp(get(tempSources.bedActual)));
set(${baseTemp}.Soll_Druckbett, formatTemp(get(tempSources.bedTarget)));
set(${baseTemp}.Ist_Duese, formatTemp(get(tempSources.nozzleActual)));
set(${baseTemp}.Soll_Duese, formatTemp(get(tempSources.nozzleTarget)));
}
// ------------------------------------------------------------
// Druckstatus
// ------------------------------------------------------------
function updatePrint() {
const percent = Number(get(printSources.percent)) || 0;
const file = String(get(printSources.file) ?? '');
const rawState = String(get(printSources.state) ?? '').toUpperCase();
const remainingRaw = get(printSources.remaining);
const remaining = parseRemaining(remainingRaw);
let status = rawState || 'BEREIT';
if (remaining > 0 && percent > 0 && percent < 100) {
status = 'DRUCKT';
} else if (percent >= 100) {
status = 'FERTIG';
} else if (
rawState.includes('FAIL') ||
rawState.includes('CANCEL') ||
rawState.includes('ABORT')
) {
status = 'ABGEBROCHEN';
} else if (remaining <= 0 && percent <= 0) {
status = 'BEREIT';
}
set(`${basePrint}.Status`, status);
if (status === 'DRUCKT') {
set(`${basePrint}.Fortschritt`, percent);
set(`${basePrint}.Datei`, file);
set(`${basePrint}.Restzeit`, formatRemaining(remainingRaw));
set(`${basePrint}.Fertig_um`, finishTime(remainingRaw));
} else {
set(`${basePrint}.Fortschritt`, 0);
set(`${basePrint}.Datei`, '');
set(`${basePrint}.Restzeit`, '--');
set(`${basePrint}.Fertig_um`, '--');
}
}
// ------------------------------------------------------------
// Alles aktualisieren
// ------------------------------------------------------------
function updateAll() {
updateAMS();
updateTemp();
updatePrint();
}
// ------------------------------------------------------------
// Trigger
// ------------------------------------------------------------
// Meross Steckdose / BambuLab Adapter
on({ id: merossId, change: 'any' }, obj => {
const isOn = obj.state.val === true || obj.state.val === 1 || obj.state.val === 'true';
setState(`${adapterId}.alive`, isOn);
if (isOn) {
setTimeout(updateAll, 8000);
}
});
// AMS Änderungen nur AMS 0
on({
id: [
trayNowId,
new RegExp(^bambulab\\.0\\.${printerId}\\.ams\\.ams\\.0\\.tray\\.[0-3]\\.),
${baseBambu}.sequence_id
],
change: 'any'
}, updateAMS);
// Temperatur Änderungen
on({
id: Object.values(tempSources),
change: 'any'
}, updateTemp);
// Druckstatus Änderungen
on({
id: Object.values(printSources),
change: 'any'
}, updatePrint);
// ------------------------------------------------------------
// Start
// ------------------------------------------------------------
createStates();
setTimeout(updateAll, 3000);
setInterval(updateAll, refreshMs);