UNPKG

iobroker.e3oncan

Version:

Collect data on CAN bus for Viessmann E3 devices, e.g. Vitocal, Vitocharge, Energy Meters E380CA and E3100CB

732 lines (694 loc) 33.7 kB
const uds = require('./canUds'); const E3DidsDict = require('./didsE3.json'); const E3DidsVarDict = require('./didsE3var.json'); const E3DidsWritable = require('./didsE3Writables.json'); const { buildTopologySummary, TOPOLOGY_DIDS } = require('./topologyAnalysis'); /** * Scan status indicators used by tab.html to show warnings: * * | State | anyScanMissing | collectScanDone | scan-warning | rescan-hint | * |--------------------------------------------|----------------|-----------------|--------------|-------------| * | No scan done | true | false | shown | — | * | Old scan (pre-v1.x), no collect data | false | false | — | shown | * | New v1.x scan, no collect devices found | false | true | — | — | * | New v1.x scan, collect devices found | false | true | — | — | * * anyScanMissing: any device has didsScanDone=false (storage.js: Object.keys(didsWritable).length === 0) * collectScanDone: info.collect exists in v1.x format (detectedIds array) — set in main.js on adapter start */ /** * Perform scan for devices or scan for data points of a specific device */ class udsScan { /** * Init */ constructor() { this.callbackBusy = false; this.udsMaxTrialsDevScan = 2; // Number of trials during UDS device scan this.udsMaxTrialsDidScan = 4; // Number of trials during UDS dids scan this.udsTimeoutDevScan = 1500; // Timeout (ms) for UDS devive scan this.udsTimeoutDidScan = 2500; // Timeout (ms) for UDS dids scan // UDS device name → Collect CAN ID mapping. this.udsDevName2CanId = { HPMUMASTER: '0x693', EMCUMASTER: '0x451', }; this.workers = {}; this.udsCntNewDevs = 0; // New devices found during scan this.cntUdsScansActive = 0; this.udsScanAddrSpan = 0x10; this.udsScanAddrRange = [0x680, 0x6a0, 0x6c0, 0x6e0]; this.cntUdsScansActive = 0; this.udsScanDids = {}; this.udsScanDidsCntSuccess = 0; this.udsScanDidsCntTotal = 0; this.udsScanDidsCntDone = 0; this.udsScanDidsCntRetries = 0; } /** * Callback function for scan for devices * * @param {object} ctx Adapter context * @param {object} ctxWorker Worker context * @param {Array} _args Optional arguments */ async scanDevCallback(ctx, ctxWorker, _args) { async function mergeDev(dev) { let newDev = true; await ctx.log.silly(`UDS Device Scan found device: ${String(dev.devStateName)}`); for (const d of Object(ctx.udsDevices).values()) { if (d.devName == dev.devStateName.split('_0x')[0] && d.devAddr == dev.devAddr) { await ctx.log.silly('Device is already known. No change applied.'); newDev = false; dev.devStateName = d.devStateName; dev.collectCanId = d.collectCanId; dev.devUnits = d.devUnits; break; } } if (newDev) { await ctx.log.info(`UDS Device Scan found NEW device: ${String(dev.devStateName)}`); await ctx.udsDevices.push(dev); ctx.udsScanWorker.udsCntNewDevs += 1; } } async function setDevUnits(devAddr, devUnits) { for (const d of Object(ctx.udsDevices).values()) { if (d.devAddr == devAddr) { await ctx.log.debug(`Set format of units for device ${String(devAddr)} to "${devUnits}"`); d.devUnits = devUnits; if (devAddr == ctx.udsMasterDevAddr) { // Store Units and Formats of master device. Will be used for devices not providing own confiuraion of units and formats. ctx.udsMasterDevUnits = devUnits; } } } } const response = _args[0]; const result = _args[1]; switch (response) { case 'ok': if (result) { if (result.did == String(ctx.udsDidForScan)) { const devName = result.val.DeviceProperty.Text; const busAddress = `00${String(result.val.BusAddress)}`; await mergeDev({ devName: devName, devStateName: `${devName}_${String(ctxWorker.canIDhex)}`, devUnits: 'n/a', devAddr: String(ctxWorker.canIDhex), collectCanId: devName in ctx.udsScanWorker.udsDevName2CanId ? ctx.udsScanWorker.udsDevName2CanId[devName] : '', devTopName: `${devName.replace('MASTER', '')}_CAN${busAddress.slice(-2)}`, }); // Collect BusIdentification for topology analysis: ctx.topologyData ??= {}; ctx.topologyData[ctxWorker.canIDhex] ??= { matrices: [] }; ctx.topologyData[ctxWorker.canIDhex].busId = result.val; } else { if (result.did == String(ctx.udsDidForUnits)) { await ctx.log.silly( `UDS Device Scan got UnitsAndFormats for ${String(ctxWorker.canIDhex)}: ${JSON.stringify(result.val)}`, ); let devUnits; try { devUnits = `${result.val.Units.Text} / ${result.val.DateFormat.Text} / ${result.val.TimeFormat.Text}`; } catch { devUnits = 'n/a'; } await setDevUnits(String(ctxWorker.canIDhex), devUnits); } else { await ctx.log.error( `UDS Device Scan: ${String(ctxWorker.canIDhex)} got unexpected data point: ${response.did}`, ); } } } else { await ctx.log.error(`UDS Device Scan: ${String(ctxWorker.canIDhex)} got ok, but empty response!`); } break; case 'timeout': if (ctxWorker.stat.cntCommTimeout < ctx.udsScanWorker.udsMaxTrialsDevScan) { if (result.did == ctx.udsDidForScan) { await ctxWorker.pushCmnd(ctx, 'read', [ctx.udsDidForScan]); ctx.udsScanWorker.udsScanDidsCntRetries += 1; } } await ctx.log.silly(`UDS Device Scan: ${String(ctxWorker.canIDhex)} ${response}`); break; case 'negative response': if (result.did == ctx.udsDidForUnits) { await setDevUnits(String(ctxWorker.canIDhex), 'n/a'); } await ctx.log.silly(`UDS Device Scan: ${String(ctxWorker.canIDhex)} ${response}`); break; default: await ctx.log.silly(`UDS Device Scan: ${String(ctxWorker.canIDhex)} ${response}`); } if (ctxWorker.cmndsQueue.length == 0) { await ctxWorker.setCallback(null); // Scan worker completed. Reset callback. ctx.udsScanWorker.cntUdsScansActive -= 1; } } /** * Callback function for scan for data points of a specific device * * @param {object} ctx Adapter context * @param {object} ctxWorker Worker context * @param {Array} _args Optional arguments */ async scanDidsCallback(ctx, ctxWorker, _args) { if (ctx.udsScanWorker.callbackBusy) { ctx.log.error(`UDS dids scan: ${ctxWorker.config.stateBase} - callback busy!`); return; } ctx.udsScanWorker.callbackBusy = true; const response = _args[0]; const result = _args[1]; if (result) { const did = await Number(result.did); const didLen = Number(result.didInfo.len); switch (response) { case 'ok': case 'negative response': if (response == 'ok') { var acc = ''; // Default value for access mode if (result.common) { ctxWorker.storage.storageDids.didsDictDevCom[did] = result.didInfo; if ('acc' in result.didInfo.args) { acc = result.didInfo.args.acc; } } else { // Device specific did. Check, if definition is available in variant dids list if (did in E3DidsVarDict && didLen in E3DidsVarDict[did]) { const existingSpec = ctxWorker.storage.storageDids.didsDictDevSpec[did]; if (existingSpec?.protected) { // User has protected this definition — use it as-is const reason = existingSpec.reason ? ` Reason: "${existingSpec.reason}"` : ''; ctx.log.debug( `Variant Did ${ctxWorker.config.stateBase}_${did}: protected by user. Update skipped.${reason}`, ); ctxWorker.storage.storageDids.didsDictDevCom[did] = existingSpec; _args[1].didInfo = existingSpec; if ('acc' in existingSpec.args) { acc = existingSpec.args.acc; } } else { // Definition is available. Use it as device specific definition. Override it, if it already exists. ctx.log.debug(`Variant Did ${ctxWorker.config.stateBase}_${did} found.`); // Remark: No backup of actual data point definition is done during scan. If needed, a backup was already performed during startup of adapter. ctxWorker.storage.storageDids.didsDictDevSpec[did] = await E3DidsVarDict[did][didLen]; ctxWorker.storage.storageDids.didsDictDevCom[did] = await E3DidsVarDict[did][didLen]; // Also valid as common did for this device _args[1].didInfo = await E3DidsVarDict[did][didLen]; // Pass new definition of state back to caller if ('acc' in E3DidsVarDict[did][didLen].args) { acc = E3DidsVarDict[did][didLen].args.acc; } // Remember version of source: ctxWorker.storage.storageDids.didsDictDevSpec[did]['source'] = `didsE3var_${E3DidsVarDict.Version}`; } } else { // Device specific did. Do not override previous data if (!(did in ctxWorker.storage.storageDids.didsDictDevSpec)) { ctxWorker.storage.storageDids.didsDictDevSpec[did] = result.didInfo; } } } if (did in E3DidsWritable || acc == 'rw') { // Did is writable according to white list or codec info (acc). Add it to device specific list of writable dids: ctxWorker.storage.storageDids.didsWritable[did] = result.didInfo.id; } ctx.udsScanWorker.udsScanDidsCntSuccess += 1; // Collect topology matrix DIDs for topology analysis: if (TOPOLOGY_DIDS.has(did) && (result.val?.Count ?? 0) > 0) { ctx.topologyData ??= {}; ctx.topologyData[ctxWorker.canIDhex] ??= { matrices: [] }; ctx.topologyData[ctxWorker.canIDhex].matrices.push(result.val); } // Collect BusIdentification for topology analysis (covers the case where the // device scan was not run in this adapter lifecycle, e.g. after a restart): if (did === Number(ctx.udsDidForScan) && result.val?.DeviceProperty?.ID != null) { ctx.topologyData ??= {}; ctx.topologyData[ctxWorker.canIDhex] ??= { matrices: [] }; ctx.topologyData[ctxWorker.canIDhex].busId ??= result.val; } } ctx.udsScanWorker.udsScanDidsCntDone += 1; ctx.udsScanWorker.udsScanDids[ctxWorker.canIDhex] -= 1; await ctx.log.silly( `UDS dids scan: ${ctxWorker.config.stateBase}.${String(ctxWorker.data.did)}: ${response}`, ); break; case 'timeout': case 'did mismatch SF': case 'did mismatch MF': case 'bad MF frame': case 'bad CF frame': if (response == 'timeout') { ctx.log.silly( `UDS dids scan: Timeout on ${ctxWorker.config.stateBase}.${String( ctxWorker.data.did, )} cnt: ${String(ctxWorker.stat.cntCommFailedPerDid[ctxWorker.data.did])}`, ); ctx.log.silly(`UDS dids scan: ${ctxWorker.config.stateBase} ${response}`); } else { ctx.log.debug( `UDS dids scan: ${ctxWorker.config.stateBase} - communication failed: ${response}`, ); } if ( ctxWorker.stat.cntCommFailedPerDid[ctxWorker.data.did] < ctx.udsScanWorker.udsMaxTrialsDidScan ) { ctxWorker.pushCmnd(ctx, 'read', [ctxWorker.data.did]); ctx.udsScanWorker.udsScanDidsCntRetries += 1; } break; default: await ctx.log.warn( `UDS dids scan: ${ctxWorker.config.stateBase} - callback received unknown status: ${String( response, )}`, ); } if (ctx.udsScanWorker.udsScanDids[ctxWorker.canIDhex] == 0) { ctx.udsScanWorker.cntUdsScansActive -= 1; } if (ctx.udsScanWorker.cntUdsScansActive == -1) { await ctx.log.error( `UDS dids scan: ${ ctxWorker.config.stateBase } - number of active dids got negative - this should not happen.`, ); } if (ctx.udsScanWorker.udsScanDids[ctxWorker.canIDhex] == -1) { await ctx.log.error( `UDS dids scan: ${ ctxWorker.config.stateBase } - number of remaining dids got negative - this should not happen.`, ); } } else { await ctx.log.error( `UDS dids scan: ${ctxWorker.config.stateBase} - callback received empty result. response=${String( response, )}`, ); } ctx.udsScanWorker.callbackBusy = false; } /** * Start an UDS worker for scan * * @param {object} ctx Adapter context * @param {object} worker UDS Worker * @param {string} opMode Operation mode of worker */ async startupUdsWorker(ctx, worker, opMode) { const rxAddr = Number(worker.config.canID) + Number(0x10); ctx.udsScanWorker.workers[rxAddr] = worker; await worker.initStates(ctx, opMode); await worker.startup(ctx, opMode); //await ctx.log.debug(ctx.udsScanWorker.workers[rxAddr].canIDhex+': '+await this.workers[rxAddr].storage.getOpMode()); } /** * Start an UDS device for scan * * @param {object} ctx Adapter context * @param {string} addr Address of device */ async startupScanUdsDevice(ctx, addr) { const udsWorker = new uds.uds({ canID: Number(addr), stateBase: 'udsScanAddr', devUnits: 'n/a', device: 'common', delay: 0, active: true, channel: ctx.channelExt, timeout: this.udsTimeoutDevScan, }); await udsWorker.initStates(ctx, 'udsDevScan'); await udsWorker.setCallback(this.scanDevCallback); await this.startupUdsWorker(ctx, udsWorker, 'udsDevScan'); await udsWorker.pushCmnd(ctx, 'read', [ctx.udsDidForScan, ctx.udsDidForUnits]); this.cntUdsScansActive += 1; } /** * Start a set of an UDS devices for scan * * @param {object} ctx Adapter context */ async scanUdsDevices(ctx) { function range(size, startAt = 0) { return [...Array(size).keys()].map(i => i + startAt); } await ctx.log.info('UDS device scan - start'); this.workers = {}; this.udsCntNewDevs = 0; ctx.udsDevices = ctx.config.tableUdsDevices; ctx.topologyData = {}; // Stop all running workers to avoid communication conflicts: for (const worker of Object.values(ctx.E3UdsWorkers)) { await worker.stop(ctx); } const canExtActivated = ctx.config.canExtActivated; const canExtName = ctx.config.canExtName; // Startup CAN: if (canExtActivated) { if (ctx.channelExt && ctx.channelExtName != canExtName) { // CAN is different from running CAN. Stop actual CAN first. [ctx.channelExt, ctx.channelExtName] = await ctx.disconnectFromCan(ctx.channelExt, ctx.channelExtName); } [ctx.channelExt, ctx.channelExtName] = await ctx.connectToCan(ctx.channelExt, canExtName, ctx.onCanMsgExt); if (!ctx.channelExt) { await ctx.log.error(`UDS device scan: Could not connect to CAN Adapter ${canExtName}. Aborting.`); return false; } } else { await ctx.log.error('UDS device scan: External CAN not activated! Aborting.'); return false; } // Start energy meter detection in parallel with device scan (both channels): const detectedMeters = { e380_98: '', e380_97: '', e3100cb: '' }; let energyMeterListening = true; const makeEnergyMeterListener = channel => msg => { if (!energyMeterListening) { return; } if (msg.id === 0x250 && msg.data.length === 8 && !detectedMeters.e380_98) { detectedMeters.e380_98 = channel; } if (msg.id === 0x251 && msg.data.length === 8 && !detectedMeters.e380_97) { detectedMeters.e380_97 = channel; } if (msg.id === 0x569 && msg.data.length === 8 && !detectedMeters.e3100cb) { detectedMeters.e3100cb = channel; } }; await ctx.channelExt.addListener('onMessage', makeEnergyMeterListener('ext')); if (ctx.channelInt) { await ctx.channelInt.addListener('onMessage', makeEnergyMeterListener('int')); } this.udsScanDidsCntRetries = 0; this.cntUdsScansActive = 0; for (const baseAddr of Object(this.udsScanAddrRange).values()) { for (const addr of Object(range(Number(this.udsScanAddrSpan), Number(baseAddr))).values()) { await this.startupScanUdsDevice(ctx, addr); await this.sleep(ctx, ctx.udsTimeDelta); } } const tsAbort = new Date().getTime() + this.udsMaxTrialsDevScan * this.udsTimeoutDevScan + 250; await ctx.log.info('UDS device scan: Waiting for scans to complete.'); while (this.cntUdsScansActive > 0 && new Date().getTime() < tsAbort) { await this.sleep(ctx, 100); } // Stop all scan workers: for (const worker of Object.values(this.workers)) { await worker.stop(ctx); } this.workers = {}; // Stop energy meter detection and store results: energyMeterListening = false; const chanLabel = ch => (ch === 'int' ? '2nd CAN' : 'UDS CAN'); const meterList = [ detectedMeters.e380_97 ? `E380 at CAN address 97 (${chanLabel(detectedMeters.e380_97)})` : null, detectedMeters.e380_98 ? `E380 at CAN address 98 (${chanLabel(detectedMeters.e380_98)})` : null, detectedMeters.e3100cb ? `E3100CB (${chanLabel(detectedMeters.e3100cb)})` : null, ].filter(Boolean); await ctx.log.info( `UDS device scan: Energy meters detected: ${meterList.length > 0 ? meterList.join(', ') : 'none'}`, ); ctx.detectedEnergyMeters = detectedMeters; let emState = {}; try { const s = await ctx.getStateAsync('info.energyMeter'); if (s && s.val) { emState = JSON.parse(s.val); } } catch { /* keep empty */ } const emJson = { ...emState, e380_97: detectedMeters.e380_97, e380_98: detectedMeters.e380_98, e3100cb: detectedMeters.e3100cb, // Preserve active/delay: from emState if present, otherwise from runtime (migration path loaded old states) e380Active: emState.e380Active ?? ctx.e380Active, e380Delay: emState.e380Delay ?? ctx.e380Delay, e3100cbActive: emState.e3100cbActive ?? ctx.e3100cbActive, e3100cbDelay: emState.e3100cbDelay ?? ctx.e3100cbDelay, }; await ctx.extendObject('info.energyMeter', { type: 'state', common: { name: 'info.energyMeter', type: 'string', role: 'json', read: true, write: true }, native: {}, }); await ctx.setStateAsync('info.energyMeter', { val: JSON.stringify(emJson), ack: true }); // Restart all previously running workers: for (const worker of Object.values(ctx.E3UdsWorkers)) { await worker.startup(ctx, 'normal'); await this.sleep(ctx, ctx.udsTimeDelta); } if (this.cntUdsScansActive < 0) { await ctx.log.warn( `UDS scan finished. Number of retries / active UDS scans (should be 0): ${String( this.udsScanDidsCntRetries, )} / ${String(this.cntUdsScansActive)}`, ); } await ctx.log.info( `UDS device scan found ${String(this.udsCntNewDevs)} new of total ${String( ctx.udsDevices.length, )} devices: ${JSON.stringify(ctx.udsDevices)}`, ); await ctx.log.debug( `UDS device scan: Units and Formats of master device (0x${Number(ctx.udsMasterDevAddr).toString(16)}) set to "${ctx.udsMasterDevUnits}"`, ); await ctx.log.info('UDS device scan - done'); return true; } /** * Start an UDS device for scan of data points * * @param {object} ctx Adapter context * @param {string} addr Address of device * @param {Array} dids List of DIDs to be scanned */ async startupScanUdsDids(ctx, addr, dids) { const hexAddr = `0x${Number(addr).toString(16)}`; const devInfo = ctx.config.tableUdsDevices.filter(item => item.devAddr == hexAddr); let devName = ''; let devUnits = 'n/a'; if (devInfo.length > 0) { devName = devInfo[0].devStateName; devUnits = devInfo[0].devUnits ? devInfo[0].devUnits : 'n/a'; } else { devName = hexAddr; devUnits = 'n/a'; } const udsWorker = await new uds.uds({ canID: Number(addr), stateBase: devName, devUnits: devUnits, device: 'common', delay: 0, active: true, channel: ctx.channelExt, timeout: this.udsTimeoutDidScan, }); await udsWorker.setCallback(this.scanDidsCallback); this.udsScanDids[udsWorker.canIDhex] = dids.length; this.udsScanDidsCntTotal += dids.length; await this.startupUdsWorker(ctx, udsWorker, 'udsDidScan'); this.cntUdsScansActive += 1; await udsWorker.pushCmnd(ctx, 'read', dids); } /** * Perform scan of data points for a set of devices * * @param {object} ctx Adapter context * @param {Array} udsAddrs List of device addresses to be scanned * @param {object} udsDidsLimits Numerical limits of dids to be scanned */ async scanUdsDids(ctx, udsAddrs, udsDidsLimits) { function range(size, startAt = 0) { return [...Array(size).keys()].map(i => i + startAt); } async function logStatus(ctx, ctxScan) { await ctx.log.info( `UDS dids scan status (retries/found/done/total): (${String(ctxScan.udsScanDidsCntRetries)}/${String( ctxScan.udsScanDidsCntSuccess, )}/${String(ctxScan.udsScanDidsCntDone)}/${String( ctxScan.udsScanDidsCntTotal, )}), remaining: ${JSON.stringify(ctxScan.udsScanDids)}`, ); } await ctx.log.info('UDS dids scan - start'); if (ctx.suppressStateStorage) { await ctx.log.info( 'UDS dids scan: Storing of data point values in object tree is restricted to existing objects.', ); } this.workers = {}; this.udsScanDids = {}; // Stop all running UDS workers to avoid communication conflicts: for (const worker of Object.values(ctx.E3UdsWorkers)) { await worker.stop(ctx); } // Stop all running Collect workers on extternal bus to avoid data storage conflicts: for (const worker of Object.values(ctx.E3CollectExt)) { await worker.stop(ctx); } // Build set of Collect CAN IDs from device configuration (collectCanId may be comma-separated): const collectIds = new Set(); for (const dev of Object.values(ctx.config.tableUdsDevices || {})) { if (dev.collectCanId) { for (const raw of String(dev.collectCanId).split(',')) { const n = Number(raw.trim()); if (!isNaN(n) && n > 0) { collectIds.add(n); } } } } // Detect Collect-capable devices by listening for periodic messages: // DID 506 time message (21 FA 01 B3) or DID 954 message (21 BA 03 B0) let collectListening = true; const onCollectMsg = function (msg) { if (!collectListening) { return; } if ( collectIds.has(msg.id) && msg.data.length >= 4 && ((msg.data[0] === 0x21 && msg.data[1] === 0xfa && msg.data[2] === 0x01 && msg.data[3] === 0xb3) || (msg.data[0] === 0x21 && msg.data[1] === 0xba && msg.data[2] === 0x03 && msg.data[3] === 0xb0)) ) { ctx.detectedCollectCanIds.add(msg.id); } }; ctx.detectedCollectCanIds = new Set(); await ctx.channelExt.addListener('onMessage', onCollectMsg); if (ctx.channelInt) { await ctx.channelInt.addListener('onMessage', onCollectMsg); } this.cntUdsScansActive = 0; this.udsScanDidsCntTotal = 0; this.udsScanDidsCntRetries = 0; this.udsScanDidsCntSuccess = 0; this.udsScanDidsCntDone = 0; const dids = udsDidsLimits.dids ? [...udsDidsLimits.dids] : range(udsDidsLimits.max - udsDidsLimits.min + 1, udsDidsLimits.min); for (const addr of Object(udsAddrs).values()) { await this.startupScanUdsDids(ctx, addr, dids); await this.sleep(ctx, 50); } let cntDoneLast = -1; await logStatus(ctx, this); let ts = new Date().getTime(); const tsAbort = ts + this.udsScanDidsCntTotal * 500; while (this.cntUdsScansActive > 0 && new Date().getTime() < tsAbort) { await this.sleep(ctx, 100); if (new Date().getTime() - ts >= 10000) { ts += 10000; await logStatus(ctx, this); const cnt = this.udsScanDidsCntDone + this.udsScanDidsCntRetries; if (cntDoneLast == cnt) { // No progress in last 10 seconds await ctx.log.warn('UDS dids scan stalled. Aborting'); break; } cntDoneLast = cnt; } } await logStatus(ctx, this); // Build and persist topology summary: if (ctx.topologyData && Object.keys(ctx.topologyData).length > 0) { try { const { json, html } = buildTopologySummary(ctx.topologyData, ctx.detectedEnergyMeters ?? {}); await ctx.extendObject('info.topology', { type: 'state', common: { name: 'Topology summary (JSON)', type: 'string', role: 'json', read: true, write: false }, native: {}, }); await ctx.setStateAsync('info.topology', { val: JSON.stringify(json), ack: true }); await ctx.extendObject('info.topologyHtml', { type: 'state', common: { name: 'Topology summary (HTML)', type: 'string', role: 'html', read: true, write: false }, native: {}, }); await ctx.setStateAsync('info.topologyHtml', { val: html, ack: true }); await ctx.log.info( `UDS dids scan: Topology summary written (${json.udsDevices.length} UDS devices, ${json.topologyElements.length} topology elements).`, ); } catch (e) { await ctx.log.warn(`UDS dids scan: Could not build topology summary: ${String(e)}`); } } // Store dids found and stop all scan workers: for (const worker of Object.values(this.workers)) { worker.storage.storageDids.didsDictDevCom['Version'] = E3DidsDict.Version; await worker.storage.storageDids.storeKnownDids(ctx); await worker.stop(ctx); } this.workers = {}; ctx.suppressStateStorage = false; // Stop Collect detection, log result and persist to states: collectListening = false; await ctx.log.info( `UDS dids scan: Collect-capable devices detected on CAN IDs: ${ ctx.detectedCollectCanIds.size > 0 ? [...ctx.detectedCollectCanIds].map(id => `0x${id.toString(16)}`).join(', ') : 'none' }`, ); const collectJson = { detectedIds: [...ctx.detectedCollectCanIds], }; await ctx.extendObject('info.collect', { type: 'state', common: { name: 'info.collect', type: 'string', role: 'json', read: true, write: false }, native: {}, }); await ctx.setStateAsync('info.collect', { val: JSON.stringify(collectJson), ack: true }); if (this.cntUdsScansActive < 0) { await ctx.log.warn( `UDS dids scan finished. Number of active UDS scans (should be 0): ${String(this.cntUdsScansActive)}`, ); } await ctx.log.info( `UDS dids scan found ${String( this.udsScanDidsCntSuccess, )} dids. See channel "info" at device objects for details.`, ); // Restart all previously running workers: // Collect workers: for (const worker of Object.values(ctx.E3CollectExt)) { await worker.startup(ctx); } // UDS workers: for (const worker of Object.values(ctx.E3UdsWorkers)) { await worker.startup(ctx, 'normal'); await this.sleep(ctx, ctx.udsTimeDelta); } } /** * Wait for a specified time * * @param {object} ctx Caller context * @param {number} milliseconds Waiting time (ms) */ sleep(ctx, milliseconds) { return new Promise(resolve => ctx.setTimeout(resolve, milliseconds)); } } module.exports = { udsScan, };