Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add multi-backend support to pure structure and recording #1345

Merged
merged 10 commits into from Apr 16, 2023
12 changes: 11 additions & 1 deletion client/source/class/cv/io/Client.js
Expand Up @@ -59,6 +59,15 @@ qx.Class.define('cv.io.Client', {
backendName = cv.io.Client.backendNameAliases[backendName];
}
this.backendName = backendName;
switch (this.backendName) {
case 'default':
this._type = 'knxd';
break;

case 'openhab':
this._type = 'openhab';
break;
}

if (backendName && backendName !== 'default') {
if (typeof backendName === 'object') {
Expand Down Expand Up @@ -208,6 +217,7 @@ qx.Class.define('cv.io.Client', {
members: {
backend: null,
backendName: null,
_type: null,
backendLoginUrl: null,
addresses: null, // the subscribed addresses
initialAddresses: null, // the addresses which should be loaded before the subscribed addresses
Expand All @@ -222,7 +232,7 @@ qx.Class.define('cv.io.Client', {
__lastError: null,

getType() {
return this.backendName;
return this._type;
},

// property apply
Expand Down
8 changes: 4 additions & 4 deletions source/class/cv/Application.js
Expand Up @@ -820,7 +820,7 @@ qx.Class.define('cv.Application', {
cv.report.Record.logCache();
cv.Config.cacheUsed = true;
cv.Config.lazyLoading = true;
cv.io.BackendConnections.initBackendClient();
cv.io.BackendConnections.initBackendClients();

// load part for structure
const structure = cv.Config.getStructure();
Expand Down Expand Up @@ -1146,9 +1146,9 @@ qx.Class.define('cv.Application', {

close() {
this.setActive(false);
const client = cv.io.BackendConnections.getClient();
if (client) {
client.terminate();
const clients = cv.io.BackendConnections.getClients();
for (const name in clients) {
clients[name].terminate();
}
},

Expand Down
2 changes: 1 addition & 1 deletion source/class/cv/TemplateEngine.js
Expand Up @@ -341,7 +341,7 @@ qx.Class.define('cv.TemplateEngine', {
// load structure-part
await this.loadParts([cv.Config.getStructure()]);
if (cv.Application.structureController.parseBackendSettings(xml)) {
cv.io.BackendConnections.initBackendClient();
cv.io.BackendConnections.initBackendClients();
}
cv.Application.structureController.parseSettings(xml);
await cv.Application.structureController.preParse(xml);
Expand Down
122 changes: 96 additions & 26 deletions source/class/cv/io/BackendConnections.js
Expand Up @@ -35,18 +35,22 @@ qx.Class.define('cv.io.BackendConnections', {
__activeChangeListenerId: null,

/**
* Initialize the {@link cv.io.Client} for backend communication
* Initialize all {@link cv.io.IClient} clients for backend communication,
* return the default one (for backwards compability)
*/
initBackendClient() {
initBackendClients() {
if (cv.Config.testMode === true || window.cvTestMode === true) {
return this.addBackendClient('main', 'simulated');
if (cv.Config.testMode === true) {
cv.data.Model.getInstance().setDefaultBackendName('simulated');
}
return this.addBackendClient(cv.data.Model.getInstance().getDefaultBackendName(), 'simulated');
}
let backendName = (
let backendNames = (
cv.Config.URL.backend ||
cv.Config.configSettings.backend ||
cv.Config.server.backend ||
'default'
).split(',')[0];
).split(',');
const backendKnxdUrl =
cv.Config.URL.backendKnxdUrl || cv.Config.configSettings.backendKnxdUrl || cv.Config.server.backendKnxdUrl;
const backendMQTTUrl =
Expand All @@ -56,21 +60,55 @@ qx.Class.define('cv.io.BackendConnections', {
cv.Config.configSettings.backendOpenHABUrl ||
cv.Config.server.backendOpenHABUrl;

switch (backendName) {
const defaultName = cv.data.Model.getInstance().getDefaultBackendName() || 'main';
let defaultClient;
let defaultType;
switch (backendNames[0]) {
case 'knxd':
case 'default':
default:
return this.addBackendClient('main', 'knxd', backendKnxdUrl, 'server');
defaultType = 'knxd';
defaultClient = this.addBackendClient(defaultName, defaultType, backendKnxdUrl, 'server');
break;

case 'mqtt':
return this.addBackendClient('main', 'mqtt', backendMQTTUrl, 'server');
defaultType = 'mqtt';
defaultClient = this.addBackendClient(defaultName, defaultType, backendMQTTUrl, 'server');
break;

case 'openhab':
case 'openhab2':
case 'oh':
case 'oh2':
return this.addBackendClient('main', 'openhab', backendOpenHABUrl, 'server');
defaultType = 'openhab';
defaultClient = this.addBackendClient(defaultName, defaultType, backendOpenHABUrl, 'server');
break;
}

// check if we need to create more clients
for (let i = 1; i < backendNames.length; i++) {
switch (backendNames[i]) {
case 'knxd':
case 'default':
if (backendKnxdUrl && defaultType !== 'knxd') {
this.addBackendClient('knxd', 'knxd', backendKnxdUrl, 'server');
}
break;

case 'mqtt':
if (defaultType !== 'mqtt') {
this.addBackendClient('mqtt', 'mqtt', backendMQTTUrl, 'server');
}
break;

case 'openhab':
if (backendKnxdUrl && defaultType !== 'openhab') {
this.addBackendClient('openhab', 'openhab', backendOpenHABUrl, 'server');
}
break;
}
}
return defaultClient;
},

addBackendClient(name, type, backendUrl, source) {
Expand Down Expand Up @@ -99,7 +137,10 @@ qx.Class.define('cv.io.BackendConnections', {
if (cv.Config.reporting) {
const recordInstance = cv.report.Record.getInstance();
client.record = function (p, d) {
recordInstance.record(cv.report.Record.BACKEND, p, d);
recordInstance.record(cv.report.Record.BACKEND, p, d, {
name: name,
type: type
});
};
}
client.showError = this._handleClientError.bind(this);
Expand Down Expand Up @@ -154,7 +195,7 @@ qx.Class.define('cv.io.BackendConnections', {

/**
* Get the backend client by name, if the name is not set the default backend is used.
* Usually that is the backend client created by initBackendClient().
* Usually that is the backend client created by initBackendClients().
* @param backendName {String?} name of the backend
*/
getClient(backendName) {
Expand All @@ -167,13 +208,38 @@ qx.Class.define('cv.io.BackendConnections', {
if (!backendName) {
backendName = cv.data.Model.getInstance().getDefaultBackendName();
}
if (!this.__clients[backendName] && cv.Config.testMode) {
// in testMode the client might not have been initialized yet
return this.addBackendClient('main', 'simulated');
if (!this.__clients[backendName]) {
if (cv.Config.testMode) {
// in testMode the client might not have been initialized yet
return this.addBackendClient('simulated', 'simulated');
}
// backendName might be a type
return this.getClientByType(backendName);
}
return this.__clients[backendName];
},

getClientByType(type) {
if (type === 'system') {
if (!this.hasClient('system')) {
this.__clients.system = new cv.io.System();
}
return this.__clients.system;
}
let client;
for (const name in this.__clients) {
client = this.__clients[name];
if (client.getType() === type) {
return client;
}
}
return null;
},

getClients() {
return this.__clients;
},

initSystemBackend() {
// make sure that we have a "system" backend
if (!this.hasClient('system')) {
Expand All @@ -189,21 +255,25 @@ qx.Class.define('cv.io.BackendConnections', {
/**
* Start retrieving data from backend
*/
startInitialRequest() {
startInitialRequests() {
Object.getOwnPropertyNames(this.__clients).forEach(name => {
this.startInitialRequest(name);
});
},

startInitialRequest(name) {
if (qx.core.Environment.get('qx.debug')) {
cv.report.Replay.start();
}
Object.getOwnPropertyNames(this.__clients).forEach(name => {
const client = this.getClient(name);
if (cv.Config.enableAddressQueue) {
// identify addresses on startpage
client.setInitialAddresses(cv.Application.structureController.getInitialAddresses(name));
}
const addressesToSubscribe = cv.data.Model.getInstance().getAddresses(name);
if (addressesToSubscribe.length !== 0) {
client.subscribe(addressesToSubscribe);
}
});
const client = this.getClient(name);
if (cv.Config.enableAddressQueue) {
// identify addresses on startpage
client.setInitialAddresses(cv.Application.structureController.getInitialAddresses(name));
}
const addressesToSubscribe = cv.data.Model.getInstance().getAddresses(name);
if (addressesToSubscribe.length !== 0) {
client.subscribe(addressesToSubscribe);
}
},

_onActiveChanged() {
Expand Down
24 changes: 23 additions & 1 deletion source/class/cv/parser/pure/WidgetParser.js
Expand Up @@ -425,6 +425,28 @@ qx.Class.define('cv.parser.pure.WidgetParser', {
let addressInfo = {};
let formatPos = +(elem.getAttribute('format-pos') || 1) | 0; // force integer
let mode = 1 | 2; // Bit 0 = read, Bit 1 = write => 1|2 = 3 = readwrite
let backendType = cv.data.Model.getInstance().getDefaultBackendName();
if (!cv.Config.testMode && !window.cvTestMode) {
if (transform) {
// only check transform when not in testMode
switch (transform.split(':')[0].toLowerCase()) {
case 'dpt':
backendType = 'knxd';
break;

case 'oh':
backendType = 'openhab';
break;

case 'mqtt':
backendType = 'mqtt';
break;
}
} else {
backendType = 'system';
}
}
addressInfo.backendType = backendType;

addressInfo.selector = elem.getAttribute('selector');
addressInfo.ignoreError = elem.getAttribute('ignore-error') === 'true';
Expand Down Expand Up @@ -452,7 +474,7 @@ qx.Class.define('cv.parser.pure.WidgetParser', {
break;
}

let backendName;
let backendName = backendType;
if (elem.hasAttribute('backend')) {
backendName = elem.getAttribute('backend');
}
Expand Down
6 changes: 4 additions & 2 deletions source/class/cv/plugins/RssLog.js
Expand Up @@ -308,11 +308,13 @@ qx.Class.define('cv.plugins.RssLog', {
100
);

let adddressSettings;
for (let addr in this.getAddress()) {
if (!cv.data.Model.isWriteAddress(this.getAddress()[addr])) {
adddressSettings = this.getAddress()[addr];
if (!cv.data.Model.isWriteAddress(adddressSettings)) {
continue;
} // skip when write flag not set
cv.io.BackendConnections.getClient().write(addr, cv.Transform.encode(this.getAddress()[addr], 0));
cv.io.BackendConnections.getClient(adddressSettings.backendType).write(addr, cv.Transform.encode(this.getAddress()[addr], 0));
}
}
},
Expand Down
12 changes: 7 additions & 5 deletions source/class/cv/plugins/openhab/Openhab.js
Expand Up @@ -45,12 +45,14 @@ qx.Class.define('cv.plugins.openhab.Openhab', {
this.__notificationRouter = cv.core.notifications.Router.getInstance();

// listen to notifications
const client = cv.io.BackendConnections.getClient();
const sse = client.getCurrentTransport && client.getCurrentTransport();
if (sse) {
sse.subscribe('notifications', this._onNotification, this);
const client = cv.io.BackendConnections.getClientByType('openhab');
if (client) {
const sse = client.getCurrentTransport && client.getCurrentTransport();
if (sse) {
sse.subscribe('notifications', this._onNotification, this);
}
cv.TemplateEngine.getInstance().executeWhenDomFinished(this._createSettings, this);
}
cv.TemplateEngine.getInstance().executeWhenDomFinished(this._createSettings, this);
}
},

Expand Down
4 changes: 2 additions & 2 deletions source/class/cv/plugins/openhab/Settings.js
Expand Up @@ -92,7 +92,7 @@ qx.Class.define('cv.plugins.openhab.Settings', {
};

const service = (this.__service = new qx.io.rest.Resource(serviceDesc));
const client = cv.io.BackendConnections.getClient();
const client = cv.io.BackendConnections.getClientByType('openhab');

this._store = new qx.data.store.Rest(service, 'get', {
configureRequest(req) {
Expand Down Expand Up @@ -136,7 +136,7 @@ qx.Class.define('cv.plugins.openhab.Settings', {
};

const config = (this.__configDescriptionResource = new qx.io.rest.Resource(description));
const client = cv.io.BackendConnections.getClient();
const client = cv.io.BackendConnections.getClientByType('openhab');

config.addListener('getSuccess', ev => {
this._createForm(ev.getRequest().getResponse());
Expand Down
4 changes: 2 additions & 2 deletions source/class/cv/report/Record.js
Expand Up @@ -144,9 +144,9 @@ qx.Class.define('cv.report.Record', {
return runtime;
},

record(category, path, data) {
record(category, path, data, options) {
if (cv.Config.reporting === true && !cv.report.Record.REPLAYING) {
cv.report.Record.getInstance().record(category, path, data);
cv.report.Record.getInstance().record(category, path, data, options);
}
},

Expand Down
10 changes: 8 additions & 2 deletions source/class/cv/report/Replay.js
Expand Up @@ -26,6 +26,7 @@
*
* @author Tobias Bräutigam
* @since 0.11.0 (2017)
* @ignore(Document)
*/
qx.Class.define('cv.report.Replay', {
extend: qx.core.Object,
Expand Down Expand Up @@ -147,6 +148,8 @@ qx.Class.define('cv.report.Replay', {
return window;
} else if (path === 'document') {
return document;
} else if (path instanceof HTMLElement || path instanceof Document) {
return path;
} else if (path.includes(':eq(')) {
const re = /:eq\(([\d]+)\)/;
let match = re.exec(path);
Expand Down Expand Up @@ -285,7 +288,10 @@ qx.Class.define('cv.report.Replay', {
},

__dispatchBackendRecord(record) {
const client = this.__getClient();
let client = this.__getDefaultClient();
if (record.o && record.o.name) {
client = cv.io.BackendConnections.getClient(record.o.name);
}
switch (record.i) {
case 'read':
if (client instanceof cv.io.openhab.Rest) {
Expand All @@ -310,7 +316,7 @@ qx.Class.define('cv.report.Replay', {
}
},

__getClient() {
__getDefaultClient() {
if (!this.__client) {
this.__client = cv.io.BackendConnections.getClient();
}
Expand Down