5
\$\begingroup\$

I'm building a web interface for a home monitoring sensor array, and fetching JSON from the backend framework. I want to start putting statistics together for monitoring different areas of the home, inside and outside.

Each thermal sensor provide the reading, in regular intervals, of temperature and time of day, so I end up with a JSON object looking like this:

{
"sensor_1" :    [
    {
    "location" : "garage_east",
    "temp_f" :  [{
                    "value" : "71.2",
                    "timestamp" : "2017-05-01T08:15:00"
                },
                {
                    "value" : "70.8",
                    "timestamp" : "2017-05-01T08:30:00"
                }, 
                {
                    "value" : "72.6",
                    "timestamp" : "2017-05-01T08:45:00"
                }
        ]}],

"sensor_2" : [{
    "location" : "kitchen",
    "temp_f" : [{
                    "value" : "78.2",
                    "timestamp" : "2017-05-01T08:15:00"
                },
                {
                    "value" : "78.8",
                    "timestamp" : "2017-05-01T08:30:00"
                }, 
                {
                    "value" : "78.6",
                    "timestamp" : "2017-05-01T08:45:00"
                }]
}]}

and so on, with multiple sensors each displaying pretty large JSON objects for the reporting periods.

I want to derive the highest and lowest temperatures around the house, and at what time they occurred, using JavaScript (with AngularJS). Currently, I'm looking to optimize this (which does work):

/* The entire array of data, within time frame set by the user 
*   and for the sensors defined by the user
*   from the RESTful API (params)
*/

api.sensor_data.get(params, function(data){

  $scope.sensorStats = {
    "kitchen" : {},
    "garage" : {}
  }
  /* find the highest temperature for a single sensor */
  let hightemp_kitchen = Math.max.apply(Math, data.sensor_1.temp_f.map(function(t){
        return t.value;
  }));
  /* grab the associated timestamp and put them both into an object */
  $scope.sensorStats.kitchen.high = data.sensor_1.temp_f.find(function(obj){
      return obj.value == hightemp_kitchen;
  });
  /* Repeat for each sensor of concern (user defined in params) */
  etc...
 });
}, ...

and then I use the in the DOM the expression {{ sensorStats.kitchen.high }} to display the high temp and date/time to the user for the kitchen.

Like I say, this works, but I'm doing this for each of the 15+ sensors in the array around the property. This method not the easiest to read and maintain - I think it can be faster and more efficient to maintain, but I don't have the lexicon to know what to research.

Is this efficient? Is there a "better" or more efficient way to filter out the data and return the temp and time? I'm reading up on ES6, and discovered the filter() method, but it seems to be useful only on arrays and not objects.

\$\endgroup\$
2
\$\begingroup\$

It seems like you should not be hard-coding concepts such as "kitchen", "garage", etc. into your code at all. You need to design that out.


Also I question the JSON structure. sensor_* are meaningless property names, giving you an unnecessary level of nesting, so if you have control over JSON generation, you might consider an array of object like:

[
    {
        "location": ...,
        "temp_f": [
            ...
        ]
    },
    {
        ...
    }
]

But let's assume you can't do that, and think of ways we can get you the information make this whole thing more dynamic. I am not sure what params is even used for, so I am discarding this.

