NOTICE: This forum has been archived (i.e. registration and posting is disabled).
Please go to the new forum at https://forum.kylegabriel.com
[Solved] Multiple Inputs for Math Equation
Kyle - This should be an easy question. Is there a way I can use multiple inputs in a math equation? I'm looking at controller_math.py and it seems like it could be pretty easy to add this functionality. Here the gist: My TDS sensor works great on my Arduino but it needs a temperature and scaling value to be accurate. I need to use a constant and a temperature variable for use within a formula to calibrate the TDS sensor and really get accurate readings. I already have a temperature sensor and the TDS sensors outputting data. Now I need to crunch some numbers based on both of the values to create my final PPM #.
Does this functionality already exist? I already poked through the documentation and couldn't find exactly what I'm looking for. If not, I'd like to take a shot at implementing it and submitting a PR if you don't see any breaking conflicts.
Alright - After further digging, I managed to get the extra input added to the page but now I'm running to a schema issue with the influxdb. I thought this sort of thing would happen. That's the huge benefit of a schemaless DB like mongodb.
So... after some thought on possible implementation of two possible inputs within a single equation, I believe this would allow for very flexible and adhoc measurements.
Here's what my formula looks like right now. Maybe you can offer a different solution.
V = 1.275 (ADC voltage of a budget TDS sensor)
T = 22 (Temperature)
k = .75 (calibrated constant)
((133.42 * (V**3) - 255.86*(V**2) + 875*V) * k ) / (1 + .02 * (T - 25) ) - This gives me a calibrated measurement of a an Amazon-cheap TDS Sensor ( https://www.dfrobot.com/product-1662.html) connected via ADC with a 1WIRE DS18B20 temperature sensor.
Just wondering if there is a better way to get this formula into Mycodo without rewriting some major changes. Thoughts?
The Equation Math controller only supports a single measurement. To perform calculations on multiple measurements, the easiest way would be to create a custom Input module. However, there should be a better approach. You got me thinking and I set out to create a custom Function that takes two configurable measurements and saves a value with a user-set unit and measurement. I created an example function module you can import from the Configure -> Custom Functions page and then add from the Setup -> Functions page, however I had to update the codebase to allow the Measurement (e.g. temperature, humidity, acceleration) and Unit (e.g. C, F, percent, G) selections for the Function's custom_options. Everything seems to be working except one piece of the puzzle: allowing channels and measurements to be pulled from the Function module into other parts of the system (Dashboard widgets, Conditionals, PID controllers, etc.). This shouldn't be terribly difficult, but I'll need to chew on it a bit before I jump into implementing it.
Attached is the function I'm planning to add to the built-in set (renamed from .py to .txt in order to attach). Keep in mind you'll need to upgrade to master to be able to import/use it (commit f9c6350). Once I figure out this last piece, it will basically make the Math controllers obsolete and I'll work on porting them over to these single-file Function modules, which should also assist in users learning to build their own Functions.
I had to take a slightly different approach, but I'm a bit closer to everything working. Here's the log output with both measurements and output working. Now to tie the measurement into the rest of the system.
2021-02-26 22:19:09,245 - INFO - mycodo.functions.function_multi_measurement_equation_40dd52f8 - Activated in 75.6 ms 2021-02-26 22:19:09,310 - DEBUG - mycodo.functions.function_multi_measurement_equation_40dd52f8 - Most recent timestamp and measurement for select_measurement_a: 2021-02-27T03:18:55.916569Z, 142.2032 2021-02-26 22:19:09,354 - DEBUG - mycodo.functions.function_multi_measurement_equation_40dd52f8 - Most recent timestamp and measurement for select_measurement_b: 2021-02-27T03:18:56.413132Z, 1.21 2021-02-26 22:19:09,355 - DEBUG - mycodo.functions.function_multi_measurement_equation_40dd52f8 - Equation: a*b = 142.2032*1.21 2021-02-26 22:19:09,355 - DEBUG - mycodo.functions.function_multi_measurement_equation_40dd52f8 - Output: 172.065872
@kylegabriel - You implemented this much faster than I did. I was doing basically the same thing you did. I thought that this could sort of fall into a different category but I've been reading through other PR's and responses on the forum to get to understand your programming style a little better. I was trying to figure out a way to implement it without breaking existing functionality for other people who leverage the use of math controllers with a single input. I got stuck on the database but I feel like I was about 90% of the way there. The next problem was going to be displaying the data like you also mentioned. What would be the best way of displaying data from a multi-input equation... Hmm.
I'll switch branches and pull the latest master. I'll let you know how it turns out. Your screenshot reflects exactly what I'm trying to do. So hopefully this has more applications more than just my instance. Thanks!!
I just pushed my latest changes (ee737f8). The new measurements/channels work similarly to how Inputs and Outputs use them, and I think it adds a lot to the system. I still need to go through and add references to Function measurements/channels throughout, but I've already added them to graph widgets and asynchronous graphs. There are a lot of other places to update, though.
Now that I think about it, building a custom Input is probably your best option. There are a couple benefits to doing this over creating a Function:
- Store three values at the time of the Input's execution (the two measurements and your calculated value).
- Store the measurements as they occur, rather than relying on a periodic function separate from the Input to pull measurements from the database and perform a calculation on them.
- Improve accuracy because the calculated value has a timestamp that is the same as when the two contributing values were acquired.
I still think the Function refactoring was very much needed and opens up a lot more options for users. Now there's this new functionality, I should start porting the Math controllers so I can eventually start phasing them out.
I've been pondering this and realized what I've done is made Functions more like Inputs. Their only real difference at this point is their name (save a few small differences). They both have measurements and channels used for storing measurements and execute code at predefined intervals, with user custom options.
BTW, I moved the custom multi-measurement function from the examples directory to the built-in directory so it will appear as a part of the built-in set, so you don't need to import it to try it out.
Just pushed a commit that adds Function measurement support for the remaining widgets. Still quite a few more places to add to.
@kylegabriel - It's looking great! I ran a sample when I stepped out of the house and added in a formula to calculate compensation between two sensors. Looks great!
I totally agree with the function versus input concept. It totally makes sense from a programatic standpoint. Functions (when compared to every programming language out there...) tend to produce an output that is generally used as an 'input' to other items. So... why not just use Functions for just that? I'll be honest. I was rather confused when I saw the term function in your application. That's the first spot I ran to when I needed to implement a custom behavior. I think your heading in the right direction here. Again, great job!
My general vision for each section of Mycodo is this:
Inputs: Receive information from systems (sensors, APIs, hardware, etc.) that is stored in the database.
Outputs: Send information out to manipulate systems (GPIOs, PWM, relays, motors, APIs, etc.).
Functions: Utilize stored data, Inputs, and Outputs to perform tasks.
There is some overlap between all of these, but if I have a new sensor I want to start acquiring measurements with, I will generally create an Input module, and similarly if I have a new chip to control a motor or something similar, I'll create an Output. For other things that may not fit nicely into either of those categories, I've usually delegated Functions for those tasks. There are also a set of abstract base classes that help each controller do its task bit better than the others. I get the sense that newcomers quickly pick up on the use of each and figure it out reasonably well.
As I've learned what works and what doesn't, I've tried to modify and adapt the system. It's sometimes challenging because I have to make drastic changes to users' database schemas, though I think it's generally been smooth. I've only made a few major releases that necessitated users abandoning their previous database in order to move to the next version. This now also occurs less often as I've become more skillful in making schema changes that are conducive to database upgrades and scripts that convert user data when necessary.
I'm always open to recommendations and feedback and learning, so let me know if you think something can be done better.
Hi Mr D, could you share some screenshots of how you have this configured? I'm trying to set up the same sensors. I'm following the DFRobot Wiki, but it seems like there is some extra steps in the C++ code compared to what you wrote here. Thanks
All I did was comb through the C++ code and rip out the formulas and fix it to what I needed. Here's the general setup..
You will need two Functions.
Measurement A = TDS Sensor via MCP3008
Equation : (133.42 * (x**3) - 255.86*(x**2) + 875*x) * 0.75
NOTE: Ensure you Adjust the 0.75 K value in the equation based on your sensor's sensitivity with EC calibration solution
Temp Calibrated EC
Measurement A : Function for the Calibrated EC above
Measurement B : DS18B20
Equation : a / (1+0.02*(b-25))