Replacing the electronics in a hot tub – Part 4: Arduino Sensor and display control

This is the fourth part of a series about replacing the electronics in a hot tub. The first post is here. This post is about the “sensors” code. This Arduino will be quite busy, as I have identified about 10 sensors in the hot tub. Most are temperature sensors and pressure sensors. I haven’t, in the testing phase, opened the hot tub to physically check what is where exactly. So the code below will change a lot. There are pressure sensors in the hot tub. In the (old) documentation, they are described as “a security feature if part (mostly very long hair) of a bather is stuck in a pump water entry). I haven’t really looked at them yet (and might not).

Sensors

The “sensors” Arduino has to read data from many sensors, some of which take up to a second to process in my testing environment. This Arduino also manages the display. I have noticed that some on the sensor libraries “block” the processor and the micro-controller might not read an incoming message in time. There is no direct user action (buttons/switches) to this micro-controller. Most of the time, the sensor Arduino will “publish” data to the server (Raspberry Pi) so this might not be an issue.

Temperature

Obviously, the first type of sensor is a temperature sensor. I am testing two different sensors: a simple one-wire DS18B20 sensor and an IIC SHT31 sensor that I had on hand. As mentioned, I have no idea what actual sensors are present in the hot tub, but the logic will be very similar.

 sht31_temp = sht31.getTemperature();
 //float hum = sht31.getHumidity();

  // Call sensors.requestTemperatures() to issue a global temperature and Requests to all devices on the bus
  sensors.requestTemperatures();
  ds18b20_temp = 0.0;
  ds18b20_temp = sensors.getTempCByIndex(0);
  ds18b20_tempF = sensors.getTempFByIndex(0);
  if (degreeCelsius) {
    temperature = ds18b20_temp;
  } else {
    temperature = ds18b20_tempF;
  }

This code gets the work done. First, we ask the SHT31 for temperature. This sensor can also return humidity level, but I didn’t think there was any use for that. The next few lines store the Celsius and Fahrenheit temperature in the ‘temperature’ variable. I use Celsius degrees for all internal processing, but since the old spa electronics used Fahrenheit degrees exclusively on the display, I kept the possibility to display the ‘old way’ (and to please my american friends…). The micro-controller start with ‘degreeCelsius’ set to 1 and the user has to press a particular button to display the temperature in Fahrenheit.

The next block of code has one part similar to the “actuators” code. When ‘command’ is “identify” it returns a JSON string: {“identity”: “sensors”}. But then this Arduino has a lot more to do: manage time, sensors and temperature. So the command can be ‘sendTime’. For this one, I use ‘sprint()‘ to build a string in one go containing formatted time like “hh:mm:ss” and then send it as JSON.

Incoming commands

