     long t = millis();
     long nextCycleStart = t;
     for(int i = 0; i < numValves; i++) {
-      // TODO valves[i].startTime = nextCycleStart;
-      // nextCycleStart += getStaggerMillis();
+      valves[i].startTime = nextCycleStart;
+      nextCycleStart += getStaggerMillis();
     }
   }
   void step(monitor_t* now) {

  - dry minutes
  - got overflow
  - fill level (cms below ultrasound sensor)
*/

// maybe: fixed cycle time, irrespective of actual fill time, and the
// differences are absorbed into the dry time?

class Valve { // each valve /////////////////////////////////////////////////
  public:
  static int counter;               // counter for setting valve number
  int number;                       // id of this valve, counting from 1
  char dryMins = 45;                // mins to leave bed drained per cycle
                                    // in beds with overflows can be 0; else
                                    // will be cycle time minus fill time

  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];

void loop() {
  dnsServer.processNextRequest();
  webServer.handleClient();

  if(loopCounter == TICK_MONITOR) { // monitor levels, step valves, push data
    monitor_t* now = &monitorData[monitorCursor];
