first
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
.idea
|
||||
.env
|
||||
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$/../SensorApi" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptLibraryMappings">
|
||||
<includedPredefinedLibrary name="Node.js Core" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/untitled1.iml" filepath="$PROJECT_DIR$/.idea/untitled1.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,11 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="bin/www" type="NodeJSConfigurationType" path-to-js-file="bin/www.js" working-dir="$PROJECT_DIR$">
|
||||
<envs>
|
||||
<env name="DEBUG" value="sensorapi:*" />
|
||||
</envs>
|
||||
<EXTENSION ID="com.jetbrains.nodejs.run.NodeJSStartBrowserRunConfigurationExtension">
|
||||
<browser url="http://localhost:3000/" />
|
||||
</EXTENSION>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,23 @@
|
||||
FROM node:alpine
|
||||
|
||||
ADD package.json /tmp/package.json
|
||||
RUN cd /tmp && npm install
|
||||
RUN mkdir -p /opt/app && cp -a /tmp/node_modules /opt/app/
|
||||
|
||||
WORKDIR /opt/app
|
||||
ADD . /opt/app
|
||||
|
||||
RUN apk add busybox-initscripts
|
||||
RUN apk add --no-cache tzdata
|
||||
ENV TZ Europe/Berlin
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
|
||||
RUN deluser --remove-home node
|
||||
|
||||
RUN touch cmds.sh \
|
||||
&& echo 'npm start' >>cmds.sh
|
||||
|
||||
CMD sh ./cmds.sh
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
// Fetch the actual (= newest) data out of the dbase to show it on the map
|
||||
import {DateTime} from "luxon"
|
||||
import {logit} from "../utilities/logit.js"
|
||||
import * as mongo from "../databases/mongo.js"
|
||||
import {reportError, returnOnError} from "../utilities/reporterror.js"
|
||||
import * as ERR from "../utilities/errortexts.js"
|
||||
|
||||
|
||||
// Default distance for center search ( in km)
|
||||
const DEFAULT_DISTANCE = 10
|
||||
|
||||
|
||||
// Relations between types and value type
|
||||
const vtype2measurement = {
|
||||
P1: 'pm', P2: 'pm', P0: 'pm',
|
||||
temperature: 'thp', humidity: 'thp', pressure: 'thp',
|
||||
noise_LAeq: 'noise',
|
||||
counts_per_minute: 'radioactivity'
|
||||
};
|
||||
|
||||
|
||||
// find first value type from measurement
|
||||
const getfieldfromtype = (typ) => {
|
||||
for (const [key, value] of Object.entries(vtype2measurement)) {
|
||||
if (value === typ) {
|
||||
return key
|
||||
}
|
||||
}
|
||||
return ' '
|
||||
}
|
||||
|
||||
|
||||
export const getData4map = async (params) => {
|
||||
let start = DateTime.now()
|
||||
let retur = { err: null, data: { params: params} }
|
||||
|
||||
// check parameters
|
||||
if ((params.type === undefined) || (params.type === '')) {
|
||||
return returnOnError(retur, ERR.NOTYP, getData4map.name)
|
||||
}
|
||||
const typ = params.type
|
||||
let poly = []
|
||||
let south = null, north = null, east = null, west = null, center = null
|
||||
let distance = DEFAULT_DISTANCE
|
||||
if (!((params.box === undefined) || (params.box == ""))) {
|
||||
const box = params.box
|
||||
if (!((box === undefined) || (box === ' ') || (box === 'null'))) {
|
||||
south = parseFloat(box.south)
|
||||
north = parseFloat(box.north)
|
||||
east = parseFloat(box.east)
|
||||
west = parseFloat(box.west)
|
||||
logit(`getData4map: S=${south} N=${north} E=${east} W=${west}`)
|
||||
}
|
||||
}
|
||||
if (!((params.poly === undefined) || (params.poly === ' '))){
|
||||
poly = JSON.parse(params.poly)
|
||||
}
|
||||
if (params.center !== undefined) {
|
||||
center = params.center
|
||||
if ((params.distance !== undefined) &&
|
||||
(params.distance >= 1) && (params.distance <= 1000)) {
|
||||
distance = params.distance
|
||||
}
|
||||
}
|
||||
const aktData = []
|
||||
let lastDate = 0
|
||||
let query = {type: typ}
|
||||
|
||||
// if polyline or box were given, set query
|
||||
if (poly.length !== 0) { // polyline given
|
||||
query.location = {
|
||||
$geoWithin: {
|
||||
$geometry: {
|
||||
type: "Polygon",
|
||||
coordinates: [poly],
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (south !== null) { // box given
|
||||
query["location.loc"] = {
|
||||
$geoWithin: {
|
||||
$box: [
|
||||
[west, south],
|
||||
[east, north]
|
||||
]
|
||||
}
|
||||
}
|
||||
} else if (center !== null) { // center point given
|
||||
query["location.loc"] = {
|
||||
$nearSphere: {
|
||||
$geometry: {
|
||||
type: "Point",
|
||||
coordinates: center
|
||||
},
|
||||
$maxDistance: distance * 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
// depending of the type find the measurement field to fetch
|
||||
const vtype = getfieldfromtype(typ)
|
||||
|
||||
// fetch mapdata from mongodb
|
||||
try {
|
||||
let { values, err } = await mongo.readMapdata(query, 0)
|
||||
if(err) {
|
||||
return returnOnError(retur, ERR.NOSENSFOUND, getData4map.name)
|
||||
}
|
||||
for (let item of values) {
|
||||
const oneAktData = {
|
||||
location: item.location.loc.coordinates,
|
||||
id: item._id,
|
||||
name: item.name,
|
||||
indoor: item.location.indoor,
|
||||
value: Math.round(item.value[vtype] * 100) / 100
|
||||
}
|
||||
let now = new Date().getTime()
|
||||
let diff = now - item.timestamp
|
||||
if (diff >= 7 * 24 * 3600 * 1000) {
|
||||
oneAktData.value = -2
|
||||
} else if (diff >= 2 * 3600 * 1000) {
|
||||
oneAktData.value = -1
|
||||
}
|
||||
if (item.timestamp > lastDate) {
|
||||
lastDate = item.timestamp
|
||||
}
|
||||
aktData.push(oneAktData)
|
||||
}
|
||||
logit(`Query duration: ${start.diffNow('seconds').toObject().seconds * -1} sec`)
|
||||
retur.data.lastDate = lastDate
|
||||
retur.data.valuetype = vtype
|
||||
retur.data.count = aktData.length
|
||||
retur.data.values = aktData
|
||||
return retur
|
||||
}
|
||||
catch(e) {
|
||||
return returnOnError(retur, `catch\n${e}`, getData4map.name)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// get data for one sensor
|
||||
|
||||
import {DateTime} from "luxon"
|
||||
import { logit, logerror } from '../utilities/logit.js'
|
||||
import * as mongo from "../databases/mongo.js"
|
||||
import {reportError, returnOnError} from "../utilities/reporterror.js"
|
||||
import {csv2Json} from "../utilities/csv2json.js"
|
||||
|
||||
|
||||
// Fetch all akw data out of the dbase
|
||||
// router.get('/getakwdata/', async function (req, res) {
|
||||
export const getakwdata = async (options) => {
|
||||
let data = {err: null, ...options, count: 0, values: []}
|
||||
let erg = []
|
||||
try {
|
||||
let rawAKWs = await mongo.readAKWs(options)
|
||||
if (rawAKWs.err) {
|
||||
return returnOnError(date, rawAKWs.err, getakwdata.name)
|
||||
}
|
||||
for (let item of rawAKWs.values.akws) {
|
||||
var oneAktData = {};
|
||||
oneAktData['location'] = {
|
||||
type: 'Point',
|
||||
coordinates: [item.lon, item.lat]
|
||||
};
|
||||
oneAktData['name'] = item.Name;
|
||||
oneAktData['active'] = item.Status == 'aktiv';
|
||||
oneAktData['start'] = item.Baujahr;
|
||||
oneAktData['end'] = item.Stillgeleg;
|
||||
oneAktData['type'] = item.Status === 'aktiv' ? 'akw_a' : 'akw_s';
|
||||
oneAktData['link'] = item.Wiki_Link;
|
||||
erg.push(oneAktData); // dies ganzen Werte nun in das Array
|
||||
}
|
||||
for (let item of rawAKWs.values.th1_akws) {
|
||||
let oneAktData = {};
|
||||
let loc = item.geo.substr(6).split(' ');
|
||||
let lon = parseFloat(loc[0]);
|
||||
let lat = parseFloat(loc[1]);
|
||||
oneAktData['location'] = {
|
||||
type: 'Point',
|
||||
coordinates: [lon, lat]
|
||||
};
|
||||
oneAktData['name'] = item.name;
|
||||
oneAktData['typeText'] = item.types;
|
||||
oneAktData['type'] = item.types == "Nuclear power plant" ? 'akw_a' : 'other';
|
||||
oneAktData['link'] = item.item;
|
||||
if (item.itemServiceretirement != undefined) {
|
||||
oneAktData['ende'] = item.itemServiceretirement.substr(0, 4);
|
||||
}
|
||||
if (item.itemServiceentry != undefined) {
|
||||
oneAktData['begin'] = item.itemServiceentry.substr(0, 4);
|
||||
}
|
||||
erg.push(oneAktData);
|
||||
}
|
||||
data.values = erg
|
||||
data.count = erg.length
|
||||
} catch (e) {
|
||||
return returnOnError(data, e, getakwdata.name)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import {returnOnError} from "../utilities/reporterror.js"
|
||||
import axios from 'axios'
|
||||
import {logit} from "../utilities/logit.js"
|
||||
import {getOneProperty} from "./getproperties.js"
|
||||
|
||||
const NOMINATIM_URL = `https://nominatim.openstreetmap.org/reverse?lat=${'xx'}&lon=${'yy'}&format=json`
|
||||
|
||||
export const getAddress = async (params) => {
|
||||
let ret = {address: "", err: null}
|
||||
let {props, err} = await getOneProperty(params)
|
||||
if (err) {
|
||||
return returnOnError(ret, err, getAddress.name)
|
||||
}
|
||||
let coord = props.location[0].loc.coordinates
|
||||
let url = NOMINATIM_URL.replace('xx', coord[1]).replace('yy', coord[0])
|
||||
try {
|
||||
const response = await axios(encodeURI(url));
|
||||
if (response.status !== 200) {
|
||||
return returnOnError(ret, ERR.RESPSTATUS.replace('ss', response.status, getAddress.name))
|
||||
}
|
||||
let akt = response.data.address
|
||||
logit(JSON.stringify(akt))
|
||||
const CITY = ['city', 'town', 'village', 'suburb', 'county']
|
||||
let city = "unknown"
|
||||
for (let c of CITY) {
|
||||
if(akt[c] !== undefined) {
|
||||
city = akt[c]
|
||||
break
|
||||
}
|
||||
}
|
||||
ret.address = `${(akt.road ? akt.road : "")} ${(akt.postcode ? akt.postcode : "")} ${city}`;
|
||||
} catch (e) {
|
||||
return returnOnError(ret, e, getAddress.name)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
/*
|
||||
let addr = "Addr";
|
||||
try {
|
||||
let ret = await $.get("api/getprops?sensorid=" + marker.options.name);
|
||||
if(ret.values[0].address.city == null) {
|
||||
addr += " unbekannt !"
|
||||
} else {
|
||||
let akt = ret.values[0].address;
|
||||
addr = (akt.street ? akt.street : "") + " " + (akt.plz ? akt.plz : "") + " " + akt.city;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("onMarker - getpops", e)
|
||||
}
|
||||
console.log("addr:", addr);
|
||||
return addr;
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,55 @@
|
||||
// Fetch the properties for the given sensor
|
||||
|
||||
import * as mongo from "../databases/mongo.js"
|
||||
import * as mock from "../mocks/mongo_mock.js"
|
||||
import {returnOnError} from "../utilities/reporterror.js"
|
||||
import * as ERR from "../utilities/errortexts.js"
|
||||
import checkParams from "../utilities/checkparams.js"
|
||||
|
||||
const mockdb = false
|
||||
|
||||
let readProperties
|
||||
if (mockdb) {
|
||||
readProperties = mock.readProperties
|
||||
} else {
|
||||
readProperties = mongo.readProperties
|
||||
}
|
||||
export const getOneProperty = async (params) => {
|
||||
let properties = {props: {}, err: null}
|
||||
let {opts, err} = checkParams(params, {mandatory:[{name:'sensorid', type: 'int'}], optional:[]})
|
||||
if (err) {
|
||||
return returnOnError(properties, err, getOneProperty.name)
|
||||
}
|
||||
let sensorEntries = [];
|
||||
try {
|
||||
let pp = await readProperties({sid: opts.sensorid});
|
||||
if ((pp.values == null) || (pp.error)) {
|
||||
return returnOnError(properties, ERR.NOPROPSREAD.replace('xx', opts.sensorid), getOneProperty.name)
|
||||
}
|
||||
// now find sensors with same location
|
||||
let query
|
||||
if (pp.values.location_id !== undefined) {
|
||||
query = {location_id: pp.values.location_id}
|
||||
} else {
|
||||
query = {id: pp.values.id}
|
||||
}
|
||||
let others = await readProperties(query)
|
||||
if (others.error) {
|
||||
return returnOnError(properties, ERR.NOPROPSREAD.replace('xx',others.errortext), getOneProperty.name)
|
||||
}
|
||||
if (others.values.length > 0) {
|
||||
for (const x of others.values) {
|
||||
if(x.name[0].name === undefined) {
|
||||
sensorEntries.push({name: x.name, sid: x._id})
|
||||
} else {
|
||||
sensorEntries.push({name: x.name[0].name, sid: x._id})
|
||||
}
|
||||
}
|
||||
}
|
||||
properties.props = pp.values
|
||||
properties.props.othersensors = sensorEntries;
|
||||
} catch (e) {
|
||||
return returnOnError(properties, e, getOneProperty.name)
|
||||
}
|
||||
return properties
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
// get data for one sensor
|
||||
|
||||
import {DateTime} from "luxon"
|
||||
import * as influx from "../databases/influx.js"
|
||||
import {returnOnError} from "../utilities/reporterror.js"
|
||||
import {csv2Json} from "../utilities/csv2json.js"
|
||||
import checkParams from "../utilities/checkparams.js";
|
||||
import * as ERR from "../utilities/errortexts.js"
|
||||
import {getOneProperty} from "./getproperties.js";
|
||||
import {getNoiseData} from "../sensorspecials/noise.js";
|
||||
import {getRadioData} from "../sensorspecials/radioact.js";
|
||||
|
||||
// Possible params for the different sensor types
|
||||
const noiseParams = [
|
||||
{name:'data', type: 'string', default: 'live'},
|
||||
{name: 'span', type: 'int', default: ''},
|
||||
{name: 'daystart', type: 'bool', default: null},
|
||||
{name: 'peak', type: 'int', default: 70},
|
||||
{name: 'since', type: 'date', default: '1900-01-01T00:00:00Z'},
|
||||
{name: 'box', type: 'array', default: null},
|
||||
{name: 'out', type: 'string', default: ''},
|
||||
{name: 'csv', type: 'bool', default: false},
|
||||
{name: 'long', type: 'bool', default: false},
|
||||
{name: 'sort', type: 'int', default: 1},
|
||||
{name: 'last_seen', type: 'date', default: '1900-01-01T00:00:00Z'},
|
||||
{name: 'datetime', type: 'date', default: null}
|
||||
]
|
||||
const thpParams = []
|
||||
const pmParams = []
|
||||
const radioParams = []
|
||||
|
||||
// >>>>>>>>>>>>>>>>>>>>> DUMMIES
|
||||
async function getPmData(opts) {
|
||||
|
||||
}
|
||||
async function getThpData(opts) {
|
||||
|
||||
}
|
||||
// <<<<<<<<<<<<<<<<<<<<< DUMMIES
|
||||
|
||||
// Table for the different sensor types
|
||||
const sensorTypeTable = [
|
||||
{ typ: 'thp', possibleParams: thpParams, func: getThpData},
|
||||
{ typ: 'noise', possibleParams: noiseParams, func: getNoiseData},
|
||||
{ typ: 'pm', possibleParams: pmParams, func: getPmData},
|
||||
{ typ: 'radioactivity', possiblePramas: radioParams, func: getRadioData}
|
||||
]
|
||||
|
||||
/* Units:
|
||||
* span -> days
|
||||
* avg -> minutes
|
||||
*/
|
||||
|
||||
// *********************************************
|
||||
// calcRange
|
||||
//
|
||||
// Calculate the date/time range ro read the data from
|
||||
//
|
||||
// If 'datetime' is not given, use 'now()' as end and 'now()' - span as start
|
||||
// If 'datetime' is given, use it as start and 'datetime'+span as end
|
||||
// If 'daystart'==true, change start and end so, that they begin at 00:00:00h
|
||||
// if 'avg' has a value, move start backward ba 'avg' minutes
|
||||
//
|
||||
// params:
|
||||
// opts: Object with different options, specially
|
||||
// datetime, avg, daystart, span
|
||||
//
|
||||
// return:
|
||||
// Object with start and stop in ISO-Format
|
||||
// *********************************************
|
||||
export const calcRange = (opts) => {
|
||||
let start, end
|
||||
let ret = { start: start, stop: end, err: null}
|
||||
if((opts.datetime === null) || (opts.datetime === undefined) || (opts.datetime === '')) {
|
||||
start = end = DateTime.local().toUTC()
|
||||
start = start.minus({days: opts.span})
|
||||
} else {
|
||||
start = end = DateTime.fromISO(opts.datetime, {zone: 'utc'})
|
||||
end = end.plus({days: opts.span})
|
||||
}
|
||||
if(opts.daystart) {
|
||||
start = start.startOf("day")
|
||||
end = end.startOf("day")
|
||||
}
|
||||
if(opts.avg !== undefined) {
|
||||
start = start.minus({minutes: opts.avg})
|
||||
}
|
||||
ret.start = `start: ${start.toISO()}`
|
||||
ret.stop = `stop: ${end.toISO()}`
|
||||
return ret
|
||||
}
|
||||
|
||||
|
||||
// *********************************************
|
||||
// getSensorData
|
||||
//
|
||||
// Depending on the parameter 'sensorid', ditribute to the speciell routines for
|
||||
// the sensors
|
||||
//
|
||||
// params:
|
||||
// params: all parameters from the url
|
||||
//
|
||||
// return:
|
||||
// Retirns from the special routines
|
||||
// *********************************************
|
||||
export const getSensorData = async (params) => {
|
||||
let ret = {err: null}
|
||||
let {opts, err} = checkParams(params, { // check for sensorid
|
||||
mandatory: [{name: 'sensorid', type: 'int'}],
|
||||
optional: []
|
||||
})
|
||||
if (err) {
|
||||
return returnOnError(ret, err, getSensordata.name)
|
||||
}
|
||||
// with the sensorid get the type of this sensor
|
||||
let {props, err1} = await getOneProperty({sensorid: opts.sensorid})
|
||||
if (err1) {
|
||||
return returnOnError(ret, err1, getSensorData.name)
|
||||
}
|
||||
|
||||
// distribute to the right routine
|
||||
for(let item of sensorTypeTable) {
|
||||
if(item.typ === props.type) {
|
||||
ret = item.func(params, item.possibleParams, props) // get the values from database
|
||||
return ret
|
||||
}
|
||||
}
|
||||
return returnOnError(ret, ERR.CMNDUNKOWN, getActData.name)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export const getActData = async (opts) => {
|
||||
let ret = {data: {count: 0, values: []}, err: null}
|
||||
let sorting = ''
|
||||
if(opts.sort) {
|
||||
if (opts.sort === 1) {
|
||||
sorting = '|> sort(columns: ["_time"], desc: false)'
|
||||
} else if (opts.sort === -1) {
|
||||
sorting = '|> sort(columns: ["_time"], desc: true)'
|
||||
}
|
||||
}
|
||||
// build the flux query
|
||||
let query = `
|
||||
from(bucket: "sensor_data")
|
||||
|> range(${opts.start}, ${opts.stop})
|
||||
|> filter(fn: (r) => r.sid == "${opts.sensorid}")
|
||||
${sorting}
|
||||
|> keep(columns: ["_time","_field","_value"])
|
||||
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
||||
`
|
||||
return await fetchFromInflux(ret, query)
|
||||
}
|
||||
|
||||
|
||||
// ..../api/getavgdata?sensorid=123&span=2&avg=10&moving=true&datetime=2022-04-12T13:14:15Z
|
||||
export const getAvgData = async (params) => {
|
||||
let ret = {data: {count: 0, values: []}, err: null}
|
||||
let {opts, err} = checkParams(params,
|
||||
{
|
||||
mandatory:
|
||||
[
|
||||
{name: 'sensorid', type: 'int'},
|
||||
],
|
||||
optional:
|
||||
[
|
||||
{name: 'span', type: 'int', default: 1},
|
||||
{name: 'datetime', type: 'date', default: null},
|
||||
{name: 'avg', type: 'int', default: 10},
|
||||
{name: 'moving', type: 'bool', default: true},
|
||||
]
|
||||
}
|
||||
)
|
||||
if (err) {
|
||||
return returnOnError(ret, err, getActdata.name)
|
||||
}
|
||||
let {start, stop} = calcRange(opts)
|
||||
if (stop === '') {
|
||||
ret.data.start = DateTime.now().toUTC().minus({days: `${opts.span}`}).toFormat("yyyy-LL-dd'T'HH:mm:ss'Z'")
|
||||
} else {
|
||||
ret.data.start = DateTime.fromISO(opts.datetime, {zone: 'utc'}).toISO()
|
||||
}
|
||||
ret.data.span = opts.span
|
||||
ret.data.moving = opts.moving
|
||||
ret.data.avg = opts.avg
|
||||
|
||||
let every = opts.moving ? '150s' : `${opts.avg}m`
|
||||
let period = `${opts.avg}m`
|
||||
let query = `
|
||||
from(bucket: "sensor_data")
|
||||
|> range(${start}, ${stop})
|
||||
|> filter(fn: (r) => r.sid == "${opts.sensorid}")
|
||||
|> timedMovingAverage(every: ${every}, period: ${period})
|
||||
|> keep(columns: ["_time","_field","_value"])
|
||||
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
||||
`
|
||||
return await fetchFromInflux(ret, query)
|
||||
}
|
||||
|
||||
// ..../api/getlongavg?sensorid=123&span=2
|
||||
export const getLongAvg = async (params) => {
|
||||
let ret = {data: {count: 0, values: []}, err: null}
|
||||
let {opts, err} = checkParams(params,
|
||||
{
|
||||
mandatory:
|
||||
[
|
||||
{name: 'sensorid', type: 'int'},
|
||||
],
|
||||
optional:
|
||||
[
|
||||
{name: 'span', type: 'int', default: 2},
|
||||
]
|
||||
}
|
||||
)
|
||||
if (err) {
|
||||
return returnOnError(ret, err, getActdata.name)
|
||||
}
|
||||
ret.data.span = opts.span
|
||||
let query = `
|
||||
from(bucket: "sensor_data")
|
||||
|> range(start: -${opts.span}d)
|
||||
|> filter(fn: (r) => r.sid == "${opts.sensorid}")
|
||||
|> mean()
|
||||
|> drop(columns: ["_start","_measurement", "sid"])
|
||||
|> pivot(rowKey:["_stop"], columnKey: ["_field"], valueColumn: "_value")
|
||||
`
|
||||
return fetchFromInflux(ret, query)
|
||||
}
|
||||
|
||||
|
||||
export const fetchFromInflux = async (data, query) => {
|
||||
let ret = {err: null}
|
||||
let { values, err} = await influx.influxRead(query)
|
||||
if(err) {
|
||||
if(err.toString().includes('400')) {
|
||||
return returnOnError(ret, ERR.SYNTAXURL, fetchFromInflux.name)
|
||||
} else {
|
||||
return returnOnError(ret, err, fetchFromInflux.name)
|
||||
}
|
||||
}
|
||||
if (values.length === 0) {
|
||||
return returnOnError(data, ERR.NODATA, fetchFromInflux.name)
|
||||
}
|
||||
ret.values = csv2Json(values)
|
||||
return ret
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import createError from 'http-errors'
|
||||
import logger from 'morgan'
|
||||
import express from 'express'
|
||||
import cookieParser from 'cookie-parser'
|
||||
import cors from 'cors'
|
||||
|
||||
import indexRouter from './routes/index.js'
|
||||
import apiRouter from './routes/index.js'
|
||||
|
||||
const app = express()
|
||||
|
||||
app.use(cors())
|
||||
app.use(logger('dev'))
|
||||
app.use(express.json())
|
||||
app.use(express.urlencoded({ extended: true }))
|
||||
app.use(cookieParser())
|
||||
|
||||
app.use('/api', apiRouter)
|
||||
app.use('/', indexRouter)
|
||||
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function(req, res, next) {
|
||||
next(createError(404))
|
||||
})
|
||||
|
||||
// error handler
|
||||
app.use(function(err, req, res, next) {
|
||||
// set locals, only providing error in development
|
||||
res.locals.message = err.message
|
||||
res.locals.error = req.app.get('env') === 'development' ? err : {}
|
||||
|
||||
// render the error page
|
||||
res.status(err.status || 500)
|
||||
res.send(`ERROR: ${err.status}, ${err.stack}`)
|
||||
})
|
||||
|
||||
export default app
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
import app from '../app.js'
|
||||
import debug from 'debug'
|
||||
import http from 'http'
|
||||
|
||||
// const debug = 'untitled1:server'
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
const port = normalizePort(process.env.PORT || '3000');
|
||||
app.set('port', port);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
|
||||
const server = http.createServer(app);
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
|
||||
server.listen(port);
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
|
||||
/**
|
||||
* Normalize a port into a number, string, or false.
|
||||
*/
|
||||
|
||||
function normalizePort(val) {
|
||||
const port = parseInt(val, 10);
|
||||
|
||||
if (isNaN(port)) {
|
||||
// named pipe
|
||||
return val;
|
||||
}
|
||||
|
||||
if (port >= 0) {
|
||||
// port number
|
||||
return port;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
*/
|
||||
|
||||
function onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const bind = typeof port === 'string'
|
||||
? 'Pipe ' + port
|
||||
: 'Port ' + port;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(bind + ' requires elevated privileges');
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error(bind + ' is already in use');
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "listening" event.
|
||||
*/
|
||||
|
||||
function onListening() {
|
||||
const addr = server.address();
|
||||
const bind = typeof addr === 'string'
|
||||
? 'pipe ' + addr
|
||||
: 'port ' + addr.port;
|
||||
debug('Listening on ' + bind);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
# Build Docker-Container
|
||||
#
|
||||
# Call: buildit.sh name [target]
|
||||
#
|
||||
# The Dockerfile must be named like Dockerfile_name
|
||||
#
|
||||
# 2018-09-20 rxf
|
||||
# - before sending docker image to remote, tag actual remote image
|
||||
#
|
||||
# 2018-09-14 rxf
|
||||
# - first Version
|
||||
#
|
||||
|
||||
set -x
|
||||
port=""
|
||||
orgName=sensorapi
|
||||
name=sensorapi
|
||||
|
||||
usage()
|
||||
{
|
||||
echo "Usage build_and_copy.sh [-p port] [-n name] target"
|
||||
echo " Build docker container $name and copy to target"
|
||||
echo "Params:"
|
||||
echo " target: Where to copy the container to "
|
||||
echo " -p port: ssh port (default 22)"
|
||||
echo " -n name: new name for container (default: $orgName)"
|
||||
}
|
||||
|
||||
while getopts n:p:h? o
|
||||
do
|
||||
case "$o" in
|
||||
n) name="$OPTARG";;
|
||||
p) port="-p $OPTARG";;
|
||||
h) usage; exit 0;;
|
||||
*) usage; exit 1;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND-1))
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
if [[ -z "$target" ]]; then
|
||||
target=$1
|
||||
shift
|
||||
else
|
||||
echo "bad option $1"
|
||||
# exit 1
|
||||
shift
|
||||
fi
|
||||
done
|
||||
|
||||
docker build -f Dockerfile_$orgName -t $name .
|
||||
|
||||
dat=`date +%Y%m%d%H%M`
|
||||
|
||||
if [ "$target" == "localhost" ]
|
||||
then
|
||||
docker tag $name $name:V_$dat
|
||||
exit
|
||||
fi
|
||||
|
||||
ssh $port $target "docker tag $name $name:V_$dat"
|
||||
docker save $name | bzip2 | pv | ssh $port $target 'bunzip2 | docker load'
|
||||
@@ -0,0 +1,71 @@
|
||||
// Access to influxDB vie HTTP
|
||||
|
||||
import axios from 'axios'
|
||||
import { DateTime } from 'luxon'
|
||||
// import csvParse from 'csv-parser'
|
||||
import { logit, logerror } from '../utilities/logit.js'
|
||||
import {returnOnError} from "../utilities/reporterror.js";
|
||||
|
||||
let INFLUXHOST = process.env.INFLUXHOST || "localhost"
|
||||
let INFLUXPORT = process.env.INFLUXPORT || 8086
|
||||
let INFLUXTOKEN = process.env.INFLUXTOKEN ||
|
||||
//"rklEClT22KfdXZhA47eyJhbqcvekb8bcKCqlUG7n72uDSmR2xGvif0CmGJe0WQtXB96y29mmt-9BdsgWA5npfg=="
|
||||
"BNR6cGdb006O1T6hQkGcfB8tgH-UPO6QkOPToeAvrP7LATJbCuWi1wYf3HBpVdZQEBxHxNSrNenZsOSMogX-lg=="
|
||||
let INFLUXDATABUCKET = process.env.INFLUXDATABUCKET || "sensor_data"
|
||||
let INFLUXORG = process.env.INFLUXORG || "citysensor"
|
||||
|
||||
const INFLUXURL_READ = `http://${INFLUXHOST}:${INFLUXPORT}/api/v2/query?org=${INFLUXORG}`
|
||||
const INFLUXURL_WRITE = `http://${INFLUXHOST}:${INFLUXPORT}/api/v2/write?org=${INFLUXORG}&bucket=${INFLUXDATABUCKET}`
|
||||
|
||||
export const influxRead = async (query) => {
|
||||
let start = DateTime.now()
|
||||
let erg = { values: [], err: null}
|
||||
try {
|
||||
let ret = await axios({
|
||||
method: 'post',
|
||||
url: INFLUXURL_READ,
|
||||
data: query,
|
||||
headers: {
|
||||
Authorization: `Token ${INFLUXTOKEN}`,
|
||||
Accept: 'application/csv',
|
||||
'Content-type': 'application/vnd.flux'
|
||||
},
|
||||
timeout: 10000,
|
||||
})
|
||||
if (ret.status !== 200) {
|
||||
let error = ERR.RESPSTATUS.replace('xx',ret.status)
|
||||
return returnOnError(erg, error, influxRead.name)
|
||||
}
|
||||
erg.values = ret.data
|
||||
} catch (e) {
|
||||
return returnOnError(erg, e, influxRead.name)
|
||||
}
|
||||
logit(`Influx read time: ${start.diffNow('seconds').toObject().seconds * -1} sec`)
|
||||
return erg
|
||||
}
|
||||
|
||||
|
||||
export const influxWrite = async (data) => {
|
||||
let start = DateTime.now()
|
||||
let ret
|
||||
try {
|
||||
ret = await axios({
|
||||
method: 'post',
|
||||
url: INFLUXURL_WRITE,
|
||||
data: data,
|
||||
headers: {
|
||||
Authorization: `Token ${INFLUXTOKEN}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'text/plain; charset=utf-8'
|
||||
},
|
||||
timeout: 10000,
|
||||
})
|
||||
if (ret.status !== 204) {
|
||||
logerror(`doWrite2API Status: ${ret.status}`)
|
||||
}
|
||||
} catch (e) {
|
||||
logerror(`doWrite2API ${e}`)
|
||||
}
|
||||
logit(`Influx-Write-Time: ${start.diffNow('seconds').toObject().seconds * -1} sec`)
|
||||
return ret
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
/* Interface for MongoDB
|
||||
*/
|
||||
import { MongoClient } from 'mongodb'
|
||||
import { logit, logerror } from '../utilities/logit.js'
|
||||
import { DateTime } from 'luxon'
|
||||
import {returnOnError} from "../utilities/reporterror.js";
|
||||
|
||||
// const nodemailer = require('nodemailer');
|
||||
|
||||
let MONGOHOST = process.env.MONGOHOST;
|
||||
let MONGOPORT = process.env.MONGOPORT;
|
||||
let MONGOAUTH = process.env.MONGOAUTH;
|
||||
let MONGOUSRP = process.env.MONGOUSRP;
|
||||
let MONGOBASE = process.env.MONGOBASE;
|
||||
|
||||
if (MONGOHOST === undefined) { MONGOHOST = 'localhost';}
|
||||
if (MONGOPORT === undefined) { MONGOPORT = 27097; }
|
||||
if (MONGOAUTH === undefined) { MONGOAUTH = 'false'; }
|
||||
if (MONGOBASE === undefined) { MONGOBASE = 'allsensors'; }
|
||||
|
||||
let MONGO_URL = 'mongodb://'+MONGOHOST+':'+MONGOPORT; // URL to mongo database
|
||||
if (MONGOAUTH === 'true') {
|
||||
MONGO_URL = 'mongodb://'+MONGOUSRP+'@' + MONGOHOST + ':' + MONGOPORT + '/?authSource=' + MONGOBASE; // URL to mongo database
|
||||
}
|
||||
|
||||
let statistics = {}
|
||||
let client
|
||||
|
||||
export const connectMongo = async () => {
|
||||
try {
|
||||
logit(`Try to connect to ${MONGO_URL}`)
|
||||
// let client = await MongoClient.connect(MONGO_URL, { useNewUrlParser: true , useUnifiedTopology: true })
|
||||
let client = await MongoClient.connect(MONGO_URL)
|
||||
logit(`Mongodbase connected to ${MONGO_URL}`)
|
||||
return client
|
||||
}
|
||||
catch(error){
|
||||
throw(error)
|
||||
}
|
||||
}
|
||||
|
||||
export const closeMongo = async (client) => {
|
||||
client.close()
|
||||
}
|
||||
|
||||
const listDatabases = async (client) => {
|
||||
let databasesList = await client.db().admin().listDatabases();
|
||||
|
||||
console.log("Databases:");
|
||||
databasesList.databases.forEach(db => console.log(` - ${db.name}`));
|
||||
}
|
||||
|
||||
/* ***************************************************
|
||||
// READ routines
|
||||
******************************************************/
|
||||
|
||||
// Read properties from the database
|
||||
export const readProperties = async (query, limit = 0) => {
|
||||
let ret = {err: null, values: null}
|
||||
client = await connectMongo()
|
||||
try {
|
||||
if ("sid" in query) { // if sid is given, read property for sid
|
||||
ret.values = await client.db(MONGOBASE).collection("properties").findOne({_id: query.sid})
|
||||
} else { // otherwise read props corresponding to query
|
||||
ret.values = await client.db(MONGOBASE).collection("properties").find(query).limit(limit).toArray()
|
||||
}
|
||||
} catch (e) {
|
||||
ret.err = e
|
||||
}
|
||||
finally {
|
||||
await closeMongo(client)
|
||||
client = null
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// read mapdata from database
|
||||
export const readMapdata = async (query, limit) => {
|
||||
let ret = {err: null, values: []}
|
||||
client = await connectMongo()
|
||||
try {
|
||||
ret.values = await client.db(MONGOBASE).collection("mapdata").find(query).limit(limit).toArray()
|
||||
} catch (e) {
|
||||
ret.err = e
|
||||
}
|
||||
finally {
|
||||
await closeMongo(client)
|
||||
client = null
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
|
||||
export const getallProperties = async (client) => {
|
||||
return await client.db(MONGOBASE).collection("properties")
|
||||
.find().toArray()
|
||||
|
||||
}
|
||||
|
||||
export const getOneproperty = async (sid) => {
|
||||
let ret = {error: false}
|
||||
client = await connectMongo()
|
||||
try {
|
||||
ret.properties = await client.db(MONGOBASE).collection("properties")
|
||||
.findOne({_id: sid})
|
||||
} catch (e) {
|
||||
ret = {error: true, errortext: e}
|
||||
}
|
||||
finally {
|
||||
await closeMongo(client)
|
||||
client = null
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
|
||||
export const doAggregate = async (coll, query, conn, clos) => {
|
||||
let ret = {error: false}
|
||||
if (conn) {
|
||||
client = await connectMongo()
|
||||
}
|
||||
try {
|
||||
ret.values = await client.db(MONGOBASE).collection(coll).aggregate(query).toArray()
|
||||
} catch (e) {
|
||||
ret = {error: true, errortext: e}
|
||||
}
|
||||
finally {
|
||||
if (clos) {
|
||||
await closeMongo(client)
|
||||
client = null
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
|
||||
export const writeOneproperty = async (client, prop) => {
|
||||
try {
|
||||
let result = await client.db(MONGOBASE).collection("properties")
|
||||
.insertOne(prop)
|
||||
} catch(e) {
|
||||
if (e.code === 11000) {
|
||||
return false
|
||||
} else {
|
||||
throw(e)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export const writeProperties = async (client, props) => {
|
||||
let start = DateTime.now();
|
||||
if (props.new.length !== 0) {
|
||||
try {
|
||||
let result = await client.db(MONGOBASE).collection("properties")
|
||||
.insertMany(props.new)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
if (props.loc.length !== 0) {
|
||||
try {
|
||||
for (let item of props.loc) {
|
||||
let result = await client.db(MONGOBASE).collection("properties")
|
||||
.updateOne({_id: item._id}, {
|
||||
$set: {location_id: item.location_id},
|
||||
$push: {location: { $each: [item.location[0]], $position: 0}}
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
if (props.sname.lenth !== 0) {
|
||||
try {
|
||||
for (let item of props.sname) {
|
||||
let result = await client.db(MONGOBASE).collection("properties")
|
||||
.updateOne({_id: item._id}, {
|
||||
$set: {name: item.name, since: item.since}
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
statistics.writePropsTime = DateTime.now().diff(start, ['seconds']).toObject().seconds
|
||||
logit(`Write properties to mongoDB: ${statistics.writePropsTime} sec.`)
|
||||
}
|
||||
|
||||
export const writeDataArray = async (data, collection, conn, clos) => {
|
||||
let start = DateTime.now();
|
||||
let result
|
||||
if (conn) {
|
||||
client = await connectMongo()
|
||||
}
|
||||
try {
|
||||
result = await client.db(MONGOBASE).collection(collection)
|
||||
.insertMany(data)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
if(clos) {
|
||||
await closeMongo(client)
|
||||
client = null
|
||||
}
|
||||
}
|
||||
statistics.writeDataTime = DateTime.now().diff(start, ['seconds']).toObject().seconds
|
||||
logit(`Write Data to mongoDB: ${statistics.writeDataTime} sec.`)
|
||||
return result
|
||||
}
|
||||
|
||||
export const writeStatistic = async (client, stat) => {
|
||||
let start = DateTime.now();
|
||||
let entry = { timestamp: new Date(), ...stat }
|
||||
try {
|
||||
let result = await client.db(MONGOBASE).collection("statistics")
|
||||
.insertOne(entry)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
statistics.writeStatisticTime = DateTime.now().diff(start, ['seconds']).toObject().seconds
|
||||
logit(`Write statistics to mongoDB: ${statistics.writeStatisticTime} sec.`)
|
||||
}
|
||||
|
||||
export const readAKWs = async (options) => {
|
||||
let ret = {values: { akws: [], th1_akws: []}, err: null}
|
||||
let erg = []
|
||||
client = await connectMongo()
|
||||
try {
|
||||
let docs = await client.db(MONGOBASE).collection("akws")
|
||||
.find().toArray()
|
||||
if(docs == null) {
|
||||
return returnOnError(ret, 'akws - docs == null', readAKWs.name)
|
||||
}
|
||||
logit(`getawkdata: data fetched from akws, length= ${docs.length}`);
|
||||
ret.values.akws = docs
|
||||
let docs1 = await client.db(MONGOBASE).collection("th1_akws")
|
||||
.find().toArray()
|
||||
if(docs1 == null) {
|
||||
return returnOnError(ret, 'th1_akws - docs == null', readAKWs.name)
|
||||
}
|
||||
logit(`getawkdata: data fetched from th1_akws, length= ${docs1.length}`)
|
||||
ret.values.th1_akws = docs1
|
||||
} catch (e) {
|
||||
return returnOnError(ret, e, readAKWs.name)
|
||||
}
|
||||
finally {
|
||||
await closeMongo(client)
|
||||
client = null
|
||||
}
|
||||
return ret
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 570 KiB |
@@ -0,0 +1,23 @@
|
||||
##Planung, Ideen und Entscheidungen
|
||||
|
||||
###Websocket
|
||||
Da Websockets nur dann Vorteile bringen, wenn der Server von sich aus was 'zu sagen' hat, wird in diesem Projekt auf
|
||||
Websockets verzichtet und der Verkher zwischen der API und dem GUI über normale HTTP-Calls ausgeführt.
|
||||
|
||||
###Planung
|
||||
.
|
||||
####Ergänzungen zur Grafik (2022-05-26)
|
||||
* Für die 3 GUIs je ein extra Container, so wie jetzt auch (Feinstaub, Geiger, Laerm)
|
||||
* ab sofort (d.h. möglichst bald) die **influx-** und die **mongo-**Databases parallel laufen lassen
|
||||
* nach ca. 1 Monat **nur noch mongo** verwenden
|
||||
*
|
||||
|
||||
|
||||
|
||||
|
||||
####Versions-History
|
||||
|
||||
|Version | Datum | Author
|
||||
|--------|------|-------
|
||||
|1.0 | 2022-05-27 | rxf
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
node:
|
||||
image: sensorapi
|
||||
environment:
|
||||
DEVELOP: "true"
|
||||
MONGOHOST: ${MONGOHOST}
|
||||
MONGOPORT: ${MONGOPORT}
|
||||
INFLUXHOST: ${INFLUXHOST}
|
||||
INFLUXTOKEN: ${INFLUXTOKEN}
|
||||
LIVE: "true"
|
||||
ports:
|
||||
- "3031:3000"
|
||||
volumes:
|
||||
- /var/log/sensorapi:/var/log
|
||||
container_name: node_sensorapi
|
||||
restart: unless-stopped
|
||||
@@ -0,0 +1,74 @@
|
||||
// Gibt für eine Property-Anfrage immer den 140er Sensor zurück
|
||||
// rxf 2022-05-25
|
||||
|
||||
export const readProperties = async (query, limit = 0) => {
|
||||
let ret = {error: false}
|
||||
if ("sid" in query) { // if sid is given, read property for sid
|
||||
ret.values = {
|
||||
"_id" : 140,
|
||||
"location_id" : 65,
|
||||
"name" : "SDS011",
|
||||
"since" : "2021-04-21T09:44:12.888Z",
|
||||
"location" : [
|
||||
{
|
||||
"loc" :
|
||||
{
|
||||
"type" : "Point",
|
||||
"coordinates" : [ 9.16, 48.778 ]
|
||||
},
|
||||
"id" : 65,
|
||||
"altitude" : 282,
|
||||
"since" : "2021-04-21T09:44:12.888Z",
|
||||
"address" : { },
|
||||
"exact_loc" : 0,
|
||||
"indoor" : 0
|
||||
}
|
||||
]
|
||||
}
|
||||
} else { // otherwise read props corresponding to query
|
||||
ret.values = [
|
||||
{
|
||||
"_id" : 140,
|
||||
"location_id" : 65,
|
||||
"name" : "SDS011",
|
||||
"since" : "2021-04-21T09:44:12.888Z",
|
||||
"location" : [
|
||||
{
|
||||
"loc" :
|
||||
{
|
||||
"type" : "Point",
|
||||
"coordinates" : [ 9.16, 48.778 ]
|
||||
},
|
||||
"id" : 65,
|
||||
"altitude" : 282,
|
||||
"since" : "2021-04-21T09:44:12.888Z",
|
||||
"address" : { },
|
||||
"exact_loc" : 0,
|
||||
"indoor" : 0
|
||||
}
|
||||
]
|
||||
},{
|
||||
"_id" : 141,
|
||||
"location_id" : 65,
|
||||
"name" : "BME280",
|
||||
"since" : "2021-04-21T09:44:12.888Z",
|
||||
"location" : [
|
||||
{
|
||||
"loc" :
|
||||
{
|
||||
"type" : "Point",
|
||||
"coordinates" : [ 9.16, 48.778 ]
|
||||
},
|
||||
"id" : 65,
|
||||
"altitude" : 282,
|
||||
"since" : "2021-04-21T09:44:12.888Z",
|
||||
"address" : { },
|
||||
"exact_loc" : 0,
|
||||
"indoor" : 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
Generated
+3698
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "sensorapi",
|
||||
"version": "1.0.3",
|
||||
"date": "0222-06-06",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node ./bin/www.js",
|
||||
"test": "mocha ./test/test.js"
|
||||
},
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"www": "./bin/www.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@influxdata/influxdb-client": "^1.24.0",
|
||||
"@influxdata/influxdb-client-apis": "^1.24.0",
|
||||
"axios": "^0.26.1",
|
||||
"cookie-parser": "~1.4.4",
|
||||
"cors": "^2.8.5",
|
||||
"debug": "~2.6.9",
|
||||
"express": "~4.16.1",
|
||||
"http-errors": "~1.6.3",
|
||||
"luxon": "^2.3.1",
|
||||
"mongodb": "^4.4.1",
|
||||
"morgan": "~1.9.1",
|
||||
"pug": "^3.0.2",
|
||||
"ws": "^8.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "^9.2.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import express from 'express'
|
||||
|
||||
import {getData4map} from "../actions/data4map.js"
|
||||
import cmdTable from "../utilities/commands.js"
|
||||
import * as ERR from "../utilities/errortexts.js"
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', function(req, res, next) {
|
||||
res.status(200).json({message: ERR.NOTHIMG})
|
||||
});
|
||||
|
||||
// normal routes called from javascript client
|
||||
router.get('/:cmd', async (req, res) => {
|
||||
const cmd = req.params.cmd
|
||||
for (let c of cmdTable) {
|
||||
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})
|
||||
})
|
||||
|
||||
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
|
||||
@@ -0,0 +1,519 @@
|
||||
// Data preparation for fetching noise data
|
||||
// rxf 2023-03-05
|
||||
|
||||
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'
|
||||
|
||||
const setoptionfromtable = (opt,tabval) => {
|
||||
let ret = opt
|
||||
if ((opt === null) || (opt === '')) {
|
||||
ret = tabval
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
|
||||
export const getNoiseData = async (params, possibles, props) => {
|
||||
let ret = {err: null}
|
||||
|
||||
let {opts, err} = checkParams(params, {
|
||||
mandatory:[
|
||||
{name:'sensorid', type: 'int'},
|
||||
],
|
||||
optional: possibles
|
||||
})
|
||||
// To be compatible with old API:
|
||||
if (opts.out === 'csv') {
|
||||
opts.csv = true
|
||||
}
|
||||
if (err) {
|
||||
return returnOnError(ret, err, getNoiseData.name)
|
||||
}
|
||||
// execute function depending on given 'data'
|
||||
for(let x of whatTable) {
|
||||
if (x.what === opts.data) {
|
||||
opts.span = setoptionfromtable(opts.span, x.span)
|
||||
opts.daystart = setoptionfromtable(opts.daystart, x.daystart)
|
||||
let {start, stop} = calcRange(opts) // calc time range
|
||||
opts.start = start
|
||||
opts.stop = stop
|
||||
let erg = await x.func(opts) // get the data
|
||||
ret.sid = opts.sensorid
|
||||
ret.start = opts.start.slice(7)
|
||||
ret.span = opts.span
|
||||
if ((x.what === 'havg') || (x.what === 'davg')) {
|
||||
ret.average = x.avg
|
||||
ret.peak = opts.peak
|
||||
}
|
||||
ret.count = erg.length
|
||||
ret.values = erg
|
||||
return ret
|
||||
}
|
||||
}
|
||||
return returnOnError(ret, ERR.CMNDUNKOWN, getNoiseData.name)
|
||||
}
|
||||
|
||||
|
||||
// *********************************************
|
||||
// getLiveData
|
||||
//
|
||||
// Get all actual data from database. Values are stored every 2.5min
|
||||
//
|
||||
// params:
|
||||
// db: Database
|
||||
// opt: different options (see further down)
|
||||
//
|
||||
// return:
|
||||
// JSON:
|
||||
// { sid: 29212, span: 1, start: "2019-10-23T00:00", count: 381, values: [
|
||||
// { datetime: "2019-10-22T22:05:34.000Z", noise_LAeq: 42.22, noise_LA_min: 39.91, noise_LA_max: 45.18, E10tel_eq: 16672.47212551061 },
|
||||
// { datetime: "2019-10-22T22:07:59.000Z", noise_LAeq: 53.72, noise_LA_min: 39.97, noise_LA_max: 63.54, E10tel_eq: 235504.9283896009 },
|
||||
// .........
|
||||
// ]}
|
||||
// CSV
|
||||
// datetime,LAeq,LAmax,LAmin,"10^(LAeq/10)"
|
||||
// 2019-10-22T22:05:34.000Z,42.22,45.18,39.91,16672.47212551061
|
||||
// 2019-10-22T22:07:59.000Z,53.72,63.54,39.97,235504.9283896009
|
||||
// 2019-10-22T22:15:16.000Z,44.02,48.99,42.14,25234.807724805756
|
||||
// ....
|
||||
//
|
||||
// *********************************************
|
||||
const getLiveData = async (opt) => {
|
||||
let retur = {sid: opt.sensorid, span: opt.span, start: opt.datetime, count: 0, values: [], err: null};
|
||||
return await getActData(opt)
|
||||
}
|
||||
|
||||
|
||||
// *********************************************
|
||||
// gethavgData
|
||||
//
|
||||
// Get average per hour, default: 5 days
|
||||
//
|
||||
// params:
|
||||
// db: Database
|
||||
// opt: different options (see further down)
|
||||
//
|
||||
// return:
|
||||
// JSON:
|
||||
// { sid: 29212, span: 5, start: "2019-11-01T23:00:00Z", average: 'hour', peak: 70, count: 120, values: [
|
||||
// { datetime: "2019-10-22T23:00:00.000Z", n_AVG: 58.27, peakcount: 3 },
|
||||
// { datetime: "2019-10-23T00:00:00.000Z", n_AVG: 45.77, peakcount: 4 },
|
||||
// { datetime: "2019-10-23T01:00:00.000Z", n_AVG: 62.34, peakcount: 6 },
|
||||
// .........
|
||||
// ]}
|
||||
|
||||
// CSV:
|
||||
// datetime,n_AVG,peakcount
|
||||
// 2019-10-22T23:00:00.000Z,58.27,3
|
||||
// 2019-10-23T00:00:00.000Z,45.77,4
|
||||
// 2019-10-23T01:00:00.000Z,62.34,6
|
||||
// ....
|
||||
//
|
||||
// *********************************************
|
||||
const gethavgData = async (opts) => {
|
||||
|
||||
let e = await getNoiseAVGData(opts)
|
||||
if (opts.csv) {
|
||||
let csvStr = "datetime,n_AVG,peakcount\n";
|
||||
for (let item of e) {
|
||||
if (item.n_AVG != -1) {
|
||||
csvStr += item.datetime + ',' + item.n_AVG + ',' + item.peakcount + '\n'
|
||||
}
|
||||
}
|
||||
return csvStr;
|
||||
} else {
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// *********************************************
|
||||
// getdavgData
|
||||
//
|
||||
// Get average per day , default: 30 days
|
||||
//
|
||||
// params:
|
||||
// db: Database
|
||||
// opt: different options (see further down)
|
||||
//
|
||||
// return:
|
||||
// JSON:
|
||||
// { sid: 29212, span: 30, start: "2019-10-23T00:00", average: 'day', peak: 70, count: 30, values: [
|
||||
// { datetime: "2019-10-22T23:00:00.000Z", n_AVG: 58.27, peakcount: 300 },
|
||||
// { datetime: "2019-10-23T23:00:00.000Z", n_AVG: 62.34, peakcount: 245 },
|
||||
// .........
|
||||
// ]}
|
||||
//
|
||||
// CSV:
|
||||
// datetime,n_AVG,peakcount
|
||||
// 2019-10-22T23:00:00.000Z,58.27,300
|
||||
// 2019-10-23T23:00:00.000Z,62.34,245
|
||||
// ....
|
||||
//
|
||||
// *********************************************
|
||||
async function getdavgData(opts) {
|
||||
opts.long = true;
|
||||
let erg = await getNoiseAVGData(opts);
|
||||
let val = [];
|
||||
let csvStr = 'datetime,n_AVG,peakcount\n';
|
||||
for (let i = 0; i < erg.length; i+=24) {
|
||||
let sum = 0;
|
||||
let count = 0;
|
||||
let pk = 0;
|
||||
let werte = {};
|
||||
for(let k=0; k<24; k++) {
|
||||
if (( erg[i+k] != null) && (erg[i+k].n_sum != -1)) {
|
||||
sum += erg[i + k].n_sum;
|
||||
count += erg[i + k].count;
|
||||
pk += erg[i + k].peakcount;
|
||||
if (werte.datetime === undefined) {
|
||||
let dt = DateTime.fromISO(erg[i + k].datetime, {zone: 'utc'})
|
||||
werte.datetime = dt.startOf('day').toISO()
|
||||
}
|
||||
}
|
||||
}
|
||||
werte.n_AVG = 10 * Math.log10( sum/count);
|
||||
werte.peakcount = pk;
|
||||
if (opts.csv) {
|
||||
csvStr += werte.datetime + ',' + werte.n_AVG + ',' + werte.peakcount + '\n'
|
||||
} else {
|
||||
val.push(werte);
|
||||
}
|
||||
}
|
||||
if (opts.csv) {
|
||||
return csvStr;
|
||||
} else {
|
||||
return val
|
||||
// {
|
||||
// sid: opts.sensorid,
|
||||
// span: opts.span,
|
||||
// start: opts.start.slice(7),
|
||||
// 'average': 'day',
|
||||
// peak: opts.peak,
|
||||
// count: val.length,
|
||||
// values: val
|
||||
// };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// *********************************************
|
||||
// getdaynightData
|
||||
//
|
||||
// Get average for day (6h00 - 22h00) and night (22h00 - 6h00) separated
|
||||
// Use the hour average calculation, which brings the sum and the count for every hour
|
||||
// then add these values up for the desired time range and calculate the average.
|
||||
//
|
||||
// The night-value of the last day is always 0, because the night is not complete (day is
|
||||
// over at 24:00 and the night lasts til 6:00)
|
||||
//
|
||||
// params:
|
||||
// db: Database
|
||||
// opt: different options (see further down)
|
||||
//
|
||||
// return
|
||||
// JSON
|
||||
// { sid: 29212, span: 30, start: "2019-09-29", count: 30, values: [
|
||||
// { date: "2019-09-29", n_dayAVG: 49.45592437272605, n_nightAVG: 53.744277577490614 },
|
||||
// { date: "2019-09-30", n_dayAVG: 51.658169450663465, n_nightAVG: 47.82407695888631 },
|
||||
// .........
|
||||
// ]}
|
||||
// CSV
|
||||
// datetime,n_dayAVG,n_nightAVG
|
||||
// 2019-09-29,49.45592437272605,53.744277577490614
|
||||
// 2019-09-30,51.658169450663465,47.82407695888631
|
||||
// ....
|
||||
//
|
||||
// *********************************************
|
||||
async function getdaynightData(opts) {
|
||||
opts.long = true;
|
||||
let erg = await getNoiseAVGData(opts);
|
||||
let val = [];
|
||||
let csvStr = 'datetime,n_dayAVG,n_nightAVG\n';
|
||||
let done = false;
|
||||
let dt;
|
||||
// The received houerly data array always (!!) starts at 0h00 local (!) time.
|
||||
// So to calculate day values, we skip the first 6 hour and start from there
|
||||
// now we add 16 hour for day and following 8 hour for night
|
||||
for (let i = 6; i < erg.length;) {
|
||||
let dsum = 0, dcnt = 0;
|
||||
let nsum = 0, ncnt = 0;
|
||||
let werte = {};
|
||||
for (let k = 0; k < 16; k++, i++) {
|
||||
if(erg[i].n_sum != -1) {
|
||||
if (werte.datetime === undefined) {
|
||||
let dt = DateTime.fromISO(erg[i].datetime, {zone: 'utc'})
|
||||
werte.datetime = dt.startOf('day').toISO()
|
||||
}
|
||||
dsum += erg[i].n_sum;
|
||||
dcnt += erg[i].count;
|
||||
}
|
||||
}
|
||||
if (i < (erg.length - 8)) {
|
||||
for (let k = 0; k < 8; k++, i++)
|
||||
{
|
||||
if(erg[i].n_sum != -1) {
|
||||
if (werte.datetime === undefined) {
|
||||
let dt = DateTime.fromISO(erg[i].datetime, {zone: 'utc'})
|
||||
werte.datetime = dt.startOf('day').toISO()
|
||||
}
|
||||
nsum += erg[i].n_sum;
|
||||
ncnt += erg[i].count;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
done = true;
|
||||
}
|
||||
if (dcnt != 0) {
|
||||
werte.n_dayAVG = 10 * Math.log10(dsum / dcnt);
|
||||
} else {
|
||||
werte.n_dayAVG = 0;
|
||||
}
|
||||
if (ncnt != 0) {
|
||||
werte.n_nightAVG = 10 * Math.log10(nsum / ncnt);
|
||||
} else {
|
||||
werte.n_nightAVG = 0;
|
||||
}
|
||||
if (opts.csv) {
|
||||
csvStr += werte.datetime + ',' + werte.n_dayAVG + ',' + werte.n_nightAVG + '\n'
|
||||
} else {
|
||||
val.push(werte);
|
||||
}
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (opts.csv) {
|
||||
return csvStr;
|
||||
} else {
|
||||
return val
|
||||
// {
|
||||
// sid: opts.sensorid,
|
||||
// span: opts.span,
|
||||
// start: opts.start.slice(7),
|
||||
// count: val.length,
|
||||
// values: val
|
||||
// };
|
||||
} }
|
||||
|
||||
// *********************************************
|
||||
// getLdenData
|
||||
//
|
||||
// Use hour averages to calculate the LDEN.
|
||||
// Formula:
|
||||
// LDEN = 10 * log10 ( 1/24 ( (12 * 10^(Lday/10)) + (4*10^((Levn+5)/10) + (8*10^((Lnight+10)/10)) )
|
||||
//
|
||||
// params:
|
||||
// db: Database
|
||||
// sid: sensor number
|
||||
// opt: different options (see further down)
|
||||
//
|
||||
// return:
|
||||
// JSON:
|
||||
// { sid: 29212, span: 30, start: "2019-09-29", count: 30, values: [
|
||||
// { lden: 59.53553743437777, date: "2019-09-29" },
|
||||
// { lden: 55.264733497513554, date: "2019-09-30" },
|
||||
// .........
|
||||
// ]}
|
||||
// CSV
|
||||
// datetime,lden
|
||||
// 2019-09-29,59.53553743437777
|
||||
// 2019-09-30,55.264733497513554
|
||||
// ....
|
||||
//
|
||||
// *********************************************
|
||||
async function getLdenData(opts) {
|
||||
opts.long = true;
|
||||
let erg = await getNoiseAVGData(opts);
|
||||
let val = [];
|
||||
let csvStr = 'datetime,lden\n';
|
||||
let done = false;
|
||||
const calcAVG = (sum,cnt) => {
|
||||
if (cnt != 0) {
|
||||
return (10 * Math.log10(sum / cnt));
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// The received hourly data array always (!!) starts at 0h00 local (!) time.
|
||||
// So to calculate day values, we skip the first 6 hour and start from there
|
||||
// now we add 12 hour for day and following 4 hour for evening and
|
||||
// additional 8 hours for night
|
||||
for (let i = 6; i < erg.length;) {
|
||||
let dsum = 0, dcnt = 0;
|
||||
let nsum = 0, ncnt = 0;
|
||||
let esum = 0, ecnt = 0;
|
||||
let werte = {};
|
||||
let dayAVG = 0, evnAVG = 0, nightAVG = 0;
|
||||
for (let k = 0; k < 12; k++, i++) {
|
||||
if (erg[i].n_sum != -1)
|
||||
{
|
||||
if (werte.datetime == undefined) {
|
||||
werte.datetime = erg[i].datetime;
|
||||
}
|
||||
dsum += erg[i].n_sum;
|
||||
dcnt += erg[i].count;
|
||||
}
|
||||
}
|
||||
for (let k = 0; k < 4; k++, i++) {
|
||||
if (erg[i].n_sum != -1)
|
||||
{
|
||||
if (werte.datetime == undefined) {
|
||||
werte.datetime = erg[i].datetime;
|
||||
}
|
||||
esum += erg[i].n_sum;
|
||||
ecnt += erg[i].count;
|
||||
}
|
||||
}
|
||||
if (i < (erg.length - 8)) {
|
||||
for (let k = 0; k < 8; k++, i++)
|
||||
{
|
||||
if (erg[i].n_sum != -1)
|
||||
{
|
||||
if (werte.datetime == undefined) {
|
||||
werte.datetime = erg[i].datetime;
|
||||
}
|
||||
nsum += erg[i].n_sum;
|
||||
ncnt += erg[i].count;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
done = true;
|
||||
}
|
||||
dayAVG = calcAVG(dsum, dcnt);
|
||||
evnAVG = calcAVG(esum, ecnt);
|
||||
nightAVG = calcAVG(nsum, ncnt);
|
||||
// Calculate LDEN:
|
||||
let day = 12 * Math.pow(10,dayAVG/10); // ... and calculate the LDEN values following ...
|
||||
let evn = 4 * Math.pow(10, (evnAVG+5)/10); // ... the LDEN formaula (see function description)
|
||||
let night = 8 * Math.pow(10,(nightAVG+10)/10);
|
||||
werte.lden = 10 * Math.log10((day+evn+night)/24);
|
||||
if (opts.csv) {
|
||||
csvStr += werte.datetime + ',' + werte.lden + '\n'
|
||||
} else {
|
||||
val.push(werte);
|
||||
}
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (opts.csv) {
|
||||
return csvStr;
|
||||
} else {
|
||||
return val
|
||||
// {
|
||||
// sid: opts.sensorid,
|
||||
// span: opts.span,
|
||||
// start: opts.start.slice(7),
|
||||
// count: val.length,
|
||||
// values: val
|
||||
// };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const getAPIprops = (opt) => {
|
||||
|
||||
}
|
||||
|
||||
const getMAPaktData = (opt) => {
|
||||
|
||||
}
|
||||
|
||||
const whatTable = [
|
||||
{'what':'live', 'span': 1, 'daystart': false, avg: null, 'func': getLiveData},
|
||||
{'what':'havg', 'span': 30, 'daystart': true, avg: 'hour', 'func': gethavgData},
|
||||
{'what':'davg', 'span': 30, 'daystart': true, avg: 'day', 'func': getdavgData},
|
||||
{'what':'daynight', 'span': 30, 'daystart': true, avg: null, 'func': getdaynightData},
|
||||
{'what':'lden', 'span': 30, 'daystart': true, avg: null, 'func': getLdenData},
|
||||
{'what':'props', 'span': 0, 'daystart': true, avg: null, 'func': getAPIprops},
|
||||
{'what':'mapdata', 'span': 0, 'daystart': false, avg: null, 'func': getMAPaktData},
|
||||
{'what':'', 'span': 0, 'daystart': true, avg: null, 'func': null},
|
||||
];
|
||||
|
||||
|
||||
const getNoiseAVGData = async (opts) => {
|
||||
let ret = {data: {count: 0, values: []}, err: null}
|
||||
let emptyValues = {n_AVG:-1};
|
||||
let small = '|> keep(columns: ["_time", "peakcount", "n_AVG"])'
|
||||
if (opts.long) {
|
||||
small = ''
|
||||
emptyValues = {n_sum: -1, n_AVG:-1}
|
||||
}
|
||||
let queryAVG = `
|
||||
import "math"
|
||||
threshold = ${opts.peak}
|
||||
|
||||
data = from(bucket: "sensor_data")
|
||||
|> range(${opts.start}, ${opts.stop})
|
||||
|> filter(fn: (r) => r["sid"] == "${opts.sensorid}")
|
||||
e10 = data
|
||||
|> filter(fn: (r) => r._field == "E10tel_eq")
|
||||
|> aggregateWindow(every: 1h, fn: mean, createEmpty: false)
|
||||
|> map(fn: (r) => ({r with _value: (10.0 * math.log10(x: r._value))}))
|
||||
|> keep(columns: ["_time","_field","_value"])
|
||||
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
||||
|> rename(columns: {"E10tel_eq" : "n_AVG"})
|
||||
ecnt = data
|
||||
|> filter(fn: (r) => r._field == "E10tel_eq")
|
||||
|> aggregateWindow(every: 1h, fn: count, createEmpty: false)
|
||||
|> keep(columns: ["_time","_field","_value"])
|
||||
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
||||
|> rename(columns: {"E10tel_eq" : "count"})
|
||||
esum = data
|
||||
|> filter(fn: (r) => r._field == "E10tel_eq")
|
||||
|> aggregateWindow(every: 1h, fn: sum, createEmpty: false)
|
||||
|> keep(columns: ["_time","_field","_value"])
|
||||
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
||||
|> rename(columns: {"E10tel_eq" : "n_sum"})
|
||||
peak = data
|
||||
|> filter(fn: (r) => r._field == "noise_LAeq")
|
||||
|> aggregateWindow(
|
||||
every: 1h,
|
||||
fn: (column, tables=<-) => tables
|
||||
|> reduce(
|
||||
identity: {peakcount: 0.0},
|
||||
fn: (r, accumulator) => ({
|
||||
peakcount: if r._value >= threshold then
|
||||
accumulator.peakcount + 1.0
|
||||
else
|
||||
accumulator.peakcount + 0.0,
|
||||
}),
|
||||
),
|
||||
)
|
||||
|> keep(columns: ["_time","peakcount"])
|
||||
part1 = join( tables: {e10: e10, ecnt: ecnt}, on: ["_time"])
|
||||
part2 = join( tables: {esum: esum, peak: peak}, on: ["_time"])
|
||||
join( tables: {P1: part1, P2: part2}, on: ["_time"])
|
||||
${small}
|
||||
`
|
||||
let erg = await fetchFromInflux(ret, queryAVG)
|
||||
if(erg.err) {
|
||||
return returnOnError(ret, err, getNoiseAVGData.name)
|
||||
}
|
||||
// The times are always the END of the period (so: period from 00:00h to 01:00h -> time is 01:00)
|
||||
|
||||
// To easily extract the values, we copy the data from docs into a new array, so that the
|
||||
// hour in an element in docs becomes the index into the new array (for every new day this
|
||||
// index will be incremented by 24). Missing values are marked by: {n_sum=-1, n_AVG=-1}.
|
||||
let hoursArr = new Array(opts.span * 24); // generate new array
|
||||
hoursArr.fill(emptyValues); // fill array with 'empty' values
|
||||
let startDay = DateTime.fromISO(erg.values[0].datetime, {zone: 'utc'}).get('day'); // calc first day
|
||||
let k = 0;
|
||||
for (let d of erg.values) { // loop through docs
|
||||
let stunde = DateTime.fromISO(d.datetime, {zone: 'utc'}).get('hour') // get current hour
|
||||
let day = DateTime.fromISO(d.datetime, {zone: 'utc'}).get('day') // get current day
|
||||
if (day != startDay) { // if date has changed
|
||||
k += 24; // increment index by 24
|
||||
startDay = day;
|
||||
}
|
||||
hoursArr[k+stunde] = d; // copy date into hourArray
|
||||
}
|
||||
return hoursArr;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
// Data preparation for fetching radioactivity data
|
||||
// rxf 2022-06-24
|
||||
|
||||
import {returnOnError} from "../utilities/reporterror.js";
|
||||
import { getActData, getAvgData, getLongAvg } from "../actions/getsensorData.js"
|
||||
|
||||
const radioactFilter = (data, opts, actual) => {
|
||||
let erg = {}
|
||||
erg.sid = opts.sensorid
|
||||
erg.sname = opts.sname
|
||||
erg.values = []
|
||||
for (let x of data.values) {
|
||||
let entry = {}
|
||||
entry.datetime = x.datetime
|
||||
if(actual) {
|
||||
entry.cpm = x.counts_per_minute,
|
||||
entry.uSvh = x.counts_per_minute / 60 * opts.factor
|
||||
} else {
|
||||
entry.cpmAvg = x.counts_per_minute,
|
||||
entry.uSvhAvg = x.counts_per_minute / 60 * opts.factor
|
||||
}
|
||||
erg.values.push(entry)
|
||||
}
|
||||
return erg
|
||||
}
|
||||
|
||||
|
||||
export const getRadioData = async (opts) => {
|
||||
let erg = { err: null, data: {}}
|
||||
|
||||
let params = {
|
||||
sensorid: opts.sensorid,
|
||||
avg: opts.avg,
|
||||
datetime: opts.start
|
||||
}
|
||||
if (opts.what === 'oneday') {
|
||||
params.span = 1
|
||||
} else if (opts.what === 'oneweek') {
|
||||
params.span = 7
|
||||
} else {
|
||||
params.span = 31
|
||||
params.moving = false
|
||||
params.avg = 1440
|
||||
}
|
||||
let { data, err } = await getAvgData(params)
|
||||
if (err != null) {
|
||||
return returnOnError(erg, err, getRadioData.name)
|
||||
}
|
||||
erg.data.radiomovavg = radioactFilter(data, opts, false)
|
||||
if (opts.what === 'oneday') {
|
||||
const { data, err} = await getActData(params)
|
||||
if (err != null) {
|
||||
return returnOnError(erg, err, getRadioData.name)
|
||||
}
|
||||
erg.data.radioactual = radioactFilter(data, opts, true)
|
||||
}
|
||||
if(opts.climatesid && ((opts.what === 'oneday') || (opts.what === 'oneweek'))) {
|
||||
params.sensorid = opts.climatesid
|
||||
params.avg = 10
|
||||
const { data, err} = await getAvgData(params)
|
||||
if (err != null) {
|
||||
return returnOnError(erg, err, getRadioData.name)
|
||||
}
|
||||
data.sid = opts.climatesid
|
||||
data.sname = opts.climatesname
|
||||
erg.data.climate = data
|
||||
}
|
||||
return erg
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import {getData4map} from '../actions/data4map.js'
|
||||
import assert from 'assert/strict'
|
||||
|
||||
describe("get data for map - test", function() {
|
||||
const box = {
|
||||
"east": 9.322391662597672,
|
||||
"north": 48.86726810417461,
|
||||
"south": 48.69057640500091,
|
||||
"west": 8.99760833740236
|
||||
}
|
||||
|
||||
it("radioactivity should return at least 4 elements", async function() {
|
||||
const erg = await getData4map(
|
||||
{
|
||||
"type": "radioactivity",
|
||||
"box": box,
|
||||
test: true
|
||||
})
|
||||
assert.ok(erg.count >= 4)
|
||||
})
|
||||
it("pm should return at least 270 elements", async function() {
|
||||
const erg = await getData4map(
|
||||
{
|
||||
"type": "pm",
|
||||
"box": box
|
||||
})
|
||||
assert.ok(erg.count >= 270)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,55 @@
|
||||
// parse the params from http call
|
||||
|
||||
import * as ERR from '../utilities/errortexts.js'
|
||||
import {returnOnError} from "./reporterror.js"
|
||||
|
||||
const checkParams = (params, mo) => {
|
||||
let o = {opts: {}, err: null}
|
||||
if ((mo.mandatory.length !== 0) && (params === undefined)) {
|
||||
return returnOnError(o, ERR.NOPARAMETER, checkParams.name )
|
||||
}
|
||||
for (let p of mo.mandatory) {
|
||||
if (!(p.name in params)) {
|
||||
return returnOnError(o, ERR.NOMANDPARAM.replace('xx', p.name), checkParams.name )
|
||||
}
|
||||
if (p.type === 'int') {
|
||||
let x = parseInt(params[p.name])
|
||||
if (isNaN(x)) {
|
||||
return returnOnError(o, ERR.PARAMNONUM, checkParams.name )
|
||||
} else {
|
||||
o.opts[p.name] = x
|
||||
continue
|
||||
}
|
||||
} else if (p.type === 'float') {
|
||||
let x = parseFloat(params[p.name])
|
||||
if (isNaN(x)) {
|
||||
return returnOnError(o, ERR.PARAMNONUM, checkParams.name )
|
||||
} else {
|
||||
o.opts[p.name] = x
|
||||
continue
|
||||
}
|
||||
}
|
||||
o.opts[p.name] = params[p.name]
|
||||
}
|
||||
for(let p of mo.optional) {
|
||||
if (!(p.name in params)) {
|
||||
o.opts[p.name] = p.default
|
||||
} else {
|
||||
if (p.type === 'int') {
|
||||
let x = parseInt(params[p.name])
|
||||
if (isNaN(x)) {
|
||||
o.opts[p.name] = p.default
|
||||
} else {
|
||||
o.opts[p.name] = x
|
||||
}
|
||||
} else if (p.type === 'bool') {
|
||||
o.opts[p.name] = params[p.name] === 'true'
|
||||
} else {
|
||||
o.opts[p.name] = params[p.name]
|
||||
}
|
||||
}
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
export default checkParams
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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"
|
||||
|
||||
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: 'getradiodata', func: radioact.getRadioData},
|
||||
{cmd: 'getnoisedata', func: noise.getNoiseData},
|
||||
{cmd: 'getsensordata', func: getData.getSensorData}
|
||||
]
|
||||
|
||||
export default cmdTable
|
||||
@@ -0,0 +1,35 @@
|
||||
// convert influx csv output to JSON
|
||||
|
||||
export function csv2Json(str, delimiter = ",") {
|
||||
// slice from start of text to the first \n index
|
||||
// use split to create an array from string by delimiter
|
||||
const headers = str.slice(0, str.indexOf("\r\n")).split(delimiter).slice(3);
|
||||
let x = headers.findIndex((x) => (x === '_time') || (x === '_stop'))
|
||||
if (x != -1) {
|
||||
headers[x] = 'datetime'
|
||||
}
|
||||
// slice from \n index + 1 to the end of the text
|
||||
// use split to create an array of each csv value row
|
||||
const rows = str.slice(str.indexOf("\r\n") + 2).split("\r\n").slice(0,-2)
|
||||
|
||||
// Map the rows
|
||||
// split values from each row into an array
|
||||
// use headers.reduce to create an object
|
||||
// object properties derived from headers:values
|
||||
// the object passed as an element of the array
|
||||
const arr = rows.map(function (row) {
|
||||
const values = row.split(delimiter).slice(3);
|
||||
const el = headers.reduce(function (object, header, index) {
|
||||
if(header !== 'datetime') {
|
||||
object[header] = parseFloat(values[index]);
|
||||
} else {
|
||||
object[header] = values[index];
|
||||
}
|
||||
return object;
|
||||
}, {});
|
||||
return el;
|
||||
});
|
||||
|
||||
// return the array
|
||||
return arr;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Errortexts
|
||||
|
||||
export const CMNDUNKOWN = 'Command not known'
|
||||
export const NOTHIMG = 'Nothing to show'
|
||||
export const PARAMNONUM = `Parameter ${'xx'} is not a number`
|
||||
export const NOTYP = 'No type given'
|
||||
export const NOSENSFOUND = 'No suitable sensors found in properties'
|
||||
export const NOMANDPARAM = `Mandatory parameter '${'xx'}' not given`
|
||||
export const NOPROPSREAD = `No properties read for sensor ${'xx'}`
|
||||
export const NOPARAMETER = 'No parameter given'
|
||||
export const RESPSTATUS = `Returned status = ${'xx'}`
|
||||
export const NODATA = 'No data found'
|
||||
export const SYNTAXURL = 'Syntax error in calling url'
|
||||
export const WRONGTYPE = `Sensor ${'xx'} is not of type ${'yy'}`
|
||||
@@ -0,0 +1,16 @@
|
||||
import { DateTime} from 'luxon'
|
||||
|
||||
const MOCHA_TEST = process.env.MOCHA_TEST || false
|
||||
|
||||
export function logit(str) {
|
||||
if(MOCHA_TEST) return
|
||||
let s = `${DateTime.now().toISO()} => ${str}`;
|
||||
console.log(s);
|
||||
}
|
||||
|
||||
export function logerror(str) {
|
||||
if(MOCHA_TEST) return
|
||||
let s = `${DateTime.utc().toISO()} => *** ERROR *** ${str}`;
|
||||
console.log(s);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import {logit} from "./logit.js";
|
||||
|
||||
export const reportError = (message, errortext) => {
|
||||
message.error = true
|
||||
message.errortext = errortext
|
||||
return message
|
||||
}
|
||||
|
||||
export const returnOnError = (pr, error, name) => {
|
||||
pr.err = error
|
||||
logit(`${name}: ${error}`)
|
||||
return pr
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user