BruControl: Brewery control & automation software

Homebrew Talk - Beer, Wine, Mead, & Cider Brewing Discussion Forum

Help Support Homebrew Talk - Beer, Wine, Mead, & Cider Brewing Discussion Forum:

This site may earn a commission from merchant affiliate links, including eBay, Amazon, and others.
I think the ability to grab/select multiple elements and move them in a group be awesome... totally not necessary.

I like the idea of grouping... could treat them as one "entity". Since no way to select them, we would either need to add that or make them part of a group name in each properties.
 
Yes - you will be creating a ground loop with this. Ground back at the enclosure end only.
Yes I'm aware, but when I called it a ground loop earlier in the thread I was told that was not the correct term although to me that is exactly what it is. I'm surprised more people dont see issues with even traditional kal style panels wired up this way. So far I can only find reports of people having this issue when they use inkbird pids in a panel so there must be some sort of filtering that many other pids are using to dampen or filter out this effect.
 
I do see an issue where if the script window is closed, you move an element down, then re-open the script window, then vertical scroll bar pops up, then scroll down and try to move the element sideways, it only allows it to move up - kind of like the scroll window is forcing it up and out of the way. We can fix that. Is that what you are reporting?
For what its worth, I have to leave my controls unlocked to gain access to things I need to adjust or enable/disable regularly and also find myself constantly readjusting things that somehow move. I just assumed that most do not use the controls as I do and have the luxury of leaving them locked.
 
sample.png

Inspired by GPargins and AugieDog, I am getting there, although I am not the artists they are. Single background image rather than a lot of globals. Would be nice to have a slot 4 for Background image that is a path. You would also need a property to edit the path in Scripts. That way, you could have a many background images as you wanted.
 
I do see an issue where if the script window is closed, you move an element down, then re-open the script window, then vertical scroll bar pops up, then scroll down and try to move the element sideways, it only allows it to move up - kind of like the scroll window is forcing it up and out of the way. We can fix that. Is that what you are reporting?
Likely the issue.
 
For what its worth, I have to leave my controls unlocked to gain access to things I need to adjust or enable/disable regularly and also find myself constantly readjusting things that somehow move. I just assumed that most do not use the controls as I do and have the luxury of leaving them locked.

Makes sense... This is the point of User Control - to change the main setting. We have discussed the idea of putting an enabled/disabled quick link too - like another icon in the element. What else would you wan't quick access to?
 
Makes sense... This is the point of User Control - to change the main setting. We have discussed the idea of putting an enabled/disabled quick link too - like another icon in the element. What else would you wan't quick access to?
The max output % on the pid controls and the quick ability to enable and disable my individual hysterisis temp controls for each fermenters heat and cooling are really the only two things that come to mind off the top of my head as I'm brewing now.
 
What if we made 'User Control' for the graph to adjust its time span?

This would be great, I haven't worked much with brucontrol for brewing yet, but I've had autospunding for quite a few batches now and could see the benefit of user control on the graph to make it easier to monitor how many times the valve opened over different time periods.
 
My only workspace comment would be that it would be nice if you could set a "show on all workspaces" on elements. For example, if there is a "Current status" or whatever box, right now I have multiple globals that all get updated, one for each workspace. It's easy enough to work around, but if it was a simple change it might be nice.
 
The max output % on the pid controls and the quick ability to enable and disable my individual hysterisis temp controls for each fermenters heat and cooling are really the only two things that come to mind off the top of my head as I'm brewing now.

OK thanks. IMO, Max Output % isn't something that should be messed with, but let's not hash that - its your system!

You could create a simple script to show and edit Max Output % right on the workspace (using an Inspector to review and edit the value in the script).
 
I think the ability to grab/select multiple elements and move them in a group be awesome... totally not necessary.
+1
I like the idea of grouping... could treat them as one "entity". Since no way to select them, we would either need to add that or make them part of a group name in each properties.
+1. Would also be nice to make the info button smaller at elements, it kind of gets in the way when interface is unlocked- someday i plan to lock my interface when finished, but will I ever be finished? Maybe make something similar to the graphic at the bottom left?

