UNPKG

iobroker.wolf-smartset

Version:
1,018 lines (895 loc) 39.3 kB
const fs = require('fs'); const path = require('path'); const utils = require('@iobroker/adapter-core'); const axios = require('axios').default; const _API_STRING = { HOST: 'www.wolf-smartset.com', WEBSERVER: 'https://www.wolf-smartset.com', PATH_PORTAL_INIT: '/portal/api/portal/Init', // unused PATH_TOKEN: '/portal/connect/token', // unused PATH_OPENID_CFG: '/idsrv/.well-known/openid-configuration', PATH_OPENID_SIGNIN_CB: '/signin-callback.html', PATH_OPENID_GET_TOKEN: '/idsrv/connect/token', PATH_OPENID_USERINFO: '/idsrv/connect/userinfo', PATH_PORTAL_USERINFO: '/portal/api/portal/CurrentUserLoadDataSlim', PATH_EXPERT_LOGIN: '/portal/api/portal/ExpertLogin', PATH_GET_SYSTEM_LIST: '/portal/api/portal/systems/50', // unused PATH_GET_SYSTEM_LIST2: '/portal/api/portal/GetSystemList', PATH_GET_GUI_DESC_FOR_GW: '/portal/api/portal/GetGuiDescriptionForGateway', PATH_GET_GUI_DESC_FOR_APP: '/portal/api/portal/GetGuiDescriptionForAppGateway2', // unused PATH_GET_PARAM_VALS: '/portal/api/portal/GetParameterValues', PATH_SET_PARAM_VALS: '/portal/api/portal/parameters/write', PATH_WRITE_PARAM_VALS: '/portal/api/portal/WriteParameterValues', // unused PATH_UPDATE_SESSION: '/portal/api/portal/UpdateSession', PATH_CREATE_SESSION: '/portal/api/portal/CreateSession2', PATH_GET_SYSTEM_STATE: '/portal/api/portal/systemstate/', PARAM_SYSTEM_ID: 'SystemId=', PARAM_GATEWAY_ID: 'GatewayId=', PARAM_EXPERT_PASSWORD: 'Password=', }; // periodic API activity intervals const _API_INTERVAL_EXPERT_LOGIN = 7200000; // refresh Expert Login Interval: 2 hours const _API_INTERVAL_SESSION_UPDATE = 60000; const { Issuer, custom, generators } = require('openid-client'); /** * * Create a new instance of the WolfSmartSet class * */ class WolfSmartSet { /** * Initialze a WolfSmartSet instance * * @param username - The login username * @param password - The login password * @param adapter - The adapter instance */ constructor(username, password, adapter) { this.USER = username; this.PASS = password; this.adapter = adapter; this.UpdateInterval = _API_INTERVAL_SESSION_UPDATE; this.UpdateTimeout = null; this.refreshTimeout = null; this.destroyed = false; this.LastAccessShort = null; // must be null on first request to get full value list this.LastAccessLong = null; // must be null on first request to get full value list this.openIdStore = {}; this.openIdClientId = 'smartset.web'; this.openIdClient = null; this.auth = {}; this.SessionId = null; this.isExpertSession = false; this.ExpertInterval = _API_INTERVAL_EXPERT_LOGIN; this.ExpertTimeout = null; const authPath = utils.getAbsoluteInstanceDataDir(this.adapter); // No more auth data caching // try { // if (!fs.existsSync(authPath)) { // fs.mkdirSync(authPath); // } // } catch (error) { // this.adapter.log.warn(`Error creating directory: ${error.message}`); // } this.authFile = path.join(authPath, 'auth.json'); } /** * Return a HTTP header feasable for the id_srv (openId) */ async _generateOpenIdHeader() { return { 'Cache-control': 'no-cache', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:108.0) Gecko/20100101 Firefox/108.0', Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', 'Accept-Language': 'de-DE,de;q=0.8,en-US;q=0.5,en;q=0.3', Referer: `${_API_STRING['WEBSERVER']}/`, 'Sec-Fetch-Dest': 'document', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-Site': 'same-origin', TE: 'trailers', }; } /** * Return a HTTP header feasable for communication with Wolf server * * @param cType - the ContentType */ async _generateApiHeader(cType) { return { 'Content-Type': cType, Authorization: this.auth ? `${this.auth.token_type} ${this.auth.access_token}` : '', 'User-Agent': 'ioBroker.wolfsmartset', Accept: '*/*', Host: _API_STRING['HOST'], Connection: 'keep-alive', 'Cache-control': 'no-cache', 'X-Pect': 'The Spanish Inquisition', }; } /** * Return current timestmp as DateTime string feasable for Wolf server */ _generateDateTimeString() { const date = new Date(); const year = date.getFullYear(); const month = (1 + date.getMonth()).toString().padStart(2, '0'); const day = date.getDate().toString().padStart(2, '0'); const hour = date.getHours().toString().padStart(2, '0'); const minute = date.getMinutes().toString().padStart(2, '0'); const second = date.getSeconds().toString().padStart(2, '0'); return `${year}-${month}-${day} ${hour}:${minute}:${second}`; } /** * Initialize auth object from adapter settings * * @param auth - the auth object holding credentials */ _setAuthData(auth) { // No more auth data caching // _setAuthData(auth, doNotSave) { if (!auth) { return; } this.auth = auth; this.auth.USER = this.USER; this.auth.PASS = this.PASS; // No more auth data caching // if (!doNotSave) { // try { // fs.writeFileSync(this.authFile, JSON.stringify(auth), 'utf-8'); // this.adapter.log.debug(`Auth data saved to ${this.authFile}`); // } catch (error) { // this.adapter.log.warn(`Error writing auth.json: ${error.message}`); // } // } } /** * Clear auth object and remove auth data cache file (incl: id_token, access_token, idsrv.session), * so the adapter is forced to do a fresh openId authentication next time * */ _removeAuthData() { this.auth = {}; try { fs.unlinkSync(this.authFile); this.adapter.log.info(`Auth data cache file ${this.authFile} was removed successfully.`); // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return; } } /** * Initialze OpenID object */ async _openIdInit() { // initialize OpenID Issuer with Wolf relevant details const openIdIssuer = await Issuer.discover(_API_STRING['WEBSERVER'] + _API_STRING['PATH_OPENID_CFG']); const openIdClient = new openIdIssuer.Client({ client_id: this.openIdClientId, redirect_uris: [_API_STRING['WEBSERVER'] + _API_STRING['PATH_OPENID_SIGNIN_CB']], response_types: ['code'], // id_token_signed_response_alg (default "RS256") token_endpoint_auth_method: 'none', // (default 'client_secret_basic') }); custom.setHttpOptionsDefaults({ timeout: 5000, }); return openIdClient; // try { // if (fs.existsSync(this.authFile)) { // const auth = JSON.parse(fs.readFileSync(this.authFile, 'utf-8')); // if (auth && auth.access_token && auth.USER === this.USER && auth.PASS === this.PASS) { // this.auth = auth; // this.adapter.log.debug(`Auth data loaded from ${this.authFile}`); // await this._refreshAuthToken(); // } // } // } catch (error) { // this.adapter.log.warn(`Error reading auth.json: ${error.message}`); // } } /** * Get authentication token from OpenID authentication */ async _getAuthToken() { this.adapter.log.debug(`_getAuthToken(): starting ...`); this.refreshTimeout && clearTimeout(this.refreshTimeout); const codeVerifier = generators.codeVerifier(); const codeChallenge = generators.codeChallenge(codeVerifier); const state = generators.state(); this.openIdStore[state] = { code_verifier: codeVerifier, }; if (!this.openIdClient) { this.openIdClient = await this._openIdInit(); } try { const authUrl = this.openIdClient.authorizationUrl({ scope: 'openid profile api role', state, code_challenge: codeChallenge, code_challenge_method: 'S256', }); this.adapter.log.debug(`_getAuthToken(): authUrl: ${authUrl}`); const headers = await this._generateOpenIdHeader(); const loginPage = await axios.get(authUrl, { headers, }); const loginUrl = loginPage.request.res.responseUrl; const requestVerificationToken = loginPage.data.match( /<input name="__RequestVerificationToken" type="hidden" value="(.*)" \/>/, )[1]; // @ts-expect-error: we checked presence of 'setcookie' haeder above const cookies = loginPage.headers['set-cookie'].map(cookie => cookie.split(';')[0]); this.adapter.log.debug(`requestVerificationToken: ${requestVerificationToken}`); const data = { 'Input.Username': this.USER, 'Input.Password': this.PASS, __RequestVerificationToken: requestVerificationToken, }; // convert to Query string const query = Object.keys(data) .map(k => `${encodeURIComponent(k)}=${encodeURIComponent(data[k])}`) .join('&'); headers['Content-Type'] = 'application/x-www-form-urlencoded'; headers.Origin = _API_STRING['WEBSERVER']; headers.Referer = loginUrl; headers['Sec-Fetch-User'] = '?1'; headers.Cookie = cookies.join('; '); const responsePost = await axios({ url: loginUrl, data: query, headers, method: 'POST', maxRedirects: 0, validateStatus: function (status) { return status === 302; }, }); const redirectUrl = `${_API_STRING['WEBSERVER']}${responsePost.headers.location}`; // @ts-expect-error: we checked presence of 'setcookie' haeder above cookies.push(...responsePost.headers['set-cookie'].map(cookie => cookie.split(';')[0])); headers.Cookie = cookies.join('; '); const response = await axios({ url: redirectUrl, headers, method: 'GET', }); if (this.destroyed) { return; } const callbackUrl = response.request.res.responseUrl; this.adapter.log.debug(`_getAuthToken(): callbackUrl: ${callbackUrl}`); const params = this.openIdClient.callbackParams(callbackUrl); if (!this.openIdStore[params.state]) { throw new Error( `Can not decode response for State ${params.state}. Please reload start page and try again!`, ); } if (params.code) { const tokenData = { client_id: this.openIdClientId, code: params.code, redirect_uri: _API_STRING['WEBSERVER'] + _API_STRING['PATH_OPENID_SIGNIN_CB'], code_verifier: this.openIdStore[params.state].code_verifier, grant_type: 'authorization_code', }; this.adapter.log.debug(`token data: ${JSON.stringify(tokenData)}`); // convert to Query string const tokenQuery = Object.keys(tokenData) .map(k => `${encodeURIComponent(k)}=${encodeURIComponent(tokenData[k])}`) .join('&'); const tokenSet = await axios({ url: _API_STRING['WEBSERVER'] + _API_STRING['PATH_OPENID_GET_TOKEN'], data: tokenQuery, headers, method: 'POST', }); this.adapter.log.info( `_getAuthToken(): received and validated tokens: ${JSON.stringify(tokenSet.data)}`, ); if (tokenSet.data.expires_in) { this.adapter.log.info( `_getAuthToken(): tokens will expire in ${tokenSet.data.expires_in} seconds, setting refresh timer.`, ); this.refreshTimeout = setTimeout(() => { this._refreshAuthToken(); }, tokenSet.data.expires_in * 1000); } tokenSet.data.idCookie = cookies.find(cookie => cookie.startsWith('idsrv.session=')); tokenSet.data.allCookies = cookies; tokenSet.data.headers = headers; return tokenSet.data; } else if (params.error) { this.adapter.log.warn(`_getAuthToken(): ERROR: ${JSON.stringify(params)}`); throw new Error(`${params.error} / ${params.error_description}`); } } catch (error) { this.adapter.log.warn(`_getAuthToken(): ERROR: ${error.message}`); this.adapter.log.warn(error.stack); } } /** * Refresh OpenId auth token */ async _refreshAuthToken() { this.adapter.log.debug(`_refreshAuthToken(): starting ...`); this.refreshTimeout && clearTimeout(this.refreshTimeout); // if no auth data available, start a fresh authentication if (!this.auth || !this.auth.id_token) { this._setAuthData(await this._getAuthToken()); return; } // otherwise try refreshing existing auth tokens const codeVerifier = generators.codeVerifier(); const codeChallenge = generators.codeChallenge(codeVerifier); const state = generators.state(); this.openIdStore[state] = { code_verifier: codeVerifier, }; // @ts-expect-error: openIdClient is initialized in init let authUrl = this.openIdClient.authorizationUrl({ scope: 'openid profile api role', state, code_challenge: codeChallenge, code_challenge_method: 'S256', }); authUrl += '&prompt=none'; authUrl += '&response_mode=query'; authUrl += `&id_token_hint=${this.auth.id_token}`; this.adapter.log.debug(`_refreshAuthToken(): refresh auth url: ${authUrl}`); const headers = await this._generateOpenIdHeader(); headers['Cookie'] = this.auth.allCookies.join('; '); try { const refreshResponse = await axios.get(authUrl, { headers, }); if (this.destroyed) { return; } const callbackUrl = refreshResponse.request.res.responseUrl; this.adapter.log.debug(`_refreshAuthToken(): callbackUrl: ${callbackUrl}`); // @ts-expect-error: openIdClient is initialized in init const params = this.openIdClient.callbackParams(callbackUrl); if (!this.openIdStore[params.state]) { throw new Error( `Can not decode response for State ${params.state}. Please reload start page and try again!`, ); } if (params.code) { const tokenData = { client_id: this.openIdClientId, code: params.code, redirect_uri: _API_STRING['WEBSERVER'] + _API_STRING['PATH_OPENID_SIGNIN_CB'], code_verifier: this.openIdStore[params.state].code_verifier, grant_type: 'authorization_code', }; this.adapter.log.debug(`token data: ${JSON.stringify(tokenData)}`); // convert to Query string const tokenQuery = Object.keys(tokenData) .map(k => `${encodeURIComponent(k)}=${encodeURIComponent(tokenData[k])}`) .join('&'); const tokenSet = await axios({ url: _API_STRING['WEBSERVER'] + _API_STRING['PATH_OPENID_GET_TOKEN'], data: tokenQuery, headers, method: 'POST', }); this.adapter.log.debug( `_refreshAuthToken(): received and validated tokens: ${JSON.stringify(tokenSet.data)}`, ); if (tokenSet.data.expires_in) { this.adapter.log.info( `_refreshAuthToken(): tokens will expire in ${tokenSet.data.expires_in} seconds, setting refresh timer.`, ); this.refreshTimeout = setTimeout(() => { this._refreshAuthToken(); }, tokenSet.data.expires_in * 1000); } tokenSet.data.idCookie = this.auth.idCookie; tokenSet.data.allCookies = this.auth.allCookies; tokenSet.data.headers = this.auth.headers; this._setAuthData(tokenSet.data); } else if (params.error) { this.adapter.log.debug(`_refreshAuthToken(): ERROR: ${JSON.stringify(params)}`); throw new Error(`${params.error} / ${params.error_description}`); } } catch (error) { this.adapter.log.warn(`_refreshAuthToken(): ERROR: ${error.message}`); if (error.message.includes('login_required')) { this.adapter.log.info(`_refreshAuthToken(): starting fresh auth`); this.auth = {}; await this.init(); } } } /** * Logout and close OpenId session */ async _destroyAuthToken() { this.adapter.log.debug(`_destroyAuthToken() starting ...`); if (!this.auth || !this.auth.id_token) { this.adapter.log.debug(`_destroyAuthToken(): id_token already removed ...`); this._removeAuthData(); return; } if (!this.openIdClient) { this.adapter.log.debug(`_destroyAuthToken(): no openIdClient found ...`); this._removeAuthData(); return; } let endSessionUrl = this.openIdClient.endSessionUrl(); if (typeof endSessionUrl == 'undefined') { this.adapter.log.warn(`_destroyAuthToken(): endSession endpoint not found!`); this._removeAuthData(); return; } endSessionUrl += `&id_token_hint=${this.auth.id_token}`; endSessionUrl += `&post_logout_redirect_uri=${_API_STRING['WEBSERVER']}/index.html`; this.adapter.log.debug(`endsession url: ${endSessionUrl}`); const headers = await this._generateOpenIdHeader(); headers['Host'] = `${_API_STRING['HOST']}`; headers['Connection'] = 'keep-alive'; headers['Priority'] = 'u=0, i'; headers['Referer'] = `${_API_STRING['HOST']}/index.html`; headers['Upgrade-Insecure-Requests'] = '1'; headers['Cookie'] = this.auth.allCookies.join('; '); try { const endsessionResponse = await axios.get(endSessionUrl, { headers, }); if (endsessionResponse.status != 200) { this.adapter.log.warn( `API ENDSESSION WARNING: unexpected response: ${endsessionResponse.statusText} (${endsessionResponse.statusText})`, ); } else { this.adapter.log.info(`API ENDSESSION: successfully logged out`); } } catch (error) { this.adapter.log.warn(`API ENDSESSION ERROR: ${error.message}`); this.adapter.log.warn(error.stack); } this._removeAuthData(); } /** * Create a Wolf server session using the existing OPenID Token */ async _createSession() { if (!this.auth || !this.auth.token_type || !this.auth.access_token) { this.adapter.log.debug('_createSession(): No auth token found!'); this._setAuthData(await this._getAuthToken()); } try { const headers = await this._generateApiHeader('application/json; charset=utf-8'); headers['Cookie'] = this.auth.idCookie; headers['Origin'] = _API_STRING['WEBSERVER']; headers['X-Requested-With'] = 'XMLHttpRequest'; headers['Sec-Fetch-Dest'] = 'empty'; headers['Sec-Fetch-Mode'] = 'cors'; headers['SSec-Fetch-Site'] = 'same-origin'; headers['TE'] = 'trailers'; /* const initResponse = await axios.get(_API_STRING["WEBSERVER"] + _API_STRING["PATH_PORTAL_INIT"] + '?_=' + Date.now(), { headers }); */ const payload = { Timestamp: this._generateDateTimeString(), //ClientSideIdentifier: '0001-01-01T00:00:00', //AppVersion: '3.0.29' }; const response = await axios.post(_API_STRING['WEBSERVER'] + _API_STRING['PATH_CREATE_SESSION'], payload, { headers: headers, }); if (this.destroyed) { return; } this.adapter.log.debug(`API CREATE SESSION: ${JSON.stringify(response.data)}`); this.adapter.log.debug(`API CREATE SESSION: ${response.data.BrowserSessionId}`); return response.data.BrowserSessionId; } catch (error) { this.adapter.log.warn(`API CREATE SESSION ERROR: ${error.message}`); this.adapter.log.warn(error.stack); } } /** * Send session keepalive (UPDATE_SESSION) to Wolf server */ async _sessionUpdate() { this.UpdateTimeout && clearTimeout(this.UpdateTimeout); const headers = await this._generateApiHeader('application/json'); const payload = { SessionId: this.SessionId, }; try { const response = await axios.post(_API_STRING['WEBSERVER'] + _API_STRING['PATH_UPDATE_SESSION'], payload, { headers: headers, }); if (this.destroyed) { return; } this.adapter.log.debug(`SESSION UPDATE: ${JSON.stringify(response.data)}`); } catch (error) { this.adapter.log.warn(`SESSION UPDATE ERROR: ${error.message}`); if (this.SessionId && error.response && (error.response.status === 401 || error.response.status === 400)) { this.SessionId = await this._createSession(); } if (!this.SessionId) { await this.init(); } } this.UpdateTimeout = setTimeout(() => { this._sessionUpdate(); }, this.UpdateInterval); } // /** // * Get the list of systems from Wolf server, includes SystemId and GatewayId // */ // async _getSystemList() { // const headers = await this._generateApiHeader('application/json'); // try { // const response = await axios.get(_API_STRING['WEBSERVER'] + _API_STRING['PATH_GET_SYSTEM_LIST'], { // headers: headers, // }); // if (this.destroyed) { // return; // } // this.adapter.log.debug(`API GET SYSTEM LIST: ${JSON.stringify(response.data)}`); // return response.data.Systems; // } catch (error) { // this.adapter.log.warn(`API GET SYSTEM LIST ERROR: ${error.message}`); // } // } /** * Get the list of systems from Wolf server, includes SystemId and GatewayId * This is an alternative implementation as done by the Web Portal */ async _getSystemList2() { const headers = await this._generateApiHeader('application/json'); try { const response = await axios.get( `${_API_STRING['WEBSERVER']}${_API_STRING['PATH_GET_SYSTEM_LIST2']}?_=${Date.now()}`, { headers: headers, }, ); if (this.destroyed) { return; } this.adapter.log.debug(`API GET SYSTEM LIST2: ${JSON.stringify(response.data)}`); return response.data; } catch (error) { this.adapter.log.warn(`API GET SYSTEM LIST2 ERROR: ${error.message}`); } } /** * Get current user's Wolf Smartset portal capabilities * * @returns userCapabilities object * { * "IsPasswordReset":false, * "UserName":"jdoe", * "UserSalutationType":2, * "Firstname":"John", * "Surname":"Doe", * "CultureInfoCode":"de-DE", * "TwoLetterCountryCode":"DE", * "Email":"john.doe@example.com", * "MaxSystemBusSamplingRateSec":60 * } */ async _getUserCapabilities() { const headers = await this._generateApiHeader('application/json'); try { const response = await axios.post( _API_STRING['WEBSERVER'] + _API_STRING['PATH_PORTAL_USERINFO'], undefined, { headers: headers }, ); if (this.destroyed) { return; } this.adapter.log.debug(`API GET USERINFO: ${JSON.stringify(response.data)}`); return response.data; } catch (error) { this.adapter.log.warn(`API GET USERINFO ERROR: ${error.message}`); } } /** * Do an Expert Login to the Wolf server within the current session * * @param expertPassword - the expert password */ async _expertLogin(expertPassword) { const headers = await this._generateApiHeader(''); try { const response = await axios.get( `${_API_STRING['WEBSERVER'] + _API_STRING['PATH_EXPERT_LOGIN']}?${ _API_STRING['PARAM_EXPERT_PASSWORD'] }${expertPassword}`, { headers: headers, }, ); if (this.destroyed) { return; } this.adapter.log.debug(`API EXPERT LOGIN: ${response.status}`); return true; } catch (error) { this.adapter.log.warn(`API EXPERT LOGIN: ${error.message}`); return false; } } /** * Initialze the adapter */ async init() { // remove Expert Login Timer to avoid duplicate login this.ExpertTimeout && clearTimeout(this.ExpertTimeout); // make sure we have an auhtenticated session if (!this.auth || !this.auth.access_token) { this._setAuthData(await this._getAuthToken()); } if (this.auth && this.auth.access_token) { this.SessionId = await this._createSession(); this.UserInfo = await this._getUserCapabilities(); this.adapter.log.info('Initialization with Login and new Session done!'); if (typeof this.UserInfo != 'undefined') { if (this.adapter.config.pollIntervalShort < this.UserInfo.MaxSystemBusSamplingRateSec) { this.adapter.log.warn( `Adapter poll interval (${this.adapter.config.pollIntervalShort.toString()} s) is less than min poll interval (${this.UserInfo.MaxSystemBusSamplingRateSec.toString()} s) permitted by Wolf: consider changing adapter settings!`, ); } } this.LastAccessShort = null; // reset last access to get full value list after re-login this.LastAccessLong = null; // reset last access to get full value list after re-login if (this.adapter.config.doExpertLogin) { let expertLoggedIn = await this._expertLogin(this.adapter.config.expertPassword); if (expertLoggedIn) { this.adapter.log.info('Expert Login done!'); this.isExpertSession = true; } else { this.adapter.log.info("Expert Login failed: check 'Expert Password' in adapter settings!"); } // Expert Mode needs periodic re-initialization this.adapter.log.debug('Setting up re-init for Expert Login'); this.ExpertTimeout && clearTimeout(this.ExpertTimeout); this.ExpertTimeout = setTimeout(async () => { await this.init(); }, this.ExpertInterval); } // setup system update timer this.UpdateTimeout && clearTimeout(this.UpdateTimeout); this.UpdateTimeout = setTimeout(() => { this._sessionUpdate(); }, this.UpdateInterval); // init finished successfully return true; } // init somewhere return false; } /** * Will bew called when adapter is stopped */ async stop() { this.UpdateTimeout && clearTimeout(this.UpdateTimeout); this.refreshTimeout && clearTimeout(this.refreshTimeout); this.ExpertTimeout && clearTimeout(this.ExpertTimeout); this._destroyAuthToken(); this.destroyed = true; } /** * Get the list of devices belonging to the given user from Wolf server * */ async adminGetDevicelist() { let system; try { // make sure we have an auhtenticated session if (!this.auth || !this.auth.access_token) { this._setAuthData(await this._getAuthToken()); this.SessionId = null; } if (this.auth && this.auth.access_token) { if (!this.SessionId) { this.SessionId = await this._createSession(); } if (!this.SessionId) { throw new Error('Could not create session'); } system = await this._getSystemList2(); } else { throw new Error('Could not authenticate - check username/password'); } } catch (error) { this.adapter.log.warn(`API GET ADMIN DEVICE LIST ERROR: ${error.message}`); } return system; } /** * Get list of value ids from Wolf server * * @param GatewayId - Gateway Id as received by _getSystemList() * @param SystemId - SystemId as received by _getSystemList() */ async getGUIDescription(GatewayId, SystemId) { const headers = await this._generateApiHeader('application/json'); try { const response = await axios.get( `${_API_STRING['WEBSERVER'] + _API_STRING['PATH_GET_GUI_DESC_FOR_GW']}?${_API_STRING['PARAM_GATEWAY_ID']}${GatewayId}&${_API_STRING['PARAM_SYSTEM_ID']}${SystemId}`, { //_API_STRING["PATH_GET_GUI_DESC_FOR_APP"] for App data headers: headers, }, ); if (this.destroyed) { return null; } this.adapter.log.debug(`API GET GUI DESC.: ${JSON.stringify(response.data)}`); return response.data; } catch (error) { this.adapter.log.warn(`API GET GUI DESC. ERROR: ${error.message}`); console.log(error); return null; } } /** * Get system state from Wolf server * * @param SystemId - SystemId as received by _getSystemList() * @returns SystemState object * { * "AccessLevel":4, * "IsSystemShareDeleted":false, * "IsSystemDeleted":false, * "IsOnline":true, * "IsLocked":false, * "MaintenanceState":{"Mode":0,"Progress":0, * "CWLFilterMaintenancePending":false}, * "RequestDate":"2021-03-13T18:49:14.9610258Z", * "LastDisconnect":"2021-03-13T10:38:37.51" * } */ async getSystemState(SystemId) { const headers = await this._generateApiHeader('application/json'); try { const response = await axios.get( _API_STRING['WEBSERVER'] + _API_STRING['PATH_GET_SYSTEM_STATE'] + SystemId, { headers: headers, }, ); if (this.destroyed) { return; } this.adapter.log.debug(`API GET SYSTEM STATE: ${JSON.stringify(response.data)}`); return response.data; } catch (error) { this.adapter.log.warn(`API GET SYSTEM STATE: ${error.message}`); } } /** * Get list of values from Wolf server * * @param GatewayId - Gateway Id as received by _getSystemList() * @param SystemId - SystemId as received by _getSystemList() * @param BundleIdRequested - BundleId to set in GetParameterValues request * @param ValueIdList - list values of interest to request in GetParameterValues request * @param PollCycle - the poll cycle type: 'short' or 'long' */ async getValList(GatewayId, SystemId, BundleIdRequested, ValueIdList, PollCycle) { const headers = await this._generateApiHeader('application/json'); const payload = { BundleId: BundleIdRequested, IsSubBundle: false, ValueIdList: ValueIdList, GatewayId: GatewayId, SystemId: SystemId, LastAccess: PollCycle == 'short' ? this.LastAccessShort : this.LastAccessLong, GuiIdChanged: (PollCycle == 'short' && this.LastAccessShort == null) || (PollCycle == 'long' && this.LastAccessLong == null) ? true : false, SessionId: this.SessionId, }; const requestParams = `BundleId:${BundleIdRequested}, ValuesReqd: ${ValueIdList.length}, LastAccess: ${payload.LastAccess == null ? '!!reset!!' : payload.LastAccess}`; // reset LastAccess of the other poll cycle this.LastAccessLong = PollCycle == 'short' ? null : this.LastAccessLong; this.LastAccessShort = PollCycle == 'long' ? null : this.LastAccessShort; if (this.adapter.config.doApiProfile) { this.adapter.setState('info_api.poll_req_bundle_id', { val: Number(BundleIdRequested), ack: true }); this.adapter.setState('info_api.poll_req_num_params', { val: ValueIdList.length, ack: true }); } try { const response = await axios.post(_API_STRING['WEBSERVER'] + _API_STRING['PATH_GET_PARAM_VALS'], payload, { headers: headers, }); if (this.destroyed) { return; } if (payload.LastAccess == null) { this.adapter.log.info( `API GET VAL LIST(${requestParams}) returns ${response.data.Values.length} values: ${JSON.stringify(response.data)}`, ); } else { this.adapter.log.debug( `API GET VAL LIST(${requestParams}) returns ${response.data.Values.length} values: ${JSON.stringify(response.data)}`, ); } if (response.data.LastAccess) { if (PollCycle == 'short') { this.LastAccessShort = response.data.LastAccess; } else { this.LastAccessLong = response.data.LastAccess; } } if (this.adapter.config.doApiProfile) { this.adapter.setState('info_api.poll_resp_num_params', { val: response.data.Values.length, ack: true }); // define a function to count elements matching given condition const countParams = (arr, condition) => arr.reduce((acc, c) => (condition(c) ? ++acc : acc), 0); const numValues = countParams(response.data.Values, o => o.State == 1); this.adapter.setState('info_api.poll_resp_num_values', { val: numValues, ack: true }); // define a function to list ValueIds of all elemnents matching given condition const listParamsWithCondition = (arr, condition) => arr.reduce( (keyList, c) => condition(c) ? keyList == '' ? (keyList = c.ValueId) : `${keyList},${c.ValueId}` : keyList, '', ); const paramsNoValue = listParamsWithCondition(response.data.Values, o => o.State != 1); if (paramsNoValue != '') { this.adapter.log.warn(`API GET VAL LIST(${requestParams}): got no value for: ${paramsNoValue}`); } } return response.data; } catch (error) { this.adapter.log.warn(`API GET VAL LIST(${requestParams}) - ERROR: ${error.message}`); if (error.response && (error.response.status === 400 || error.response.status === 401)) { await this._refreshAuthToken(); } } } /** * Set param values on Wolf server * * @param GatewayId - Gateway Id as received by _getSystemList() * @param SystemId - SystemId as received by _getSystemList() * @param paramValList - list of parameter values to be changed */ async setValList(GatewayId, SystemId, paramValList) { const payload = { SessionId: this.SessionId, BundleId: 1000, GatewayId: GatewayId, SystemId: SystemId, WriteParameterValues: paramValList, WaitForResponseTimeout: null, GuiId: null, }; const headers = await this._generateApiHeader('application/json'); this.adapter.log.debug( `SEND VALUE ${_API_STRING['WEBSERVER']}${_API_STRING['PATH_SET_PARAM_VALS']} : ${JSON.stringify(payload)}`, ); try { const response = await axios.post(_API_STRING['WEBSERVER'] + _API_STRING['PATH_SET_PARAM_VALS'], payload, { //old PATH_WRITE_PARAM_VALS, new PATH_SET_PARAM_VALS headers: headers, }); if (this.destroyed) { return; } this.adapter.log.debug(`SEND VALUE : ${JSON.stringify(response.data)}`); return response.data; } catch (error) { this.adapter.log.warn(`SEND VALUE ERROR: ${error.message}`); } } } module.exports = WolfSmartSet;