
// sketch for ATLAS Upgrade Interlock II

// Bart Hommels
// University of Cambridge

// 16/7/2012
// V 0.1: basic H0-7 NTC readout
// v 0.3: H0-7, U8-9 ntc readout, set menu complete.
// v 0.4: 31/08/2012 reduced memory footprint
// v 0.5: 01/09/2012 various improvements
// v 0.6: 04/09 major memory footprint reduction using F() for LCD character strings
// w/ full sketch, freeMemory() returns 1428 B
// v 0.7: 11/09 added menu watchdog timer, sensirion SHT7x on I2C functionality: readout, interlock, configuration
// freememory returns 957B
// v 0.8: 18/09 menu branching added to reduce code footprint. Cleanup & variable name rationalisation.
// Getting very close to v 1.0
// freememory returns 919B
// 26/09 menu state cleanup, bug fixes
// 28/09 bug fix. To do: serial output of values upon request, or upon status change.
// 02/10 0.86 changed menu potmeter mapping from 0..1023 to 0..1020
// v 0.90: 10/10/2012 Implement serial protocol. Minimum feature is data request command. More later?
// v 0.91: 24/10/2012 serial protocol to set Tlimits, RHlimits, NTCparameters, Nenable,
// + serial commands + request data + enabe outputs
// deliberately no functionality to set EEPROM remotely (EEPROM should be the safety net one can rely on after unforeseen resets etc)
// memfree: 857
// v 0.94 memfree: 865. Sketch 26818 Introduced different T limits range for NTC, SHT. Generalised SHT71 to SHT7x. 
// v 0.95, 02/11/2012 implemented serial disable command.
// v 0.96  07/11/2012 implemented serial command settings request & write settings to EEPROM. Should also enum menu states
// v 0.97  09/11/2012 enumerated menu states. Nsamples configurable for H, U NTCs, through menu and serial. Memfree 752.
// Arduino v3 bootloader sketch limit encountered: sketch size 28984 (bootloader limit is 30720)
// v 0.97  12/11/2012 bugfixes. Sketch 28960, Memfree 753 (Sketch 28818 w/o DEBUG)
// v 0.98  22/11/2012 bugfix, and put comma separator requirement between command and value for serial comm
// sketch 29094, memfree 736 (sketch 28952 w/o debug)
// Ready to call v1.00
// disable debug, and call v 1.0 dd 30/11/2012
// v1.01 05/12/2012 added proper timeout to sht readout. 28906B Sketch size
// v1.02 12/12/2012 fixed garbled display bug

//+-------------------------------------------------------+
// Libraries
#include <stdio.h>
#include <stdlib.h>
#include "Wire.h"// for driving SPI, I2C buses
#include "LiquidCrystal.h"// for LCD on SPI
#include <EEPROM.h>// for using EEPROM
#include "EEPROMAnything.h" // for writing multiple-byte objects to EEPROM
#include <NTCreadlean.h> // low memory useage NTC readout class
#include "SHTxx.h" // for reading out SHT7x on I2C

using namespace myardu; // for SHT7x on I2C

//#define DEBUG // flag to enable debug information / code
#define ENTER 13 // byte value of the Enter key (LF)

//+-------------------------------------------------------+
#ifdef DEBUG
// Memory Usage Monitoring
extern unsigned int __heap_start;
extern void *__brkval;
/*
     * The free list structure as maintained by the 
 * avr-libc memory allocation routines.
 */
struct __freelist {
  size_t sz;
  struct __freelist *nx;
};
/* The head of the free list structure */
extern struct __freelist *__flp;
#endif


// +-------------------------------------------------------+
// Definitions
const char fwVersion[5] = "1.02";
const char fwDate[11] = "12/12/2012";
const char dataRequestString[4] = "idr";// Interlock Unit Data Request
const char settingsRequestString[4] = "irs";// Interlock Read Settings
const char write2EepromString[4] = "wre";//Write to EEPROM
const char enableRequestString[4] = "ena";// Enable request
const char disableRequestString[4] = "dis";// Disable request
const char* lookup_table[] = {
  "tl","th", "ne", "na", "rn", "tn", "bn", "rl", "rh"};// serial command decoder
enum{
  Tlow, Thigh, Nenable, nSamples, Rnom, Tnom, Bntc, RHlow, RHhigh, None };// serial command enumeration


// enumerate menu states
enum{
  DefaultMenu, DeviceMenu, 
  TLimitsMenu, SetTemperatureLimit, StoreTemperatureLimit, 
  NTCMenu, SetHNEnable, SetUNEnable, StoreHNEnable, StoreUNEnable,
  NTCValuesMenu, SetNTCNSamples, StoreNTCNSamples,
  SetNTCRnom, StoreNTCRnom, SetNTCTnom, StoreNTCTnom, SetNTCB, StoreNTCB,
  SHTMenu, SetSHTEnable, SHTRHLimitsMenu, SetSHTRHLimit, StoreSHTRHLimit };


// +-------------------------------------------------------+
// pin assignments
const byte hNTCMuxPin = A0;
const byte uNTC09Pin = A1;
const byte uNTC08Pin = A2;
const byte menuPot = A3;
const byte i2cData = A4;
const byte i2cClk = A5;

const byte d0_output = 0;
const byte d1_output = 1;
const byte muxCtrl2 = 2;
const byte menuButton = 3;
const byte lcdLatch = 4;
const byte lcdClk = 5;
const byte lcdData = 6;
const byte duoLed = 7;
const byte outputEnable = 8;
const byte selectRseries = 9;
const byte muxCtrl0 = 10;
const byte muxCtrl1 = 11;
const byte enablePbLED = 12;
const byte pbEnable = 13;

//+-------------------------------------------------------+
// variables
// stuff to do with the serial interface
char cmdbuf[4], intbuf[5];
byte nBytes = 0;
int inSerial = 0;

// High, low temperature limits for H0-7, U8-9 NTCs, SHT7x RH/T sensor.
// values here are extremely conservative fallbacks. actual settings should be collected from EEPROM
float h_NTC_T_Hi = 30.0;
float h_NTC_T_Lo = 10.0;
float u_NTC_T_Hi = 30.0;
float u_NTC_T_Lo = 10.0;
float SHT_T_Hi = 30.0;
float SHT_T_Lo = 10.0;
float SHT_RH_Hi = 60.0;
float SHT_RH_Lo = 30.0;

// NTC parameters to reconstruct temperature from ADC readings
byte h_NTC_Rnom = 10;// H0-7 NTC resistance at nominal T, in kOhm. Hardware options are 1, 10kOhm
byte h_NTC_Tnom = 25;// H0-7 NTC nominal temperature
unsigned int h_NTC_B = 3435; // H0-7 NTC B factor

// U8-9 NTC defaults (use GL23 for the moment: R(20 deg C) = 2kOhm, B=3125)
byte u_NTC_Rnom = 2;// U8-9 NTC resistance at nominal T in kOhm: [1,2,3,5,10,20]
const byte u_NTC_Rseries = 10;// assume 10kOhm series resistance. Hardware has 9k1 resistor + 2k trimpot for tuning Tnom/Rnom
byte u_NTC_Tnom = 20;// should be 20 or 25
unsigned int u_NTC_B = 3125;// adjustable 3000-4000 K in 5 K steps

// EEPROM addresses for non volatile parameters
const unsigned int addr_h_NTC_T_Lo = 0;// float: 4B
const unsigned int addr_h_NTC_T_Hi = 4;// float: 4B
const unsigned int addr_h_NTC_N_enable = 8;// byte
const unsigned int addr_h_NTC_nSamples = 12;//byte
const unsigned int addr_h_NTC_Rnom = 16;// byte
const unsigned int addr_h_NTC_Tnom = 20;// byte
const unsigned int addr_h_NTC_B = 24;// u_int: 2B

const unsigned int addr_u_NTC_T_Lo = 40;// float: 4B
const unsigned int addr_u_NTC_T_Hi = 44;// float: 4B
const unsigned int addr_u_NTC_N_enable = 48;// byte
const unsigned int addr_u_NTC_nSamples = 52;//byte
const unsigned int addr_u_NTC_Rnom = 56;// byte
const unsigned int addr_u_NTC_Tnom = 60;// byte
const unsigned int addr_u_NTC_B = 64;// u_int: 2B

