Data Smoothies & Interpolation
Data points change over time. However, they might be changing across large or potentially inconsistent intervals, depending on the type of data you are retrieving.
New values coming in will end up ‘snapping’ from one value to another, which is especially noticeable over a longer interval. For example:
0
50 (new data point, 1s later)
100 (new data point, 3s later)
44 (new data point, 5s later)
This might result in ‘jerky’ or ‘laggy’ behaviour, which may be undesirable. We can write a very basic algorithm that allows a far smoother ‘transition’ by slowly ‘spreading’ this change in value over time. This is also known as interpolation:
0
1 (new data point set: 50)
2 (interpolation begins... notice how it doesn't 'snap' to 50 right away)
3
...
48
49
50 (data point reached: 50)
50
50
51 (new data point set: 100)
52 (interpolation transitions to new point...)
53
...
98
99
100 (data point reached: 100)
100
This above illustration can also be understood as a line graph, traversing from one known point to another:
Customisations made to this algorithm allow you to set how quickly you want the data to smooth over, making this a very handy tool for very quick, linear translations between data points.
The examples discussed here are of the LINEAR nature, i.e. the data points traverse in a straight path towards the next. This linear interpolation (also known as ‘lerping’ in some software packages) provides a surprisingly-sufficient dynamism to your physical actuations. Other more advanced modes of interpolation can also be explored, using easing functions.
The below example uses pure integers
for simplicity and legibility. See if you can convert this example code to use doubles
instead (doubles are double-precision floating point
numbers, i.e. fractions with higher resolution than regular floating point numbers).
To see the results, open a Serial monitor to your microcontroller.
Note
This recipe does not include any other bits of working with actuators/sensors – it is meant for you to integrate into your existing code.
Code
Two code variants are provided as starting examples. The Arduino Uno requires you to install the Ticker code library, while the Particle Photon comes with the built-in Timer library and demonstrates how a server-triggered Particle function can be used to drive the change in value of myVar
.
-
Libraries Used
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
/* Data Smoothies – an example on how to take incoming data values for a variable but smoothly transition to the new value (instead of having the old value 'snap' to the new one) */ /* Please note that the code provided here is licensed under the MIT license. The MIT License (MIT) Copyright © 2018 Chuan Khoo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "Ticker.h" // The Arduino UNO needs the Ticker code library (or any other similar) to work. Set up a timer that can 'manage' the updating of our variable that we wish to 'smooth' // Check the updateDataVar function - because this is where all of the data smoothing action happens // The smaller this interval is (50), the smoother + faster the transition will be void updateDataVar(); Ticker updateDataTimer(updateDataVar, 50); // setting up some variables to manage incoming test Serial data: String inputString = ""; // a String to hold incoming data bool stringComplete = false; // whether the string is complete // we declare newVar as 'volatile' because it is a variable that is actively being changed from the Particle cloud (via our ParticleFunc node) volatile int newVar = 0; int myVar = 0; // set a value to increment the variable by. // in this case, we are using integers (whole numbers), // so the minimum increment must be 1. int incre = 1; // the larger this is, the faster the transition too void setup() { Serial.begin(115200); inputString.reserve(200); updateDataTimer.start(); // start the timer to keep updating the data smoothing! } void loop() { // add your other code here to read sensor readings and set it to newVar, for example... checkSerial(); // keep checking the Serial port for available data updateDataTimer.update(); // keep updating the Ticker } //////////// CUSTOM FUNCTIONS BELOW // checkSerial runs each time loop() runs, to check for incoming data from our Serial Monitor void checkSerial() { if (Serial.available() > 0) { // get the new byte: char inChar = (char)Serial.read(); // add it to the inputString: inputString += inChar; // if the incoming character is a newline, set a flag so the main loop can // do something about it: if (inChar == '\n') { stringComplete = true; } } if (stringComplete) { // let's convert the incoming string into a number newVar = inputString.toInt(); Serial.print(">>> newVar is now: "); Serial.println(newVar); inputString = ""; // clear the string stringComplete = false; } } void updateDataVar() { // here is the 'smoothing' algorithm used for linear traversal // it runs all the time and automatically slews myVar to newVar // find how how far we are away form target value int delta = myVar - newVar; // check if absolute (positive) value of delta is larger than incre if (abs(delta) >= incre) { if (newVar < myVar) { myVar -= incre; } else if (newVar > myVar) { myVar += incre; } } else { // we are too 'near' the target value but the size of incre means we will oscillate just above/under the target value! // we don't want that, so this else condition catches that (delta < incre) and sets the final value immediately // this is a lot more noticeable if you use a very large value for incre myVar = newVar; } Serial.println(myVar); }
-
Libraries Used
- None
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
/* Data Smoothies – an example on how to take incoming data values for a variable but smoothly transition to the new value (instead of having the old value 'snap' to the new one) */ /* Please note that the code provided here is licensed under the MIT license. The MIT License (MIT) Copyright © 2018 Chuan Khoo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // Set up a timer that can 'manage' the updating of our variable that we wish to 'smooth' // Check the updateDataVar function - because this is where all of the data smoothing action happens // The smaller this interval is (50), the smoother + faster the transition will be Timer updateDataTimer(50, updateDataVar); // we declare newVar as 'volatile' because it is a variable that is actively being changed from the Particle cloud (via our ParticleFunc node) volatile int newVar = 0; int myVar = 0; // set a value to increment the variable by. // in this case, we are using integers (whole numbers), // so the minimum increment must be 1. int incre = 1; // the larger this is, the faster the transition too void setup() { Serial.begin(115200); // register a function with the Particle cloud, so you can call this function using the ParticleFunc node in your flows Particle.function("newData", newData); delay(2000); // start the timer to keep updating the data smoothing! updateDataTimer.start(); } void loop() { // nothing to do here (except add your other code to read sensor readings, for example) } //////////// CUSTOM FUNCTIONS BELOW int newData(String command) { // assume for this example that we have a Particle flow setup to send a string containing just a single value ("91") // ready to transition to this new value newVar = atoi(command); // return a 1 back to ParticleFunc, to say we are 'good' return 1; } void updateDataVar() { // here is the 'smoothing' algorithm used for linear traversal // it runs all the time and automatically slews myVar to newVar // find how how far we are away form target value int delta = myVar - newVar; // check if absolute (positive) value of delta is larger than incre if(abs(delta) >= incre) { if(newVar < myVar) { myVar -= incre; } else if(newVar > myVar) { myVar += incre; } } else { // we are too 'near' the target value but the size of incre means we will oscillate just above/under the target value! // we don't want that, so this else condition catches that (delta < incre) and sets the final value immediately // this is a lot more noticeable if you use a very large value for incre myVar = newVar; } Serial.println(myVar); }