Picaxe Web Server Water System Monitor and Controller

lbenson

Senior Member
Project Outline


This project is a "table-driven" picaxe webserver program running on a 20M2. It reads 17 inputs representing water level switches, salinity monitors, water flow sensors, and temperature monitor, and controls 5 outputs representing pumps. It produces a web page which shows the input and output status and some history, and provides some elements of control.

History and Motivation

When I got my HLK-RM04 and TLG10UA03 wifi serial devices, it made me think that you could build a fairly complex wireless web page controller with a picaxe. Not long after I had this thought, user jsmanson posted his problem--two wells, 5 pumps, a reverse osmosis salinity filter, and many sensors: http://www.picaxeforum.co.uk/showthread.php?24779-Advice-needed-Water-Treatment-Monitoring-Project This seems perfect to me as a model. I started it on a 14M2 but migrated to a 20M2.

Here's a sample page from the finished project.
InitialScreen.jpg
My main purpose was to make a template for doing web pages on the picaxe, but the actual details of the water system took over to a degree. I’ll start with the choice of hardware, continue with the plan for data to be served by any web page, and then move on to the design of the water monitor system, which includes hardware simulation of all the sensors and pump control outputs.

Choice of PICAXE chip and Auxillaries

The trade-offs for choice of picaxe were basically between the 14M2, 18M2, and 20M2 chips with 512 bytes of RAM, and the X2 series with higher speed, more named registers, more I/O, and hardware background receive. I ultimately went with more RAM, with additional I/O, and so chose a 20M2 with an MCP23017 I2C 16-bit I/O extender.

I made extensive use of the RAM. The scratchpad on the X2s could have been used, but with no peek/poke and only @ptr for access, it would have been more awkward.

The rest of the hardware of the basic system consisted of a module which contained a DS3231 high-accuracy Real Time Clock and a AT24LC32 4 kilobyte eeprom, and a wifi-to-serial module, the TLG10UA03.

The MCP23017 handled the 8 float switch inputs and the 5 pump outputs.

In order to do hardware simulation, I included two 08M2s for pulse generation and counting for water flow measurement and a 14M2 to do simulation of the output of salinity sensors.
 

Attachments

Last edited:

lbenson

Senior Member
Web Page Template

The template for a web page to be displayed by the picaxe consists of ordinary HTML code, with the placement of variables to be filled in by the picaxe indicated by “~” followed by two numerals, for example, “~01”. When the page is to be served, the picaxe runs through the HTML code (stored in I2C eeprom) outputting the text and replacing each variable with the appropriate dynamic values. The template for this project is attached below—it’s 3,531 characters long and contains a chart and 26 variables.

I put a refresh time in it: <meta http-equiv="refresh" content="30" >. By experimentation, it turned out that 15 seconds was the ideal time. With 30, it occasionally timed out, with 10 it often timed out because it was still updating variable when the request was made. With the timeout of 15 seconds, I worked through a 47-step test series with only one timeout. A refresh gets the display back in sync.

[c:\home\user0\watermon_chart.html.template]

I loaded this web page template into the I2C eeprom using a lua program on the PC (which sends 16-byte chunks followed by a time delay to allow the receiving picaxe time to write to I2C) and an 08M2 programmed to receive the text. I installed LuaForWindows to give the PC the needed lua features.

Lua code:
Code:
-- sendSerial.lua sends serial to com port using luars232.dll
--http://ccgi.dougrice.plus.com/cgi-bin/wiki.pl?Serial-Lua
rs232 = require( "luars232" )
-- mode COM6:1200,n,8,1
--os.execute("mode COM6:1200,n,8,1")
port_name = "COM6"
local e, p = rs232.open(port_name)
  if e == rs232.RS232_ERR_NOERROR then
-- set port settings
    assert(p:set_baud_rate(rs232.RS232_BAUD_2400) == rs232.RS232_ERR_NOERROR)
    assert(p:set_data_bits(rs232.RS232_DATA_8) == rs232.RS232_ERR_NOERROR)
    assert(p:set_parity(rs232.RS232_PARITY_NONE) == rs232.RS232_ERR_NOERROR)
    assert(p:set_stop_bits(rs232.RS232_STOP_1) == rs232.RS232_ERR_NOERROR)
    assert(p:set_flow_control(rs232.RS232_FLOW_OFF)  == rs232.RS232_ERR_NOERROR)
    fn = "c:\\home\\user0\\watermon_chart.html.template"
    filein = io.open(fn,"r")
    if filein == nil then print("Couldn't open "..fn.."\n") os.exit() end
    txt = filein:read("*a")         -- read the whole file
    n = 1
    l = string.len(txt)
    k = l / 16
    for i = 1, k do
      a = string.sub(txt,n,n+15)
      n = n + 16
--      print(a)
      io.stdout:write(a)
      p:write(a)
      for m = 1, 10 do
        os.execute("dir > nul") -- delay for a while
      end
    end
    if k % 16 > 0 then 
      do
        a = string.sub(txt,n,k % 16 + n - 1)
        io.stdout:write(a)
        p:write(a)
       end
    end
    a = "<html>\n".."\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
    io.stdout:write(a)
    p:write(a)
    filein:close()
    assert(p:close() == rs232.RS232_ERR_NOERROR)
    print (k,n,k%16,a)
    
  else
    io.stdout:write(string.format("Serial Port '%s' not found\n",port_name))
  end
The only trick in the Lua program is this code:
for m = 1, 10
do os.execute("dir > nul") -- delay for a while
end
Lua doesn’t have a command to wait for a period, so this shells out and writes a directory to nowhere a number of times. The number depends on the speed of your PC—5 was sufficient for one, another took 25, and for the one I use most often, 10 was ok.