const unsigned int addr_Sht_T_Lo = 80;// float: 4B
const unsigned int addr_Sht_T_Hi = 84;// float: 4B
const unsigned int addr_Sht_RH_Lo = 88;// float: 4B
const unsigned int addr_Sht_RH_Hi = 92;// float: 4B
const unsigned int addr_Sht_Enable = 96;// byte

// number of samples to use in averaging filter on NTC groups
byte HnSamples = 2;// 2 should be a good fallback number
byte UnSamples = 2;

// enable masks for sensors
byte h_NTC_N_enable = 1;
byte u_NTC_N_enable = 0;
byte sht_enable = 0;

char dispTemp[5];
// menu pot limits and values
const int menuPotLeftmost = 70;
const int potClickLo = 470;
const int potClickHi = 560;
const int menuPotRightmost = 950; 

const char degC[] = { 
  (char)223, 'C'};
const char kOhm[] = { 
  'k', (char)244 };

// readout interval for SHT7x. 
//Device should not be active more than 10% to limit self-heating to 0.1degC
// with clock frequency of 80 kHz, readout sequence takes 302ms.
const unsigned int userShtInterval = 3500UL;//3.5s
const unsigned int readShtTimeOut = 1000UL;// 1s. Nominal readout time 300 ms
// pushbutton flash interval
const unsigned int flashInterval = 500UL;// 0.5s
// menu timeout
const unsigned int maxMenuInterval = 120000UL;// 2 minutes

// menu state machine variables
byte menuButtonRead = 0;
byte prevMenuButtonRead = 0;
byte menuState = DefaultMenu;
byte nextMenuState = DefaultMenu;
byte menuChoice = 0;
byte menuDevice = 0;
byte eepromWritten = 0;

int setTemp = 0;
byte enableLedState = LOW;
byte outputState = LOW;
byte prevOutputState = LOW;
byte userEnable = LOW;
byte serialEnable = LOW;
byte serialDisable = LOW;
byte mustPrintSensorValues = LOW;
// timing control of refresh rates
unsigned long flashTimerLast, userShtTime, menuWatchDog;

byte commaReceived = LOW;
//+-------------------------------------------------------+
// instantiations

// SPI LCD interface through 74LS595 shift register, see:
// http://learn.adafruit.com/i2c-spi-lcd-backpack
// DATA (p14 on 74LS595), CLK (p11 on 74LS595), LATCH (p12 on 74LS595)
LiquidCrystal lcd(6,5,4);

// instantiate NTCs
// NTC on A0 serving H0-7 on external 4051 analog 8x multiplexer
NTCtemp h_NTC_Mux;
byte h_T_notOK = 0x00;
// NTC serving U8-908, U8-909 on A1, A2 respectively
NTCtemp u_NTC_8;
NTCtemp u_NTC_9;
byte u_T_notOK = 0x00;
// Sensirion SHT7x on I2C port
SHTxx shtRHT(i2cData, i2cClk);
byte sht_RHT_notOK = 0x00;

// temperature variables
float h_NTC_T[8];// keeping this fixed length is lots faster
float u_NTC_8_T = 20.0;
float u_NTC_9_T = 20.0;
float sht_T = 20.0;
float sht_RH = 50.0;

//+-------------------------------------------------------+
void setup(){
  // set up Serial communication (through USB)
  Serial.begin(115200);
  Serial.flush();// clear receive buffer
  memset(cmdbuf, '\0', 4);// clear command string
  memset(intbuf, '\0', 5);// clear integer string

  // use ADC analog reference from external precision supply
  analogReference(EXTERNAL);

  // set mode for all remaining pins
  pinMode(muxCtrl0, OUTPUT);
  pinMode(muxCtrl1, OUTPUT);
  pinMode(muxCtrl2, OUTPUT);
  pinMode(menuButton, INPUT);
  pinMode(duoLed, OUTPUT);
  pinMode(outputEnable, OUTPUT);
  pinMode(selectRseries, OUTPUT);
  pinMode(d0_output, OUTPUT);
  pinMode(d1_output, OUTPUT);
  pinMode(enablePbLED, OUTPUT);
  pinMode(pbEnable, INPUT);

  // write default outputs to pins
  digitalWrite(muxCtrl0, LOW);
  digitalWrite(muxCtrl1, LOW);
  digitalWrite(muxCtrl2, LOW);
  digitalWrite(selectRseries, HIGH);

  // set variables
  menuState = DefaultMenu;
  nextMenuState = DefaultMenu;
  menuDevice = 0;
  eepromWritten = 0;

  enableLedState = LOW;
  outputState = LOW;
  prevOutputState = LOW;
  serialEnable = LOW;
  serialDisable = LOW;
  mustPrintSensorValues = LOW;
  commaReceived = LOW;
  // initialise timers
  flashTimerLast = millis();
  userShtTime = millis();
  menuWatchDog = millis();

  // retrieve variables from EEPROM
  // H0-7 NTCs
  EEPROM_readAnything(addr_h_NTC_T_Lo, h_NTC_T_Lo);
  EEPROM_readAnything(addr_h_NTC_T_Hi, h_NTC_T_Hi);
  EEPROM_readAnything(addr_h_NTC_N_enable, h_NTC_N_enable);
  EEPROM_readAnything(addr_h_NTC_nSamples, HnSamples);
  EEPROM_readAnything(addr_h_NTC_Rnom, h_NTC_Rnom);
  EEPROM_readAnything(addr_h_NTC_Tnom, h_NTC_Tnom);
  EEPROM_readAnything(addr_h_NTC_B, h_NTC_B);
  // U8-9 NTCs
  EEPROM_readAnything(addr_u_NTC_T_Lo, u_NTC_T_Lo);
  EEPROM_readAnything(addr_u_NTC_T_Hi, u_NTC_T_Hi);
  EEPROM_readAnything(addr_u_NTC_N_enable, u_NTC_N_enable);
  EEPROM_readAnything(addr_u_NTC_nSamples, UnSamples);
  EEPROM_readAnything(addr_u_NTC_Rnom, u_NTC_Rnom);
  EEPROM_readAnything(addr_u_NTC_Tnom, u_NTC_Tnom);
  EEPROM_readAnything(addr_u_NTC_B, u_NTC_B);
  // SHT 71 RH/T
  EEPROM_readAnything(addr_Sht_T_Lo, SHT_T_Lo);
  EEPROM_readAnything(addr_Sht_T_Hi, SHT_T_Hi);
  EEPROM_readAnything(addr_Sht_RH_Lo, SHT_RH_Lo);
  EEPROM_readAnything(addr_Sht_RH_Hi, SHT_RH_Hi);
  EEPROM_readAnything(addr_Sht_Enable, sht_enable);

  // initialise NTC instances
  h_NTC_Mux.setNTCup(hNTCMuxPin, HnSamples, h_NTC_Rnom);// Rnom is either 1 or 10 kOhm
  h_NTC_Mux.setNTCParameters(h_NTC_Rnom, h_NTC_Tnom, h_NTC_B);

  u_NTC_8.setNTCup(uNTC08Pin, UnSamples, u_NTC_Rseries);
  u_NTC_8.setNTCParameters(u_NTC_Rnom, u_NTC_Tnom, u_NTC_B);

  u_NTC_9.setNTCup(uNTC09Pin, UnSamples, u_NTC_Rseries);
  u_NTC_9.setNTCParameters(u_NTC_Rnom, u_NTC_Tnom, u_NTC_B);

  // setup lcd + print welcome message
  lcd.begin(40,2);

  lcd.setCursor(9,0); 
  lcd.setBacklight(HIGH);
  lcd.print(F("Temperature Interlock"));// 21 chars 
  lcd.setCursor(6,1);
  lcd.print(F("firmware: "));// 10 chars
  lcd.setCursor(16,1);
  lcd.print(fwVersion);// 4 chars
  lcd.setCursor(20,1);
  lcd.print(' ');// 1 char
  lcd.setCursor(21,1);
  lcd.print(fwDate);// 10 chars
  delay(500);
  lcd.setBacklight(LOW);
  delay(500);
  lcd.setBacklight(HIGH);
  lcd.clear();

  // print welcome msg to serial
  Serial.println("Temperature Interlock Unit Ready");
}


