iobroker.openmediavault
Version:
343 lines (342 loc) • 17.8 kB
JavaScript
/*
* Created with @iobroker/create-adapter v2.6.5
*/
// The adapter-core module gives you access to the core ioBroker functions
// you need to create an adapter
import * as utils from '@iobroker/adapter-core';
import _ from 'lodash';
import url from 'node:url';
import * as schedule from 'node-schedule';
import moment from 'moment';
// Load your modules here, e.g.:
import { OmvApi } from './lib/omv-rpc.js';
import * as tree from './lib/tree/index.js';
import { myIob } from './lib/myIob.js';
class Openmediavault extends utils.Adapter {
omvApi = undefined;
myIob = undefined;
updateSchedule = undefined;
subscribedList = [];
statesUsingValAsLastChanged = [ // id of states where lc is taken from the value
];
updateTimeout = undefined;
configDevicesCache = {};
constructor(options = {}) {
super({
...options,
name: 'openmediavault',
useFormatDate: true
});
this.on('ready', this.onReady.bind(this));
this.on('stateChange', this.onStateChange.bind(this));
// this.on('objectChange', this.onObjectChange.bind(this));
this.on('message', this.onMessage.bind(this));
this.on('unload', this.onUnload.bind(this));
}
//#region adapter methods
/**
* Is called when databases are connected and adapter received configuration.
*/
async onReady() {
const logPrefix = '[onReady]:';
try {
this.connected = false;
await utils.I18n.init(`${utils.getAbsoluteDefaultDataDir().replace('iobroker-data/', '')}node_modules/iobroker.${this.name}/admin`, this);
this.myIob = new myIob(this, utils, this.statesUsingValAsLastChanged);
if (this.config.url && this.config.user && this.config.password) {
this.omvApi = new OmvApi(this);
await this.updateData(true);
}
else {
this.log.warn(`${logPrefix} url and / or login data missing!`);
}
this.myIob.findMissingTranslation();
// const tmp = tree.smart.getStateIDs();
// let list = []
// for (let id of tmp) {
// list.push({ id: id });
// }
// this.log.warn(JSON.stringify(list));
}
catch (error) {
this.log.error(`${logPrefix} error: ${error}, stack: ${error.stack}`);
}
}
/**
* Is called when adapter shuts down - callback has to be called under any circumstances!
*
* @param callback
*/
async onUnload(callback) {
try {
// Here you must clear all timeouts or intervals that may still be active
if (this.updateTimeout) {
clearTimeout(this.updateTimeout);
}
if (this.updateSchedule) {
this.updateSchedule.cancel();
}
// clearTimeout(timeout2);
// ...
// clearInterval(interval1);
await this.omvApi?.logout();
callback();
}
catch (e) {
callback();
}
}
// If you need to react to object changes, uncomment the following block and the corresponding line in the constructor.
// You also need to subscribe to the objects with `this.subscribeObjects`, similar to `this.subscribeStates`.
// /**
// * Is called if a subscribed object changes
// */
// private onObjectChange(id: string, obj: ioBroker.Object | null | undefined): void {
// if (obj) {
// // The object was changed
// this.log.info(`object ${id} changed: ${JSON.stringify(obj)}`);
// } else {
// // The object was deleted
// this.log.info(`object ${id} deleted`);
// }
// }
/**
* Is called if a subscribed state changes
*
* @param id
* @param state
*/
onStateChange(id, state) {
if (state) {
// The state was changed
this.log.info(`state ${id} changed: ${state.val} (ack = ${state.ack})`);
}
else {
// The state was deleted
this.log.info(`state ${id} deleted`);
}
}
/**
* Some message was sent to this instance over message box. Used by email, pushover, text2speech, ...
* Using this method requires 'common.messagebox' property to be set to true in io-package.json
*
* @param obj
*/
onMessage(obj) {
const logPrefix = '[onMessage]:';
try {
if (typeof obj === 'object' && this.myIob) {
if (obj.command.endsWith('StateList')) {
const endpoint = obj.command.replace('StateList', '');
const states = tree[endpoint].getStateIDs();
let list = [];
if (states) {
for (let i = 0; i <= states.length - 1; i++) {
if (states[i + 1] && states[i] === this.myIob.getIdWithoutLastPart(states[i + 1])) {
list.push({
label: `[Channel]\t ${states[i]}`,
value: states[i],
});
}
else {
list.push({
label: `[State]\t\t ${states[i]}`,
value: states[i],
});
}
}
}
list = _.orderBy(list, ['value'], ['asc']);
if (obj.callback) {
this.sendTo(obj.from, obj.command, list, obj.callback);
}
}
else if (obj.command.endsWith('BlackList')) {
if (this.connected && this.omvApi?.isConnected) {
let list = [];
if (this.configDevicesCache[obj.command.replace('BlackList', '')]) {
list = this.configDevicesCache[obj.command.replace('BlackList', '')];
list = _.orderBy(list, ['label'], ['asc']);
if (obj.callback) {
this.sendTo(obj.from, obj.command, list, obj.callback);
}
}
}
}
}
}
catch (error) {
this.log.error(`${logPrefix} error: ${error}, stack: ${error.stack}`);
}
}
//#endregion
//#region updateData
async updateData(isAdapterStart = false) {
const logPrefix = '[updateData]:';
try {
if (!this.config.updateMethode) {
if (this.updateTimeout) {
this.clearTimeout(this.updateTimeout);
this.updateTimeout = undefined;
}
this.updateTimeout = this.setTimeout(async () => {
await this.updateData();
}, this.config.updateInterval * 1000);
}
else {
if (isAdapterStart) {
this.log.info(`${logPrefix} starting cron job with parameter '${this.config.updateCron}'`);
this.updateSchedule = schedule.scheduleJob(this.config.updateCron, async () => {
await this.updateData();
});
}
}
if (this.omvApi) {
if (!this.omvApi.isConnected || moment().diff(this.omvApi.lastLogin ?? 0, 'minutes') >= this.omvApi.MAX_LOGIN_AGE_MINUTES) {
if (this.omvApi.lastLogin !== null && moment().diff(this.omvApi.lastLogin ?? 0, 'minutes') >= this.omvApi.MAX_LOGIN_AGE_MINUTES) {
this.log.debug(`${logPrefix} re-login because session id expired`);
}
await this.omvApi.login();
}
if (this.connected && this.omvApi?.isConnected) {
for (const endpoint of Object.keys(tree)) {
if (Object.hasOwn(tree[endpoint], 'iobObjectDefintions')) {
await this.updateDataGeneric(endpoint, tree[endpoint], tree[endpoint].iobObjectDefintions, isAdapterStart);
}
else {
if (this.log.level === 'debug') {
this.log.warn(`${logPrefix} no iob definitions for endpoint ${endpoint} exists!`);
}
}
}
}
}
}
catch (error) {
this.log.error(`${logPrefix} error: ${error}, stack: ${error.stack}`);
}
}
/**
* update data gerneric
*
* @param endpoint
* @param treeType
* @param iobObjectDefintions
* @param isAdapterStart
*/
async updateDataGeneric(endpoint, treeType, iobObjectDefintions, isAdapterStart = false) {
const logPrefix = `[updateDataGeneric]: [${endpoint}]: `;
const configDynamic = this.config;
try {
if (this.myIob) {
if (this.connected && this.omvApi?.isConnected) {
if (this.config[`${endpoint}Enabled`]) {
this.log.debug(`${logPrefix} [${endpoint}]: start updating data...`);
if (isAdapterStart) {
await this.myIob.createOrUpdateChannel(treeType.idChannel, iobObjectDefintions.channelName, undefined, true);
}
const data = await this.omvApi?.retrievData(endpoint);
if (data) {
if (Array.isArray(data)) {
this.configDevicesCache[endpoint] = [];
for (let device of data) {
if (iobObjectDefintions.deviceIdProperty) {
let deviceName = 'unknown';
if ((typeof iobObjectDefintions.deviceNameProperty === 'function')) {
deviceName = iobObjectDefintions.deviceNameProperty(device, this);
}
else if (typeof iobObjectDefintions.deviceNameProperty === 'string') {
deviceName = device[iobObjectDefintions.deviceNameProperty];
}
let deviceIdProperty = '';
if ((typeof iobObjectDefintions.deviceIdProperty === 'function')) {
deviceIdProperty = iobObjectDefintions.deviceIdProperty(device, this);
}
else {
deviceIdProperty = device[iobObjectDefintions.deviceIdProperty];
}
if (deviceIdProperty) {
const idDevice = `${treeType.idChannel}.${deviceIdProperty}`;
const isWhiteList = configDynamic[`${endpoint}IsWhiteList`] ?? false;
const blackList = configDynamic[`${endpoint}BlackList`] ?? [];
if ((!isWhiteList && !_.some(blackList, { id: deviceIdProperty })) || (isWhiteList && _.some(blackList, { id: deviceIdProperty }))) {
if (Object.hasOwn(iobObjectDefintions, 'additionalRequest')) {
if (iobObjectDefintions.additionalRequest) {
for (const additionalRequest of iobObjectDefintions.additionalRequest) {
if (device[additionalRequest.conditionProperty]) {
const addtionalData = await this.omvApi?.retrievData(additionalRequest.endpoint, {
[additionalRequest.paramsProperty]: device[additionalRequest.paramsProperty]
});
if (additionalRequest.converter) {
device = { ...additionalRequest.converter(addtionalData, this), ...device };
}
else {
device = { ...addtionalData, ...device };
}
}
else {
this.log.debug(`${logPrefix} device '${deviceIdProperty}' - no additional data request because condition property '${additionalRequest.conditionProperty}' is '${device[additionalRequest.conditionProperty]}'`);
}
}
}
}
this.log.debug(`${logPrefix} final data ${JSON.stringify(device)}`);
this.configDevicesCache[endpoint].push({
label: `${deviceName} (${deviceIdProperty})`,
value: deviceIdProperty,
});
await this.myIob.createOrUpdateDevice(idDevice, deviceName, iobObjectDefintions.deviceIsOnlineState ? `${idDevice}.${iobObjectDefintions.deviceIsOnlineState}` : undefined, iobObjectDefintions.deviceHasErrorsState ? `${idDevice}.${iobObjectDefintions.deviceHasErrorsState}` : undefined, undefined, isAdapterStart, true);
await this.myIob.createOrUpdateStates(idDevice, treeType.get(), device, device, configDynamic[`${endpoint}StatesBlackList`] ?? [], configDynamic[`${endpoint}StatesIsWhiteList`] ?? false, deviceName, isAdapterStart);
this.log.debug(`${logPrefix} device '${deviceIdProperty}' data successfully updated`);
}
else {
if (isAdapterStart) {
if (await this.objectExists(idDevice)) {
await this.delObjectAsync(idDevice, { recursive: true });
this.log.warn(`${logPrefix} device '${deviceName}' (id: ${deviceIdProperty}) delete, ${isWhiteList ? 'it\'s not on the whitelist' : 'it\'s on the blacklist'}`);
}
}
}
}
else {
this.log.error(`${logPrefix} deviceIdProperty for device not exists! ('${JSON.stringify(device)}')`);
}
}
else {
this.log.error(`${logPrefix} deviceIdProperty property not exists in tree definition!`);
}
}
}
else {
await this.myIob.createOrUpdateStates(treeType.idChannel, treeType.get(), data, data, configDynamic[`${endpoint}StatesBlackList`] ?? [], configDynamic[`${endpoint}StatesIsWhiteList`] ?? false, iobObjectDefintions.channelName, isAdapterStart);
this.log.debug(`${logPrefix} channel '${iobObjectDefintions.channelName}' data successfully updated`);
}
if (isAdapterStart) {
this.log.info(`${logPrefix} data successfully updated`);
}
}
}
else {
if (await this.objectExists(treeType.idChannel)) {
await this.delObjectAsync(treeType.idChannel, { recursive: true });
this.log.debug(`${logPrefix} '${treeType.idChannel}' deleted`);
}
}
}
}
}
catch (error) {
this.log.error(`${logPrefix} error: ${error}, stack: ${error.stack}`);
}
}
}
// replace only needed for dev system
const modulePath = url.fileURLToPath(import.meta.url).replace('/development/', '/node_modules/');
if (process.argv[1] === modulePath) {
// start the instance directly
new Openmediavault();
}
export default function startAdapter(options) {
// compact mode
return new Openmediavault(options);
}