The picaxe program follows. The text is written starting at byte 16, and when all is done the length is written as a word at byte 0. The final length was 3915. With much more code I’d have had to go to a bigger device than the 4KB eeprom included on the RTC module I used.
Code:
' 08loadI2CSer flash i2c eeprom over serial

#picaxe 08M2                  ' leg 8: 0V; leg 7: O0, serout, infraout; leg 6: I1, O1, ADC1; leg 

#no_data
#terminal 2400 'turn on terminal after program load
'setfreq M2 ' all baud rates are halved

symbol tcpInPin = 3

'symbol baudrate=T600_4 ' serial in will be 300 baud
symbol baudrate=T2400_4 ' serial in will be 300 baud
symbol sertxBaud=N2400_4 ' 1200 baud
symbol i2cDevice=%10101110 ' note "111" address

symbol eeprom_Length_Addr=0
symbol htmlLengthAddr=0
symbol eeprom_base=16
symbol htmlBase=16
symbol inputBuffBase=120 ' WARNING! may wrap to b0

symbol value=b18
symbol n1 = b19

symbol year=b20
symbol month=b21
symbol day=b22
symbol hour=b23
symbol minute=b24

symbol eeprom_addr=w13
symbol addr_low=b26
symbol addr_high=b27
symbol wTemp1=w13
symbol wTemp1Low=b26
symbol wTemp1Hi=b27

symbol i2c_addr=s_w1
symbol i2c_addr_Limit=s_w2

start:
  pause 4000  ' startup time