void loop(){
  prevOutputState = outputState;
  // readout loop for H0-7 NTCs
  // set series R according to H0-7 type. 
  // For 1 kOhm, drive selectRseries line low during mux'ed measurement only to avoid self-heating when idle
  if(h_NTC_Rnom == 1) digitalWrite(selectRseries, LOW);

  for(byte count=0; count < 8; count++){
    if(count < h_NTC_N_enable){
      digitalWrite(muxCtrl0, (bitRead(count,0) ) );// set external mux control lines
      digitalWrite(muxCtrl1, (bitRead(count,1) ) );
      digitalWrite(muxCtrl2, (bitRead(count,2) ) );   
      h_NTC_T[count] = h_NTC_Mux.getDegC(); // read out temperature  
      if(h_NTC_T[count] > h_NTC_T_Hi || h_NTC_T[count] < h_NTC_T_Lo){ // check T in range
        bitWrite(h_T_notOK, count, HIGH);
        //h_T_notOK = HIGH;
      }
      else{
        bitWrite(h_T_notOK, count, LOW);
        //h_T_notOK = LOW;
      }
    }
    else{
      bitWrite(h_T_notOK, count, LOW);
      //h_T_notOK = LOW;
    }
  }
  digitalWrite(selectRseries, HIGH);

  // read out U8-9 NTCs if enabled
  // check temperature readings against limits and set bits in u_T_notOK accordingly
  if(u_NTC_N_enable > 0){
    u_NTC_8_T = u_NTC_8.getDegC();
    if(u_NTC_8_T > u_NTC_T_Hi || u_NTC_8_T < u_NTC_T_Lo) u_T_notOK = HIGH;
    //else u_T_notOK = LOW;
    else bitWrite(u_T_notOK, 0, LOW);

    if(u_NTC_N_enable > 1){ 
      u_NTC_9_T = u_NTC_9.getDegC();
      if(u_NTC_9_T > u_NTC_T_Hi || u_NTC_9_T < u_NTC_T_Lo) u_T_notOK = HIGH;
      //else u_T_notOK = LOW;
      else bitWrite(u_T_notOK, 1, LOW);
    }
  }
  else u_T_notOK = LOW;

  // read out SHT7x on I2C port if enabled. 
  //Keep track of timing interval
  if(sht_enable == 1){// cheap byte comparison first
    if( (millis() - userShtTime) > userShtInterval ){
      userShtTime = millis();
      sht_T = shtRHT.getTemp();
      sht_RH = shtRHT.getHum();
      if(millis() - userShtTime > readShtTimeOut) sht_enable = 0;// if readout takes too long, SHT is probably not connected and should be disabled
      if(sht_T > SHT_T_Hi || sht_T < SHT_T_Lo) sht_RHT_notOK = 1;
      else if(sht_RH > SHT_RH_Hi || sht_RH < SHT_RH_Lo) sht_RHT_notOK = 1;
      else sht_RHT_notOK = 0; 
    }
  }
  else sht_RHT_notOK = 0;

  // read enable pushbutton pin, see if Temp. is OK and set outputState accordingly
  userEnable = digitalRead(pbEnable);

  // check if temperatures are OK, and if there is at least one sensor enabled
  if( h_T_notOK == 0 && u_T_notOK == 0 && sht_RHT_notOK == 0 && ((h_NTC_N_enable+u_NTC_N_enable+sht_enable) > 0) ){// T in range
    digitalWrite(duoLed, HIGH);// T indicator LEDs go green
    if(userEnable == HIGH || serialEnable == HIGH){// Temperature OK, enable PB pushed or serial cmd received
      if(outputState == LOW) mustPrintSensorValues = HIGH;
      outputState = HIGH;
      serialEnable = LOW;// reset serial enable command flag
    }  
    if(outputState == HIGH){// Temperatures OK, output enabled. enable PB led should be solid on. Check serial cmd for output disable
      enableLedState = HIGH;
      if(serialDisable == HIGH){// disable cmd received over serial: set output low, reset disable receive flag
        outputState = LOW;
        serialDisable = LOW;
      }
    }
    else if(millis()- flashTimerLast > flashInterval){// flash LED in enable button
      flashTimerLast = millis();
      if(enableLedState == LOW) enableLedState = HIGH;
      else enableLedState = LOW;
    }
  }
  else{// temperature not good, or no sensors enabled
    digitalWrite(duoLed, LOW);
    if(outputState == HIGH) mustPrintSensorValues = HIGH;
    outputState = LOW;
    enableLedState = LOW;
    serialEnable = LOW;// set serial Enable request flag low if T is not in range
  }
  digitalWrite(outputEnable, outputState);
  if(prevOutputState == LOW && outputState == HIGH) delay(100);// avoid display trouble
  digitalWrite(enablePbLED, enableLedState);
  if(mustPrintSensorValues){
    printSensorValues();
    mustPrintSensorValues = LOW;
  }

  //commaReceived = LOW;  
  // see if anything arrived over serial communication and put it in appropriate strings until CR is received
  while(Serial.available() && inSerial != ENTER){
    inSerial = Serial.read();
    if(inSerial == ','){
      #ifdef DEBUG
      Serial.println("Comma received");
      #endif
      commaReceived = HIGH;
    }
    if(nBytes < 3 && commaReceived == LOW) cmdbuf[nBytes] = (char)inSerial;
    else if(nBytes > 3 && nBytes < 8 && commaReceived == HIGH) intbuf[nBytes-4] = (char)inSerial;
    nBytes++;
  }



  if(inSerial == ENTER){// concluding CR received: start to interpret received cmd
    Serial.println();// clear local echo line
    Serial.flush();// flush remaining bytes in buffer to be ready for next receive

    int convint = atoi(intbuf);// convert received ASCII to int
    Serial.print(F("Received: "));
    Serial.print(cmdbuf);
    Serial.print(", ");
    Serial.println(convint);

    // see if received first 3B string is a request for 
    // data, output enable, output disable, settings request, or write settings to EEPROM, successively
    if(strcmp(cmdbuf, dataRequestString) == 0 && convint == 100) printSensorValues();
    else if(strcmp(cmdbuf, enableRequestString) == 0 && convint == 100) serialEnable = HIGH;
    else if(strcmp(cmdbuf, disableRequestString) == 0 && convint == 100) serialDisable = HIGH;
    else if(strcmp(cmdbuf, settingsRequestString) == 0 && convint == 100){
      Serial.print("htl,");
      Serial.println(int(h_NTC_T_Lo));
      Serial.print("hth,");
      Serial.println(int(h_NTC_T_Hi));
      Serial.print("hne,");
      Serial.println(h_NTC_N_enable, DEC);
      Serial.print("hna,");
      Serial.println(HnSamples, DEC);
      Serial.print("hrn,");
      Serial.println(h_NTC_Rnom, DEC);
      Serial.print("htn,");
      Serial.println(h_NTC_Tnom, DEC);
      Serial.print("hbn,");
      Serial.println(h_NTC_B);
      Serial.print("utl,");
      Serial.println(int(u_NTC_T_Lo));
      Serial.print("uth,");
      Serial.println(int(u_NTC_T_Hi));
      Serial.print("une,");
      Serial.println(u_NTC_N_enable, DEC);
      Serial.print("una,");
      Serial.println(UnSamples, DEC);
      Serial.print("urn,");
      Serial.println(u_NTC_Rnom, DEC);
      Serial.print("utn,");
      Serial.println(u_NTC_Tnom, DEC);
      Serial.print("ubn,");
      Serial.println(u_NTC_B);
      Serial.print("rtl,");
      Serial.println(int(SHT_T_Lo));
      Serial.print("rth,");
      Serial.println(int(SHT_T_Hi));
      Serial.print("rrl,");
      Serial.println(int(SHT_RH_Lo));
      Serial.print("rrh,");
      Serial.println(int(SHT_RH_Hi));
      Serial.print("rne,");
      Serial.println(sht_enable, DEC);
    }
    else if(strcmp(cmdbuf, write2EepromString) == 0 && convint == 100){
      // write all variables to EEPROM
      // H0-7 NTCs
      EEPROM_writeAnything(addr_h_NTC_T_Lo, h_NTC_T_Lo);
      EEPROM_writeAnything(addr_h_NTC_T_Hi, h_NTC_T_Hi);
      EEPROM_writeAnything(addr_h_NTC_N_enable, h_NTC_N_enable);
      EEPROM_writeAnything(addr_h_NTC_nSamples, HnSamples);
      EEPROM_writeAnything(addr_h_NTC_Rnom, h_NTC_Rnom);
      EEPROM_writeAnything(addr_h_NTC_Tnom, h_NTC_Tnom);
      EEPROM_writeAnything(addr_h_NTC_B, h_NTC_B);
      // U8-9 NTCs
      EEPROM_writeAnything(addr_u_NTC_T_Lo, u_NTC_T_Lo);
      EEPROM_writeAnything(addr_u_NTC_T_Hi, u_NTC_T_Hi);
      EEPROM_writeAnything(addr_u_NTC_N_enable, u_NTC_N_enable);
      EEPROM_writeAnything(addr_u_NTC_nSamples, UnSamples);
      EEPROM_writeAnything(addr_u_NTC_Rnom, u_NTC_Rnom);
      EEPROM_writeAnything(addr_u_NTC_Tnom, u_NTC_Tnom);
      EEPROM_writeAnything(addr_u_NTC_B, u_NTC_B);
      // SHT 71 RH/T
      EEPROM_writeAnything(addr_Sht_T_Lo, SHT_T_Lo);
      EEPROM_writeAnything(addr_Sht_T_Hi, SHT_T_Hi);
      EEPROM_writeAnything(addr_Sht_RH_Lo, SHT_RH_Lo);
      EEPROM_writeAnything(addr_Sht_RH_Hi, SHT_RH_Hi);
      EEPROM_writeAnything(addr_Sht_Enable, sht_enable);

      Serial.println(F("Settings written to EEPROM"));

    }
    else{// decode command & perform action
      // now resolve the command. 
      // First character encodes what it applies to: 'h' for H0-7, 'u' for U8-9, 'r' for SHT7x RH/T
      // Further 2 chars indicate which command it acutally is; see enum list at top
      char setcmd[3];
      for(int i = 0; i<3;i++){
        setcmd[i] = cmdbuf[i+1];
      }
      switch( lookup(setcmd) ){// lookup command in LUT
      case Tlow:
        {
          switch(cmdbuf[0]){
          case 'h':// set H0-7 NTC Tlow limit
            if(convint > -26 && convint < 76) h_NTC_T_Lo = (float) convint;
            break;
          case 'u':// U8-9 NTC Tlow limit
            if(convint > -26 && convint < 76) u_NTC_T_Lo = (float) convint;
            break;
          case 'r':// SHT7x RH/T T low limit
            if(convint > -41 && convint < 100) SHT_T_Lo = (float) convint;
            break;
          }
          break;
        }
      case Thigh:
        {
          switch(cmdbuf[0]){
          case 'h':// set H0-7 Thigh limit
            if(convint > -26 && convint < 76) h_NTC_T_Hi = (float) convint;
            break;
          case 'u':
            if(convint > -26 && convint < 76) u_NTC_T_Hi = (float) convint;
            break;
          case 'r':
            if(convint > -41 && convint < 100) SHT_T_Hi = (float) convint;
            break;
          }
          break;
        }
      case Nenable:
        {
          byte convbyte = (byte) convint;// convbyte is always positive
          switch(cmdbuf[0]){
          case 'h':// set H0-7 N enable
            if(convbyte < 9) h_NTC_N_enable = convbyte;
            else h_NTC_N_enable = 8;
            break;
          case 'u':// set U8-9 N enable
            if(convbyte > 2) u_NTC_N_enable = 2;
            else u_NTC_N_enable = convbyte;
            break;
          case 'r':// set SHT7x RH/T enable
            if(convbyte) sht_enable = 1;
            else sht_enable = 0;
            break;
          }
          break;
        }
      case nSamples:
        {
          byte convbyte = byte(convint);// convbyte is always positive
          if(convbyte > 8) convbyte = 8;
          if(convbyte == 0) convbyte = 1;
          switch(cmdbuf[0]){
          case 'h': 
            HnSamples = convbyte;
            h_NTC_Mux.setNTCup(hNTCMuxPin, HnSamples, h_NTC_Rnom);
            break;
          case 'u': 
            UnSamples = convbyte;
            u_NTC_8.setNTCup(uNTC08Pin, UnSamples, u_NTC_Rseries);
            u_NTC_9.setNTCup(uNTC09Pin, UnSamples, u_NTC_Rseries);
            break;
          }
          break;
        }
      case Rnom:
        {
          byte convbyte = (byte) convint;// convbyte is always positive
          switch(cmdbuf[0]){
          case 'h':
            if(convbyte == 1 || convbyte == 10){ 
              h_NTC_Rnom = convbyte;
              h_NTC_Mux.setNTCParameters(h_NTC_Rnom, h_NTC_Tnom, h_NTC_B);
            }
            break;
          case 'u':
            if(convbyte == 1 || convbyte == 2 || convbyte == 3 || 
              convbyte == 5 || convbyte == 10 || convbyte == 20){ 
              u_NTC_Rnom = convbyte;
              u_NTC_8.setNTCParameters(u_NTC_Rnom, u_NTC_Tnom, u_NTC_B);
              u_NTC_9.setNTCParameters(u_NTC_Rnom, u_NTC_Tnom, u_NTC_B);
            }
            break;
          }
          break; 
        }
      case Tnom:
        {
          if(convint == 20 || convint == 25){
            switch(cmdbuf[0]){
            case 'h':
              h_NTC_Tnom = (byte) convint;
              h_NTC_Mux.setNTCParameters(h_NTC_Rnom, h_NTC_Tnom, h_NTC_B);
              break;
            case 'u':
              u_NTC_Tnom = (byte) convint;
              u_NTC_8.setNTCParameters(u_NTC_Rnom, u_NTC_Tnom, u_NTC_B);
              u_NTC_9.setNTCParameters(u_NTC_Rnom, u_NTC_Tnom, u_NTC_B);
              break;
            }
          }
          break;
        }
      case Bntc:
        {
          if(convint > 2999 && convint < 4001){
            unsigned int convuint = (unsigned int) convint;
            switch(cmdbuf[0]){
            case 'h':
              h_NTC_B = convuint;
              h_NTC_Mux.setNTCParameters(h_NTC_Rnom, h_NTC_Tnom, h_NTC_B);
              break;
            case 'u':
              u_NTC_B = convuint;
              u_NTC_8.setNTCParameters(u_NTC_Rnom, u_NTC_Tnom, u_NTC_B);
              u_NTC_9.setNTCParameters(u_NTC_Rnom, u_NTC_Tnom, u_NTC_B);
              break;
            }
          }
          break;
        }
      case RHlow:
        {
          if(convint > 0 && convint < 100 && cmdbuf[0] == 'r') SHT_RH_Lo = (float) convint;
          break;
        }
      case RHhigh:
        {
          if(convint > 0 && convint < 100 && cmdbuf[0] == 'r') SHT_RH_Hi = (float) convint;
          break;
        }
      default:
        Serial.println(F("Command not recognised, or value out of range"));
        break;

      }// command interpreter case statement
    }// interpret and execute command
    // command digestion completed. Reset variables to get ready for next serial cmd
    inSerial = 0;
    memset(cmdbuf, '\0', 4);
    memset(intbuf, '\0', 5);
    nBytes = 0;
    commaReceived = LOW;
  }// concluding ENTER received over serial

  // read menu potmeter, and menu pushbutton 
  int menuPotVal = analogRead(menuPot);// for use in menu
  menuButtonRead = digitalRead(menuButton); // read menu button
  if(prevMenuButtonRead == LOW && menuButtonRead == HIGH){// detect leading edge
    delay(50);//require button to be pressed for a bit of time / debounce
    menuButtonRead = digitalRead(menuButton);
    if( menuButtonRead == HIGH ){// if menubutton still high after leading edge, advance menu state to next
      menuState = nextMenuState;
      menuWatchDog = millis();// reset menu timeout timer
    }
  }
  prevMenuButtonRead = menuButtonRead;

  // menu watchdog: check interval between menu button push actions has not exceeded timeout
  if(menuState){// skip watchdog if not in menu (cheap byte check)
    if(millis()-menuWatchDog > maxMenuInterval){// see if timeout occurred (expensive unsigned long operations)
      menuState = 0;
      nextMenuState = DefaultMenu;
    }
  }

  // +=+=+=+=+=+=+=+=+ DISPLAY & MENU STATE MACHINE +=+=+=+=+=+=+=+=+
  // menu uses top line of display. Avoid clearing LCD as it causes flicker
  lcd.home();
  // print Temperature values to lcd on bottom line
  // default is to print H0-7 temperatures. 
  // If no H0-7 NTCs enabled: print U8-9 and RH/T if enabled. Otherwise print blanks.
  if(h_NTC_N_enable){
    lcd.setCursor(0,1);
    for(byte count = 0; count < 8; count++){
      if(count < h_NTC_N_enable){
        dtostrf(h_NTC_T[count], 5, 1, dispTemp);
        lcd.print(dispTemp);
      }
      else lcd.print(F("     "));
    }
  }
  else{// print others
    lcd.setCursor(0,1);
    if(u_NTC_N_enable > 0){
      lcd.print("U8:");// 3 chars
      lcd.setCursor(3,1);
      dtostrf(u_NTC_8_T, 5,1, dispTemp);
      lcd.print(dispTemp);// 5 chars
      lcd.setCursor(8,1);
      lcd.print(degC);// 2 chars
      lcd.setCursor(10,1);
      if(u_NTC_N_enable > 1){
        lcd.print(" U9:");// 4 chars
        lcd.setCursor(14,1);
        dtostrf(u_NTC_9_T, 5,1, dispTemp);
        lcd.print(dispTemp);// 5 chars
        lcd.setCursor(19,1);
        lcd.print(degC);// 2 chars
      }
      else lcd.print(F("          "));// 11 chars
    }
    else lcd.print(F("                     "));// 21 chars
    lcd.setCursor(21,1);
    if(sht_enable){
      lcd.print(F(" RH/T:"));// 6 chars
      lcd.setCursor(27,1);
      dtostrf(sht_T, 5,1, dispTemp);
      lcd.print(dispTemp);// 5 chars
      lcd.setCursor(32,1);
      lcd.print(degC);// 2 chars
      lcd.setCursor(34,1);
      lcd.print(", ");
      lcd.setCursor(36,1);
      dtostrf(sht_RH, 2,0, dispTemp);
      lcd.print(dispTemp);// 2 chars
      lcd.setCursor(38,1);
      lcd.print(" %");
    }
    else lcd.print(F("                   "));// 19 chars
  }

  switch(menuState){
  case DefaultMenu:// menu ground state. Display top line depends on pot position:
    // 0: H0-7 NTC parameters
    // 1: H0-7 & U8-9 T limits
    // 2: H0-7 text
    // 3: U8-9 & RH/T status
    // 4: SHT T, RH limits
    // 5: U8-9 NTC parameters

    setTemp = map(menuPotVal, 0, 1020, 0, 5);
    lcd.home();
    switch(setTemp){
    case 0:
      lcd.print(F("H0-7 nS, Rn, Tn, B: "));//20 chars
      lcd.setCursor(20,0);
      lcd.print(HnSamples, DEC);
      lcd.setCursor(21,0);
      lcd.print(", ");
      lcd.setCursor(23,0);
      if(h_NTC_Rnom < 10){
        lcd.print(' ');
        lcd.setCursor(24,0);
      }
      lcd.print(h_NTC_Rnom, DEC);
      lcd.setCursor(25,0);
      lcd.print(kOhm);// 2 chars
      lcd.setCursor(27,0);
      lcd.print(", ");
      lcd.setCursor(29,0);
      lcd.print(h_NTC_Tnom, DEC);
      lcd.setCursor(31,0);
      lcd.print(degC);
      lcd.setCursor(33,0);
      lcd.print(", ");//2
      lcd.setCursor(35,0);
      lcd.print(h_NTC_B);
      lcd.setCursor(39,0);
      lcd.print('K');
      break;
    case 1:
      lcd.print(F("T limits H0-7: "));// 15 chars
      lcd.setCursor(15,0);
      dtostrf(h_NTC_T_Lo, 3,0, dispTemp);
      lcd.print(dispTemp);
      lcd.setCursor(18,0);
      lcd.print(',');
      lcd.setCursor(19,0);
      dtostrf(h_NTC_T_Hi, 3,0, dispTemp);
      lcd.print(dispTemp);
      lcd.setCursor(22,0);
      lcd.print(degC);
      lcd.setCursor(24,0);
      lcd.print(F("  U8-9: "));// 6 chars
      lcd.setCursor(31,0);
      dtostrf(u_NTC_T_Lo, 3,0, dispTemp);
      lcd.print(dispTemp);
      lcd.setCursor(34,0);
      lcd.print(',');
      lcd.setCursor(35,0);
      dtostrf(u_NTC_T_Hi, 3,0, dispTemp);
      lcd.print(dispTemp);
      lcd.setCursor(38,0);
      lcd.print(degC);
      break;
    case 2:
      if(h_NTC_N_enable) lcd.print(F("|H0  |H1  |H2  |H3  |H4  |H5  |H6  |H7  "));
      else lcd.print(F("H0-7 NTCs disabled                      "));
      break;
    case 3:
      if(u_NTC_N_enable == 0){
        lcd.print(F("U8-9 NTC disabled    "));// 21 chars
      }
      else{
        lcd.setCursor(0,0);
        if(u_NTC_N_enable > 0){
          lcd.print("U8:");// 3 chars
          lcd.setCursor(3,0);
          dtostrf(u_NTC_8_T, 5,1, dispTemp);
          lcd.print(dispTemp);// 5 chars
          lcd.setCursor(8,0);
          lcd.print(degC);// 2 chars
          lcd.setCursor(10,0);
          if(u_NTC_N_enable > 1){
            lcd.print(" U9:");// 4 chars
            lcd.setCursor(14,0);
            dtostrf(u_NTC_9_T, 5,1, dispTemp);
            lcd.print(dispTemp);// 5 chars
            lcd.setCursor(19,0);
            lcd.print(degC);// 2 chars
          }
          else lcd.print(F("           "));// 11 chars
        }
        else lcd.print(F("                     "));// 21 chars
      }
      lcd.setCursor(21,0);
      if(sht_enable){
        lcd.print(F(" RH/T:"));// 6 chars
        lcd.setCursor(27,0);
        dtostrf(sht_T, 5,1, dispTemp);
        lcd.print(dispTemp);// 5 chars
        lcd.setCursor(32,0);
        lcd.print(degC);// 2 chars
        lcd.setCursor(34,0);
        lcd.print(", ");
        lcd.setCursor(36,0);
        dtostrf(sht_RH, 2,0, dispTemp);
        lcd.print(dispTemp);// 2 chars
        lcd.setCursor(38,0);
        lcd.print(" %");
      }
      else lcd.print(F(" SHT RH/T disabled "));// 19 chars
      break;
    case 4:
      lcd.print(F("T, RH limits SHT7x:  "));// 21 chars
      lcd.setCursor(21,0);
      dtostrf(SHT_T_Lo, 3,0, dispTemp);
      lcd.print(dispTemp);
      lcd.setCursor(24,0);
      lcd.print(",");
      lcd.setCursor(25,0);
      dtostrf(SHT_T_Hi, 3,0, dispTemp);
      lcd.print(dispTemp);
      lcd.setCursor(28,0);
      lcd.print(degC);
      lcd.setCursor(30,0);
      lcd.print(" | ");
      lcd.setCursor(33,0);
      dtostrf(SHT_RH_Lo, 2,0, dispTemp);
      lcd.print(dispTemp);
      lcd.setCursor(35,0);
      lcd.print(",");
      lcd.setCursor(36,0);
      dtostrf(SHT_RH_Hi, 2,0, dispTemp);
      lcd.print(dispTemp);
      lcd.setCursor(38,0);
      lcd.print(" %");
      break;
    case 5:
      lcd.print(F("U8-9 nS, Rn, Tn, B: "));//20 chars
      lcd.setCursor(20,0);
      lcd.print(UnSamples, DEC);
      lcd.setCursor(21,0);
      lcd.print(", ");
      lcd.setCursor(23,0);
      if(u_NTC_Rnom < 10){
        lcd.print(' ');
        lcd.setCursor(24,0);
      }
      lcd.print(u_NTC_Rnom, DEC);
      lcd.setCursor(25,0);
      lcd.print(kOhm);// 2 chars
      lcd.setCursor(27,0);
      lcd.print(", ");
      lcd.setCursor(29,0);
      lcd.print(u_NTC_Tnom, DEC);
      lcd.setCursor(31,0);
      lcd.print(degC);
      lcd.setCursor(33,0);
      lcd.print(", ");//2
      lcd.setCursor(35,0);
      lcd.print(u_NTC_B);
      lcd.setCursor(39,0);
      lcd.print('K');
      break;
    }
    nextMenuState = DeviceMenu;
    break;

  case DeviceMenu:
    if(menuPotVal <= menuPotLeftmost){
      //lcd.print(F("                                        "));
      lcd.print(F("H0-7 NTC   u8-9_ntc   sht7x rh/t    exit"));
      menuDevice = 1;
      nextMenuState = NTCMenu;
    }
    else if(menuPotVal > menuPotLeftmost && menuPotVal < 512){
      lcd.print(F("h0-7 ntc   U8-9 NTC   sht7x rh/t    exit"));
      menuDevice = 2;
      nextMenuState = NTCMenu;
    }
    else if(menuPotVal > 512 && menuPotVal < menuPotRightmost){
      lcd.print(F("h0-7 ntc   u8-9 ntc   SHT7x RH/T    exit"));
      menuDevice = 3;
      nextMenuState = SHTMenu;
    }
    else{
      lcd.print(F("h0-7 ntc   u8-9 ntc   sht71 rh/t    EXIT"));
      menuDevice = 0;
      nextMenuState = DefaultMenu;
    }
    break;

  case NTCMenu:
    if(menuDevice == 1) lcd.print(F("H0-7 NTCs:"));//10 chars
    else if(menuDevice == 2) lcd.print(F("U8-9 NTCs:"));//10 chars
    lcd.setCursor(10,0);
    if(menuPotVal < potClickLo){
      lcd.print(F("T LIMITS  N enable  set values"));//30 chars
      nextMenuState = TLimitsMenu;
    }
    else if(menuPotVal > potClickHi){
      lcd.print(F("t limits  N enable  SET VALUES"));
      nextMenuState = NTCValuesMenu;
    }
    else{
      lcd.print(F("t limits  N ENABLE  set values"));
      if(menuDevice == 1) nextMenuState = SetHNEnable;
      else if(menuDevice == 2) nextMenuState = SetUNEnable;
    }
    break;

  case SHTMenu:
    eepromWritten = 0;
    lcd.print(F("SHT7x RH/T: "));//12 chars
    lcd.setCursor(12,0);
    if(menuPotVal < potClickLo){
      if(sht_enable){
        lcd.print(F("DISABLE"));// 7 chars
        menuChoice = 1;
      }
      else{
        lcd.print(F("ENABLE "));
        menuChoice = 2;
      }
      lcd.setCursor(19,0);
      lcd.print(F("  t limits  rh limits"));// 21 chars
      nextMenuState = SetSHTEnable;
    }
    else{  
      if(sht_enable) lcd.print(F("disable"));
      else lcd.print(F("enable "));
      lcd.setCursor(19,0);
      if(menuPotVal > potClickHi){
        lcd.print(F("  t limits  RH LIMITS"));
        nextMenuState = SHTRHLimitsMenu;
      }
      else{
        lcd.print(F("  T LIMITS  rh limits"));
        nextMenuState = TLimitsMenu;
      }
      menuChoice = 0;
    }
    break;

  case TLimitsMenu:
    switch(menuDevice){
    case 1: 
      lcd.print(F("H0-7 NTC T limits:  "));//20 chars
      break;
    case 2: 
      lcd.print(F("U8-9 NTCs T limits: "));
      break;
    case 3: 
      lcd.print(F("SHT7x RH/T T limits:"));
      break;
    case 0: 
      lcd.print(F("+-+-+   ERROR  +-+-+")); 
      nextMenuState = DefaultMenu;
      break;
    }
    lcd.setCursor(20,0);
    if(menuPotVal < potClickLo){
      lcd.print(F("LOW     exit    high"));// 20 chars
      nextMenuState = SetTemperatureLimit;
      menuChoice = 1;
    }
    else if(menuPotVal > potClickHi){
      lcd.print(F("low     exit    HIGH"));
      nextMenuState = SetTemperatureLimit;
      menuChoice = 2;
    }
    else{
      lcd.print(F("low     EXIT    high"));
      nextMenuState = DefaultMenu;
      menuChoice = 0;
    }
    break;

  case SetTemperatureLimit:
    eepromWritten = 0;
    lcd.print(F("Dial & push to set Temperature:  "));//35 chars
    switch(menuDevice){
    case 1:
    case 2:
      setTemp = map(menuPotVal, 0, 1020, -25, 75);// range for NTCs
      break;
    case 3:
      setTemp = map(menuPotVal, 0, 1020, -40, 99);// range for SHT7x
      break;
    }
    lcd.setCursor(35,0);
    dtostrf(setTemp, 3, 0, dispTemp);
    lcd.print(dispTemp);
    lcd.setCursor(38,0);
    lcd.print(degC);
    nextMenuState = StoreTemperatureLimit;
    break;

  case StoreTemperatureLimit:
    float compTemp;
    unsigned int addrTemp;
    if(menuChoice == 1){// low T limit
      switch(menuDevice){
      case 1:// H0-7 NTC
        compTemp = h_NTC_T_Hi;
        addrTemp = addr_h_NTC_T_Lo;
        break;
      case 2:// U8-9 NTC
        compTemp = u_NTC_T_Hi;
        addrTemp = addr_u_NTC_T_Lo;
        break;
      case 3://  RH/T
        compTemp = SHT_T_Hi;
        addrTemp = addr_Sht_T_Lo;
        break;
      }
      if(float(setTemp) > compTemp){
        lcd.print(F("Temperature not set: Low > High limit   "));
      }
      else{
        //h_NTC_T_Lo = float(setTemp);
        if(eepromWritten == 0){
          EEPROM_writeAnything(addrTemp, float(setTemp) );
        }
        lcd.print(F("Low Temperature limit set to:      "));
        lcd.setCursor(35,0);
        dtostrf(setTemp, 3, 0, dispTemp);
        lcd.print(dispTemp);
        lcd.setCursor(38,0);
        lcd.print(degC);
      }
    }
    else if(menuChoice == 2){// high temperature limit
      switch(menuDevice){
      case 1:
        compTemp = h_NTC_T_Lo;
        addrTemp = addr_h_NTC_T_Hi;
        break;
      case 2:
        compTemp = u_NTC_T_Lo;
        addrTemp = addr_u_NTC_T_Hi;
        break;
      case 3:
        compTemp = SHT_T_Lo;
        addrTemp = addr_Sht_T_Hi;
        break;
      }
      if(float(setTemp) < compTemp){
        lcd.print(F("Temperature not set: High < Low limit   "));
      }
      else{
        //h_NTC_T_Hi = float(setTemp);
        if(eepromWritten == 0){
          EEPROM_writeAnything(addrTemp, float(setTemp));

        }
        lcd.print(F("High Temperature limit set to:     "));
        lcd.setCursor(35,0);
        dtostrf(setTemp, 3, 0, dispTemp);
        lcd.print(dispTemp);
        lcd.setCursor(38,0);
        lcd.print(degC);
      }
    }
    else{
      lcd.print(F("Error: temperature limit not set        "));
    }
    if(eepromWritten == 0){
      switch(menuDevice){
      case 1:
        EEPROM_readAnything(addr_h_NTC_T_Lo, h_NTC_T_Lo);
        EEPROM_readAnything(addr_h_NTC_T_Hi, h_NTC_T_Hi);
        break;
      case 2:
        EEPROM_readAnything(addr_u_NTC_T_Lo, u_NTC_T_Lo);
        EEPROM_readAnything(addr_u_NTC_T_Hi, u_NTC_T_Hi);
        break;
      case 3:
        EEPROM_readAnything(addr_Sht_T_Lo, SHT_T_Lo);
        EEPROM_readAnything(addr_Sht_T_Hi, SHT_T_Hi);
        break;
      }
      eepromWritten = 1;
    }
    nextMenuState = TLimitsMenu;
    break;

  case SetHNEnable:
    eepromWritten = 0;
    lcd.print(F("H0-7 NTC enable (0..8, exit):   "));// 32 chars
    menuChoice = map(menuPotVal, 0, 1020, 0, 9);

    lcd.setCursor(32,0);
    if(menuChoice == 9){
      lcd.print(F("    EXIT"));
      nextMenuState = DefaultMenu;
    }
    else{
      for(byte count = 0; count < 9; count++){
        lcd.setCursor((32+count),0);
        if(count < menuChoice) lcd.print('O');
        else lcd.print('X');
      }
      nextMenuState = StoreHNEnable;
    }
    break;

  case StoreHNEnable:
    h_NTC_N_enable = menuChoice;
    if(eepromWritten == 0){
      EEPROM_writeAnything(addr_h_NTC_N_enable, menuChoice);
      eepromWritten = 1;
    }
    lcd.print(F("H0-7 NTC enable mask set to:    "));// 32 chars
    for(byte count = 0; count < 8; count++){
      lcd.setCursor((32+count),0);
      if(count < menuChoice) lcd.print('O');
      else lcd.print('X');
    }
    nextMenuState = SetHNEnable;
    break;

  case SetUNEnable:
    eepromWritten = 0;
    lcd.print(F("U8-9 NTC enable (0..2, exit): "));// 30 chars
    lcd.setCursor(30,0);
    if(menuPotVal < menuPotLeftmost){
      lcd.print(F("        XX"));
      menuChoice = 0;
      nextMenuState = StoreUNEnable;
    }
    else if(menuPotVal > menuPotLeftmost && menuPotVal < potClickHi){
      lcd.print(F("        OX"));
      menuChoice = 1;
      nextMenuState = StoreUNEnable;
    }
    else if(menuPotVal > potClickHi && menuPotVal < menuPotRightmost){
      lcd.print(F("        OO"));
      menuChoice = 2;
      nextMenuState = StoreUNEnable;
    }
    else{
      lcd.print(F("      EXIT"));
      nextMenuState = DefaultMenu;
    }
    break;

  case StoreUNEnable:
    u_NTC_N_enable = menuChoice;
    if(eepromWritten == 0){
      EEPROM_writeAnything(addr_u_NTC_N_enable, menuChoice);
      eepromWritten = 1;
    }
    lcd.print(F("U8-9 NTC enable mask set to:          "));// 38 chars
    for(byte count = 0; count < 2; count++){
      lcd.setCursor((38+count),0);
      if(count < menuChoice) lcd.print('O');
      else lcd.print('X');
    }
    nextMenuState = SetUNEnable;
    break;

  case SetSHTEnable:
    if(menuChoice == 2){// previous disabled state should become enabled
      lcd.print(F("SHT7x RH/T sensor is now ENABLED        "));
      sht_enable = 1;
    }
    else{
      lcd.print(F("SHT7x RH/T sensor is now DISABLED       "));
      sht_enable = 0;
    }
    if(eepromWritten == 0){
      EEPROM_writeAnything(addr_Sht_Enable, sht_enable);
      eepromWritten = 1;
    }
    nextMenuState = DeviceMenu;
    break;

  case SHTRHLimitsMenu:
    lcd.print(F("SHT7x RH/T RH limits: "));// 22 chars
    lcd.setCursor(22,0);
    if(menuPotVal < potClickLo){
      lcd.print(F("LOW    exit   high"));// 18 chars
      nextMenuState = SetSHTRHLimit;
      menuChoice = 1;
    }
    else if(menuPotVal > potClickHi){
      lcd.print(F("low    exit   HIGH"));
      nextMenuState = SetSHTRHLimit;
      menuChoice = 2;
    }
    else{
      lcd.print(F("low    EXIT   high"));
      nextMenuState = DefaultMenu;
      menuChoice = 0;
    }
    break;

  case SetSHTRHLimit:
    eepromWritten = 0;
    lcd.print(F("Dial & push to set Rel. Humidity:   "));//36 chars
    setTemp = map(menuPotVal, 0, 1020, 5, 95);
    lcd.setCursor(36,0);
    dtostrf(setTemp, 2, 0, dispTemp);
    lcd.print(dispTemp);
    lcd.setCursor(38,0);
    lcd.print(" %");
    nextMenuState = StoreSHTRHLimit;
    break;

  case StoreSHTRHLimit:
    if(menuChoice == 1){// low RH limit
      if(float(setTemp) > SHT_RH_Hi){
        lcd.print(F("Rel. Humidity not set: Low > High limit "));
      }
      else{
        SHT_RH_Lo = float(setTemp);
        if(eepromWritten == 0){
          EEPROM_writeAnything(addr_Sht_RH_Lo, SHT_RH_Lo);
          eepromWritten = 1;
        }
        lcd.print(F("Low Relative Humidity limit set to: "));// 36 chars
      }
    }
    else if(menuChoice == 2){// high RH limit
      if(float(setTemp) < SHT_RH_Lo){
        lcd.print(F("Rel. Humidity not set: High < Low limit "));
      }
      else{
        SHT_RH_Hi = float(setTemp);
        if(eepromWritten == 0){
          EEPROM_writeAnything(addr_Sht_RH_Hi, SHT_RH_Hi);
          eepromWritten = 1;
        }
        lcd.print(F("High Relative Humidity limit set to:"));// 36 chars
      }
    }
    lcd.setCursor(36,0);
    lcd.print(setTemp);
    lcd.setCursor(38,0);
    lcd.print(" %");
    nextMenuState = SHTRHLimitsMenu;
    break;

  case NTCValuesMenu:
    if(menuDevice == 1) lcd.print(F("H0-7 NTCs:"));//10 chars
    else if(menuDevice == 2) lcd.print(F("U8-9 NTCs:"));//10 chars
    lcd.setCursor(10,0);
    if(menuPotVal < potClickLo){
      lcd.print(F("NSAMPLES  exit  ntc parameters"));
      nextMenuState = SetNTCNSamples;
    }
    else if(menuPotVal > potClickHi){
      lcd.print(F("nsamples  exit  NTC PARAMETERS"));
      nextMenuState = SetNTCRnom;
    }
    else{
      lcd.print(F("nsamples  EXIT  ntc parameters"));
      menuDevice = 0;
      nextMenuState = DefaultMenu;
    }
    break;

  case SetNTCNSamples:
    {
      if(menuDevice == 1) lcd.print(F("H0-7 "));//5 chars
      else if(menuDevice == 2) lcd.print(F("U8-9 "));//5 chars
      lcd.setCursor(5,0);
      setTemp = map(menuPotVal, 0, 1020, 0, 3);
      menuChoice = 1;
      byte nStep = 0;
      while(nStep != byte(setTemp) ){
        menuChoice *= 2;
        nStep++;
      }
      lcd.print(F("NTCs: nSamples for average filter "));//34
      lcd.setCursor(39,0);
      lcd.print(menuChoice, DEC);
      eepromWritten = 0;
      nextMenuState = StoreNTCNSamples;
    }
    break;

  case StoreNTCNSamples:
    if(menuDevice == 1){
      if(eepromWritten == 0){
        HnSamples = menuChoice;
        EEPROM_writeAnything(addr_h_NTC_nSamples, HnSamples);
        h_NTC_Mux.setNTCup(hNTCMuxPin, HnSamples, h_NTC_Rnom);
        eepromWritten = 1;
      }
      lcd.print(F("H0-7"));// 4 chars
    }
    else if(menuDevice == 2){
      if(eepromWritten == 0){
        UnSamples = menuChoice;
        EEPROM_writeAnything(addr_u_NTC_nSamples, UnSamples);
        u_NTC_8.setNTCup(uNTC08Pin, UnSamples, u_NTC_Rseries);
        u_NTC_9.setNTCup(uNTC09Pin, UnSamples, u_NTC_Rseries);
        eepromWritten = 1;
      }
      lcd.print(F("U8-9"));
    }
    lcd.setCursor(4,0);
    lcd.print(F(" averaging filter nSamples set to  "));// 35 chars
    lcd.setCursor(39,0);
    if(menuDevice == 1) lcd.print(HnSamples, DEC);
    else if(menuDevice == 2) lcd.print(UnSamples, DEC);
    nextMenuState = DefaultMenu;
    break;

  case SetNTCRnom:
    eepromWritten = 0;
    if(menuDevice == 1) lcd.print(F("H0-7"));// 4 chars
    if(menuDevice == 2) lcd.print(F("U8-9"));
    lcd.setCursor(4,0);
    lcd.print(F(": dial & push to set Rnom/EXIT  "));// 32 chars
    setTemp = map(menuPotVal, 0, 1020, 0, 6);
    if(menuDevice == 2){// U8-9 NTC: selected values 1-20kOhm
      switch(setTemp){
      case 0:
        menuChoice = 1;
        break;
      case 1:
        menuChoice = 2;
        break;
      case 2:
        menuChoice = 3;
        break;
      case 3:
        menuChoice = 5;
        break;
      case 4:
        menuChoice = 10;
        break;
      case 5:
        menuChoice = 20;
        break;
      case 6:
        menuChoice = 0;
        break;
      }      
    }
    else{// H0-7 NTC, Rnom is either 10 or 1 kOhm
      if(setTemp == 6) menuChoice = 0;
      else{
        if(setTemp < 3) menuChoice = 1;
        else if (setTemp < 6) menuChoice = 10;    
      }
    }
    lcd.setCursor(36,0);
    if(menuChoice == 0){
      lcd.print(F("EXIT"));
      nextMenuState = DefaultMenu;
    }
    else{
      if(menuChoice < 10){
        lcd.print(' ');
        lcd.setCursor(37,0);
      }
      lcd.print(menuChoice, DEC);
      lcd.setCursor(38,0);
      lcd.print(kOhm);
      nextMenuState = StoreNTCRnom;
    }
    break;

  case StoreNTCRnom:
    if(menuDevice == 1){
      if(eepromWritten == 0){
        h_NTC_Rnom = menuChoice;
        EEPROM_writeAnything(addr_h_NTC_Rnom, h_NTC_Rnom);
        eepromWritten = 1;
      }
      lcd.print(F("H0-7"));// 4 chars
    }
    else if(menuDevice == 2){
      if(eepromWritten == 0){
        if(menuChoice) u_NTC_Rnom = menuChoice;
        EEPROM_writeAnything(addr_u_NTC_Rnom, u_NTC_Rnom);
        eepromWritten = 1;
      }
      lcd.print(F("U8-9"));
    }
    lcd.setCursor(4,0);
    lcd.print(F(" NTC nominal R set to:         "));//31 chars 
    lcd.setCursor(35,0);
    if(menuChoice < 10){
      lcd.print("  ");
      lcd.setCursor(37,0);
    }
    else{
      lcd.print(' ');
      lcd.setCursor(36,0);
    }
    lcd.print(menuChoice, DEC);
    lcd.setCursor(38,0);
    lcd.print(kOhm);
    nextMenuState = SetNTCTnom;
    break;

  case SetNTCTnom:
    eepromWritten = 0;
    lcd.print(F("Dial & push to set NTC T(Rnom):     "));// 36 chars
    lcd.setCursor(36,0);
    if(menuPotVal < potClickHi){
      lcd.print(F("20"));
      menuChoice = 20;
    }
    else{
      lcd.print(F("25"));// deg C");
      menuChoice = 25;
    }
    lcd.setCursor(38,0);
    lcd.print(degC);
    nextMenuState = StoreNTCTnom;
    break;

  case StoreNTCTnom:
    lcd.print(F("NTC Temperature (Rnominal) set to:  "));//36 chars
    lcd.setCursor(36,0);
    lcd.print(menuChoice, DEC);
    lcd.setCursor(38,0);
    lcd.print(degC);
    if(eepromWritten == 0){
      if(menuDevice == 1){
        EEPROM_writeAnything(addr_h_NTC_Tnom, menuChoice);
        h_NTC_Tnom = menuChoice;
      }
      else if(menuDevice == 2){
        EEPROM_writeAnything(addr_u_NTC_Tnom, menuChoice);
        u_NTC_Tnom = menuChoice;
      }
      eepromWritten = 1;
    }
    nextMenuState = SetNTCB;
    break;

  case SetNTCB:
    eepromWritten = 0;
    lcd.print(F("Dial & push to set NTC B value:    "));//34 chars
    setTemp = 5*(map(menuPotVal, 0, 1020, 600, 800));// u_NTC_B: 3000-4000 steps of 5
    lcd.setCursor(34,0);
    lcd.print(setTemp); 
    lcd.setCursor(38,0);
    lcd.print(" K");
    if(menuDevice == 1) h_NTC_B = setTemp;
    else if (menuDevice == 2) u_NTC_B = setTemp;
    nextMenuState = StoreNTCB;
    break;

  case StoreNTCB:
    if(menuDevice == 1){
      if(eepromWritten == 0){
        EEPROM_writeAnything(addr_h_NTC_B, h_NTC_B);
        EEPROM_readAnything(addr_h_NTC_Rnom, h_NTC_Rnom);
        EEPROM_readAnything(addr_h_NTC_Tnom, h_NTC_Tnom);
        EEPROM_readAnything(addr_h_NTC_B, h_NTC_B);
        h_NTC_Mux.setNTCup(hNTCMuxPin, HnSamples, h_NTC_Rnom);
        h_NTC_Mux.setNTCParameters(h_NTC_Rnom, h_NTC_Tnom, h_NTC_B);
        eepromWritten = 1;
      }
      lcd.print(F("H0-7"));
    }
    else if(menuDevice == 2){
      if(eepromWritten == 0){
        EEPROM_writeAnything(addr_u_NTC_B, u_NTC_B);
        EEPROM_readAnything(addr_u_NTC_Rnom, u_NTC_Rnom);
        EEPROM_readAnything(addr_u_NTC_Tnom, u_NTC_Tnom);
        EEPROM_readAnything(addr_u_NTC_B, u_NTC_B);
        u_NTC_8.setNTCParameters(u_NTC_Rnom, u_NTC_Tnom, u_NTC_B);
        u_NTC_9.setNTCParameters(u_NTC_Rnom, u_NTC_Tnom, u_NTC_B);
        eepromWritten = 1;
      }
      lcd.print(F("U8-9"));
    }
    lcd.setCursor(4,0);
    lcd.print(F(" NTC B value set to:          "));// 30 chars
    lcd.setCursor(34,0);
    if(menuDevice == 1) lcd.print(h_NTC_B);
    if(menuDevice == 2) lcd.print(u_NTC_B);
    lcd.setCursor(38,0);
    lcd.print(" K");
    nextMenuState = DeviceMenu;
    break;
  }
}// loop()

