iobroker.doorbird
Version:
964 lines (860 loc) • 36.1 kB
JavaScript
'use strict';
/*
* Created with @iobroker/create-adapter v2.3.0
*/
// The adapter-core module gives you access to the core ioBroker functions
// you need to create an adapter
const utils = require('@iobroker/adapter-core');
const dgram = require('node:dgram');
const Axios = require('axios').default;
Axios.defaults.timeout = 5000;
const http = require('node:http');
const udpserver = dgram.createSocket('udp4');
const InterimSolutionForDeletionOfDuplicates = false;
const MAX_SCHEDULES = 200;
let COUNT_SCHEDULES = 0;
let adapterStarted = false;
class Doorbird extends utils.Adapter {
/**
* @param [options]
*/
constructor(options) {
super({
...options,
name: 'doorbird',
});
this.on('ready', this.onReady.bind(this));
this.on('stateChange', this.onStateChange.bind(this));
this.on('message', this.onMessage.bind(this));
this.on('unload', this.onUnload.bind(this));
this.sockets = [];
this.bellCount = 0;
this.scheduleState = {};
this.motionState = {};
this.doorbellsArray = []; // Contains all Doorbell IDs
this.favoriteState = {}; // {'ID of Doorbell/Motion/RFID': 'ID of Favorite'}
this.authorized = false;
this.wizard = false;
this.wizardTimeout = null;
}
/**
* Is called when databases are connected and adapter received configuration.
*/
async onReady() {
// Reset the connection indicator during startup
await this.setState('info.connection', { val: false, ack: true });
await Promise.all([this.migrateAsync(), this.mainAsync()]);
}
async mainAsync() {
if (this.config.birdip && this.config.birdpw && this.config.birduser) {
this.testBirdAsync(); // no await because code should run parallel
}
try {
udpserver.on('listening', () => {
const address = udpserver.address();
this.log.debug(`Adapter listening on IP: ${address} - UDP Port 35344`);
});
} catch (error) {
this.log.error(`address already in use ${this.config.adapterAddress}:${this.config.adapterport}: ${error}`);
}
// udpserver.bind(35344);
if (this.config.adapterAddress) {
try {
this.server = http.createServer(async (req, res) => {
if (res.socket && res.socket.remoteAddress) {
const remoteAddress = res.socket.remoteAddress.replace(/^.*:/, '');
if (remoteAddress === this.config.birdip) {
res.writeHead(204, { 'Content-Type': 'text/plain' });
if (req.url == '/motion') {
this.log.debug('Received Motion-alert from Doorbird!');
await Promise.all([
this.setState('Motion.trigger', { val: true, ack: true }),
this.downloadFileAsync('Motion'),
]);
this.setTimeout(async () => {
await this.setState('Motion.trigger', { val: false, ack: true });
}, 2500);
}
if (req.url && req.url.indexOf('ring') != -1) {
const id = req.url.substring(req.url.indexOf('?') + 1, req.url.length);
this.log.debug(`Received Ring-alert (ID: ${id}) from Doorbird!`);
await Promise.all([
this.setState(`Doorbell.${id}.trigger`, { val: true, ack: true }),
this.downloadFileAsync(`Doorbell${id}`),
]);
this.setTimeout(async () => {
await this.setState(`Doorbell.${id}.trigger`, { val: false, ack: true });
}, 2000);
}
res.end();
}
} else {
res.writeHead(401, { 'Content-Type': 'text/plain' });
res.end();
}
});
this.server.listen(this.config.adapterport || 8081, this.config.adapterAddress, () => {
this.log.debug(
`Server started on port ${this.config.adapterport || 8100} with IP ${this.config.adapterAddress}`,
);
});
} catch (e) {
this.log.warn(`There was an Error starting the HTTP Server! (${e})`);
}
} else {
this.log.warn('You need to set the Adapteraddress in the Instance-Settings!!');
}
this.subscribeStates('*');
}
/**
* Is called if a subscribed state changes
*
* @param id
* @param state
*/
async onStateChange(id, state) {
if (!state || state.ack) {
return;
}
const comp = id.split('.');
if (!this.authorized) {
this.log.warn('Cannot do that because not authorized! Check you config!');
return;
}
switch (comp[2]) {
case 'Restart':
this.log.debug('Trying to restart DoorBird Device..');
await this.restartDoorbirdAsync();
break;
case 'TakeSnapshot':
this.log.info('Trying to take snapshot..');
await this.downloadFileAsync('TakeSnapshot');
break;
case 'Light':
this.log.info('Trying to turn on light..');
await this.turnOnLightAsync();
break;
case 'Relays':
await this.relaysAsync(comp[3]);
break;
}
}
/**
* @param obj
*/
async onMessage(obj) {
if (typeof obj === 'object' && obj.message) {
if (obj.command === 'wizard' && !this.wizard) {
this.startWizard(obj);
this.wizard = true;
}
}
}
/**
* @param key
* @param value
* @returns
*/
decryption(key, value) {
let result = '';
for (let i = 0; i < value.length; ++i) {
result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i));
}
return result;
}
/**
* Generate URL
*
* @param command
*/
buildURL(command) {
return `http://${this.config.birdip}/bha-api/${command}.cgi?http-user=${this.config.birduser}&http-password=${
this.config.birdpw
}`;
}
/**
* Check Doorbird Connection
*
* @async
* @returns
*/
async testBirdAsync() {
try {
const url = this.buildURL('schedule');
const response = await Axios.get(url);
if (response.status === 401) {
this.authorized = false;
await this.setState('info.connection', { val: false, ack: true });
this.log.warn(`Whooops.. DoorBird says User ${this.config.birduser} is unauthorized!!`);
this.log.warn('Check Username + Password and enable the "API-Operator" Permission for the User!!');
} else if (response.status === 200) {
this.authorized = true;
await this.setState('info.connection', { val: true, ack: true });
this.log.debug(`Authorization with User ${this.config.birduser} successful!`);
if (!adapterStarted) {
adapterStarted = true;
await this.getInfoAsync();
}
}
} catch (error) {
if (error.code === 'EHOSTUNREACH') {
this.authorized = false;
await this.setState('info.connection', { val: false, ack: true });
this.log.warn('DoorBird Device is offline!!');
} else if (error.code === 'ECONNABORTED') {
this.authorized = false;
await this.setState('info.connection', { val: false, ack: true });
this.log.warn(`Error in testBird() Request timed out: ${error}`);
} else {
this.authorized = false;
await this.setState('info.connection', { val: false, ack: true });
this.log.warn(`Error in testBird() Request: ${error}`);
}
}
if (this.birdConCheck) {
this.clearTimeout(this.birdConCheck);
this.birdConCheck = null;
}
this.birdConCheck = this.setTimeout(async () => {
this.log.debug(`Refresh connection check...`);
this.birdConCheck = null;
await this.testBirdAsync();
}, 180000);
}
/**
* Get Doorbird Info
*
* @async
* @returns
*/
async getInfoAsync() {
if (this.authorized) {
try {
const url = this.buildURL('info');
const response = await Axios.get(url);
if (response.status === 200) {
const info = response.data;
await this.setState('info.firmware', { val: info.BHA.VERSION[0].FIRMWARE, ack: true });
await this.setState('info.build', { val: info.BHA.VERSION[0].BUILD_NUMBER, ack: true });
await this.setState('info.type', { val: info.BHA.VERSION[0]['DEVICE-TYPE'], ack: true });
const relays = info.BHA.VERSION[0].RELAYS;
for (const value of relays) {
//create channel
await this.createObjectsAsync('Relays', 'channel', {
name: {
en: 'Available Relays',
de: 'Verfügbare Relais',
ru: 'Доступные реле',
pt: 'Relés disponíveis',
nl: 'Available Relay',
fr: 'Relais disponibles',
it: 'Relè disponibili',
es: 'Relés disponibles',
pl: 'Dostępny Relay',
uk: 'Доступні реле',
'zh-cn': '现有费用',
},
});
//create State
await this.createObjectsAsync(`Relays.${value}`, 'state', {
name: {
en: 'Activate relay',
de: 'Relais aktivieren',
ru: 'Активировать реле',
pt: 'Activar o relé',
nl: 'Activeer relay',
fr: 'Activer le relais',
it: 'Attiva relè',
es: 'Activar el relé',
pl: 'Aktywacja',
uk: 'Активувати реле',
'zh-cn': '法 律 法 律 律 律 律 律 律 律 律 律 律 律 律 律 律 律 律 律 律 律 重',
},
type: 'boolean',
role: 'button',
read: false,
write: true,
desc: `ID: ${value}`,
});
}
}
} catch (error) {
this.log.warn(`Error in getInfo(): ${error}`);
}
await this.checkFavoritesAsync();
} else {
this.log.warn('Execution of getInfo not allowed because not authorized!');
}
}
/**
* Check Favorites
*
* @async
* @returns
*/
async checkFavoritesAsync() {
this.log.debug('Checking favorites on DoorBird Device..');
try {
const url = this.buildURL('favorites');
const response = await Axios.get(url);
if (response.status === 200) {
const favorites = response.data;
if (!favorites.http || typeof favorites.http !== 'object') {
this.log.warn('It seems that the list of HTTP Favorites is empty!');
await this.createFavoritesAsync(0);
return;
}
for (const [key, obj] of Object.entries(favorites.http)) {
if (
obj.title.includes(`ioBroker ${this.namespace}`) &&
!obj.value.includes(`${this.config.adapterAddress}:${this.config.adapterport}`)
) {
this.log.warn(
`The Favorite ID '${key}' contains a wrong URL ('${obj.value}').. I will update that..`,
);
await this.updateFavoriteAsync(key, obj);
return;
}
if (obj.value.includes(`${this.config.adapterAddress}:${this.config.adapterport}`)) {
this.log.debug(
`Found a Favorite that belongs to me.. (ID: '${key}') ('${obj.title}': '${obj.value}')`,
);
const favoriteKey = obj.title.split(' ')[2];
const favoriteUrl = obj.value;
this.favoriteState[favoriteKey] = {
ID: key,
URL: obj.value,
};
const duplicate = Object.values(this.favoriteState).find(
item => item.URL === favoriteUrl && item.ID !== key,
);
if (duplicate) {
this.log.warn(`Found a duplicate favorite! (ID : '${key}') URL ${obj.value}`);
if (InterimSolutionForDeletionOfDuplicates) {
this.log.warn(
`deleting duplicates is currently in dev-mode. Please delete duplicates yourself via the Doorbird app if necessary.`,
);
} else {
this.log.debug(`Trying to delete the duplicate..`);
const deleteUrl = `http://${this.config.birdip}/bha-api/favorites.cgi?http-user=${this.config.birduser}&http-password=${this.config.birdpw}&action=remove&type=http&id=${key}`;
try {
const deleteResponse = await Axios.get(deleteUrl);
if (deleteResponse.status === 200) {
delete this.favoriteState[favoriteKey];
this.log.debug(`Deleted the duplicate (ID: '${key}') successfully!`);
// toDelete
this.log.debug(`favoriteState: ${JSON.stringify(this.favoriteState)}`);
} else {
this.log.warn(`I was unable to delete the duplicate! (ID: ${key})`);
}
} catch (error) {
this.log.error(
`An error occurred while trying to delete the duplicate (ID: ${key}): ${error}`,
);
}
}
}
}
}
if (Object.keys(this.favoriteState).length === 0) {
this.log.debug('I did not find any Favorite on the DoorBird Device that belongs to me.');
} else {
this.log.debug(`Result of Favorites: ${JSON.stringify(this.favoriteState)}`);
}
await this.getSchedulesAsync();
return;
}
} catch (error) {
this.log.warn(`Error in checkFavoritesAsync(): ${error}`);
}
}
/**
* Update Favorites
*
* @async
* @param key
* @param obj
* @returns
*/
async updateFavoriteAsync(key, obj) {
const newURL = obj.value.replace(
/(http:\/\/)(.*)(\/.*)/,
`$1${this.config.adapterAddress}:${this.config.adapterport}$3`,
);
try {
const url = `http://${this.config.birdip}/bha-api/favorites.cgi?http-user=${
this.config.birduser
}&http-password=${this.config.birdpw}&action=save&type=http&id=${key}&title=${obj.title}&value=${newURL}`;
const response = await Axios.get(url);
if (response.status === 200) {
this.log.debug('Favorite Updated successfully..');
await this.checkFavoritesAsync();
} else {
this.log.warn(`There was an error while updating the Favorite! ${response.status}`);
}
} catch (error) {
if (error.code === 'ECONNABORTED') {
this.log.warn(`Error in testBird() Request timed out: ${error}`);
} else {
this.log.warn(`There was an error while updating the Favorite! (${error})`);
}
}
}
/**
* Get Schedules
*
* @async
* @returns
*/
async getSchedulesAsync() {
this.log.debug(`[ getSchedulesAsync ] Schedule call count: ${COUNT_SCHEDULES}`);
COUNT_SCHEDULES++;
try {
const url = this.buildURL('schedule');
const response = await Axios.get(url);
if (response.status === 200) {
try {
const schedules = response.data;
this.bellCount = 0;
this.log.debug(`Following schedules found: ${JSON.stringify(schedules)}`);
this.log.debug('Looping through the Schedules..');
for (let i = 0; i < schedules.length && i < MAX_SCHEDULES; i++) {
if (schedules[i].input === 'doorbell') {
this.bellCount++;
this.log.debug('Detected a Doorbell Schedule!');
this.scheduleState[schedules[i].param] = schedules[i].output;
this.log.debug(`The Param of the actual Schedule is: ${schedules[i].param}`);
this.doorbellsArray.push(schedules[i].param);
this.log.debug(`The Output contains ${schedules[i].output.length} entries!`);
for (let k = 0; k < schedules[i].output.length; k++) {
this.log.debug(`Entry "${k}" is: ${JSON.stringify(schedules[i].output[k])}`);
}
this.log.debug(`Counted ${this.bellCount} Doorbells.`);
}
if (schedules[i].input === 'motion') {
this.log.debug('Detected Motion Schedule!');
this.motionState.output = schedules[i].output;
this.log.debug(`The Output contains ${schedules[i].output.length} entries!`);
for (let k = 0; k < schedules[i].output.length; k++) {
this.log.debug(`Entry "${k}" is: ${JSON.stringify(schedules[i].output[k])}`);
}
}
}
await this.createFavoritesAsync(0);
} catch (error) {
this.log.warn(`Error in Parsing Schedules: ${error}. Error-Stack: ${error.stack}`);
}
}
} catch (error) {
this.log.warn(`Error in getSchedules(): ${error}`);
}
}
/**
* @async
* @param i
* @param [action]
* @param [motion]
* @returns
*/
async createFavoritesAsync(i, action, motion) {
this.log.debug('Checking if we need to create any favorites..');
if (i < this.doorbellsArray.length && !motion) {
this.log.debug(`Cheking if Favorite for Doorbell ID ${this.doorbellsArray[i]} exists.`);
if (this.favoriteState[this.doorbellsArray[i]]) {
this.log.debug(`Favorite for Doorbell ID ${this.doorbellsArray[i]} exists!`);
i++;
await this.createFavoritesAsync(i, false, false);
return;
}
try {
this.log.debug(`Favorite for Doorbell ID ${this.doorbellsArray[i]} has to be created!`);
const createUrl = `http://${this.config.birdip}/bha-api/favorites.cgi?http-user=${
this.config.birduser
}&http-password=${this.config.birdpw}&action=save&type=http&title=ioBroker ${this.namespace} ${
this.doorbellsArray[i]
} Ring&value=http://${this.config.adapterAddress}:${this.config.adapterport}/ring?${
this.doorbellsArray[i]
}`;
const response = await Axios.get(createUrl);
if (response.status === 200) {
i++;
this.log.debug(`Favorite created successfully..`);
await this.createFavoritesAsync(i, true, false);
return;
}
} catch (error) {
this.log.warn(`Error in creating Favorite: ${error}`);
}
}
if (typeof this.favoriteState['Motion'] === 'undefined' && !motion) {
try {
const url = `http://${this.config.birdip}/bha-api/favorites.cgi?http-user=${
this.config.birduser
}&http-password=${this.config.birdpw}&action=save&type=http&title=ioBroker ${
this.namespace
} Motion&value=http://${this.config.adapterAddress}:${this.config.adapterport}/motion`;
const response = await Axios.get(url);
if (response.status === 200) {
this.log.debug('Favorite for Motionsensor created.');
await this.createFavoritesAsync(i, true, true);
return;
}
} catch (error) {
this.log.warn(`Error creating favorite for Motionsensor: ${error}`);
return;
}
}
if (action) {
this.log.debug('Finished creating Favorites.. Checking again - just to be sure!');
//toDelete
this.log.debug(`favoriteState before check again: ${JSON.stringify(this.favoriteState)}`);
await this.checkFavoritesAsync();
} else {
this.log.debug('Favorites checked successfully. No actions needed!');
await this.createSchedulesAsync();
}
}
/**
* Create Schedules
*
* @async
* @returns
*/
async createSchedulesAsync() {
this.log.debug('Checking if we need to create Schedules on DoorBird Device..');
for (const key of Object.keys(this.scheduleState)) {
let actionNeeded = true;
for (let i = 0; i < this.scheduleState[key].length; i++) {
if (this.scheduleState[key][i].event !== 'http') {
continue;
}
if (this.scheduleState[key][i].param === this.favoriteState[key]['ID']) {
actionNeeded = false;
}
}
if (actionNeeded) {
this.log.debug(`We need to create a Schedule for Doorbell ID: ${key}`);
const scheduleArray = this.scheduleState[key];
scheduleArray.push({
event: 'http',
param: this.favoriteState[key]['ID'],
enabled: '1',
schedule: { weekdays: [{ from: '79200', to: '79199' }] },
});
this.toCreate = { input: 'doorbell', param: key, output: scheduleArray };
try {
const createUrl = this.buildURL('schedule');
const response = await Axios.post(createUrl, this.toCreate);
if (response.status === 200) {
this.log.debug('Schedule created successfully!');
} else {
this.log.warn(
`There was an Error while creating the schedule. Status: ${response.status}, Text: ${response.statusText}`,
);
}
} catch (error) {
this.log.warn(`Error in creating schedule: ${error}`);
}
} else {
this.log.debug('Okay we dont need to create any Doorbell-Schedules..');
}
}
for (const value of this.doorbellsArray) {
await this.createObjectsAsync(`Doorbell.${value}`, 'device', {
name: {
en: 'Doorbell',
de: 'Türklingel',
// ...
},
desc: `ID: ${value}`,
});
// create State
await this.createObjectsAsync(`Doorbell.${value}.trigger`, 'state', {
role: 'indicator',
name: `Doorbell ID '${value}' pressed`,
type: 'boolean',
read: true,
write: false,
def: false,
});
}
await this.createMotionScheduleAsync();
}
/**
* Create Motion Schedule
*
* @async
* @returns
*/
async createMotionScheduleAsync() {
for (const key of Object.keys(this.motionState)) {
let actionNeeded = true;
for (let i = 0; i < this.motionState[key].length; i++) {
if (this.motionState[key][i].event !== 'http') {
continue;
}
if (this.motionState[key][i].param === this.favoriteState['Motion']['ID']) {
actionNeeded = false;
}
}
if (actionNeeded) {
this.log.debug('We need to create a Schedule for the Motion Sensor!');
const motionArray = this.motionState[key];
motionArray.push({
event: 'http',
param: this.favoriteState['Motion']['ID'],
enabled: '1',
schedule: { weekdays: [{ from: '79200', to: '79199' }] },
});
const toCreate = { input: 'motion', param: '', output: motionArray };
try {
const createUrl = this.buildURL('schedule');
const response = await Axios.post(createUrl, toCreate);
if (response.status === 200) {
this.log.debug('Schedule for Motion Sensor set successfully!');
} else {
this.log.warn(
`There was an Error while setting the Motion schedule. (Statuscode: ${response.status}), (Statustext: ${response.statusText}`,
);
}
} catch (error) {
this.log.warn(`Error in setting Motion schedule: ${error}`);
}
}
}
}
/**
* Is called when adapter shuts down - callback has to be called under any circumstances!
*
* @param callback
*/
onUnload(callback) {
try {
this.setState('info.connection', { val: false, ack: true });
if (this.birdConCheck) {
this.clearTimeout(this.birdConCheck);
this.birdConCheck = null;
}
if (this.server) {
this.server.close(() => {
this.log.debug(`Server closed`);
});
}
for (let i = 0; i < this.sockets.length; i++) {
this.sockets[i].destroy();
}
this.sockets = [];
callback();
} catch (error) {
this.log.error(`Error in onUnload: ${error}`);
callback();
}
}
/**
* Wizard to find Doorbird
*
* @async
* @param msg
*/
async startWizard(msg) {
this.wizard = true;
const wizServer = dgram.createSocket('udp4');
wizServer.on('listening', () => {
const address = wizServer.address();
this.log.debug(`Wizard listening on IP: ${address.address} - UDP Port 6524`);
});
wizServer.on('message', (message, remote) => {
const text = message.toString('utf-8');
this.log.debug(`Wizard received message [${message.length}]: ${remote.address}: ${text}`);
if (remote.address && message.includes(':')) {
const parts = text.split(':', 3); // e.g. 847:jxazqm:1741003295
this.sendTo(
msg.from,
msg.command,
{
native: {
birdip: remote.address,
birdid: parts[1],
},
saveConfig: true,
error: null,
},
msg.callback,
);
this.wizard = false;
(() => {
if (this.wizardTimeout) {
this.clearTimeout(this.wizardTimeout);
this.wizardTimeout = null;
}
})();
if (this.wizardTimeout) {
this.clearTimeout(this.wizardTimeout);
}
wizServer.close();
}
});
wizServer.bind(6524);
if (this.wizardTimeout) {
this.clearTimeout(this.wizardTimeout);
}
this.wizardTimeout = this.setTimeout(() => {
wizServer.close();
this.wizard = false;
this.sendTo(
msg.from,
msg.command,
{
error: 'timeout',
},
msg.callback,
);
this.log.debug('Wizard timeout!');
}, 60000);
}
/**
* Turn on light
*
* @async
*/
async turnOnLightAsync() {
try {
const url = this.buildURL('light-on');
const response = await Axios.get(url);
if (response.status === 200) {
this.log.debug('Light successfully triggered!');
} else {
this.log.warn(`Could not trigger light. (Got Statuscode ${response.status})`);
}
} catch (error) {
this.log.warn(`Error in triggering Light: ${error}`);
}
}
/**
* Restart Doorbird
*
* @async
*/
async restartDoorbirdAsync() {
try {
const url = this.buildURL('restart');
const response = await Axios.get(url);
if (response.status === 200) {
this.log.debug('DoorBird Device is now restarting!!');
} else if (response.status === 503) {
this.log.warn('DoorBird denied restart! (Device is busy and cannot restart now!)');
} else {
this.log.warn(`DoorBird denied restart! (Statuscode: ${response.status})`);
}
} catch (error) {
this.log.warn(`DoorBird denied restart: ${error}`);
}
}
/**
* Trigger Relays
*
* @async
* @param comp
*/
async relaysAsync(comp) {
try {
const url = `http://${this.config.birdip}/bha-api/open-door.cgi?http-user=${
this.config.birduser
}&http-password=${this.config.birdpw}&r=${comp}`;
const response = await Axios.get(url);
if (response.status === 200) {
this.log.debug(`Relay ${comp} triggered successfully!`);
} else if (response.status === 204) {
this.log.warn(`Could not trigger relay ${comp}! (Insufficient permissions)`);
} else {
this.log.warn(`Could not trigger relay ${comp}. (Got Statuscode ${response.status})`);
}
} catch (error) {
this.log.warn(`Error in triggering Relay: ${error}`);
}
}
/**
* Download File from Doorbird
*
* @async
* @param cmd
*/
async downloadFileAsync(cmd) {
try {
const url = this.buildURL('image');
const response = await Axios.get(url, { responseType: 'stream' });
const chunks = [];
response.data.on('data', chunk => {
chunks.push(chunk);
});
response.data.on('end', async () => {
const fileData = Buffer.concat(chunks);
await this.writeFileAsync(this.namespace, `${cmd}_1.jpg`, fileData);
this.log.debug('Snapshot saved successfully!');
});
} catch (error) {
this.log.warn(`Error in downloading file: ${error}`);
}
}
/**
* Create datapoint and update datapoints
*
* @async
* @param dp path to datapoint
* @param type type of object
* @param common common part of object
* @param [foreign] set adapter states = false; set foreign states = true
*/
async createObjectsAsync(dp, type, common, foreign) {
const obj = !foreign ? await this.getObjectAsync(dp) : await this.getForeignObjectAsync(dp);
if (!obj) {
const obj = {
_id: dp,
type: type,
common: common,
native: {},
};
await (foreign ? this.setForeignObjectAsync(dp, obj) : this.setObjectAsync(dp, obj));
this.log.debug(`Object: ${dp} created.`);
} else {
if (JSON.stringify(obj.common) !== JSON.stringify(common) || !('native' in obj)) {
obj.common = common;
obj.native = obj.native ?? {};
await (foreign ? this.setForeignObjectAsync(dp, obj) : this.setObjectAsync(dp, obj));
this.log.debug(`Object: ${dp} updated.`);
}
}
}
/**
* Migrate versions above 1.0.1
*
* @async
*/
async migrateAsync() {
try {
let migrate = false;
const arrayOfStates = await this.getStatesOfAsync();
for (const obj of arrayOfStates) {
if (obj._id.includes('.snapshot') && obj.type === 'state') {
await this.delObjectAsync(obj._id);
migrate = true;
}
}
if (migrate) {
this.log.info(`Version migrated. Snapshot states deleted.`);
}
} catch (error) {
this.log.warn(`Error on migrate. Error: ${error}`);
}
}
}
if (require.main !== module) {
// Export the constructor in compact mode
/**
* @param [options]
*/
module.exports = options => new Doorbird(options);
} else {
// otherwise start the instance directly
new Doorbird();
}