diff --git a/actions/data4map.js b/actions/data4map.js index d21aeda..7911d50 100644 --- a/actions/data4map.js +++ b/actions/data4map.js @@ -67,7 +67,7 @@ const readLastDates = async (typ) => { return await fetchFromInflux(ret, query) } -export const getData4map = async (params) => { +export var getData4map = async (params) => { let start = DateTime.now() let ret = {err: null} diff --git a/actions/getsensorData.js b/actions/getsensorData.js index cdd59d3..45142b5 100644 --- a/actions/getsensorData.js +++ b/actions/getsensorData.js @@ -104,7 +104,8 @@ export const calcRange = (opts) => { // return: // Returns from the special routines // ********************************************* -export const getSensorData = async (params) => { +// export const getSensorData = async (params) => { +export async function getSensorData(params) { let ret = {err: null} let {opts, err} = checkParams(params, { // check for sensorid mandatory: [{name: 'sensorid', type: 'int'}], @@ -116,7 +117,7 @@ export const getSensorData = async (params) => { // with the sensorid get the type of this sensor let erg = await getOneProperty({sensorid: opts.sensorid}) if (erg.err) { - return returnOnError(ret, err1, getSensorData.name) + return returnOnError(ret, erg.err, getSensorData.name) } // distribute to the right routine @@ -132,7 +133,8 @@ export const getSensorData = async (params) => { - export const getActData = async (opts) => { +// export const getActData = async (opts) => { +export async function getActData(opts) { let ret = {err: null, values: []} let sorting = '' if(opts.sort) { @@ -156,7 +158,7 @@ ${sorting} // ..../api/getavgdata?sensorid=123&span=2&avg=10&moving=true&datetime=2022-04-12T13:14:15Z -export const getAvgData = async (params) => { +export var getAvgData = async (params) => { let ret = {data: {count: 0, values: []}, err: null} let {opts, err} = checkParams(params, { @@ -200,7 +202,7 @@ export const getAvgData = async (params) => { } // ..../api/getlongavg?sensorid=123&span=2 -export const getLongAvg = async (params) => { +export var getLongAvg = async (params) => { let ret = {data: {count: 0, values: []}, err: null} let {opts, err} = checkParams(params, { diff --git a/app.js b/app.js index b0ad79b..eb2e1d6 100644 --- a/app.js +++ b/app.js @@ -5,8 +5,8 @@ import cookieParser from 'cookie-parser' import cors from 'cors' import indexRouter from './routes/index.js' -import { apiRouter } from './routes/api.js' -import chartRouter from './routes/chartapi.js' +import { apiRouter, chartapiRouter } from './routes/api.js' +// import chartRouter from './routes/chartapi.js' const app = express() @@ -18,7 +18,7 @@ app.use(cookieParser()) app.use('/', indexRouter) app.use('/api', apiRouter) -app.use('/chart', chartRouter) +app.use('/chart', chartapiRouter) // catch 404 and forward to error handler diff --git a/routes/api.js b/routes/api.js index c79601a..62e0520 100644 --- a/routes/api.js +++ b/routes/api.js @@ -1,16 +1,33 @@ import express from 'express' import {getData4map} from "../actions/data4map.js" -import * as cTable from "../utilities/commands.js" +import * as tables from "../utilities/tables.js" import * as ERR from "../utilities/errortexts.js" +import * as getData from "../actions/getsensorData.js"; +import * as getProps from "../actions/getproperties.js"; +import * as getAKWs from "../actions/getAKWData.js"; +import * as holAddr from "../actions/getaddress.js"; export const apiRouter = express.Router(); +export const chartapiRouter = express.Router(); -export const dispatchCommand = async (cmd, table, query, res) => { +const cmdTable = [ + {cmd: 'getactdata', func: getData.getActData}, + {cmd: 'getlongavg', func: getData.getLongAvg}, + {cmd: 'getavgdata', func: getData.getAvgData}, + {cmd: 'getoneproperty', func: getProps.getOneProperty}, + {cmd: 'getakwdata', func: getAKWs.getakwdata}, + {cmd: 'getaddress', func: holAddr.getAddress}, + {cmd: 'getsensordata', func: getData.getSensorData}, + {cmd: 'getmapdata', func: getData4map} +] + + +export const dispatchCommand = async (cmd, table, params, res) => { for (let c of table) { if (c.cmd === cmd) { - let erg = await c.func(query) + let erg = await c.func(params) if (typeof erg === 'string') { res.type('text') res.send(erg) @@ -25,6 +42,14 @@ export const dispatchCommand = async (cmd, table, query, res) => { // normal routes called from javascript client apiRouter.get('/:cmd', async (req, res) => { - await dispatchCommand(req.params.cmd, cTable.cmdTable, req.query, res) + const params = req.query + params.chart = false + await dispatchCommand(req.params.cmd, cmdTable, params, res) }) +// normal routes called from javascript client +chartapiRouter.get('/:cmd', async (req, res) => { + const params = req.query + params.chart = true + await dispatchCommand(req.params.cmd, cmdTable, params, res) +}) diff --git a/routes/chartapi.js b/routes/chartapi.js index 2c75055..2571039 100644 --- a/routes/chartapi.js +++ b/routes/chartapi.js @@ -1,40 +1,14 @@ import express from 'express' +import * as tables from "../utilities/tables.js" +import { dispatchCommand } from "./api.js" -import {getData4map} from "../actions/data4map.js" -import * as cTable from "../utilities/commands.js" -import * as ERR from "../utilities/errortexts.js" - -const router = express.Router(); - -/* GET home page. */ +const chartapiRouter = express.Router(); // normal routes called from javascript client -router.get('/:cmd', async (req, res) => { - const cmd = req.params.cmd - let table - for (let c of cTable.chartcmdTable) { - if (c.cmd === cmd) { - let erg = await c.func(req.query) - if (typeof erg === 'string') { - res.type('text') - res.send(erg) - } else { - res.json(erg) - } - return - } - } - res.json({err: ERR.CMNDUNKOWN}) +chartapiRouter.get('/:cmd', async (req, res) => { + const params = req.query + params.kind = 'chart' + await dispatchCommand(req.params.cmd, tables.cmdTable, params, res) }) -router.post('/:cmd', async (req, res) => { - const cmd = req.params.cmd - if (cmd === 'getdata4maps') { - const erg = await getData4map(req.body) - res.json(erg) - } else { - res.json({err: ERR.CMNDUNKOWN}) - } -}) - -export default router +export default chartapiRouter \ No newline at end of file diff --git a/routes/index.js b/routes/index.js index af56114..5a6a40b 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,7 +1,4 @@ import express from 'express' - -import {getData4map} from "../actions/data4map.js" -import * as cTable from "../utilities/commands.js" import * as ERR from "../utilities/errortexts.js" const router = express.Router(); diff --git a/sensorspecials/noise.js b/sensorspecials/noise.js index e52aeee..5143f4f 100644 --- a/sensorspecials/noise.js +++ b/sensorspecials/noise.js @@ -4,11 +4,9 @@ import {returnOnError} from "../utilities/reporterror.js"; import { getActData, getAvgData, getLongAvg, fetchFromInflux, calcRange} from "../actions/getsensorData.js" import checkParams from "../utilities/checkparams.js"; -import {getOneProperty} from "../actions/getproperties.js"; import * as ERR from "../utilities/errortexts.js" import {DateTime} from 'luxon' -import {getData4map} from "../actions/data4map.js"; -import {NODATA} from "../utilities/errortexts.js"; +import * as noiseChart from "./noiseChart.js"; const setoptionfromtable = (opt,tabval) => { let ret = opt @@ -25,6 +23,7 @@ export const getNoiseData = async (params, possibles, props) => { let {opts, err} = checkParams(params, { mandatory:[ {name:'sensorid', type: 'int'}, + {name:'chart', type: 'bool'}, ], optional: possibles }) @@ -44,6 +43,9 @@ export const getNoiseData = async (params, possibles, props) => { opts.start = start opts.stop = stop let erg = await x.func(opts) // get the data + if (opts.chart) { + return erg + } ret = { err: erg.err, sid: opts.sensorid, @@ -92,8 +94,12 @@ export const getNoiseData = async (params, possibles, props) => { // .... // // ********************************************* -const getLiveData = async (opt) => { - return await getActData(opt) +const getLiveData = async (opts) => { + const erg = await getActData(opts) + if (!opts.chart || erg.err) { + return erg + } + return noiseChart.liveData(opts, erg.values) } @@ -135,9 +141,10 @@ const gethavgData = async (opts, props) => { } } return csvStr - } else { + } else if (!opts.chart || erg.err) { return {err: erg.err, values: erg.values} - + } else { + return noiseChart.havgData(opts, erg.values) } } @@ -413,16 +420,6 @@ const getAPIprops = (opt) => { } -const whatTable = [ - {'what':'live', 'span': 1, 'daystart': false, peak: false, 'func': getLiveData}, - {'what':'havg', 'span': 7, 'daystart': true, peak: true, 'func': gethavgData}, - {'what':'davg', 'span': 30, 'daystart': true, peak: true, 'func': getdavgData}, - {'what':'daynight', 'span': 30, 'daystart': true, peak: false, 'func': getdaynightData}, - {'what':'lden', 'span': 30, 'daystart': true, peak: false, 'func': getLdenData}, - {'what':'props', 'span': 0, 'daystart': true, peak:false, 'func': getAPIprops}, -]; - - const getNoiseAVGData = async (opts) => { let ret = {err: null, values: []} let emptyValues = {n_AVG:-1} @@ -506,3 +503,12 @@ peak = data return { err: ret.err, values: hoursArr} } +const whatTable = [ + {'what':'live', 'span': 1, 'daystart': false, peak: false, 'func': getLiveData}, + {'what':'havg', 'span': 7, 'daystart': true, peak: true, 'func': gethavgData}, + {'what':'davg', 'span': 30, 'daystart': true, peak: true, 'func': getdavgData}, + {'what':'daynight', 'span': 30, 'daystart': true, peak: false, 'func': getdaynightData}, + {'what':'lden', 'span': 30, 'daystart': true, peak: false, 'func': getLdenData}, + {'what':'props', 'span': 0, 'daystart': true, peak:false, 'func': getAPIprops}, +]; + diff --git a/sensorspecials/noiseChart.js b/sensorspecials/noiseChart.js index 40ba3d4..b992111 100644 --- a/sensorspecials/noiseChart.js +++ b/sensorspecials/noiseChart.js @@ -1,2 +1,273 @@ -// Prepare all data to plt the charts +// Prepare all data to plot the charts +import * as utils from '../utilities/chartoptions.js' +export const liveData = (params, values) => { + let series1 = [] + let series2 = [] + let series3 = [] + + // save the current (actual) values here + let aktVal = {} + + + // Put values into the arrays + let cnt = 0 + values.forEach((x) => { + let dat = new Date(x.datetime).getTime() + series1.push([dat, x.noise_LAeq]) + series2.push([dat, x.noise_LA_max]) + series3.push([dat, x.noise_LA_min]) // put data and value into series array + }) + + if (values.length != 0) { + const lastidx = values.length - 1 + // Aktuelle Werte speichern + aktVal['LAeq'] = values[lastidx].noise_LAeq + aktVal['LAMax'] = values[lastidx].noise_LA_max + aktVal['LAMin'] = values[lastidx].noise_LA_min + + // InfoTafel füllen + let infoTafel = + '' + + '' + + '' + + '' + infoTafel += + '' + + '' + infoTafel += + '' + + '' + infoTafel += + '
Aktuelle Werte
LAeq' + (aktVal.LAeq).toFixed(1) + 'dbA
LAmin' + (aktVal.LAMin).toFixed(0) + 'dbA
LAmax' + (aktVal.LAMax).toFixed(0) + 'dbA
' + + '' + } + let txtMeldung = false + + + // Plot-Options + let options = utils.createGlobObtions('dd.LL HH:mm:ss') + + let series_LAeq = { + name: 'LAeq', + data: series1, + color: utils.colors.eq, + yAxis: 0, + zIndex: 1, + marker: { + enabled: false, + symbol: 'square', + }, + visible: true, + fillColor: { + linearGradient: [0, 600, 0, 0], + stops: [ + [0, 'rgb(238,223,236)'], + [1, 'rgb(238,223,236)'] + ] + }, + } + + let series_LAMax = { + name: 'LAmax', + data: series2, + color: utils.colors.max, + yAxis: 0, + zIndex: 0, + marker: { + enabled: false, + symbol: 'square', + }, + visible: true, + fillColor: { + linearGradient: [0, 600, 0, 0], + stops: [ + [0, 'rgb(255,233,236)'], + [1, 'rgb(255,233,236)'] + ] + }, +// fillOpacity: 0.2, + } + + let series_LAMin = { + name: 'LAmin', + data: series3, + color: utils.colors.min, + yAxis: 0, + zIndex: 6, + marker: { + enabled: false, + symbol: 'square', + }, + visible: true, + fillColor: { + linearGradient: [0, 600, 0, 0], + stops: [ + [0, 'rgb(219,216,217)'], + [1, 'rgb(219,216,217)'] + ] + }, + } + + let yAxis_LAeq = { // 1 + title: { + text: 'dbA', + style: { + color: 'black' + } + }, + max: utils.noise_ymax, +// min: utils.noise_ymin, + min: 20, +// opposite: true, + tickAmount: 11, + useHTML: true, + } + + options.series = [] + options.yAxis = [] + options.series[0] = series_LAeq + options.series[1] = series_LAMin + options.title.text = 'Noise level for one day' + options.subtitle.useHTML = true + options.subtitle.text = '2.5min log. average values
 ' + options.series[2] = series_LAMax +// options.yAxis[2] = yAxis_LAMin + options.yAxis[0] = yAxis_LAeq +// options.yAxis[1] = yAxis_LAMax + options.chart.zoomType = 'x' + options.chart.type = 'area' + options.chart.resetZoomButton= { + position: { + align: 'left', + verticalAlign: 'bottom', + x: 20, + y: -50, + }, + relativeTo: 'chart' + } + options.tooltip.formatter = function () { + let y = Math.round(this.y * 100) / 100 + return '
' + + DateTime.fromMillis(this.x, {zone: 'utc'}).toFormat("dd.LL - HH:mm:ss") + '
' + + '● ' + + this.series.name + ':  ' + y + '
' + } + + + let noDataTafel = '
' + + 'Für heute liegen leider keine Daten vor!
Bitte den Sensor überprüfen!\'
' + + '
' + + + let navy = 20 + let navbreit = 55 + let navtxt = ['-24h', '-12h', 'live', '+12h', '+24h'] + let navtime = [-24, -12, 0, 12, 24] + + return { options: options, params: params} +} + + + +export const havgData = (params, values) => { + let series1 = [] + let series2 = [] + // Put values into the serieses + let cnt = 0 + values.forEach((x) => { + if (x.n_AVG != -1) { + let dat = new Date(x.datetime).getTime() // retrieve the date + series1.push([dat, x.n_AVG]) + series2.push([dat, x.peakcount]) + } + }) + + // Plot-Options + let options = utils.createGlobObtions() + options.xAxis.tickInterval = 6 * 3600 * 1000 +// options.xAxis.plotBands = utils.calcWeekends(data, true) + options.xAxis.minTickInterval = 3600 * 1000 + + options.yAxis = { + title: { + text: 'dbA', + useHTML: true, + }, + min: utils.noise_ymin, + tickAmount: 10, + gridLineColor: 'lightgray', + } + options.series = [ + { + name: 'Mean value LAeq', + data: series1, + color: utils.colors.eq, + type: 'column', + zIndex: 2, + }, + ] + options.plotOptions.column = { + pointWidth: 8, + groupPadding: 0.5 + } + options.title.text = 'Noise hourly mean values' + options.subtitle.text = 'Hourly mean values of LAeq for each full hour
 ' + options.subtitle.useHTML = true + options.chart.zoomType = 'x' + options.chart.height = 350 + options.chart.resetZoomButton= { + position: { + align: 'left', +// verticalAlign: 'bottom', + x: 20, + y: 300, + }, + relativeTo: 'chart' + } + options.tooltip.formatter = function () { + let y = Math.round(this.y * 10) / 10 + return '
' + + DateTime.fromMillis(this.x, {zone: 'utc'}).toFormat("dd.LL - HH'h'") + '
' + + '● ' + + this.series.name + ':  ' + y + '
' + } + + return { options: options, params: params} +/* + + // Do PLOT 1 + let ch = Highcharts.chart('dhour', options, function (chart) { + utils.addSensorID2chart(chart, {sid: data.sid, indoor: data.indoor}, document.getElementById('dhour').offsetWidth) + }) + // Do PLOT 2 + options.series[0] = { + name: 'Peaks', + data: series2, + color: utils.colors.peaks, + type: 'column' + } + options.title.text = 'Peaks more than ' + data.peak + ' dbA' + options.subtitle.text = 'Number of exceedances per hour
 ' + options.yAxis = { + title: { + text: 'Counts', + useHTML: true, + }, + tickAmount: 10, + allowDecimals: false, + gridLineColor: 'lightgray', + } + options.tooltip.formatter = function () { + let y = Math.round(this.y) + return '
' + + DateTime.fromMillis(this.x, {zone: 'utc'}).toFormat("dd.LL - HH'h'") + '
' + + '● ' + + this.series.name + ':  ' + y + '
' + } + + let ch1 = Highcharts.chart('dpeak', options) +} + +*/ +} \ No newline at end of file diff --git a/utilities/chartoptions.js b/utilities/chartoptions.js new file mode 100644 index 0000000..60c5cfd --- /dev/null +++ b/utilities/chartoptions.js @@ -0,0 +1,142 @@ +// Utility routine for plotting the data + +export const colors = {'eq': '#0000FF', 'max': '#FF0000', 'min': '#008000', 'peaks': '#DAA520'}; + +export function createGlobObtions() { + // Options, die für alle Plots identisch sind + let globObject = { + chart: { + height: 600, +// width: 1000, + spacingRight: 20, + spacingLeft: 20, + spacingTop: 25, + backgroundColor: { + linearGradient: [0, 400, 0, 0], + stops: [ + [0, '#eee'],//[0, '#ACD0AA'], //[0, '#A18D99'], // [0, '#886A8B'], // [0, '#F2D0B5'], + [1, '#fff'] + ] + }, + type: 'line', + borderWidth: '2', + events: { + selection: function (event) { + if (event.xAxis) { + doUpdate = false; + } else { + doUpdate = true; + } + } + } + }, + title: { +// text: 'Feinstaub über 1 Tag', + align: 'left', + style: {'fontSize': '25px'}, + useHTML: true, + }, + subtitle: { +// text: 'Gemessene Werte und ' + avgTime + 'min-gleitende Mittelwerte', + align: 'left', + }, + tooltip: { + valueDecimals: 1, + backgroundColor: 0, + borderWidth: 0, + borderRadius: 0, + useHTML: true, + }, + xAxis: { + type: 'datetime', + title: { + text: 'date/time', + }, + gridLineWidth: 2, + labels: { + formatter: function () { + let v = this.axis.defaultLabelFormatter.call(this); + if (v.indexOf(':') == -1) { + return '' + v + ''; + } else { + return v; + } + } + }, + }, + legend: { + enabled: true, + layout: 'horizontal', +// verticalAlign: 'top', + borderWidth: 1, + align: 'center', + }, + plotOptions: { + series: { + animation: false, + turboThreshold: 0, + marker: { + enabled: false, + }, + }, + } + }; + return globObject; +} + +export function calcWeekends(data, isyear) { + let weekend = []; + let oldDay = 8; + for (let i = 0; i < data.length; i++) { + let mom = moment(data[i].date); + if (isyear) { + mom = moment(data[i]._id) + } + let day = mom.day(); + let st = mom.startOf('day'); + if (day != oldDay) { + if (day == 6) { + weekend.push({ + color: 'rgba(169,235,158,0.4)', + from: st.valueOf(), + to: st.add(1, 'days').valueOf(), + zIndex: 0 + }) + } else if (day == 0) { + weekend.push({ + color: 'rgba(169,235,158,0.4)', + from: st.valueOf(), + to: st.add(1, 'days').valueOf(), + zIndex: 0 + }) + } + oldDay = day; + } + } + return weekend; +} + +export function calcDays(data, isyear) { + let days = []; + if (data.length == 0) { + return days + } + let oldday = moment(data[0].date).day(); + if (isyear) { + oldday = moment(data[0]._id).day(); + } + for (let i = 0; i < data.length; i++) { + let m = moment(data[i].date); + if (isyear) { + m = moment(data[i]._id); + } + let tag = m.day() + if (tag != oldday) { + m.startOf('day'); + days.push({color: 'lightgray', value: m.valueOf(), width: 1, zIndex: 2}); + oldday = tag; + } + } + return days; +}; + diff --git a/utilities/commands.js b/utilities/commands.js deleted file mode 100644 index b5aea60..0000000 --- a/utilities/commands.js +++ /dev/null @@ -1,31 +0,0 @@ -// Command table for http calls - -import * as getData from "../actions/getsensorData.js" -import * as getProps from "../actions/getproperties.js" -import * as getAKWs from "../actions/getAKWData.js" -import * as holAddr from "../actions/getaddress.js" -import * as radioact from "../sensorspecials/radioact.js" -import * as noise from "../sensorspecials/noise.js" -import {getData4map} from "../actions/data4map.js"; - -export const cmdTable = [ - {cmd: 'getactdata', func: getData.getActData}, - {cmd: 'getlongavg', func: getData.getLongAvg}, - {cmd: 'getavgdata', func: getData.getAvgData}, - {cmd: 'getoneproperty', func: getProps.getOneProperty}, - {cmd: 'getakwdata', func: getAKWs.getakwdata}, - {cmd: 'getaddress', func: holAddr.getAddress}, - {cmd: 'getsensordata', func: getData.getSensorData}, - {cmd: 'getmapdata', func: getData4map} -] - -export const chartcmdTable = [ - {cmd: 'getactdata', func: getData.getActData}, - {cmd: 'getlongavg', func: getData.getLongAvg}, - {cmd: 'getavgdata', func: getData.getAvgData}, - {cmd: 'getoneproperty', func: getProps.getOneProperty}, - {cmd: 'getakwdata', func: getAKWs.getakwdata}, - {cmd: 'getaddress', func: holAddr.getAddress}, - {cmd: 'getsensordata', func: getData.getChartSensorData}, - {cmd: 'getmapdata', func: getData4map} -] \ No newline at end of file diff --git a/utilities/tables.js b/utilities/tables.js new file mode 100644 index 0000000..8cfcc29 --- /dev/null +++ b/utilities/tables.js @@ -0,0 +1,13 @@ +// Command table for http calls + +import * as getData from "../actions/getsensorData.js" +import * as getProps from "../actions/getproperties.js" +import * as getAKWs from "../actions/getAKWData.js" +import * as holAddr from "../actions/getaddress.js" +import * as radioact from "../sensorspecials/radioact.js" +import * as noise from "../sensorspecials/noise.js" +import {getData4map} from "../actions/data4map.js" +import * as noiseChart from '../sensorspecials/noiseChart.js' + + +