// FUNCTIONS

// clear lcd line
void lcdClearLine(int lineNum){
  if(lineNum == 0 || lineNum == 1){
    lcd.setCursor(0,lineNum);
    lcd.print(F("                                        "));
  }
  return;
}

void printSensorValues(){
  for(byte count = 0; count < 8; count++){
    if(count < h_NTC_N_enable){   
      Serial.print(h_NTC_T[count],1  );
      Serial.print(',');
    }
    else{
      Serial.print("-99.9,");
    }
  }
  switch(u_NTC_N_enable){
  case 0:
    Serial.print(F("-99.9,-99.9,"));
    break;
  case 1:
    Serial.print(u_NTC_8_T,1);
    Serial.print(F("-99.9,"));
  case 2:
    Serial.print(u_NTC_8_T,1);
    Serial.print(',');
    Serial.print(u_NTC_9_T,1);
    Serial.print(',');
    break;
  }
  if(sht_enable > 0){
    Serial.print(sht_T,1);
    Serial.print(',');
    Serial.print(sht_RH,1);
    Serial.print(',');
  }
  else Serial.print("-99.9,99.9,");
  if(outputState) Serial.println(F("ENABLED"));
  else Serial.println(F("DISABLED"));

#ifdef DEBUG
  Serial.print(F("freeMemory():"));
  Serial.println( freeMemory() );
#endif

  return;
}


// lookup commands received over serial in lookup table
int lookup(char rxcmd[])
{
  const int ncmds = sizeof lookup_table / sizeof *lookup_table;
  for(int i=0; i!= ncmds; i++)
    if( strcmp(rxcmd, lookup_table[i]) == 0) return i;
  return None;
}

#ifdef DEBUG
/* Calculates the size of the free list */
int freeListSize() {
  struct __freelist* current;
  int total = 0;
  for (current = __flp; current; current = current->nx) {
    total += 2; /* Add two bytes for the memory block's header  */
    total += (int) current->sz;
  }
  return total;
}

int freeMemory() {
  int free_memory;
  if ((int)__brkval == 0) {
    free_memory = ((int)&free_memory) - ((int)&__heap_start);
  } 
  else {
    free_memory = ((int)&free_memory) - ((int)__brkval);
    free_memory += freeListSize();
  }
  return free_memory;
}
#endif







