zigbee2mqtt/lib/util/settings.js

313 lines
8.4 KiB
JavaScript
Raw Normal View History

2018-05-11 20:04:15 -07:00
const data = require('./data');
2018-05-12 01:58:06 -07:00
const file = data.joinPath('configuration.yaml');
const objectAssignDeep = require(`object-assign-deep`);
const path = require('path');
2019-03-08 11:30:00 -07:00
const fs = require('./fs');
const onChangeHandlers = [];
const defaults = {
permit_join: false,
2018-11-16 12:23:11 -07:00
mqtt: {
include_device_information: false,
},
groups: {},
device_options: {},
2019-02-04 10:39:45 -07:00
experimental: {
livolo: false,
// json or attribute
output: 'json',
2019-02-04 10:39:45 -07:00
},
advanced: {
log_directory: path.join(data.getPath(), 'log', '%TIMESTAMP%'),
log_level: process.env.DEBUG ? 'debug' : 'info',
soft_reset_timeout: 0,
2018-11-16 12:23:11 -07:00
pan_id: 0x1a62,
ext_pan_id: [0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD],
2018-11-16 12:23:11 -07:00
channel: 11,
baudrate: 115200,
rtscts: true,
// Availability timeout in seconds, disabled by default.
availability_timeout: 0,
2019-02-02 10:10:25 -07:00
availability_blacklist: [],
2018-11-16 12:23:11 -07:00
/**
* Home Assistant requires ALL attributes to be present in ALL MQTT messages send by the device.
* https://community.home-assistant.io/t/missing-value-with-mqtt-only-last-data-set-is-shown/47070/9
*
* Therefore zigbee2mqtt BY DEFAULT caches all values and resend it with every message.
* advanced.cache_state in configuration.yaml allows to configure this.
2019-02-12 13:39:37 -07:00
* https://www.zigbee2mqtt.io/configuration/configuration.html
2018-11-16 12:23:11 -07:00
*/
cache_state: true,
/**
* Add a last_seen attribute to mqtt messages, contains date/time of zigbee message arrival
* "ISO_8601": ISO 8601 format
* "ISO_8601_local": Local ISO 8601 format (instead of UTC-based)
* "epoch": milliseconds elapsed since the UNIX epoch
* "disable": no last_seen attribute (default)
*/
last_seen: 'disable',
// Optional: Add an elapsed attribute to MQTT messages, contains milliseconds since the previous msg
elapsed: false,
/**
* https://github.com/Koenkk/zigbee2mqtt/issues/685#issuecomment-449112250
*
* Network key will serve as the encryption key of your network.
* Changing this will require you to repair your devices.
*/
network_key: [1, 3, 5, 7, 9, 11, 13, 15, 0, 2, 4, 6, 8, 10, 12, 13],
2019-02-26 12:21:35 -07:00
/**
* Enables reporting feature
*/
report: false,
/**
* Home Assistant discovery topic
*/
homeassistant_discovery_topic: 'homeassistant',
},
};
2018-05-12 01:58:06 -07:00
2019-03-08 08:27:09 -07:00
let _settings;
function getSettings() {
if (!_settings) {
_settings = read();
}
if (_settings.hasOwnProperty('advanced') && !_settings.advanced) {
delete _settings.advanced;
}
return _settings;
}
2018-04-18 09:25:40 -07:00
2018-05-17 08:20:46 -07:00
function writeRead() {
2018-05-12 06:54:02 -07:00
write();
2019-03-08 08:27:09 -07:00
_settings = read();
onChangeHandlers.forEach((handler) => handler());
2018-05-12 06:54:02 -07:00
}
2018-04-18 09:25:40 -07:00
function write() {
2019-03-08 08:27:09 -07:00
const settings = getSettings();
const toWrite = objectAssignDeep.noMutate(settings);
2019-03-09 06:36:06 -07:00
// Read settings to check if we have to split devices/groups into separate file.
2019-03-08 11:30:00 -07:00
const actual = fs.readYaml(file);
if (typeof actual.devices === 'string') {
2019-03-08 11:30:00 -07:00
fs.writeYaml(data.joinPath(actual.devices), settings.devices);
toWrite.devices = actual.devices;
}
if (typeof actual.groups === 'string') {
2019-03-08 11:30:00 -07:00
fs.writeYaml(data.joinPath(actual.groups), settings.groups);
toWrite.groups = actual.groups;
}
2019-03-08 11:30:00 -07:00
fs.writeYaml(file, toWrite);
}
function read() {
2019-03-08 11:30:00 -07:00
const s = fs.readYaml(file);
// Read devices/groups configuration from separate file.
if (typeof s.devices === 'string') {
const file = data.joinPath(s.devices);
2019-03-08 11:30:00 -07:00
s.devices = fs.readYamlIfExists(file);
}
if (typeof s.groups === 'string') {
const file = data.joinPath(s.groups);
2019-03-08 11:30:00 -07:00
s.groups = fs.readYamlIfExists(file);
}
return s;
}
2019-02-18 11:46:19 -07:00
function set(path, value) {
2019-03-08 08:27:09 -07:00
let obj = getSettings();
2019-02-18 11:46:19 -07:00
for (let i = 0; i < path.length; i++) {
const key = path[i];
if (i === path.length - 1) {
obj[key] = value;
} else {
if (!obj[key]) {
obj[key] = {};
}
obj = obj[key];
}
}
writeRead();
}
2019-03-08 09:30:38 -07:00
const getDevices = () => getSettings().devices || [];
2019-03-08 08:27:09 -07:00
2019-03-08 09:30:38 -07:00
const getDevice = (ieeeAddr) => getDevices()[ieeeAddr];
const getGroups = () => getSettings().groups || [];
const getGroup = (ID) => getGroups()[ID];
2019-03-08 08:27:09 -07:00
2018-11-16 12:23:11 -07:00
function addDevice(ieeeAddr) {
2019-03-08 08:27:09 -07:00
const settings = getSettings();
if (!settings.devices) {
settings.devices = {};
2018-04-25 10:29:03 -07:00
}
2018-11-16 12:23:11 -07:00
settings.devices[ieeeAddr] = {friendly_name: ieeeAddr, retain: false};
2018-05-17 08:20:46 -07:00
writeRead();
2018-04-25 10:29:03 -07:00
}
2018-04-18 09:25:40 -07:00
2018-11-16 12:23:11 -07:00
function removeDevice(ieeeAddr) {
2019-03-08 08:27:09 -07:00
const settings = getSettings();
2019-03-08 09:30:38 -07:00
if (!settings.devices || !settings.devices[ieeeAddr]) return;
delete settings.devices[ieeeAddr];
writeRead();
2018-06-07 10:41:11 -07:00
}
function addGroup(groupName) {
const settings = getSettings();
if (!settings.groups) {
settings.groups = {};
}
let ID = '1';
while (settings.groups.hasOwnProperty(ID)) {
ID = (Number.parseInt(ID) + 1).toString();
}
settings.groups[ID] = {friendly_name: groupName};
writeRead();
return true;
}
function removeGroup(name) {
const settings = getSettings();
if (!settings.groups) return;
const ID = Object.keys(settings.groups).find((key) => {
return settings.groups[key].friendly_name === name;
});
if (ID) {
delete settings.groups[ID];
writeRead();
return true;
} else {
return false;
}
}
2018-11-16 12:23:11 -07:00
function getIeeeAddrByFriendlyName(friendlyName) {
2019-03-08 09:30:38 -07:00
const entry = Object.entries(getDevices()).find(([ieeeAddr, device]) =>
device.friendly_name === friendlyName
);
2019-03-08 09:30:38 -07:00
return entry && entry[0];
}
function getGroupIDByFriendlyName(friendlyName) {
2019-03-08 09:30:38 -07:00
const entry = Object.entries(getGroups()).find(([ID, group]) =>
group.friendly_name === friendlyName
);
2019-03-08 09:30:38 -07:00
return entry && entry[0];
}
function changeDeviceOptions(ieeeAddr, newOptions) {
2019-03-08 08:27:09 -07:00
const settings = getSettings();
const currentOptions = settings.devices[ieeeAddr];
if (!currentOptions) {
return;
}
Object.keys(currentOptions).forEach((key) => {
if (newOptions[key]) {
currentOptions[key] = newOptions[key];
}
});
writeRead();
}
2018-07-24 09:25:16 -07:00
function changeFriendlyName(old, new_) {
2019-03-08 08:27:09 -07:00
const settings = getSettings();
2018-11-16 12:23:11 -07:00
const ieeeAddr = getIeeeAddrByFriendlyName(old);
2018-07-24 09:25:16 -07:00
2018-11-16 12:23:11 -07:00
if (!ieeeAddr) {
2018-07-24 09:25:16 -07:00
return false;
2018-07-21 09:15:56 -07:00
}
2018-07-24 09:25:16 -07:00
2018-11-16 12:23:11 -07:00
settings.devices[ieeeAddr].friendly_name = new_;
2018-07-21 09:15:56 -07:00
writeRead();
2018-07-24 09:25:16 -07:00
return true;
2018-07-21 09:15:56 -07:00
}
// An entity can be either a group or a device.
function resolveEntity(ID) {
let type = null;
let friendlyName = null;
if (module.exports.getIeeeAddrByFriendlyName(ID)) {
// Check if the ID is a friendly_name of a device.
friendlyName = ID;
ID = module.exports.getIeeeAddrByFriendlyName(ID);
type = 'device';
} else if (module.exports.getGroupIDByFriendlyName(ID)) {
// Check if the ID is a friendly_name of a group.
friendlyName = ID;
ID = Number(module.exports.getGroupIDByFriendlyName(ID));
type = 'group';
} else if (module.exports.getGroup(ID)) {
friendlyName = module.exports.getGroup(ID).friendly_name;
ID = Number(ID);
type = 'group';
} else {
// By default it is a device with ID as ID.
type = 'device';
const device = module.exports.getDevice(ID);
friendlyName = device ? device.friendly_name : ID;
}
return {ID, type, friendlyName};
}
2018-04-18 09:25:40 -07:00
module.exports = {
2019-03-08 08:27:09 -07:00
get: () => objectAssignDeep.noMutate(defaults, getSettings()),
2018-04-18 11:53:22 -07:00
write: () => write(),
2019-02-18 11:46:19 -07:00
set: (path, value) => set(path, value),
2018-11-16 12:23:11 -07:00
2019-03-08 08:27:09 -07:00
getDevice,
getGroup,
getGroups,
2019-03-08 08:27:09 -07:00
getDevices,
2018-11-16 12:23:11 -07:00
addDevice: (ieeeAddr) => addDevice(ieeeAddr),
removeDevice: (ieeeAddr) => removeDevice(ieeeAddr),
addGroup: (name) => addGroup(name),
removeGroup: (name) => removeGroup(name),
2018-11-16 12:23:11 -07:00
getIeeeAddrByFriendlyName: (friendlyName) => getIeeeAddrByFriendlyName(friendlyName),
getGroupIDByFriendlyName: (friendlyName) => getGroupIDByFriendlyName(friendlyName),
2018-07-24 09:25:16 -07:00
changeFriendlyName: (old, new_) => changeFriendlyName(old, new_),
changeDeviceOptions: (ieeeAddr, options) => changeDeviceOptions(ieeeAddr, options),
resolveEntity,
addOnChangeHandler: (handler) => onChangeHandlers.push(handler),
// For test
_getDefaults: () => {
return objectAssignDeep.noMutate(defaults);
},
2019-03-08 08:27:09 -07:00
_clear: () => _settings = undefined,
2018-05-17 08:20:46 -07:00
};