Is it possible to lock user control of element via script?
 
I would appreciate the ability to ignore obviously invalid temperature readings.

Temperature was:
- 156F and then you get a reading of -190F
- 156 and then you get a reading of 450F

As you know our systems are pretty slow to change (as you have stated many times) and instantaneous heating isn't really possible.
This would help with noise in the lines and avoid those nasty looking spikes in the graph that throws the range off and makes it hard to see.
 
I would appreciate the ability to ignore obviously invalid temperature readings.

Temperature was:
- 156F and then you get a reading of -190F
- 156 and then you get a reading of 450F

As you know our systems are pretty slow to change (as you have stated many times) and instantaneous heating isn't really possible.
This would help with noise in the lines and avoid those nasty looking spikes in the graph that throws the range off and makes it hard to see.

It would seem you have some EMI going on if you are seeing readings like that. What probes are you using? Are you already dampening the input (if possible)? To your point, you can already do what you want via the floor and ceiling calibration.

Edit: Didn't see you said ignore bad readings. A high/low limit calibration addition might be a good option to throw out readings vs floor/ceiling which will just put that value in place of a reading outside the range.
 
Last edited:
It would seem you have some EMI going on if you are seeing readings like that. What probes are you using? Are you already dampening the input (if possible)? To your point, you can already do what you want via the floor and ceiling calibration.

Edit: Didn't see you said ignore bad readings. A high/low limit calibration addition might be a good option to throw out readings vs floor/ceiling which will just put that value in place of a reading outside the range.
I also experience these as mentioned previously and other have messaged me about them privately. I would love to know why some have the issue and others dont so we can correct it. Ive tried ferrite clips, and power filters as well as snubbers and diodes which did stop the panels own relays and contactors from causing spikes.
However I cant seem to stop the noise in the main power from causing this.
Normally I dont mind the occational spike here and there but saturday night I was trying to heat my strike water for sundays brew session at the brewpub and our kitchen was still open... every time our electric convection over element kicked on and off I got a spike that made all my temp probes spike and elements turn off and on in my HLT one of my customers actually pointed it out as I was bartending... I have had no luck stopping these spikes caused by other appliances on the same power in the building. originally the contactors and other kettles contributed to this but that ive corrected. I have tried power filters and even isolating the power to the arduino/rtds (as best as I could)

Some sort of power isolation/filtering device or shematic would be great. I currently keep all my DC stuff above the high voltage lines towards the bottom of my panel. I realize there multiple possible causes.
 
Last edited:
I wonder if a double inversion UPS powering your DC power supplies could isolate the noise? Somewhat unrelated, we had some older VFDs at work that wreaked havoc on our control system. Once we replaced them, we opened one up and determined that the internal grounding wire appeared to be undersized for the load. I don't know much about electric ovens, but I would wonder about its internal/external grounding if I were seeing spikes like that and could directly attribute them to said oven.
 
It would seem you have some EMI going on if you are seeing readings like that. What probes are you using? Are you already dampening the input (if possible)? To your point, you can already do what you want via the floor and ceiling calibration.

Edit: Didn't see you said ignore bad readings. A high/low limit calibration addition might be a good option to throw out readings vs floor/ceiling which will just put that value in place of a reading outside the range.
I have a section of script that says

wait "TILT BLACK" SG <= rest_gravity.

during my first wet test, I lost signal to my tilt and this caused condition to be met allowing for a premature jump to next section of code. Could this be prevented using floor/ceiling?
 
I had a similar issue, so I created a global that serves as the weighted average holder of the "raw" SG values from the tilt. Every 10 minutes the average is updated, so I set the floor of the tilt as .999 and throw out values input to the average calculation < 1, thus also ignoring when the tilt drops out. This also stabilizes the SG trend quite a bit during active fermentation.
 
General Question regarding Hysteresis and PID Elements:

There is a Target (easy to Understand although I use Globals for Setpoints as I like the Standard Look and just use scripts to update the target)