if (Serial.available()) {
    String command = Serial.readStringUntil('\n');
    if (command == "identify") {
      Serial.flush();
      Serial.println("{\"identity\": \"sensors\"}");
      //Serial.println("}");
      //Serial.print("identity");
    } else if (command == "sendTime") {
      handleTime();
      Serial.flush();
      sprintf(jsonBuffer, "\"time\": \"%02d\:%02d\:%02d\"}", clock.hour, clock.minute, clock.second);
      Serial.print("{\"controller\": \"time\", ");
      Serial.println(jsonBuffer);

Return time or temperature

The rest of the block will use a 4 digit LED display to show time or temperature. Because of the C/F degree possibility, receiving the command alternates between displaying the temperature in Celsius or Fahrenheit. It the calls the ‘displayTemperature()’ function.

The last part of this code will receive the command ‘setTemp’ that contains a floating point number formatted as a string (ex “setTemp18.5). The ‘command.substring(7).toFloat()’ reads from position 7 to the end of the string. This is not JSONified for efficiency purposes. I display the temperature on the LCD display (more later) and finally call the ‘displayTemperature()’ function.

      else if (command == "showTime") {
      displayMode = timeMode;
    } else if (command == "showTemp") {
      displayMode = tempMode;
      // Alternate between deg C/F
      if (degreeCelsius) {
        degreeCelsius = 0;  //show temp in Farenheit next time
        temperature = ds18b20_tempF;
      } else {
        degreeCelsius = 1;  //show twmp in Celsius next time
        temperature = ds18b20_temp;
      }
      displayTemperature();
    } else if (command.substring(0, 7) == "setTemp") {
      displayMode = tempMode;
      degreeCelsius = 1;  //show temp in Celsius
      showTemp = millis();
      temperature = command.substring(7).toFloat();
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print(command.substring(7));
      displayTemperature();
    }

Display time and send data

Part of the main loop will call the ‘handleTime()’ function every second (when the current second is not the same as the previous (last) second). Finally, after a set interval (10 seconds while testing) I format the sensor data and send it to the main computer.

  clock.getTime();
  // Display time and temperature every second
  if (lastSecond != clock.second) {
    handleTime();
    if (displayMode == tempMode) {
      displayTemperature();
    }
    lastSecond = clock.second;
  }
  // Display and send Sensor data at fixed ineterval
  if (millis() - lastDataSent >= sendInterval) {
    lastDataSent = millis();
    prepareSensors(); //Format the data as JSON string
    sendData(); //Send it!
  }

Functions

There are a few functions accompanying the code. ‘handleTime()’ formats thee time as above for LCD display and formats the JSON string that will contain the time (sent in the next function)

void handleTime() {

  //clock.getTime();

  if (displayMode == timeMode) {
    int displayTime = clock.hour * 100 + clock.minute;
    //Little trick to turn the display colon on and off
    ClockPoint = (~ClockPoint) & 0x01;
    if (ClockPoint) {
      display.showNumberDecEx(displayTime, 0b01000000);  // Colon ON
    } else {
      display.showNumberDecEx(displayTime);
    }
  }

  // Format to print leading zero and colon
  sprintf(timeBuffer, "%02d:%02d:%02d", clock.hour, clock.minute, clock.second);

  // Show formated time on LCD Display
  lcd.setCursor(0, 0);
  lcd.print("                ");  // Blank the TIME line
  lcd.setCursor(0, 0);
  lcd.print(timeBuffer);


  // Send data as JSON
  sprintf(jsonBuffer, "\"time\": \"%02d\:%02d\:%02d\"}", clock.hour, clock.minute, clock.second);

}

The temperature data has to be read from the sensors. This part of thee code will be optimized when I have access to the real sensors in the hot tub.

void prepareSensors() {

  sht31_temp = sht31.getTemperature();

  // Call sensors.requestTemperatures() to issue a global temperature and Requests to all devices on the bus
  sensors.requestTemperatures();
  ds18b20_temp = 0.0;
  ds18b20_temp = sensors.getTempCByIndex(0);
  ds18b20_tempF = sensors.getTempFByIndex(0);
  if (degreeCelsius) {
    temperature = ds18b20_temp;
  } else {
    temperature = ds18b20_tempF;
  }

}

Send data

We then send the temperature as a JSON string: {“controller”: “sensors”, “DS18B20”: 999.9, “SHT31”: 999.9}

void sendData() {

  Serial.print("{\"controller\": \"sensors\", ");
  Serial.print("\"DS18B20\": ");
  Serial.print(ds18b20_temp);  // Always sending Celsius temperature
  Serial.print(", \"SHT31\": ");
  Serial.print(sht31_temp);
  Serial.println("}");
}

Display temperature

The ‘displayTemperature()’ function has two parts. First I format the temperature to display it on the 4-digit LED display. When displaying in Celsius, the first digit is always 0 so is not displayed. Then we format digits 2 and 3 to display the integer value of the temp. Note that for precision, I do not round the temperature. So I display a ‘minus’ kind of sign above the letter ‘c’ in the last digit when the actual temperature is within half a degree less than the temperature shown. The library that I use allows me to choose exactly which of the 7 segments are lit at any time. For the Fahrenheit temp, I round the value and display 2 or 3 digits, depending. The last little part display the Celsius temp on the tested LCD display.

There is a good chance that the display used in the final version will be different, so this code will change.

void displayTemperature() {

  if (degreeCelsius) {
    displayData[0] = 0;  // No segment lit
    displayData[1] = display.encodeDigit(int(round(temperature)) / 10);
    displayData[2] = display.encodeDigit(int(round(temperature)) % 10);
    // Trick to display a single top bar if you don´t have dots for decimal
    if (int(round(temperature)) - temperature > 0)
      displayData[3] = 0x59;  // "c" on display = 0x58 and 0x59 for minus (-) bar
    else
      displayData[3] = 0x58;  // "c" on display = 0x58 and 0x59 for minus (-) bar
    display.setSegments(displayData);
  } else {
    tempFarenheit = int(round(temperature));
    if (tempFarenheit >= 100) {
      displayData[0] = display.encodeDigit(1);
    } else {
      displayData[0] = 0;
    }
    displayData[1] = display.encodeDigit(tempFarenheit / 10);
    displayData[2] = display.encodeDigit(tempFarenheit % 10);
    displayData[3] = 0x71;  // "F" on display = 0x71
    display.setSegments(displayData);
  }

  lcd.setCursor(0, 1);
  lcd.print("                ");
  lcd.setCursor(0, 1);
  lcd.print("Temp: ");
  lcd.print(ds18b20_temp);  // Always in Celcius
  lcd.print(" C");
}

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *