iobroker.meteoalarm
Version:
1,327 lines (1,086 loc) • 75.8 kB
JavaScript
/**
*
* ioBroker Meteoalarm Adapter
*
* (c) 2019 Alexander K <blacksonj7@gmail.com>
*
* MIT License
*
*/
'use strict';
const utils = require('@iobroker/adapter-core');
//const request = require('request');
const moment = require('moment');
const util = require('util')
const turf = require('@turf/turf')
var parseString = require('xml2js').parseString;
var parseStringPromise = require('xml2js').parseStringPromise;
const stateAttr = require('./lib/stateAttr.js'); // State attribute definitions
const i18nHelper = require(`${__dirname}/lib/i18nHelper`);
const bent = require("bent");
const parseCSV = require('csv-parse');
const geoCodeJSON = require('./admin/geocodes.json')
const fs = require("fs");
const path = require('path');
const { hasUncaughtExceptionCaptureCallback, features } = require('process');
const { count } = require('console');
const { level } = require('./lib/stateAttr.js');
const { addAbortSignal } = require('stream');
var DescFilter1 = '';
var DescFilter2 = '';
var country = '';
//var countryConfig = '';
var geocodeLocationConfig = []
var geocodeCountry = ""
//var regionConfig = '';
var latConfig = '';
var longConfig = '';
var countEntries = 0;
var typeArray = [];
var urlArray = [];
var regionCSV = ""
//var regionName = ""
var xmlLanguage = ""
const warnMessages = {};
var tempFirst = true
var channelNames = []
var csvContent = [];
var alarmAll = []
var alarmOldIdentifier = []
var alarmOldArray = []
var urlAtom = ""
var locationArray = new Array()
let adapter;
let lang;
var noOfAlarmsAtEnd= 0
var noOfAlarmsAtStart = 0
var htmlCode = ""
var today = new Date();
var maxAlarmLevel = 1
var notificationAlarmArray = []
var imageSizeSetup = 0
var updateError = false
var initialDataLoaded = false
//var geoCodeLoaded = false
let Sentry;
let SentryIntegrations;
function initSentry(callback) {
if (!adapter.ioPack.common || !adapter.ioPack.common.plugins || !adapter.ioPack.common.plugins.sentry) {
return callback && callback();
}
const sentryConfig = adapter.ioPack.common.plugins.sentry;
if (!sentryConfig.dsn) {
adapter.log.warn('Invalid Sentry definition, no dsn provided. Disable error reporting');
return callback && callback();
}
// Require needed tooling
Sentry = require('@sentry/node');
SentryIntegrations = require('@sentry/integrations');
// By installing source map support, we get the original source
// locations in error messages
require('source-map-support').install();
let sentryPathWhitelist = [];
if (sentryConfig.pathWhitelist && Array.isArray(sentryConfig.pathWhitelist)) {
sentryPathWhitelist = sentryConfig.pathWhitelist;
}
if (adapter.pack.name && !sentryPathWhitelist.includes(adapter.pack.name)) {
sentryPathWhitelist.push(adapter.pack.name);
}
let sentryErrorBlacklist = [];
if (sentryConfig.errorBlacklist && Array.isArray(sentryConfig.errorBlacklist)) {
sentryErrorBlacklist = sentryConfig.errorBlacklist;
}
if (!sentryErrorBlacklist.includes('SyntaxError')) {
sentryErrorBlacklist.push('SyntaxError');
}
Sentry.init({
release: adapter.pack.name + '@' + adapter.pack.version,
dsn: sentryConfig.dsn,
integrations: [
new SentryIntegrations.Dedupe()
]
});
Sentry.configureScope(scope => {
scope.setTag('version', adapter.common.installedVersion || adapter.common.version);
if (adapter.common.installedFrom) {
scope.setTag('installedFrom', adapter.common.installedFrom);
}
else {
scope.setTag('installedFrom', adapter.common.installedVersion || adapter.common.version);
}
scope.addEventProcessor(function(event, hint) {
// Try to filter out some events
if (event.exception && event.exception.values && event.exception.values[0]) {
const eventData = event.exception.values[0];
// if error type is one from blacklist we ignore this error
if (eventData.type && sentryErrorBlacklist.includes(eventData.type)) {
return null;
}
if (eventData.stacktrace && eventData.stacktrace.frames && Array.isArray(eventData.stacktrace.frames) && eventData.stacktrace.frames.length) {
// if last exception frame is from an nodejs internal method we ignore this error
if (eventData.stacktrace.frames[eventData.stacktrace.frames.length - 1].filename && (eventData.stacktrace.frames[eventData.stacktrace.frames.length - 1].filename.startsWith('internal/') || eventData.stacktrace.frames[eventData.stacktrace.frames.length - 1].filename.startsWith('Module.'))) {
return null;
}
// Check if any entry is whitelisted from pathWhitelist
const whitelisted = eventData.stacktrace.frames.find(frame => {
if (frame.function && frame.function.startsWith('Module.')) {
return false;
}
if (frame.filename && frame.filename.startsWith('internal/')) {
return false;
}
if (frame.filename && !sentryPathWhitelist.find(path => path && path.length && frame.filename.includes(path))) {
return false;
}
return true;
});
if (!whitelisted) {
return null;
}
}
}
return event;
});
adapter.getForeignObject('system.config', (err, obj) => {
if (obj && obj.common && obj.common.diag !== 'none') {
adapter.getForeignObject('system.meta.uuid', (err, obj) => {
// create uuid
if (!err && obj) {
Sentry.configureScope(scope => {
scope.setUser({
id: obj.native.uuid
});
});
}
callback && callback();
});
}
else {
callback && callback();
}
});
});
}
//var Interval
function startAdapter(options) {
options = options || {};
Object.assign(options, {
name: 'meteoalarm',
useFormatDate: true,
ready: function() {
//main()
}
});
adapter = new utils.Adapter(options);
adapter.on(`unload`, callback => {
//clearInterval(Interval);
callback && callback();
});
adapter.on('ready', function() {
if (adapter.supportsFeature && adapter.supportsFeature('PLUGINS')) {
const sentryInstance = adapter.getPluginInstance('sentry');
if (sentryInstance) {
Sentry = sentryInstance.getSentryObject();
}
main();
}
else {
initSentry(main);
}
});
return adapter;
}
function main() {
getData()
}
function initialSetup(){
// run once when adapter starts
adapter.getForeignObject('system.config', (err, systemConfig) => {
if (!systemConfig.common.language){
lang = 'en'
}
else{
lang = systemConfig.common.language
}
})
latConfig = adapter.config.lat
longConfig = adapter.config.long
locationArray = adapter.config.geocode
geocodeLocationConfig = adapter.config.geocodeLocation
geocodeCountry = adapter.config.geocodeCountry
if (geocodeCountry == ''){
// could be the case for the people who tested version 3.0.0
geocodeCountry = locationArray[0]+locationArray[1]
}
// check if there is a space after the comma, and if not, add it
/*
if (geocodeLocationConfig.indexOf(',') > -1)
{
let indexFind = geocodeLocationConfig.indexOf(',')
if (geocodeLocationConfig.substring(indexFind+1, indexFind+2) != ' '){
geocodeLocationConfig = geocodeLocationConfig.substring(0,indexFind) + ', ' +
}
adapter.log.debug('TTT: ' + geocodeLocationConfig.substring(indexFind+1, indexFind+2))
//if ()
}
*/
imageSizeSetup = Number(adapter.config.imageSize)
adapter.log.debug('0.0 Initial setup loaded')
}
async function getData(){
alarmAll = []
if (!initialDataLoaded){
initialSetup()
initialDataLoaded = true
}
if (geocodeCountry == ""|| !geocodeCountry || latConfig == "" || !latConfig ||longConfig == ""|| !longConfig || !locationArray){
adapter.log.error('0.1 Please maintain country, geocode and location in setup!')
let htmlCode = '<table style="border-collapse: collapse; width: 100%;" border="1"><tbody><tr>'
htmlCode += '<td style="width: 100%; background-color: #fc3d03;">Please maintain country and location in setup!</td></tr></tbody></table>'
await Promise.all([
adapter.setStateAsync({device: '' , channel: '',state: 'level'}, {val: 0, ack: true}),
adapter.setStateAsync({device: '' , channel: '',state: 'htmlToday'}, {val: htmlCode, ack: true}),
adapter.setStateAsync({device: '' , channel: '',state: 'noOfAlarms'}, {val: 0, ack: true}),
adapter.setStateAsync({device: '' , channel: '',state: 'JSON'}, {val: '', ack: true}),
adapter.setStateAsync({device: '' , channel: '',state: 'location'}, {val: 'Check Setup!', ack: true})
])
adapter.terminate ? adapter.terminate(0) : process.exit(0);
}
else{
adapter.log.debug('0.1 Setup found: country ' + geocodeCountry + ' with geocode(s) ' + locationArray + ' for location ' + geocodeLocationConfig+ ' and Lat ' + latConfig + ' Long ' + longConfig )
if (Sentry){
adapter.log.debug('Sentry aktiv - Breadcrumb gesetzt')
Sentry.addBreadcrumb({
category: "info",
message: 'Country ' + geocodeCountry + ', Location '+ latConfig + ' - ' + longConfig,
level: "info",
});
}
/*
if (!geoCodeLoaded){
findGeoCode()
geoCodeLoaded = true
adapter.log.debug('0.2: Geocode loaded. Result: ' + locationArray)
}
*/
urlAtom = getCountryLink(geocodeCountry)
xmlLanguage = getXMLLanguage(geocodeCountry)
if (xmlLanguage == ""){
xmlLanguage = 'en-GB'
}
adapter.log.debug('0.3 XML Language: ' + xmlLanguage)
const checkState = await adapter.getStateAsync('weatherMapCountry')
if (checkState != null ){
adapter.log.debug('0.3: Cleaning up old objects');
const cleaned = await cleanupOld()
}
const csv = await getCSVData()
const temp = await adapter.getStateAsync('noOfAlarms')
if (temp){
noOfAlarmsAtStart = temp.val
}
adapter.log.debug('0.4: Existing alarm objects at adapter start: ' + noOfAlarmsAtStart)
const temp2 = await saveAlarmNamesForLater()
for (const alarmLoop of alarmOldIdentifier) {
const temp1 = await saveAlarmsForLater(alarmLoop)
};
adapter.log.debug('1: Parsed CSV File')
adapter.log.debug('2: Request Atom from ' + urlAtom )
const getJSON = bent('string')
let xmlAtom
try {
xmlAtom = await getJSON(urlAtom)
} catch (err){
adapter.log.warn('2.1: Atom URL ' + urlAtom + ' not available - error ' + err)
adapter.terminate ? adapter.terminate(0) : process.exit(0);
}
if (xmlAtom){
adapter.log.debug('3: Received Atom')
parseString(xmlAtom, {
//mergeAttrs: true
explicitArray: false
},
function (err, result) {
if (err) {
adapter.log.error("Fehler: " + err);
adapter.terminate ? adapter.terminate(0) : process.exit(0);
} else {
adapter.log.debug('4: Process Atom')
var newdate = moment(new Date()).local().format('DD.MM.YYYY HH:mm')
adapter.setState({device: '' , channel: '',state: 'lastUpdate'}, {val: newdate, ack: true});
//adapter.log.debug('4.1 Content: ' + util.inspect(result.feed.entry, {showHidden: false, depth: null, colors: true}))
if (result.feed.entry){
if (result.feed.entry[0]){
adapter.log.debug('4.1.1: Check Entries')
checkRelevante(result.feed.entry)
}
else {
// try to fix the damaged xml
adapter.log.debug('4.2.1 tried to fix xml')
let newObject = [result.feed.entry]
if (newObject[0]){
adapter.log.debug('4.2.2 new object after fixing: ' + util.inspect(newObject, {showHidden: false, depth: null, colors: true}))
checkRelevante(newObject)
}
}
}
}
});
}
// continue now to request details
var countEntries = 0
//adapter.log.debug('Object Result: ' + util.inspect(urlArray, {showHidden: false, depth: null}))
urlArray.sort(function(a, b) {
var keyA = new Date(a.effective),
keyB = new Date(b.effective);
// Compare the 2 dates
if (keyA < keyB) return -1;
if (keyA > keyB) return 1;
return 0;
});
//adapter.log.debug('Object Sorted Result: ' + util.inspect(urlArray, {showHidden: false, depth: null}))
adapter.log.debug('5: Processed Atom')
var countTotalURLs = urlArray.length
adapter.log.debug('5.1 Found ' + countTotalURLs + ' URLs')
var countURL = 0
var detailsType = ""
var detailsIdentifier = ""
var detailsReference = ""
var detailssent = ""
for (var i = 0, l = urlArray.length; i < l; i++){
countURL += 1
var jsonResult;
var awarenesstype = ""
adapter.log.debug('6: Request Details from URL ' + countURL + ': ' + urlArray[i].url)
const getJSON1 = bent('string')
let xmlDetails
try {
xmlDetails = await getJSON1(urlArray[i].url)
} catch (err){
adapter.log.debug('6.1: Details URL ' + urlArray[i].url + ' not valid any more - error ' + err)
updateError = true
}
var typeRelevant = false
if (xmlDetails ){
// Just go here if Request for Details is successful
adapter.log.debug('7: Received Details for URL ' + countURL)
parseString(xmlDetails, {
explicitArray: false
},
function (err, result) {
if (err) {
adapter.log.error("Fehler: " + err);
adapter.terminate ? adapter.terminate(0) : process.exit(0);
} else {
var info = []
if (result.alert.info[0]){
info = result.alert.info
}
else {
info = [result.alert.info]
}
detailsType= result.alert.msgType
detailsIdentifier = result.alert.identifier
detailsIdentifier = detailsIdentifier.replace(/\./g,'') // remove dots
detailssent = result.alert.sent
if (detailsType != "Alert" && result.alert.references != ""){
detailsReference = result.alert.references
var searchTerm = ","
const indexOfFirstComma = detailsReference.indexOf(searchTerm);
const indexOfSecondComma = detailsReference.indexOf(searchTerm, indexOfFirstComma +1);
detailsReference = detailsReference.substring(indexOfFirstComma +1,indexOfSecondComma)
detailsReference = detailsReference.replace(/\./g,'') // remove dots
}
for (var j = 0, l = info.length; j < l; j++){
var element = info[j]
if (element.language == xmlLanguage){
element.parameter.forEach(function (parameter){
if (parameter.valueName == "awareness_type") {
awarenesstype =parameter.value
var n = awarenesstype.indexOf(";");
awarenesstype = awarenesstype.substring(0, n)
typeRelevant = checkTypeRelevant(awarenesstype,"general")
//adapter.log.debug('Alarm ' + countURL + ' with type ' + awarenesstype + ' relevant: ' + typeRelevant)
}
})
jsonResult = element
}
}
}
});
}
if (jsonResult && typeRelevant){
countEntries += 1
const promises = await processDetails(jsonResult,countEntries,detailsType,detailsIdentifier,detailsReference,detailssent)
adapter.log.debug('8: Processed Details for Alarm ' + countURL)
}
}
//const widget = await createHTMLWidget()
adapter.log.debug('9: Checking for duplicate alarms')
adapter.log.debug('9.0.1 alarmAll Array before removing duplicates: ' + JSON.stringify(alarmAll))
checkDuplicates()
//const created = await createAlarms(countEntries)
// adapter.log.debug('8: Alarm States created for Alarm ' + countURL + ' type: ' + awarenesstype)
notificationAlarmArray = []
adapter.log.debug('10: Create alarm states')
for (var j = 0, l = alarmAll.length; j < l; j++){
//adapter.log.debug('10.TEMP: level= ' + alarmAll[j].Level)
if (checkRelevanceAlarmLevel(String(alarmAll[j].Level),"general","")){
const promises = await fillAlarm(alarmAll, j)
}
}
adapter.log.debug('10.2: Created alarm states')
adapter.log.debug('11: Cleaning up obsolete alarms')
if (!updateError){
const clean = await cleanObsoleteAlarms(alarmAll)
adapter.log.debug('11.1: Cleaned up obsolete alarms')
}
adapter.log.debug('12: Creating HTML Widget')
htmlCode = ''
var JSONAll = []
var warningCount = 0
if (channelNames.length >= 1){
htmlCode += '<table style="border-collapse: collapse; width: 100%;"><tbody>'
for (const channelLoop of channelNames) {
warningCount += 1
var path = 'alarms.' + channelLoop
var colorHTML = ''
let event = await adapter.getStateAsync(path + '.event')
let headline = await adapter.getStateAsync(path + '.headline')
let description = await adapter.getStateAsync(path + '.description');
let icon = await adapter.getStateAsync(path + '.icon');
let color = await adapter.getStateAsync(path + '.color');
let effectiveDate = await adapter.getStateAsync(path + '.effective');
let expiresDate = await adapter.getStateAsync(path + '.expires');
let level = await adapter.getStateAsync(path + '.level');
let alarmType = await adapter.getStateAsync(path + '.typeText');
if (color && color.val){
if (!adapter.config.noBackgroundColor){
colorHTML = 'background-color: ' + color.val
}
}
if (level && level.val){
if (level.val > maxAlarmLevel){
maxAlarmLevel = Number(level.val)
}
}
if (!adapter.config.noIcons){
// Dummy cell to move picture away from the left side
htmlCode += '<tr><td style="width: 1%; border-style: none; ' + colorHTML + '"></td>'
htmlCode += '<td style="width: 9%; border-style: none; ' + colorHTML + '">'
htmlCode += '<img style="display:block;"'
var imageSize = ''
if (icon && icon.val){
switch (imageSizeSetup) {
case 0:
imageSize = ' width="20" height="20"';
break;
case 1:
imageSize = ' width="35" height="35"';
break;
case 2:
imageSize = ' width="50" height="50"';
break;
default:
imageSize = ' width="35" height="35"';
break;
}
//adapter.log.debug('Image Size: ' + imageSizeSetup + ' -> Result: ' + imageSize)
htmlCode += imageSize
htmlCode += ' alt="Warningimmage" src="' + icon.val + '"/>'
}
htmlCode += '</td>'
}
htmlCode += '<td style="width: 90%; border-style: none; ' + colorHTML + '">'
if (headline && headline.val){
adapter.log.debug('12.1: Added Alarm to widget for ' + headline.val)
htmlCode += '<h4 style = "margin-top: 5px;margin-bottom: 1px;">' + headline.val + ': '
}
if (effectiveDate && effectiveDate.val && expiresDate && expiresDate.val){
htmlCode += getAlarmTime(effectiveDate.val, expiresDate.val) + '</h4>'
}
if (description && description.val){
htmlCode += description.val
}
htmlCode += '</td></tr>'
if (effectiveDate && effectiveDate.val && expiresDate && expiresDate.val){
JSONAll.push(
{
Event: event.val,
Description: description.val,
Level: level.val,
Effective: getAlarmTime(effectiveDate.val, expiresDate.val),
Icon: icon.val,
AlarmType: alarmType.val
}
);
}
}
}
else{
// No Alarm Found
htmlCode += '<table style="border-collapse: collapse; width: 100%;"><tbody>'
htmlCode += '<tr><td style= "border-style: none; '
if (!adapter.config.noBackgroundColor){
htmlCode += 'background-color: ' + getColor('1')
}
htmlCode += '">' + getLevelName('1')
htmlCode += '</td></tr>'
maxAlarmLevel = 1
}
if (htmlCode){
htmlCode += '</tbody></table>'
}
// Check if no alarms are found, then add "no alarm found"
if (JSONAll.length == 0){
JSONAll.push(
{
Event: "",
Description: getLevelName('1'),
Level: "1",
Effective: "",
Icon: ""
}
);
}
noOfAlarmsAtEnd = warningCount
await Promise.all([
adapter.setStateAsync({device: '' , channel: '',state: 'level'}, {val: maxAlarmLevel, ack: true}),
adapter.setStateAsync({device: '' , channel: '',state: 'htmlToday'}, {val: htmlCode, ack: true}),
adapter.setStateAsync({device: '' , channel: '',state: 'location'}, {val: geocodeLocationConfig, ack: true}),
adapter.setStateAsync({device: '' , channel: '',state: 'link'}, {val: urlAtom, ack: true}),
adapter.setStateAsync({device: '' , channel: '',state: 'color'}, {val: getColor(maxAlarmLevel.toString()), ack: true}),
adapter.setStateAsync({device: '' , channel: '',state: 'noOfAlarms'}, {val: warningCount, ack: true}),
adapter.setStateAsync({device: '' , channel: '',state: 'JSON'}, {val: JSON.stringify(JSONAll), ack: true})
])
adapter.log.debug('13: Set State for Widget')
adapter.log.debug('14: Processing notifications')
// Important, also go in there if no notifications are valid, because it could be that we need to trigger the "All warnings done" message
const promises = await processNotifications(alarmAll)
adapter.log.debug('15: All Done')
if (geocodeLocationConfig){
adapter.log.info('15.1: Updated Weather Alarms for ' + geocodeLocationConfig + ' -> ' + warningCount + ' alarm(s) found')
}
adapter.terminate ? adapter.terminate('All data processed. Adapter stopped until next scheduled process.') : process.exit(0);
}
}
function checkDuplicates(){
var alarmAllChecked = []
// 1. check for duplicate entries of type Alarm with the same Type, Level, Onset and Expires Date -> saved in Alarm_Key
for(var i = 0; i < alarmAll.length; i += 1) {
if (alarmAll[i].Alarm_Type == "Alert")
{
let check = alarmAllChecked.some(function(item) {
return item.Alarm_Key === alarmAll[i].Alarm_Key})
if (!check){
alarmAllChecked.push(alarmAll[i])
}
}
else{
alarmAllChecked.push(alarmAll[i])
}
}
//2. Check for Alarmupdates, duplicate updates and cancles
alarmAll = alarmAllChecked
adapter.log.debug('9.1 Finished checking alerts - ' + alarmAll.length + ' relevant alarm(s)')
//adapter.log.debug('9.3 alarmAll Array after removing duplicates: ' + JSON.stringify(alarmAll))
//adapter.log.debug('9.3.1 alarmAll sorted by sent1 date:' + JSON.stringify(alarmAll.sort((a, b) => a.Alarm_Sent - b.Alarm_Sent)))
}
function createPolyDataString(PolyDataToConvert){
var result = []
var first = true
var i = 0
var lat = ''
var long = ''
do {
var loc = PolyDataToConvert.indexOf(' ')
var countComma = (PolyDataToConvert.match(/,/g) || []).length;
var lengthpolyDataToConvert = PolyDataToConvert.length
var tempString = PolyDataToConvert.substring(0, loc)
var locComma = tempString.indexOf(',')
if (countComma >1){
// still multiple objects
PolyDataToConvert = PolyDataToConvert.substring(loc+1,lengthpolyDataToConvert)
long = tempString.substring(locComma+1,tempString.length)
lat = tempString.substring(0,locComma)
i ++
result.push([long, lat ])
}
else{
//last object
var locComma = PolyDataToConvert.indexOf(',')
var locComma = tempString.indexOf(',')
var longLast = PolyDataToConvert.substring(locComma+1,PolyDataToConvert.length)
var latLast = PolyDataToConvert.substring(0,locComma)
if (longLast != long && latLast != lat){
result.push([long, lat ])
}
PolyDataToConvert = ''
}
lengthpolyDataToConvert = PolyDataToConvert.length
} while (lengthpolyDataToConvert > 1);
return result
}
function checkIfInPoly(polyData){
var polyArray = createPolyDataString(polyData)
var myLoc = {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates":
[longConfig, latConfig]
}
};
let i = 0;
let pathArray = [];
while (i < polyArray.length) {
var lat = polyArray[i][0]
var long = polyArray[i][1]
pathArray.push([Number(lat), Number(long)])
i++;
}
var poly = {
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates":
[pathArray]
}
}
var isInside = turf.booleanPointInPolygon(myLoc, poly);
return isInside
}
function checkRelevante(entry){
var i = 0
var now = new Date();
entry.forEach(function (element){
var expiresDate = new Date(element['cap:expires']);
var effectiveDate = new Date(element['cap:onset']);
var messagetype = ""
var messagetypeRelevant = false
if (element['cap:message_type']){
messagetype = element['cap:message_type']
}
// Ignore Cancles
if (messagetype == "Alert"){
// show Alert only if no Update and no cancle found
messagetypeRelevant = true
}
if (messagetype == "Update"){
// Show all updates
messagetypeRelevant = true
}
var locationRelevant = false
if (element['cap:geocode'] && element['cap:geocode'].valueName ){
locationRelevant = checkLocation(element['cap:geocode'].valueName , element['cap:geocode'].value)
}
if (element['cap:polygon']) {
// found polygon
let polygon = element['cap:polygon']
var areaDesc = ''
if (element['cap:areaDesc']) {
areaDesc = element['cap:areaDesc']
}
locationRelevant = checkIfInPoly(polygon)
if (locationRelevant){
adapter.log.debug('4.1.2: Found relevant polygon warning for location ' + areaDesc)
}
}
var statusRelevant = false
if (element['cap:status'] == 'Actual'){
statusRelevant = true
}
var given = moment(effectiveDate);
var current = moment().startOf('day');
var daysDifference = moment.duration(given.diff(current)).asDays()
var dateRelevant = false
if ((expiresDate >= now)&&(daysDifference < 2)){
dateRelevant = true
}
var eventType = element['cap:event']
if (locationRelevant){
//adapter.log.debug('4.1.2: Check Result: dateRelevant = ' + dateRelevant + "statusrelevant= " + statusRelevant + " messagetyperelevant = " + messagetypeRelevant )
}
if (locationRelevant && (dateRelevant) && statusRelevant && messagetypeRelevant){
for(var i = 0; i < element.link.length; i += 1) {
//adapter.log.debug('4.1.1: Link ' + i + ': ' + element.link[i].$.href)
if (element.link[i].$.type){
//adapter.log.debug('4.1.1: Typ ' + i + ': ' + element.link[i].$.type)
if (element.link[i].$.type == 'application/cap+xml'){
var detailsLink = element.link[i].$.href
}
}
}
adapter.log.debug('4.2: Warning found: ' + detailsLink + ' of message type ' + messagetype)
let obj = {
"id": i,
"event": eventType,
"url": detailsLink,
"effective": effectiveDate,
"expires": expiresDate
}
urlArray.push(obj)
i += 1;
}
});
adapter.log.debug('4.2: Checked relevance, found ' + urlArray.length + ' relevant alarms')
}
function checkLocation(type,locationValue){
//check which type it is and if it is relevant for us
if (type == "EMMA_ID"){
return locationArray.includes(locationValue)
}
else{
var successful = false
for(var i = 0; i < csvContent.length; i += 1) {
if((locationArray.includes(csvContent[i][0])) && (csvContent[i][2] == type) ) {
if (locationValue == csvContent[i][1] ){
successful = true
}
}
}
return successful
}
}
function getAlarmTime(onset,expires){
var expiresDate = new Date(expires)
var onsetDate = new Date(onset)
var dateString = ''
var expiresToday = today.toDateString() == expiresDate.toDateString()
var onsetToday = today.toDateString() == onsetDate.toDateString()
var expiresDay = moment(expires).locale(lang).format("ddd")
var onsetDay = moment(onset).locale(lang).format("ddd")
//if (expiresToday && onsetToday){
if (expiresDate.toDateString() == onsetDate.toDateString()){
if (adapter.config.dayInWords) {
dateString = dateDifferenceInWord(onsetDate,today) + ' ' + getDateFormatedShort(onset) + ' - ' + getDateFormatedShort(expires)
}
else{
dateString = onsetDay + ' ' + getDateFormatedShort(onset) + ' - ' + getDateFormatedShort(expires)
}
}
else{
//adapter.log.debug('Days difference onset: ' + dateDifferenceInWord(onsetDate,today) + ' : ' + onsetDate)
//adapter.log.debug('Days difference expires: ' + dateDifferenceInWord(expiresDate,today)+ ' : ' + expiresDate)
if (adapter.config.dayInWords) {
dateString = dateDifferenceInWord(onsetDate,today) + ' ' + getDateFormatedShort(onset) + ' - ' + dateDifferenceInWord(expiresDate,today) + ' ' + getDateFormatedShort(expires)
}
else{
dateString = onsetDay + ' ' + getDateFormatedShort(onset) + ' - ' + expiresDay + ' ' + getDateFormatedShort(expires)
}
}
return dateString
}
function getDateFormatedShort(dateTimeString)
{
return new Date(dateTimeString).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
}
function dateDifferenceInWord(inputDate,comparison){
// Take the difference between the dates and divide by milliseconds per day.
// Round to nearest whole number to deal with DST.
//var difference = Math.round((comparison-inputDate)/(1000*60*60*24))
var difference = 0
//adapter.log.debug('Days difference: ' + inputDate + ' - ' + difference)
var date1_tomorrow = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
var date1_today = new Date(today.getFullYear(), today.getMonth(), today.getDate() );
var date1_yesterday = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 1);
//adapter.log.debug('Date tomorrow ' + date1_tomorrow)
//adapter.log.debug('Date today ' + date1_today)
//adapter.log.debug('Date yesterday ' + date1_yesterday)
if (date1_tomorrow.getFullYear() == inputDate.getFullYear() && date1_tomorrow.getMonth() == inputDate.getMonth() && date1_tomorrow.getDate() == inputDate.getDate()) {
//date is tomorrow
difference = 1
}
if (date1_today.getFullYear() == inputDate.getFullYear() && date1_today.getMonth() == inputDate.getMonth() && date1_today.getDate() == inputDate.getDate()) {
//date is today
difference = 2
}
if (date1_yesterday.getFullYear() == inputDate.getFullYear() && date1_yesterday.getMonth() == inputDate.getMonth() && date1_yesterday.getDate() == inputDate.getDate()) {
//date is yesterday
difference = 3
}
switch (difference) {
case 2:
return i18nHelper.today[lang]
break;
case 3:
return i18nHelper.yesterday[lang]
break;
case 1:
return i18nHelper.tomorrow[lang]
break;
default:
return moment(inputDate).locale(lang).format("ddd") // Hier wird was falsches zurückgegeben!!
break;
}
return Math.round((comparison-inputDate)/(1000*60*60*24));
}
async function cleanupOld(){
const promises = await Promise.all([
adapter.deleteChannelAsync('today'),
adapter.deleteChannelAsync('tomorrow'),
adapter.deleteStateAsync('weatherMapCountry')
])
}
async function saveAlarmNamesForLater(){
return new Promise(function(resolve){
adapter.getChannelsOf('alarms', function (err, result) {
for (const channel of result) {
alarmOldIdentifier.push(channel.common.name
);
}
resolve('done')
})
})
}
async function saveAlarmsForLater(alarmName){
let path = 'alarms.' + alarmName
var effective = await adapter.getStateAsync(path + '.effective')
if (effective == null){
effective = ""
}
var referenz = await adapter.getStateAsync(path + '.updateIdentifier')
if (referenz == null){
referenz = ""
}
var sent = await adapter.getStateAsync(path + '.sent')
if (sent == null){
sent = ""
}
var expires = await adapter.getStateAsync(path + '.expires')
if (expires == null){
expires = ""
}
var tempLevel = await adapter.getStateAsync(path + '.level')
if (tempLevel == null){
tempLevel = ""
}
var type = await adapter.getStateAsync(path + '.type')
if (type == null){
type = ""
}
alarmOldArray.push(
{
Alarm_Identifier: alarmName,
Alarm_Reference: referenz.val,
Alarm_Sent: sent.val,
Expires: expires.val,
Effective: effective.val,
Level: Number(tempLevel.val),
Type: Number(type.val)
}
);
}
async function cleanObsoleteAlarms(allAlarms){
//const promises = await Promise.all([
return new Promise(async function(resolve){
const done = await adapter.getChannelsOfAsync('alarms', async function (err, result) {
for (const channel of result) {
// check if the alarm is included in the new alarms, either as identifier or reference for the updates
let check = allAlarms.some(function(item) {
return item.Alarm_Identifier === channel.common.name})
let check1 = allAlarms.some(function(item) {
//adapter.log.debug('TEst1 ' + item.Alarm_Reference)
//adapter.log.debug('TEst2 ' + channel.common.name)
return item.Alarm_Reference === channel.common.name})
if (!check){
//adapter.log.debug('11.0.1: ' + channel.common.name + ' Check = ' + check + ' , check1 = ' + check1)
//if (!check && !check1){
adapter.log.debug('11.0.2: Alarm ' + channel.common.name + ' will be deleted.')
const obj = await adapter.deleteChannelAsync('alarms',channel.common.name);
adapter.log.debug('11.0.3: After delete alarm ' + channel.common.name)
}
}
resolve('done')
})
})
}
/*
async function getJSONData(){
fetch('./geocodes.json')
.then((response) => response.json())
.then((json) => adapter.log.debug(json));
}
*/
async function getCSVData(){
return new Promise(function(resolve,reject){
fs.createReadStream(path.resolve(__dirname + '/meteoalarm', 'geocodes-aliases.csv'))
.pipe(parseCSV({delimiter: ','}))
.on('data', function(csvrow) {
//console.log(csvrow);
//do something with csvrow
csvContent.push(csvrow);
})
.on('end',function() {
//do something with csvData
//adapter.log.debug(csvContent);
resolve(csvContent);
})
.on('error', reject);
})
}
async function processNotifications(alarms){
return new Promise(function(resolve,){
if (notificationAlarmArray.length >= 1){
adapter.log.debug('14.1: Notifications available for alarms: ' + util.inspect(notificationAlarmArray, {showHidden: false, depth: null, colors: true}))
}
adapter.log.debug('14.1.0 Details: noofAlarmsAtStart: ' + noOfAlarmsAtStart + ', noofAlarmsatEnd: ' + noOfAlarmsAtEnd + ' , noWarningsSetupActive: ' + adapter.config.noWarningsNotification)
for(var i = 0; i < notificationAlarmArray.length; i += 1) {
alarms.map(function (alarms) {
if (alarms.Alarm_Identifier == notificationAlarmArray[i]) {
var tempDate = ""
if (alarms.Effective && alarms.Expires){
tempDate = getAlarmTime(alarms.Effective, alarms.Expires)
}
var region = ""
if (adapter.config.showLocation){
region = ' - ' + geocodeLocationConfig
}
var notificationLevel = getNotificationLevel(alarms.Level)
var notificationText = prepareNotificationText(alarms.Headline,alarms.Description,tempDate,region,notificationLevel,alarms.Alarm_Identifier)
adapter.log.debug('14.1.1 Type relevant: ' +checkTypeRelevant(String(alarms.Type),"notification") )
if ((checkRelevanceAlarmLevel(alarms.Level,"notification",alarms.Alarm_Identifier ))&& (checkTypeRelevant(String(alarms.Type),"notification"))){
adapter.setStateAsync({device: '' , channel: '',state: 'notification'}, {val: notificationText, ack: true})
sendNotification(alarms.Headline,alarms.Description,tempDate,region,notificationLevel,alarms.Alarm_Identifier,alarms.Alarm_Type)
}
}
})
}
if ((noOfAlarmsAtStart >= 1) &&(noOfAlarmsAtEnd == 0) && (adapter.config.noWarningsNotification)){
// all Alarms Ended notification should be sent
adapter.log.debug('14.1.1: Warning for "All warnings ended" sent')
var region = ""
if (adapter.config.showLocation){
region = ' - ' + geocodeLocationConfig
}
var notificationLevel = getNotificationLevel(1)
var notificationText = notificationLevel + region + ': ' + i18nHelper.warningsLifted[lang]
sendMessage('',notificationText,'')
adapter.setStateAsync({device: '' , channel: '',state: 'notification'}, {val: notificationText, ack: true})
}
resolve('done')
})
}
function prepareNotificationText(headline,description,date,region,level,identifier){
var notificationText = ""
notificationText += level + headline + region + ' (' + date + ') ' + description
return notificationText
}
function sendNotification(headline,description,date,region,levelText,identifier,type){
var notificationText = ""
var descriptionText = ""
var typeText = ""
if (type == 'Update'){
if (adapter.config.levelInWords){
typeText = ' ' + i18nHelper.update[lang] + ': '
}
else{
typeText = i18nHelper.update[lang] + ': '
}
}
else
{
if (adapter.config.levelInWords){
typeText = ': ' + typeText
}
}
if (!adapter.config.noDetails ){
descriptionText = description
}
switch (adapter.config.notificationsType){
case 'None':
// Do nothing
break;
case 'Telegram':
if (adapter.config.notificationsType){
notificationText = '<b>' + typeText + headline + region + '</b>' + '\r\n' + levelText + '\r\n' + descriptionText + '\r\n' + date
}
else{
notificationText = levelText + '<b>' + typeText + headline + region + '</b>' + '\r\n' + ' (' + date + ') ' + '\r\n' + descriptionText
}
break;
case 'Mail':
notificationText = levelText + typeText + headline + region + ' (' + date + ') ';
descriptionText = description
break;
case 'Pushover':
notificationText = levelText + typeText + headline + region + ' (' + date + ') ' + descriptionText
break;
case 'Signal':
notificationText = levelText+ typeText+ headline + region + ' (' + date + ') ' + descriptionText
break;
case 'SynoChat':
notificationText = levelText+ typeText+ headline + region + ' (' + date + ') ' + descriptionText
break;
default:
//Do nothing
break;
}
sendMessage(identifier,notificationText,descriptionText)
}
function checkRelevanceAlarmLevel(alarmlevel,type,identifier){
var typeRelevant = false
var alarmlevelInt = parseInt(alarmlevel)
if (type == "notification"){
//adapter.log.debug('14.1.2.1: Settings relevance notification: ' + adapter.config.warningLevelSetupNotification)
switch (adapter.config.warningLevelSetupNotification){
case '0':
typeRelevant = true
break;
case '1':
if (alarmlevelInt >= 3){
typeRelevant = true
}
break;
case '2':
if (alarmlevelInt == 4){
typeRelevant = true
}
break;
default:
//Do nothing
adapter.log.debug('14.1.1: Type not found -> no check for relevance possible')
break;
}
adapter.log.debug('10.1.2: Alarm ' + identifier + ' with level ' + alarmlevel + ' relevant for ' + type +': ' + typeRelevant + ' (setting: ' + adapter.config.warningLevelSetupNotification + ')')
}
if (type == "general"){
//adapter.log.debug('14.1.2.1: Settings relevance general: ' + adapter.config.warningLevelSetupGeneral)
switch (adapter.config.warningLevelSetupGeneral){
case '0':
typeRelevant = true
break;
case '1':
if (alarmlevelInt >= 3){
typeRelevant = true
}
break;
case '2':
if (alarmlevelInt == 4){
typeRelevant = true
}
break;
default:
//Do nothing
adapter.log.debug('10.1.1: Type not found -> no check for relevance possible')
break;
}
adapter.log.debug('10.1.2: Alarm ' + identifier + ' with level ' + alarmlevel + ' relevant for ' + type +': ' + typeRelevant + ' (setting: ' + adapter.config.warningLevelSetupGeneral)
}
return typeRelevant
}
function getNotificationLevel(level){
var notificationText = ""
if(adapter.config.levelType == "Rufezeichen"){
switch (level) {
case 1:
notificationText += ''
break;
case 2:
notificationText += '❗'
break;
case 3:
notificationText += '❗❗'
break;
case 4:
notificationText