iobroker.garmin
Version:
Adapter for Garmin Connect
720 lines (678 loc) • 28.5 kB
JavaScript
'use strict';
/*
* Created with @iobroker/create-adapter v2.3.0
*/
// The adapter-core module gives you access to the core ioBroker functions
// you need to create an adapter
const utils = require('@iobroker/adapter-core');
const axios = require('axios').default;
const got = require('got').default;
const Json2iob = require('json2iob');
const { CookieJar, MemoryCookieStore } = require('tough-cookie');
const qs = require('qs');
const { HttpsCookieAgent } = require('http-cookie-agent/http');
// Load your modules here, e.g.:
// const fs = require("fs");
class Garmin extends utils.Adapter {
/**
* @param {Partial<utils.AdapterOptions>} [options={}]
*/
constructor(options) {
super({
...options,
name: 'garmin',
});
this.on('ready', this.onReady.bind(this));
this.on('stateChange', this.onStateChange.bind(this));
this.on('unload', this.onUnload.bind(this));
this.deviceArray = [];
this.json2iob = new Json2iob(this);
class CustomStore extends MemoryCookieStore {
putCookie(cookie, cb) {
// Remove expiration before saving
cookie.expires = 'Infinity';
cookie.maxAge = Infinity;
super.putCookie(cookie, cb);
}
}
this.cookieJar = new CookieJar(new CustomStore());
this.requestClient = axios.create({
withCredentials: true,
httpsAgent: new HttpsCookieAgent({
cookies: {
jar: this.cookieJar,
},
}),
});
}
/**
* Is called when databases are connected and adapter received configuration.
*/
async onReady() {
// Reset the connection indicator during startup
this.setState('info.connection', false, true);
this.session = {};
if (this.config.interval < 0.5) {
this.log.info('Set interval to minimum 0.5');
this.config.interval = 0.5;
}
if (!this.config.username || !this.config.password) {
this.log.error('Please set username and password in the instance settings');
return;
}
await this.extendObject('auth', {
type: 'channel',
common: {
name: 'Auth',
},
native: {},
});
await this.extendObject('auth.token', {
type: 'state',
common: {
name: 'Token',
type: 'string',
role: 'value',
read: true,
write: false,
},
native: {},
});
const tokenState = await this.getStateAsync('auth.token');
if (tokenState && tokenState.val) {
this.session = JSON.parse(tokenState.val);
this.log.info('Old Session found');
const cookieState = await this.getStateAsync('cookie');
if (cookieState && cookieState.val) {
this.log.debug('Load cookie');
this.cookieJar = CookieJar.fromJSON(cookieState.val);
// const cookieString = 'JWT_FGP=' + this.cookieJar.store.idx['connect.garmin.com']['/']['JWT_FGP'].value + '; Domain=.connect.garmin.com; Path=/;Secure';
// this.cookieJar.setCookieSync(cookieString, 'https://connect.garmin.com');
await this.sleep(200);
}
} else if (this.config.token) {
this.log.info('Use settings token');
this.session = JSON.parse(this.config.token);
//set JWT_FGP cookie from config.fgp value on domain .connect.garmin.com and path /
const cookieString = 'JWT_FGP=' + this.config.fgp.trim() + '; Domain=.connect.garmin.com; Path=/;Secure';
this.cookieJar.setCookieSync(cookieString, 'https://connect.garmin.com');
}
if (!this.session || !this.session.access_token) {
this.log.warn('No token found. Please enter token in the settings');
return;
}
await this.refreshToken();
if (!this.session.access_token) {
this.log.error('Failed to login');
return;
}
await got
.get('https://connect.garmin.com/userprofile-service/userprofile/userProfileBase', {
cookieJar: this.cookieJar,
http2: true,
headers: {
Authorization: 'Bearer ' + this.session.access_token,
Accept: 'application/json, text/plain, */*',
'cache-control': 'no-cache',
'di-backend': 'connectapi.garmin.com',
nk: 'NT',
pragma: 'no-cache',
priority: 'u=1, i',
referer: 'https://connect.garmin.com/modern/home',
'user-agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
'x-app-ver': '5.9.0.31a',
'x-lang': 'de-DE',
},
})
.then((res) => {
this.log.debug(res.body);
this.userpreferences = JSON.parse(res.body);
})
.catch((error) => {
this.log.error(error);
error.response && this.log.error(JSON.stringify(error.response.data));
});
this.updateInterval = null;
this.reLoginTimeout = null;
this.refreshTokenTimeout = null;
this.subscribeStates('*');
// this.log.info('Login to Garmin');
// const result = await this.login();
await this.getDeviceList();
await this.updateDevices();
this.updateInterval = setInterval(
async () => {
await this.updateDevices();
},
this.config.interval * 60 * 1000,
);
this.refreshTokenInterval = this.setInterval(
async () => {
await this.refreshToken();
},
13 * 60 * 1000 - 5234,
);
}
async login() {
const form = await this.requestClient({
method: 'get',
url: 'https://sso.garmin.com/sso/signin?service=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F&webhost=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F&source=https%3A%2F%2Fconnect.garmin.com%2Fsignin%2F&redirectAfterAccountLoginUrl=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F&redirectAfterAccountCreationUrl=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso&locale=en_GB&id=gauth-widget&cssUrl=https%3A%2F%2Fconnect.garmin.com%2Fgauth-custom-v1.2-min.css&privacyStatementUrl=https%3A%2F%2Fwww.garmin.com%2Fen-GB%2Fprivacy%2Fconnect%2F&clientId=GarminConnect&rememberMeShown=true&rememberMeChecked=false&createAccountShown=true&openCreateAccount=false&displayNameShown=false&consumeServiceTicket=false&initialFocus=true&embedWidget=false&socialEnabled=false&generateExtraServiceTicket=true&generateTwoExtraServiceTickets=true&generateNoServiceTicket=false&globalOptInShown=true&globalOptInChecked=false&mobile=false&connectLegalTerms=true&showTermsOfUse=false&showPrivacyPolicy=false&showConnectLegalAge=false&locationPromptShown=true&showPassword=true&useCustomHeader=false&mfaRequired=false&performMFACheck=false&rememberMyBrowserShown=true&rememberMyBrowserChecked=false',
headers: {
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'user-agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15',
'accept-language': 'en-GB,en;q=0.9',
referer: 'https://connect.garmin.com/',
},
})
.then((res) => {
this.log.debug(JSON.stringify(res.data));
return this.extractHidden(res.data);
})
.catch((error) => {
this.log.error(error);
error.response && this.log.error(JSON.stringify(error.response.data));
});
let url =
'https://sso.garmin.com/sso/signin?service=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F&webhost=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F&source=https%3A%2F%2Fconnect.garmin.com%2Fsignin&redirectAfterAccountLoginUrl=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F&redirectAfterAccountCreationUrl=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso&locale=en_GB&id=gauth-widget&cssUrl=https%3A%2F%2Fconnect.garmin.com%2Fgauth-custom-v1.2-min.css&privacyStatementUrl=https%3A%2F%2Fwww.garmin.com%2Fen-GB%2Fprivacy%2Fconnect%2F&clientId=GarminConnect&rememberMeShown=true&rememberMeChecked=false&createAccountShown=true&openCreateAccount=false&displayNameShown=false&consumeServiceTicket=false&initialFocus=true&embedWidget=false&socialEnabled=false&generateExtraServiceTicket=true&generateTwoExtraServiceTickets=true&generateNoServiceTicket=false&globalOptInShown=true&globalOptInChecked=false&mobile=false&connectLegalTerms=true&showTermsOfUse=false&showPrivacyPolicy=false&showConnectLegalAge=false&locationPromptShown=true&showPassword=true&useCustomHeader=false&mfaRequired=false&performMFACheck=false&rememberMyBrowserShown=true&rememberMyBrowserChecked=false';
let data = {
username: this.config.username,
password: this.config.password,
_csrf: form._csrf,
embed: 'false',
};
if (this.config.mfa) {
url =
'https://sso.garmin.com/sso/verifyMFA/loginEnterMfaCode?service=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F&webhost=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F&source=https%3A%2F%2Fconnect.garmin.com%2Fsignin%2F&redirectAfterAccountLoginUrl=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F&redirectAfterAccountCreationUrl=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2F&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso&locale=en_GB&id=gauth-widget&cssUrl=https%3A%2F%2Fconnect.garmin.com%2Fgauth-custom-v1.2-min.css&privacyStatementUrl=https%3A%2F%2Fwww.garmin.com%2Fen-GB%2Fprivacy%2Fconnect%2F&clientId=GarminConnect&rememberMeShown=true&rememberMeChecked=false&createAccountShown=true&openCreateAccount=false&displayNameShown=false&consumeServiceTicket=false&initialFocus=true&embedWidget=false&socialEnabled=false&generateExtraServiceTicket=true&generateTwoExtraServiceTickets=true&generateNoServiceTicket=false&globalOptInShown=true&globalOptInChecked=false&mobile=false&connectLegalTerms=true&showTermsOfUse=false&showPrivacyPolicy=false&showConnectLegalAge=false&locationPromptShown=true&showPassword=true&useCustomHeader=false&mfaRequired=false&performMFACheck=false&rememberMyBrowserShown=true&rememberMyBrowserChecked=false';
data = {
'mfa-code': this.config.mfa,
embed: 'false',
fromPage: 'setupEnterMfaCode',
};
}
const ticket = await got
.post(url, {
http2: true,
headers: {
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'content-type': 'application/x-www-form-urlencoded',
'accept-language': 'de-de',
'user-agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15',
},
body: qs.stringify(data),
})
.then((res) => {
res.data = res.body;
this.log.debug(JSON.stringify(res.data));
const body = res.data;
try {
if (res.data.includes('window.VIEWER_USERPREFERENCES')) {
this.userpreferences = JSON.parse(res.data.split('window.VIEWER_USERPREFERENCES = ')[1].split(';\n')[0]);
this.social_media = JSON.parse(res.data.split('window.VIEWER_SOCIAL_PROFILE = ')[1].split(';\n')[0]);
this.json2iob.parse('userpreferences', this.userpreferences);
this.json2iob.parse('social_profile', this.social_media);
}
} catch (error) {
this.log.error(error);
}
if (res.data.includes('submit-mfa-verification-code-form')) {
this.log.info('MFA required. Please enter MFA in the settings');
return;
}
return body.split('ticket=')[1].split('";')[0];
})
.catch((error) => {
if (error.response && error.response.status === 403) {
this.log.error('Please update node to version 18 or higher');
return;
}
this.log.error('Failed ticket please check username and password');
this.log.error(error);
error.response && this.log.debug(JSON.stringify(error.response.data));
if (this.config.mfa) {
const adapterConfig = 'system.adapter.' + this.name + '.' + this.instance;
this.getForeignObject(adapterConfig, (error, obj) => {
if (obj && obj.native && obj.native.mfa) {
obj.native.mfa = '';
this.setForeignObject(adapterConfig, obj);
}
});
}
});
if (!ticket) {
return;
}
const result = await this.requestClient({
method: 'get',
url: 'https://connect.garmin.com/modern/?ticket=' + ticket,
headers: {
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'user-agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15',
'accept-language': 'en-GB,en;q=0.9',
},
})
.then(async (res) => {
this.log.debug(JSON.stringify(res.data));
this.setState('cookie', JSON.stringify(this.cookieJar.toJSON()), true);
try {
if (res.data.includes('window.VIEWER_USERPREFERENCES')) {
this.userpreferences = JSON.parse(res.data.split('window.VIEWER_USERPREFERENCES = ')[1].split(';\n')[0]);
this.social_media = JSON.parse(res.data.split('window.VIEWER_SOCIAL_PROFILE = ')[1].split(';\n')[0]);
this.json2iob.parse('userpreferences', this.userpreferences);
this.json2iob.parse('social_profile', this.social_media);
}
} catch (error) {
this.log.error(error);
}
this.setState('info.connection', true, true);
await this.requestClient({
method: 'post',
url: 'https://connect.garmin.com/modern/di-oauth/exchange',
headers: {
accept: 'application/json, text/plain, */*',
'x-app-ver': '4.60.2.0',
NK: 'NT',
},
})
.then((res) => {
this.log.debug(JSON.stringify(res.data));
this.session = res.data;
})
.catch((error) => {
this.log.error(error);
error.response && this.log.error(JSON.stringify(error.response.data));
});
return true;
})
.catch((error) => {
this.log.error(error);
error.response && this.log.error(JSON.stringify(error.response.data));
});
return result;
}
async getDeviceList() {
await got('https://connect.garmin.com/device-service/deviceregistration/devices', {
method: 'get',
headers: {
Authorization: 'Bearer ' + this.session.access_token,
'DI-Backend': 'connectapi.garmin.com',
Accept: 'application/json, text/plain, */*',
'X-app-ver': '5.9.0.31a',
'Accept-Language': 'en-GB,en;q=0.9',
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
NK: 'NT',
},
cookieJar: this.cookieJar,
http2: true,
})
.then(async (res) => {
res.data = JSON.parse(res.body);
this.log.debug(JSON.stringify(res.data));
if (res.data) {
this.log.info(`Found ${res.data.length} devices`);
await this.setObjectNotExistsAsync('devices', {
type: 'channel',
common: {
name: 'Devices',
},
native: {},
});
for (const device of res.data) {
this.log.debug(JSON.stringify(device));
const id = device.unitId.toString();
this.deviceArray.push(device);
const name = device.productDisplayName;
await this.setObjectNotExistsAsync('devices.' + id, {
type: 'device',
common: {
name: name,
},
native: {},
});
// await this.setObjectNotExistsAsync(id + ".remote", {
// type: "channel",
// common: {
// name: "Remote Controls",
// },
// native: {},
// });
// const remoteArray = [{ command: "Refresh", name: "True = Refresh" }];
// remoteArray.forEach((remote) => {
// this.setObjectNotExists(id + ".remote." + remote.command, {
// type: "state",
// common: {
// name: remote.name || "",
// type: remote.type || "boolean",
// role: remote.role || "boolean",
// def: remote.def || false,
// write: true,
// read: true,
// },
// native: {},
// });
// });
this.json2iob.parse('devices.' + id + '.general', device, { forceIndex: true });
}
}
})
.catch((error) => {
this.log.error(error);
error.response && this.log.error(JSON.stringify(error.response.data));
});
}
async updateDevices() {
this.userpreferences;
const date = new Date().toISOString().split('T')[0];
const dateMinus10 = new Date(new Date().setDate(new Date().getDate() - 6)).toISOString().split('T')[0];
const statusArray = [
{
path: 'usersummary',
url:
'https://connect.garmin.com/usersummary-service/usersummary/daily/' +
this.userpreferences.displayName +
'?calendarDate=' +
date,
desc: 'User Summary Daily',
},
{
path: 'maxmet',
url: 'https://connect.garmin.com/metrics-service/metrics/maxmet/daily/' + date + '/' + date,
desc: 'Max Metrics Daily',
},
{
path: 'hydration',
url: 'https://connect.garmin.com/usersummary-service/usersummary/hydration/daily/' + date,
desc: 'Hydration Daily',
},
{
path: 'personalrecords',
url: 'https://connect.garmin.com/personalrecord-service/personalrecord/prs/' + this.userpreferences.displayName,
desc: 'Personal Records',
},
{
path: 'adhocchallenge',
url: 'https://connect.garmin.com/adhocchallenge-service/adHocChallenge/historical',
desc: 'Adhoc Challenge',
},
{
path: 'dailysleep',
url:
'https://connect.garmin.com/wellness-service/wellness/dailySleepData/' +
this.userpreferences.displayName +
'?date=' +
date +
'&nonSleepBufferMinutes=60',
desc: 'Daily Sleep',
},
{
path: 'dailystress',
url: 'https://connect.garmin.com/wellness-service/wellness/dailyStress/' + date,
desc: 'Daily Stress',
},
{
path: 'heartrate',
url:
'https://connect.garmin.com/userstats-service/wellness/daily/' +
this.userpreferences.displayName +
'?fromDate=' +
dateMinus10,
desc: 'Resting Heartrate',
},
{
path: 'trainingstatus',
url: 'https://connect.garmin.com/metrics-service/metrics/trainingstatus/aggregated/' + date,
desc: 'Training Status',
},
{
path: 'activities',
url: 'https://connect.garmin.com/activitylist-service/activities/search/activities?start=0&limit=10',
desc: 'Activities',
},
{
path: 'weight',
url: 'https://connect.garmin.com/weight-service/weight/dateRange?startDate=' + dateMinus10 + '&endDate=' + date,
desc: 'Weight',
},
];
for (const element of statusArray) {
// const url = element.url.replace("$id", id);
await got({
cookieJar: this.cookieJar,
method: element.method || 'get',
url: element.url,
headers: {
Accept: 'application/json, text/plain, */*',
'X-app-ver': '5.9.0.31a',
'Accept-Language': 'en-GB,en;q=0.9',
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
Authorization: 'Bearer ' + this.session.access_token,
'DI-Backend': 'connectapi.garmin.com',
},
})
.then(async (res) => {
res.data = JSON.parse(res.body);
this.log.debug(JSON.stringify(res.data));
if (!res.data) {
return;
}
const data = res.data;
const forceIndex = true;
const preferedArrayName = null;
this.json2iob.parse(element.path, data, {
forceIndex: forceIndex,
write: true,
preferedArrayName: preferedArrayName,
channelName: element.desc,
});
// await this.setObjectNotExistsAsync(element.path + ".json", {
// type: "state",
// common: {
// name: "Raw JSON",
// write: false,
// read: true,
// type: "string",
// role: "json",
// },
// native: {},
// });
// this.setState(element.path + ".json", JSON.stringify(data), true);
})
.catch((error) => {
if (error.response) {
if (error.response.statusCode === 401) {
error.response && this.log.debug(JSON.stringify(error.response.body));
this.log.info(element.path + ' received 401 error. Refreshing token in 60 seconds');
this.refreshTokenTimeout && clearTimeout(this.refreshTokenTimeout);
this.refreshTokenTimeout = setTimeout(() => {
this.refreshToken();
}, 1000 * 60);
return;
}
}
this.log.error(element.url);
this.log.error(error);
error.response && this.log.error(JSON.stringify(error.response.body));
});
}
}
extractHidden(body) {
const returnObject = {};
const matches = body.matchAll(/<input (?=[^>]* name=["']([^'"]*)|)(?=[^>]* value=["']([^'"]*)|)/g);
for (const match of matches) {
if (match[2] != null) {
returnObject[match[1]] = match[2];
}
}
return returnObject;
}
async sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async refreshToken() {
this.log.debug('Refresh token');
//set this.config.fgp as cookie JWT_FGP
if (this.config.fgp) {
const cookieString = 'JWT_FGP=' + this.config.fgp.trim() + '; Domain=.connect.garmin.com; Path=/;Secure';
this.cookieJar.setCookieSync(cookieString, 'https://connect.garmin.com');
}
// await this.login();
// await this.requestClient({
// method: 'get',
// maxBodyLength: Infinity,
// url: 'https://sso.garmin.com/sso/login?service=https%3A%2F%2Fconnect.garmin.com%2Fmodern%2Factivities&webhost=https%3A%2F%2Fconnect.garmin.com&gateway=true&generateExtraServiceTicket=true&generateTwoExtraServiceTickets=true&clientId=CAS_CLIENT_DEFAULT',
// headers: {
// Host: 'sso.garmin.com',
// 'Sec-Fetch-Site': 'same-site',
// 'Sec-Fetch-Mode': 'navigate',
// Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
// 'User-Agent':
// 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15',
// 'Accept-Language': 'en-GB,en;q=0.9',
// Referer: 'https://sso.garmin.com/',
// 'Sec-Fetch-Dest': 'document',
// },
// }).catch((error) => {
// this.log.warn('Failed refresh cookies');
// this.log.warn(error);
// error.response && this.log.warn(JSON.stringify(error.response.data));
// });
this.log.debug(JSON.stringify(this.cookieJar.toJSON()));
this.log.debug(this.session.access_token);
this.log.debug(this.session.refresh_token);
await got
.post('https://connect.garmin.com/services/auth/token/refresh', {
cookieJar: this.cookieJar,
http2: true,
headers: {
'Content-Type': 'application/json;charset=utf-8',
baggage:
'sentry-environment=prod,sentry-release=connect%405.9.30,sentry-public_key=f0377f25d5534ad589ab3a9634f25e71,sentry-trace_id=72fb803ded6b453a886dec69c8ecb129,sentry-sample_rate=1,sentry-sampled=true',
Accept: 'application/json, text/plain, */*',
'X-app-ver': '5.9.0.31a',
'Accept-Language': 'en-GB,en;q=0.9',
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
NK: 'NT',
},
json: {
refresh_token: this.session.refresh_token,
},
})
.then(async (res) => {
this.log.debug(JSON.stringify(res.body));
// this.session = res.data;
const resJson = JSON.parse(res.body);
if (resJson.access_token) {
this.session = resJson;
try {
//extract JWT_FGP cookie from response header
this.config.fgp = res.headers['set-cookie'][0].split('JWT_FGP=')[1].split(';')[0];
//eslint-disable-next-line
} catch (error) {
this.log.error('Failed to extract JWT_FGP cookie');
}
}
this.setState('info.connection', true, true);
await this.setState('auth.token', res.body, true);
//set cookie state
await this.setState('cookie', JSON.stringify(this.cookieJar.toJSON()), true);
})
.catch((error) => {
//check for error status 500
//log cookie request header
this.log.debug(error.request.options.headers);
if (error.response && error.response.statusCode === 500) {
this.log.error('FGP missmatch. Please logout and login in garmin and update FGP in the settings');
this.log.debug(error);
this.setState('info.connection', false, true);
this.setState('auth.token', '', true);
this.session = {};
return;
}
this.log.error('Failed refresh token');
this.log.error(error);
error.response && this.log.error(JSON.stringify(error.response.data));
});
}
/**
* Is called when adapter shuts down - callback has to be called under any circumstances!
* @param {() => void} callback
*/
async onUnload(callback) {
try {
this.setState('info.connection', false, true);
this.refreshTimeout && clearTimeout(this.refreshTimeout);
this.reLoginTimeout && clearTimeout(this.reLoginTimeout);
this.refreshTokenTimeout && clearTimeout(this.refreshTokenTimeout);
this.updateInterval && clearInterval(this.updateInterval);
this.refreshTokenInterval && clearInterval(this.refreshTokenInterval);
if (this.config.token) {
const adapterSettings = await this.getForeignObjectAsync('system.adapter.' + this.namespace);
adapterSettings.native.token = null;
adapterSettings.native.fgp = null;
await this.setForeignObjectAsync('system.adapter.' + this.namespace, adapterSettings);
}
callback();
} catch (e) {
this.log.error(e);
callback();
}
}
/**
* Is called if a subscribed state changes
* @param {string} id
* @param {ioBroker.State | null | undefined} state
*/
async onStateChange(id, state) {
if (state) {
if (!state.ack) {
const deviceId = id.split('.')[2];
const command = id.split('.')[5];
if (id.split('.')[4] === 'Refresh') {
this.updateDevices();
return;
}
const data = {
body: {},
header: {
command: 'setAttributes',
said: deviceId,
},
};
data.body[command] = state.val;
await this.requestClient({
method: 'post',
url: '',
})
.then((res) => {
this.log.info(JSON.stringify(res.data));
})
.catch(async (error) => {
this.log.error(error);
error.response && this.log.error(JSON.stringify(error.response.data));
});
this.refreshTimeout = setTimeout(async () => {
this.log.info('Update devices');
await this.updateDevices();
}, 10 * 1000);
}
}
}
}
if (require.main !== module) {
// Export the constructor in compact mode
/**
* @param {Partial<utils.AdapterOptions>} [options={}]
*/
module.exports = (options) => new Garmin(options);
} else {
// otherwise start the instance directly
new Garmin();
}