I used an Elegoo Uno R3 Arduino clone with a prototype expansion shield. The shield provided enough tie-ins for power and ground wires to run everything. I used a SunFounder 20x4 display with I2C backpack board (four-wire hook up), an AM2302 for temperature and humidity, and two DS18B20 as temperature sensors. For power control, I went with IoT Relay boxes from Digital Loggers for convenience and elegance.
I had a lot of trouble sourcing the kind of heaters I wanted at a reasonable cost. I finally found the right form factor at Banggood, and they were less than $8 each. If I had paid $40 I would have gotten a Chinese heater either way, so we're giving this a shot. Last but not least, I used telephone wire to extend everything, and I wired it all through a pair of VGA connectors that had the pins expanded out to screw terminals. Wiring a bunch of things using idential red/green/yellow/black wires was fun to keep straight. The DS18B20 require a 4.7 kOhm pull-up resistor wired between data and power, and I simply soldered these in where my wires entered the connector terminals, with a little heat shrink tubing to protect them from shorting out by accident.
Here is the sensor bundle tangle (resistors are inside that plug):
The DS18B20 are One Wire sensors that can share a bus, but I had plenty of unused data pins and it was less complicated to wire them into separate buses. Each sensor has a unique address, which means you can address them individually on the same bus, but it also means you can't swap a component without changing the address in code.
Here are the parts that got installed into the case. Actually, this is an old picture. The resistor ended up in the plug, and I moved the relay wires from pins 0 and 1 to pins 10 and 11. Something, somewhere was interfering with 0 and 1, which took me forever to figure out. I never did figure out what. Moving the wires to different pins got everything working, and I lived happily ever after:
There are public libraries for all components. So many public libraries that finding one that worked for me was one of the hardest challenges For the curious, here's the code. I didn't invest effort making it elegant, because I didn't really expect to post it in public, but I don't really see any reason not to publish it. What, I was going to manufacture these things? Not on your life!
Incidentally, I went live with the system with the 5-second delay, because I never uploaded the code again after changing it to a 60-second delay. I was worried about switching things off and on too rapidly, but the sensors an heaters are both slow, and I think this will be fine as is.
/*
Dewbane Condensation Management System
Prevents condensation on equipment by keeping its temperature
elevated above the dew point. This system is designed to manage
one lathe and one mill from a single unit.
Copyright (c) 2018 D. Michael McIntyre
michael@highlanddragonforge.com
This code was developed for an Arduino Uno R3 using the following:
1 AM2302 temperature and humidity sensor
2 DS18B20 temperature sensors (on separate buses for convenience)
2 IoT Relay enclosed high-power power relay
4 100W silicone heaters (2 per machine)
Lathe: yellow zip ties
The theory of operation is as follows:
* poll AM2302 to get temperature in Celsius and relative humidity
* use temperature and humidity to calculate dew point
* for equipment 1, equipment 2 do
* poll the DS18B20 for the equipment n
* turn off IoT relay for equipment n heaters if lathe is above dew point
* else turn on IoT relay for equipment n heaters
* update LCD display to reflect current status
* loop infinitely
*/
#include "cactus_io_AM2302.h"
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "cactus_io_DS18B20.h"
// set the LCD address to 0x27
LiquidCrystal_I2C lcd(0x27, 20, 4);
// pin defines
#define AM2302_PIN 2 // temp/humidity data pin
#define LATHE_T_PIN 8 // lathe temp data pin
#define MILL_T_PIN 9 // mill temp data pin
#define LATHE_R_PIN 10 // lathe heat relay
#define MILL_R_PIN 11 // mill heat relay
// set to 60000 for production, 5000 for debugging
//#define LOOP_DELAY 60000
#define LOOP_DELAY 5000
// Create DS18B20 objects
DS18B20 LATHE(LATHE_T_PIN);
DS18B20 MILL(MILL_T_PIN);
bool latheHeat = false;
bool millHeat = false;
float dewPoint = 0.0;
float displayTemperature = 0.0;
float displayHumidity = 0.0;
float latheTemperature = 0.0;
float millTemperature = 0.0;
// degree symbol
byte degreeSymbol[8] = {
0b00110,
0b01001,
0b01001,
0b00110,
0b00000,
0b00000,
0b00000,
0b00000
};
// Initialize DHT sensor for normal 16mhz Arduino.
AM2302 dht(AM2302_PIN);
void setup() {
// set relay pins as output and turn them off
pinMode(LATHE_R_PIN, OUTPUT);
pinMode(MILL_R_PIN, OUTPUT);
digitalWrite(LATHE_R_PIN, LOW);
digitalWrite(MILL_R_PIN, LOW);
Serial.begin(9600);
Serial.println("Dewbane by Highland Dragon Forge");
Serial.println("Initializing AM2302...");
dht.begin();
Serial.println("Initializing DS18B20 lathe sensor...");
LATHE.readSensor();
Serial.println("Initializing DS18B20 mill sensor...");
MILL.readSensor();
Serial.println("Initializing display...");
// initialize the lcd
lcd.init();
// turn on the backlight
lcd.backlight();
// create degree custom character
lcd.createChar(0, degreeSymbol);
}
void loop() {
// read AM2302 to get temperature and humidity
//
// Reading temperature or humidity takes about 250 milliseconds!
// Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
dht.readHumidity();
dht.readTemperature();
// Check if any reads failed and exit early (to try again).
if (isnan(dht.humidity) || isnan(dht.temperature_C)) {
Serial.println("DHT sensor read failure!");
return;
}
// calculate our dew point
dewPoint = dewPointFast(dht.temperature_C, dht.humidity);
displayTemperature = dht.temperature_F;
displayHumidity = dht.humidity;
// read lathe temperature
LATHE.readSensor();
latheTemperature = LATHE.getTemperature_C();
// read mill temperature
MILL.readSensor();
millTemperature = MILL.getTemperature_C();
// toggle heaters
processHeat();
// update the display at the end of each loop
updateDisplay();
// Wait 60 seconds before looping, to give everything time to settle before
// a new round of decision making
delay(LOOP_DELAY);
}
// reference: http://en.wikipedia.org/wiki/Dew_point
double dewPointFast(double celsius, double humidity) {
double a = 17.271;
double b = 237.7;
double temp = (a * celsius) / (b + celsius) + log(humidity*0.01);
double Td = (b * temp) / (a - temp);
return Td;
}
// quick hack to figure out how many spaces to use for a number
int len(float data) {
if (data > 100.0) return 3;
else if (data > 10.0) return 2;
else if (data > 1.0) return 1;
else if (data < -100.0) return 4;
else if (data < -10.0) return 3;
else if (data < -1.0) return 2;
}
float convF(float data) {
return data * 1.8 + 32;
}
void updateDisplay() {
int row = 0;
lcd.setCursor(0,row);
lcd.print(" [Dewbane] ");
lcd.setCursor(0, row);
int temp = (int) displayTemperature;
int humi = (int) displayHumidity;
lcd.print(temp);
lcd.print((char)0);
lcd.print("F");
lcd.setCursor((20 - len(humi) - 1), row);
lcd.print(humi);
lcd.print("%");
row++;
lcd.setCursor(0,row);
lcd.print("Dew Point:");
float dpF = convF(dewPoint);
// move cursor to right minus string length
lcd.setCursor((20 - len(dpF) - 2), row);
int intF = (int) dpF;
lcd.print(intF);
lcd.print((char)0);
lcd.print("F");
row++;
lcd.setCursor(0, row);
lcd.print("Lathe ");
int l = (int) convF(latheTemperature);
lcd.print(l);
lcd.print((char)0);
lcd.print("F "); // intentional trailing spaces
lcd.setCursor(16, row);
if (latheHeat) lcd.print("HEAT");
else lcd.print(" OK ");
row++;
lcd.setCursor(0, row);
lcd.print("Mill ");
l = (int) convF(millTemperature);
lcd.print(l);
lcd.print((char)0);
lcd.print("F "); // intentional trailing spaces
lcd.setCursor(16, row);
if (millHeat) lcd.print("HEAT");
else lcd.print(" OK ");
}
// keep the equipment at least 1.0 C above the dew point at all times
void processHeat() {
if (latheTemperature <= (dewPoint + 1.0)) {
// turn on heat
digitalWrite(LATHE_R_PIN, HIGH);
latheHeat = true;
} else {
// turn off heat
digitalWrite(LATHE_R_PIN, LOW);
latheHeat = false;
}
if (millTemperature <= (dewPoint + 1.0)) {
// turn on heat
digitalWrite(MILL_R_PIN, HIGH);
millHeat = true;
} else {
// turn off heat
digitalWrite(MILL_R_PIN, LOW);
millHeat = false;
}
}