const SkriptVersion = "0.3.4-fixed"; // vom 14.11.2025 - LOKALE STEUERUNG MIT TOKENS - MIoT STRUKTUR - REORGANISIERT - Power read-only
const mihome = require('node-mihome');
// ============================================
// 🔑 KONFIGURATION - TOKENS & LOKALE IPs
// ============================================
// Token-Auslesen: https://github.com/PiotrMachowski/Xiaomi-cloud-tokens-extractor
const devices = [
{
name: "Mi Air Purifier 3/3H",
model: "zhimi.airpurifier.mb3",
did: "xxxxxxxxx",
localip: "10.1.1.92",
token: "xxxxxxxxxxxxxxxxxxxxxxxxx",
enabled: true
}
];
const praefix0 = "javascript.0.MiHomeDE";
const refresh = 10000;
const logging = true;
// ============================================
// DEVICE DEFINITIONS - MIoT STRUKTUR - REORGANISIERT
// ============================================
const DefineDevice = [];
// Luftreiniger MB3 (3H)
DefineDevice[0] = {
model: "zhimi.airpurifier.mb3",
description: "Purifier 3H",
setter: {
"air-purifier.mode": async function (obj, val) {
try {
await device[obj].setMode(val);
log("✅ Mode set to: " + val);
} catch(e) {
log("❌ setMode error: " + e.message, "error");
}
},
"air-purifier.fan-level": async function (obj, val) {
try {
await device[obj].setFanLevel(val);
log("✅ Fan level set to: " + val);
} catch(e) {
log("❌ setFanLevel error: " + e.message, "error");
}
},
"motor-speed.favorite-fan-level": async function (obj, val) {
try {
await device[obj].setFavLevel(val);
log("✅ Favorite level set to: " + val);
} catch(e) {
log("❌ setFavLevel error: " + e.message, "error");
}
},
"indicator-light.on": async function (obj, val) {
try {
await device[obj].setLcdBrightness(val ? 2 : 0);
log("✅ Indicator light set to: " + (val ? 'on' : 'off'));
} catch(e) {
log("❌ setLcdBrightness error: " + e.message, "error");
}
},
"physical-controls-locked.physical-controls-locked": async function (obj, val) {
try {
await device[obj].setChildLock(val);
log("✅ Child lock set to: " + val);
} catch(e) {
log("❌ setChildLock error: " + e.message, "error");
}
}
},
control: [
{ name: "air-purifier.on", type: "boolean", role: "switch", read: true, write: false },
{ name: "air-purifier.mode", type: "number", read: true, write: true, min: 0, max: 3, states: { 0: "auto", 1: "sleep", 2: "favorite", 3: "fanset" } },
{ name: "air-purifier.fan-level", type: "number", read: true, write: true, min: 1, max: 3 },
{ name: "motor-speed.favorite-fan-level", type: "number", read: true, write: true, min: 0, max: 14 },
{ name: "indicator-light.on", type: "boolean", role: "switch", read: true, write: true },
{ name: "physical-controls-locked.physical-controls-locked", type: "boolean", role: "switch", read: true, write: true }
],
sensor: [
{ name: "alarm.alarm", type: "boolean", role: "switch", read: true, write: false },
{ name: "indicator-light.brightness", type: "number", read: true, write: false, min: 0, max: 2 },
{ name: "environment.temperature", type: "number", role: "value.temperature", read: true, write: false, unit: "°C" },
{ name: "motor-speed.motor-speed", type: "number", read: true, write: false, unit: "rpm" },
{ name: "use-time.use-time", type: "number", read: true, write: false, unit: "h" },
{ name: "environment.relative-humidity", type: "number", role: "value.humidity", read: true, write: false, unit: "%" },
{ name: "environment.pm2_5-density", type: "number", read: true, write: false, unit: "μg/m³" },
{ name: "filter.filter-life-level", type: "number", read: true, write: false, unit: "%" },
{ name: "filter.filter-used-time", type: "number", read: true, write: false, unit: "h" }
]
};
// ============================================
// AB HIER NICHT ÄNDERN
// ============================================
let device = [];
const States = [];
let DpCount = 0;
let GenericDpRefreshIntervalObj;
const DefineDeviceInfo = [
{ id: "localip", name: "IP Address", type: "string", read: true, write: false },
{ id: "token", name: "Token", type: "string", read: true, write: false },
{ id: "did", name: "Device ID", type: "string", read: true, write: false },
{ id: "model", name: "Model", type: "string", read: true, write: false },
{ id: "isOnline", name: "Is Online", type: "boolean", read: true, write: false }
];
log("Starting AllMyMi V." + SkriptVersion);
log("Devices configured: " + devices.length);
function CorrectChannelId(cid) {
let temp = cid;
if (typeof temp !== "string") {
temp = JSON.stringify(cid);
}
temp = cid.replace(/\./g, "-");
temp = cid.replace(/:/g, ".");
return temp;
}
function PrepareGenericDps(did) {
if (logging) log("Preparing generic DPs for: " + did);
for (let y in DefineDeviceInfo) {
States[DpCount] = {
id: praefix0 + "." + did + ".info." + DefineDeviceInfo[y].id,
initial: "",
forceCreation: false,
common: {
read: DefineDeviceInfo[y].read,
write: DefineDeviceInfo[y].write,
name: DefineDeviceInfo[y].name,
type: DefineDeviceInfo[y].type,
role: "value",
unit: DefineDeviceInfo[y].unit || ""
}
};
DpCount++;
}
}
function PrepareDeviceDps(did, model) {
if (logging) log("Preparing device DPs for model: " + model);
for (let x in DefineDevice) {
if (DefineDevice[x].model === model) {
// Control Datenpunkte
if (DefineDevice[x].control) {
for (let y in DefineDevice[x].control) {
let channelId = CorrectChannelId(DefineDevice[x].control[y].name);
States[DpCount] = {
id: praefix0 + "." + did + ".control." + channelId,
initial: "",
forceCreation: false,
common: DefineDevice[x].control[y]
};
DpCount++;
}
}
// Sensor Datenpunkte
if (DefineDevice[x].sensor) {
for (let y in DefineDevice[x].sensor) {
let channelId = CorrectChannelId(DefineDevice[x].sensor[y].name);
States[DpCount] = {
id: praefix0 + "." + did + ".sensor." + channelId,
initial: "",
forceCreation: false,
common: DefineDevice[x].sensor[y]
};
DpCount++;
}
}
}
}
}
function CreateStates() {
if (logging) log("Creating States...");
let numStates = States.length;
log("Total states to create: " + numStates);
if (numStates === 0) {
log("❌ No states to create! Check device configuration.", "error");
return;
}
States.forEach(function (state) {
createState(state.id, state.initial, state.forceCreation, state.common, function () {
numStates--;
if (numStates === 0) {
log("✅ " + States.length + " States created!");
setObject(praefix0, {
type: 'channel',
common: { name: "MiHome Local Control" },
native: {}
});
for (let i = 0; i < devices.length; i++) {
if (devices[i].enabled) {
setObject(praefix0 + "." + devices[i].did, {
type: 'device',
common: { name: devices[i].name },
native: {}
});
setObject(praefix0 + "." + devices[i].did + ".control", {
type: 'channel',
common: { name: "Control" },
native: {}
});
setObject(praefix0 + "." + devices[i].did + ".sensor", {
type: 'channel',
common: { name: "Sensor" },
native: {}
});
}
}
Init();
}
});
});
}
async function Init() {
log("🚀 Initializing local device connections...");
mihome.miioProtocol.init();
let deviceCount = 0;
for (let i = 0; i < devices.length; i++) {
if (!devices[i].enabled) {
if (logging) log("Device " + devices[i].name + " is disabled, skipping");
continue;
}
try {
log("Connecting to: " + devices[i].name + " (" + devices[i].localip + ")");
device[deviceCount] = mihome.device({
id: devices[i].did,
model: devices[i].model,
address: devices[i].localip,
token: devices[i].token,
refresh: refresh
});
device[deviceCount].model = devices[i].model;
device[deviceCount].deviceConfig = devices[i];
device[deviceCount].deviceIndex = i;
device[deviceCount].isOnline = false;
device[deviceCount].data = {};
device[deviceCount].on('properties', (data) => {
if (typeof data !== "undefined" && Object.keys(data).length > 0) {
RefreshDps(deviceCount, data);
}
});
device[deviceCount].on('connected', () => {
log("✅ Device connected: " + devices[i].name);
device[deviceCount].isOnline = true;
setState(praefix0 + "." + devices[i].did + ".info.isOnline", true, true);
});
device[deviceCount].on('disconnected', () => {
log("⚠️ Device disconnected: " + devices[i].name);
device[deviceCount].isOnline = false;
setState(praefix0 + "." + devices[i].did + ".info.isOnline", false, true);
});
await device[deviceCount].init();
log("✅ Device initialized: " + devices[i].name);
// Set device info
setState(praefix0 + "." + devices[i].did + ".info.did", devices[i].did, true);
setState(praefix0 + "." + devices[i].did + ".info.model", devices[i].model, true);
setState(praefix0 + "." + devices[i].did + ".info.localip", devices[i].localip, true);
setState(praefix0 + "." + devices[i].did + ".info.isOnline", true, true);
CreateDpTrigger(deviceCount);
deviceCount++;
} catch (e) {
log("❌ Error connecting to " + devices[i].name + ": " + e.message, "error");
}
}
log("✅ Initialization complete! " + deviceCount + " devices connected.");
GenericDpRefreshIntervalObj = setInterval(function () {
RefreshGenericDpsTicker();
}, refresh);
}
async function RefreshGenericDpsTicker() {
// Periodical status updates
for (let i = 0; i < device.length; i++) {
if (device[i]) {
setState(praefix0 + "." + device[i].deviceConfig.did + ".info.isOnline", device[i].isOnline, true);
}
}
}
function RefreshDps(deviceIndex, data) {
let deviceId = device[deviceIndex].deviceConfig.did;
let model = device[deviceIndex].model;
if (logging) log("Updating properties for: " + device[deviceIndex].deviceConfig.name);
for (let x in DefineDevice) {
if (DefineDevice[x].model === model) {
// Control Datenpunkte
if (DefineDevice[x].control) {
for (let y in DefineDevice[x].control) {
let propName = DefineDevice[x].control[y].name;
let propType = DefineDevice[x].control[y].type;
let channelId = CorrectChannelId(propName);
if (data[propName] !== undefined) {
let value = data[propName];
// TYPE CONVERSION
if (propType === "boolean") {
if (typeof value === "string") {
value = (value === "true" || value === "on" || value === "1" || value === true || value === 1);
} else {
value = !!value;
}
}
else if (propType === "number") {
if (typeof value === "string") {
value = parseFloat(value) || 0;
} else if (typeof value !== "number") {
value = Number(value) || 0;
}
}
setState(praefix0 + "." + deviceId + ".control." + channelId, value, true);
if (logging) log("Set control." + propName + " = " + value);
}
}
}
// Sensor Datenpunkte
if (DefineDevice[x].sensor) {
for (let y in DefineDevice[x].sensor) {
let propName = DefineDevice[x].sensor[y].name;
let propType = DefineDevice[x].sensor[y].type;
let channelId = CorrectChannelId(propName);
if (data[propName] !== undefined) {
let value = data[propName];
// TYPE CONVERSION
if (propType === "boolean") {
if (typeof value === "string") {
value = (value === "true" || value === "on" || value === "1" || value === true || value === 1);
} else {
value = !!value;
}
}
else if (propType === "number") {
if (typeof value === "string") {
value = parseFloat(value) || 0;
} else if (typeof value !== "number") {
value = Number(value) || 0;
}
}
setState(praefix0 + "." + deviceId + ".sensor." + channelId, value, true);
if (logging) log("Set sensor." + propName + " = " + value);
}
}
}
}
}
}
function CreateDpTrigger(deviceIndex) {
let deviceId = device[deviceIndex].deviceConfig.did;
let model = device[deviceIndex].model;
for (let x in DefineDevice) {
if (DefineDevice[x].model === model) {
// Nur Control Datenpunkte sind steuerbar
if (DefineDevice[x].control) {
for (let y in DefineDevice[x].control) {
if (DefineDevice[x].control[y].write) {
let propName = DefineDevice[x].control[y].name;
let channelId = CorrectChannelId(propName);
let dpPath = praefix0 + "." + deviceId + ".control." + channelId;
on({ id: dpPath, change: "ne", ack: false }, function (dp) {
if (logging) log("Command received: " + propName + " = " + dp.state.val);
if (DefineDevice[x].setter[propName]) {
DefineDevice[x].setter[propName](deviceIndex, dp.state.val);
}
});
}
}
}
}
}
}
onStop(function () {
log("Stopping script...");
for (let x in device) {
if (device[x]) {
try {
device[x].destroy();
} catch(e) {
log("Error destroying device: " + e.message);
}
}
}
if (GenericDpRefreshIntervalObj) {
clearInterval(GenericDpRefreshIntervalObj);
}
}, 10);
// ============================================
// SCRIPT START
// ============================================
log("Configuration:");
log("- Devices: " + devices.length);
log("- Refresh: " + refresh + "ms");
log("- Root path: " + praefix0);
// Prepare datapoints
for (let i = 0; i < devices.length; i++) {
if (devices[i].enabled) {
log("📝 Preparing DPs for: " + devices[i].name);
PrepareGenericDps(devices[i].did);
PrepareDeviceDps(devices[i].did, devices[i].model);
}
}
log("🔍 Total datapoints prepared: " + DpCount);
CreateStates();