iobroker.imow
Version:
Adapter for Stihl Vikin iMow
292 lines (280 loc) • 12.9 kB
JavaScript
//v1.5
/*
options:
write //set common write variable to true
forceIndex //instead of trying to find names for array entries, use the index as the name
channelName //set name of the root channel
preferedArrayName //set key to use this as an array entry name
autoCast (true false) // make JSON.parse to parse numbers correctly
descriptions: Object of names for state keys
*/
const JSONbig = require("json-bigint")({ storeAsString: true });
module.exports = class Json2iob {
constructor(adapter) {
this.adapter = adapter;
this.alreadyCreatedObjects = {};
}
async parse(path, element, options) {
try {
if (element === null || element === undefined) {
this.adapter.log.debug("Cannot extract empty: " + path);
return;
}
const objectKeys = Object.keys(element);
if (!options || !options.write) {
if (!options) {
options = { write: false };
} else {
options["write"] = false;
}
}
if (typeof element === "string" || typeof element === "number") {
let name = element;
if (typeof element === "number") {
name = element.toString();
}
if (!this.alreadyCreatedObjects[path]) {
await this.adapter
.setObjectNotExistsAsync(path, {
type: "state",
common: {
name: name,
role: this.getRole(element, options.write),
type: element !== null ? typeof element : "mixed",
write: options.write,
read: true,
},
native: {},
})
.then(() => {
// this.alreadyCreatedObjects[path] = true;
})
.catch((error) => {
this.adapter.log.error(error);
});
}
this.adapter.setState(path, element, true);
return;
}
if (!this.alreadyCreatedObjects[path]) {
await this.adapter
.setObjectNotExistsAsync(path, {
type: "channel",
common: {
name: options.channelName || "",
write: false,
read: true,
},
native: {},
})
.then(() => {
// this.alreadyCreatedObjects[path] = true;
options.channelName = undefined;
})
.catch((error) => {
this.adapter.log.error(error);
});
}
if (Array.isArray(element)) {
await this.extractArray(element, "", path, options);
return;
}
for (const key of objectKeys) {
if (this.isJsonString(element[key]) && options.autoCast) {
element[key] = JSONbig.parse(element[key]);
}
if (Array.isArray(element[key])) {
await this.extractArray(element, key, path, options);
} else if (element[key] !== null && typeof element[key] === "object") {
await this.parse(path + "." + key, element[key], options);
} else {
if (!this.alreadyCreatedObjects[path + "." + key]) {
let objectName = key;
if (options.descriptions && options.descriptions[key]) {
objectName = options.descriptions[key];
}
const type = element[key] !== null ? typeof element[key] : "mixed";
const common = {
name: objectName,
role: this.getRole(element[key], options.write),
type: type,
write: options.write,
read: true,
};
await this.adapter
.setObjectNotExistsAsync(path + "." + key, {
type: "state",
common: common,
native: {},
})
.then(() => {
// this.alreadyCreatedObjects[path + "." + key] = true;
})
.catch((error) => {
this.adapter.log.error(error);
});
}
this.adapter.setState(path + "." + key, element[key], true);
}
}
} catch (error) {
this.adapter.log.error("Error extract keys: " + path + " " + JSON.stringify(element));
this.adapter.log.error(error);
}
}
async extractArray(element, key, path, options) {
try {
if (key) {
element = element[key];
}
for (let index in element) {
const arrayElement = element[index];
index = parseInt(index) + 1;
if (index < 10) {
index = "0" + index;
}
let arrayPath = key + index;
if (typeof arrayElement === "string") {
await this.parse(path + "." + key + "." + arrayElement, arrayElement, options);
continue;
}
if (typeof arrayElement[Object.keys(arrayElement)[0]] === "string") {
arrayPath = arrayElement[Object.keys(arrayElement)[0]];
}
for (const keyName of Object.keys(arrayElement)) {
if (keyName.endsWith("Id") && arrayElement[keyName] !== null) {
if (arrayElement[keyName] && arrayElement[keyName].replace) {
arrayPath = arrayElement[keyName].replace(/\./g, "");
} else {
arrayPath = arrayElement[keyName];
}
}
}
for (const keyName in Object.keys(arrayElement)) {
if (keyName.endsWith("Name")) {
if (arrayElement[keyName] && arrayElement[keyName].replace) {
arrayPath = arrayElement[keyName].replace(/\./g, "");
} else {
arrayPath = arrayElement[keyName];
}
}
}
if (arrayElement.id) {
if (arrayElement.id.replace) {
arrayPath = arrayElement.id.replace(/\./g, "");
} else {
arrayPath = arrayElement.id;
}
}
if (arrayElement.name) {
arrayPath = arrayElement.name.replace(/\./g, "");
}
if (arrayElement.label) {
arrayPath = arrayElement.label.replace(/\./g, "");
}
if (arrayElement.labelText) {
arrayPath = arrayElement.labelText.replace(/\./g, "");
}
if (arrayElement.start_date_time) {
arrayPath = arrayElement.start_date_time.replace(/\./g, "");
}
if (options.preferedArrayName && options.preferedArrayName.indexOf("+") !== -1) {
const preferedArrayNameArray = options.preferedArrayName.split("+");
if (arrayElement[preferedArrayNameArray[0]]) {
const element0 = arrayElement[preferedArrayNameArray[0]].replace(/\./g, "").replace(/\ /g, "");
let element1 = "";
if (preferedArrayNameArray[1].indexOf("/") !== -1) {
const subArray = preferedArrayNameArray[1].split("/");
const subElement = arrayElement[subArray[0]];
if (subElement && subElement[subArray[1]] !== undefined) {
element1 = subElement[subArray[1]];
} else if (arrayElement[subArray[1]] !== undefined) {
element1 = arrayElement[subArray[1]];
}
} else {
element1 = arrayElement[preferedArrayNameArray[1]].replace(/\./g, "").replace(/\ /g, "");
}
arrayPath = element0 + "-" + element1;
}
} else if (options.preferedArrayName && options.preferedArrayName.indexOf("/") !== -1) {
const preferedArrayNameArray = options.preferedArrayName.split("/");
const subElement = arrayElement[preferedArrayNameArray[0]];
if (subElement) {
arrayPath = subElement[preferedArrayNameArray[1]].replace(/\./g, "").replace(/\ /g, "");
}
} else if (options.preferedArrayName && arrayElement[options.preferedArrayName]) {
arrayPath = arrayElement[options.preferedArrayName].replace(/\./g, "");
}
if (options.forceIndex) {
arrayPath = key + index;
}
//special case array with 2 string objects
if (
!options.forceIndex &&
Object.keys(arrayElement).length === 2 &&
typeof Object.keys(arrayElement)[0] === "string" &&
typeof Object.keys(arrayElement)[1] === "string" &&
typeof arrayElement[Object.keys(arrayElement)[0]] !== "object" &&
typeof arrayElement[Object.keys(arrayElement)[1]] !== "object" &&
arrayElement[Object.keys(arrayElement)[0]] !== "null"
) {
let subKey = arrayElement[Object.keys(arrayElement)[0]];
const subValue = arrayElement[Object.keys(arrayElement)[1]];
const subName = Object.keys(arrayElement)[0] + " " + Object.keys(arrayElement)[1];
if (key) {
subKey = key + "." + subKey;
}
if (!this.alreadyCreatedObjects[path + "." + subKey]) {
await this.adapter
.setObjectNotExistsAsync(path + "." + subKey, {
type: "state",
common: {
name: subName,
role: this.getRole(subValue, options.write),
type: subValue !== null ? typeof subValue : "mixed",
write: options.write,
read: true,
},
native: {},
})
.then(() => {
// this.alreadyCreatedObjects[path + "." + subKey] = true;
});
}
this.adapter.setState(path + "." + subKey, subValue, true);
continue;
}
await this.parse(path + "." + arrayPath, arrayElement, options);
}
} catch (error) {
this.adapter.log.error("Cannot extract array " + path);
this.adapter.log.error(error);
}
}
isJsonString(str) {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
}
getRole(element, write) {
if (typeof element === "boolean" && !write) {
return "indicator";
}
if (typeof element === "boolean" && write) {
return "switch";
}
if (typeof element === "number" && !write) {
return "value";
}
if (typeof element === "number" && write) {
return "level";
}
if (typeof element === "string") {
return "text";
}
return "state";
}
};