From fb6dd2e1d40515a924d14b0917978305b00dfb3c Mon Sep 17 00:00:00 2001 From: Fabian During Date: Thu, 4 Feb 2021 10:01:42 +0100 Subject: [PATCH 01/20] Move config of services to json Signed-off-by: Fabian During --- horme-common/src/types.ts | 9 ++++ reconf/config/automations/bedroomLamp.json | 8 +++ .../config/automations/bedroomSwitches.json | 16 ++++++ reconf/config/device-groups/lights.json | 3 ++ reconf/config/device-groups/switches.json | 3 ++ reconf/src/db.ts | 50 +++++++++++++---- reconf/src/service.ts | 54 ++++++++++--------- 7 files changed, 109 insertions(+), 34 deletions(-) create mode 100644 reconf/config/automations/bedroomLamp.json create mode 100644 reconf/config/automations/bedroomSwitches.json create mode 100644 reconf/config/device-groups/lights.json create mode 100644 reconf/config/device-groups/switches.json diff --git a/horme-common/src/types.ts b/horme-common/src/types.ts index 2ea82c3..5869235 100644 --- a/horme-common/src/types.ts +++ b/horme-common/src/types.ts @@ -66,6 +66,15 @@ export const ServiceConfig = Record({ args: Array(String), }); +/*export interface Automation { + type: String; + alias: String; + mainDevices: [String]; + replacementDevices: [String]; + room: String; + configMsg: String; +};*/ + export type ServiceConfig = Static; export const parseAs = >(r: R, msg: any): Static | undefined => { diff --git a/reconf/config/automations/bedroomLamp.json b/reconf/config/automations/bedroomLamp.json new file mode 100644 index 0000000..90276bc --- /dev/null +++ b/reconf/config/automations/bedroomLamp.json @@ -0,0 +1,8 @@ +[{ + "type": "ceiling-lamp", + "alias": "bed1", + "mainDevices": ["switch1"], + "replacementDevices": "switches", + "room": "bedroom", + "configMsg": "" +}] \ No newline at end of file diff --git a/reconf/config/automations/bedroomSwitches.json b/reconf/config/automations/bedroomSwitches.json new file mode 100644 index 0000000..1738892 --- /dev/null +++ b/reconf/config/automations/bedroomSwitches.json @@ -0,0 +1,16 @@ +[{ + "type": "light-switch", + "alias": "bri", + "mainDevices": [], + "replacementDevices": "", + "room": "bedroom", + "configMsg": "" +}, +{ + "type": "light-switch", + "alias": "fra", + "mainDevices": [], + "replacementDevices": "", + "room": "bedroom", + "configMsg": "" +}] \ No newline at end of file diff --git a/reconf/config/device-groups/lights.json b/reconf/config/device-groups/lights.json new file mode 100644 index 0000000..a8d4593 --- /dev/null +++ b/reconf/config/device-groups/lights.json @@ -0,0 +1,3 @@ +{ + "type": ["ceiling-lamp", "light-stip"] +} \ No newline at end of file diff --git a/reconf/config/device-groups/switches.json b/reconf/config/device-groups/switches.json new file mode 100644 index 0000000..347ef0f --- /dev/null +++ b/reconf/config/device-groups/switches.json @@ -0,0 +1,3 @@ +{ + "type": ["wall-switch", "remote-switch"] +} \ No newline at end of file diff --git a/reconf/src/db.ts b/reconf/src/db.ts index 69327f1..4831b43 100644 --- a/reconf/src/db.ts +++ b/reconf/src/db.ts @@ -1,25 +1,37 @@ import { ServiceType, Uuid } from './service'; +import fs from 'fs/promises'; +import { env as getEnv, util, ConfigMessage, Subscription, ServiceConfig, ServiceInfo, parseAs } from 'horme-common'; +import path from 'path'; export default { queryServiceSelection }; /** The array of selected service type and instances. */ export type ServiceSelection = [ServiceType, ServiceEntry[]][]; + +export type AutomationSelection = [ServiceType, ServiceEntry[]][]; /** Options for specifying which changes need to be made in the database. */ export type ConfigUpdates = { del: Uuid[]; }; /** The description of an un-instantiated service and its dependencies. */ export type ServiceEntry = { - uuid: Uuid; + /*uuid: Uuid; type: ServiceType; room: string | null; - depends: Uuid[]; + depends: Uuid[];*/ + type: string; + alias: string; + mainDevices: [string]; + replacementDevices: [string]; + room: string; + configMsg: string; }; /********** implementation ************************************************************************/ -async function queryServiceSelection(updates?: ConfigUpdates): Promise { - if (updates) { +async function queryServiceSelection(updates?: ConfigUpdates): Promise> { + var config = importConfig(); + /*if (updates) { if (updateCount === 0) { console.assert(updates.del[0] === 'fra'); config.set('light-switch', [bedroomSwitch1]); @@ -36,11 +48,31 @@ async function queryServiceSelection(updates?: ConfigUpdates): Promise> { + const testFolder = './config/automations/'; + const fs = require('fs'); + var test :Array = [] - return Array.from(config); + fs.readdirSync(testFolder).forEach((file: any) => { + let fullPath = path.join(testFolder, file); + console.log(fullPath); + console.log(file); + let config: Array = JSON.parse(fs.readFileSync(fullPath.toString(),'utf8')); + config.forEach((test1) => { + console.log(test1.type); + test.push(test1) + }) + + }); + return test } -const bedroomSwitch1: ServiceEntry = { +/*const bedroomSwitch1: ServiceEntry = { uuid: 'bri', room: 'bedroom', type: 'light-switch', @@ -75,10 +107,10 @@ const failureReasoner: ServiceEntry = { depends: [bedroomSwitch1.uuid, bedroomSwitch2.uuid], }; -const config: Map = new Map([ +const config: Map = new Map([ ['ceiling-lamp', [bedroomLamp]], ['light-switch', [bedroomSwitch1, bedroomSwitch2]], //['failure-reasoner', [failureReasoner]] -]); +]);*/ -let updateCount = 0; +//let updateCount = 0; diff --git a/reconf/src/service.ts b/reconf/src/service.ts index 723571d..999bf82 100644 --- a/reconf/src/service.ts +++ b/reconf/src/service.ts @@ -39,7 +39,7 @@ async function configureServices(): Promise { // instantiate all not yet instantiated services, insert them into global map const instantiated = await instantiateServices(result); // set and configure all service dependencies - await Promise.all(instantiated.map((args) => configureService(...args, true))); + await Promise.all(instantiated.map((args) => configureService(args, [] ,true))); } /** Removes the service with the given `uuid` and triggers a full service selection @@ -49,14 +49,15 @@ async function removeService(uuid: string): Promise { const reconfiguration = await db.queryServiceSelection({ del: [uuid] }); const previousServices = Array.from(services.values()); - const newServices = Array.from( + const newServices = reconfiguration + /*const newServices = Array.from( reconfiguration.flatMap(([_, instances]) => { - return instances.map((instance) => instance.uuid); + return instances.map((instance) => instance.alias); }) - ); + );*/ // determine services which are no longer present in updated service selection - const removals = previousServices.filter((prev) => !newServices.includes(prev.info.uuid)); + const removals = previousServices.filter((prev) => newServices.forEach((test) => test.alias != prev.info.uuid)) // remove all services no longer present in the new configuration and kill their respective // processes @@ -73,7 +74,7 @@ async function removeService(uuid: string): Promise { // configure all newly instantiated services and re-configure all changed services logger.info('initiating service reconfiguration...'); - await Promise.all(instantiatedServices.map((args) => configureService(...args))); + await Promise.all(instantiatedServices.map((args) => configureService(args, []))); } function cleanUp(): void { @@ -84,16 +85,19 @@ function cleanUp(): void { /** Instantiates all (not yet instantiated) services in the given `selection`. */ async function instantiateServices( - selection: ServiceSelection -): Promise<[ServiceHandle, Uuid[]][]> { - const promises = await Promise.all(selection.map(async ([type, selected]) => { - const file = await fs.readFile(`./config/services/${type}.json`); + selection: Array +): Promise> { + var ret :Array = [] + for (const elem of selection) { + const file = await fs.readFile(`./config/services/${elem.type}.json`); const config = parseAs(ServiceConfig, JSON.parse(file.toString())); if (!config) return []; - return await Promise.all(Array.from(selected.map(sel => instantiateService(sel, config)))); - })); - - return promises.flat(); + var handle = await instantiateService(elem, config); + for (let i = 0;i { - const handle = services.get(entry.uuid); +): Promise<[ServiceHandle]> { + const handle = services.get(entry.alias); if (handle === undefined) { const topic = buildTopic(entry); const proc = startService(entry, config, topic); @@ -115,16 +119,16 @@ async function instantiateService( topic, apartment: process.env.HORME_APARTMENT!, location: entry.room ?? 'global', - uuid: entry.uuid, + uuid: entry.alias, type: entry.type, sensor: null, }, }; - services.set(entry.uuid, handle); - return [handle, entry.depends]; + services.set(entry.alias, handle); + return [handle]; } else { - return [handle, entry.depends]; + return [handle]; } } @@ -187,7 +191,7 @@ function startService(entry: ServiceEntry, config: ServiceConfig, topic: string) '-t', '--rm', '--name', - serviceNamePrefix + entry.uuid, + serviceNamePrefix + entry.alias, '-e', 'HORME_LOG_LEVEL=' + env.logLevel, '-e', @@ -195,7 +199,7 @@ function startService(entry: ServiceEntry, config: ServiceConfig, topic: string) '-e', 'HORME_SERVICE_TOPIC=' + topic, '-e', - 'HORME_SERVICE_UUID=' + entry.uuid, + 'HORME_SERVICE_UUID=' + entry.alias, '--network', 'horme_default', config.image, @@ -204,7 +208,7 @@ function startService(entry: ServiceEntry, config: ServiceConfig, topic: string) const instance = spawn('docker', cmd); instance.stdout.on('data', (data: Buffer) => { - console.log(`\tfrom '${entry.type}/${chalk.underline(entry.uuid)}' (stdout):`); + console.log(`\tfrom '${entry.type}/${chalk.underline(entry.alias)}' (stdout):`); const lines = data.toString('utf-8').split('\n'); for (const line of lines) { console.log(`\t${line}`); @@ -212,7 +216,7 @@ function startService(entry: ServiceEntry, config: ServiceConfig, topic: string) }); instance.stderr.on('data', (data: Buffer) => { - console.log(`\tfrom '${entry.type}/${chalk.underline(entry.uuid)}' (stderr):`); + console.log(`\tfrom '${entry.type}/${chalk.underline(entry.alias)}' (stderr):`); const lines = data.toString('utf-8').split('\n'); for (const line of lines) { console.log(`\t${line}`); @@ -228,5 +232,5 @@ function buildTopic(entry: ServiceEntry): string { entry.room !== null ? `${process.env.HORME_APARTMENT}/${entry.room}` : `${process.env.HORME_APARTMENT}/global`; - return `${base}/${entry.type}${entry.uuid}`; + return `${base}/${entry.type}${entry.alias}`; } From 99c5439e953a2a5ef5a4208a9eb5b82fb09d7fd5 Mon Sep 17 00:00:00 2001 From: Fabian During Date: Wed, 10 Feb 2021 13:51:50 +0100 Subject: [PATCH 02/20] Config: Added device-groups and removed deprec. functions Signed-off-by: Fabian During --- reconf/src/db.ts | 46 ++++++++++++++++++++++++++++--------------- reconf/src/service.ts | 38 +++++++++++++++++++---------------- 2 files changed, 51 insertions(+), 33 deletions(-) diff --git a/reconf/src/db.ts b/reconf/src/db.ts index 4831b43..768e6df 100644 --- a/reconf/src/db.ts +++ b/reconf/src/db.ts @@ -3,7 +3,7 @@ import fs from 'fs/promises'; import { env as getEnv, util, ConfigMessage, Subscription, ServiceConfig, ServiceInfo, parseAs } from 'horme-common'; import path from 'path'; -export default { queryServiceSelection }; +export default { DataToDB }; /** The array of selected service type and instances. */ export type ServiceSelection = [ServiceType, ServiceEntry[]][]; @@ -29,9 +29,9 @@ export type ServiceEntry = { /********** implementation ************************************************************************/ -async function queryServiceSelection(updates?: ConfigUpdates): Promise> { - var config = importConfig(); - /*if (updates) { +/*async function queryServiceSelection(updates?: ConfigUpdates): Promise> { + var config = importAutomations(); + if (updates) { if (updateCount === 0) { console.assert(updates.del[0] === 'fra'); config.set('light-switch', [bedroomSwitch1]); @@ -48,28 +48,42 @@ async function queryServiceSelection(updates?: ConfigUpdates): Promise> { - const testFolder = './config/automations/'; +async function DataToDB() { + importAutomations(); + importDeviceGroups(); +} + +async function importAutomations() { + const automationFolder = './config/automations/'; + const fs = require('fs'); + + fs.readdirSync(automationFolder).forEach((file: any) => { + let fullPath = path.join(automationFolder, file); + let config: Array = JSON.parse(fs.readFileSync(fullPath.toString(),'utf8')); + config.forEach((test1) => { + console.log(test1.type); + // Add automation to DB + }) + }); +} + +async function importDeviceGroups() { + const deviceGroupsFolder = './config/device-groups/'; const fs = require('fs'); - var test :Array = [] - fs.readdirSync(testFolder).forEach((file: any) => { - let fullPath = path.join(testFolder, file); - console.log(fullPath); - console.log(file); + fs.readdirSync(deviceGroupsFolder).forEach((file: any) => { + let fullPath = path.join(deviceGroupsFolder, file); let config: Array = JSON.parse(fs.readFileSync(fullPath.toString(),'utf8')); config.forEach((test1) => { console.log(test1.type); - test.push(test1) + // Add device group to DB }) - }); - return test } /*const bedroomSwitch1: ServiceEntry = { diff --git a/reconf/src/service.ts b/reconf/src/service.ts index 999bf82..48e2f02 100644 --- a/reconf/src/service.ts +++ b/reconf/src/service.ts @@ -35,9 +35,12 @@ const serviceNamePrefix = 'horme-'; /** Instantiates and configures the set of services selected from the database. */ async function configureServices(): Promise { // query current service selection from database - const result = await db.queryServiceSelection(); + // Only need to be done one per execution + //const result = await db.queryServiceSelection(); + // instantiate all not yet instantiated services, insert them into global map - const instantiated = await instantiateServices(result); + //Dont need services. Can be extracted from database. + const instantiated = await instantiateServices(); // set and configure all service dependencies await Promise.all(instantiated.map((args) => configureService(args, [] ,true))); } @@ -46,10 +49,11 @@ async function configureServices(): Promise { * and configuration update. */ async function removeService(uuid: string): Promise { // retrieve updated service selection from database - const reconfiguration = await db.queryServiceSelection({ del: [uuid] }); + //TODO + //const reconfiguration = await db.queryServiceSelection({ del: [uuid] }); const previousServices = Array.from(services.values()); - const newServices = reconfiguration + //const newServices = reconfiguration /*const newServices = Array.from( reconfiguration.flatMap(([_, instances]) => { return instances.map((instance) => instance.alias); @@ -57,24 +61,25 @@ async function removeService(uuid: string): Promise { );*/ // determine services which are no longer present in updated service selection - const removals = previousServices.filter((prev) => newServices.forEach((test) => test.alias != prev.info.uuid)) + //const removals = previousServices.filter((prev) => newServices.forEach((test) => test.alias != prev.info.uuid)) // remove all services no longer present in the new configuration and kill their respective // processes - for (const service of removals) { - logger.warn('killing process of service ' + chalk.underline(service.info.uuid)); + /*for (const service of removals) { + logger.warn('killing process of service ' + chalk.underline(service.info.uuid)); - execSync(`docker stop ${serviceNamePrefix}${service.info.uuid}`); - execSync(`docker rm ${serviceNamePrefix}${service.info.uuid}`); - services.delete(service.info.uuid); - } + execSync(`docker stop ${serviceNamePrefix}${service.info.uuid}`); + execSync(`docker rm ${serviceNamePrefix}${service.info.uuid}`); + services.delete(service.info.uuid); + }*/ // instantiate all new services - const instantiatedServices = await instantiateServices(reconfiguration); + //TODO + //const instantiatedServices = await instantiateServices(reconfiguration); // configure all newly instantiated services and re-configure all changed services logger.info('initiating service reconfiguration...'); - await Promise.all(instantiatedServices.map((args) => configureService(args, []))); + //await Promise.all(instantiatedServices.map((args) => configureService(args, []))); } function cleanUp(): void { @@ -84,11 +89,9 @@ function cleanUp(): void { } /** Instantiates all (not yet instantiated) services in the given `selection`. */ -async function instantiateServices( - selection: Array -): Promise> { +async function instantiateServices(): Promise> { var ret :Array = [] - for (const elem of selection) { + /*for (const elem of selection) { const file = await fs.readFile(`./config/services/${elem.type}.json`); const config = parseAs(ServiceConfig, JSON.parse(file.toString())); if (!config) return []; @@ -97,6 +100,7 @@ async function instantiateServices( ret.push(handle[i]) } } + */ return ret } From 98404ed9f3379ae7ebce2532f4deac1064ba1304 Mon Sep 17 00:00:00 2001 From: Fabian During Date: Wed, 10 Feb 2021 15:25:37 +0100 Subject: [PATCH 03/20] Reconf: Fixed device-group import Signed-off-by: Fabian During --- reconf/config/device-groups/lights.json | 9 ++++++--- reconf/config/device-groups/switches.json | 9 ++++++--- reconf/src/app.ts | 2 ++ reconf/src/db.ts | 22 ++++++++++++++++------ 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/reconf/config/device-groups/lights.json b/reconf/config/device-groups/lights.json index a8d4593..5e6d581 100644 --- a/reconf/config/device-groups/lights.json +++ b/reconf/config/device-groups/lights.json @@ -1,3 +1,6 @@ -{ - "type": ["ceiling-lamp", "light-stip"] -} \ No newline at end of file +[ + { + "name": "lights", + "types": ["ceiling-lamp", "light-stip"] + } +] \ No newline at end of file diff --git a/reconf/config/device-groups/switches.json b/reconf/config/device-groups/switches.json index 347ef0f..39fdc8d 100644 --- a/reconf/config/device-groups/switches.json +++ b/reconf/config/device-groups/switches.json @@ -1,3 +1,6 @@ -{ - "type": ["wall-switch", "remote-switch"] -} \ No newline at end of file +[ + { + "name": "switches", + "types": ["wall-switch", "remote-switch"] + } +] \ No newline at end of file diff --git a/reconf/src/app.ts b/reconf/src/app.ts index b2c8ab6..3b060fd 100644 --- a/reconf/src/app.ts +++ b/reconf/src/app.ts @@ -3,6 +3,7 @@ import 'source-map-support/register'; import { env as getEnv, util } from 'horme-common'; import fail from './fail'; import srv from './service'; +import db from './db'; const env = getEnv.readEnvironment('reconf'); const logger = util.logger; @@ -20,6 +21,7 @@ main().catch((err) => util.abort(err)); async function main() { logger.setLogLevel(env.logLevel); + await db.DataToDB(); await fail.setupFailureListener(); await srv.configureServices(); logger.info('initial configuration instantiated, listening...'); diff --git a/reconf/src/db.ts b/reconf/src/db.ts index 768e6df..5c9ed37 100644 --- a/reconf/src/db.ts +++ b/reconf/src/db.ts @@ -8,6 +8,11 @@ export default { DataToDB }; /** The array of selected service type and instances. */ export type ServiceSelection = [ServiceType, ServiceEntry[]][]; +export type DeviceGroup = { + name: string; + types: [string]; +}; + export type AutomationSelection = [ServiceType, ServiceEntry[]][]; /** Options for specifying which changes need to be made in the database. */ export type ConfigUpdates = { @@ -27,6 +32,8 @@ export type ServiceEntry = { configMsg: string; }; +const logger = util.logger; + /********** implementation ************************************************************************/ /*async function queryServiceSelection(updates?: ConfigUpdates): Promise> { @@ -59,6 +66,7 @@ async function DataToDB() { } async function importAutomations() { + logger.info('Import external Automations...'); const automationFolder = './config/automations/'; const fs = require('fs'); @@ -66,23 +74,25 @@ async function importAutomations() { let fullPath = path.join(automationFolder, file); let config: Array = JSON.parse(fs.readFileSync(fullPath.toString(),'utf8')); config.forEach((test1) => { - console.log(test1.type); // Add automation to DB - }) + }); }); } async function importDeviceGroups() { + logger.info('Import external Device Groups...'); const deviceGroupsFolder = './config/device-groups/'; const fs = require('fs'); + console.log(deviceGroupsFolder); fs.readdirSync(deviceGroupsFolder).forEach((file: any) => { let fullPath = path.join(deviceGroupsFolder, file); - let config: Array = JSON.parse(fs.readFileSync(fullPath.toString(),'utf8')); - config.forEach((test1) => { - console.log(test1.type); + let config: Array = JSON.parse(fs.readFileSync(fullPath.toString(),'utf8')); + for(const x of config) { + logger.info(x.name); + logger.info(x.types); // Add device group to DB - }) + }; }); } From 691a1a5cb90d757bf7782c4fc555eba940770232 Mon Sep 17 00:00:00 2001 From: Fabian During Date: Sun, 14 Feb 2021 12:23:44 +0100 Subject: [PATCH 04/20] Neo4j: Add Automations and Device Groups to DB Signed-off-by: Fabian During --- reconf/config/automations/bedroomLamp.json | 2 +- ...ing-lamp.json => ceiling-lamp-switch.json} | 0 reconf/src/app.ts | 2 ++ reconf/src/db.ts | 34 ++++++++++++++----- reconf/src/neo4j.ts | 6 ++-- 5 files changed, 33 insertions(+), 11 deletions(-) rename reconf/config/services/{ceiling-lamp.json => ceiling-lamp-switch.json} (100%) diff --git a/reconf/config/automations/bedroomLamp.json b/reconf/config/automations/bedroomLamp.json index 90276bc..572d0fe 100644 --- a/reconf/config/automations/bedroomLamp.json +++ b/reconf/config/automations/bedroomLamp.json @@ -1,5 +1,5 @@ [{ - "type": "ceiling-lamp", + "type": "ceiling-lamp-switch", "alias": "bed1", "mainDevices": ["switch1"], "replacementDevices": "switches", diff --git a/reconf/config/services/ceiling-lamp.json b/reconf/config/services/ceiling-lamp-switch.json similarity index 100% rename from reconf/config/services/ceiling-lamp.json rename to reconf/config/services/ceiling-lamp-switch.json diff --git a/reconf/src/app.ts b/reconf/src/app.ts index 3b060fd..0c07634 100644 --- a/reconf/src/app.ts +++ b/reconf/src/app.ts @@ -4,6 +4,7 @@ import { env as getEnv, util } from 'horme-common'; import fail from './fail'; import srv from './service'; import db from './db'; +import { resetDatabase } from './neo4j'; const env = getEnv.readEnvironment('reconf'); const logger = util.logger; @@ -21,6 +22,7 @@ main().catch((err) => util.abort(err)); async function main() { logger.setLogLevel(env.logLevel); + await resetDatabase(); await db.DataToDB(); await fail.setupFailureListener(); await srv.configureServices(); diff --git a/reconf/src/db.ts b/reconf/src/db.ts index 828108d..8afa388 100644 --- a/reconf/src/db.ts +++ b/reconf/src/db.ts @@ -1,4 +1,4 @@ -import { addConfigToDB} from './neo4j'; +import { returnQuery} from './neo4j'; import { ServiceType, Uuid } from './service'; import fs from 'fs/promises'; import { env as getEnv, util, ConfigMessage, Subscription, ServiceConfig, ServiceInfo, parseAs } from 'horme-common'; @@ -73,12 +73,22 @@ async function importAutomations() { const automationFolder = './config/automations/'; const fs = require('fs'); - fs.readdirSync(automationFolder).forEach((file: any) => { + fs.readdirSync(automationFolder).forEach(async (file: any) => { let fullPath = path.join(automationFolder, file); let config: Array = JSON.parse(fs.readFileSync(fullPath.toString(),'utf8')); - config.forEach((test1) => { - // Add automation to DB - }); + for(const x of config) { + + //Walkaround for illegal '-' in typename + let type = x.type; + type = type.split('-').join('_'); + + // Add device group to DB + const a: string = 'MATCH (n: Automation:' + type + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\' }) RETURN n'; + if (await returnQuery(a) == '') { + const b: string = 'CREATE (n: Automation:' + type + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\' })'; + await returnQuery(b); + } + }; }); } @@ -88,13 +98,21 @@ async function importDeviceGroups() { const fs = require('fs'); console.log(deviceGroupsFolder); - fs.readdirSync(deviceGroupsFolder).forEach((file: any) => { + fs.readdirSync(deviceGroupsFolder).forEach(async (file: any) => { let fullPath = path.join(deviceGroupsFolder, file); let config: Array = JSON.parse(fs.readFileSync(fullPath.toString(),'utf8')); for(const x of config) { - logger.info(x.name); - logger.info(x.types); + + //Walkaround for illegal '-' in typename + let name = x.name; + name = name.split('-').join('_'); + // Add device group to DB + const a: string = 'MATCH (n: DeviceGroup:' + name + ' { alias: \'' + x.types + '\' }) RETURN n'; + if (await returnQuery(a) == '') { + const b: string = 'CREATE (n: DeviceGroup:' + name + ' { alias: \'' + x.types + '\' })'; + await returnQuery(b); + } }; }); } diff --git a/reconf/src/neo4j.ts b/reconf/src/neo4j.ts index 36c5b35..69a8e27 100644 --- a/reconf/src/neo4j.ts +++ b/reconf/src/neo4j.ts @@ -73,7 +73,7 @@ async function updateAllDependencies(config: [string, ServiceEntry[]][]) { for (const element of config) { for (const elem2 of element[1]) { - for (const deps of elem2.depends) { + /*for (const deps of elem2.depends) { //if dependency dev exists const dev: string = 'MATCH (n) WHERE n.uuid = \'' + deps + '\' RETURN n'; @@ -91,6 +91,7 @@ async function updateAllDependencies(config: [string, ServiceEntry[]][]) { } } } + */ } } } @@ -120,7 +121,7 @@ export async function addConfigToDB(config: [string, ServiceEntry[]][]): Promise type = type.split('-').join('_'); //Check if Service does exist - const a: string = 'MATCH (n:' + type + ' { uuid: \'' + elem2.uuid + '\' }) RETURN n'; + /*const a: string = 'MATCH (n:' + type + ' { uuid: \'' + elem2.uuid + '\' }) RETURN n'; if (await returnQuery(a) == '') { const b: string = 'CREATE (n:' + type + ' { uuid: \'' + elem2.uuid + '\'})'; await returnQuery(b); @@ -140,6 +141,7 @@ export async function addConfigToDB(config: [string, ServiceEntry[]][]): Promise } } + */ } } From 86f58042e3ea0de0a3c766b2c06fdc196ed62026 Mon Sep 17 00:00:00 2001 From: Fabian During Date: Fri, 19 Feb 2021 14:30:34 +0100 Subject: [PATCH 05/20] Config: Added search for main devices Signed-off-by: Fabian During --- reconf/config/automations/bedroomLamp.json | 5 +- .../config/automations/bedroomSwitches.json | 6 ++- reconf/src/db.ts | 51 +++++++++++++++++-- reconf/src/neo4j.ts | 50 ++++++++++++++++-- 4 files changed, 100 insertions(+), 12 deletions(-) diff --git a/reconf/config/automations/bedroomLamp.json b/reconf/config/automations/bedroomLamp.json index 572d0fe..83a1877 100644 --- a/reconf/config/automations/bedroomLamp.json +++ b/reconf/config/automations/bedroomLamp.json @@ -1,8 +1,9 @@ [{ "type": "ceiling-lamp-switch", "alias": "bed1", - "mainDevices": ["switch1"], + "mainDevices": ["bri"], "replacementDevices": "switches", "room": "bedroom", - "configMsg": "" + "configMsg": "", + "online": true }] \ No newline at end of file diff --git a/reconf/config/automations/bedroomSwitches.json b/reconf/config/automations/bedroomSwitches.json index 1738892..3cd471a 100644 --- a/reconf/config/automations/bedroomSwitches.json +++ b/reconf/config/automations/bedroomSwitches.json @@ -4,7 +4,8 @@ "mainDevices": [], "replacementDevices": "", "room": "bedroom", - "configMsg": "" + "configMsg": "", + "online": true }, { "type": "light-switch", @@ -12,5 +13,6 @@ "mainDevices": [], "replacementDevices": "", "room": "bedroom", - "configMsg": "" + "configMsg": "", + "online": true }] \ No newline at end of file diff --git a/reconf/src/db.ts b/reconf/src/db.ts index 8afa388..d31287d 100644 --- a/reconf/src/db.ts +++ b/reconf/src/db.ts @@ -64,8 +64,9 @@ const logger = util.logger; */ async function DataToDB() { - importAutomations(); - importDeviceGroups(); + await importAutomations(); + await importDeviceGroups(); + await searchMainDevices(); } async function importAutomations() { @@ -82,9 +83,11 @@ async function importAutomations() { let type = x.type; type = type.split('-').join('_'); - // Add device group to DB + // If Device does not exist, add it to DB const a: string = 'MATCH (n: Automation:' + type + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\' }) RETURN n'; - if (await returnQuery(a) == '') { + + const query = await returnQuery(a); + if (query == '') { const b: string = 'CREATE (n: Automation:' + type + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\' })'; await returnQuery(b); } @@ -92,6 +95,46 @@ async function importAutomations() { }); } +async function searchMainDevices() { + + logger.info('searching for main devices'); + + // Search for devices with all main devices + const a: string = 'MATCH (n: Automation) WHERE NOT n.mainDevices = \'\' RETURN n.alias'; + const res = await returnQuery(a); + logger.info(a); + if ( res != '') { + //iterate over all devices + for(const x of res) { + logger.info('searching for ' + x); + + //check if needed device is in database + const d: string = 'MATCH (n: Automation) WHERE n.alias = \'' + x + '\' RETURN n.alias'; + const res1 = await returnQuery(d); + if (res1 == '') { + logger.warn('Device with alias \'' + x + '\' does not exist'); + break; + } + + const e: string = 'MATCH (n: Automation) WHERE n.online = true RETURN n.alias'; + const res2 = await returnQuery(e); + if (res2 == '') { + logger.warn('Device with alias \'' + x + '\' is not online!'); + //rekonf + break; + } else { + const e: string = 'MATCH (n,m: Automation) WHERE n.alias = \'' + x + '\' AND m.alias = \'' + d + '\' CREATE (n)-[r:SUBSCRIBE]->(m)'; + const res2 = await returnQuery(e); + } + } + + // search for main devices + //const c: string = 'MATCH (n: Automation WHERE n.alias = \'\' ) RETURN n'; + //const b: string = 'CREATE (n: Automation:' + type + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\' })'; + //await returnQuery(b); + } +} + async function importDeviceGroups() { logger.info('Import external Device Groups...'); const deviceGroupsFolder = './config/device-groups/'; diff --git a/reconf/src/neo4j.ts b/reconf/src/neo4j.ts index 69a8e27..4328cef 100644 --- a/reconf/src/neo4j.ts +++ b/reconf/src/neo4j.ts @@ -52,13 +52,55 @@ export async function returnQuery(n :string): Promise { } const session = driver.session(); let entireResult = ''; - await session.run(n).then(result => { - return result.records.map(record => { // Iterate through records - entireResult = record.get('n'); // Access the name property from the RETURN statement + let json: String = '['; + + session.run(n).then(function (result) { + if (result.records.length == 0) { + return ''; + } + result.records.forEach(function (record) { + json = json + '{'; + logger.info(record.keys.toString); + for(const x in record.keys) { + logger.info(x); + json += record.get(x); + } + json += record.entries.toString(); + json += '},'; + }); + json = json.substring(0, json.length - 1); + json += ']'; + entireResult += json; + session.close(); + }).catch(function (error) { + console.log(error); + }); + + /*await session.run(n).then(result => { + json = '['; + + if (result.records.length == 0) { + return ''; + } else { + logger.error(result.records[0].keys); + } + result.records.map(record => { // Iterate through records + json = json + '{'; + record.map(elem => { + json += elem; + }); + json += '},'; + //entireResult = record.get('n'); // Access the name property from the RETURN statement }); + json = json.substring(0, json.length - 1); + json += ']'; + entireResult += json; }) .then(() => { session.close();}); + if (entireResult != '') { + logger.info(entireResult); + }*/ return entireResult; } @@ -103,7 +145,7 @@ async function resetAllDependencies() { } const session = driver.session(); logger.info('Reset all Depends_Of relations...'); - await session.run('MATCH ()-[r:DEPENDS_ON]-() DELETE r') + await session.run('MATCH ()-[r:SUBSCRIBE]-() DELETE r') .then(() => { session.close();}); return; From 39dd6c7a4562753119379c24dc3d59257a68abab Mon Sep 17 00:00:00 2001 From: Fabian During Date: Sun, 21 Feb 2021 18:33:32 +0100 Subject: [PATCH 06/20] Neo4j: Added maindevice relations to database Signed-off-by: Fabian During --- reconf/config/automations/bedroomLamp.json | 2 +- reconf/src/db.ts | 103 +++++++++++++-------- reconf/src/neo4j.ts | 30 +++--- reconf/src/service.ts | 30 +++--- 4 files changed, 92 insertions(+), 73 deletions(-) diff --git a/reconf/config/automations/bedroomLamp.json b/reconf/config/automations/bedroomLamp.json index 83a1877..7745f8b 100644 --- a/reconf/config/automations/bedroomLamp.json +++ b/reconf/config/automations/bedroomLamp.json @@ -1,7 +1,7 @@ [{ "type": "ceiling-lamp-switch", "alias": "bed1", - "mainDevices": ["bri"], + "mainDevices": ["bri","fra"], "replacementDevices": "switches", "room": "bedroom", "configMsg": "", diff --git a/reconf/src/db.ts b/reconf/src/db.ts index d31287d..538abe0 100644 --- a/reconf/src/db.ts +++ b/reconf/src/db.ts @@ -1,4 +1,4 @@ -import { returnQuery} from './neo4j'; +import { returnQuery } from './neo4j'; import { ServiceType, Uuid } from './service'; import fs from 'fs/promises'; import { env as getEnv, util, ConfigMessage, Subscription, ServiceConfig, ServiceInfo, parseAs } from 'horme-common'; @@ -31,6 +31,7 @@ export type ServiceEntry = { replacementDevices: [string]; room: string; configMsg: string; + online: boolean; }; const logger = util.logger; @@ -73,65 +74,83 @@ async function importAutomations() { logger.info('Import external Automations...'); const automationFolder = './config/automations/'; const fs = require('fs'); + const files = await fs.readdirSync(automationFolder); - fs.readdirSync(automationFolder).forEach(async (file: any) => { + //await fs.readdirSync(automationFolder).forEach(async (file: any) => { + for (let file of files) { let fullPath = path.join(automationFolder, file); - let config: Array = JSON.parse(fs.readFileSync(fullPath.toString(),'utf8')); - for(const x of config) { - + let config: Array = JSON.parse(fs.readFileSync(fullPath.toString(), 'utf8')); + for (const x of config) { + //Walkaround for illegal '-' in typename let type = x.type; type = type.split('-').join('_'); // If Device does not exist, add it to DB - const a: string = 'MATCH (n: Automation:' + type + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\' }) RETURN n'; - + const a: string = 'MATCH (n: Automation:' + type + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\' }) RETURN n'; + const query = await returnQuery(a); - if (query == '') { - const b: string = 'CREATE (n: Automation:' + type + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\' })'; + if (query.records.length == 0) { + const b: string = 'CREATE (n: Automation:' + type + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\' })'; await returnQuery(b); } }; - }); + }; } async function searchMainDevices() { - logger.info('searching for main devices'); + logger.info('Searching for main devices'); // Search for devices with all main devices - const a: string = 'MATCH (n: Automation) WHERE NOT n.mainDevices = \'\' RETURN n.alias'; + const a: string = 'MATCH (n: Automation) WHERE NOT n.mainDevices = \'\' RETURN n.alias, n.mainDevices'; + const res = await returnQuery(a); - logger.info(a); - if ( res != '') { - //iterate over all devices - for(const x of res) { - logger.info('searching for ' + x); - - //check if needed device is in database - const d: string = 'MATCH (n: Automation) WHERE n.alias = \'' + x + '\' RETURN n.alias'; - const res1 = await returnQuery(d); - if (res1 == '') { - logger.warn('Device with alias \'' + x + '\' does not exist'); - break; - } - const e: string = 'MATCH (n: Automation) WHERE n.online = true RETURN n.alias'; - const res2 = await returnQuery(e); - if (res2 == '') { - logger.warn('Device with alias \'' + x + '\' is not online!'); - //rekonf - break; - } else { - const e: string = 'MATCH (n,m: Automation) WHERE n.alias = \'' + x + '\' AND m.alias = \'' + d + '\' CREATE (n)-[r:SUBSCRIBE]->(m)'; + if (res.records.length != 0) { + //iterate over all devices with main devices + res.records.forEach(async function (record) { + //iterate over all searched main devices + let md = record.get('n.mainDevices'); + let x = record.get('n.alias'); + var splitted = md.split(',', 30); + + for (let dev of splitted) { + const d: string = 'MATCH (n: Automation) WHERE n.alias = \'' + dev + '\' RETURN n.alias'; + const res1 = await returnQuery(d); + if (res1.records.length == 0) { + logger.warn('Device with alias \'' + dev + '\' does not exist'); + return; + } + + const e: string = 'MATCH (n: Automation) WHERE n.online = \'true\' AND n.alias = \'' + dev + '\' RETURN n.alias'; const res2 = await returnQuery(e); + if (res2.records.length == 0) { + logger.warn('Device with alias \'' + dev + '\' is not online!'); + //rekonf + return; + } else { + logger.info('Adding relation from \"' + x + '\" to \"' + dev + '\".'); + if (res2.records.length == 1) { + res2.records.forEach(async function (record) { + const e: string = 'MATCH (n: Automation {alias: \'' + x + '\'}), (m: Automation {alias: \'' + dev + '\'}) CREATE (n)-[r:SUBSCRIBE]->(m)'; + const res2 = await returnQuery(e); + }); + } else { + logger.error('Internal Error'); + } + } } - } + + + }); // search for main devices //const c: string = 'MATCH (n: Automation WHERE n.alias = \'\' ) RETURN n'; //const b: string = 'CREATE (n: Automation:' + type + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\' })'; //await returnQuery(b); + } else { + logger.error('got empty set'); } } @@ -139,12 +158,13 @@ async function importDeviceGroups() { logger.info('Import external Device Groups...'); const deviceGroupsFolder = './config/device-groups/'; const fs = require('fs'); - console.log(deviceGroupsFolder); + const files = await fs.readdirSync(deviceGroupsFolder); - fs.readdirSync(deviceGroupsFolder).forEach(async (file: any) => { + //await fs.readdirSync(deviceGroupsFolder).forEach(async (file: any) => { + for (let file of files) { let fullPath = path.join(deviceGroupsFolder, file); - let config: Array = JSON.parse(fs.readFileSync(fullPath.toString(),'utf8')); - for(const x of config) { + let config: Array = JSON.parse(fs.readFileSync(fullPath.toString(), 'utf8')); + for (const x of config) { //Walkaround for illegal '-' in typename let name = x.name; @@ -152,12 +172,13 @@ async function importDeviceGroups() { // Add device group to DB const a: string = 'MATCH (n: DeviceGroup:' + name + ' { alias: \'' + x.types + '\' }) RETURN n'; - if (await returnQuery(a) == '') { + let res = await returnQuery(a); + if (res.records.length == 0) { const b: string = 'CREATE (n: DeviceGroup:' + name + ' { alias: \'' + x.types + '\' })'; await returnQuery(b); } - }; - }); + }; + }; } /*const bedroomSwitch1: ServiceEntry = { diff --git a/reconf/src/neo4j.ts b/reconf/src/neo4j.ts index 4328cef..044aeee 100644 --- a/reconf/src/neo4j.ts +++ b/reconf/src/neo4j.ts @@ -1,6 +1,7 @@ import neo4j, { Driver } from 'neo4j-driver'; import { env as getEnv, util } from 'horme-common'; import { ServiceEntry } from './db'; +import result, { QueryResult } from 'neo4j-driver/types/result'; const env = getEnv.readEnvironment('reconf'); const logger = util.logger; @@ -46,18 +47,16 @@ export async function resetDatabase(): Promise{ } //execute query with return -export async function returnQuery(n :string): Promise { +export async function returnQuery(n :string): Promise { if(driver === undefined) { await connectNeo4j(); } const session = driver.session(); - let entireResult = ''; - let json: String = '['; - - session.run(n).then(function (result) { - if (result.records.length == 0) { - return ''; - } + //let entireResult = ''; + //let json: String = '['; + let result = await session.run(n); + /*let result = session.run(n).then(function (result) { + return result; result.records.forEach(function (record) { json = json + '{'; logger.info(record.keys.toString); @@ -74,7 +73,7 @@ export async function returnQuery(n :string): Promise { session.close(); }).catch(function (error) { console.log(error); - }); + });*/ /*await session.run(n).then(result => { json = '['; @@ -101,7 +100,7 @@ export async function returnQuery(n :string): Promise { if (entireResult != '') { logger.info(entireResult); }*/ - return entireResult; + return result; } //add all dependencies from services to other services @@ -113,19 +112,19 @@ async function updateAllDependencies(config: [string, ServiceEntry[]][]) { //Reset all current dependencies, as device dependencies may change during reconfiguration await resetAllDependencies(); - for (const element of config) { + /*for (const element of config) { for (const elem2 of element[1]) { - /*for (const deps of elem2.depends) { + for (const deps of elem2.) { //if dependency dev exists const dev: string = 'MATCH (n) WHERE n.uuid = \'' + deps + '\' RETURN n'; const res = await returnQuery(dev); - if (res != '') { + if (res.records.length != 0) { //check if relation already exists const checkrel: string = 'MATCH (n)-[DEPENDS_ON]->(m) WHERE n.uuid = \'' + elem2.uuid + '\' AND m.uuid = \'' + deps + '\' RETURN n'; const result = await returnQuery(checkrel); - if (result == '') { + if (result.records.length != 0) { //create relation const newrel: string = 'MATCH (n), (m) WHERE n.uuid = \'' + elem2.uuid + '\' AND m.uuid = \'' + deps + '\' CREATE (n)-[r:DEPENDS_ON]->(m)'; @@ -133,9 +132,8 @@ async function updateAllDependencies(config: [string, ServiceEntry[]][]) { } } } - */ } - } + }*/ } //Reset all current dependencies diff --git a/reconf/src/service.ts b/reconf/src/service.ts index 957f2e1..89ce2d9 100644 --- a/reconf/src/service.ts +++ b/reconf/src/service.ts @@ -44,13 +44,13 @@ const serviceNamePrefix = 'horme-'; async function configureServices(): Promise { // query current service selection from database // Only need to be done one per execution - //const result = await db.queryServiceSelection(); + //const result = await db.queryServiceSelection(); // instantiate all not yet instantiated services, insert them into global map - //Dont need services. Can be extracted from database. - //const instantiated = await instantiateServices(); + //Dont need services. Can be extracted from database. + //const instantiated = await instantiateServices(); // set and configure all service dependencies - //await Promise.all(instantiated.map((args) => configureService(args, [] ,true))); + //await Promise.all(instantiated.map((args) => configureService(args, [] ,true))); } /** Removes the service with the given `uuid` and triggers a full service selection @@ -58,10 +58,10 @@ async function configureServices(): Promise { async function removeService(uuid: string): Promise { // retrieve updated service selection from database //TODO - //const reconfiguration = await db.queryServiceSelection({ del: [uuid] }); + //const reconfiguration = await db.queryServiceSelection({ del: [uuid] }); const previousServices = Array.from(services.values()); - //const newServices = reconfiguration + //const newServices = reconfiguration /*const newServices = Array.from( reconfiguration.flatMap(([_, instances]) => { return instances.map((instance) => instance.alias); @@ -69,25 +69,25 @@ async function removeService(uuid: string): Promise { );*/ // determine services which are no longer present in updated service selection - //const removals = previousServices.filter((prev) => newServices.forEach((test) => test.alias != prev.info.uuid)) + //const removals = previousServices.filter((prev) => newServices.forEach((test) => test.alias != prev.info.uuid)) // remove all services no longer present in the new configuration and kill their respective // processes - /*for (const service of removals) { - logger.warn('killing process of service ' + chalk.underline(service.info.uuid)); + /*for (const service of removals) { + logger.warn('killing process of service ' + chalk.underline(service.info.uuid)); - execSync(`docker stop ${serviceNamePrefix}${service.info.uuid}`); - execSync(`docker rm ${serviceNamePrefix}${service.info.uuid}`); - services.delete(service.info.uuid); - }*/ + execSync(`docker stop ${serviceNamePrefix}${service.info.uuid}`); + execSync(`docker rm ${serviceNamePrefix}${service.info.uuid}`); + services.delete(service.info.uuid); + }*/ // instantiate all new services //TODO - //const instantiatedServices = await instantiateServices(reconfiguration); + //const instantiatedServices = await instantiateServices(reconfiguration); // configure all newly instantiated services and re-configure all changed services logger.info('initiating service reconfiguration...'); - //await Promise.all(instantiatedServices.map((args) => configureService(args, []))); + //await Promise.all(instantiatedServices.map((args) => configureService(args, []))); } function cleanUp(): void { From b4bf9b8ca2e04dcd30a4b447921a0491af732120 Mon Sep 17 00:00:00 2001 From: Fabian During Date: Wed, 24 Feb 2021 19:15:16 +0100 Subject: [PATCH 07/20] Rekonf: Added simple reconfiguration algorithm Signed-off-by: Fabian During --- reconf/config/automations/bedroomLamp.json | 4 +- .../config/automations/bedroomSwitches.json | 10 +-- reconf/src/db.ts | 67 ++++++++++++++----- 3 files changed, 59 insertions(+), 22 deletions(-) diff --git a/reconf/config/automations/bedroomLamp.json b/reconf/config/automations/bedroomLamp.json index 7745f8b..0b4aec5 100644 --- a/reconf/config/automations/bedroomLamp.json +++ b/reconf/config/automations/bedroomLamp.json @@ -1,8 +1,8 @@ [{ "type": "ceiling-lamp-switch", "alias": "bed1", - "mainDevices": ["bri","fra"], - "replacementDevices": "switches", + "mainDevices": ["bri"], + "replacementDevices": "", "room": "bedroom", "configMsg": "", "online": true diff --git a/reconf/config/automations/bedroomSwitches.json b/reconf/config/automations/bedroomSwitches.json index 3cd471a..e395d70 100644 --- a/reconf/config/automations/bedroomSwitches.json +++ b/reconf/config/automations/bedroomSwitches.json @@ -1,17 +1,17 @@ [{ - "type": "light-switch", + "type": "wall-switch", "alias": "bri", "mainDevices": [], - "replacementDevices": "", + "replacementDevices": "switches", "room": "bedroom", "configMsg": "", - "online": true + "online": false }, { - "type": "light-switch", + "type": "remote-switch", "alias": "fra", "mainDevices": [], - "replacementDevices": "", + "replacementDevices": "switches", "room": "bedroom", "configMsg": "", "online": true diff --git a/reconf/src/db.ts b/reconf/src/db.ts index 538abe0..72adaa2 100644 --- a/reconf/src/db.ts +++ b/reconf/src/db.ts @@ -70,6 +70,7 @@ async function DataToDB() { await searchMainDevices(); } +//TODO: check for redundant aliases async function importAutomations() { logger.info('Import external Automations...'); const automationFolder = './config/automations/'; @@ -87,17 +88,47 @@ async function importAutomations() { type = type.split('-').join('_'); // If Device does not exist, add it to DB - const a: string = 'MATCH (n: Automation:' + type + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\' }) RETURN n'; + const a: string = 'MATCH (n: Automation:' + type + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\' }) RETURN n'; const query = await returnQuery(a); if (query.records.length == 0) { - const b: string = 'CREATE (n: Automation:' + type + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\' })'; + const b: string = 'CREATE (n: Automation:' + type + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\' })'; await returnQuery(b); } }; }; } +//TODO: currently always first replacement devices from type t is used +async function alternativeConfiguration(dev:string) { + logger.info('searching alternative for device \'' + dev + '\'!'); + //get all importent attr from dev + const repldev: string = 'MATCH (n: Automation) WHERE n.alias = \'' + dev + '\' RETURN n.replacementDevices, n.room'; + let realdev = await returnQuery(repldev); + + let devGroup = realdev.records[0].get('n.replacementDevices'); + let room = realdev.records[0].get('n.room'); + + //get all types from group + const grouprq: string = 'MATCH (n: DeviceGroup: ' + devGroup + ') RETURN n.devices'; + + let groupres = await returnQuery(grouprq); + + //get device types (sorted by prio) + var splitted = groupres.records[0].get('n.devices').split(',', 30); + for (let type of splitted) { + type = type.split('-').join('_'); + const e: string = 'MATCH (n: Automation: ' + type + ') WHERE n.online = \'true\' AND n.room = \'' + room + '\' RETURN n.alias'; + let back = await returnQuery(e); + if(back.records.length != 0){ + logger.info('Found replacement device(s). Yey!'); + return back.records[0].get('n.alias'); + } + } + return null; + +} + async function searchMainDevices() { logger.info('Searching for main devices'); @@ -116,7 +147,7 @@ async function searchMainDevices() { var splitted = md.split(',', 30); for (let dev of splitted) { - const d: string = 'MATCH (n: Automation) WHERE n.alias = \'' + dev + '\' RETURN n.alias'; + const d: string = 'MATCH (n: Automation) WHERE n.alias = \'' + dev + '\' RETURN n.alias, n.replacementDevices'; const res1 = await returnQuery(d); if (res1.records.length == 0) { logger.warn('Device with alias \'' + dev + '\' does not exist'); @@ -127,18 +158,17 @@ async function searchMainDevices() { const res2 = await returnQuery(e); if (res2.records.length == 0) { logger.warn('Device with alias \'' + dev + '\' is not online!'); - //rekonf - return; - } else { - logger.info('Adding relation from \"' + x + '\" to \"' + dev + '\".'); - if (res2.records.length == 1) { - res2.records.forEach(async function (record) { - const e: string = 'MATCH (n: Automation {alias: \'' + x + '\'}), (m: Automation {alias: \'' + dev + '\'}) CREATE (n)-[r:SUBSCRIBE]->(m)'; - const res2 = await returnQuery(e); - }); + //get alias from alternative + let alt = await alternativeConfiguration(dev); + if(alt) { + initRelationship(x, alt); } else { - logger.error('Internal Error'); + logger.info('No Device found :('); + // TODO remember not inited automations + return; } + } else { + initRelationship(x, dev); } } @@ -154,6 +184,13 @@ async function searchMainDevices() { } } +async function initRelationship(dev1:string, dev2:string) { + logger.info('Adding relation from \"' + dev1 + '\" to \"' + dev2 + '\".'); + const e: string = 'MATCH (n: Automation {alias: \'' + dev1 + '\'}), (m: Automation {alias: \'' + dev2 + '\'}) CREATE (n)-[r:SUBSCRIBE]->(m)'; + await returnQuery(e); + return; +} + async function importDeviceGroups() { logger.info('Import external Device Groups...'); const deviceGroupsFolder = './config/device-groups/'; @@ -171,10 +208,10 @@ async function importDeviceGroups() { name = name.split('-').join('_'); // Add device group to DB - const a: string = 'MATCH (n: DeviceGroup:' + name + ' { alias: \'' + x.types + '\' }) RETURN n'; + const a: string = 'MATCH (n: DeviceGroup:' + name + ' { devices: \'' + x.types + '\' }) RETURN n'; let res = await returnQuery(a); if (res.records.length == 0) { - const b: string = 'CREATE (n: DeviceGroup:' + name + ' { alias: \'' + x.types + '\' })'; + const b: string = 'CREATE (n: DeviceGroup:' + name + ' { devices: \'' + x.types + '\' })'; await returnQuery(b); } }; From 75c1589147da4808abeb633cf745588681b71d81 Mon Sep 17 00:00:00 2001 From: Fabian During Date: Wed, 3 Mar 2021 10:59:14 +0100 Subject: [PATCH 08/20] Rekonf: remember not instantiated devices Signed-off-by: Fabian During --- reconf/src/db.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/reconf/src/db.ts b/reconf/src/db.ts index 72adaa2..7275ce3 100644 --- a/reconf/src/db.ts +++ b/reconf/src/db.ts @@ -92,7 +92,7 @@ async function importAutomations() { const query = await returnQuery(a); if (query.records.length == 0) { - const b: string = 'CREATE (n: Automation:' + type + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\' })'; + const b: string = 'CREATE (n: Automation:' + type + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\', instantiated: \'false\' })'; await returnQuery(b); } }; @@ -188,6 +188,8 @@ async function initRelationship(dev1:string, dev2:string) { logger.info('Adding relation from \"' + dev1 + '\" to \"' + dev2 + '\".'); const e: string = 'MATCH (n: Automation {alias: \'' + dev1 + '\'}), (m: Automation {alias: \'' + dev2 + '\'}) CREATE (n)-[r:SUBSCRIBE]->(m)'; await returnQuery(e); + const g: string = 'MATCH (n: Automation {alias: \'' + dev1 + '\'}) SET n.initiated = \'true\''; + await returnQuery(g); return; } From 93ca2ecfb98ab04be9b830794fbe63fa1b92c8f1 Mon Sep 17 00:00:00 2001 From: Fabian During Date: Wed, 3 Mar 2021 17:08:51 +0100 Subject: [PATCH 09/20] Rekonf: Readded service starting Signed-off-by: Fabian During --- reconf/src/db.ts | 36 +++++++++++++++++++++++++++--------- reconf/src/fail.ts | 2 ++ reconf/src/neo4j.ts | 7 ++++++- reconf/src/service.ts | 4 ++-- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/reconf/src/db.ts b/reconf/src/db.ts index 7275ce3..300fd7c 100644 --- a/reconf/src/db.ts +++ b/reconf/src/db.ts @@ -3,6 +3,8 @@ import { ServiceType, Uuid } from './service'; import fs from 'fs/promises'; import { env as getEnv, util, ConfigMessage, Subscription, ServiceConfig, ServiceInfo, parseAs } from 'horme-common'; import path from 'path'; +import { QueryResult } from 'neo4j-driver'; +import { instantiateService } from './service'; export default { DataToDB }; @@ -85,15 +87,19 @@ async function importAutomations() { //Walkaround for illegal '-' in typename let type = x.type; - type = type.split('-').join('_'); + let newworld = type.split('-').join('_'); // If Device does not exist, add it to DB - const a: string = 'MATCH (n: Automation:' + type + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\' }) RETURN n'; + const a: string = 'MATCH (n: Automation:' + newworld + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\' }) RETURN n'; const query = await returnQuery(a); if (query.records.length == 0) { - const b: string = 'CREATE (n: Automation:' + type + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\', instantiated: \'false\' })'; + const b: string = 'CREATE (n: Automation:' + newworld + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\', instantiated: \'false\' })'; await returnQuery(b); + const file = await fs.readFileSync(`./config/services/${type}.json`, 'utf8'); + const back = await parseAs(ServiceConfig, JSON.parse(file.toString())); + if (!back) return []; + return await instantiateService(x, back); } }; }; @@ -129,15 +135,17 @@ async function alternativeConfiguration(dev:string) { } -async function searchMainDevices() { - - logger.info('Searching for main devices'); +async function initMissingAutomations() { + logger.info('Try to add not instantiated devices to the database'); - // Search for devices with all main devices - const a: string = 'MATCH (n: Automation) WHERE NOT n.mainDevices = \'\' RETURN n.alias, n.mainDevices'; + // Search for devices which are not instantiated + const a: string = 'MATCH (n: Automation) WHERE n.instantiated = \'false\' RETURN n.mainDevices'; const res = await returnQuery(a); + initiateDavices(res); +} +async function initiateDavices(res: QueryResult) { if (res.records.length != 0) { //iterate over all devices with main devices res.records.forEach(async function (record) { @@ -164,7 +172,6 @@ async function searchMainDevices() { initRelationship(x, alt); } else { logger.info('No Device found :('); - // TODO remember not inited automations return; } } else { @@ -184,6 +191,17 @@ async function searchMainDevices() { } } +async function searchMainDevices() { + + logger.info('Searching for main devices'); + + // Search for devices with all main devices + const a: string = 'MATCH (n: Automation) WHERE NOT n.mainDevices = \'\' RETURN n.mainDevices, n.alias'; + + const res = await returnQuery(a); + initiateDavices(res); +} + async function initRelationship(dev1:string, dev2:string) { logger.info('Adding relation from \"' + dev1 + '\" to \"' + dev2 + '\".'); const e: string = 'MATCH (n: Automation {alias: \'' + dev1 + '\'}), (m: Automation {alias: \'' + dev2 + '\'}) CREATE (n)-[r:SUBSCRIBE]->(m)'; diff --git a/reconf/src/fail.ts b/reconf/src/fail.ts index 900f76f..c531c09 100644 --- a/reconf/src/fail.ts +++ b/reconf/src/fail.ts @@ -1,5 +1,6 @@ import chalk from 'chalk'; import mqtt from 'async-mqtt'; +import { updateDatabase } from './neo4j'; import { env as getEnv, util, FailureMessage, parseAs } from 'horme-common'; import srv from './service'; @@ -16,6 +17,7 @@ async function setupFailureListener(): Promise { // set MQTT client message event listener client.on('message', (topic, msg) => { onFailure(topic, msg).catch((err) => util.abort(err)); + updateDatabase(topic, msg.toString()); }); await client.subscribe([ diff --git a/reconf/src/neo4j.ts b/reconf/src/neo4j.ts index 044aeee..5c0c6cb 100644 --- a/reconf/src/neo4j.ts +++ b/reconf/src/neo4j.ts @@ -1,7 +1,7 @@ import neo4j, { Driver } from 'neo4j-driver'; import { env as getEnv, util } from 'horme-common'; import { ServiceEntry } from './db'; -import result, { QueryResult } from 'neo4j-driver/types/result'; +import { QueryResult } from 'neo4j-driver/types/result'; const env = getEnv.readEnvironment('reconf'); const logger = util.logger; @@ -136,6 +136,11 @@ async function updateAllDependencies(config: [string, ServiceEntry[]][]) { }*/ } +export async function updateDatabase(topic: string, message: string) { + logger.error('topic: ' + topic); + logger.error('message: ' + message); +} + //Reset all current dependencies async function resetAllDependencies() { if(driver === undefined) { diff --git a/reconf/src/service.ts b/reconf/src/service.ts index 89ce2d9..c54fefb 100644 --- a/reconf/src/service.ts +++ b/reconf/src/service.ts @@ -115,7 +115,7 @@ function cleanUp(): void { }*/ /** Instantiates a service of the given type/description/config if it does not already exist. */ -async function instantiateService( +export async function instantiateService( entry: ServiceEntry, config: ServiceConfig ): Promise<[ServiceHandle]> { @@ -216,7 +216,7 @@ function startService(entry: ServiceEntry, config: ServiceConfig, topic: string) config.image, config.args.join(' '), ]; - + logger.error('nope'); const instance = spawn('docker', cmd); instance.stdout.on('data', (data: Buffer) => { console.log(`\tfrom '${entry.type}/${chalk.underline(entry.alias)}' (stdout):`); From f8bdafa4e4874abc8233077e5d5e40369b777ad6 Mon Sep 17 00:00:00 2001 From: Fabian During Date: Sat, 6 Mar 2021 14:29:47 +0100 Subject: [PATCH 10/20] Rekonf: Devices are no longer usable multiple times in one service Signed-off-by: Fabian During --- reconf/config/automations/bedroomLamp.json | 2 +- reconf/config/services/remote-switch.json | 4 + reconf/config/services/wall-switch.json | 4 + reconf/src/db.ts | 102 ++++----------------- reconf/src/service.ts | 1 - 5 files changed, 25 insertions(+), 88 deletions(-) create mode 100644 reconf/config/services/remote-switch.json create mode 100644 reconf/config/services/wall-switch.json diff --git a/reconf/config/automations/bedroomLamp.json b/reconf/config/automations/bedroomLamp.json index 0b4aec5..9d26a33 100644 --- a/reconf/config/automations/bedroomLamp.json +++ b/reconf/config/automations/bedroomLamp.json @@ -1,7 +1,7 @@ [{ "type": "ceiling-lamp-switch", "alias": "bed1", - "mainDevices": ["bri"], + "mainDevices": ["bri", "fra"], "replacementDevices": "", "room": "bedroom", "configMsg": "", diff --git a/reconf/config/services/remote-switch.json b/reconf/config/services/remote-switch.json new file mode 100644 index 0000000..fbd92ff --- /dev/null +++ b/reconf/config/services/remote-switch.json @@ -0,0 +1,4 @@ +{ + "image": "light-switch", + "args": [] +} \ No newline at end of file diff --git a/reconf/config/services/wall-switch.json b/reconf/config/services/wall-switch.json new file mode 100644 index 0000000..fbd92ff --- /dev/null +++ b/reconf/config/services/wall-switch.json @@ -0,0 +1,4 @@ +{ + "image": "light-switch", + "args": [] +} \ No newline at end of file diff --git a/reconf/src/db.ts b/reconf/src/db.ts index 300fd7c..e88bd85 100644 --- a/reconf/src/db.ts +++ b/reconf/src/db.ts @@ -40,32 +40,6 @@ const logger = util.logger; /********** implementation ************************************************************************/ -/*async function queryServiceSelection(updates?: ConfigUpdates): Promise> { - var config = importAutomations(); - if (updates) { - if (updateCount === 0) { - console.assert(updates.del[0] === 'fra'); - config.set('light-switch', [bedroomSwitch1]); - bedroomLamp.depends = [bedroomSwitch1.uuid]; - failureReasoner.depends = [bedroomSwitch1.uuid]; - updateCount = 1; - } else if (updateCount === 1) { - console.assert(updates.del[0] === 'bri'); - config.set('camera-motion-detect', [camera]); - bedroomLamp.depends = []; - failureReasoner.depends = []; - updateCount = 2; - } else { - throw new Error('exceeded bounds of static reconfiguration scenario'); - } - } - - await addConfigToDB(Array.from(config)); - - return null; -} -*/ - async function DataToDB() { await importAutomations(); await importDeviceGroups(); @@ -83,6 +57,7 @@ async function importAutomations() { for (let file of files) { let fullPath = path.join(automationFolder, file); let config: Array = JSON.parse(fs.readFileSync(fullPath.toString(), 'utf8')); + //config.forEach(x => for (const x of config) { //Walkaround for illegal '-' in typename @@ -98,16 +73,20 @@ async function importAutomations() { await returnQuery(b); const file = await fs.readFileSync(`./config/services/${type}.json`, 'utf8'); const back = await parseAs(ServiceConfig, JSON.parse(file.toString())); - if (!back) return []; - return await instantiateService(x, back); + if (!back) break; + await instantiateService(x, back); } }; }; -} +}; //TODO: currently always first replacement devices from type t is used +//BUG: When searching a replacement device for a device which is offline and the device-group of the missing device-type is the same as a later main device, +// - the replacement device could 'steal' the device of a later main-device, which then also needs a replacement device, as the main device is already in use. + async function alternativeConfiguration(dev:string) { logger.info('searching alternative for device \'' + dev + '\'!'); + //get all importent attr from dev const repldev: string = 'MATCH (n: Automation) WHERE n.alias = \'' + dev + '\' RETURN n.replacementDevices, n.room'; let realdev = await returnQuery(repldev); @@ -124,7 +103,7 @@ async function alternativeConfiguration(dev:string) { var splitted = groupres.records[0].get('n.devices').split(',', 30); for (let type of splitted) { type = type.split('-').join('_'); - const e: string = 'MATCH (n: Automation: ' + type + ') WHERE n.online = \'true\' AND n.room = \'' + room + '\' RETURN n.alias'; + const e: string = 'MATCH (n: Automation: ' + type + ') WHERE n.online = \'true\' AND n.room = \'' + room + '\' AND NOT (n)-[:SUBSCRIBE]->() RETURN n.alias'; let back = await returnQuery(e); if(back.records.length != 0){ logger.info('Found replacement device(s). Yey!'); @@ -142,18 +121,17 @@ async function initMissingAutomations() { const a: string = 'MATCH (n: Automation) WHERE n.instantiated = \'false\' RETURN n.mainDevices'; const res = await returnQuery(a); - initiateDavices(res); + initiateDevices(res); } -async function initiateDavices(res: QueryResult) { +async function initiateDevices(res: QueryResult) { if (res.records.length != 0) { //iterate over all devices with main devices - res.records.forEach(async function (record) { + for(const record of res.records) { //iterate over all searched main devices let md = record.get('n.mainDevices'); let x = record.get('n.alias'); var splitted = md.split(',', 30); - for (let dev of splitted) { const d: string = 'MATCH (n: Automation) WHERE n.alias = \'' + dev + '\' RETURN n.alias, n.replacementDevices'; const res1 = await returnQuery(d); @@ -162,7 +140,7 @@ async function initiateDavices(res: QueryResult) { return; } - const e: string = 'MATCH (n: Automation) WHERE n.online = \'true\' AND n.alias = \'' + dev + '\' RETURN n.alias'; + const e: string = 'MATCH (n: Automation) WHERE n.online = \'true\' AND n.alias = \'' + dev + '\' AND NOT (n)-[:SUBSCRIBE]->() RETURN n.alias'; const res2 = await returnQuery(e); if (res2.records.length == 0) { logger.warn('Device with alias \'' + dev + '\' is not online!'); @@ -180,12 +158,7 @@ async function initiateDavices(res: QueryResult) { } - }); - - // search for main devices - //const c: string = 'MATCH (n: Automation WHERE n.alias = \'\' ) RETURN n'; - //const b: string = 'CREATE (n: Automation:' + type + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\' })'; - //await returnQuery(b); + }; } else { logger.error('got empty set'); } @@ -199,12 +172,12 @@ async function searchMainDevices() { const a: string = 'MATCH (n: Automation) WHERE NOT n.mainDevices = \'\' RETURN n.mainDevices, n.alias'; const res = await returnQuery(a); - initiateDavices(res); + initiateDevices(res); } async function initRelationship(dev1:string, dev2:string) { logger.info('Adding relation from \"' + dev1 + '\" to \"' + dev2 + '\".'); - const e: string = 'MATCH (n: Automation {alias: \'' + dev1 + '\'}), (m: Automation {alias: \'' + dev2 + '\'}) CREATE (n)-[r:SUBSCRIBE]->(m)'; + const e: string = 'MATCH (n: Automation {alias: \'' + dev1 + '\'}), (m: Automation {alias: \'' + dev2 + '\'}) CREATE (m)-[r:SUBSCRIBE]->(n)'; await returnQuery(e); const g: string = 'MATCH (n: Automation {alias: \'' + dev1 + '\'}) SET n.initiated = \'true\''; await returnQuery(g); @@ -237,46 +210,3 @@ async function importDeviceGroups() { }; }; } - -/*const bedroomSwitch1: ServiceEntry = { - uuid: 'bri', - room: 'bedroom', - type: 'light-switch', - depends: [], -}; - -const bedroomSwitch2: ServiceEntry = { - uuid: 'fra', - room: 'bedroom', - type: 'light-switch', - depends: [], -}; - -const bedroomLamp: ServiceEntry = { - uuid: 'abc', - room: 'bedroom', - type: 'ceiling-lamp', - depends: [bedroomSwitch1.uuid, bedroomSwitch2.uuid], -}; - -const camera: ServiceEntry = { - uuid: 'cam', - room: 'bedroom', - type: 'camera-motion-detect', - depends: [], -}; - -const failureReasoner: ServiceEntry = { - uuid: 'flr', - room: null, - type: 'failure-reasoner', - depends: [bedroomSwitch1.uuid, bedroomSwitch2.uuid], -}; - -const config: Map = new Map([ - ['ceiling-lamp', [bedroomLamp]], - ['light-switch', [bedroomSwitch1, bedroomSwitch2]], - //['failure-reasoner', [failureReasoner]] -]);*/ - -//let updateCount = 0; diff --git a/reconf/src/service.ts b/reconf/src/service.ts index c54fefb..05332ca 100644 --- a/reconf/src/service.ts +++ b/reconf/src/service.ts @@ -216,7 +216,6 @@ function startService(entry: ServiceEntry, config: ServiceConfig, topic: string) config.image, config.args.join(' '), ]; - logger.error('nope'); const instance = spawn('docker', cmd); instance.stdout.on('data', (data: Buffer) => { console.log(`\tfrom '${entry.type}/${chalk.underline(entry.alias)}' (stdout):`); From 821506df44df77fde1532bfc1ef95e652d119f4f Mon Sep 17 00:00:00 2001 From: Fabian During Date: Sat, 6 Mar 2021 15:07:40 +0100 Subject: [PATCH 11/20] Rekonf: Fixed bug - same device can be multiple times in automation Signed-off-by: Fabian During --- reconf/src/db.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/reconf/src/db.ts b/reconf/src/db.ts index e88bd85..d67fcc4 100644 --- a/reconf/src/db.ts +++ b/reconf/src/db.ts @@ -84,7 +84,7 @@ async function importAutomations() { //BUG: When searching a replacement device for a device which is offline and the device-group of the missing device-type is the same as a later main device, // - the replacement device could 'steal' the device of a later main-device, which then also needs a replacement device, as the main device is already in use. -async function alternativeConfiguration(dev:string) { +async function alternativeConfiguration(dev:string, to:string) { logger.info('searching alternative for device \'' + dev + '\'!'); //get all importent attr from dev @@ -103,11 +103,13 @@ async function alternativeConfiguration(dev:string) { var splitted = groupres.records[0].get('n.devices').split(',', 30); for (let type of splitted) { type = type.split('-').join('_'); - const e: string = 'MATCH (n: Automation: ' + type + ') WHERE n.online = \'true\' AND n.room = \'' + room + '\' AND NOT (n)-[:SUBSCRIBE]->() RETURN n.alias'; + const e: string = 'MATCH (n: Automation: ' + type + '), ( m: Automation ) WHERE n.online = \'true\' AND m.alias = \'' + to + '\' AND n.room = \'' + room + '\' AND NOT (n)-[:SUBSCRIBE]->(m) RETURN n.alias'; let back = await returnQuery(e); if(back.records.length != 0){ logger.info('Found replacement device(s). Yey!'); return back.records[0].get('n.alias'); + } else { + //logger.error(' '); } } return null; @@ -140,12 +142,12 @@ async function initiateDevices(res: QueryResult) { return; } - const e: string = 'MATCH (n: Automation) WHERE n.online = \'true\' AND n.alias = \'' + dev + '\' AND NOT (n)-[:SUBSCRIBE]->() RETURN n.alias'; + const e: string = 'MATCH (n: Automation), (m: Automation) WHERE n.online = \'true\' AND n.alias = \'' + dev + '\' AND m.alias = \'' + x + '\' AND NOT (n)-[:SUBSCRIBE]->(m) RETURN n.alias'; const res2 = await returnQuery(e); if (res2.records.length == 0) { - logger.warn('Device with alias \'' + dev + '\' is not online!'); + logger.warn('Device with alias \'' + dev + '\' is available for this configuration!'); //get alias from alternative - let alt = await alternativeConfiguration(dev); + let alt = await alternativeConfiguration(dev, x); if(alt) { initRelationship(x, alt); } else { From b6516f53ee67bb45ea6bbf966272966fd6e858bc Mon Sep 17 00:00:00 2001 From: Fabian During Date: Sun, 7 Mar 2021 12:10:09 +0100 Subject: [PATCH 12/20] Rekonf: Fixed missing instantiation Signed-off-by: Fabian During --- reconf/config/automations/bedroomSwitches.json | 2 +- reconf/src/db.ts | 11 +++++++++++ reconf/src/service.ts | 12 ++++++------ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/reconf/config/automations/bedroomSwitches.json b/reconf/config/automations/bedroomSwitches.json index e395d70..b36db25 100644 --- a/reconf/config/automations/bedroomSwitches.json +++ b/reconf/config/automations/bedroomSwitches.json @@ -5,7 +5,7 @@ "replacementDevices": "switches", "room": "bedroom", "configMsg": "", - "online": false + "online": true }, { "type": "remote-switch", diff --git a/reconf/src/db.ts b/reconf/src/db.ts index 7ed10bc..8316d9b 100644 --- a/reconf/src/db.ts +++ b/reconf/src/db.ts @@ -38,6 +38,8 @@ export type ServiceEntry = { const logger = util.logger; +let allSE: Array = []; + /********** implementation ************************************************************************/ async function DataToDB() { @@ -77,9 +79,18 @@ async function importAutomations() { await instantiateService(x, back); } }; + allSE = allSE.concat(config); }; }; +export async function getSEfromUuid(uuid: string): Promise { + for (const x of allSE) { + if (x.uuid == uuid) return x; + } + return undefined; +} + +//depricated /*export async function queryService(uuid: string): Promise { for (let [_, value] of config) { for (let entry of value) { diff --git a/reconf/src/service.ts b/reconf/src/service.ts index 3d7dadd..0c12334 100644 --- a/reconf/src/service.ts +++ b/reconf/src/service.ts @@ -4,7 +4,7 @@ import fs from 'fs/promises'; import chalk from 'chalk'; import mqtt from 'async-mqtt'; -import db, { ServiceEntry, ServiceSelection } from './db'; +import db, { getSEfromUuid, ServiceEntry, ServiceSelection } from './db'; import { env as getEnv, util, @@ -16,7 +16,7 @@ import { } from 'horme-common'; import ServiceFactory from './service/ServiceFactory'; -export default { cleanUp, configureServices, removeService, stopService }; +export default { cleanUp, configureServices, removeService, stopService, startService }; /** The service UUID. */ export type Uuid = string; @@ -91,8 +91,8 @@ async function removeService(uuid: string): Promise { //await Promise.all(instantiatedServices.map((args) => configureService(args, []))); } -/*async function startService(uuid: string) { - const entry = await queryService(uuid); +export async function startService(uuid: string) { + const entry = await getSEfromUuid(uuid); if (!entry) return; const config = await readConfig(entry.type); if (!config) return; @@ -101,11 +101,11 @@ async function removeService(uuid: string): Promise { const handle = getServiceHandle(entry); handle.info.version++; // TODO: not here.. - configureService(handle, entry.depends); + //configureService(handle, entry.depends); const proc = _startService(entry, config, buildTopic(entry)); handle.proc = proc; updateServiceHandle(handle); -}*/ +} async function stopService(uuid: string) { // stop and remove container, continue rm on stop failure From 41cad5444940dbe2ae30395f32b4b68f73f3f56c Mon Sep 17 00:00:00 2001 From: Fabian During Date: Sun, 7 Mar 2021 12:57:52 +0100 Subject: [PATCH 13/20] Rekonf: Merged alias and uuid Signed-off-by: Fabian During --- horme-common/src/types.ts | 2 +- reconf/config/automations/bedroomLamp.json | 2 +- .../config/automations/bedroomSwitches.json | 4 +- reconf/src/db.ts | 40 ++++++++++--------- reconf/src/service.ts | 14 +++---- 5 files changed, 33 insertions(+), 29 deletions(-) diff --git a/horme-common/src/types.ts b/horme-common/src/types.ts index 22350f9..0d77825 100644 --- a/horme-common/src/types.ts +++ b/horme-common/src/types.ts @@ -59,7 +59,7 @@ export const ServiceConfig = Record({ /*export interface Automation { type: String; - alias: String; + uuid: String; mainDevices: [String]; replacementDevices: [String]; room: String; diff --git a/reconf/config/automations/bedroomLamp.json b/reconf/config/automations/bedroomLamp.json index 9d26a33..743d622 100644 --- a/reconf/config/automations/bedroomLamp.json +++ b/reconf/config/automations/bedroomLamp.json @@ -1,6 +1,6 @@ [{ "type": "ceiling-lamp-switch", - "alias": "bed1", + "uuid": "bed1", "mainDevices": ["bri", "fra"], "replacementDevices": "", "room": "bedroom", diff --git a/reconf/config/automations/bedroomSwitches.json b/reconf/config/automations/bedroomSwitches.json index b36db25..9fa5566 100644 --- a/reconf/config/automations/bedroomSwitches.json +++ b/reconf/config/automations/bedroomSwitches.json @@ -1,6 +1,6 @@ [{ "type": "wall-switch", - "alias": "bri", + "uuid": "bri", "mainDevices": [], "replacementDevices": "switches", "room": "bedroom", @@ -9,7 +9,7 @@ }, { "type": "remote-switch", - "alias": "fra", + "uuid": "fra", "mainDevices": [], "replacementDevices": "switches", "room": "bedroom", diff --git a/reconf/src/db.ts b/reconf/src/db.ts index 8316d9b..59410cf 100644 --- a/reconf/src/db.ts +++ b/reconf/src/db.ts @@ -23,12 +23,12 @@ export type ConfigUpdates = { }; /** The description of an un-instantiated service and its dependencies. */ export type ServiceEntry = { - uuid: Uuid; + //uuid: Uuid; /*type: ServiceType; room: string | null; depends: Uuid[];*/ type: string; - alias: string; + uuid: string; mainDevices: [string]; replacementDevices: [string]; room: string; @@ -48,7 +48,7 @@ async function DataToDB() { await searchMainDevices(); } -//TODO: check for redundant aliases +//TODO: check for redundant uuides async function importAutomations() { logger.info('Import external Automations...'); const automationFolder = './config/automations/'; @@ -67,15 +67,19 @@ async function importAutomations() { let newworld = type.split('-').join('_'); // If Device does not exist, add it to DB - const a: string = 'MATCH (n: Automation:' + newworld + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\' }) RETURN n'; + const a: string = 'MATCH (n: Automation:' + newworld + ' { uuid: \'' + x.uuid + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\' }) RETURN n'; const query = await returnQuery(a); if (query.records.length == 0) { - const b: string = 'CREATE (n: Automation:' + newworld + ' { alias: \'' + x.alias + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\', instantiated: \'false\' })'; + const b: string = 'CREATE (n: Automation:' + newworld + ' { uuid: \'' + x.uuid + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\', instantiated: \'false\' })'; await returnQuery(b); const file = await fs.readFileSync(`./config/services/${type}.json`, 'utf8'); const back = await parseAs(ServiceConfig, JSON.parse(file.toString())); - if (!back) break; + if (!back) { + logger.error('service config could not be parsed for: ' + type); + break; + } + logger.error('service should be instantiated for: ' + x.uuid); await instantiateService(x, back); } }; @@ -108,7 +112,7 @@ async function alternativeConfiguration(dev:string, to:string) { logger.info('searching alternative for device \'' + dev + '\'!'); //get all importent attr from dev - const repldev: string = 'MATCH (n: Automation) WHERE n.alias = \'' + dev + '\' RETURN n.replacementDevices, n.room'; + const repldev: string = 'MATCH (n: Automation) WHERE n.uuid = \'' + dev + '\' RETURN n.replacementDevices, n.room'; let realdev = await returnQuery(repldev); let devGroup = realdev.records[0].get('n.replacementDevices'); @@ -123,11 +127,11 @@ async function alternativeConfiguration(dev:string, to:string) { var splitted = groupres.records[0].get('n.devices').split(',', 30); for (let type of splitted) { type = type.split('-').join('_'); - const e: string = 'MATCH (n: Automation: ' + type + '), ( m: Automation ) WHERE n.online = \'true\' AND m.alias = \'' + to + '\' AND n.room = \'' + room + '\' AND NOT (n)-[:SUBSCRIBE]->(m) RETURN n.alias'; + const e: string = 'MATCH (n: Automation: ' + type + '), ( m: Automation ) WHERE n.online = \'true\' AND m.uuid = \'' + to + '\' AND n.room = \'' + room + '\' AND NOT (n)-[:SUBSCRIBE]->(m) RETURN n.uuid'; let back = await returnQuery(e); if(back.records.length != 0){ logger.info('Found replacement device(s). Yey!'); - return back.records[0].get('n.alias'); + return back.records[0].get('n.uuid'); } else { //logger.error(' '); } @@ -152,21 +156,21 @@ async function initiateDevices(res: QueryResult) { for(const record of res.records) { //iterate over all searched main devices let md = record.get('n.mainDevices'); - let x = record.get('n.alias'); + let x = record.get('n.uuid'); var splitted = md.split(',', 30); for (let dev of splitted) { - const d: string = 'MATCH (n: Automation) WHERE n.alias = \'' + dev + '\' RETURN n.alias, n.replacementDevices'; + const d: string = 'MATCH (n: Automation) WHERE n.uuid = \'' + dev + '\' RETURN n.uuid, n.replacementDevices'; const res1 = await returnQuery(d); if (res1.records.length == 0) { - logger.warn('Device with alias \'' + dev + '\' does not exist'); + logger.warn('Device with uuid \'' + dev + '\' does not exist'); return; } - const e: string = 'MATCH (n: Automation), (m: Automation) WHERE n.online = \'true\' AND n.alias = \'' + dev + '\' AND m.alias = \'' + x + '\' AND NOT (n)-[:SUBSCRIBE]->(m) RETURN n.alias'; + const e: string = 'MATCH (n: Automation), (m: Automation) WHERE n.online = \'true\' AND n.uuid = \'' + dev + '\' AND m.uuid = \'' + x + '\' AND NOT (n)-[:SUBSCRIBE]->(m) RETURN n.uuid'; const res2 = await returnQuery(e); if (res2.records.length == 0) { - logger.warn('Device with alias \'' + dev + '\' is available for this configuration!'); - //get alias from alternative + logger.warn('Device with uuid \'' + dev + '\' is available for this configuration!'); + //get uuid from alternative let alt = await alternativeConfiguration(dev, x); if(alt) { initRelationship(x, alt); @@ -191,7 +195,7 @@ async function searchMainDevices() { logger.info('Searching for main devices'); // Search for devices with all main devices - const a: string = 'MATCH (n: Automation) WHERE NOT n.mainDevices = \'\' RETURN n.mainDevices, n.alias'; + const a: string = 'MATCH (n: Automation) WHERE NOT n.mainDevices = \'\' RETURN n.mainDevices, n.uuid'; const res = await returnQuery(a); initiateDevices(res); @@ -199,9 +203,9 @@ async function searchMainDevices() { async function initRelationship(dev1:string, dev2:string) { logger.info('Adding relation from \"' + dev1 + '\" to \"' + dev2 + '\".'); - const e: string = 'MATCH (n: Automation {alias: \'' + dev1 + '\'}), (m: Automation {alias: \'' + dev2 + '\'}) CREATE (m)-[r:SUBSCRIBE]->(n)'; + const e: string = 'MATCH (n: Automation {uuid: \'' + dev1 + '\'}), (m: Automation {uuid: \'' + dev2 + '\'}) CREATE (m)-[r:SUBSCRIBE]->(n)'; await returnQuery(e); - const g: string = 'MATCH (n: Automation {alias: \'' + dev1 + '\'}) SET n.initiated = \'true\''; + const g: string = 'MATCH (n: Automation {uuid: \'' + dev1 + '\'}) SET n.initiated = \'true\''; await returnQuery(g); return; } diff --git a/reconf/src/service.ts b/reconf/src/service.ts index 0c12334..1739402 100644 --- a/reconf/src/service.ts +++ b/reconf/src/service.ts @@ -66,12 +66,12 @@ async function removeService(uuid: string): Promise { //const newServices = reconfiguration /*const newServices = Array.from( reconfiguration.flatMap(([_, instances]) => { - return instances.map((instance) => instance.alias); + return instances.map((instance) => instance.uuid); }) );*/ // determine services which are no longer present in updated service selection - //const removals = previousServices.filter((prev) => newServices.forEach((test) => test.alias != prev.info.uuid)) + //const removals = previousServices.filter((prev) => newServices.forEach((test) => test.uuid != prev.info.uuid)) // remove all services no longer present in the new configuration and kill their respective // processes @@ -181,7 +181,7 @@ function _startService(entry: ServiceEntry, config: ServiceConfig, topic: string '-t', '--rm', '--name', - serviceNamePrefix + entry.alias, + serviceNamePrefix + entry.uuid, '-e', 'HORME_LOG_LEVEL=' + env.logLevel, '-e', @@ -189,7 +189,7 @@ function _startService(entry: ServiceEntry, config: ServiceConfig, topic: string '-e', 'HORME_SERVICE_TOPIC=' + topic, '-e', - 'HORME_SERVICE_UUID=' + entry.alias, + 'HORME_SERVICE_UUID=' + entry.uuid, '--network', 'horme_default', config.image, @@ -199,7 +199,7 @@ function _startService(entry: ServiceEntry, config: ServiceConfig, topic: string logger.debug(`Service start: ${cmd}`); const instance = spawn('docker', cmd); instance.stdout.on('data', (data: Buffer) => { - console.log(`\tfrom '${entry.type}/${chalk.underline(entry.alias)}' (stdout):`); + console.log(`\tfrom '${entry.type}/${chalk.underline(entry.uuid)}' (stdout):`); const lines = data.toString('utf-8').split('\n'); for (const line of lines) { console.log(`\t${line}`); @@ -207,7 +207,7 @@ function _startService(entry: ServiceEntry, config: ServiceConfig, topic: string }); instance.stderr.on('data', (data: Buffer) => { - console.log(`\tfrom '${entry.type}/${chalk.underline(entry.alias)}' (stderr):`); + console.log(`\tfrom '${entry.type}/${chalk.underline(entry.uuid)}' (stderr):`); const lines = data.toString('utf-8').split('\n'); for (const line of lines) { console.log(`\t${line}`); @@ -290,5 +290,5 @@ export function buildTopic(entry: ServiceEntry): string { entry.room !== null ? `${process.env.HORME_APARTMENT}/${entry.room}` : `${process.env.HORME_APARTMENT}/global`; - return `${base}/${entry.type}${entry.alias}`; + return `${base}/${entry.type}${entry.uuid}`; } From dea0a73ec644ca37f1f8d74547e5510450bda93b Mon Sep 17 00:00:00 2001 From: Fabian During Date: Sat, 13 Mar 2021 17:26:11 +0100 Subject: [PATCH 14/20] Rekonf: Send config to devices Signed-off-by: Fabian During --- reconf/src/db.ts | 12 +++++++----- reconf/src/service.ts | 21 ++++++++++++++++++++- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/reconf/src/db.ts b/reconf/src/db.ts index 59410cf..9545bee 100644 --- a/reconf/src/db.ts +++ b/reconf/src/db.ts @@ -1,10 +1,11 @@ import { returnQuery } from './neo4j'; -import { ServiceType, Uuid } from './service'; +import { configureServiceUUID, ServiceType, Uuid } from './service'; import fs from 'fs/promises'; import { env as getEnv, util, ConfigMessage, Subscription, ServiceConfig, ServiceInfo, parseAs } from 'horme-common'; import path from 'path'; import { QueryResult } from 'neo4j-driver'; import { instantiateService } from './service'; +import { Server } from 'http'; export default { DataToDB }; @@ -76,10 +77,8 @@ async function importAutomations() { const file = await fs.readFileSync(`./config/services/${type}.json`, 'utf8'); const back = await parseAs(ServiceConfig, JSON.parse(file.toString())); if (!back) { - logger.error('service config could not be parsed for: ' + type); break; } - logger.error('service should be instantiated for: ' + x.uuid); await instantiateService(x, back); } }; @@ -159,6 +158,7 @@ async function initiateDevices(res: QueryResult) { let x = record.get('n.uuid'); var splitted = md.split(',', 30); for (let dev of splitted) { + logger.error('Initiate: ' + dev); const d: string = 'MATCH (n: Automation) WHERE n.uuid = \'' + dev + '\' RETURN n.uuid, n.replacementDevices'; const res1 = await returnQuery(d); if (res1.records.length == 0) { @@ -181,10 +181,12 @@ async function initiateDevices(res: QueryResult) { } else { initRelationship(x, dev); } + configureServiceUUID(dev); } - + configureServiceUUID(x); }; + } else { logger.error('got empty set'); } @@ -196,7 +198,6 @@ async function searchMainDevices() { // Search for devices with all main devices const a: string = 'MATCH (n: Automation) WHERE NOT n.mainDevices = \'\' RETURN n.mainDevices, n.uuid'; - const res = await returnQuery(a); initiateDevices(res); } @@ -236,3 +237,4 @@ async function importDeviceGroups() { }; }; } + diff --git a/reconf/src/service.ts b/reconf/src/service.ts index 1739402..1463c4b 100644 --- a/reconf/src/service.ts +++ b/reconf/src/service.ts @@ -15,6 +15,7 @@ import { parseAs, } from 'horme-common'; import ServiceFactory from './service/ServiceFactory'; +import { returnQuery } from './neo4j'; export default { cleanUp, configureServices, removeService, stopService, startService }; @@ -101,7 +102,7 @@ export async function startService(uuid: string) { const handle = getServiceHandle(entry); handle.info.version++; // TODO: not here.. - //configureService(handle, entry.depends); + const proc = _startService(entry, config, buildTopic(entry)); handle.proc = proc; updateServiceHandle(handle); @@ -160,6 +161,24 @@ export async function instantiateService( } } +/** Sets the dependencies of the corresponding service instance. */ +export async function configureServiceUUID(uuid: string) { + logger.error('Configuring Service: ' + uuid); + const config: string = 'MATCH (m: Automation { uuid: \'' + uuid +'\' }), ( n: Automation ) WHERE (n)-[:SUBSCRIBE]->(m) RETURN n.uuid'; + let depen = await returnQuery(config); + let uuids: string[] = []; + for(let x of depen.records) { + uuids.push(x.get('n.uuid')); + } + let i = await getSEfromUuid(uuid); + if (typeof i !== 'undefined') { + let handle = getServiceHandle(i); + configureService(handle, uuids); + } else { + logger.error('No ServiceEntry found..'); + } +} + /** Sets the dependencies of the corresponding service instance. */ async function configureService(service: ServiceHandle, depends: Uuid[], init = false) { const { add, del } = setServiceDependencies(service, depends); From 90ab4fc85cc5dbecd9ed1cf809d12294457a8bc8 Mon Sep 17 00:00:00 2001 From: Fabian During Date: Thu, 18 Mar 2021 10:43:47 +0100 Subject: [PATCH 15/20] Rekonf: added api for db Signed-off-by: Fabian During --- reconf/src/PersistentStorage.ts | 24 ++++++ reconf/src/db.ts | 72 +++++++++++------- reconf/src/db/PersistentStorageQueries.ts | 91 ++++++++++++++++++++++ reconf/src/neo4j.ts | 93 ----------------------- 4 files changed, 160 insertions(+), 120 deletions(-) create mode 100644 reconf/src/PersistentStorage.ts create mode 100644 reconf/src/db/PersistentStorageQueries.ts diff --git a/reconf/src/PersistentStorage.ts b/reconf/src/PersistentStorage.ts new file mode 100644 index 0000000..a676c47 --- /dev/null +++ b/reconf/src/PersistentStorage.ts @@ -0,0 +1,24 @@ +import { ServiceEntry } from "./db"; + +/** The service UUID. */ +export type Uuid = string; +export type Room = string; + +/** The string describing the type of a service. */ +export type ServiceType = string; + +/** The array of selected service type and instances. */ +export type ServiceSelection = [ServiceType, ServiceEntry[]][]; +/** Options for specifying which changes need to be made in the database. */ +export type ConfigUpdates = { + del: Uuid[]; +}; + +export interface PersistentStorage { + createService(service: ServiceEntry): void; + updateService(service: ServiceEntry): Promise; + removeService(uuid: Uuid): Promise; + queryServices(): Promise; + queryService(uuid: Uuid): Promise; + queryServicesInRoom(room: Room): Promise; +}; \ No newline at end of file diff --git a/reconf/src/db.ts b/reconf/src/db.ts index 9545bee..b5df0aa 100644 --- a/reconf/src/db.ts +++ b/reconf/src/db.ts @@ -1,14 +1,14 @@ import { returnQuery } from './neo4j'; import { configureServiceUUID, ServiceType, Uuid } from './service'; -import fs from 'fs/promises'; -import { env as getEnv, util, ConfigMessage, Subscription, ServiceConfig, ServiceInfo, parseAs } from 'horme-common'; +import { ServiceConfig, parseAs, util } from 'horme-common'; import path from 'path'; import { QueryResult } from 'neo4j-driver'; import { instantiateService } from './service'; -import { Server } from 'http'; export default { DataToDB }; +const logger = util.logger; + /** The array of selected service type and instances. */ export type ServiceSelection = [ServiceType, ServiceEntry[]][]; @@ -37,21 +37,21 @@ export type ServiceEntry = { online: boolean; }; -const logger = util.logger; - let allSE: Array = []; /********** implementation ************************************************************************/ async function DataToDB() { + logger.info('Import external Automations...'); await importAutomations(); + logger.info('Import external DeviceGroups...'); await importDeviceGroups(); + logger.info('Import external MainDevices...'); await searchMainDevices(); } //TODO: check for redundant uuides async function importAutomations() { - logger.info('Import external Automations...'); const automationFolder = './config/automations/'; const fs = require('fs'); const files = await fs.readdirSync(automationFolder); @@ -62,25 +62,7 @@ async function importAutomations() { let config: Array = JSON.parse(fs.readFileSync(fullPath.toString(), 'utf8')); //config.forEach(x => for (const x of config) { - - //Walkaround for illegal '-' in typename - let type = x.type; - let newworld = type.split('-').join('_'); - - // If Device does not exist, add it to DB - const a: string = 'MATCH (n: Automation:' + newworld + ' { uuid: \'' + x.uuid + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\' }) RETURN n'; - - const query = await returnQuery(a); - if (query.records.length == 0) { - const b: string = 'CREATE (n: Automation:' + newworld + ' { uuid: \'' + x.uuid + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\', instantiated: \'false\' })'; - await returnQuery(b); - const file = await fs.readFileSync(`./config/services/${type}.json`, 'utf8'); - const back = await parseAs(ServiceConfig, JSON.parse(file.toString())); - if (!back) { - break; - } - await instantiateService(x, back); - } + addService(x); }; allSE = allSE.concat(config); }; @@ -131,14 +113,26 @@ async function alternativeConfiguration(dev:string, to:string) { if(back.records.length != 0){ logger.info('Found replacement device(s). Yey!'); return back.records[0].get('n.uuid'); - } else { - //logger.error(' '); } } return null; } + +//remove automation from db +//TODO: remove depends +export async function removeService(uuid: string): Promise { + const a: string = 'MATCH (n: Automation { uuid: \'' + uuid + '\' }) RETURN n'; + let back = await returnQuery(a); + if(back.records.length != 0){ + logger.info('Removing automation with uuid \'' + uuid + '\'!'); + + const removeQuery: string = 'MATCH (n: Automation { uuid: \'' + uuid + '\' }) DETACH DELETE n'; + back = await returnQuery(removeQuery); + } +} + async function initMissingAutomations() { logger.info('Try to add not instantiated devices to the database'); @@ -238,3 +232,27 @@ async function importDeviceGroups() { }; } +export async function addService(x: ServiceEntry){ + + const fs = require('fs'); + + //Walkaround for illegal '-' in typename + let type = x.type; + let newworld = type.split('-').join('_'); + + // If Device does not exist, add it to DB + const a: string = 'MATCH (n: Automation:' + newworld + ' { uuid: \'' + x.uuid + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\' }) RETURN n'; + + const query = await returnQuery(a); + if (query.records.length == 0) { + const b: string = 'CREATE (n: Automation:' + newworld + ' { uuid: \'' + x.uuid + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\', instantiated: \'false\' })'; + await returnQuery(b); + const file = await fs.readFileSync(`./config/services/${type}.json`, 'utf8'); + const back = await parseAs(ServiceConfig, JSON.parse(file.toString())); + if (!back) { + return; + } + await instantiateService(x, back); + } +} + diff --git a/reconf/src/db/PersistentStorageQueries.ts b/reconf/src/db/PersistentStorageQueries.ts new file mode 100644 index 0000000..bd1f017 --- /dev/null +++ b/reconf/src/db/PersistentStorageQueries.ts @@ -0,0 +1,91 @@ + +import { addService, removeService, ServiceEntry } from "../db"; +import { returnQuery } from "../neo4j"; +import { PersistentStorage } from "../PersistentStorage"; +import { util } from 'horme-common'; + +const logger = util.logger; + +export class PseudoPersistentStorage implements PersistentStorage { + + //Adds ServiceEntry to db and instantiateService + //TODO: Add return for successful instantiation + async createService(service: ServiceEntry) { + addService(service); + } + async updateService(service: ServiceEntry): Promise { + throw new Error("Method not implemented."); + } + + //Removes service from DB + async removeService(uuid: string): Promise { + removeService(uuid); + } + + //returns all existing automations in the database as ServiceEntity + async queryServices(): Promise { + const entries: ServiceEntry[] = []; + const a: string = 'MATCH (n: Automation RETURN n'; + const query = await returnQuery(a); + if (query.records.length != 0) { + for (let x of query.records) { + const entry: ServiceEntry = { + type: x.get('n.type'), + uuid: x.get('n.uuid'), + mainDevices: x.get('n.mainDevices'), + replacementDevices: x.get('n.replacementDevices'), + room: x.get('n.room'), + configMsg: x.get('n.configMsg'), + online: x.get('n.online'), + }; + entries.push(entry); + } + + } + return entries; + } + + //returns service entry of service with uuid + async queryService(uuid: string): Promise { + const a: string = 'MATCH (n: Automation { uuid: \'' + uuid + '\'}) RETURN n'; + const query = await returnQuery(a); + if (query.records.length != 0) { + if (query.records.length > 0) { + logger.error('possible redundancies found') + } + const entry: ServiceEntry = { + type: query.records[0].get('n.type'), + uuid: query.records[0].get('n.uuid'), + mainDevices: query.records[0].get('n.mainDevices'), + replacementDevices: query.records[0].get('n.replacementDevices'), + room: query.records[0].get('n.room'), + configMsg: query.records[0].get('n.configMsg'), + online: query.records[0].get('n.online'), + }; + return entry; + + } + return undefined; + } + + //get all service entries from room + async queryServicesInRoom(room: string): Promise { + const entries: ServiceEntry[] = []; + const a: string = 'MATCH (n: Automation: { room: \'' + room + '\'}) RETURN n'; + const query = await returnQuery(a); + for (let x of query.records) { + const entry: ServiceEntry = { + type: x.get('n.type'), + uuid: x.get('n.uuid'), + mainDevices: x.get('n.mainDevices'), + replacementDevices: x.get('n.replacementDevices'), + room: x.get('n.room'), + configMsg: x.get('n.configMsg'), + online: x.get('n.online'), + }; + entries.push(entry); + + } + return entries; + } +}; diff --git a/reconf/src/neo4j.ts b/reconf/src/neo4j.ts index 5c0c6cb..f083eb7 100644 --- a/reconf/src/neo4j.ts +++ b/reconf/src/neo4j.ts @@ -52,54 +52,7 @@ export async function returnQuery(n :string): Promise { await connectNeo4j(); } const session = driver.session(); - //let entireResult = ''; - //let json: String = '['; let result = await session.run(n); - /*let result = session.run(n).then(function (result) { - return result; - result.records.forEach(function (record) { - json = json + '{'; - logger.info(record.keys.toString); - for(const x in record.keys) { - logger.info(x); - json += record.get(x); - } - json += record.entries.toString(); - json += '},'; - }); - json = json.substring(0, json.length - 1); - json += ']'; - entireResult += json; - session.close(); - }).catch(function (error) { - console.log(error); - });*/ - - /*await session.run(n).then(result => { - json = '['; - - if (result.records.length == 0) { - return ''; - } else { - logger.error(result.records[0].keys); - } - result.records.map(record => { // Iterate through records - json = json + '{'; - record.map(elem => { - json += elem; - }); - json += '},'; - //entireResult = record.get('n'); // Access the name property from the RETURN statement - }); - json = json.substring(0, json.length - 1); - json += ']'; - entireResult += json; - }) - .then(() => { - session.close();}); - if (entireResult != '') { - logger.info(entireResult); - }*/ return result; } @@ -111,29 +64,6 @@ async function updateAllDependencies(config: [string, ServiceEntry[]][]) { //Reset all current dependencies, as device dependencies may change during reconfiguration await resetAllDependencies(); - - /*for (const element of config) { - for (const elem2 of element[1]) { - for (const deps of elem2.) { - - //if dependency dev exists - const dev: string = 'MATCH (n) WHERE n.uuid = \'' + deps + '\' RETURN n'; - const res = await returnQuery(dev); - if (res.records.length != 0) { - - //check if relation already exists - const checkrel: string = 'MATCH (n)-[DEPENDS_ON]->(m) WHERE n.uuid = \'' + elem2.uuid + '\' AND m.uuid = \'' + deps + '\' RETURN n'; - const result = await returnQuery(checkrel); - if (result.records.length != 0) { - - //create relation - const newrel: string = 'MATCH (n), (m) WHERE n.uuid = \'' + elem2.uuid + '\' AND m.uuid = \'' + deps + '\' CREATE (n)-[r:DEPENDS_ON]->(m)'; - await returnQuery(newrel); - } - } - } - } - }*/ } export async function updateDatabase(topic: string, message: string) { @@ -164,29 +94,6 @@ export async function addConfigToDB(config: [string, ServiceEntry[]][]): Promise //Walkaround for illegal '-' in typename let type = elem2.type; type = type.split('-').join('_'); - - //Check if Service does exist - /*const a: string = 'MATCH (n:' + type + ' { uuid: \'' + elem2.uuid + '\' }) RETURN n'; - if (await returnQuery(a) == '') { - const b: string = 'CREATE (n:' + type + ' { uuid: \'' + elem2.uuid + '\'})'; - await returnQuery(b); - - //check if Room exist - if(elem2.room) { - const room: string = 'MATCH (n:Room { name: \'' + elem2.room + '\'}) RETURN n'; - const me = await returnQuery(room); - if (me == '') { - const newroom: string = 'CREATE (n:Room { name: \'' + elem2.room + '\'})'; - await returnQuery(newroom); - } - - //set service and room in ralationship - const newroom: string = 'MATCH (n:Room), (m:' + type + ') WHERE n.name = \'' + elem2.room + '\' AND m.uuid = \'' + elem2.uuid + '\' CREATE (m)-[r:BELONGS_TO]->(n)'; - await returnQuery(newroom); - } - - } - */ } } From 9964fe6fe897071e6513258d443f29b9f3d7e765 Mon Sep 17 00:00:00 2001 From: Fabian During Date: Sun, 21 Mar 2021 11:28:30 +0100 Subject: [PATCH 16/20] Rekonf: cleanup & renaming Signed-off-by: Fabian During --- horme-common/src/types.ts | 9 -- .../bedroomLamp.json | 0 .../bedroomSwitches.json | 0 reconf/src/app.ts | 5 +- reconf/src/db.ts | 142 +++++++++--------- reconf/src/db/PersistentStorageQueries.ts | 10 +- reconf/src/service.ts | 100 ++++-------- 7 files changed, 103 insertions(+), 163 deletions(-) rename reconf/config/{automations => serviceconf}/bedroomLamp.json (100%) rename reconf/config/{automations => serviceconf}/bedroomSwitches.json (100%) diff --git a/horme-common/src/types.ts b/horme-common/src/types.ts index 0d77825..397b009 100644 --- a/horme-common/src/types.ts +++ b/horme-common/src/types.ts @@ -57,15 +57,6 @@ export const ServiceConfig = Record({ args: Array(String), }); -/*export interface Automation { - type: String; - uuid: String; - mainDevices: [String]; - replacementDevices: [String]; - room: String; - configMsg: String; -};*/ - export type ServiceConfig = Static; export const parseAs = >(r: R, msg: any): Static | undefined => { diff --git a/reconf/config/automations/bedroomLamp.json b/reconf/config/serviceconf/bedroomLamp.json similarity index 100% rename from reconf/config/automations/bedroomLamp.json rename to reconf/config/serviceconf/bedroomLamp.json diff --git a/reconf/config/automations/bedroomSwitches.json b/reconf/config/serviceconf/bedroomSwitches.json similarity index 100% rename from reconf/config/automations/bedroomSwitches.json rename to reconf/config/serviceconf/bedroomSwitches.json diff --git a/reconf/src/app.ts b/reconf/src/app.ts index 0c07634..6b4e3d6 100644 --- a/reconf/src/app.ts +++ b/reconf/src/app.ts @@ -3,7 +3,7 @@ import 'source-map-support/register'; import { env as getEnv, util } from 'horme-common'; import fail from './fail'; import srv from './service'; -import db from './db'; +import db, { initMissingServices } from './db'; import { resetDatabase } from './neo4j'; const env = getEnv.readEnvironment('reconf'); @@ -23,8 +23,7 @@ main().catch((err) => util.abort(err)); async function main() { logger.setLogLevel(env.logLevel); await resetDatabase(); - await db.DataToDB(); + await db.initializeDatabase(); await fail.setupFailureListener(); - await srv.configureServices(); logger.info('initial configuration instantiated, listening...'); } diff --git a/reconf/src/db.ts b/reconf/src/db.ts index b5df0aa..89f599d 100644 --- a/reconf/src/db.ts +++ b/reconf/src/db.ts @@ -1,11 +1,11 @@ import { returnQuery } from './neo4j'; -import { configureServiceUUID, ServiceType, Uuid } from './service'; +import { setDependencies, ServiceType, Uuid } from './service'; import { ServiceConfig, parseAs, util } from 'horme-common'; import path from 'path'; import { QueryResult } from 'neo4j-driver'; -import { instantiateService } from './service'; +import { createService } from './service'; -export default { DataToDB }; +export default { initializeDatabase }; const logger = util.logger; @@ -17,17 +17,12 @@ export type DeviceGroup = { types: [string]; }; -export type AutomationSelection = [ServiceType, ServiceEntry[]][]; /** Options for specifying which changes need to be made in the database. */ export type ConfigUpdates = { del: Uuid[]; }; /** The description of an un-instantiated service and its dependencies. */ export type ServiceEntry = { - //uuid: Uuid; - /*type: ServiceType; - room: string | null; - depends: Uuid[];*/ type: string; uuid: string; mainDevices: [string]; @@ -37,54 +32,51 @@ export type ServiceEntry = { online: boolean; }; -let allSE: Array = []; - /********** implementation ************************************************************************/ -async function DataToDB() { - logger.info('Import external Automations...'); - await importAutomations(); +async function initializeDatabase() { + logger.info('Import external Services...'); + await importServices(); logger.info('Import external DeviceGroups...'); await importDeviceGroups(); logger.info('Import external MainDevices...'); - await searchMainDevices(); + await initMissingServices(); } //TODO: check for redundant uuides -async function importAutomations() { - const automationFolder = './config/automations/'; +async function importServices() { + const serviceFolder = './config/serviceconf/'; const fs = require('fs'); - const files = await fs.readdirSync(automationFolder); + const files = await fs.readdirSync(serviceFolder); - //await fs.readdirSync(automationFolder).forEach(async (file: any) => { for (let file of files) { - let fullPath = path.join(automationFolder, file); + let fullPath = path.join(serviceFolder, file); let config: Array = JSON.parse(fs.readFileSync(fullPath.toString(), 'utf8')); - //config.forEach(x => for (const x of config) { addService(x); }; - allSE = allSE.concat(config); }; }; export async function getSEfromUuid(uuid: string): Promise { - for (const x of allSE) { - if (x.uuid == uuid) return x; + const a: string = 'MATCH (n: Service { uuid: \'' + uuid + '\'}) RETURN n.type, n.uuid, n.mainDevices, n.replacementDevices, n.room, n.configMsg, n.online'; + const query = await returnQuery(a); + if (query.records.length != 0) { + let x = query.records[0]; + const entry: ServiceEntry = { + type: x.get('n.type'), + uuid: x.get('n.uuid'), + mainDevices: x.get('n.mainDevices'), + replacementDevices: x.get('n.replacementDevices'), + room: x.get('n.room'), + configMsg: x.get('n.configMsg'), + online: x.get('n.online'), + }; + return entry; } return undefined; } -//depricated -/*export async function queryService(uuid: string): Promise { - for (let [_, value] of config) { - for (let entry of value) { - if (entry.uuid === uuid) return entry; - } - } - return undefined; -}*/ - //TODO: currently always first replacement devices from type t is used //BUG: When searching a replacement device for a device which is offline and the device-group of the missing device-type is the same as a later main device, // - the replacement device could 'steal' the device of a later main-device, which then also needs a replacement device, as the main device is already in use. @@ -92,8 +84,8 @@ export async function getSEfromUuid(uuid: string): Promise(m) RETURN n.uuid'; + + //for each replacement type, search for devices in the current room + const e: string = 'MATCH (n: Service: ' + type + '), ( m: Service ) WHERE n.online = \'true\' AND m.uuid = \'' + to + '\' AND n.room = \'' + room + '\' AND NOT (n)-[:SUBSCRIBE]->(m) RETURN n.uuid'; let back = await returnQuery(e); if(back.records.length != 0){ - logger.info('Found replacement device(s). Yey!'); - return back.records[0].get('n.uuid'); + let newuuid = back.records[0].get('n.uuid'); + logger.debug('Found replacement device for \'' + dev + '\' with name \'' + newuuid + '\''); + return newuuid; } } return null; } - -//remove automation from db +//remove service from db //TODO: remove depends export async function removeService(uuid: string): Promise { - const a: string = 'MATCH (n: Automation { uuid: \'' + uuid + '\' }) RETURN n'; + const a: string = 'MATCH (n: Service { uuid: \'' + uuid + '\' }) RETURN n'; let back = await returnQuery(a); if(back.records.length != 0){ - logger.info('Removing automation with uuid \'' + uuid + '\'!'); + logger.info('Removing service with uuid \'' + uuid + '\'!'); - const removeQuery: string = 'MATCH (n: Automation { uuid: \'' + uuid + '\' }) DETACH DELETE n'; + //DETACH implies that all relations are deleted too + const removeQuery: string = 'MATCH (n: Service { uuid: \'' + uuid + '\' }) DETACH DELETE n'; back = await returnQuery(removeQuery); } } -async function initMissingAutomations() { - logger.info('Try to add not instantiated devices to the database'); - // Search for devices which are not instantiated - const a: string = 'MATCH (n: Automation) WHERE n.instantiated = \'false\' RETURN n.mainDevices'; +//should be called when new devices are added to net network/switched state to online again +export async function initMissingServices() { + // Search for devices which are not configured + const a: string = 'MATCH (n: Service) WHERE n.configured = \'false\' AND NOT n.mainDevices = \'\' RETURN n.mainDevices, n.uuid'; const res = await returnQuery(a); - initiateDevices(res); + configureServices(res); } -async function initiateDevices(res: QueryResult) { + +//create configurations for services +async function configureServices(res: QueryResult) { if (res.records.length != 0) { //iterate over all devices with main devices for(const record of res.records) { @@ -152,32 +149,33 @@ async function initiateDevices(res: QueryResult) { let x = record.get('n.uuid'); var splitted = md.split(',', 30); for (let dev of splitted) { - logger.error('Initiate: ' + dev); - const d: string = 'MATCH (n: Automation) WHERE n.uuid = \'' + dev + '\' RETURN n.uuid, n.replacementDevices'; + const d: string = 'MATCH (n: Service) WHERE n.uuid = \'' + dev + '\' RETURN n.uuid, n.replacementDevices'; const res1 = await returnQuery(d); if (res1.records.length == 0) { logger.warn('Device with uuid \'' + dev + '\' does not exist'); return; } - const e: string = 'MATCH (n: Automation), (m: Automation) WHERE n.online = \'true\' AND n.uuid = \'' + dev + '\' AND m.uuid = \'' + x + '\' AND NOT (n)-[:SUBSCRIBE]->(m) RETURN n.uuid'; + const e: string = 'MATCH (n: Service), (m: Service) WHERE n.online = \'true\' AND n.uuid = \'' + dev + '\' AND m.uuid = \'' + x + '\' AND NOT (n)-[:SUBSCRIBE]->(m) RETURN n.uuid'; const res2 = await returnQuery(e); if (res2.records.length == 0) { - logger.warn('Device with uuid \'' + dev + '\' is available for this configuration!'); + logger.warn('Device with uuid \'' + dev + '\' is not available for this configuration!'); //get uuid from alternative let alt = await alternativeConfiguration(dev, x); if(alt) { initRelationship(x, alt); } else { - logger.info('No Device found :('); + logger.info('No replacement device found for \'' + dev + '\''); return; } } else { initRelationship(x, dev); } - configureServiceUUID(dev); + setDependencies(dev); } - configureServiceUUID(x); + setDependencies(x); + const finished: string = 'MATCH (n: Service { uuid: \'' + x + '\' }) SET n.configured = \'true\''; + await returnQuery(finished); }; @@ -186,32 +184,22 @@ async function initiateDevices(res: QueryResult) { } } -async function searchMainDevices() { - - logger.info('Searching for main devices'); - - // Search for devices with all main devices - const a: string = 'MATCH (n: Automation) WHERE NOT n.mainDevices = \'\' RETURN n.mainDevices, n.uuid'; - const res = await returnQuery(a); - initiateDevices(res); -} - async function initRelationship(dev1:string, dev2:string) { logger.info('Adding relation from \"' + dev1 + '\" to \"' + dev2 + '\".'); - const e: string = 'MATCH (n: Automation {uuid: \'' + dev1 + '\'}), (m: Automation {uuid: \'' + dev2 + '\'}) CREATE (m)-[r:SUBSCRIBE]->(n)'; + const e: string = 'MATCH (n: Service {uuid: \'' + dev1 + '\'}), (m: Service {uuid: \'' + dev2 + '\'}) CREATE (m)-[r:SUBSCRIBE]->(n)'; await returnQuery(e); - const g: string = 'MATCH (n: Automation {uuid: \'' + dev1 + '\'}) SET n.initiated = \'true\''; + const g: string = 'MATCH (n: Service {uuid: \'' + dev1 + '\'}) SET n.initiated = \'true\''; await returnQuery(g); return; } +//read device types from json async function importDeviceGroups() { logger.info('Import external Device Groups...'); const deviceGroupsFolder = './config/device-groups/'; const fs = require('fs'); const files = await fs.readdirSync(deviceGroupsFolder); - //await fs.readdirSync(deviceGroupsFolder).forEach(async (file: any) => { for (let file of files) { let fullPath = path.join(deviceGroupsFolder, file); let config: Array = JSON.parse(fs.readFileSync(fullPath.toString(), 'utf8')); @@ -232,6 +220,7 @@ async function importDeviceGroups() { }; } +//create service based on a service entry export async function addService(x: ServiceEntry){ const fs = require('fs'); @@ -241,18 +230,25 @@ export async function addService(x: ServiceEntry){ let newworld = type.split('-').join('_'); // If Device does not exist, add it to DB - const a: string = 'MATCH (n: Automation:' + newworld + ' { uuid: \'' + x.uuid + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\' }) RETURN n'; + const a: string = 'MATCH (n: Service:' + newworld + ' { uuid: \'' + x.uuid + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\' }) RETURN n'; const query = await returnQuery(a); if (query.records.length == 0) { - const b: string = 'CREATE (n: Automation:' + newworld + ' { uuid: \'' + x.uuid + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\', instantiated: \'false\' })'; - await returnQuery(b); + + //check if service can be configured (has main devices) + if (x.mainDevices.length > 0) { + const b: string = 'CREATE (n: Service:' + newworld + ' { uuid: \'' + x.uuid + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\', configured: \'false\' })'; + await returnQuery(b); + } else { + const b: string = 'CREATE (n: Service:' + newworld + ' { uuid: \'' + x.uuid + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\', configured: \'true\' })'; + await returnQuery(b); + } const file = await fs.readFileSync(`./config/services/${type}.json`, 'utf8'); const back = await parseAs(ServiceConfig, JSON.parse(file.toString())); if (!back) { return; } - await instantiateService(x, back); + await createService(x, back); } } diff --git a/reconf/src/db/PersistentStorageQueries.ts b/reconf/src/db/PersistentStorageQueries.ts index bd1f017..edc132d 100644 --- a/reconf/src/db/PersistentStorageQueries.ts +++ b/reconf/src/db/PersistentStorageQueries.ts @@ -6,6 +6,8 @@ import { util } from 'horme-common'; const logger = util.logger; +//This class is used as an api for communication with the db + export class PseudoPersistentStorage implements PersistentStorage { //Adds ServiceEntry to db and instantiateService @@ -22,10 +24,10 @@ export class PseudoPersistentStorage implements PersistentStorage { removeService(uuid); } - //returns all existing automations in the database as ServiceEntity + //returns all existing service in the database as ServiceEntity async queryServices(): Promise { const entries: ServiceEntry[] = []; - const a: string = 'MATCH (n: Automation RETURN n'; + const a: string = 'MATCH (n: Service) RETURN n'; const query = await returnQuery(a); if (query.records.length != 0) { for (let x of query.records) { @@ -47,7 +49,7 @@ export class PseudoPersistentStorage implements PersistentStorage { //returns service entry of service with uuid async queryService(uuid: string): Promise { - const a: string = 'MATCH (n: Automation { uuid: \'' + uuid + '\'}) RETURN n'; + const a: string = 'MATCH (n: Service { uuid: \'' + uuid + '\'}) RETURN n'; const query = await returnQuery(a); if (query.records.length != 0) { if (query.records.length > 0) { @@ -71,7 +73,7 @@ export class PseudoPersistentStorage implements PersistentStorage { //get all service entries from room async queryServicesInRoom(room: string): Promise { const entries: ServiceEntry[] = []; - const a: string = 'MATCH (n: Automation: { room: \'' + room + '\'}) RETURN n'; + const a: string = 'MATCH (n: Service: { room: \'' + room + '\'}) RETURN n'; const query = await returnQuery(a); for (let x of query.records) { const entry: ServiceEntry = { diff --git a/reconf/src/service.ts b/reconf/src/service.ts index 1463c4b..8eb3ca8 100644 --- a/reconf/src/service.ts +++ b/reconf/src/service.ts @@ -4,7 +4,7 @@ import fs from 'fs/promises'; import chalk from 'chalk'; import mqtt from 'async-mqtt'; -import db, { getSEfromUuid, ServiceEntry, ServiceSelection } from './db'; +import db, { getSEfromUuid, ServiceEntry, removeService } from './db'; import { env as getEnv, util, @@ -17,7 +17,7 @@ import { import ServiceFactory from './service/ServiceFactory'; import { returnQuery } from './neo4j'; -export default { cleanUp, configureServices, removeService, stopService, startService }; +export default { cleanUp, configureService: setDependencies, removeService: _removeService, stopService, startService }; /** The service UUID. */ export type Uuid = string; @@ -43,55 +43,6 @@ const services: Map = new Map(); const factory = new ServiceFactory(); const serviceNamePrefix = 'horme-'; -/** Instantiates and configures the set of services selected from the database. */ -async function configureServices(): Promise { - // query current service selection from database - // Only need to be done one per execution - //const result = await db.queryServiceSelection(); - - // instantiate all not yet instantiated services, insert them into global map - //Dont need services. Can be extracted from database. - //const instantiated = await instantiateServices(); - // set and configure all service dependencies - //await Promise.all(instantiated.map((args) => configureService(args, [] ,true))); -} - -/** Removes the service with the given `uuid` and triggers a full service selection - * and configuration update. */ -async function removeService(uuid: string): Promise { - // retrieve updated service selection from database - //TODO - //const reconfiguration = await db.queryServiceSelection({ del: [uuid] }); - - const previousServices = Array.from(services.values()); - //const newServices = reconfiguration - /*const newServices = Array.from( - reconfiguration.flatMap(([_, instances]) => { - return instances.map((instance) => instance.uuid); - }) - );*/ - - // determine services which are no longer present in updated service selection - //const removals = previousServices.filter((prev) => newServices.forEach((test) => test.uuid != prev.info.uuid)) - - // remove all services no longer present in the new configuration and kill their respective - // processes - /*for (const service of removals) { - logger.warn('killing process of service ' + chalk.underline(service.info.uuid)); - - stopService(service.info.uuid); - services.delete(service.info.uuid); - }*/ - - // instantiate all new services - //TODO - //const instantiatedServices = await instantiateServices(reconfiguration); - - // configure all newly instantiated services and re-configure all changed services - logger.info('initiating service reconfiguration...'); - //await Promise.all(instantiatedServices.map((args) => configureService(args, []))); -} - export async function startService(uuid: string) { const entry = await getSEfromUuid(uuid); if (!entry) return; @@ -119,30 +70,13 @@ function cleanUp(): void { execSync(`docker rm $(docker ps -a -q -f "name=${serviceNamePrefix}")`); } -/** Instantiates all (not yet instantiated) services in the given `selection`. */ -/*async function instantiateServices( - selection: ServiceSelection -): Promise<[ServiceHandle, Uuid[]][]> { - const promises = await Promise.all( - selection.map(async ([type, selected]) => { - const config = await readConfig(type); - if (!config) return []; - return await Promise.all( - Array.from(selected.map((sel) => instantiateService(sel, config))) - ); - }) - ); - - return promises.flat(); -}*/ - async function readConfig(type: ServiceType): Promise { const file = await fs.readFile(`./config/services/${type}.json`); return parseAs(ServiceConfig, JSON.parse(file.toString())); } /** Instantiates a service of the given type/description/config if it does not already exist. */ -export async function instantiateService( +export async function createService( entry: ServiceEntry, config: ServiceConfig ): Promise<[ServiceHandle]> { @@ -162,9 +96,8 @@ export async function instantiateService( } /** Sets the dependencies of the corresponding service instance. */ -export async function configureServiceUUID(uuid: string) { - logger.error('Configuring Service: ' + uuid); - const config: string = 'MATCH (m: Automation { uuid: \'' + uuid +'\' }), ( n: Automation ) WHERE (n)-[:SUBSCRIBE]->(m) RETURN n.uuid'; +export async function setDependencies(uuid: string) { + const config: string = 'MATCH (m: Service { uuid: \'' + uuid +'\' }), ( n: Service ) WHERE (n)-[:SUBSCRIBE]->(m) RETURN n.uuid'; let depen = await returnQuery(config); let uuids: string[] = []; for(let x of depen.records) { @@ -173,14 +106,33 @@ export async function configureServiceUUID(uuid: string) { let i = await getSEfromUuid(uuid); if (typeof i !== 'undefined') { let handle = getServiceHandle(i); - configureService(handle, uuids); + transferConfig(handle, uuids); } else { logger.error('No ServiceEntry found..'); } } +/** Removes the service with the given `uuid` and triggers a full service selection + * and configuration update. */ +async function _removeService(uuid: string): Promise { + + let se = await getSEfromUuid(uuid); + if (!se) return; + let toRemove = getServiceHandle(se); + + // retrieve updated service selection from database + await removeService(uuid); + + // remove all services no longer present in the new configuration and kill their respective + // processes + logger.warn('killing process of service ' + chalk.underline(toRemove.info.uuid)); + + stopService(toRemove.info.uuid); + services.delete(toRemove.info.uuid); +} + /** Sets the dependencies of the corresponding service instance. */ -async function configureService(service: ServiceHandle, depends: Uuid[], init = false) { +async function transferConfig(service: ServiceHandle, depends: Uuid[], init = false) { const { add, del } = setServiceDependencies(service, depends); const reconfigure = add.length && del.length; From 59b7916dc4dc40cb1c9cffddbaa72b57e5718490 Mon Sep 17 00:00:00 2001 From: Fabian During Date: Wed, 31 Mar 2021 13:41:29 +0200 Subject: [PATCH 17/20] state updates are now applied to neo4j --- reconf/src/fail.ts | 2 -- reconf/src/neo4j.ts | 36 +++++++++++++++++++++++++++----- reconf/src/service.ts | 2 +- services/ceiling-lamp/src/app.ts | 1 - 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/reconf/src/fail.ts b/reconf/src/fail.ts index 86f7902..2477378 100644 --- a/reconf/src/fail.ts +++ b/reconf/src/fail.ts @@ -1,5 +1,4 @@ import mqtt from 'async-mqtt'; -import { updateDatabase } from './neo4j'; import { env as getEnv, util, FailureMessage, parseAs } from 'horme-common'; import DefaultHandler from './failure/DefaultHandler'; @@ -16,7 +15,6 @@ async function setupFailureListener(): Promise { // set MQTT client message event listener client.on('message', (topic, msg) => { onFailure(topic, msg).catch((err) => util.abort(err)); - updateDatabase(topic, msg.toString()); }); await client.subscribe([ diff --git a/reconf/src/neo4j.ts b/reconf/src/neo4j.ts index f083eb7..e765c9a 100644 --- a/reconf/src/neo4j.ts +++ b/reconf/src/neo4j.ts @@ -1,7 +1,8 @@ import neo4j, { Driver } from 'neo4j-driver'; -import { env as getEnv, util } from 'horme-common'; +import { env as getEnv, util, DeviceMessage } from 'horme-common'; import { ServiceEntry } from './db'; import { QueryResult } from 'neo4j-driver/types/result'; +import mqtt from 'async-mqtt'; const env = getEnv.readEnvironment('reconf'); const logger = util.logger; @@ -26,12 +27,12 @@ export async function connectNeo4j() { if (await finish) { driver = d; logger.info('Connected to Neo4j!'); + initDataListener(); return; } } catch (error) {} } - } //reset whole database @@ -66,9 +67,34 @@ async function updateAllDependencies(config: [string, ServiceEntry[]][]) { await resetAllDependencies(); } -export async function updateDatabase(topic: string, message: string) { - logger.error('topic: ' + topic); - logger.error('message: ' + message); +export async function initDataListener() { + logger.info('Setup Database Listener...'); + // connect MQTT client + const client = await mqtt.connectAsync(env.host, env.auth); + // set MQTT client message event listener + client.on('message', (topic, msg) => { + updateState(topic, msg.toString()); + }); + await client.subscribe([ + `data/${process.env.HORME_APARTMENT}/bedroom/#`, + ]); +} + +export async function updateState(topic: string, message: string) { + + //search for id + var splitted = topic.split('_', 2); + var id = splitted[1]; + logger.error(splitted[1]); + + //parse message to devicemsg + //extract value from devicemsg + const deviceMsg = DeviceMessage.check(JSON.parse(message)); + var val = deviceMsg.value; + + //set value in db + const finished: string = 'MATCH (n: Service { uuid: \'' + id + '\' }) SET n.state = \'' + val+ '\''; + await returnQuery(finished); } //Reset all current dependencies diff --git a/reconf/src/service.ts b/reconf/src/service.ts index 8eb3ca8..e412568 100644 --- a/reconf/src/service.ts +++ b/reconf/src/service.ts @@ -261,5 +261,5 @@ export function buildTopic(entry: ServiceEntry): string { entry.room !== null ? `${process.env.HORME_APARTMENT}/${entry.room}` : `${process.env.HORME_APARTMENT}/global`; - return `${base}/${entry.type}${entry.uuid}`; + return `${base}/${entry.type}_${entry.uuid}`; } diff --git a/services/ceiling-lamp/src/app.ts b/services/ceiling-lamp/src/app.ts index cb5c0e7..68c0c50 100644 --- a/services/ceiling-lamp/src/app.ts +++ b/services/ceiling-lamp/src/app.ts @@ -108,7 +108,6 @@ async function handleDataMessage( value: device.value, timestamp: new Date().getTime(), }; - await client.publish(sendTopic, JSON.stringify(response), { retain: true }); logger.debug(`retained state set to: '${device.value}'`) } \ No newline at end of file From c95fbcd8858a61add23f4c145d2fde54cc2be35c Mon Sep 17 00:00:00 2001 From: Fabian During Date: Mon, 5 Apr 2021 00:38:11 +0200 Subject: [PATCH 18/20] Reconf: Added automatic reconfiguration on malfunction --- .../config/serviceconf/bedroomSwitches.json | 9 +++++++ reconf/src/db.ts | 18 ++++++++++--- reconf/src/failure/DefaultHandler.ts | 5 ++-- reconf/src/neo4j.ts | 1 - reconf/src/service.ts | 27 ++++++++++++++++--- services/ceiling-lamp/src/app.ts | 3 --- services/light-switch/src/app.ts | 7 +++-- 7 files changed, 55 insertions(+), 15 deletions(-) diff --git a/reconf/config/serviceconf/bedroomSwitches.json b/reconf/config/serviceconf/bedroomSwitches.json index 9fa5566..4c0c00a 100644 --- a/reconf/config/serviceconf/bedroomSwitches.json +++ b/reconf/config/serviceconf/bedroomSwitches.json @@ -15,4 +15,13 @@ "room": "bedroom", "configMsg": "", "online": true +}, +{ + "type": "remote-switch", + "uuid": "fra2", + "mainDevices": [], + "replacementDevices": "switches", + "room": "bedroom", + "configMsg": "", + "online": true }] \ No newline at end of file diff --git a/reconf/src/db.ts b/reconf/src/db.ts index 89f599d..54859ba 100644 --- a/reconf/src/db.ts +++ b/reconf/src/db.ts @@ -37,7 +37,7 @@ export type ServiceEntry = { async function initializeDatabase() { logger.info('Import external Services...'); await importServices(); - logger.info('Import external DeviceGroups...'); + logger.info('Import external Device Groups...'); await importDeviceGroups(); logger.info('Import external MainDevices...'); await initMissingServices(); @@ -115,7 +115,6 @@ async function alternativeConfiguration(dev:string, to:string) { } //remove service from db -//TODO: remove depends export async function removeService(uuid: string): Promise { const a: string = 'MATCH (n: Service { uuid: \'' + uuid + '\' }) RETURN n'; let back = await returnQuery(a); @@ -128,12 +127,24 @@ export async function removeService(uuid: string): Promise { } } +//set service offline +export async function disableService(uuid: string): Promise { + const a: string = 'MATCH (n: Service { uuid: \'' + uuid + '\' }) RETURN n'; + let back = await returnQuery(a); + if(back.records.length != 0){ + logger.info('Set service with uuid \'' + uuid + '\' offline!'); + + //DETACH implies that all relations are deleted too + const removeQuery: string = 'MATCH (n: Service { uuid: \'' + uuid + '\' }) SET n.online = \'false\''; + back = await returnQuery(removeQuery); + } +} + //should be called when new devices are added to net network/switched state to online again export async function initMissingServices() { // Search for devices which are not configured const a: string = 'MATCH (n: Service) WHERE n.configured = \'false\' AND NOT n.mainDevices = \'\' RETURN n.mainDevices, n.uuid'; - const res = await returnQuery(a); configureServices(res); } @@ -195,7 +206,6 @@ async function initRelationship(dev1:string, dev2:string) { //read device types from json async function importDeviceGroups() { - logger.info('Import external Device Groups...'); const deviceGroupsFolder = './config/device-groups/'; const fs = require('fs'); const files = await fs.readdirSync(deviceGroupsFolder); diff --git a/reconf/src/failure/DefaultHandler.ts b/reconf/src/failure/DefaultHandler.ts index 30f9b9f..54c3d70 100644 --- a/reconf/src/failure/DefaultHandler.ts +++ b/reconf/src/failure/DefaultHandler.ts @@ -7,7 +7,8 @@ const logger = util.logger; export default class DefaultHandler implements FailureHandler { async handle(msg: { uuid: string; reason: string; }): Promise { logger.debug(`Default failure handler for ${msg.uuid} caused by ${msg.reason}`); - await service.stopService(msg.uuid); - await service.startService(msg.uuid); + await service.removeService(msg.uuid); + //await service.stopService(msg.uuid); + //await service.startService(msg.uuid); } } \ No newline at end of file diff --git a/reconf/src/neo4j.ts b/reconf/src/neo4j.ts index e765c9a..3ba5202 100644 --- a/reconf/src/neo4j.ts +++ b/reconf/src/neo4j.ts @@ -85,7 +85,6 @@ export async function updateState(topic: string, message: string) { //search for id var splitted = topic.split('_', 2); var id = splitted[1]; - logger.error(splitted[1]); //parse message to devicemsg //extract value from devicemsg diff --git a/reconf/src/service.ts b/reconf/src/service.ts index e412568..63631a4 100644 --- a/reconf/src/service.ts +++ b/reconf/src/service.ts @@ -4,7 +4,7 @@ import fs from 'fs/promises'; import chalk from 'chalk'; import mqtt from 'async-mqtt'; -import db, { getSEfromUuid, ServiceEntry, removeService } from './db'; +import db, { getSEfromUuid, ServiceEntry, removeService, initMissingServices, disableService } from './db'; import { env as getEnv, util, @@ -120,8 +120,25 @@ async function _removeService(uuid: string): Promise { if (!se) return; let toRemove = getServiceHandle(se); - // retrieve updated service selection from database - await removeService(uuid); + //search for all services that did get information from malfunctional device + const config: string = 'MATCH (n: Service { uuid: \'' + uuid +'\' }), ( m: Service ) WHERE (n)-[:SUBSCRIBE]->(m) RETURN m.uuid'; + let depen = await returnQuery(config); + + //retrieve updated service selection from database + await disableService(uuid); + + //set those services to unconfigured + for(let x of depen.records) { + const unconfservice: string = 'MATCH (n: Service)-[r:SUBSCRIBE]->(m: Service) WHERE m.uuid = \'' + x.get('m.uuid') +'\' DELETE r'; + await returnQuery(unconfservice); + + //delete all relations from them + const unconfigure: string = 'MATCH (n: Service { uuid: \'' + x.get('m.uuid') +'\', online: \'true\' }) SET n.configured = \'false\''; + await returnQuery(unconfigure); + } + + //reconfigure all these services + initMissingServices(); // remove all services no longer present in the new configuration and kill their respective // processes @@ -156,6 +173,10 @@ function _startService(entry: ServiceEntry, config: ServiceConfig, topic: string '-e', 'HORME_LOG_LEVEL=' + env.logLevel, '-e', + 'HORME_NEO4J_USER=' + process.env.HORME_MQTT_USER, + '-e', + 'HORME_NEO4J_PASS=' + process.env.HORME_MQTT_PASS, + '-e', 'HORME_MQTT_HOST=' + env.host, '-e', 'HORME_SERVICE_TOPIC=' + topic, diff --git a/services/ceiling-lamp/src/app.ts b/services/ceiling-lamp/src/app.ts index 68c0c50..5856e55 100644 --- a/services/ceiling-lamp/src/app.ts +++ b/services/ceiling-lamp/src/app.ts @@ -62,16 +62,13 @@ async function handleConfigMessage(client: AsyncMqttClient, topic: string, msg: logger.info('initial configuration received'); serviceInfo = config.info; } - const add = config.add.map(sub => 'data/' + sub.topic); const del = config.del.map(sub => 'data/' + sub.topic); - if (add.length > 0) { subCount += add.length; await client.subscribe(add); logger.debug('subscribed to topic(s): ' + add.join(', ')); } - if (del.length > 0) { subCount -= del.length; await client.subscribe(del); diff --git a/services/light-switch/src/app.ts b/services/light-switch/src/app.ts index a2c0947..6783291 100644 --- a/services/light-switch/src/app.ts +++ b/services/light-switch/src/app.ts @@ -46,8 +46,11 @@ async function main() { if (!isConfigured) { logger.info(`initial configuration received ${msg.info.version}`); if (msg.info.version === 0) { - // simulate a start error - process.exit(1); + if(msg.info.uuid === 'fra') { + // simulate a start error for fra + logger.info('INFO: simulated start error for ' + msg.info.uuid); + process.exit(1); + } } isConfigured = true; simulateSwitchActivity( From 061987eb5c3518de1cf487b8aee394b61f44ee94 Mon Sep 17 00:00:00 2001 From: Fabian During Date: Mon, 5 Apr 2021 10:34:47 +0200 Subject: [PATCH 19/20] Reconf: Added comments --- reconf/src/db.ts | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/reconf/src/db.ts b/reconf/src/db.ts index 54859ba..5ddedfd 100644 --- a/reconf/src/db.ts +++ b/reconf/src/db.ts @@ -129,6 +129,8 @@ export async function removeService(uuid: string): Promise { //set service offline export async function disableService(uuid: string): Promise { + + //check if service is available const a: string = 'MATCH (n: Service { uuid: \'' + uuid + '\' }) RETURN n'; let back = await returnQuery(a); if(back.records.length != 0){ @@ -146,6 +148,8 @@ export async function initMissingServices() { // Search for devices which are not configured const a: string = 'MATCH (n: Service) WHERE n.configured = \'false\' AND NOT n.mainDevices = \'\' RETURN n.mainDevices, n.uuid'; const res = await returnQuery(a); + + //configure them configureServices(res); } @@ -157,50 +161,56 @@ async function configureServices(res: QueryResult) { for(const record of res.records) { //iterate over all searched main devices let md = record.get('n.mainDevices'); - let x = record.get('n.uuid'); + let uuid = record.get('n.uuid'); var splitted = md.split(',', 30); - for (let dev of splitted) { - const d: string = 'MATCH (n: Service) WHERE n.uuid = \'' + dev + '\' RETURN n.uuid, n.replacementDevices'; + for (let singlemd of splitted) { + + //iterate over all maindevices + const d: string = 'MATCH (n: Service) WHERE n.uuid = \'' + singlemd + '\' RETURN n.uuid, n.replacementDevices'; const res1 = await returnQuery(d); if (res1.records.length == 0) { - logger.warn('Device with uuid \'' + dev + '\' does not exist'); + + //if the device does not exists, abort + logger.error('Device with uuid \'' + singlemd + '\' does not exist in the database!'); return; } - - const e: string = 'MATCH (n: Service), (m: Service) WHERE n.online = \'true\' AND n.uuid = \'' + dev + '\' AND m.uuid = \'' + x + '\' AND NOT (n)-[:SUBSCRIBE]->(m) RETURN n.uuid'; + + // check if the desired device is available + const e: string = 'MATCH (n: Service), (m: Service) WHERE n.online = \'true\' AND n.uuid = \'' + singlemd + '\' AND m.uuid = \'' + uuid + '\' AND NOT (n)-[:SUBSCRIBE]->(m) RETURN n.uuid'; const res2 = await returnQuery(e); if (res2.records.length == 0) { - logger.warn('Device with uuid \'' + dev + '\' is not available for this configuration!'); - //get uuid from alternative - let alt = await alternativeConfiguration(dev, x); + logger.warn('Device with uuid \'' + singlemd + '\' is not available for this configuration!'); + + //desired device is not available,search for an alternative + let alt = await alternativeConfiguration(singlemd, uuid); if(alt) { - initRelationship(x, alt); + initRelationship(uuid, alt); } else { - logger.info('No replacement device found for \'' + dev + '\''); + logger.info('No replacement device found for \'' + singlemd + '\''); return; } } else { - initRelationship(x, dev); + // desired device is available, add relationship + initRelationship(uuid, singlemd); } - setDependencies(dev); + setDependencies(singlemd); } - setDependencies(x); - const finished: string = 'MATCH (n: Service { uuid: \'' + x + '\' }) SET n.configured = \'true\''; + setDependencies(uuid); + const finished: string = 'MATCH (n: Service { uuid: \'' + uuid + '\' }) SET n.configured = \'true\''; await returnQuery(finished); }; } else { - logger.error('got empty set'); + logger.error('Nothing to configure. Check your Queries!'); } } +//Adding a Subscribe relation between devices with uuid dev1 and dev2. async function initRelationship(dev1:string, dev2:string) { logger.info('Adding relation from \"' + dev1 + '\" to \"' + dev2 + '\".'); const e: string = 'MATCH (n: Service {uuid: \'' + dev1 + '\'}), (m: Service {uuid: \'' + dev2 + '\'}) CREATE (m)-[r:SUBSCRIBE]->(n)'; await returnQuery(e); - const g: string = 'MATCH (n: Service {uuid: \'' + dev1 + '\'}) SET n.initiated = \'true\''; - await returnQuery(g); return; } From 312f6ca0d835c779cc130861eb9199eae430a3ae Mon Sep 17 00:00:00 2001 From: Fabian During Date: Mon, 5 Apr 2021 11:19:42 +0200 Subject: [PATCH 20/20] Reconf: Added type of service to database --- reconf/src/db.ts | 6 +++--- reconf/src/service.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/reconf/src/db.ts b/reconf/src/db.ts index 5ddedfd..a0c910b 100644 --- a/reconf/src/db.ts +++ b/reconf/src/db.ts @@ -250,17 +250,17 @@ export async function addService(x: ServiceEntry){ let newworld = type.split('-').join('_'); // If Device does not exist, add it to DB - const a: string = 'MATCH (n: Service:' + newworld + ' { uuid: \'' + x.uuid + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\' }) RETURN n'; + const a: string = 'MATCH (n: Service:' + newworld + ' { uuid: \'' + x.uuid + '\', mainDevices: \'' + x.mainDevices + '\', type: \'' + type + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\' }) RETURN n'; const query = await returnQuery(a); if (query.records.length == 0) { //check if service can be configured (has main devices) if (x.mainDevices.length > 0) { - const b: string = 'CREATE (n: Service:' + newworld + ' { uuid: \'' + x.uuid + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\', configured: \'false\' })'; + const b: string = 'CREATE (n: Service:' + newworld + ' { uuid: \'' + x.uuid + '\', mainDevices: \'' + x.mainDevices + '\', type: \'' + type + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\', configured: \'false\' })'; await returnQuery(b); } else { - const b: string = 'CREATE (n: Service:' + newworld + ' { uuid: \'' + x.uuid + '\', mainDevices: \'' + x.mainDevices + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\', configured: \'true\' })'; + const b: string = 'CREATE (n: Service:' + newworld + ' { uuid: \'' + x.uuid + '\', mainDevices: \'' + x.mainDevices + '\', type: \'' + type + '\', replacementDevices: \'' + x.replacementDevices + '\', online: \'' + x.online + '\', room: \'' + x.room + '\', configured: \'true\' })'; await returnQuery(b); } const file = await fs.readFileSync(`./config/services/${type}.json`, 'utf8'); diff --git a/reconf/src/service.ts b/reconf/src/service.ts index 4ed875a..44d2355 100644 --- a/reconf/src/service.ts +++ b/reconf/src/service.ts @@ -129,7 +129,7 @@ async function _removeService(uuid: string): Promise { //set those services to unconfigured for(let x of depen.records) { - const unconfservice: string = 'MATCH (n: Service)-[r:SUBSCRIBE]->(m: Service) WHERE m.uuid = \'' + x.get('m.uuid') +'\' DELETE r'; + const unconfservice: string = 'MATCH (n: Service)-[r:SUBSCRIBE]->(m: Service) WHERE m.uuid = \'' + x.get('m.uuid') + '\' DELETE r'; await returnQuery(unconfservice); //delete all relations from them