iobroker.weatherflow_udp
Version:
Weatherflow UDP receiver
1,390 lines (1,256 loc) • 59.4 kB
JavaScript
/*
* Created with @iobroker/create-adapter v1.25.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");
// Load your modules here, e.g.:
const dgram = require("dgram");
// If radiation is more than 120 W/m2 it is counted as sunshine (https://de.wikipedia.org/wiki/Sonnenschein)
const SUNSHINETHRESHOLD = 120;
// const as min max function parameter
const MIN = 1;
const MAX = 2;
let mServer = null;
let timer;
let now = new Date(); // set as system time for now, will be overwritten if timestamp is recieved
let oldNow = new Date(); // set as system time for now, will be overwritten if timestamp is recieved
const existingStates = [];
// Import constants with static interpretation data
const {
devices,
messages,
windDirections,
minCalcs,
maxCalcs,
sensorfails,
powermodes,
} = require(`${__dirname}/lib/messages`);
class WeatherflowUdp extends utils.Adapter {
/**
* @param {Partial<utils.AdapterOptions>} [options]
*/
constructor(options) {
super({
...options,
name: "weatherflow_udp",
});
this.on("ready", this.onReady.bind(this));
this.on("unload", this.onUnload.bind(this));
}
/**
* Is called when databases are connected and adapter received configuration.
*/
onReady() {
// Initialize your adapter here
this.main();
}
async main() {
const that = this;
mServer = dgram.createSocket("udp4");
// Attach to UDP Port
try {
mServer.bind(this.config.UDP_port, "0.0.0.0");
} catch (e) {
that.log.error(
[
"Could not bind to port: ",
this.config.UDP_port,
". Adapter stopped.",
].join(""),
);
}
mServer.on("error", (err) => {
this.log.error(`Cannot open socket:\n${err.stack}`);
mServer.close();
timer = setTimeout(() => process.exit(), 1000); // delay needed to wait for logging
});
// Reset the connection indicator during startup
this.setState("info.connection", false, true);
mServer.on("listening", () => {
const address = mServer.address();
that.log.info(`adapter listening ${address.address}:${address.port}`);
});
// Receive UDP message
mServer.on("message", (messageString, rinfo) => {
let message; // JSON parsed message
if (that.config.debug === true) {
that.log.debug(
`${rinfo.address}:${rinfo.port} - ${messageString.toString("ascii")}`,
);
}
try {
message = JSON.parse(messageString.toString());
} catch (e) {
// Anweisungen für jeden Fehler
that.log.warn(
['Non-JSON message received: "', message, '". Ignoring.'].join(""),
);
return;
}
// stop processing if message does not have a type
if ("type" in message === false) {
that.log.warn(
[
'Non- or unknown weatherflow message received: "',
message,
'". Ignoring.',
].join(""),
);
return;
}
// Set connection state when message received and expire after 6 minutes of inactivity
that.setStateAsync("info.connection", {
val: true,
ack: true,
expire: 360,
});
const messageType = message.type; // e.g. 'rapid_wind'
if (that.config.debug === true) {
that.log.info(['Message type: "', message.type, '"'].join(""));
}
const messageInfo = messages[messageType];
if (that.config.debug === true) {
that.log.info(["messageInfo: ", JSON.stringify(messageInfo)].join(""));
}
let statePath; // name of current state to set or create
if (!messageInfo) {
if (that.config.debug === true) {
that.log.info(
["Unknown message type: ", messageType, " - ignoring"].join(""),
);
}
} else {
if (that.config.debug === true) {
that.log.info(
["messageInfo: ", JSON.stringify(messageInfo)].join(""),
);
}
if ("serial_number" in message) {
// create structure for device
// Get type from first 2 characters of serial number
const deviceType = devices[message.serial_number.substring(0, 2)];
const serialParameters = {
type: "device",
common: {
name: `${deviceType}: ${message.serial_number}`,
},
native: {},
};
if ("hub_sn" in message) {
// device message with serial and hub serial
// Create state for hub
// Type is first 2 chars of serial number
const hubType = devices[message.hub_sn.substring(0, 2)];
const hubSnParameters = {
type: "device",
common: {
name: `${hubType}:${message.hub_sn}`,
},
native: {},
};
that.myCreateState(message.hub_sn, hubSnParameters); // create device
that.myCreateState(
[message.hub_sn, message.serial_number].join("."),
serialParameters,
); // create device
// Set complete path to state hub and serial
statePath = [
message.hub_sn,
message.serial_number,
message.type,
].join(".");
} else {
// device message without hub serial (probably only hub)
that.myCreateState(message.serial_number, serialParameters); // create device
// Set path to state
statePath = [message.serial_number, ".", message.type].join("");
}
}
if (that.config.debug) {
that.log.info(["statepath: ", statePath].join(""));
}
// Write last message to the node lastMessage in statepath
const lastMessageParameter = {
type: "state",
common: {
name: "Last message on this channel",
type: "string",
role: "text",
read: true,
write: false,
},
native: {},
};
that.myCreateState(
[statePath, "lastMessage"].join("."),
lastMessageParameter,
messageString.toString("ascii"),
); // create/update lestMessage state per message type
// Walk through items of message
Object.keys(message).forEach((item) => {
let itemvalue = [];
if (typeof message[item][0] === "object") {
// some items like 'obs' are double arrays [[]], remove outer array
itemvalue = message[item][0];
} else if (typeof message[item] === "object") {
// some are arrays, take as is
itemvalue = message[item];
} else if (
typeof message[item] === "number" ||
typeof message[item] === "string"
) {
// others are just numbers or strings, then wrap into an array
itemvalue.push(message[item]);
}
if (that.config.debug) {
that.log.info(["item: ", item, " = ", itemvalue].join(""));
}
// Set some Items to be ignored later as they are parsed differently
const ignoreItems = ["type", "serial_number", "hub_sn"];
// Check for unknown/new items
if (
item in messageInfo === false &&
ignoreItems.includes(item) === false
) {
that.log.warn(
[
"Message ",
messageType,
" contains unknown parameter: ",
item,
" = ",
itemvalue,
". Ignoring. Please check UDP message version and check with adapter developer.",
].join(""),
);
}
// only parse if part of 'states' definition
if (
messageInfo[item] !== null &&
ignoreItems.includes(item) === false
) {
// Walk through fields 0 ... n
Object.keys(itemvalue).forEach(async (field) => {
if (!messageInfo[item][field]) {
that.log.warn(
[
'Message contains unknown field "(',
field,
'" in message ',
item,
')". Check UDP message version and inform adapter developer.',
].join(""),
);
return;
}
const pathParameters = {
type: "channel",
common: {
name: messageInfo.name,
},
native: {},
};
const stateParameters = messageInfo[item][field][1];
const stateName = [statePath, messageInfo[item][field][0]].join(
".",
);
let fieldvalue = itemvalue[field];
// Deal with timestamp messages
if (messageInfo[item][field][0] === "timestamp") {
fieldvalue = new Date(fieldvalue * 1000).getTime(); // timestamp in iobroker is milliseconds and provided timestamp is seconds
}
if (that.config.debug === true) {
that.log.info(
[
"[",
field,
"] ",
"state: ",
stateName,
" = ",
fieldvalue,
].join(""),
);
}
// handle timestamp old and new as now and oldNow
// used later
if (messageInfo[item][field][0] === "timestamp") {
now = new Date(fieldvalue); // now is date/time of current message
const obj = await that.getValObj(stateName);
if (obj !== null) {
oldNow = new Date(obj.val);
}
}
// Special corrections on data
//= =====================================
if (
messageInfo[item][field][0] === "lightningStrikeAvgDistance" &&
fieldvalue === 0
) {
// If average lightning distance is zero, no lightning was detected, set to 999 to mark this fact
fieldvalue = 999;
}
// Walkaround for for occasional 0-pressure values
if (
messageInfo[item][field][0] === "stationPressure" &&
fieldvalue === 0
) {
return; // skip value if this happens
}
// Calculate minimum values of today and yesterday for native values
// Min-values
if (minCalcs.includes(messageInfo[item][field][0])) {
that.calcMinMaxValue(
stateName,
stateParameters,
fieldvalue,
MIN,
);
}
// Max-values
if (maxCalcs.includes(messageInfo[item][field][0])) {
that.calcMinMaxValue(
stateName,
stateParameters,
fieldvalue,
MAX,
);
}
// And update states
//= ============
that.myCreateState(statePath, pathParameters); // create channel
that.myCreateState(stateName, stateParameters, fieldvalue); // create node
//= =====================================
// Do special tasks based on message type
//= =====================================
// set a state for rain intensity
// ------------------------------
// NONE: 0 mm / hour
// VERY LIGHT: > 0, < 0.25 mm / hour
// LIGHT: ≥ 0.25, < 1.0 mm / hour
// MODERATE: ≥ 1.0, < 4.0 mm / hour
// HEAVY: ≥ 4.0, < 16.0 mm / hour
// VERY HEAVY: ≥ 16.0, < 50 mm / hour
// EXTREME: > 50.0 mm / hour
if (messageInfo[item][field][0] === "precipAccumulated") {
const stateNameRainIntensity = [
statePath,
"rainIntensity",
].join(".");
const stateParametersRainIntensity = {
type: "state",
common: {
type: "mixed",
states: {
0: "none",
1: "very light",
2: "light",
3: "moderate",
4: "heavy",
5: "very heavy",
6: "extreme",
},
read: true,
write: false,
role: "value.precipitation.level",
name: "Rain intensity; adapter calculated",
},
native: {},
};
const reportIntervalName = [statePath, "reportInterval"].join(
".",
);
let rainIntensity = 0;
const reportInterval = await that.getValObj(reportIntervalName);
if (reportInterval !== null) {
if ((fieldvalue * 60) / reportInterval.val > 50) {
rainIntensity = 6;
} else if ((fieldvalue * 60) / reportInterval.val > 16) {
rainIntensity = 5;
} else if ((fieldvalue * 60) / reportInterval.val > 4) {
rainIntensity = 4;
} else if ((fieldvalue * 60) / reportInterval.val > 1) {
rainIntensity = 3;
} else if ((fieldvalue * 60) / reportInterval.val > 0.25) {
rainIntensity = 2;
} else if ((fieldvalue * 60) / reportInterval.val > 0) {
rainIntensity = 1;
}
that.myCreateState(
stateNameRainIntensity,
stateParametersRainIntensity,
rainIntensity,
);
}
}
// raining or not as boolean state?
//-------------------------------
if (messageInfo[item][field][0] === "precipAccumulated") {
const statePathCorrected = statePath
.replace("obs_st", "evt_precip")
.replace("obs_sky", "evt_precip"); // move state from observation to evt_precip
const stateNameRaining = [statePathCorrected, "raining"].join(
".",
);
const stateParametersRaining = {
type: "state",
common: {
type: "boolean",
read: true,
write: false,
role: "indicator.rain",
name: "Raining; adapter calculated",
def: false,
},
native: {},
};
if (fieldvalue > 0) {
that.myCreateState(
stateNameRaining,
stateParametersRaining,
true,
);
} else {
that.myCreateState(
stateNameRaining,
stateParametersRaining,
false,
);
}
}
if (
messageType === "evt_precip" &&
messageInfo[item][field][0] === "timestamp"
) {
// if precipitation start is received also set to true
const stateNameRaining = [statePath, "raining"].join(".");
const stateParametersRaining = {
type: "state",
common: {
type: "boolean",
read: true,
write: false,
role: "indicator.rain",
name: "Raining; adapter calculated",
def: false,
},
native: {},
};
that.myCreateState(
stateNameRaining,
stateParametersRaining,
true,
);
}
// rain accumulation and time of current and previous hour
//-------------------------------------------------------
if (messageInfo[item][field][0] === "precipAccumulated") {
// rain amount
// -----------
const stateNameCurrentHourA = [
statePath,
"precipAccumulatedCurrentHour",
].join(".");
const stateParametersCurrentHourA = {
type: "state",
common: {
type: "number",
unit: "mm",
read: true,
write: false,
role: "value.precipitation",
name: "Accumulated rain in current hour; adapter calculated",
},
native: {},
};
const stateNamePreviousHourA = [
statePath,
"precipAccumulatedPreviousHour",
].join(".");
const stateParametersPreviousHourA = {
type: "state",
common: {
type: "number",
unit: "mm",
read: true,
write: false,
role: "value.precipitation",
name: "Accumulated rain in previous hour; adapter calculated",
},
native: {},
};
const stateNameTodayA = [
statePath,
"precipAccumulatedToday",
].join(".");
const stateParametersTodayA = {
type: "state",
common: {
type: "number",
unit: "mm",
read: true,
write: false,
role: "value.precipitation",
name: "Accumulated rain today; adapter calculated",
},
native: {},
};
const stateNameYesterdayA = [
statePath,
"precipAccumulatedYesterday",
].join(".");
const stateParametersYesterdayA = {
type: "state",
common: {
type: "number",
unit: "mm",
read: true,
write: false,
role: "value.precipitation",
name: "Accumulated rain yesterday; adapter calculated",
},
native: {},
};
let newValueHourA = 0;
let newValueDayA = 0;
// hour
const objhourA = await this.getValObj(stateNameCurrentHourA); // get old value
if (objhourA !== null) {
if (now.getHours() === oldNow.getHours()) {
// same hour
newValueHourA = objhourA.val + fieldvalue; // add
} else {
// different hour
newValueHourA = fieldvalue; // replace
that.myCreateState(
stateNamePreviousHourA,
stateParametersPreviousHourA,
objhourA.val,
); // save value from current hour to last hour
}
}
that.myCreateState(
stateNameCurrentHourA,
stateParametersCurrentHourA,
newValueHourA,
); // always write value for current hour
// day
const objdayA = await this.getValObj(stateNameTodayA); // get old value
if (objdayA !== null) {
if (now.getDay() === oldNow.getDay()) {
// same hour
newValueDayA = objdayA.val + fieldvalue; // add
} else {
// different hour
newValueDayA = fieldvalue; // replace
that.myCreateState(
stateNameYesterdayA,
stateParametersYesterdayA,
objdayA.val,
); // save value from current day to yesterday
}
}
that.myCreateState(
stateNameTodayA,
stateParametersTodayA,
newValueDayA,
); // always write value for current day
// rain duration
// -------------
const stateNameCurrentHourD = [
statePath,
"precipDurationCurrentHour",
].join(".");
const stateParametersCurrentHourD = {
type: "state",
common: {
type: "number",
unit: "min",
read: true,
write: false,
role: "value.precipitation.duration",
name: "Rain duration in current hour; adapter calculated",
},
native: {},
};
const stateNamePreviousHourD = [
statePath,
"precipDurationPreviousHour",
].join(".");
const stateParametersPreviousHourD = {
type: "state",
common: {
type: "number",
unit: "min",
read: true,
write: false,
role: "value.precipitation.duration",
name: "Rain duration in previous hour; adapter calculated",
},
native: {},
};
const stateNameTodayD = [statePath, "precipDurationToday"].join(
".",
);
const stateParametersTodayD = {
type: "state",
common: {
type: "number",
unit: "h",
read: true,
write: false,
role: "value.precipitation.duration",
name: "Rain duration today; adapter calculated",
},
native: {},
};
const stateNameYesterdayD = [
statePath,
"precipDurationYesterday",
].join(".");
const stateParametersYesterdayD = {
type: "state",
common: {
type: "number",
unit: "h",
read: true,
write: false,
role: "value.precipitation.duration",
name: "Rain duration yesterday; adapter calculated",
},
native: {},
};
const reportIntervalNameD = [statePath, "reportInterval"].join(
".",
);
let newValueHourD = 0;
let newValueDayD = 0;
// hour
const objhourD = await this.getValObj(stateNameCurrentHourD); // get old value
const reportIntervalD =
await this.getValObj(reportIntervalNameD); // get report Interval for multiplication
if (objhourD !== null && reportIntervalD !== null) {
if (now.getHours() === oldNow.getHours()) {
// same hour
if (fieldvalue > 0) {
newValueHourD = objhourD.val + reportIntervalD.val; // add
} else {
newValueHourD = objhourD.val; // no change
}
} else {
// different hour
if (fieldvalue > 0) {
newValueHourD = reportIntervalD.val; // replace
} else {
newValueHourD = 0; // reset todays value
}
that.myCreateState(
stateNamePreviousHourD,
stateParametersPreviousHourD,
objhourD.val,
); // save value from current hour to last hour
}
}
that.myCreateState(
stateNameCurrentHourD,
stateParametersCurrentHourD,
newValueHourD,
); // always write value for current hour
const objdayD = await this.getValObj(stateNameTodayD); // get old value
if (objdayD !== null && reportIntervalD !== null) {
if (now.getDay() === oldNow.getDay()) {
// same day
if (fieldvalue > 0) {
newValueDayD = objdayD.val + reportIntervalD.val / 60; // add in hours
} else {
newValueDayD = objdayD.val;
}
} else {
// different day
if (fieldvalue > 0) {
newValueDayD = reportIntervalD.val / 60; // replace
} else {
newValueDayD = 0;
}
that.myCreateState(
stateNameYesterdayD,
stateParametersYesterdayD,
objdayD.val,
); // save value from current day to last yesterday
}
}
that.myCreateState(
stateNameTodayD,
stateParametersTodayD,
newValueDayD,
); // always write value for current day
}
// sunshine duration of previous and current hour, today and last day
//------------------------------------------------------------------
if (messageInfo[item][field][0] === "solarRadiation") {
// sunshine duration
const stateNameCurrentHour = [
statePath,
"sunshineDurationCurrentHour",
].join(".");
const stateParametersCurrentHour = {
type: "state",
common: {
type: "number",
unit: "min",
read: true,
write: false,
role: "value.sunshine",
name: "Sunshine duration in current hour; adapter calculated",
},
native: {},
};
const stateNamePreviousHour = [
statePath,
"sunshineDurationPreviousHour",
].join(".");
const stateParametersPreviousHour = {
type: "state",
common: {
type: "number",
unit: "min",
read: true,
write: false,
role: "value.sunshine",
name: "Sunshine duration in previous hour; adapter calculated",
},
native: {},
};
const stateNameToday = [
statePath,
"sunshineDurationToday",
].join(".");
const stateParametersToday = {
type: "state",
common: {
type: "number",
unit: "h",
read: true,
write: false,
role: "value.sunshine",
name: "Sunshine duration today; adapter calculated",
},
native: {},
};
const stateNameYesterday = [
statePath,
"sunshineDurationYesterday",
].join(".");
const stateParametersYesterday = {
type: "state",
common: {
type: "number",
unit: "h",
read: true,
write: false,
role: "value.sunshine",
name: "Sunshine duration yesterday; adapter calculated",
},
native: {},
};
const reportIntervalName = [statePath, "reportInterval"].join(
".",
);
// hour
let newValueHour = 0;
let newValueDay = 0;
const objhour = await that.getValObj(stateNameCurrentHour); // get old value
const reportInterval = await that.getValObj(reportIntervalName);
if (objhour !== null && reportInterval !== null) {
if (now.getHours() === oldNow.getHours()) {
// same hour
if (fieldvalue >= SUNSHINETHRESHOLD) {
newValueHour = objhour.val + reportInterval.val; // add
}
} else {
// different hour
if (fieldvalue >= SUNSHINETHRESHOLD) {
newValueHour = reportInterval.val; // replace
} else {
newValueHour = 0;
}
that.myCreateState(
stateNamePreviousHour,
stateParametersPreviousHour,
objhour.val,
); // save value from current hour to last hour
}
}
that.myCreateState(
stateNameCurrentHour,
stateParametersCurrentHour,
newValueHour,
); // always write value for current hour
// day
const objday = await that.getValObj(stateNameToday); // get old value
if (objday !== null && reportInterval !== null) {
if (now.getDay() === oldNow.getDay()) {
// same day
if (fieldvalue >= SUNSHINETHRESHOLD) {
newValueDay = objday.val + reportInterval.val / 60; // add
} else {
newValueDay = objday.val;
}
} else {
// different hour
if (fieldvalue >= SUNSHINETHRESHOLD) {
newValueDay = reportInterval.val / 60; // replace
} else {
newValueDay = 0;
}
that.myCreateState(
stateNameYesterday,
stateParametersYesterday,
objday.val,
); // save value from current day to last yesterday
}
}
that.myCreateState(
stateNameToday,
stateParametersToday,
newValueDay,
); // always write value for current day
}
// Set a state sunshine to true, if above threshold
//------------------------------------------------
if (messageInfo[item][field][0] === "solarRadiation") {
const stateNameSunshine = [statePath, "sunshine"].join(".");
const stateParametersSunshine = {
type: "state",
common: {
type: "boolean",
read: true,
write: false,
role: "indicator.sunshine",
name: "Sunshine (> 120 W/m2); adapter calculated",
},
native: {},
};
if (fieldvalue >= SUNSHINETHRESHOLD) {
that.myCreateState(
stateNameSunshine,
stateParametersSunshine,
true,
);
} else {
that.myCreateState(
stateNameSunshine,
stateParametersSunshine,
false,
);
}
}
// Reduced pressure (sea level) from station pressure
//--------------------------------------------------
if (messageInfo[item][field][0] === "stationPressure") {
let airTemperature = 15; // standard value if not available
let relativeHumidity = 50; // standard value if not available
const stateNameAirTemperature = [
statePath,
"airTemperature",
].join(".");
const stateNameRelativeHumidity = [
statePath,
"relativeHumidity",
].join(".");
const stateNameReducedPressure = [
statePath,
"reducedPressure",
].join(".");
const stateParametersReducedPressure = {
type: "state",
common: {
type: "number",
unit: "hPa",
read: true,
write: false,
role: "value.pressure",
name: "Reduced pressure (sea level); adapter calculated",
},
native: {},
};
const obj = await that.getValObj(stateNameAirTemperature);
const obj1 = await that.getValObj(stateNameRelativeHumidity);
if (obj !== null && obj1 !== null) {
airTemperature = obj.val;
relativeHumidity = obj1.val;
const reducedPressure = getQFF(
airTemperature,
fieldvalue,
that.config.height,
relativeHumidity,
);
// Calculate min/max for reduced pressure
that.calcMinMaxValue(
stateNameReducedPressure,
stateParametersReducedPressure,
reducedPressure,
MIN,
);
that.calcMinMaxValue(
stateNameReducedPressure,
stateParametersReducedPressure,
reducedPressure,
MAX,
);
that.myCreateState(
stateNameReducedPressure,
stateParametersReducedPressure,
reducedPressure,
);
if (that.config.debug) {
that.log.info(
[
"Pressure conversion: ",
"Station pressure: ",
fieldvalue,
", Height: ",
that.config.height,
", Temperature: ",
airTemperature,
", Humidity: ",
relativeHumidity,
", Reduced pressure: ",
reducedPressure,
].join(""),
);
}
}
}
// Dewpoint from temperature and humidity
//----------------------------------------------
// Is calculated and written when humidity is received (temperature comes before that, so it should be current)
if (messageInfo[item][field][0] === "relativeHumidity") {
const stateNameAirTemperature = [
statePath,
"airTemperature",
].join(".");
const stateNameDewpoint = [statePath, "dewpoint"].join(".");
const stateParametersDewpoint = {
type: "state",
common: {
type: "number",
unit: "°C",
read: true,
write: false,
role: "value.temperature.dewpoint",
name: "Dewpoint; adapter calculated",
},
native: {},
};
const obj = await that.getValObj(stateNameAirTemperature);
if (obj !== null) {
const airTemperature = obj.val;
// Calculate min/max for dewpoint
that.calcMinMaxValue(
stateNameDewpoint,
stateParametersDewpoint,
dewpoint(airTemperature, fieldvalue).dewpointTemp,
MIN,
);
that.calcMinMaxValue(
stateNameDewpoint,
stateParametersDewpoint,
dewpoint(airTemperature, fieldvalue).dewpointTemp,
MAX,
);
that.myCreateState(
stateNameDewpoint,
stateParametersDewpoint,
dewpoint(airTemperature, fieldvalue).dewpointTemp,
);
}
}
// absolute Humidity from temperature, humidity and station pressure
//----------------------------------------------
// Is calculated and written when humidity is received (temperature comes before that, so it should be current)
if (messageInfo[item][field][0] === "relativeHumidity") {
const stateNameAirTemperature = [
statePath,
"airTemperature",
].join(".");
const stateNameStationPressure = [
statePath,
"stationPressure",
].join(".");
const stateNameAbsoluteHumidity = [
statePath,
"absoluteHumidity",
].join(".");
const stateParametersAbsoluteHumidity = {
type: "state",
common: {
type: "number",
unit: "g/m³",
read: true,
write: false,
role: "value.humidity",
name: "absolute Humidity; adapter calculated",
},
native: {},
};
const airTemp = await that.getValObj(stateNameAirTemperature);
const stationPressure = await that.getValObj(
stateNameStationPressure,
);
if (airTemp !== null && stationPressure !== null) {
// Calculate min/max for dewpoint
that.calcMinMaxValue(
stateNameAbsoluteHumidity,
stateParametersAbsoluteHumidity,
dewpoint(airTemp.val, fieldvalue, stationPressure.val)
.absoluteHumidity,
MIN,
);
that.calcMinMaxValue(
stateNameAbsoluteHumidity,
stateParametersAbsoluteHumidity,
dewpoint(airTemp.val, fieldvalue, stationPressure.val)
.absoluteHumidity,
MAX,
);
that.myCreateState(
stateNameAbsoluteHumidity,
stateParametersAbsoluteHumidity,
dewpoint(airTemp.val, fieldvalue, stationPressure.val)
.absoluteHumidity,
);
}
}
// Feels like from temperature and humidity and wind
//-------------------------------------------------
// Is calculated and written when humidity is received (wind and temperature comes before that, so they should be current)
if (messageInfo[item][field][0] === "relativeHumidity") {
const stateNameAirTemperature = [
statePath,
"airTemperature",
].join(".");
const stateNameFeelsLike = [statePath, "feelsLike"].join(".");
const stateNameWindAvg = [statePath, "windAvg"].join(".");
const stateParametersFeelsLike = {
type: "state",
common: {
type: "number",
unit: "°C",
read: true,
write: false,
role: "value.temperature.feelslike",
name: "Feels like temperature (Heat index/wind chill), °C; adapter calculated",
},
native: {},
};
const obj1 = await that.getValObj(stateNameAirTemperature);
const obj2 = await that.getValObj(stateNameWindAvg);
if (obj1 !== null && obj2 !== null) {
const airTemperature = obj1.val;
const windAvg = obj2.val;
// Calculate min/max for feelsLike
that.calcMinMaxValue(
stateNameFeelsLike,
stateParametersFeelsLike,
feelsLike(airTemperature, windAvg, fieldvalue),
MIN,
);
that.calcMinMaxValue(
stateNameFeelsLike,
stateParametersFeelsLike,
feelsLike(airTemperature, windAvg, fieldvalue),
MAX,
);
that.myCreateState(
stateNameFeelsLike,
stateParametersFeelsLike,
feelsLike(airTemperature, windAvg, fieldvalue),
);
}
}
// Convert wind directions from degrees to cardinal directions
// -----------------------------------------------------------
if (messageInfo[item][field][0] === "windDirection") {
const stateNameWindDirectionText = [
statePath,
"windDirectionCardinal",
].join(".");
const stateParametersWindDirectionText = {
type: "state",
common: {
type: "string",
unit: "",
read: true,
write: false,
role: "text.direction.wind",
name: "Cardinal wind direction; adapter calculated",
},
native: {},
};
that.myCreateState(
stateNameWindDirectionText,
stateParametersWindDirectionText,
windDirections[Math.round(fieldvalue / 22.5)],
);
}
// Convert wind speed from m/s to Beaufort
// ---------------------------------------
if (
["windSpeed", "windGust", "windLull", "windAvg"].includes(
messageInfo[item][field][0],
)
) {
let stateNameBeaufort;
switch (messageInfo[item][field][0]) {
case "windGust":
stateNameBeaufort = [statePath, "beaufortGust"].join(".");
break;
case "windLull":
stateNameBeaufort = [statePath, "beaufortLull"].join(".");
break;
case "windAvg":
stateNameBeaufort = [statePath, "beaufortAvg"].join(".");
break;
default:
stateNameBeaufort = [statePath, "beaufort"].join(".");
}
const stateParametersBeaufort = {
type: "state",
common: {
type: "number",
unit: "",
read: true,
write: false,
role: "value.speed.wind",
name: "Wind speed in Beaufort; adapter calculated",
},
native: {},
};
// Calculate max for beaufort windspeeds
that.calcMinMaxValue(
stateNameBeaufort,
stateParametersBeaufort,
beaufort(fieldvalue),
MAX,
);
// Write new value to state (or create first, if needed)
that.myCreateState(
stateNameBeaufort,
stateParametersBeaufort,
beaufort(fieldvalue),
);
}
// Sensor status as text from binary
//---------------------------------
if (messageInfo[item][field][0] === "sensor_status") {
let sensorStatusText = "";
const stateNameSensorStatusText = [
statePath,
"sensor_statusText",
].join(".");
const stateParametersSensorStatusText = {
type: "state",
common: {
type: "string",
unit: "",
read: true,
write: false,
role: "text.status",
name: "Sensor status; adapted calculated",
},
native: {},
};
Object.keys(sensorfails).forEach((item) => {
if ((fieldvalue & parseInt(item)) === parseInt(item)) {
if (sensorStatusText !== "") {
sensorStatusText += ", ";
}
sensorStatusText += sensorfails[item];
}
if (sensorStatusText === "") {
sensorStatusText = "Sensors OK";
}
});
that.myCreateState(
stateNameSensorStatusText,
stateParametersSensorStatusText,
sensorStatusText,
);
}
// Powermodes from sensor_status
//---------------------------------
if (messageInfo[item][field][0] === "sensor_status") {
const stateNamePowerMode = [statePath, "powerMode"].join(".");
const stateParametersPowerMode = {
type: "state",
common: {
type: "mixed",
states: {
0: "Mode 0: Full power all sensors enabled",
1: "Mode 1: Rapid sample interval set to six seconds",
2: "Mode 2: Rapid sample interval set to one minute",
3: "Mode 3: Rapid sample interval set to five minutes; Sensor sample interval set to five minutes; Lightning sensor disabled; Haptic sensor disabled",
},
read: true,
write: false,
role: "text.status",
name: "Power mode; adapter calculated",
},
native: {},
};
let Mode = 0;
Object.keys(powermodes).forEach((powermode) => {
if (
(fieldvalue & parseInt(powermode)) ===
parseInt(powermode)
) {
Mode = powermodes[powermode];
}
});
that.myCreateState(
stateNamePowerMode,
stateParametersPowerMode,
Mode,
);
}
//= =============================
// End of special tasks section
});
}
});
}
});
}
/**
* Is called when adapter shuts down - callback has to be called under any circumstances!
*
* @param {() => void} callback
*/
onUnload(callback) {
try {
clearTimeout(timer); // stop timeout from loggin at stop
mServer.close(); // close UDP port
this.log.info("cleaned everything up...");
callback();
} catch (e) {
callback();
}
}
/**
* Write value to state or create if not already existing
*
* @param {string} stateName The full path and name of the state to be created
* @param {object} stateParameters Set of parameters for creation of state
* @param {number | string | null | boolean} stateValue Value of the state (optional)
* @param {number} expiry Time in seconds until the value is set back to false (optional)
*/
async myCreateState(
stateName,
stateParameters,
stateValue = null,
expiry = 0,
) {
const that = this;
if (!existingState