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");
}
Leave a Reply