diff --git a/ioBroker/Blockly/CardLChart_Influx2.ts b/ioBroker/Blockly/CardLChart_Influx2.ts new file mode 100644 index 00000000..4840919a --- /dev/null +++ b/ioBroker/Blockly/CardLChart_Influx2.ts @@ -0,0 +1,130 @@ +/** + * Dieses Script fragt eine influxDb ab, um Daten für die cardLcart (Liniendiagramm) zuberechnen und im richtigen Format bereitzustellen. + * Es erstellt automatisch einen Datenpunkt. + * Die Abfrage muss ggf. angepasst werden. Aktuell ermittelt sie Werte der letzten 24h, zu Stundenwerten zusammengefasst. + */ + +const Debug = false; // true für erweiterte Ausgaben im Log + +const NSPanel_Path = '0_userdata.0.NSPanel.'; +const Path = NSPanel_Path + 'Influx2NSPanel.cardLChart.'; +const InfluxInstance = 'influxdb.0'; +const influxDbBucket = 'storage_short'; +const numberOfHoursAgo = 24; +const xAxisTicksEveryM = 60; +const xAxisLabelEveryM = 240; +// + +const sensors : Record> = {}; +/** + * Hier werden die Sensoren festgelegt nach flogendem Schema + * + * sensors[‘Datenpunkt(kompletter Pfad) des Messwertes'] = {'taget': 'Name des Datenpunkt für die Chartwerte', 'measurement': 'genutzter Alias in der Influxdb für den Messwert'}; + * + * Wenn der Wert in der Datenbank keinen Alias hat bleibt der Wert 'measurement': weg. + * Jeder Messwert bekommt einen eigenen sensors[...] = {'target':....} + */ + + +sensors['netatmo-crawler.0.stationData.1.temperature'] = {'target':'AussenTemp', 'measurement':'wetter.temperatur'}; + +// ##### ab hier keine Änderungen mehr nötig ##### + +// create data source for NsPanel on script startup +Object.keys(sensors).forEach(async id => { + await generateDateAsync(id); +}); + +// then listen to the sensors and update the data source states accordingly +on({ id: Object.keys(sensors), change: 'any' }, async function (obj) { + if (!obj.id) { + return; + } + await generateDateAsync(obj.id); +}); + +//__________________________ +// Beschreibe diese Funktion: Daten generieren +async function generateDateAsync(sensorId: string) { + let idMeasurement = sensors[sensorId].measurement; + if (idMeasurement =='' ||idMeasurement == undefined) {idMeasurement = sensorId}; + const dataPointId:string = Path + sensors[sensorId].target +'.ACTUAL'; + if (Debug) log(`(f) generateDateAsync: ${sensorId} ${dataPointId} > ${idMeasurement}`); + + const query =[ + 'from(bucket: "' + influxDbBucket + '")', + '|> range(start: -' + numberOfHoursAgo + 'h)', + '|> filter(fn: (r) => r["_measurement"] == "' + idMeasurement + '")', + '|> filter(fn: (r) => r["_field"] == "value")', + '|> drop(columns: ["from", "ack", "q"])', + '|> aggregateWindow(every: 1h, fn: last, createEmpty: false)', + '|> map(fn: (r) => ({ r with _rtime: int(v: r._time) - int(v: r._start)}))', + '|> yield(name: "_result")'].join(''); + + if (Debug) console.log('Query: ' + query); + + const result : any = await sendToAsync(InfluxInstance, 'query', query); + if (result.error) { + console.error(result.error); + return; + } + if (Debug) console.log(JSON.stringify(result)); + const numResults = result.result.length; + let coordinates : string = ''; + for (let r = 0; r < numResults; r++) + { + const list : string[] = []; + const numValues = result.result[r].length; + + for (let i = 0; i < numValues; i++) + { + const time = Math.round(result.result[r][i]._rtime/1000/1000/1000/60); + const value = Math.round(result.result[r][i]._value * 10); + list.push(time + ":" + value); + } + + coordinates = list.join("~"); + if (Debug) console.log(coordinates); + } + + const ticksAndLabelsList : string[] = [] + const date = new Date(); + date.setMinutes(0, 0, 0); + const ts = Math.round(date.getTime() / 1000); + const tsYesterday = ts - (numberOfHoursAgo * 3600); + if (Debug) console.log('Iterate from ' + tsYesterday + ' to ' + ts + ' stepsize=' + (xAxisTicksEveryM * 60)); + for (let x = tsYesterday, i = 0; x < ts; x += (xAxisTicksEveryM * 60), i += xAxisTicksEveryM) + { + if ((i % xAxisLabelEveryM)) + ticksAndLabelsList.push('' + i); + else + { + const currentDate = new Date(x * 1000); + // Hours part from the timestamp + const hours = "0" + String(currentDate.getHours()); + // Minutes part from the timestamp + const minutes = "0" + String(currentDate.getMinutes()); + const formattedTime = hours.slice(-2) + ':' + minutes.slice(-2); + + ticksAndLabelsList.push(String(i) + "^" + formattedTime); + } + } + if (Debug) console.log('Ticks & Label: ' + ticksAndLabelsList); + if (Debug) console.log('Coordinates: ' + coordinates); + await setOrCreate(dataPointId, ticksAndLabelsList.join("+") + '~' + coordinates, true); +} + +//__________________________ +// Beschreibe diese Funktion: Datenpunkte anlegen bzw. schreiben +async function setOrCreate(id : string, value : any, ack : boolean) { + if (!(await existsStateAsync(id))) { + await createStateAsync(id, value, { + name: id.split('.').reverse()[0], + desc: 'Sensor Values [~