mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-08-22 18:49:59 +00:00
Allow devices and groups to be specified in separate file. https://github.com/Koenkk/zigbee2mqtt/issues/1148
This commit is contained in:
+12
-5
@@ -2,14 +2,21 @@ const path = require('path');
|
||||
|
||||
let dataPath = null;
|
||||
|
||||
if (process.env.ZIGBEE2MQTT_DATA) {
|
||||
dataPath = process.env.ZIGBEE2MQTT_DATA;
|
||||
} else {
|
||||
dataPath = path.join(__dirname, '..', '..', 'data');
|
||||
dataPath = path.normalize(dataPath);
|
||||
function load() {
|
||||
if (process.env.ZIGBEE2MQTT_DATA) {
|
||||
dataPath = process.env.ZIGBEE2MQTT_DATA;
|
||||
} else {
|
||||
dataPath = path.join(__dirname, '..', '..', 'data');
|
||||
dataPath = path.normalize(dataPath);
|
||||
}
|
||||
}
|
||||
|
||||
load();
|
||||
|
||||
module.exports = {
|
||||
joinPath: (file) => path.join(dataPath, file),
|
||||
getPath: () => dataPath,
|
||||
|
||||
// For test only.
|
||||
_reload: () => load(),
|
||||
};
|
||||
|
||||
+42
-2
@@ -71,11 +71,46 @@ function writeRead() {
|
||||
}
|
||||
|
||||
function write() {
|
||||
fs.writeFileSync(file, yaml.safeDump(settings));
|
||||
const toWrite = objectAssignDeep.noMutate(settings);
|
||||
|
||||
// Read settings to check if we have to split devices/groups into seperate file.
|
||||
const actual = readYaml(file);
|
||||
if (typeof actual.devices === 'string') {
|
||||
writeYaml(data.joinPath(actual.devices), settings.devices);
|
||||
toWrite.devices = actual.devices;
|
||||
}
|
||||
|
||||
if (typeof actual.groups === 'string') {
|
||||
writeYaml(data.joinPath(actual.groups), settings.groups);
|
||||
toWrite.groups = actual.groups;
|
||||
}
|
||||
|
||||
writeYaml(file, toWrite);
|
||||
}
|
||||
|
||||
function readYaml(file) {
|
||||
return yaml.safeLoad(fs.readFileSync(file, 'utf8'));
|
||||
}
|
||||
|
||||
function writeYaml(file, content) {
|
||||
fs.writeFileSync(file, yaml.safeDump(content));
|
||||
}
|
||||
|
||||
function read() {
|
||||
return yaml.safeLoad(fs.readFileSync(file, 'utf8'));
|
||||
const s = readYaml(file);
|
||||
|
||||
// Read devices/groups configuration from separate file.
|
||||
if (typeof s.devices === 'string') {
|
||||
const file = data.joinPath(s.devices);
|
||||
s.devices = fs.existsSync(file) ? readYaml(file) : null;
|
||||
}
|
||||
|
||||
if (typeof s.groups === 'string') {
|
||||
const file = data.joinPath(s.groups);
|
||||
s.groups = fs.existsSync(file) ? readYaml(file) : null;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
function set(path, value) {
|
||||
@@ -178,4 +213,9 @@ module.exports = {
|
||||
changeDeviceOptions: (ieeeAddr, options) => changeDeviceOptions(ieeeAddr, options),
|
||||
|
||||
addOnChangeHandler: (handler) => onChangeHandlers.push(handler),
|
||||
|
||||
// For test
|
||||
_getDefaults: () => {
|
||||
return objectAssignDeep.noMutate(defaults);
|
||||
},
|
||||
};
|
||||
|
||||
+2
-1
@@ -57,6 +57,7 @@
|
||||
"mocha": "*",
|
||||
"nyc": "*",
|
||||
"proxyquire": "*",
|
||||
"sinon": "*"
|
||||
"sinon": "*",
|
||||
"tmp": "*"
|
||||
}
|
||||
}
|
||||
|
||||
+6
-4
@@ -1,21 +1,23 @@
|
||||
const chai = require('chai');
|
||||
const proxyquire = require('proxyquire').noPreserveCache();
|
||||
const data = () => proxyquire('../lib/util/data.js', {});
|
||||
const data = require('../lib/util/data.js');
|
||||
const path = require('path');
|
||||
|
||||
describe('Data', () => {
|
||||
describe('Get path', () => {
|
||||
it('Should return correct path', () => {
|
||||
const expected = path.normalize(path.join(__dirname, '..', 'data'));
|
||||
const actual = data().getPath();
|
||||
const actual = data.getPath();
|
||||
chai.assert.strictEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('Should return correct path when ZIGBEE2MQTT_DATA set', () => {
|
||||
const expected = path.join('var', 'zigbee2mqtt');
|
||||
process.env.ZIGBEE2MQTT_DATA = expected;
|
||||
const actual = data().getPath();
|
||||
data._reload();
|
||||
const actual = data.getPath();
|
||||
chai.assert.strictEqual(actual, expected);
|
||||
delete process.env.ZIGBEE2MQTT_DATA;
|
||||
data._reload();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
const chai = require('chai');
|
||||
const sinon = require('sinon');
|
||||
const data = require('../lib/util/data');
|
||||
const proxyquire = require('proxyquire').noPreserveCache();
|
||||
const settingsProxy = () => proxyquire('../lib/util/settings.js', {});
|
||||
const tmp = require('tmp');
|
||||
const sandbox = sinon.createSandbox();
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const yaml = require('js-yaml');
|
||||
const rimraf = require('rimraf');
|
||||
|
||||
describe('Settings', () => {
|
||||
let dir = null;
|
||||
let settings = null;
|
||||
let configurationFile = null;
|
||||
|
||||
const write = (file, json) => {
|
||||
fs.writeFileSync(file, yaml.safeDump(json));
|
||||
};
|
||||
|
||||
const read = (file) => {
|
||||
return yaml.safeLoad(fs.readFileSync(file, 'utf8'));
|
||||
};
|
||||
|
||||
const setup = (configuration) => {
|
||||
configurationFile = path.join(dir.name, 'configuration.yaml');
|
||||
write(configurationFile, configuration);
|
||||
settings = settingsProxy();
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
dir = tmp.dirSync();
|
||||
sandbox.stub(data, 'joinPath').callsFake((file) => {
|
||||
return path.join(dir.name, file);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
rimraf.sync(dir.name);
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('Settings', () => {
|
||||
it('Should return default settings', () => {
|
||||
setup({});
|
||||
const s = settings.get();
|
||||
chai.assert.deepEqual(s, settings._getDefaults());
|
||||
});
|
||||
|
||||
it('Should return settings', () => {
|
||||
setup({permit_join: true});
|
||||
const s = settings.get();
|
||||
const expected = settings._getDefaults();
|
||||
expected.permit_join = true;
|
||||
chai.assert.deepEqual(s, expected);
|
||||
});
|
||||
|
||||
it('Should add devices', () => {
|
||||
setup({});
|
||||
settings.addDevice('0x12345678');
|
||||
|
||||
const actual = read(configurationFile);
|
||||
const expected = {
|
||||
devices: {
|
||||
'0x12345678': {
|
||||
friendly_name: '0x12345678',
|
||||
retain: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
chai.assert.deepEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('Should read devices', () => {
|
||||
const content = {
|
||||
devices: {
|
||||
'0x12345678': {
|
||||
friendly_name: '0x12345678',
|
||||
retain: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
setup(content);
|
||||
|
||||
const device = settings.getDevice('0x12345678');
|
||||
const expected = {
|
||||
friendly_name: '0x12345678',
|
||||
retain: false,
|
||||
};
|
||||
|
||||
chai.assert.deepEqual(device, expected);
|
||||
});
|
||||
|
||||
it('Should read devices form a seperate file', () => {
|
||||
const contentConfiguration = {
|
||||
devices: 'devices.yaml',
|
||||
};
|
||||
|
||||
const contentDevices = {
|
||||
'0x12345678': {
|
||||
friendly_name: '0x12345678',
|
||||
retain: false,
|
||||
},
|
||||
};
|
||||
|
||||
write(path.join(dir.name, 'devices.yaml'), contentDevices);
|
||||
setup(contentConfiguration);
|
||||
|
||||
const device = settings.getDevice('0x12345678');
|
||||
const expected = {
|
||||
friendly_name: '0x12345678',
|
||||
retain: false,
|
||||
};
|
||||
|
||||
chai.assert.deepEqual(device, expected);
|
||||
});
|
||||
|
||||
it('Should add devices to a seperate file', () => {
|
||||
const contentConfiguration = {
|
||||
devices: 'devices.yaml',
|
||||
};
|
||||
|
||||
const contentDevices = {
|
||||
'0x12345678': {
|
||||
friendly_name: '0x12345678',
|
||||
retain: false,
|
||||
},
|
||||
};
|
||||
|
||||
const devicesFile = path.join(dir.name, 'devices.yaml');
|
||||
write(devicesFile, contentDevices);
|
||||
setup(contentConfiguration);
|
||||
|
||||
settings.addDevice('0x1234');
|
||||
|
||||
chai.assert.deepEqual(read(configurationFile), {devices: 'devices.yaml'});
|
||||
|
||||
const expected = {
|
||||
'0x12345678': {
|
||||
friendly_name: '0x12345678',
|
||||
retain: false,
|
||||
},
|
||||
'0x1234': {
|
||||
friendly_name: '0x1234',
|
||||
retain: false,
|
||||
},
|
||||
};
|
||||
|
||||
chai.assert.deepEqual(read(devicesFile), expected);
|
||||
});
|
||||
|
||||
it('Should add devices to a seperate file if devices.yaml doesnt exist', () => {
|
||||
const contentConfiguration = {
|
||||
devices: 'devices.yaml',
|
||||
};
|
||||
|
||||
const devicesFile = path.join(dir.name, 'devices.yaml');
|
||||
setup(contentConfiguration);
|
||||
|
||||
settings.addDevice('0x1234');
|
||||
|
||||
chai.assert.deepEqual(read(configurationFile), {devices: 'devices.yaml'});
|
||||
|
||||
const expected = {
|
||||
'0x1234': {
|
||||
friendly_name: '0x1234',
|
||||
retain: false,
|
||||
},
|
||||
};
|
||||
|
||||
chai.assert.deepEqual(read(devicesFile), expected);
|
||||
});
|
||||
|
||||
it('Should add and remove devices to a seperate file if devices.yaml doesnt exist', () => {
|
||||
const contentConfiguration = {
|
||||
devices: 'devices.yaml',
|
||||
};
|
||||
|
||||
const devicesFile = path.join(dir.name, 'devices.yaml');
|
||||
setup(contentConfiguration);
|
||||
|
||||
settings.addDevice('0x1234');
|
||||
chai.assert.deepEqual(read(configurationFile), {devices: 'devices.yaml'});
|
||||
|
||||
settings.removeDevice('0x1234');
|
||||
chai.assert.deepEqual(read(configurationFile), {devices: 'devices.yaml'});
|
||||
|
||||
chai.assert.deepEqual(read(devicesFile), {});
|
||||
});
|
||||
|
||||
it('Should read groups', () => {
|
||||
const content = {
|
||||
groups: {
|
||||
'1': {
|
||||
friendly_name: '123',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
setup(content);
|
||||
|
||||
const group = settings.getGroup('1');
|
||||
const expected = {
|
||||
friendly_name: '123',
|
||||
};
|
||||
|
||||
chai.assert.deepEqual(group, expected);
|
||||
});
|
||||
|
||||
it('Should read groups form a seperate file', () => {
|
||||
const contentConfiguration = {
|
||||
groups: 'groups.yaml',
|
||||
};
|
||||
|
||||
const contentGroups = {
|
||||
'1': {
|
||||
friendly_name: '123',
|
||||
},
|
||||
};
|
||||
|
||||
write(path.join(dir.name, 'groups.yaml'), contentGroups);
|
||||
setup(contentConfiguration);
|
||||
|
||||
const group = settings.getGroup('1');
|
||||
const expected = {
|
||||
friendly_name: '123',
|
||||
};
|
||||
|
||||
chai.assert.deepEqual(group, expected);
|
||||
});
|
||||
|
||||
it('Combine everything! groups and devices from separte file :)', () => {
|
||||
const contentConfiguration = {
|
||||
devices: 'devices.yaml',
|
||||
groups: 'groups.yaml',
|
||||
};
|
||||
|
||||
const contentGroups = {
|
||||
'1': {
|
||||
friendly_name: '123',
|
||||
},
|
||||
};
|
||||
|
||||
write(path.join(dir.name, 'groups.yaml'), contentGroups);
|
||||
setup(contentConfiguration);
|
||||
|
||||
const devicesFile = path.join(dir.name, 'devices.yaml');
|
||||
setup(contentConfiguration);
|
||||
|
||||
const expectedConfiguration = {
|
||||
devices: 'devices.yaml',
|
||||
groups: 'groups.yaml',
|
||||
};
|
||||
|
||||
chai.assert.deepEqual(read(configurationFile), expectedConfiguration);
|
||||
|
||||
settings.addDevice('0x1234');
|
||||
|
||||
chai.assert.deepEqual(read(configurationFile), expectedConfiguration);
|
||||
|
||||
const expectedDevice = {
|
||||
'0x1234': {
|
||||
friendly_name: '0x1234',
|
||||
retain: false,
|
||||
},
|
||||
};
|
||||
|
||||
chai.assert.deepEqual(read(devicesFile), expectedDevice);
|
||||
|
||||
const group = settings.getGroup('1');
|
||||
const expectedGroup = {
|
||||
friendly_name: '123',
|
||||
};
|
||||
|
||||
chai.assert.deepEqual(group, expectedGroup);
|
||||
|
||||
chai.assert.deepEqual(read(configurationFile), expectedConfiguration);
|
||||
|
||||
const expectedDevice2 = {
|
||||
friendly_name: '0x1234',
|
||||
retain: false,
|
||||
};
|
||||
|
||||
chai.assert.deepEqual(settings.getDevice('0x1234'), expectedDevice2);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user