(by mcguinn)
I was looking for a way to use Annex to measure waterlevels, and that with as little extra hardware as possible. I ended up using pwm and analogue voltage measurement. If a probe is used with enough capacitance swing, it is accurate enough for home-use
Details and schematic are in the code.
Constructing a capacitive probe doesn't have to be too complicated: I used 2 copperfoil strips in a laminating pouch.
When you have to monitor levels of more than the size of such a pouch, you will have to be creative with, for instance, metal strips and heat-shrinkable tube.
With the dimesioned 27k resistor the capacitance (when fully immersed) must be about 150 pf or over. Much bigger is much better.
Expect a jitter of about 4 % (less with higher capacitance). Twisted pair leads, not too long, can be used to connect the probe(s)
After starting the program begin with adding a new probe. Then open the setup for that probe and fill in the particulars.
If no valid pwm pinnumber is supplied (ie <0), then the program works in demo-mode.
When calibrating a probe (learning what is 100% and what is 0%) the hintbox shows the steps to take.
I think it can also be used to monitor the moisturelevel in a flowerpot, but I'm not sure about how water is distributed in the soil.
Or use it to detect waterspillage on the floor.
On the pictures:
One shows the laminated probes, the others show a rough test-setup.
The bottle was previously calibrated on 100% bottle is 100% probe. Then it is filled up at 25%, 50%, 75% and 100%.
Hope it's useful for someone out there.
Greetings
mcGuinn
Code: [Local Link Removed for Guests]
'capacitive-probe fluidlevel meters
'can show up to 5 levelmeters indicating the amount of fluid in a tank
'intended for watery fluids.
'Written by Mcguinn 07/2020 using Annex version 1.41.beta 6. Tested on Wemos D1 Mini esp8266
'Free to use. No warranties given. Only intended as an example.
'A probe here acts as a capacitor whose value changes with the amount of water covering the plates.
'A squarewave charges and discharges this capacitor through a resistor. A peakdetector feeds the resulting voltage to the adc.
'When calibrating a probe, immersed to the maximum level, the program looks for a frequency and dutycycle that result in a certain adc reading.
'After that, with these parameters, the adc is read with the probe immersed to the minimum level.
'The values read during normal operation are converted to a percentage of minimum-maximum, and shown by an html meter-element.
'When reaching a certain percentage, actions can be executed. eg switching on a pump or sounding an alarm
'Please note that readings are not linear, but close enough for a fair indication.
'The probe circuit is VERY sensitive to jittery contacts. If you build it on a breadboard, expect jittery readings.
'schematic for probe(s):
' _____ 1n4148 A0 ______
'pwm(pin) o---[_27K_]------|-----[>|--x--|-o---|_220k_|---|--------o adc
' _|_ _|_ ---
' probe --- --- 1uf | | 100k
' | | | |
' | | ---
' |__________x__|________________|________o gnd
'
'to the left of x, 1 probe. Other probes connected in parallel on the x points, Voltage devider (220k/100k) is incorporated in D1 mini
'about actions:
'Actions are short pieces of annex code typed into a textfield; they can be saved.
'Action fields may be empty or filled with commands that are executed using the COMMAND directive
'for example to switch on a pump when reaching a high level, and switching the pump off when reaching a safe level
'redaction PUMP=13: PIN.MODE PUMP,OUTPUT:PIN(PUMP)=1
'optimumaction PUMP=13:PIN.MODE PUMP,OUTPUT:PIN(PUMP)=0
'for more complex actions: write it out in programlines as a subroutine under a label.
'In the actionline put: GOSUB <label>
'(because the action is executed by COMMAND, only simple commands can be used, no jumps,loops,functions, if then, pause and such
'therefore if the action begins with GOSUB it is handled in a different way)
'furthermore: if you want to use a literal string, enclose it in || instead of "" (otherwise websocket complains)
'to show for example an alert message as an action, put in: jscall |alert('your warningtext')|
'Actions are executed once and reset (prepared for next execution) when reaching a certain level (even if that actionlevel has no action assigned to it)
'Optimum resets orange and red actions, orange cq red reset optimum action
'an empty actionfield means 'no action' and it's actionstatus will be 0, otherwise 1.
'When an action is executed, the actionstatus is changed from 1 to -1, and on reset back to 1
'About optimum, high, low:
'The meter-element normally has a green bar indicating the value between min and max. here 0..100 (percentage)
'It can also have a low, high and optimum value that are used to control the bar-colour from green to orange to red
'The colorchange can be in the lower values region or in the higher values region.
'When monitoring a falling level: value>high=green; value<=high=orange; value<low=red. requires optimum>high
'When monitoring a rising level: value<low=green; value>=low=orange; value>high=red. requires optimum<low
'when these 3 values are all 0 there will be no colorchange and no actions will be executed
maxprobes=5' if increased also create subroutines setupN, accordingly
usedprobes=0'number of defined probes
'for main webpage
dim filename$(maxprobes)'1 file per probe
dim reading$(maxprobes)'to indicate which probe is being processed
dim full$(maxprobes)' if probe reads maximum or above, has the word 'Full'
dim meter(maxprobes)'has the percent value for the level-meter
dim empty$(maxprobes)'if probe reads minimum or below, has the word 'Empty'
dim percentage(maxprobes)'the percentage for the probe. can show below 0 or over 100
dim litre(maxprobes)'if a coversion quantity is given, shows the measured volume of liquid in litres
dim actions$(maxprobes)'shows the actionstatus of the 3 actions (0, 1 or -1)
defbackcolour$="lightgreen"
defbordercolour$="green"
pollintervallist$="0,5,30,60,300,600"'time between the end of a series of measurements and the start of the next series. 0=off
pollinterval$=word$(pollintervallist$,1,",")
newtime$=""'when not connected to the internet, the systemtime can be input
systemtime$=""'shows systemtime
'various variables
dummy=0
dummy$=""
pollprobenr=0'probenr currently being processed
demoactive=0'if no defined probe has been assigned to a valid pwm pin. the program will run in demo mode.
timer0interval=1000
buttonsvisible=0
pollinterval=val(pollinterval$)
pollrunning=0
polldowncounter=0
probename$="/probes/probe"'basename for probefiles
pollintervalname$="/probes/pollinterval"'last set pollinterval is saved in this file
wwwconnected=ping ("8.8.8.8")'if not on the internet, systemtime can be input
'to keep track of actions to perform
'0= no action, 1=to perform when level reaches set point, -1=action has been performed
dim orangeaction(maxprobes)
dim redaction(maxprobes)
dim optimumaction(maxprobes)
'probefile data
caption$=""'informative text
backcolour$=""'for quick recognition
lowperc=0'level where meter changes colour
highperc=0'see above
optimumperc=0'pivot value that determines monitoring rising or falling level (high level orange/red or low level orange/red)
orangeaction$=""'action to perform when level reaches orange point
redaction$=""'action to perform when level reaches red point
optimumaction$=""'action to perform when reaching safe point
fulllitres=0'if <>0, coversion of percentage to litres
pwmgpionr=-1'pin to use for measuring. <0 is not valid/active
'following values filled by probe calibration
pwmfreq=0'the frequency of the pwm signal
maximumadc=0'adc value when probe immersed to maximum level
minimumadc=0'adc value when probe immersed to minimum level
pwmdutycycle=0'dutycycle of the pwm signal
'for setup webpage
setupprobenr=0
hint$=""' shows steps to take when calibrating
calibratestepnr=0'step to perform when calibrate is clicked
backcolourlist$="lightblue,mediumseagreen,mediumvioletred,lightslategray,lightgreen,orange,red,sandybrown,sienna,tan"'colors to choose
pausetime=500'timepause between consecutive measurements
'if a pollinterval was set earlier, use it
if file.exists(pollintervalname$)=1 then
pollinterval$=file.read$(pollintervalname$)
changeinterval
end if
countprobes -1'count the number of probes defined and initialise actionstatus
if usedprobes=0 then
pollinterval$="0"
changeinterval
end if
gosub fillmainpage
if pollinterval=0 then
show_setupbuttons
else
hide_setupbuttons
end if
timer0 timer0interval,timer0routine
wait
'---------------------
dummy:
return
sub countprobes(initactions)
usedprobes=0
demoactive=1
for t=1 to maxprobes
f$=probename$+str$(t)
if file.exists(f$)=1 then
usedprobes=usedprobes+1
filename$(usedprobes)=f$
readprobefile usedprobes
if pwmgpionr>=0 then demoactive=0'there is a defined probe, no demo
if (initactions=-1) or (usedprobes=initactions) then' for all, or just this one probe, init actions
redaction(usedprobes)=(redaction$<>"")
orangeaction(usedprobes)=(orangeaction$<>"")
optimumaction(usedprobes)=(optimumaction$<>"")
end if
end if
next t
end sub
fillmainpage:
cls
autorefresh 1000
p$="<div style='background-color: #20B2AA;'>"
'meters and various data are put in a table with as many columns as there are probes
p=100/maxprobes'width percentage per probe
p$=p$+"<style>table, td {border: 2px solid "+defbordercolour$+"; border-collapse:collapse;text-align: center;} table {border-spacing: 5px;background-color:"+defbackcolour$+"}</style>"
p$=p$+"<table id='leveltable' style='width:"+str$(p*usedprobes)+"%;'>"
l$=""
if demoactive then l$=" (Demo mode)"
p$=p$+"<caption><h3>Fluid levels"+l$+"</h3></caption>"
'row with filename(s)
p$=p$+"<tr>"
for t=1 to usedprobes
p$=p$+"<th>"
p$=p$+filename$(t)
p$=p$+"</th>"
next t
p$=p$+"</tr>"
'row with captions
p$=p$+"<tr>"
for t=1 to usedprobes
readprobefile t
p$=p$+"<th>"
p$=p$+caption$
p$=p$+"</th>"
next t
p$=p$+"</tr>"
'row with 'reading' textbox
p$=p$+"<tr>"
addproperty "readonly size='6' style='color:red; border:0px'",textbox$(dummy$,"ro_textbox"),l$
for t=1 to usedprobes
v$="reading$("+str$(t)+")"'variablename underlying the textbox
p$=p$+"<th>"
p$=p$+replace$(l$,"dummy$",v$)
p$=p$+"</th>"
next t
p$=p$+"</tr>"
'row with 'full' textbox
p$=p$+"<tr>"
addproperty "readonly size='6' style='color:red; border:0px'",textbox$(dummy$,"ro_textbox"),l$
for t=1 to usedprobes
v$="full$("+str$(t)+")"'variablename underlying the textbox
p$=p$+"<th>"
p$=p$+replace$(l$,"dummy$",v$)
p$=p$+"</th>"
next t
p$=p$+"</tr>"
'row with meter(s)
p$=p$+"<tr>"
l$="<br><br><br><br><br><meter style='background:CCC; width: 200px; height: 40px;transform: rotate(270deg);' data-var='dummy' id='meters' value='0' min='0.00' max='100.00' low='LLL' high='HHH' optimum='PPP'></meter>"
l$=l$+"<br><br><br><br><br><br>"
for t=1 to usedprobes
readprobefile t
v$="meter("+str$(t)+")"'variablename underlying the meter
p$=p$+"<td style='background:"+backcolour$+"'>"
m$=replace$(l$,"dummy",v$)
if lowperc+highperc+optimumperc=0 then
m$=replace$(m$,"low='LLL' high='HHH' optimum='PPP'","")
else
m$=replace$(m$,"LLL",str$(lowperc))
m$=replace$(m$,"HHH",str$(highperc))
m$=replace$(m$,"PPP",str$(optimumperc))
end if
p$=p$+m$
p$=p$+"</td>"
next t
p$=p$+"</tr>"
'send a chunk to prevent out of memory
html p$
p$=""
'row with 'empty' textbox
p$=p$+"<tr>"
addproperty "readonly size='6' style='color:red; border:0px'",textbox$(dummy$,"ro_textbox"),l$
for t=1 to usedprobes
v$="empty$("+str$(t)+")"'variablename underlying the textbox
p$=p$+"<td>"
p$=p$+replace$(l$,"dummy$",v$)
p$=p$+"</td>"
next t
p$=p$+"</tr>"
'row with percentagebox
p$=p$+"<tr>"
addproperty "readonly size='1' style='color:black; border:0px; text-align:center;'",textbox$(dummy,"ro_textbox"),l$
for t=1 to usedprobes
v$="percentage("+str$(t)+")"'variablename
p$=p$+"<td>"
p$=p$+replace$(l$,"dummy",v$)+" %"
p$=p$+"</td>"
next t
p$=p$+"</tr>"
'row with litres
p$=p$+"<tr>"
addproperty "readonly size='1' style='color:black; border:0px; text-align:center;'",textbox$(dummy,"ro_textbox"),l$
for t=1 to usedprobes
readprobefile t
if (demoactive=1) and (fulllitres=0) then fulllitres=20
v$="litre("+str$(t)+")"'variablename
p$=p$+"<td>"
if fulllitres>0 then p$=p$+replace$(l$,"dummy",v$)+"ltr"
p$=p$+"</td>"
next t
p$=p$+"</tr>"
'row with setup buttons
p$=p$+"<tr>"
l$=button$("Setup",dummy,"setupbtn")
for t=1 to usedprobes
v$="setup"+str$(t)'construct labelname
p$=p$+"<td>"
p$=p$+replace$(l$,"dummy",v$)
p$=p$+"</td>"
next t
p$=p$+"</tr>"
'row with 'actions' textbox
p$=p$+"<tr>"
addproperty "readonly size='12' style='color:red; border:0px'",textbox$(dummy$,"ro_textbox"),l$
for t=1 to usedprobes
v$="actions$("+str$(t)+")"'variablename
p$=p$+"<th>"
p$=p$+replace$(l$,"dummy$",v$)
p$=p$+"</th>"
next t
p$=p$+"</tr>"
p$=p$+"</table><br>"
'send a chunk to prevent out of memory
html p$
p$=""
'table with pollinterval, new probe, systemtime and time setup
p$=p$+"<table style='width: 40%;'>"
p$=p$+"<caption><h3>For setup-functions polling must be off</h3></caption>"
'row with headers
p$=p$+"<tr>"
p$=p$+"<th>Polling interval in seconds (o=Off)</th>"
p$=p$+"<th>Add a new probe</th>"
p$=p$+"<th>System time</th>"
if wwwconnected=0 then p$=p$+"<th>Set time hh:mm</th>"
p$=p$+"</tr>"
'row with datafields and buttons
p$=p$+"<tr>"
p$=p$+"<td>"
'listbox pollinterval
l$=listbox$(pollinterval$,pollintervallist$,1,"polllistbox")
'replace the standard 'Choose here' by a more informative prompt
l$=replace$(l$,"Choose here","Poll every")
p$=p$+l$
addproperty "readonly size='4' style='color:black; border:0px; text-align:center;'",textbox$(polldowncounter,"ro_textbox"),l$
p$=p$+l$
p$=p$+"</td>"
p$=p$+"<td>"
'new probe button
if usedprobes<maxprobes then p$=p$+button$("new probe",newprobe,"setupbtn")
p$=p$+"</td>"
p$=p$+"<td>"
'systemtime
addproperty "readonly size='4' style='color:black; border:0px; text-align:center;'",textbox$(systemtime$,"ro_textbox"),l$
p$=p$+l$
p$=p$+"</td>"
if wwwconnected=0 then
p$=p$+"<td>"
'setuptime button and input time
p$=p$+button$("Set time",setuptime,"setupbtn")
addproperty "size='1' placeholder='hh:mm'",textbox$(newtime$,"settimebox"),l$
p$=p$+l$
p$=p$+"</td>"
end if
p$=p$+"</tr>"
p$=p$+"</table><br><br><br><br></div>"
p$=p$+cssid$("setupbtn", "text-align:center;background-color:"+defbackcolour$+";")
p$=p$+cssid$("ro_textbox", "text-align:center;background-color:"+defbackcolour$+";")
p$=p$+cssid$("polllistbox", "text-align:center;background-color:"+defbackcolour$+";")
html p$
p$=""
ONHTMLRELOAD fillmainpage
OnHtmlChange checkchange'for the pollinterval listbox
return
checkchange:
if htmleventvar$="pollinterval$" then
changeinterval
end if
return
sub changeinterval
pollinterval=val(pollinterval$)
if pollinterval>0 then
hide_setupbuttons
polldowncounter=1'start poll immediately then delay as set
end if
file.save pollintervalname$,pollinterval$
end sub
sub addproperty(p$,h$,r$)
'add properties to an html element
'assuming there is an 'id=' clause in the element
r$=replace$(h$,"id=",p$+" id=")
end sub
setuptime:
'not too much testing on correct input. enter rubbish, get rubbish
y=val(word$(date$(2),1,"/"))
m=val(word$(date$(2),2,"/"))
d=val(word$(date$(2),3,"/"))
onerror skip 3
h=val(word$(newtime$,1,":"))
mi=val(word$(newtime$,2,":"))
settime h,m,d,h,mi
showmessage "Time set to "+left$(time$,5)
return
sub hide_setupbuttons
'hide setup buttons/time input textbox
html cssid$("setupbtn","visibility: hidden;")
html cssid$("settimebox","visibility: hidden;")
buttonsvisible=0
end sub
sub show_setupbuttons
'show setup buttons/time input textbox
html cssid$("setupbtn","visibility: visible;")
html cssid$("settimebox","visibility: visible;")
buttonsvisible=1
end sub
timer0routine:
systemtime$=left$(time$,5)'update that textbox
if pollrunning then return
if pollinterval=0 then
if buttonsvisible=0 then show_setupbuttons
else
if buttonsvisible=1 then hide_setupbuttons
polldowncounter=polldowncounter-1
if polldowncounter<1 then
pollrunning=1
gosub processprobes
polldowncounter=pollinterval
pollrunning=0
end if
end if
return
processprobes:
for pollprobenr=1 to usedprobes
readprobefile pollprobenr
'to determine the actions to take, need to know whether safe level is low or high
probetype=(optimumperc>highperc)*1+(optimumperc<lowperc)*2
if (pwmgpionr<0) and (demoactive=0) then
reading$(pollprobenr)="Not active."
else
reading$(pollprobenr)="Reading..."
if demoactive=1 then
'fake values
select case probetype
case 0,2,3'rising or treat as rising
mvalue=meter(pollprobenr)+rnd(20)
if (mvalue>110) or (mvalue<0) then mvalue=0'<0 could be some old 'non demo' value
case 1'falling
mvalue=meter(pollprobenr)-rnd(20)
if (mvalue>110) or (mvalue<0) then mvalue=100'>110 could be some old 'non demo' value
end select
else
'read real values
scalefactor=100
onerror skip 1'to deal with minimumadc = maximumadc
scalefactor=int(100000/(minimumadc-maximumadc))/1000'limit to 3 decimals. To convert adcvalue to a percentage
adcval=0
getaveragedadc pwmfreq,pwmgpionr,pwmdutycycle,adcval
valuecorrection pollprobenr,maximumadc,minimumadc,adcval'now, effectively, an empty routine, but the shape of tank could call for correction of the adcval
mvalue=int((minimumadc-adcval)*scalefactor+0.5)
end if
'display values and do actions
percentage(pollprobenr)=mvalue
meter(pollprobenr)=mvalue
litre(pollprobenr)=fulllitres*mvalue/100
if meter(pollprobenr)>=100 then
full$(pollprobenr)="FULL"
else
full$(pollprobenr)=""
end if '
if meter(pollprobenr)<=0 then
empty$(pollprobenr)="EMPTY"
else
empty$(pollprobenr)=""
end if
if (optimumperc+highperc+lowperc)>0 then
testforaction
else
actions$(pollprobenr)="no action"
end if
if pollinterval>0 then pause 1000'user not waiting to do setup
reading$(pollprobenr)=left$(time$,5)'update last time processed
end if
if pollinterval=0 then exit for'user waiting to do setup
next pollprobenr
return
sub testforaction
select case probetype
case 0,3'no orange, red, optimum defined in a sensible way, so no action
actions$(pollprobenr)="no action"
case 1'falling. value: >=optimumperc optimumaction <=highperc orangeaction <lowperc redaction
if mvalue>=optimumperc then'optimum
'reset red and orange action
redaction(pollprobenr)=abs(redaction(pollprobenr))
orangeaction(pollprobenr)=abs(orangeaction(pollprobenr))
if optimumaction(pollprobenr)>0 then
'optimumaction not yet performed
optimumaction(pollprobenr)=-1
action$=optimumaction$
gosub doaction
end if
end if
if mvalue<=highperc then'orange
'reset optimum action
optimumaction(pollprobenr)=abs(optimumaction(pollprobenr))
if orangeaction(pollprobenr)>0 then
orangeaction(pollprobenr)=-1
action$=orangeaction$
gosub doaction
end if
end if
if mvalue<lowperc then'red
optimumaction(pollprobenr)=abs(optimumaction(pollprobenr))
if redaction(pollprobenr)>0 then
redaction(pollprobenr)=-1
action$=redaction$
gosub doaction
end if
end if
updateactions pollprobenr
case 2'rising. value: <=optimumperc optimumactions >=low orangeaction >high redaction
if mvalue<=optimumperc then'optimum
redaction(pollprobenr)=abs(redaction(pollprobenr))
orangeaction(pollprobenr)=abs(orangeaction(pollprobenr))
if optimumaction(pollprobenr)>0 then
optimumaction(pollprobenr)=-1
action$= optimumaction$
gosub doaction
end if
end if
if mvalue>=lowperc then'orange
optimumaction(pollprobenr)=abs(optimumaction(pollprobenr))
if orangeaction(pollprobenr)>0 then
orangeaction(pollprobenr)=-1
action$= orangeaction$
gosub doaction
end if
end if
if mvalue>highperc then'red
optimumaction(pollprobenr)=abs(optimumaction(pollprobenr))
if redaction(pollprobenr)>0 then
redaction(pollprobenr)=-1
action$= redaction$
gosub doaction
end if
end if
updateactions pollprobenr
end select
end sub
testorangeaction:
demoactive=-demoactive'if demoactive=1 (>0) then actions are displayed not executed. for testing they must be executed
action$= orangeaction$
gosub doaction
demoactive=abs(demoactive)
return
testredaction:
demoactive=-demoactive
action$=redaction$
gosub doaction
demoactive=abs(demoactive)
return
testoptimumaction:
demoactive=-demoactive
action$= optimumaction$
gosub doaction
demoactive=abs(demoactive)
return
doaction:
if demoactive > 0 then
actions$(pollprobenr)=action$'show action but do not execute
refresh
pause 1000
return
end if
onerror clear
if instr(ucase$(action$),"GOSUB")=1 then
lab$=trim$(mid$(action$,6))
onerror goto syntaxerror
gosub lab$
onerror goto off
return
end if
onerror goto syntaxerror
command action$
onerror goto off
return
syntaxerror:
x$="error in: "+action$+":"+str$(bas.errnum)+" "+bas.errmsg$
showmessage x$
return
sub updateactions(p)
actions$(p)="red:"+str$(redaction(p))+" ora:"+str$(orangeaction(p))+" opt:"+str$(optimumaction(p))
end sub
sub valuecorrection (p,ma,mi,av)'probenr, maximumadc, minimumadc, adcval
correctionfactor=1
av=av*correctionfactor
'if the tank tapers like a bottle or bucket, the higher/lower levelreadings could need a correction
'especially true if a conversion to litres follows
'besides that, the readings are not linear. If you need more linearity,
'there has to be a correction, and that may be different for each probe
end sub
'for each possible probe (maxprobes) there must be a setup-routine
setup1:
setupprobenr=1
setupprobe
return
setup2:
setupprobenr= 2
setupprobe
return
setup3:
setupprobenr= 3
setupprobe
return
setup4:
setupprobenr= 4
setupprobe
return
setup5:
setupprobenr= 5
setupprobe
return
sub setupprobe
ONHTMLRELOAD off
hint$=""
calibratestepnr=0
readprobefile setupprobenr
gosub fillsetuppage
end sub
fillsetuppage:
cls
autorefresh 1000
p$="<div style='background-color: #20B2AA;'>"
l$=""
'probedata fields are put in a table
p$=p$+"<style>table, td {border: 2px solid "+defbordercolour$+"; border-collapse:collapse;text-align: center;} table {border-spacing: 5px;background-color:"+defbackcolour$+"}</style>"
p$=p$+"<table id='probetable' style='width:20%;'>"
p$=p$+"<caption><h3>Setup probe #"+str$(setupprobenr)+"</h3></caption>"
'row with filename
p$=p$+"<tr>"
p$=p$+"<th>"
p$=p$+"Filename:"
p$=p$+"</th>"
p$=p$+"<th>"
p$=p$+filename$(setupprobenr)
p$=p$+"</th>"
p$=p$+"</tr>"
'row with caption
p$=p$+"<tr>"
p$=p$+"<th>"
p$=p$+"Caption:"
p$=p$+"</th>"
p$=p$+"<td>"
addproperty "size='20' style='color:black; border:0px; text-align:center;'",textbox$(caption$,"captionbox"),l$
p$=p$+l$
p$=p$+"</td>"
p$=p$+"</tr>"
'row with backcolour
p$=p$+"<tr>"
p$=p$+"<th>"
p$=p$+"Backcolour:"
p$=p$+"</th>"
p$=p$+"<td>"
l$=listbox$(backcolour$,backcolourlist$,1,"colourlistbox")
p$=p$+l$
p$=p$+"</td>"
p$=p$+"</tr>"
'row with lowperc
p$=p$+"<tr>"
p$=p$+"<th>"
p$=p$+"Low %%:"
p$=p$+"</th>"
p$=p$+"<td>"
addproperty "size='3' style='color:black; border:0px; text-align:center;'",textbox$(lowperc,"lowpercbox"),l$
p$=p$+l$
p$=p$+"</td>"
p$=p$+"</tr>"
'row with highperc
p$=p$+"<tr>"
p$=p$+"<th>"
p$=p$+"High %%:"
p$=p$+"</th>"
p$=p$+"<td>"
addproperty "size='3' style='color:black; border:0px; text-align:center;'",textbox$(highperc,"highpercbox"),l$
p$=p$+l$
p$=p$+"</td>"
p$=p$+"</tr>"
'row with optimumperc
p$=p$+"<tr>"
p$=p$+"<th>"
p$=p$+"Optimum %%:"
p$=p$+"</th>"
p$=p$+"<td>"
addproperty "size='3' style='color:black; border:0px; text-align:center;'",textbox$(optimumperc,"optimumpercbox"),l$
p$=p$+l$
p$=p$+"</td>"
p$=p$+"</tr>"
'row with orange action
p$=p$+"<tr>"
p$=p$+"<th>"
addproperty "title='Click to execute' style='width:120px;background-color:"+defbackcolour$+"';",button$("Orange action",testorangeaction,"testbutton"),l$
p$=p$+l$
p$=p$+"</th>"
p$=p$+"<td>"
addproperty "size='40' style='color:black; border:0px; text-align:center;'",textbox$(orangeaction$,"orangeactionbox"),l$
p$=p$+l$
p$=p$+"</td>"
p$=p$+"</tr>"
'row with red action
p$=p$+"<tr>"
p$=p$+"<th>"
addproperty "title='Click to execute' style='width:120px;background-color:"+defbackcolour$+"';",button$("Red action",testredaction,"testbutton"),l$
p$=p$+l$
p$=p$+"</th>"
p$=p$+"<td>"
addproperty "size='40' style='color:black; border:0px; text-align:center;'",textbox$(redaction$,"redactionbox"),l$
p$=p$+l$
p$=p$+"</td>"
p$=p$+"</tr>"
'row with optimumaction
p$=p$+"<tr>"
p$=p$+"<th>"
addproperty "title='Click to execute' style='width:120px;background-color:"+defbackcolour$+"';",button$("Optimum action",testoptimumaction,"testbutton"),l$
p$=p$+l$
p$=p$+"</th>"
p$=p$+"<td>"
addproperty "size='40' style='color:black; border:0px; text-align:center;'",textbox$(optimumaction$,"optimumactionbox"),l$
p$=p$+l$
p$=p$+"</td>"
p$=p$+"</tr>"
'row with fulllitres
p$=p$+"<tr>"
p$=p$+"<th>"
p$=p$+"Full litres:"
p$=p$+"</th>"
p$=p$+"<td>"
addproperty "size='3' style='color:black; border:0px; text-align:center;'",textbox$(fulllitres,"fulllitresbox"),l$
p$=p$+l$
p$=p$+"</td>"
p$=p$+"</tr>"
'row with pwmgpio
p$=p$+"<tr>"
p$=p$+"<th>"
p$=p$+"PWM gpionr:"
p$=p$+"</th>"
p$=p$+"<td>"
addproperty "size='3' style='color:black; border:0px; text-align:center;'",textbox$(pwmgpionr,"pwmgpionrbox"),l$
p$=p$+l$
p$=p$+"</td>"
p$=p$+"</tr>"
html p$
p$=""
'row with calibratebutton
p$=p$+"<tr>"
p$=p$+"<th>"
addproperty "title='Click to calibrate probe' style='width:120px;background-color:"+defbackcolour$+"';",button$("Calibrate",calibrateprobe,"calibratebtn"),l$
p$=p$+l$
p$=p$+"</th>"
p$=p$+"<td>"
addproperty "readonly rows='4' cols='50' ",textarea$(hint$,"hintbox"),l$
p$=p$+l$
p$=p$+"</td>"
p$=p$+"</tr>"
'row with pwmfreq
p$=p$+"<tr>"
p$=p$+"<th>"
p$=p$+"PWM Freq.:"
p$=p$+"</th>"
p$=p$+"<td>"
addproperty "readonly size='6' style='color:black;background-color:"+defbackcolour$+"; border:0px; text-align:center;'",textbox$(pwmfreq,"pwmfreqbox"),l$
p$=p$+l$
p$=p$+"</td>"
p$=p$+"</tr>"
'row with maximumadc
p$=p$+"<tr>"
p$=p$+"<th>"
p$=p$+"maximum adc:"
p$=p$+"</th>"
p$=p$+"<td>"
addproperty "readonly size='6' style='color:black; background-color:"+defbackcolour$+"; border:0px; text-align:center;'",textbox$(maximumadc,"maximumadcbox"),l$
p$=p$+l$
p$=p$+"</td>"
p$=p$+"</tr>"
'row with minimumadc
p$=p$+"<tr>"
p$=p$+"<th>"
p$=p$+"minimum adc:"
p$=p$+"</th>"
p$=p$+"<td>"
addproperty "readonly size='6' style='color:black; background-color:"+defbackcolour$+";border:0px; text-align:center;'",textbox$(minimumadc,"minimumadcbox"),l$
p$=p$+l$
p$=p$+"</td>"
p$=p$+"</tr>"
'row with pwmdutycycle
p$=p$+"<tr>"
p$=p$+"<th>"
p$=p$+"pwmdutycycle:"
p$=p$+"</th>"
p$=p$+"<td>"
addproperty "readonly size='6' style='color:black; background-color:"+defbackcolour$+";border:0px; text-align:center;'",textbox$(pwmdutycycle,"pwmdutycyclebox"),l$
p$=p$+l$
p$=p$+"</td>"
p$=p$+"</tr>"
'row with save cancel delete buttons
p$=p$+"<tr>"
p$=p$+"<th>"
p$=p$+"What to do:"
p$=p$+"</th>"
p$=p$+"<td>"
p$=p$+button$("Save",saveprobe,"savebtn")+" "+button$("Cancel",cancelprobe,"cancelbtn")+" "+button$("Delete",deleteprobe,"deletebtn")
p$=p$+"</td>"
p$=p$+"</tr>"
p$=p$+"</table><br><br><br><br><br></div>"
html p$
p$=""
return
calibrateprobe:
if pwmgpionr<0 then
showmessage "First enter a valid pwmgpionr.(0 or higher)"
return
end if
select case calibratestepnr
case 0
hint$="Immerse the probe to maximum level and then click on 'Calibrate'"
calibratestepnr=1
case 1
o=0
learnmaximum o'o for out of bounds 0=no 1=yes
if o=0 then
hint$="Immerse the probe to minimum level and then click on 'Calibrate'"
calibratestepnr=2
else
'capacity too high or too low
'text for the hint$ has already been set in the learnmaximum sub
calibratestepnr=0
end if
case 2
hint$= "Busy..."
refresh
v1=0
v2=-1
'need 2 (nearly) identical readings
do
getaveragedadc pwmfreq,pwmgpionr,pwmdutycycle,v1
if abs(v1-v2)<=1 then exit do
v2=v1
pause pausetime
loop while 1
minimumadc=int((v1+v2)/2)
hint$="Probe calibrated. Click on 'Save' to use these settings."
calibratestepnr=0
end select
return
sub showmessage (m$)
m$=replace$(replace$(replace$(m$,"\","\\"),"/","\/"),"'","\'")'escape special characters
jscall "alert('"+m$+"')"
end sub
sub learnmaximum(outofbounds)
mindutycycle=150
maxdutycycle=600
testdutycycle=0
goal=550'steer towards this adc value
margin=2'goal plus or minus this value is ok
maxtestfreq=15000'higher frequencies show too inconsistent readings
mintestfreq=1000
adcval=0
amaxf=0
aminf=0
dutycyclestep=-50
hint$="busy..."
outofbounds=0
'test for the lowest adcval that can be achieved with this probe
'if the capacitance is too small, the adcval will still be higher than goal
getaveragedadc maxtestfreq,pwmgpionr,mindutycycle,adcval
if adcval>goal then
toosmall
else
'test for the highest adcval with this probe
'if the capacitance is too large, the adcval will still be lower than goal
getaveragedadc mintestfreq,pwmgpionr,maxdutycycle,adcval
if adcval<goal then
toolarge
end if
end if
if outofbounds=0 then
outofbounds=1
for testdutycycle=maxdutycycle to mindutycycle step dutycyclestep
'test if goal will be within range
getaveragedadc maxtestfreq,pwmgpionr,testdutycycle,amaxf
getaveragedadc mintestfreq,pwmgpionr,testdutycycle,aminf
if (aminf>=goal) and (amaxf<=goal) then
'in range, but it can be just marginal
outofbounds=0
stepfrequency
exit for
end if
hint$=hint$+"busy.."
next testdutycycle
if outofbounds=0 then
'probe still immersed to maximum level, read the maximum value
v1=0
v2=-1
'need 2 (nearly) identical readings
do
getaveragedadc pwmfreq,pwmgpionr,pwmdutycycle,v1
if abs(v1-v2)<=1 then exit do
v2=v1
pause pausetime
loop while 1
maximumadc=int((v1+v2)/2)
else
hint$= "Could not find a fitting range. Capacitance most likely too small"
end if
end if
end sub
sub toosmall
outofbounds=1
hint$="Capacitance too small (or disconnected probe?). Use a probe with larger plates."
end sub
sub toolarge
outofbounds=1
hint$="Capacitance too large (or shortcircuited?). Use a probe with smaller plates."
end sub
sub stepfrequency
freqstep=1000
testfreq=maxtestfreq
do while freqstep >0.1
f=testfreq-freqstep
getaveragedadc f,pwmgpionr,testdutycycle,adcval
if abs(adcval-goal)<=margin then'found it
testfreq=testfreq-freqstep
exit do
end if
if adcval<goal then
testfreq=testfreq-freqstep' this step was ok, but need more
if testfreq<mintestfreq then
outofbounds=1
exit do
end if
else
'there was an overshoot
freqstep=freqstep/10'step was too big, take a smaller step
end if
loop
if outofbounds=0 then
'found a fitting frequency and dutycycle
pwmfreq=testfreq
pwmdutycycle=testdutycycle
end if
end sub
sub getaveragedadc(f,p,d,v)'frequency, pin, dutycycle, returnvalue
'take the average of several measurements
local t,a
a=6'number of measurements
option.pwmfreq f
pwm(p)=d
pause pausetime'needs enough time to settle
v=0
for t=1 to a
v=v+adc
pause pausetime/2'some waittime between measurements
next t
v=int(v/a)'average to a whole number
pwm(p)=0'pin low behind the rectifier is of little influence to other probes
end sub
sub readprobefile(f)
record$=""
f$=filename$(f)
record$=file.read$(f$)
caption$=WORD.GETPARAM$(record$,"caption")
backcolour$=WORD.GETPARAM$(record$,"backcolour")
lowperc=val(WORD.GETPARAM$(record$,"lowperc"))
highperc=val(WORD.GETPARAM$(record$,"highperc"))
optimumperc=val(WORD.GETPARAM$(record$,"optimumperc"))
orangeaction$=WORD.GETPARAM$(record$,"orangeaction")
redaction$=WORD.GETPARAM$(record$,"redaction")
optimumaction$=WORD.GETPARAM$(record$,"optimumaction")
fulllitres=val(WORD.GETPARAM$(record$,"fulllitres"))
pwmgpionr=val(WORD.GETPARAM$(record$,"pwmgpionr"))
pwmfreq=val(WORD.GETPARAM$(record$,"pwmfreq"))
maximumadc=val(WORD.GETPARAM$(record$,"maximumadc"))
minimumadc=val(WORD.GETPARAM$(record$,"minimumadc"))
pwmdutycycle=val(WORD.GETPARAM$(record$,"pwmdutycycle"))
end sub
saveprobe:
'save settings for next run
record$=""
WORD.SETPARAM record$,"caption",caption$
WORD.SETPARAM record$,"backcolour",backcolour$
WORD.SETPARAM record$,"lowperc",str$(lowperc)
WORD.SETPARAM record$,"highperc",str$(highperc)
WORD.SETPARAM record$,"optimumperc",str$(optimumperc)
WORD.SETPARAM record$,"orangeaction",orangeaction$
WORD.SETPARAM record$,"redaction",redaction$
WORD.SETPARAM record$,"optimumaction",optimumaction$
WORD.SETPARAM record$,"fulllitres",str$(fulllitres)
WORD.SETPARAM record$,"pwmgpionr",str$(pwmgpionr)
WORD.SETPARAM record$,"pwmfreq",str$(pwmfreq)
WORD.SETPARAM record$,"maximumadc",str$(maximumadc)
WORD.SETPARAM record$,"minimumadc",str$(minimumadc)
WORD.SETPARAM record$,"pwmdutycycle",str$(pwmdutycycle)
file.save filename$(setupprobenr),record$
countprobes setupprobenr
dofinally
return
sub dofinally
gosub fillmainpage
end sub
deleteprobe:
x=file.delete (filename$(setupprobenr))
if x=0 then showmessage "Probe was NOT deleted!!"
countprobes -1
dofinally
return
newprobe:
onhtmlreload off
for t=1 to maxprobes
f$=probename$+str$(t)
if file.exists(f$)=0 then
file.save f$,"caption=new probe"+chr$(10)+"pwmgpionr=-1"
exit for
end if
next t
countprobes t
dofinally
return
cancelprobe:
dofinally
return