iobroker.accuweather
Version:
Weather forecast using AccuWeather API
720 lines (719 loc) • 26.6 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var utils = __toESM(require("@iobroker/adapter-core"));
var import_accuapi = require("./lib/accuapi");
var nextHour = __toESM(require("./lib/nexthour-obj"));
let updateInterval = void 0;
let timeout1 = void 0;
let timeout2 = void 0;
let timeout3 = void 0;
class Accuweather extends utils.Adapter {
forecast;
/**
* @param [options] - Optional settings for the adapter
*/
constructor(options = {}) {
super({
...options,
name: "accuweather",
strictObjectChecks: false
});
this.on("ready", this.onReady.bind(this));
this.on("stateChange", this.onStateChange.bind(this));
this.on("unload", this.onUnload.bind(this));
}
getCardinalDirection(angle) {
if (typeof angle === "string") {
angle = parseInt(angle);
}
if (angle <= 0 || angle > 360 || typeof angle === "undefined") {
return "\u2608";
}
const arrows = {
north: "\u2191N",
north_east: "\u2197NE",
east: "\u2192E",
south_east: "\u2198SE",
south: "\u2193S",
south_west: "\u2199SW",
west: "\u2190W",
north_west: "\u2196NW"
};
const directions = Object.keys(arrows);
const degree = 360 / directions.length;
angle = angle + degree / 4;
for (let i = 0; i < directions.length; i++) {
if (angle >= i * degree && angle < (i + 1) * degree) {
return arrows[directions[i]];
}
}
return arrows.north;
}
async setDailyStates(obj) {
this.log.debug(`Daily: ${JSON.stringify(obj)}`);
const days = obj.DailyForecasts;
try {
for (let day = 1; day <= 5; day++) {
const json = days[day - 1];
for (const key in json) {
let dt = null;
switch (key) {
case "Date":
dt = new Date(json[key]);
await this.setState(`Daily.Day${day}.${key}`, {
val: json[key],
ack: true
});
await this.setState(`Summary.DateTime_d${day}`, {
val: json[key],
ack: true
});
await this.setState(`Summary.DayOfWeek_d${day}`, {
val: dt.toLocaleString(this.config.language, {
weekday: "short"
}),
ack: true
});
break;
case "Sun":
await this.setState(`Daily.Day${day}.Sunrise`, {
val: json[key].Rise,
ack: true
});
await this.setState(`Daily.Day${day}.Sunset`, {
val: json[key].Set,
ack: true
});
if (day === 1) {
await this.setState("Summary.Sunrise", {
val: json[key].Rise,
ack: true
});
await this.setState("Summary.Sunset", {
val: json[key].Set,
ack: true
});
}
break;
case "HoursOfSun":
await this.setState(`Daily.Day${day}.HoursOfSun`, {
val: json[key],
ack: true
});
if (day === 1) {
await this.setState("Summary.HoursOfSun", {
val: json[key],
ack: true
});
}
break;
case "Temperature":
await this.setState(`Daily.Day${day}.Temperature.Minimum`, {
val: json[key].Minimum.Value,
ack: true
});
await this.setState(`Daily.Day${day}.Temperature.Maximum`, {
val: json[key].Maximum.Value,
ack: true
});
await this.setState(`Summary.TempMin_d${day}`, {
val: json[key].Minimum.Value,
ack: true
});
await this.setState(`Summary.TempMax_d${day}`, {
val: json[key].Maximum.Value,
ack: true
});
break;
case "RealFeelTemperature":
await this.setState(`Daily.Day${day}.RealFeelTemperature.Minimum`, {
val: json[key].Minimum.Value,
ack: true
});
await this.setState(`Daily.Day${day}.RealFeelTemperature.Maximum`, {
val: json[key].Maximum.Value,
ack: true
});
break;
case "Day":
case "Night":
{
const json1 = json[key];
for (const key1 in json1) {
if (typeof json1[key1] !== "object") {
await this.setState(`Daily.Day${day}.${key}.${key1}`, {
val: json1[key1],
ack: true
});
if (key1 === "Icon") {
await this.setState(`Daily.Day${day}.${key}.IconURL`, {
val: `https://developer.accuweather.com/sites/default/files/${String(json1[key1]).padStart(2, "0")}-s.png`,
ack: true
});
await this.setState(`Daily.Day${day}.${key}.IconURLS`, {
val: `http://vortex.accuweather.com/adc2010/images/slate/icons/${String(json1[key1]).padStart(2, "0")}.svg`,
ack: true
});
if (key === "Day") {
await this.setState(`Summary.WeatherIconURL_d${day}`, {
val: `http://vortex.accuweather.com/adc2010/images/slate/icons/${String(json1[key1]).padStart(2, "0")}.svg`,
ack: true
});
await this.setState(`Summary.WeatherIcon_d${day}`, {
val: json1[key1],
ack: true
});
}
} else if (key === "Day") {
if (key1 === "IconPhrase") {
await this.setState(`Summary.WeatherText_d${day}`, {
val: json1[key1],
ack: true
});
} else {
await this.setState(`Summary.${key1}_d${day}`, {
val: json1[key1],
ack: true
});
}
}
} else if (typeof json1[key1] == "object") {
if (json1[key1].Value !== void 0) {
if (["TotalLiquid", "Rain", "Snow", "Ice"].includes(key1)) {
await this.setState(`Daily.Day${day}.${key}.${key1}Volume`, {
val: json1[key1].Value,
ack: true
});
if (key === "Day" && key1 === "TotalLiquid") {
await this.setState(`Summary.TotalLiquidVolume_d${day}`, {
val: json1[key1].Value,
ack: true
});
}
} else {
await this.setState(`Daily.Day${day}.${key}.${key1}`, {
val: json1[key1].Value,
ack: true
});
}
} else if (key1 === "Wind") {
await this.setState(`Daily.Day${day}.${key}.WindSpeed`, {
val: json1[key1].Speed.Value,
ack: true
});
await this.setState(`Daily.Day${day}.${key}.WindDirection`, {
val: json1[key1].Direction.Degrees,
ack: true
});
if (key === "Day") {
await this.setState(`Summary.WindSpeed_d${day}`, {
val: json1[key1].Speed.Value,
ack: true
});
await this.setState(`Summary.WindDirection_d${day}`, {
val: json1[key1].Direction.Degrees,
ack: true
});
await this.setState(`Summary.WindDirectionStr_d${day}`, {
val: this.getCardinalDirection(json1[key1].Direction.Degrees),
ack: true
});
}
} else if (key1 === "WindGust") {
await this.setState(`Daily.Day${day}.${key}.WindGust`, {
val: json1[key1].Speed.Value,
ack: true
});
}
}
}
}
break;
default:
break;
}
}
}
} catch (err) {
this.log.error(String(err));
}
}
async setNextHourStates(obj, item, hour) {
this.log.debug(`Hours: ${JSON.stringify(obj)}`);
const json = obj[item];
try {
for (const key in json) {
if (typeof json[key] !== "object") {
await this.setState(`Hourly.h${hour}.${key}`, {
val: json[key],
ack: true
});
if (key === "WeatherIcon") {
await this.setState(`Hourly.h${hour}.WeatherIconURL`, {
val: `https://developer.accuweather.com/sites/default/files/${String(json[key]).padStart(2, "0")}-s.png`,
ack: true
});
await this.setState(`Hourly.h${hour}.WeatherIconURLS`, {
val: `http://vortex.accuweather.com/adc2010/images/slate/icons/${String(json[key]).padStart(2, "0")}.svg`,
ack: true
});
}
} else if (typeof json[key] == "object") {
if (json[key].Value !== void 0) {
if (["TotalLiquid", "Rain", "Snow", "Ice"].includes(key)) {
await this.setState(`Hourly.h${hour}.${key}Volume`, {
val: json[key].Value,
ack: true
});
} else {
await this.setState(`Hourly.h${hour}.${key}`, {
val: json[key].Value,
ack: true
});
}
} else if (key === "Wind") {
await this.setState(`Hourly.h${hour}.WindSpeed`, {
val: json[key].Speed.Value,
ack: true
});
await this.setState(`Hourly.h${hour}.WindDirection`, {
val: json[key].Direction.Degrees,
ack: true
});
await this.setState(`Hourly.h${hour}.WindDirectionText`, {
val: json[key].Direction.Localized,
ack: true
});
} else if (key === "WindGust") {
await this.setState(`Hourly.h${hour}.WindGust`, {
val: json[key].Speed.Value,
ack: true
});
}
}
}
} catch (err) {
this.log.error(String(err));
}
}
async setCurrentStates(obj) {
this.log.debug(`Current: ${JSON.stringify(obj)}`);
const json = obj[0];
try {
for (const key in json) {
if (typeof json[key] !== "object" || json[key] == null) {
await this.setState(`Current.${key}`, { val: json[key], ack: true });
if (key === "WeatherIcon") {
await this.setState("Current.WeatherIconURL", {
val: `https://developer.accuweather.com/sites/default/files/${String(json[key]).padStart(2, "0")}-s.png`,
ack: true
});
await this.setState("Current.WeatherIconURLS", {
val: `http://vortex.accuweather.com/adc2010/images/slate/icons/${String(json[key]).padStart(2, "0")}.svg`,
ack: true
});
await this.setState("Summary.WeatherIconURL", {
val: `http://vortex.accuweather.com/adc2010/images/slate/icons/${String(json[key]).padStart(2, "0")}.svg`,
ack: true
});
await this.setState("Summary.WeatherIcon", {
val: json[key],
ack: true
});
} else if (key === "LocalObservationDateTime") {
const dt = new Date(json[key]);
const dow = dt.toLocaleString(this.config.language, {
weekday: "short"
});
await this.setState("Summary.CurrentDateTime", {
val: json[key],
ack: true
});
await this.setState("Summary.DayOfWeek", { val: dow, ack: true });
this.log.debug(
`Date ${dt.getTime()}, dow: ${dt.toLocaleString(this.config.language, { weekday: "short" })}`
);
} else {
await this.setState(`Summary.${key}`, {
val: json[key],
ack: true
});
}
} else if (json[key] !== null) {
if (json[key][this.config.metric] !== void 0) {
await this.setState(`Current.${key}`, {
val: json[key][this.config.metric].Value,
ack: true
});
await this.setState(`Summary.${key}`, {
val: json[key][this.config.metric].Value,
ack: true
});
} else if (key === "Wind") {
await this.setState("Current.WindSpeed", {
val: json[key].Speed[this.config.metric].Value,
ack: true
});
await this.setState("Summary.WindSpeed", {
val: json[key].Speed[this.config.metric].Value,
ack: true
});
await this.setState("Current.WindDirection", {
val: json[key].Direction.Degrees,
ack: true
});
await this.setState("Current.WindDirectionText", {
val: json[key].Direction.Localized,
ack: true
});
await this.setState("Summary.WindDirection", {
val: json[key].Direction.Degrees,
ack: true
});
await this.setState("Summary.WindDirectionStr", {
val: this.getCardinalDirection(json[key].Direction.Degrees),
ack: true
});
} else if (key === "WindGust") {
await this.setState("Current.WindGust", {
val: json[key].Speed[this.config.metric].Value,
ack: true
});
} else if (key === "PressureTendency") {
await this.setState("Current.PressureTendency", {
val: json[key].LocalizedText,
ack: true
});
} else if (key === "Photos" && Array.isArray(json[key])) {
const l = json[key].length;
const index = Math.round(Math.random() * (l - 1));
await this.setState("Current.LandscapeLink", json[key][index].LandscapeLink, true);
await this.setState("Current.PortraitLink", json[key][index].PortraitLink, true);
}
}
}
await this.setState("CurrentNextUpdate", Date.now(), true);
} catch (err) {
this.log.error(String(err));
}
}
async setHourlyStates(obj) {
for (const hr in obj) {
if (typeof obj[hr] === "object" && obj[hr].DateTime) {
const d = new Date(obj[hr].DateTime);
await this.setNextHourStates(obj, hr, String(d.getHours()));
}
}
await this.setState("HourlyNextUpdate", Date.now(), true);
}
async request5Days() {
if (typeof this.forecast !== "undefined") {
const loc = this.config.loKey;
const lang = this.config.language;
this.forecast = this.forecast.localkey(loc).timeInt("daily/5day").language(lang).details(true).metric(true);
const res = await this.forecast.get();
await this.setDailyStates(res);
}
await this.setState("DailyNextUpdate", Date.now(), true);
}
async request12Hours() {
if (typeof this.forecast !== "undefined") {
const loc = this.config.loKey;
const lang = this.config.language;
this.forecast = this.forecast.localkey(loc).timeInt("hourly/12hour").language(lang).details(true).metric(true);
const res = await this.forecast.get();
await this.setHourlyStates(res);
}
}
async requestCurrent() {
if (typeof this.forecast !== "undefined") {
const loc = this.config.loKey;
const lang = this.config.language;
this.forecast = this.forecast.localkey(loc).timeInt().language(lang).details(true).metric(true).getphotos(true);
const res = await this.forecast.getCurrent();
await this.setCurrentStates(res);
}
}
async onReady() {
const nameSpaceObj = await this.getForeignObjectAsync(this.namespace);
if (!nameSpaceObj) {
await this.setForeignObjectAsync(this.namespace, {
_id: this.namespace,
type: "meta",
common: { name: "Accuweather device", type: "meta.folder" },
native: {}
});
}
const obj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);
if (obj && obj.native && obj.native.apiKey) {
obj.native.apiKeyEncrypted = this.encrypt(obj.native.apiKey);
this.config.apiKeyEncrypted = obj.native.apiKey;
delete obj.native.apiKey;
await this.setForeignObjectAsync(`system.adapter.${this.namespace}`, obj);
}
if (this.config.metric !== "Metric" && this.config.metric !== "Imperial") {
this.config.metric = "Metric";
}
if (!this.config.language) {
const systemConfig = await this.getForeignObjectAsync("system.config");
if (systemConfig && systemConfig.common && systemConfig.common.language) {
this.config.language = systemConfig.common.language;
}
}
this.log.debug(`API: ********; Loc: ${this.config.loKey} Lang: ${this.config.language}`);
if (this.config.apiKeyEncrypted) {
this.forecast = new import_accuapi.Accuapi(this.config.apiKeyEncrypted);
} else {
this.log.error("API Key is missing. Please enter Accuweather API key");
return;
}
await nextHour.createHourlyForecastObjects(this);
await nextHour.createCurrentConditionObjects(this);
await nextHour.createDailyForecastObjects(this);
await nextHour.createSummaryObjects(this);
await this.extendObject("CurrentNextUpdate", {
type: "state",
common: {
name: "Last Update of Current Weather",
type: "number",
role: "date",
read: true,
write: false
},
native: {}
});
await this.extendObject("HourlyNextUpdate", {
type: "state",
common: {
name: "Last Update of Hourly Weather",
type: "number",
role: "date",
read: true,
write: false
},
native: {}
});
await this.extendObject("DailyNextUpdate", {
type: "state",
common: {
name: "Last Update of Daily Weather",
type: "number",
role: "date",
read: true,
write: false
},
native: {}
});
const startForbidden = {};
let state = await this.getStateAsync("CurrentNextUpdate");
startForbidden.current = !!(state && state.val && Number(state.val) + 66 * 6e4 > Date.now());
state = await this.getStateAsync("HourlyNextUpdate");
startForbidden.hourly = !!(state && state.ts && state.ts + ((/* @__PURE__ */ new Date()).setHours(7, 5, 0) < Date.now() && (/* @__PURE__ */ new Date()).setHours(20, 6, 0) > Date.now() ? 13 : 11) * 60 * 6e4 + 36e4 > Date.now());
state = await this.getStateAsync("DailyNextUpdate");
startForbidden.daily = !!(state && state.ts && state.ts + 6 * 60 * 6e4 + 36e4 > Date.now());
updateInterval = this.setInterval(() => {
const now = /* @__PURE__ */ new Date();
if ((now.getHours() === 7 || now.getHours() === 20) && now.getMinutes() < 5) {
const _get5DaysTimeout = () => {
timeout1 && this.clearTimeout(timeout1);
timeout1 = this.setTimeout(
async () => {
try {
timeout1 = null;
await this.request5Days();
} catch (error) {
if (this.errorLog(error)) {
this.log.info(`Retry in 10 Minutes`);
timeout1 = this.setTimeout(_get5DaysTimeout, 6e5);
}
}
},
Math.random() * 1e4 + 1
);
};
_get5DaysTimeout();
}
if (now.getMinutes() < 5) {
const _getMinutesTimeout = () => {
timeout2 && this.clearTimeout(timeout2);
timeout2 = this.setTimeout(
async () => {
try {
timeout2 = null;
await this.requestCurrent();
} catch (error) {
if (this.errorLog(error)) {
this.log.info(`Retry in 10 Minutes`);
timeout2 = this.setTimeout(_getMinutesTimeout, 6e5);
}
}
},
Math.random() * 1e4 + 1
);
};
_getMinutesTimeout();
}
if ((now.getHours() === 6 || now.getHours() === 12 || now.getHours() === 18 || now.getHours() === 0) && now.getMinutes() < 5) {
const _get12HoursTimeout = () => {
timeout3 && this.clearTimeout(timeout3);
timeout3 = this.setTimeout(
async () => {
try {
timeout3 = null;
await this.request12Hours();
} catch (error) {
if (this.errorLog(error)) {
this.log.info(`Retry in 10 Minutes`);
timeout3 = this.setTimeout(_get12HoursTimeout, 6e5);
}
}
},
Math.random() * 1e4 + 1
);
};
_get12HoursTimeout();
}
}, 3e5);
try {
if (!startForbidden.hourly) {
this.log.info("Update hourly weather!");
await this.request12Hours();
}
if (!startForbidden.current) {
this.log.info("Update current weather!");
await this.requestCurrent();
}
if (!startForbidden.daily) {
this.log.info("Update daily weather!");
await this.request5Days();
}
} catch (error) {
this.errorLog(error);
}
await this.extendObject("updateCurrent", {
type: "state",
common: {
name: "Update Current Weather",
type: "boolean",
role: "button",
read: false,
write: true
},
native: {}
});
await this.extendObject("updateHourly", {
type: "state",
common: {
name: "Update 12 Hours Forecast",
type: "boolean",
role: "button",
read: false,
write: true
},
native: {}
});
await this.extendObject("updateDaily", {
type: "state",
common: {
name: "Update 5 Days Forecast",
type: "boolean",
role: "button",
read: false,
write: true
},
native: {}
});
await this.subscribeStatesAsync("updateCurrent");
await this.subscribeStatesAsync("updateHourly");
await this.subscribeStatesAsync("updateDaily");
}
errorLog(error) {
if (error && error.cause && error.cause.status) {
if (error.cause.status === 503) {
this.log.error(
`${error.message} - apikey incorrect, too many queries or incorrect settings in accuweather!`
);
} else {
this.log.error(`${error.message} - User action required!`);
}
return false;
}
this.log.error(error);
return true;
}
/**
* Is called when adapter shuts down - callback has to be called under any circumstances!
*
* @param callback - The callback function to be called when the unload process is complete
*/
onUnload(callback) {
try {
this.log.info("cleaned everything up...");
updateInterval && this.clearInterval(updateInterval);
updateInterval = null;
timeout1 && this.clearTimeout(timeout1);
timeout1 = null;
timeout2 && this.clearTimeout(timeout2);
timeout2 = null;
timeout3 && this.clearTimeout(timeout3);
timeout3 = null;
callback();
} catch {
callback && callback();
}
}
/**
* Is called if a subscribed state changes
*
* @param id - The id of the state that changed
* @param state - The state object that changed
*/
async onStateChange(id, state) {
if (state) {
if (!state.ack) {
try {
if (id === `${this.namespace}.updateCurrent`) {
await this.requestCurrent();
} else if (id === `${this.namespace}.updateHourly`) {
await this.request12Hours();
} else if (id === `${this.namespace}.updateDaily`) {
await this.request5Days();
}
this.log.debug(`state ${id} changed: ${state.val} (ack = ${state.ack})`);
} catch (error) {
this.log.error(error);
}
}
} else {
this.log.debug(`state ${id} deleted`);
}
}
}
if (require.main !== module) {
module.exports = (options) => new Accuweather(options);
} else {
(() => new Accuweather())();
}
module.exports = Accuweather;
//# sourceMappingURL=main.js.map