and there is the Input where there is a Numeric Valve. Does anyone use this value? Is it useful to "always" see this Value. I am thinking to replace the PID or Hysteresis with a Global pix of an Element (Basically powered or Not, but not actually interactive with the Input Value. I had a small element graphic that showed up red inside my vessels when I was commanding heat on the BCS. It was also not interactive but simply On or Off. Just another Clue that it should be "working".

I would likely put the true Element PID or Hysteresis on a different Workspace and just have the Heating Element Pix On/Off on the Main Brew Page.
 
I had a similar issue, so I created a global that serves as the weighted average holder of the "raw" SG values from the tilt. Every 10 minutes the average is updated, so I set the floor of the tilt as .999 and throw out values input to the average calculation < 1, thus also ignoring when the tilt drops out. This also stabilizes the SG trend quite a bit during active fermentation.
Example of the script?
 
I had a similar issue, so I created a global that serves as the weighted average holder of the "raw" SG values from the tilt. Every 10 minutes the average is updated, so I set the floor of the tilt as .999 and throw out values input to the average calculation < 1, thus also ignoring when the tilt drops out. This also stabilizes the SG trend quite a bit during active fermentation.
Can you explain a bit more how you achieved this? How are you accessing the raw data? I assume this is being referenced in a script then some equation to find the average? The global then references this value?
 
I'm having an issue with a portion of my script. The idea is to be able to change the hysteresis device (FV-1) target via a global (FV-1 PRIMARY) as the script is running. This works correctly on the first iteration but the output is then stuck in that state. Any idea what I'm doing wrong?

Code:
    [Active-Ramp]
new value ramp_high
new value ramp_low
ramp_high = "FV-1" Target + 1
ramp_low = "FV-1" Target - 1

"FV-1" target = "FV-1 PRIMARY" value

if "FV-1 PT100" value >= ramp_high
   sleep 500
   goto "Active-Ramp"
endif

if "FV-1 PT100" value <= ramp_low
   sleep 500
   goto "Active-Ramp"
endif
 
The more I play with the UI, the need for a path property for:
Alarms wav file
Element Background Image (you could provide it for Image 1 only)
Workspace Background Image

The property could be "path".
//Alarm
"Alarm 1" path = "c:\BruControl\Media\whining.wav"
//Element
//Replaces path in Image 1
"Brew Kettle Burner" path = "c:\BruControl\Media\jetburnerOn.png"

Workspace
workspace "Workspace 1" path = "c:\BruControl\Media\mynewpony.png"

where you could change the path would basically make it unlimited Alarm wav files and Background images. I find 3 images not enough and only one Alarm Wav file forces me to create different alarms for different wav file.

I could get rid of lots of Alarms (mostly hidden and used just for the wav file and
Background images would allow me to show flow desired patterns based on the conditions with a background images.

I think it could only be interactive with Global Elements. If I could make hoses or pipes with Globals, it could be interactive base on valves opened or closed.
 
I noticed that on the ESP 32 wiring map:

Pin 0 = D, O, P, R but port = N/A

Am I missing something on this port?

I really have no intention yet of adding any physical wiring to my ESP 32, but what does the future hold. I do plan to add another Mega for valves.

Right now, I am using the ESP 32 for the Tilt and other Ports as Digital Outs to turn on other Digital Outs on a different wired Mega. I like the way LEDS and Digital Outs look and feel on the UI so I am replacing many switches with "Fake Digital Out" so I can press the LED to turn on my burners and so on. This also makes the code easier and fewer Elements as I was having two switches where a single digital out works well. I could have done with one switch but did not figure that out until I decided to do the fake digital outs.

This also allows me to use an "LED" Pump switch on a different Workspace that looks like the one on a different workspace (The real Pump Element).
 
I'm having an issue with a portion of my script. The idea is to be able to change the hysteresis device (FV-1) target via a global (FV-1 PRIMARY) as the script is running. This works correctly on the first iteration but the output is then stuck in that state. Any idea what I'm doing wrong?

Code:
    [Active-Ramp]
new value ramp_high
new value ramp_low
ramp_high = "FV-1" Target + 1
ramp_low = "FV-1" Target - 1

"FV-1" target = "FV-1 PRIMARY" value

if "FV-1 PT100" value >= ramp_high
   sleep 500
   goto "Active-Ramp"
endif

if "FV-1 PT100" value <= ramp_low
   sleep 500
   goto "Active-Ramp"
endif

I'm still writing my scripts and have not tested much about them but I would think it is not a best practice to recreate a value (like "ramp_high" or "ramp_low" in this case) every time through the loop. Which looks like every 0.5 seconds. I would pull the variable creation out of the loop and add more sleep time (at least one cycle, such that values can be updated before you call for it to be reassigned). This may not be the problem but that is more work being done that need not be done.
 
Last edited:
OK, here is the code I use for tilt averaging as well as some other calcs I do during fermentation. Attached is also a screenshot of the display with the element names (globals involved in this script are in yellow). Hopefully the comments in the code are pretty self explanatory.

Code:
[setup]
new value OG
OG precision = 0
new value CurrSG
CurrSG precision = 0
new value CurrAvg
CurrAvg = 0
new value OGcalc
OGcalc = 0
new value ABV
new value AA
new value SGAVG
SGAVG precision = 4
new value SGRAW
SGRAW precision = 4
new value SGUPD
SGUPD precision = 4
SGAVG = 0
SGRAW = 0
new value Tilt
Tilt = 0
new bool FV1Active
new bool FV2Active
FV1Active = False
FV2Active = False



[check_active]
if "FV1 Control" state == True
    if FV1Active == False
        //Set AVG to current Tilt value on startup
        "FV1 SG Avg" Value = "FV1 Tilt" SG
        FV1Active = True
    endif
    if "FV1 Tilt" SG > 1.001  //Check if tilt reading is valid
        Tilt = "FV1 Tilt" SG  //capture raw tilt reading
        SGUPD = "FV1 SG Avg" Value * .80  //weight current average at 80%
        OG = "FV1 OG" Value  //grab recipe OG for calcs
        CurrAvg = "FV1 SG Avg" Value  //grab current average for calcs
        call calculate
        //update calc display
        "FV1 SG Avg" Value = SGAVG
        "FV1 AA%" Value = AA
        "FV1 ABV%" Value = ABV
    endif
else
    FV1Active = False
endif
if "FV2 Control" state == True
    if FV2Active == False
        //Set AVG to current Tilt value on startup
        "FV2 SG Avg" Value = "FV2 Tilt" SG
        FV2Active = True
    endif
    if "FV2 Tilt" SG > 1.001  //Check if tilt reading is valid
        Tilt = "FV2 Tilt" SG  //capture raw tilt reading
        SGUPD = "FV2 SG Avg" Value * .80  //weight current average at 80%
        OG = "FV2 OG" Value  //grab recipe OG for calcs
        CurrAvg = "FV2 SG Avg" Value  //grab current average for calcs
        call calculate
        //update calc display
        "FV2 SG Avg" Value = SGAVG
        "FV2 AA%" Value = AA
        "FV2 ABV%" Value = ABV
    endif
else
    FV2Active = False
endif

sleep 600000
goto check_active

[calculate]
//Update rolling gravity average
SGRAW = Tilt * .20  //new tilt reading weighted at 20%
if SGUPD < .5  //make sure 80% average makes numerical sense
    SGUPD = Tilt
endif
SGAVG = SGUPD + SGRAW  //update average

//Update Attenuation
OGcalc = OG - 1
OGcalc = OGcalc * 1000
CurrSG = CurrAvg - 1
CurrSG = CurrSG * 1000
AA = OGCalc - CurrSG
AA = AA / OGCalc
AA = AA * 100

//Update ABV
ABV = OG - CurrAvg
ABV = ABV * 131.25
return

[end]
stop "Fermentation Calcs"

FermentScreen.png
 
I'm still writing my scripts and have not tested much about them but I would think it is not a best practice to recreate a value (like "ramp_high" or "ramp_low" in this case) every time through the loop. Which looks like every 0.5 seconds. I would pull the variable creation out of the loop and add more sleep time (at least one cycle, such that values can be updated before you call for it to be reassigned). This may not be the problem but that is more work being done that need not be done.
I agree. I originally had these values outside of the loop but kept getting issues where it seemed the stored value was lost. I ended up putting it back in the loop and that seemed to fix my issue.

Edit: I tried moving it out of the loop. I recall why its there now- I am allowing a change of FV-1 target during the script. If this is not inside this loop, this doesnt allow for the change. After the change, I need to redefine ramp_high/ramp_low using the new setpoint.
 
Last edited:
OK, here is the code I use for tilt averaging as well as some other calcs I do during fermentation. Attached is also a screenshot of the display with the element names (globals involved in this script are in yellow). Hopefully the comments in the code are pretty self explanatory.

Code:
[setup]
new value OG
OG precision = 0
new value CurrSG
CurrSG precision = 0
new value CurrAvg
CurrAvg = 0
new value OGcalc
OGcalc = 0
new value ABV
new value AA
new value SGAVG
SGAVG precision = 4
new value SGRAW
SGRAW precision = 4
new value SGUPD
SGUPD precision = 4
SGAVG = 0
SGRAW = 0
new value Tilt
Tilt = 0
new bool FV1Active
new bool FV2Active
FV1Active = False
FV2Active = False



[check_active]
if "FV1 Control" state == True
    if FV1Active == False
        //Set AVG to current Tilt value on startup
        "FV1 SG Avg" Value = "FV1 Tilt" SG
        FV1Active = True
    endif
    if "FV1 Tilt" SG > 1.001  //Check if tilt reading is valid
        Tilt = "FV1 Tilt" SG  //capture raw tilt reading
        SGUPD = "FV1 SG Avg" Value * .80  //weight current average at 80%
        OG = "FV1 OG" Value  //grab recipe OG for calcs
        CurrAvg = "FV1 SG Avg" Value  //grab current average for calcs
        call calculate
        //update calc display
        "FV1 SG Avg" Value = SGAVG
        "FV1 AA%" Value = AA
        "FV1 ABV%" Value = ABV
    endif
else
    FV1Active = False
endif
if "FV2 Control" state == True
    if FV2Active == False
        //Set AVG to current Tilt value on startup
        "FV2 SG Avg" Value = "FV2 Tilt" SG
        FV2Active = True
    endif
    if "FV2 Tilt" SG > 1.001  //Check if tilt reading is valid
        Tilt = "FV2 Tilt" SG  //capture raw tilt reading
        SGUPD = "FV2 SG Avg" Value * .80  //weight current average at 80%
        OG = "FV2 OG" Value  //grab recipe OG for calcs
        CurrAvg = "FV2 SG Avg" Value  //grab current average for calcs
        call calculate
        //update calc display
        "FV2 SG Avg" Value = SGAVG
        "FV2 AA%" Value = AA
        "FV2 ABV%" Value = ABV
    endif
else
    FV2Active = False
endif

sleep 600000
goto check_active

[calculate]
//Update rolling gravity average
SGRAW = Tilt * .20  //new tilt reading weighted at 20%
if SGUPD < .5  //make sure 80% average makes numerical sense
    SGUPD = Tilt
endif
SGAVG = SGUPD + SGRAW  //update average

//Update Attenuation
OGcalc = OG - 1
OGcalc = OGcalc * 1000
CurrSG = CurrAvg - 1
CurrSG = CurrSG * 1000
AA = OGCalc - CurrSG
AA = AA / OGCalc
AA = AA * 100

//Update ABV
ABV = OG - CurrAvg
ABV = ABV * 131.25
return

[end]
stop "Fermentation Calcs"

View attachment 660570
Awesome. Thankyou for sharing

What do you use the reset button for?
 
General Question regarding Hysteresis and PID Elements:

There is a Target (easy to Understand although I use Globals for Setpoints as I like the Standard Look and just use scripts to update the target)


and there is the Input where there is a Numeric Valve. Does anyone use this value? Is it useful to "always" see this Value. I am thinking to replace the PID or Hysteresis with a Global pix of an Element (Basically powered or Not, but not actually interactive with the Input Value. I had a small element graphic that showed up red inside my vessels when I was commanding heat on the BCS. It was also not interactive but simply On or Off. Just another Clue that it should be "working".

I would likely put the true Element PID or Hysteresis on a different Workspace and just have the Heating Element Pix On/Off on the Main Brew Page.

Sorry, I don't quite understand what you are looking for. I personally hide the input device element since it is duplicitous. If you don't want the input value shown in these Hysteresis to PID elements, then you would need to handle it via a script. We include it thinking more info is better.
 
I'm having an issue with a portion of my script. The idea is to be able to change the hysteresis device (FV-1) target via a global (FV-1 PRIMARY) as the script is running. This works correctly on the first iteration but the output is then stuck in that state. Any idea what I'm doing wrong?

Code:
    [Active-Ramp]
new value ramp_high
new value ramp_low
ramp_high = "FV-1" Target + 1
ramp_low = "FV-1" Target - 1

"FV-1" target = "FV-1 PRIMARY" value

if "FV-1 PT100" value >= ramp_high
   sleep 500
   goto "Active-Ramp"
endif

if "FV-1 PT100" value <= ramp_low
   sleep 500
   goto "Active-Ramp"
endif

What do you mean "Output" is stuck in that state? If you mean it is not reading, that is because you are re-establishing the target every 500 milliseconds. With a refresh interval at 1000 milliseconds, it can never get read. Change the 500 ms sleeps to 5000 and see what happens.

Also, minor programming practice thing, but you should not have both >= and <= in subsequent comparisons... the equals should only appear in one, since the first equals will execute first.
 
What do you mean "Output" is stuck in that state? If you mean it is not reading, that is because you are re-establishing the target every 500 milliseconds. With a refresh interval at 1000 milliseconds, it can never get read. Change the 500 ms sleeps to 5000 and see what happens.

Also, minor programming practice thing, but you should not have both >= and <= in subsequent comparisons... the equals should only appear in one, since the first equals will execute first.
OK, i understand. The hysteresis device is trying to read every 1 second but yet I'm changing it every .5 seconds. So my sleep command must be greater then refresh interval.

I see what you mean about >= Ill revise thankyou for the tip.
 
I noticed that on the ESP 32 wiring map:

Pin 0 = D, O, P, R but port = N/A

Am I missing something on this port?

I really have no intention yet of adding any physical wiring to my ESP 32, but what does the future hold. I do plan to add another Mega for valves.

Right now, I am using the ESP 32 for the Tilt and other Ports as Digital Outs to turn on other Digital Outs on a different wired Mega. I like the way LEDS and Digital Outs look and feel on the UI so I am replacing many switches with "Fake Digital Out" so I can press the LED to turn on my burners and so on. This also makes the code easier and fewer Elements as I was having two switches where a single digital out works well. I could have done with one switch but did not figure that out until I decided to do the fake digital outs.

This also allows me to use an "LED" Pump switch on a different Workspace that looks like the one on a different workspace (The real Pump Element).

Typo. Thanks for pointing out.
 
OK, i understand. The hysteresis device is trying to read every 1 second but yet I'm changing it every .5 seconds. So my sleep command must be greater then refresh interval.

I see what you mean about >= Ill revise thankyou for the tip.

If you need to execute that fast, you just need to add a check that the target has changed, and only send it then.
 
I think the answer to @augiedoggy 's question is this "simple": There are many, many variables from one system to the next.

Bottom line is noise needs to be prevented, isolated, and squelched, in that order of priority. I know that doesn't help much - we need a comprehensive bit of documentation on the topic.
 
Back
Top