   long waterLevel1;
   long waterLevel2;
   long waterLevel3;
+  // TODO add last reboot
+  // TODO add free memory?
 } monitor_t;
 monitor_t monitorData[MONITOR_POINTS];
 int monitorCursor = 0;

  startWebServer();

  mcp.begin();      // use default address 0 for mcp23008
  mcp.pinMode(P1_MCP_PIN, OUTPUT);
  mcp.pinMode(P2_MCP_PIN, OUTPUT);
  mcp.pinMode(P3_MCP_PIN, OUTPUT);
  mcp.pinMode(S1_MCP_PIN, OUTPUT);
  mcp.pinMode(S2_MCP_PIN, OUTPUT);
  mcp.pinMode(S3_MCP_PIN, OUTPUT);    

  if(WiFi.hostname("waterelf"))
    Serial.println("set hostname succeeded");

  bool gotOverflow = false;         // does the growbed have an overflow?
  char fillLevel = 5;               // cms below the level sensor: drain point

  Valve() { number = counter++; }
  void stateChange(bool newState) { // turn on or off
    Serial.print("Growbed Valve Air Pump ");
    Serial.print(number);
    int pumpMcpPin = valvePinMap[number][0];
    int solenoidMcpPin = valvePinMap[number][1];

    // trigger MOSFETs via the MCP
    if(newState == true){
      mcp.digitalWrite(pumpMcpPin, HIGH);
      mcp.digitalWrite(solenoidMcpPin, HIGH);
      Serial.println(" on");
    } else {
      mcp.digitalWrite(pumpMcpPin, LOW);
      mcp.digitalWrite(solenoidMcpPin, LOW);
      Serial.println(" off");
    }
  }
  void on()  { stateChange(true); } // fill time
  void off() { stateChange(false); }// drain time
  void step(monitor_t* now) {       // check conditions and adjust state
    /* TODO
    if(! running) {
      if(startTime >= millis()) {
        running = true;
        ...
      }
    }
    */
  }
};
int Valve::counter = 1;             // definition (the above only declares)

class FlowController { // the set of valves and their config ////////////////
  public:
  int numValves = 3;                // WARNING! call init if resetting!
  char cycleMinutes = 60;           // how long is a flood/drain cycle?
  char maxSimultaneousDrainers = 1; // how many beds can drain simultaneously