api.sensor_data.get(function(data){
    let sensorData = {};
    // loop through each sensoer object, ignore top level key value
    Object.values(data).forEach( (sensor) => {
        // reduce f_temp array to single entry with highest temp
        // and set to sensorData result object with location as key
        sensorData[sensor.location] = sensor.f_temp.reduce( (acc, metric) => {
            if(parseFloat(metric.value) > parseFloat(acc.value)) acc = metric;
            return acc;
        }, sensor.f_temp[0]);
    });

    $scope.sensorStats = sensorData;
}

Based on your comment regarding params, you might have something more like this:

    let sensorData = {};

    let sensors = Object.values(data);
    // filter by location
    if (location in params) {
        sensors = sensors.filter( (sensor) => sensor.location === params.location );
    }
    // loop through each sensor objects        
    sensors.forEach( (sensor) => {
        let result = null;
        let metrics = sensor.f_temp;
        if(minTimestamp in params) {
            metrics = metrics.filter( metric => {
                return (Date.parse(metric.timestamp) >= params.minTimeStamp );
            });
        }
        if(maxTimestamp in params) {
            metrics = metrics.filter( metric => {
                return (Date.parse(metric.timestamp) =< params.maxTimeStamp );
            });
        }
        if(metrics.length > 0) {
        result = metrics.reduce( (acc, metric) => {
            if(parseFloat(metric.value) > parseFloat(acc.value)) acc = metric;
            return acc;
        }, metrics[0]);
       sensorData[sensor.location] = result;
    });

    $scope.sensorStats = sensorData;
}
\$\endgroup\$
  • \$\begingroup\$ Thanks for this feedback. I don't have control over the JSON in this scenario - in-so-far as I want to keep this conversation scoped to the frontend, and updating the JSON format would involve changing the backend and the datastore. \$\endgroup\$ – Smittles May 5 '17 at 20:44
  • \$\begingroup\$ The params argument is an object passed to the RESTful API that includes the time and date ranges to report on as well as which of the sensors to report on. For example, if the end user only wants to know what the temperature in the garage was, she can select just the garage check box on the web interface and use a calendar and time selector to pick the date ranges and time ranges (usually a filter of "last 7 days" from now() ) \$\endgroup\$ – Smittles May 5 '17 at 20:48
  • \$\begingroup\$ @Smittles Ok. Then there would certainly be some additional steps there to optionally filter by location and time range. \$\endgroup\$ – Mike Brant May 5 '17 at 20:54
  • \$\begingroup\$ I have that filtering logic built in already - really, the question is about optimizing the collection of min & max temperature, and pairing that with the time of day. In the event a sensor available on the frontend for filtering on is deselected, I need to maintain that node as undefined, which I'm doing now. If it's undefined, the DOM element that expects a value there is not shown. That's all handled in AngularJS. This is really just about the capability of using a built-in function to more legibly and/or more efficiently populate the requested sensor high / low + datetime(s). \$\endgroup\$ – Smittles May 5 '17 at 21:00
  • 1
    \$\begingroup\$ @Smittles Yep the filtering is not really my concern when it comes to the code you have shown. It is the fact that you mention /* Repeat for each sensor of concern (user defined in params) */. That and the hard-coded kitchern, garage, etc. really need to be designed away. \$\endgroup\$ – Mike Brant May 5 '17 at 21:12
-1
\$\begingroup\$

The solution depends on the desired output format. My example:

const a = {
"sensor_1" :    [
    {
    "location" : "garage_east",
    "temp_f" :  [{
                    "value" : "71.2",
                    "timestamp" : "2017-05-01T08:15:00"
                },
                {
                    "value" : "70.8",
                    "timestamp" : "2017-05-01T08:30:00"
                }, 
                {
                    "value" : "72.6",
                    "timestamp" : "2017-05-01T08:45:00"
                }
        ]}],

"sensor_2" : [{
    "location" : "kitchen",
    "temp_f" : [{
                    "value" : "78.2",
                    "timestamp" : "2017-05-01T08:15:00"
                },
                {
                    "value" : "78.8",
                    "timestamp" : "2017-05-01T08:30:00"
                }, 
                {
                    "value" : "78.6",
                    "timestamp" : "2017-05-01T08:45:00"
                }]
}]}

const b = Object.keys(a)
  .map(k => {
    const key = a[k][0].location;
    const value = a[k][0].temp_f.sort((a, b) => a.value - b.value).pop();
    return { [key]: value }
  })

console.log(JSON.stringify(b, null, 2))

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.