'  high 0
  readtemp b.4,b4
  serout 0,sertxBaud,("08loadI2CSer ",#b4,cr,lf)
  pause 500  ' startup time
'  i2cslave %11010000, i2cslow, i2cbyte
'  writei2c 0, ($00, $22, $17, $03, $12, $12, $13, $10) ' smhwdmy
  gosub getDateTime
  gosub loadI2Ceeprom
  pause 10000
  gosub printTemplate

main:
  do
    pause 1000
    readtemp b.4,b4
'    sertxd ("Temp ",#b4,13,10)
    serout 0,sertxBaud,("Temp ",#b4,13,10)
    pause 1000
  loop

getDateTime:
'#rem
  i2cslave %11010000, i2cslow, i2cbyte
'  writei2c 0, ($00, $06, $17, $01, $09, $12, $13, $10) ' smhwdmy
  pause 40
  gosub getDate
'  sertxd ("Date is ", #year,":",#month,":",#day,13,10)
  serout 0,sertxBaud,("Date is ", #year,":",#month,":",#day,13,10)
  gosub getTime
'  sertxd ("Time is ", #hour,":",#minute,13,10)
  serout 0,sertxBaud,("Time is ", #hour,":",#minute,13,10)
  return

getTime:
  readi2c 0, (b1,b2,b3)
'    sertxd ("Getting Time3 ", #n1," ",#n2," ",#n3, 13, 10)
  bcdtoascii b1, b5, b6
'  second = n5-"0"*10+n6-"0"
  b4 = b2
  bcdtoascii b4, b1, b2
  minute = b1-"0"*10+b2-"0"
'sertxd ("n4 n1 n2 minute: ", #n4," ",n1," ",n2," ",#minute, 13, 10)
  bcdtoascii b3, b4, b5
  hour = b4-"0"*10+b5-"0"
  return

getDate:  ' 
  readi2c 4, (b1,b2,b3) ' xxxxdmy
'    sertxd ("Getting Date ", #n1," ",#n2," ",#n3, 13, 10)
  'day = bcdtobin n1
  'month = bcdtobin n2
  'year = bcdtobin n3
  bcdtoascii b1, b5, b6
  day = b5-"0"*10+b6-"0"
  b4 = b2
  bcdtoascii b2, b5, b6
  month = b5-"0"*10+b6-"0"
  bcdtoascii b3, b5, b6
  year = b5-"0"*10+b6-"0"
  return

loadI2Ceeprom:
  i2cslave i2cDevice, i2cfast_16, i2cword
  eeprom_addr=eeprom_base
'  sertxd ("Load to ",#eeprom_addr,13,10)
  b0 = i2cDevice / 2 & %00000111
  serout 0,sertxBaud,("Load to ",#eeprom_addr,"; I2C device ",#b0,13,10)
'  serout tcpOutPin, baudrate, ("14LoadI2C ",13,10)

  do
    serin tcpInPin, baudrate, b0,b1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11,b12,b13,b14,b15
'    sertxd (b0,b1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11,b12,b13,b14,b15)
    serout 0,sertxBaud,(b0,b1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11,b12,b13,b14,b15)
    writei2c eeprom_addr, (b0,b1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11,b12,b13,b14,b15)
    eeprom_addr = eeprom_addr + 16
    pause 10
    if b15 = 0 then
      bptr = 15
'      b15 = @bptrdec
      do while @bptrdec = 0 : dec eeprom_addr : loop
      eeprom_addr = eeprom_addr - 16
      writei2c eeprom_Length_Addr, (addr_low,addr_high)
'      sertxd (cr,lf,#eeprom_addr," bytes written",cr,lf)
      serout 0,sertxBaud,(cr,lf,#eeprom_addr," bytes written",cr,lf)
      pause 2000
      exit
    endif
    pause 40
  loop
  return

 printTemplate:
'#rem
  i2cslave i2cDevice, i2cfast_16, i2cword
  value = $fe
  i2c_addr = htmlLengthAddr  ' Length of html code
  readi2c i2c_addr, (wTemp1Low,wTemp1Hi)
  i2c_addr_Limit = wTemp1 + 16 ' don't read past this address
  i2c_addr = htmlBase  ' start of html with embedded "~nn" variables
  serout 0,sertxBaud,("HTML Template: base, length ",#i2c_addr," ",#wTemp1,cr,lf)
  'i2c_addr = 0
  do while value <> 0 and value <> $ff and i2c_addr < i2c_addr_Limit
    gosub readI2Cbuff
    bptr = bptr - 16
    for n1 = 1 to 16
      if @bptrinc = 0 then
	  dec bptr  ' point back to null
	  value = 0
	  do while n1 <= 16 : @bptrinc = " " : inc n1 loop
	endif
    next n1
    bptr = bptr - 16
    serout 0,sertxBaud,(@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc, _
      @bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptr)
  loop
  serout 0,sertxBaud,(cr,lf)
'#endrem
  return

readI2Cbuff:
  bptr = inputBuffBase
  readi2c i2c_addr, (@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc, _
    @bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc)
  i2c_addr=i2c_addr+16
  return
This template includes a 28-day chart of water usage, generated through Google Charts.

In the template image attached, you can see the locations of the "~" variables, "~01" through "~31" on the web screen page. You can't see ~24-~28 because the value filled in by the control program will include html control code giving the color (green or grey). The actual code in the template is invalid when presented directly to the browser, but the browser fails gracefully.
 

Attachments

Last edited:

lbenson

Senior Member
Variable Definition

The part of the web server program which deals with the definition of the variables to be displayed is &#8220;table driven&#8221;&#8212;that is, I made an excel table which defined the characteristics of the data&#8212;data type, source, address in RAM of variable value, name of variable.
Here are the data types I defined, bit, byte, word, etc.:
Code:
data type
    0          no data item
    1          bit data--stored length 2 bytes, null terminated
    2          byte data--stored length 4 bytes, null terminated
    3          word data--stored length 6 bytes, null terminated
    4          temperature--stored length 5 bytes, null terminated
    5          date--stored length from source field, which gives format
    6          time--stored length from source field, which gives format
    7          text--variable
    8          chart
    %10nnnnnn  string data of up to 64 bytes (as designated by nnnnnn), null
The variables may have different sources, such as an binary input pin, temperature pin, adc pin, or other source. These are the sources I defined.
Code:
source--variable holding data
    0          Special handling required or bit0 for bit data, or rtc for date/time
    0-31       bit variable number, e.g., bit13
                       or
    %10000001-%10011111 pin # 0-31 for digital
    %11000001-%11011111 pin # 0-31 for analog 8-bit
    %11100001-%11111111 pin # 0-31 for analog 10-bit

    0-27       byte variable number, e.g., b24
    28-75      byte variable in ram accessed with peek/poke
    0-13       Word variable number, e.g., w5
    14-29      word variable in ram accessed with peek/poke

               RTC Date/Time
    0          Date: format mmm dd, yyyy, e.g., Nov 08, 2013
    0          Time: format hh:mm, e.g., 10:01--24 hour
    1          Date: format yymmdd, e.g., 131108
    1          Time: format hhmmss, e.g., 100133

    0          String: Normal text display
1	String: Warning--displayed in red and emailed every 6 hours
The variable type and variable source for each variable is kept in a table in eeprom, along with two additional bytes which are either the address in RAM where the ascii value of the variable is stored, or, in the case of bit variables, two byte addresses in table data, the first a pointer to the text displayed when the bit variable value is 0, the second when it is 1.

Here is the excel table which defines the variables:
Code:
                                                                                  Default     Var
   var #          Data Item          Type  Source   Address    Length              Value    Name      Description
       0 None                                            98         1
       1 Date                           5       0        99        13       Nov 09, 2013 vDate       mmm dd, yyyy
       2 Time                           6       0       112         6              21:13 vTime       hh:mm
       3 Temperature                    4       4       118         5                 22 vTemp       pin c.4
       4 Pumped today House             3      28       123         6                 31 vPdDayH     1st var register above b27
       5 Pumped yesterday H             3      30       129         6                 62 vPdYesH
       6 Pumped 7 Days H                3      32       135         6                407 vPdWkH
       7 Pumped today RO                3      34       141         6                 26 vPdDayRO
       8 Pumped yesterday RO            3      36       147         6                 57 vPdYesRO
       9 Pumped 7 Days RO               3      38       153         6                388 vPdWkRO
      10 Pumped today Well1             3      40       159         6                 25 vPdDayW1
      11 Pumped yesterday W1            3      42       165         6                 48 vPdYesW1
      12 Pumped 7 Days W1               3      44       171         6                320 vPdWkW1
      13 Pumped today Well2             3      46       177         6                  6 vPdDayW2
      14 Pumped yesterday W2            3      48       183         6                 14 vPdYesW2
      15 Pumped 7 Days W2               3      50       189         6                 87 vPdWkW2
      16 Salinity, Well1                3      52       195         6                215 vSalW1      in parts per million (ppm)
      17 Salinity, Well2                3      54       201         6                380 vSalW2
      18 Salinity, RO in                3      56       207         6                280 vSalROin
      19 Salinity, RO out               3      58       213         6                180 vSalROout
      20 Water Level, Well1             1      60       219         4                 OK vLevelW1    0=Low,1=OK
      21 Water Level, Well2             1      61       223         4                Low vLevelW2    0=Low,1=OK
      22 Water Level, House Tank        1      62       227         4                 OK vLevelHouse 0=Low,1=OK
      23 Water Level, Holding Tank      1      63       231         4                 OK vLevelHold  0=Low,1=OK
      24 House Pump Status              7       2       235         2                  1 vHPump      Enabled/Disabled
      25 Well1 Pump Status              7       2       237         2                  0 vW1Pump     ON/OFF
      26 Well2 Pump Status              7       2       239         2                  0 vW2Pump     ON/OFF
      27 RO in  Pump Status             7       2       241         2                  0 vROinPump   ON/OFF
      28 Bypass Pump Status             7       2       243         2                  0 vBypassPump ON/OFF
      29 Messages, Warning              7       1       245         2                    vMsgWarn    Warning messages in red
      30 Messages, Normal               7       0       247         2                    vMsgNorm    Normal message text
      31 7-Day Chart by Hour            8       1       249         2                    v7DayChart  Weekly Chart by Hour
The table defines the variable number, data item, type, address in RAM of where text representation of value will be held, length of text representation, default text value, name of binary (bit, byte, word) variable storage location in RAM (peek, poke address), and item description.

For instance, variable 3 is temperature (type 4&#8212;read of ds18b20 is implied) to be read from pin 4 (source), with the ascii value to be stored at ram location 118 for a length of 5, including a null termination (binary 0) and a possible minus sign. The default value (replaced by a reading of the sensor on the first pass through the program) is &#8220;31&#8221;. The address of the 1-byte temperature value in RAM will be named &#8220;vTemp&#8221;. The &#8220;Address&#8221; cell value is determined by the starting address (98 in this case), and the sum of the lengths of the prior variables.

I wrote a Visual Basic for Applications program to read this table and generate 3 separate chunks of code to be plugged into the program&#8212;symbol definitions for the names and locations in RAM of the binary values which feed the web presentation, eeprom definitions of the variable types, sources, and RAM addresses for the ascii representation of the values, and table definitions of the binary values of the variables.

Symbol Definitions for Names and Locations in RAM
Code:
symbol vPdDayH=28    ' Pumped today House
symbol vPdYesH=30    ' Pumped yesterday H
symbol vPdWkH=32    ' Pumped 7 Days H
symbol vPdDayRO=34    ' Pumped today RO
symbol vPdYesRO=36    ' Pumped yesterday RO
symbol vPdWkRO=38    ' Pumped 7 Days RO
symbol vPdDayW1=40    ' Pumped today Well1
symbol vPdYesW1=42    ' Pumped yesterday W1
symbol vPdWkW1=44    ' Pumped 7 Days W1
symbol vPdDayW2=46    ' Pumped today Well2
symbol vPdYesW2=48    ' Pumped yesterday W2
symbol vPdWkW2=50    ' Pumped 7 Days W2
symbol vSalW1=52    ' Salinity, Well1
symbol vSalW2=54    ' Salinity, Well2
symbol vSalROin=56    ' Salinity, RO in
symbol vSalROout=58    ' Salinity, RO out
symbol vLevelW1=60    ' Water Level, Well1
symbol vLevelW2=61    ' Water Level, Well2
symbol vLevelWHT=62    ' Water Level, House Tank
symbol vLevelWHold=63    ' Water Level, Holding Tank
EEPROM Definitions of Variable Types
Code:
eeprom 4,(5,0,99,0) ' vDate
eeprom 8,(6,0,112,0) ' vTime
eeprom 12,(4,4,118,0) ' vTemp
eeprom 16,(3,28,123,0) ' vPdDayH
eeprom 20,(3,30,129,0) ' vPdYesH
eeprom 24,(3,32,135,0) ' vPdWkH
eeprom 28,(3,34,141,0) ' vPdDayRO
eeprom 32,(3,36,147,0) ' vPdYesRO
eeprom 36,(3,38,153,0) ' vPdWkRO
eeprom 40,(3,40,159,0) ' vPdDayW1
eeprom 44,(3,42,165,0) ' vPdYesW1
eeprom 48,(3,44,171,0) ' vPdWkW1
eeprom 52,(3,46,177,0) ' vPdDayW2
eeprom 56,(3,48,183,0) ' vPdYesW2
eeprom 60,(3,50,189,0) ' vPdWkW2
eeprom 64,(3,52,195,0) ' vSalW1
eeprom 68,(3,54,201,0) ' vSalW2
eeprom 72,(3,56,207,0) ' vSalROin
eeprom 76,(3,58,213,0) ' vSalROout
eeprom 80,(1,60,219,0) ' vLevelW1
eeprom 84,(1,61,223,0) ' vLevelW2
eeprom 88,(1,62,227,0) ' vLevelHT
eeprom 92,(1,63,231,0) ' vLevelHold
eeprom 96,(7,2,235,0) ' vHPump
eeprom 100,(7,2,237,0) ' vW1Pump
eeprom 104,(7,2,239,0) ' vW2Pump
eeprom 108,(7,2,241,0) ' vROinPump
eeprom 112,(7,2,243,0) ' vBypassPump
eeprom 116,(7,1,245,0) ' vMsgWarn
eeprom 120,(7,0,247,0) ' vMsgNorm
eeprom 124,(8,1,249,0) ' v7DayChart
Table Definitions of Binary Values of Variables is attached.

A great deal of the work of the program is done by and through these tables. I&#8217;ll outline how the code works later.

Here is the excel VBA macro which generates the above code from the excel table, attached.
 

Attachments

Last edited:

lbenson

Senior Member
The Application: A Water System Monitor and Controller

The problem described by jsmanson, referred to earlier, provided a basis for this application. It is not a solution to his problem, but it is very similar. The version of the system which I have defined consists of two wells (of varying salinity), a holding tank, a reverse osmosis module for reducing salinity, a house tank, five pumps, 8 float level sensors, 4 flow gauges, 4 salinity sensors, and a temperature sensor.

The work flow is as follows. If the holding tank is low (as defined by one of the level sensors), then if well 1 is not dry and its salinity is lower than that of well 2, then its pump comes on (pump 1); otherwise if well 2 is not dry, its pump comes on(pump 2). When the maximum level sensor for the holding tank is triggered, the active pump is turned off. If the house tank is low, then if the salinity of the holding tank is less than 220 parts per million (ppm), then pump 4 is activated to pump water directly from the holding tank to the house tank. If the salinity is higher, pump 3 is activated to run the water through the reverse osmosis module. If the holding tank goes low, the pumps go off. If the salinity of the water out of the RO module exceeds 330ppm, pump 3 goes off and a warning message is displayed saying that the RO filter needs to be changed.

Pump 5, the house pump, is not directly controlled by this system. It runs, on demand, according to the draw on it and the pressure in its pressure tank. If, however, the house tank runs dry, this system turns off the power to the house pump so that its own control system cannot turn it on.

In addition to the controlling sensors, there are four sensors which monitor the flow of water—from well 1, well 2, the holding tank, and the house tank.

The flow sensors are defined as emitting 330 pulses per liter. The float sensors are on/off. I don’t know how salinity sensors work, so I defined one as providing a 0-5V voltage level, such that an 8-bit ADC reading would represent one-third of the parts per million of salt, so there would be a range of 0-767 ppm. The temperature sensor is a DS18B20.
 

Attachments

Last edited:

lbenson

Senior Member
Layout and Breadboard

The schematic for this project is attached below. I did the layout using westaust55’s Pebble breadboard program, which did fine for this not-too-complex layout. I did many Pebble modifications, and 3 different breadboards before I ended up with this one. It has a 20m2, an MCP23017, 2 08M2s, a 14M2, a module with RTC and eeprom, and a TLG10UA03 wifi-to-serial module.

Here are the pinouts for the 20M2 and the MCP23017.
Code:
                      +V 1 |       | 20 0V
                      SI 2 |       | 19 SO debug
           pulse CTS c.7 3 |       | 18 b.0 
     pulse RTS&input c.6 4 |       | 17 b.1 
                     c.5 5 | 20M2  | 16 b.2 
      toggle new day c.4 6 |       | 15 b.3 RO in salinity
      well1 salinity c.3 7 |       | 14 b.4 PumpsTesting
      well2 salinity c.2 8 |       | 13 b.5 sda
     RO out salinity c.1 9 |       | 12 b.6 hserin (tcp input--net)
       (tcp output) c.0 10 |       | 11 b.7 slc
	
                    Ad0  1 |       | 28 NC
                    AD1  2 |       | 27 sda 
                    AD2  3 |       | 26 slc 
                 *Reset  4 |       | 25 NC
                   IntB  5 |  MCP  | 24 0V
                   IntA  6 | 23017 | 23 +V
    house pump relay A0  7 |  I/O  | 22 B7 *well2 dry
    well1 pump relay A1  8 |       | 21 B6 *well1 dry
    well2 pump relay A2  9 |       | 20 B5 housetank full
  holding pump relay A3 10 |       | 19 B4 *housetank refill
RO bypass pump relay A4 11 |       | 18 B3 *housetank dry
                     A5 12 |       | 17 B2 Holding Tank Full
                     A6 13 |       | 16 B1 *Holding Tank Refill
                     A7 14 |       | 15 B0 *Holding Tank Dry

Note: "*"  means TRUE when low.
Pebble breadboard code in post 12 below.

Note for anyone worried about the LEDs without resistors on the breadboard. These are "resistorized" LEDs, Digikey# 67-1103-ND. I like to use them on breadboards for the convenience.
 

Attachments

Last edited:

lbenson

Senior Member
Hardware Simulation

Binary Inputs and Outputs

I felt that in order to test the system I had to simulate all of the inputs. The float levels and pumps were easy&#8212;an 8-position switch represented the float switches, and leds represented the 5 pumps. The MCP23017 handled these inputs and outputs.

ADC for Salinity

For the salinity levels, I generated varying voltage levels by doing pwm at 10kHz with duty cycles ranging in 10% steps from 40% to 99% through a 4K7 resistor with a 1uF capacitor to 0V. This gave 7 distinctive voltages which an 8-bit ADC read rendered at around 33, 66, 100, 133, 166, 200, and 233 giving a range, when multiplied by 3, between 100 and 700ppm.

I wrote a program for a 14M2 to do the 4 PWM outputs. It takes as input (over a usb-to-serial module from my laptop), two characters per command&#8212;the first giving the PWM channel (well 1, well 2, holding tank out, RO out) and the second giving the duty cycle (with 1-7 representing 40%-90%). Here&#8217;s the code
Code:
'14SalinityGen generates ADC with pwm through 4k7R w. 1uF->0V
'  representing 7 levels of salinity for 4 sensors
#picaxe 14M2
#terminal 4800 'turn on terminal after program load
SETFREQ M4

pause 2000
sertxd("14SalinityGen",cr,lf)
'disconnect
s_w1 = 39
pwmout pwmdiv4, B.4, 99, s_w1 ; 10000Hz at 10% @ 4MHz
pwmout pwmdiv4, C.0, 99, s_w1 ; 10000Hz at 10% @ 4MHz
pwmout pwmdiv4, C.2, 99, s_w1 ; 10000Hz at 10% @ 4MHz
pwmout pwmdiv4, B.2, 99, s_w1 ; 10000Hz at 10% @ 4MHz
do
'  serrxd b2,b1
  serin b.3, t4800_4, b2,b1 ' if no bytes ready, no change
  sertxd("Port ",b2,", level ",b1,cr,lf)
  if b2 > "0" and b2 < "5" then ' skip noise
    serout b.5, t4800_4,("Port ",b2,", level ",b1,cr,lf)
    if b1 > "0" and b1 <= "9" then
      s_w1 = b1 - "0" * 40 - 1 ' e.g., 39,79...359
      if b1 = "4" or b1 = "7" then 
        s_w1 = s_w1 + 10 ' kluge to fix jitter
      endif
      select b2
        case "1"
          pwmout pwmdiv4, B.4, 99, s_w1 ' 20M2 b.3 ROinSalinity
        case "2"
          pwmout pwmdiv4, C.0, 99, s_w1 ' 20M2 c.1 ROoutSalinity
        case "3"
          pwmout pwmdiv4, B.2, 99, s_w1 ' 20M2 c.3 well1Salinity
        case "4"
          pwmout pwmdiv4, C.2, 99, s_w1 ' 20M2 c.2 Well2Salinity
      endselect
    endif
  endif
loop

Pulse Counting

The flow sensors described by jsmanson put out 330 pulses per liter. I recommended to him that he use an 08M2 &#8220;front end&#8221; to count the pulses, and I used the same procedure here. The program runs at 16mHz and in a tight loop counts logic level transitions on pins 2, 3, 4, and 5. Pin0 is used as a Request-To-Send (RTS) line, and as the line on which a character is sent indicating which channel has measured another liter. Pin1 is a Clear-To-Send (CTS) line which indicates that the controller chip (the 20M2) is ready to receive and has entered serin.

When the count for a given input exceeds 329, the program asserts RTS and continues counting while looking for CTS assertion. When it sees it, it de-asserts RTS and sends a character (&#8220;1&#8221;, &#8220;2&#8221;, &#8220;3&#8221;, &#8220;4&#8221;) indicating that another liter has been counted on the designated channel. The code is attached below in post 11.

Pulse Generation

This was the trickiest code other than the web server itself&#8212;the code to do a reasonable simulation of 4 flow sensors which produce 330 pulses for each liter of simulated flow for up to a maximum of 20-some liters per minute each. I don&#8217;t guarantee that the program does exactly what I wanted it to, but it did a good enough job so that when running it at full speed for a simulated 28 days it produced data from which I could make rational charts of 28-day by daily water usage and 7-day by hourly water usage&#8212;see attached images.

I made some assumptions&#8212;a maximum of 700 liters per day (an average of ½ liter per minute), varying up or down per day by a random amount of up to 50% more or less with 95% of the house usage coming through the RO filter, and all being supplied by the two wells, with well 1 providing on average 65% and well 2 35% (but that percentage varying up or down randomly by up to 50% of the well 2 average).

This varying total amount per day is distributed by hour according to a table providing hourly percentage use for hours 0-23.
Code:
' hour 0-1 2 3 4 5 6  7  8 9  10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   %     2,1,1,1,1,4, 8, 5,4,  4, 5,10, 5, 5, 5, 5, 5,10, 5, 3, 1, 5, 3, 2
So 10% is used, on average, between 11am and 12 noon.

Each minute is divided into &#8220;ticks&#8221;. I used 4 ticks (15 seconds) per minute. For each minute of the hour, the number of pulses per minute and pulses per tick is calculated, with the number randomly varied. The main loop of the program checks for a new tick (&#8220;time&#8221;&#8212;the built-in seconds-counting variable divided by 15), and sends the appropriate number of pulses for each simulated flow sensor&#8212;well1, well2, holding tank out, and house in. Pulses are bunched at the beginning of each 15-second tick, but overall it should approximate water usage.

I don&#8217;t claim any elegance for this program. It started off being one thing, and became another, and I created a couple of bugs which were bears to wrestle with. See the code attached below&#8212;495 lines.
So the pulse-generating program feeds pulses to the pulse-counting program, 330 pulses per liter for 4 flow sensors, and the pulse-counting program counts the pulses and sends a marker to the control program when a sensor count exceeds 330. The salinity-generating program reads 2-digit inputs and sets a pwm duty cycle of 40-90% through an RC filter for each of 4 salinity sensors. The control program does an ADC reading the filtered voltages, where the ADC values of 0-255 represent 1/3rd of the salinity reading in parts-per-million. The MCP23017 I/O expander passes on the logic levels of the 8 switches which represent float switches which measure water level in the wells, holding tank, and house tank.

The code is below in post 11.

Thus there is complete hardware simulation of the system (not necessarily accurate as to timing). All of this is to provide data for a web page.
 

Attachments

Last edited:

lbenson

Senior Member
Webserver Program

The program has 6 main parts—definition of variables and setup of eeprom and table data, initialization, collection of input from the sensors (subroutine setOutputs), building of text representation of the values to be displayed (subroutine updateData), monitoring of HTTP GET requests (main loop of program), and output of the html code(subroutine outputHTML).

The program uses both slots on the 20M2—794 lines and 1958 bytes in slot 1 and 497 lines and 1924 bytes in slot 0. There is an include file which 344 lines long. In slot 1, a fair number of the lines were for diagnostic purposes and are “REMed out” to all space for the working code.

1) Definition of variables and setup of eeprom and table data. The include file defines the input ports, the named variables, named locations in RAM, other constants, and sets up the values for eeprom and table data. It includes the definitions generated by the excel macro.

2) initialization. This part of the code gets the date and time, updates values from some of the sensors, moves some data from table to ram, and performs other miscellaneous initializations.

3) Collection of input from the sensors (subroutine setOutputs). This routine loops through all of the web page variables, reads the sensors, updates binary values which drive the web display, and sets the outputs (for pumps) as appropriate. This is code which is specific to an application, and would have to be rewritten for any specific purpose.

4) Building of text representation of the values to be displayed (subroutine updateData). This is the main “table-driven” part of the code. It loops through the variables defined in the picaxe eeprom, and builds ascii text representations of the values to be displayed on the web page based on the variable type and variable source.

In the case of values which come directly from pins, it reads the pin and writes the ascii depending on the 0 or 1 value of the pin. Most of this code would not vary for a new application. If I had a new application, I could probably finish generalizing this code so that 95% would be unchanged for the next application.

5) Monitoring of HTTP GET requests (main loop of program). This is fairly simple. The program sits in serin with a timeout for 8 seconds (at 32mHz) (it spends about a second on the rest of the code).

serin [64000,skip], tcpInPin, baudrate, n1, n2, n3, n4, n5, form,n6,command,b27,value

The main thing this code retrieves is a “GET” (in n1,n2,n3), and if this is the result of the user clicking one of the radio buttons, the form, command, and command value. This code only provides for one form (which I named “z” in the html code), but there could be more. The three radio buttons have two commands defined, “X” and “Y”. The “X” command has two values—“0” and “1”—the “Y” has only the value of 1. When a “GET” is received, the program performs the appropriate command action if any (for instance, turning on a pump), and then serves up the html code.

6 ) Output of the html code(subroutine outputHTML). This routine scans character by character through the html template. If it finds a variable (indicated by “~##), it looks up the variable information in the eeprom table, and outputs the ascii data built by the last update (no more than a few seconds earlier); otherwise it outputs the character. This continues until a binary 0 is found. At 32mHz it takes a few seconds for the output to be generated. You can watch the page being built in your browser.
 

Attachments

Last edited:

lbenson

Senior Member
Testing

I performed ad hoc testing throughout the development process and discovered many errors. That has always been my practice—code and fix, code and fix, repeat until numb, do it again the next day.

A program as complicated as this requires a formal test procedure. I don’t claim rigor, but I put together a 47-step test sequence, shown below. The first time I ran through the entire sequence, everything performed as expected except that one output cell displayed “LowOK” instead of just “Low”. I changed a single digit in the symbol definitions and reflashed, and all was well.
Code:
               Testing Sequence -- Water System Monitor and Control with PICAXE Web Server
                Sensor Output                        Command       Result
      1 All float      switches high                  switch    Pump 5 enabled
      2 Salinity low for ROout,ROin,Well1,well2   11,21,31,41   No salinity issues

           Exercise House Tank Conditions
      3 House Tank Max goes low                       switch     - 
      4 House Tank Refill goes low                    switch    Pump 4 on
      5 House Tank Refill goes high                   switch     - 
      6 House Tank Max goes high                      switch    Pump 4 off
      7 House Tank Max goes low                       switch     - 
      8 House Tank Refill goes low                    switch    Pump 4 on
      9 ROin salinity goes high                           34    Pump 4 off, Pump3 on
     10 House Tank Refill goes high                   switch     - 
     11 House Tank Max goes high                      switch    Pump 3 off
     12 House Tank Max goes low                       switch     - 
     13 House Tank Refill goes low                    switch    Pump 3 on
     14 ROout salinity goes high                          45    Pump 3 off, Filter Change Message appears
     15 House Tank Dry goes low                       switch    Pump 5 disabled, House Tank Message appears
     16 ROout salinity goes low                           41    Pump 3 on,  Filter Change Message disappears
     17 House Tank Dry goes high                      switch    Pump 5 enabled
     18 House Tank Refill goes high                   switch     - 
     19 House Tank Max goes high                      switch    Pump 3 off, Refilling Tank Message diaappears

           Exercise Holding Tank Conditions
     20 All float      switches high                            Pump 5 enabled
     21 Salinity low for ROout, ROin, Well1, Well2              No salinity issues
     22 Holding Tank Max goes low                     switch     - 
     23 Holding Tank Refill goes low                  switch    Pump 1 on
     24 Well1 Salinity > Well2                         21,13    Pump 1 off, Pump 2 on
     25 Holding Tank Refill goes high                 switch     - 
     26 Holding Tank Max goes high                    switch    Pump 2 off
     27 Well1 Salinity < Well2                         22,11     - 
     28 Holding Tank Max goes low                     switch     - 
     29 Holding Tank Refill goes low                  switch    Pump 1 on
     30 Well1 Dry goes low                            switch    Pump 1 off, Pump 2 on, Well1 Dry Message appea
     31 Well2 Dry goes low                            switch    Pump 2 off, Well2 Dry Message appears
     32 Holding Tank Dry goes low                     switch    Holding Tank Dry Message appears, pumps 1-4 of
     33 Well1 Dry goes high                           switch    Pump 1 on, Well1 Dry Message disappears
     34 Holding Tank Dry goes high                    switch    Holding Tank Dry Message disappears
     35 Holding Tank Refill goes high                 switch     - 
     36 Holding Tank Max goes high                    switch    Pump 1 off
     37 Well2 Dry goes high                           switch    Well2 Dry Message disappears

           Exercise radio buttons
  
     38 Holding Tank Max goes low                     switch
     39 Click "Top off Holding Tank"                            Pump 1 or 2 on
     40 Holding Tank Max goes high                    switch    Pump 1 and 2 off
     41 House Tank Max goes low                       switch
     42 Click "Top off House Tank"                              Pump 3 or 4 on
     43 House Tank Max goes high                      switch    Pump 3 and 4 off
     44 Click "Show/Hide Chart"                                 Chart disappears
     45 Click "Show/Hide Chart"                                 Chart appears

           Check temperature
     46 Hold DS18B20 Temperature sensor                         Displayed temperature increases
     47 Remove fingers from sensor                              Displayed temperature decreases
[/code]
 

Attachments

Last edited:

lbenson

Senior Member
Testing 2

More Testing

The first of the attached screen shots shows the warning message, in red, to change the RO filter. The second shows the water level low in well1, well2, and the holding tank.

Occasional lack of connection was the biggest problem I encountered in testing. In the single-user environment, just clicking refresh was enough to restore the web page. Background serial receive would fix the problem, but I’d be more inclined to add another front end—an 08M2 which did nothing but sit in serin waiting for the GET, and when received, it would send two bytes to the background receive of the 20M2—the command value (if any, or a default for a bare GET), and the value of the command (e.g., “0” or “1” or “9” or “a”). The 20M2 would probably always be able to respond within the timeout period.
 

Attachments

Last edited:

lbenson

Senior Member
Conclusion

The picaxe is perfectly capable of serving a fairly complex web page to monitor and control a system with a couple dozen inputs and maybe a dozen outputs--for a single user. Thus it will work well for most home automation tasks.

I was pleased with the performance of the wifi-to-serial module, the TLG10UA03. I was a bit worried because the previous module I had tested, the HLK-RM04 occasionally had trouble connecting to my router. The TLG10UA03 connected every time, and usually within a couple of seconds after power-up.

This was the biggest software project I have worked on since my retirement 15 years ago. Times have changed a lot. Except for a few short episodes with the soldering iron at my desk, I did all the development--breadboard wiring and coding--sitting in my La-Z-Boy with my laptop and often a cat and sometimes an additional laptop, with a view of the Mersey river, in rural Nova Scotia.

Upon looking up I've seen eagles dozens of times, the only two otters I've ever seen in the wild, and merganser and goldeneye ducks, as well as the usual river denizens (cormorants, seagulls, mallards) and the few inland birds still sticking around (crows, bluejays, chickadees, downie woodpecker). The river has gone to icy. Snow has arrived. Some overcast mornings it appears that black and white and grey are the only colors.

And yet, I have access to the world, with packages of electronics goodies delivered for free from the far ends of the earth, and coding advice from Great Britain and Australia and elsewhere. A new programming editor arrived, doubling the code capacity of my chips (and just in time). A new oscilloscope--one one-hundredth the size and weight of the used one I had 30 years ago, and probably less than one-hundredth the price when new (and that in 1979 dollars).

I could go on, but this is way too long already.
 
Last edited:

geoff07

Senior Member
Very interesting. I shall have to study this as I may need a long-life alternative to the Picaxe net Server which is currently controlling my central heating.
 

jsmanson

Member
Hi Lance, just a quick update on 'our' project lol....

For my version of the project, I now have 95% of the code written up, running on the 40x2, 2 slots, using an eeprom for storage of constants and monthly cumulative data, a clock/timer, a digital pot (all i2c) for pump 'speed' (a DC solar well pump which can run between 12 and 30 volts, using a programmable power supply), 4-20ma sensors for tank status and well water level depth, Reverse Osmosis inputs and outputs that allow me to automatically do a flush cycle on the RO every so many hours of run time, a reverse flow 'override' that makes sure that the first water tank doesn't go dry when the water softener is in periodic 'regeneration' mode (needs 150-200 gal of available water to do this cycle), and other digital ins and outs. I am most of the way through the programming on this part, I haven't yet programmed the pump control portion, partly because I have run out of room on the program slot, and need to figure out how to throw it into another slot. This is going to be the fun part of the project, I am going to provide an 'optimum' well water level (i.e. drawdown) for each well, and let the program try to limit pumping rates to that optimum level, by delaying daytime pumping (when demands are higher) to the nightime, using the storage capacity in the larger tank to manage peak demand during the day. I could also switch wells as needed, depending on each well's capacity, which the program can calculate using previous flow history. I think this part of the program will need it's own slot lol.

As I noted before in another post, I decided to host the website portion on a raspberry Pi, which originally I was going to leave in the house - I have now decided to put the Pi out with the picaxe at the water system, and connect the Pi to the world with hard wired cat5 back to the house. I chose to interface the Pi and the Picaxe 40x2 using hardware serial at 9600 baud, using the serial GPIO port on the Pi, and it's working fine using a simple text based python program which is checking that the communications are working OK. Next up on the Pi will be a website, using python and the flask framework, which sounds like a similar thing to your lua extension? I am learning python and flask (and unix) for this little bit lol....

I am now going to modify your flow pulse code to work on a 28x2, and set it up as an i2c slave, so the 40x2 can just read the data direct as you (or someone?) recommended. The pulse program then becomes fairly simple, just recording instantaneous flows, this months cumulative flows, and last months flow in memory for the 40x2 to read, and the 40x2 will write the current time for the 28x2 to use for calculations.

I must have 200 hours into this little guy already, I'm telling my wife that it's 90% complete, so nobody say anything different, OK?? Cheers, and thanks to all of you for your support on my many questions to date, and Lance for your inspiration on this. It's been a fun learning experience in many ways.

John
 

lbenson

Senior Member
Thanks for the update, John. You've added some nice wrinkles, sounds good. We won't mention to your wife that some have said that for projects of a certain size, 50% of the time is taken in getting 90% of the project done, and the remaining 10% takes the other half of the time-to-completion.

I would say that I also spent over 200 hours--most of which for me was in setting up the web server and data-driven generalized code. A relatively small portion was spent on the actual application (though I did spin my wheels some on the hardware simulation part).

Lua can do much the same as python, and works on machines with very little memory (4M flash and 16M ram--small for a Linux device). Python has much more support on the Raspberry Pi. The Pi has excellent general capabilities for serving web pages--I just wanted to prove to myself that the M2/X2 generation of picaxe chips could also do it, and do it pretty well for single-user systems.

Good luck on your last 10%.
 
Top