Home made heat pump controller
I need a heat pump controller for a flat that I have in the nearby city. I have installed under floor heating as it is probably the most comfortable and efficient heating there is (and it can also work for cooling).
There are commercial heat pumps around but I have enough experience with my own home setup to be able to build my own unit. One of the 'problems' with building your own whatever is the case and display, especially if it is visible to everyone. I did think about a touch screen LCD display or an OLED display and some buttons but anything I was going to make would look home-made and I wanted something a bit better. Looking on ebay I found lots of fancy looking thermostats with nice displays and buttons but nothing that could run both heating and cooling, also there was no way o communicate with these units as they were self contained. After some hunting I finally came across a thermostat, a BAC-1000, on AliBaba that has a RS485 interface so I can communicate with it, works for heating and cooling, looks good and is touch control. It also fits into a standard electrical outlet box - perfect! http://becaenergy.en.ecplaza.net/6.jpg One down, several to go. My initial list of hardware requirements for the controller were:
And obviously a processor board to run it all :-) For many years I have been using pic processors to control my projects and have avoided the Arduinos. My reason has mainly been the lack of range of Arduino boards and the poor support for debugging, there's nothing like single stepping through code to see where you messed up. I decided to take the plunge though and bought myself a UNO, loaded up the Arduino IDE and wrote a few lines of code. All worked well however the IDE is very basic so I went looking for alternatives. I use Visual Studio extensively so I was really happy to find someone had developed an plugin for VS for programming Arduinos - Visual Micro Arduino Plugin (new version, simpler & more powerful). The plugin works well and is being actively supported, it also includes a debugger that can be activated and although it's not single step it can help - I've not yet tried out much of it's functionality. The great thing about the Arduino is the number of libraries there are available for almost any peripheral you can think of. Most work first time however some need a bit of coaxing into life. Compared to the pic it's luxury. I started putting together some modules for the controller using the various libraries I found and then ran out of memory space on the UNO, it really is very limited. So I moved up to a Mega 2560. The great thing is all the code works just the same. Anyone who programs these small boards will know the typical program structure is a single loop that contains all the logic and calls to read the various sensors, write to displays, set relays etc. All good if you have a fairly simple program with few peripherals but this project quickly exceeded this, especially with the Ethernet interface which runs a web server for (initially) displaying status information. The solution to this problem is a 'Real time operating system' (RTOS). This allows you to have what seems like separate programs all running at the same time on the same processor, just like your pc or mac. Each 'program' runs in its own thread and has its own memory allocated to it. The individual threads can interact with each other or just run autonomously but they can share the same program variables. The RTOS I found and use is called NilRTOS, it is very small and has been ported to the Arduino. So, for example, a thread running to read temperature sensors can set values for variables 'sensor1, sensor2, sensor3' etc. and a thread that is running completely separately that displays stuff on an lcd can display these values. Using this approach allows the ever more complex single thread program to be split up into individual mini 'programs' or threads, each one independently running a peripheral and with the main program loop now free to handle the control of the heat pump without worrying about what is going on around it with the lcd, the Ethernet, reading sensors etc. Hopefully this is clear! The threads I have in my controller are:
... to be continued Acuario |
Cool to see other guys thinking about tackling this item too. I'm interested to see what you come up with.
I'm working on a similar project to control the GSHP I assembled this summer. I just posted some (admittedly sloppy) Arduino code that I'm using for datalogging the DS18B20's. It's a little bloated for the 328P chip, but runs fine on chips with more memory. The direct control of the GSHP is working now using two OMRON ZEN PLC units with some thermister / voltage divider circuits. I plan to indirectly control everything with a Raspberry Pi / Raspian / Apache web server. Have you considered using a tablet or cheap smart phone with Android or Linux and a custom UI as a thermostat interface? I am considering something in that direction. |
I did consider various options for the thermostat but at 36.90 euros including shipping on AliExpress it would have been difficult to find or build something comparable.
|
2 Attachment(s)
The benefit of having each part of the program running in its own thread is that it becomes somewhat easier to debug the program as you can disable individual threads and concentrate on the bit you are writing.
A couple of the oddities I found that didn't occur with a single threaded program is that many of the Arduino string class functions failed to work. Sorry if this gets technical.. the reason appears to be that calls to malloc do not allocate memory as they should do, the return is a null pointer and so any function that relies on the dynamically allocated memory fails. Sadly this means allocating fixed length strings to hold, for example, http requests. The other oddity was that I started getting odd results when reading analogue peripherals. Initially I was using a 4 button keypad using a single analogue input and a resistor divider to give 4 distinct values (a neat way to use a single pin for a simple keyboard). I was also reading NTC thermistors in a separate thread. Individually the peripherals worked fine but when running in threads they failed. Finally I realised that it was because the threads would clash and both try to read analogue voltages at the same time. It turns out that although the Arduino has multiple analogue pins it only has a single adc (analogue to digital converter) so can only do a single conversion at a time. A flag to indicate an analogue read is in progress that stops any other analogue read taking place sorted that problem out. Initially I was going to use a keyboard to program the various parameters for such things as defrost temperature (start/stop), defrost cycle time, maximum temperature etc. but it started to become very complex and with 4 buttons and a small display it was getting over complex. As the controller has an inbuilt web server for displaying status information it seemed the easiest option to use the same server to set up all the settings. A 'few' lines of code, a bit of html and response processing and now programming the controller is dead easy. http://ecorenovator.org/forum/attach...1&d=1447700433 The status page looks like this: http://ecorenovator.org/forum/attach...1&d=1447700433 The web pages aren't pretty but they are functional. You could 'beautify' them with some css but the Arduino itself isn't really up to serving up complex web pages, css files etc. The status page auto updates every 15 seconds using a technique called ajax whereby it only receives the values and then fills in all the spaces on the page with the current values - this saves sending the whole web page each time a refresh is requested. |
Cheater, cheater, pumpkin eater! You've offloaded the work onto other devices! You're supposed to strain the dev board! Just kidding.
In the general purpose controller discussion, many of the contributors could not grasp the idea that the dev board (either uno or mega) should not need to do everything itself. The wish list of functions became so enormous that both ac_hacker and I could not reel in the diversity of devices that were built by many for their own specific sets of needs. My iteration is very basic: it reads a single onewire thermometer and functions as a thermostat with auto changeover. It would be AWESOME if it could speak WiFi, Ethernet or USB with a more intelligent sand-based lifeform. Having a defrost control built into the uno is a primary goal for a unit I am pondering right now. Ok, so your arduino is acting as an Ethernet to rs485 bridge and a humidity sensor? Are you still using an rtos or did you switch back to wiring/python/c? What else does it do? I'm very interested. |
Acuario, nice work. I don't think you need to add any frills to your web pages. That touch screen thermostat will put a nice face on the finished product.
Jeff, for the reasons you have cited, I'm an advocate of using an inexpensive PLC relay unit to do the dirty work at the heat pump as commanded by the system controller or thermostat (in this case, Arduino). The simple ladder logic makes it possible to control a complex heat pump arrangement (compressor, pumps, fans, etc with a basic thermostat and these little PLCs can be found in the same price range as a basic Arduino. |
Quote:
|
2 Attachment(s)
Quote:
Quote:
Quote:
Using the web setup it allows the system to be remotely controlled, overriding the digital thermostat and setting the thermostats settings. Operating mode (heat/cool), on/off, desired temperature can all be set via the web page. With the correct settings in my router I can access this from anywhere making it an iot (internet of things) device. Here are a couple of (not very pretty!) pictures, one of the whole controller mounted on a board and one of the oled status display. The only bit I needed to actually build was a small board (just visible at the bottom left) that acts as a break-out board for various connections. It has the balance resistors for the NTC thermistors, screw connectors for the ds18b20 temperature sensors, the pins for the dht22 humidity sensor and several rows of common pins for vcc, ground, I2C. The small red board with green terminals is a RS485 to serial converter, the rest of the bits should be easily identified. http://ecorenovator.org/forum/attach...1&d=1447739914 http://ecorenovator.org/forum/attach...1&d=1447739914 Acuario |
1 Attachment(s)
Quote:
You could add WiFi quite easily too. Check out the ESP8266 module which has built in WiFi plus lots more. http://ecorenovator.org/forum/attach...1&d=1447740861 Acuario |
1 Attachment(s)
Quote:
I presume the big 40 Amp SSR is meant for your compressor? RJ-11 (telephone) jacks and plugs seem to be the ticket for the sensors. You can use inexpensive telephone wire for long sensor runs too. Quote:
Omron CPU Unit Zen 10C1DR D V2 | eBay 100v - 240v (line level input and power version) Omron Zen 10C3AR A V2 Smart Relay AC100 240V | eBay I have attached the manual for the ZEN here as well. |
Quote:
I used a "freebie" power board out of a donated portable dehumidifier for a relay shield equivalent. The board also has a regulated 5VDC power supply built into it that feeds the Uno board through the USB plug. http://i1326.photobucket.com/albums/...psrjfw3oeh.jpg The board in its old home http://i1326.photobucket.com/albums/...psugxbkcww.jpg The board connected to the Uno with LCD keypad shield Please take note that this rig functions as a non-standard heat pump thermostat as wired. The yellow and green wires are compressor and reversing valve call, the red is the 24VAC feed to run contactors and controls inside the unit. Defrost control is handled by an ICM 315 board inside the unit. That's what I had in it before the GP controller was made. However, these power boards are pretty much the same in every window AC or portable dehumidifier out there that has a digital readout and pushbutton control. These boards have enough relays on them that are matched to the demands of the patient unit, so all that wiring and such is already tested and proven by the OEM. To morph a unit into a heat pump, one of the fan relays could easily be rewired to control a reversing valve solenoid. For a water-source unit, a HX pump or two could run straight off a relay or two. |
Quote:
I'm mentally breaking down that board: Transformer to bring down the mains voltage... four discrete diodes are the rectifier... Perhaps the TO-220 package to the upper left is the LM7805 with a few filter caps surrounding it for 5v power. I imagine that little DIP 16 sized IC in the upper right quadrant of the first picture is some kind of gate array or buffer to interface the relays with TTL signals... or perhaps some other low complexity IC? The white header at the top is the control interface. ...and some kind of piezoelectric alarm/horn in there too? I would presume the large black square relay is the compressor. The 3 blue relays are fan motors, etc. Good stuff. |
Quote:
Another power board mentioned is the Amana p/n 30132033. This one has no mains transformer, but has extra high-current relays (original purpose:strip heaters) and thermistors on board that connect to the same type of pin headers. This board originally went in hotel PTAC units and still sells for around $20. https://www.ptacunits.com/wp-content...5252268816.jpg |
Code for heatpump controller
Ok, here we go.. This is my code for my heat pump controller. First an explanation of the structure.
There are 9 individual modules (.ino files) that make up the controller. Each one attempts to encapsulate functionality of a particular part of the controller. As the complexity of the controller and the necessity to run various peripheral parts increased it became evident that a real time operating system would be needed, many of the modules run functions specifically related to the peripherals leaving the 'main' program (thread) to control the state of the machine. Enough waffle.. The code for the RTOS was found here: https://github.com/greiman/NilRTOS-Arduino Other code libraries are either part of the Arduino code or are easily found searching on Google. Some code is modified from samples and extended to meet my needs. The code is commented so hopefully makes sense. I'll split it into individual posts as it's a bit much all in one... Note: Some defines/code may not be used as it was there in development and I haven't removed it yet.. 1. Main header file HeatpumpController.h #pragma once #ifndef HPCONTROLLER_H #define HPCONTROLLER_H int defTemp; //Temperature to run a defrost cycle int defTime; //Time to run defrost cycle int defUpperTemp; //If evap reaches this temperature abandon the defrost cycle int maxWaterTemp; //Maximum circuit water temperature int desiredRoomTemp; //Desired room temperature int hysterisis; //Hysterisis setting int defStep; //Defrost step int deltaOn; //Temp to turn on pump int deltaOff; //Temp to turn off pump bool operatingState; //System operating state - true=on, false=off bool machineRunningState = FALSE; //State of machine - true=on, false=off int operatingMode; //System operating mode char systemState[20]; //System state string bool readAnalog = FALSE; //Flag to indicate an analogue conversion is taking place. Needed for rtos so 2 analogue reads don't clash bool rtosStarted = FALSE; //Flag the RTOS has started (needed for change from delay to nilDelay...) unsigned long lastDefrost = 0; unsigned long timeBetweenDefrost; //Time between defrost cycles unsigned long defrostRunTime; unsigned long defrostStartDelay; unsigned long defrostStartTime; bool defrostFlag = FALSE; //Flag to say if we are in a defrost cycle (and to start a defrost cycle) float lastTempEvaporator; float lastTempCompressor; float tempCompressor; float tempEvaporator; float humidity; float dht11Temperature; float dewPointTemp; #define ON 0 #define OFF 1 #define COOL 0 #define HEAT 1 //Analogue sensors int ntcSensor[4]; #define flowTemp ntcSensor[0] #define returnTemp ntcSensor[1] #define ambientTemp ntcSensor[2] #define s4 ntcSensor[3] //Heat pump control pins (Digital outputs) #define FANLOW 2 #define FANHIGH 3 #define COMPRESSOR 4 #define DHT11PIN 5 #define DS18B20 7 #define PUMP 8 #define VALVE 9 //Status of individual relays. Set all on so startup sets the pin outputs correctly bool compStat = ON; bool fanLow = ON; bool fanHigh = ON; bool valve = ON; bool pump = ON; //Non volatile memory storage //#define STARTDELAY 0 //Startup delay time //#define MAX_TEMP 1 // //#define MIN_TEMP 2 //#define HEATCOOL 3 #define DEFROSTTIME 4 //Defrost time #define DEFROSTTEMP 5 //Defrost start temperature #define DEFROSTMAXTEMP 6 //Temperature to stop defrost (overrides time for defrost cycle) #define MAXCOMPTEMP 7 //Maximum compressor temperature #define LASTSYSTEMSTATE 8 //Last state - on or off #define LASTSYSTEMMODE 9 //Last operating mode (heat/cool) #define DESIREDROOMTEMP 10 //Temperature for room #define MAXWATERTEMP 11 //Maximum water temperature #define HYSTERISIS 12 //Hysterisis setting #define TIMEBETWEENDEFROST 13 //Time between defrost cycles #define LASTCOMPRUN 14 //Time last compressor run - note 4 bytes! #define NEXTFREE 18 /* System constants */ #define DEFROSTTEMP 2 #define DEFROST_OFFSET 10 #define COMPSTARTDELAY 180000 //Minimum delay time between compressor starts (for power fail/reset etc) /* LCD Pages */ #define LCD_PAGE0 0 //General status #define LCD_PAGE1 1 //Defrost settings #define LCD_PAGE2 2 #define LCD_PAGE3 3 #define LCD_PAGE4 4 #define LCD_PAGES 4 int lcd_page = LCD_PAGE0; //Starting LCD page #include <Time.h> tmElements_t tm; void writeTime(tmElements_t tmw); void readTime(); void saveSystemState(char *state); //Stuff for debug output via serial port void SerialPrint2(char *format, ...); #ifdef DEBUG #define DEBUG_PRINT(x) Serial.print (x) #define DEBUG_PRINTST(x,...) SerialPrint2 (x,__VA_ARGS__) #define DEBUG_PRINTLN(x) Serial.println (x) #else #define DEBUG_PRINT(x) #define DEBUG_PRINTST(x,...) #define DEBUG_PRINTLN(x) #endif //Thread definitions for NilRTOS NIL_THREAD(threadEthernet, arg); NIL_THREAD(threadReadNTC, arg); NIL_THREAD(threadReadDHT11, arg); NIL_THREAD(threadReadSensors, arg); NIL_THREAD(threadReadBAC1000, arg); NIL_THREAD(threadDisplayOLED, arg); NIL_THREAD(threadSetBACTime, arg); //System strings const char sOn[] = "On"; const char sOff[] = "Off"; const char sTrue[] = "True"; const char sFalse[] = "False"; #endif |
2. Main code file HeatpumpController.ino
/* ROCA heat pump pins: 1 - Live 2 - Neutral (Common) 3 - Earth 4 - Valve 5 - 6 - Fan HIGH 7 - Fan LOW 8 - Sensors: #1 - pipe sensor (bottom of evaporator) #5 - Environment */ #define DEBUG #include <DallasTemperature.h> #include <NilRTOS.h> #include <Arduino.h> #include <avr/wdt.h> #include <NilSerial.h> #include <NilAnalog.h> #include <Wire.h> #include "HeatpumpController.h" #include "BAC1000.h" // Macro to redefine Serial as NilSerial to save RAM. // Remove definition to use standard Arduino Serial. #define Serial NilSerial #define analogRead(pin) nilAnalogRead(pin) //#define delay(ms) nilThdSleep(ms) void setup(void) { byte data[1]; //Disable watchdog while setting up everything wdt_disable(); // Open serial communications Serial.begin(115200); setupEthernet(); // start the Ethernet connection and the server: readTime(); //Get the time from the DS1307 setupRS485(); //Setup RS485 (BAC1000) EEPROM_read_byte(DEFROSTTEMP, data); //Temperature to run defrost cycle defTemp = data[0] - DEFROST_OFFSET; if (defTemp > 10) { defTemp = 2; EEPROM_write_byte(DEFROSTTEMP, defTemp + DEFROST_OFFSET); } EEPROM_read_byte(DEFROSTTIME, data); //Defrost cycle time (minutes) defTime = data[0]; if (defTime > 10) { defTime = 2; EEPROM_write_byte(DEFROSTTIME, defTime); } EEPROM_read_byte(DEFROSTMAXTEMP, data); //Defrost Maximum temperature (temperature to stop defrost, overrides timer) defUpperTemp = data[0]; if (defUpperTemp > 20) { defUpperTemp = 10; EEPROM_write_byte(DEFROSTMAXTEMP, defUpperTemp); } EEPROM_read_byte(TIMEBETWEENDEFROST, data); //Minimum time between defrost cycles timeBetweenDefrost = data[0]; if ((timeBetweenDefrost > 60) || (timeBetweenDefrost < 15)) { timeBetweenDefrost = 30; EEPROM_write_byte(TIMEBETWEENDEFROST, timeBetweenDefrost); } EEPROM_read_byte(DESIREDROOMTEMP, data); //Desired room temperature desiredRoomTemp = data[0]; if ((desiredRoomTemp > 35) || (desiredRoomTemp < 10)) { desiredRoomTemp = 21; EEPROM_write_byte(DESIREDROOMTEMP, desiredRoomTemp); } EEPROM_read_byte(MAXWATERTEMP, data); //Max water temperature maxWaterTemp = data[0]; if ((maxWaterTemp > 35) || (maxWaterTemp < 10)) { maxWaterTemp = 21; EEPROM_write_byte(MAXWATERTEMP, maxWaterTemp); } EEPROM_read_byte(HYSTERISIS, data); //Hysterisis hysterisis = data[0]; if ((hysterisis > 15) || (hysterisis < 2)) { hysterisis = 5; EEPROM_write_byte(HYSTERISIS, hysterisis); } EEPROM_read_byte(LASTSYSTEMSTATE, data); //Last system state (on/off) operatingState = data[0]; //If the system was on then start the circulating pump before waiting for everything else if (operatingState == TRUE) { setRelay(PUMP, ON); } sendBAC1000Command(POWERONOFF, 0, 1, (operatingState << 4), 0, 0, 0); EEPROM_read_byte(LASTSYSTEMMODE, data); //Last system operating mode (heat/cool) operatingMode = data[0]; if (operatingMode > 1) { operatingMode = 1; EEPROM_write_byte(LASTSYSTEMMODE, operatingMode); } DEBUG_PRINTLN(F("STARTUP - STARTUP - STARTUP")); debugStateOutput(); DEBUG_PRINTLN(F("END STARTUP - END STARTUP")); //For testing defrost //defrostUpperTemperature = 40; //defrostFlag = TRUE; //Set BAC1000 time and then read its settings so we can synchronise operations sendBAC1000Command(SETTIME, 0, 1, tm.Second, tm.Minute, tm.Hour, 1); do { delay(2000); sendBAC1000Command(READALL, 0, 1, 0, 0, 0, 0); } while (BACRead == FALSE); //Set outputs to off before enabling pinMode so relays don't turn on at startup setRelay(PUMP, OFF); setRelay(VALVE, OFF); setRelay(FANLOW, OFF); setRelay(FANHIGH, OFF); setRelay(COMPRESSOR, OFF); pinMode(PUMP, OUTPUT); // sets the digital pin as output pinMode(VALVE, OUTPUT); // sets the digital pin as output pinMode(FANLOW, OUTPUT); // sets the digital pin as output pinMode(FANHIGH, OUTPUT); // sets the digital pin as output pinMode(COMPRESSOR, OUTPUT); // sets the digital pin as output wdt_enable(WDTO_8S); //Start watchdog timer - watchdog reset is in oled code as no delays nilSysBegin(); // Start Nil RTOS. rtosStarted = TRUE; //Flag the RTOS has started (needed for change from delay to nilDelay...) } void loop(void) { //int keyValue = 0; //keyValue = readKeys(); //if (keyValue == 1) { // lcd_page = lcd_page++ >= LCD_PAGES ? 0 : lcd_page; //Cycle the lcd page count //} } //Main running loop NIL_THREAD(threadMain, arg) { unsigned long lastCompressorRun; char buf[10]; while (TRUE) { debugStateOutput(); //See if the machine is on or off if ((operatingMode == HEAT)&&(operatingState == TRUE)) { //Operational modes for heating DEBUG_PRINTLN(F("Mode:HEAT, operatingState:ON")); if ((defrostFlag == FALSE)&&((flowTemp >= maxWaterTemp)||(bacRoomTemp >= desiredRoomTemp))) { //If we have achieved the target temperature then turn everything but the circulating pump off saveSystemState("Circulate"); setRelay(PUMP, ON); setRelay(VALVE, OFF); setRelay(FANLOW, OFF); setRelay(FANHIGH, OFF); setRelay(COMPRESSOR, OFF); machineRunningState = FALSE; } //Machine startup else if ((flowTemp <= maxWaterTemp - hysterisis)&&(machineRunningState == FALSE)){ //See if the compressor ran recently if so delay startup lastCompressorRun = EEPROM_readlong(LASTCOMPRUN); //DEBUG_PRINTST("Last run=%lu\n", lastCompressorRun); unsigned long currentTimeMs = makeTimeMilli(tm); //DEBUG_PRINTST("Current=%lu\n", currentTimeMs); setRelay(PUMP, ON); if ((lastCompressorRun + COMPSTARTDELAY) < currentTimeMs) { saveSystemState("Startup"); setRelay(FANLOW, ON); setRelay(FANHIGH, ON); setRelay(VALVE, ON); nilThdSleepSeconds(5); setRelay(COMPRESSOR, ON); machineRunningState = TRUE; } else { saveSystemState("Wait for startup"); } } //Check to see if we need to do a defrost. else if (tempEvaporator == defTemp) { if(millis() - lastDefrost >= timeBetweenDefrost) defrostFlag = TRUE; } else if (machineRunningState == TRUE) { saveSystemState("Running"); } } //Else if it is running turn everything off else if(operatingState == FALSE) { saveSystemState("Off"); setRelay(FANLOW, OFF); setRelay(FANHIGH, OFF); setRelay(COMPRESSOR, OFF); setRelay(VALVE, OFF); setRelay(PUMP, OFF); defrostFlag = FALSE; machineRunningState = FALSE; } //If the compressor is running save the current time for startup delay if (compStat == ON) { EEPROM_writelong(LASTCOMPRUN, makeTimeMilli(tm)); } // Sleep for 5S. nilThdSleepSeconds(5); } } //Debug out system status void debugStateOutput(void) { DEBUG_PRINTST("Operating mode=%s\n", mode[operatingMode]); DEBUG_PRINTST("operatingState=%s\n", operatingState == TRUE ? sTrue : sFalse); DEBUG_PRINTST("machineRunningState=%s\n", machineRunningState == TRUE ? sTrue : sFalse); DEBUG_PRINTST("flowTemp=%d\n", flowTemp); DEBUG_PRINTST("maxWaterTemp=%d\n", maxWaterTemp); DEBUG_PRINTST("bacRoomTemp=%d\n", bacRoomTemp); DEBUG_PRINTST("desiredRoomTemp=%d\n", desiredRoomTemp); } //Save string for system state and output the state on debugger void saveSystemState(char *state) { sprintf(systemState, state); DEBUG_PRINTLN(systemState); } // Turn on/off relays and preserve state flag void setRelay(int relay, bool state) { switch (relay) { case COMPRESSOR: if (compStat != state) { digitalWrite(COMPRESSOR, state); compStat = state; } break; case FANLOW: if (fanLow != state) { digitalWrite(FANLOW, state); fanLow = state; } break; case FANHIGH: if (fanHigh != state) { digitalWrite(FANHIGH, state); fanHigh = state; } break; case VALVE: if (valve != state) { digitalWrite(VALVE, state); valve = state; } break; case PUMP: //DEBUG_PRINTST("Pump = %d, set = %d\n", pump, state); if (pump != state) { digitalWrite(PUMP, state); pump = state; } break; } } //Thread to control derfost cycle NIL_THREAD(threadDefrost, arg) { while (TRUE) { const char defStepS[] = "Defrost step"; if (defrostFlag == TRUE) { sprintf(systemState, "%s 1", defStepS); DEBUG_PRINTLN(systemState); defStep = 1; defrostStartTime = millis(); setRelay(FANLOW, OFF); setRelay(FANHIGH, OFF); setRelay(COMPRESSOR, OFF); nilThdSleepSeconds(10); defStep = 2; sprintf(systemState, "%s 2", defStepS); setRelay(VALVE, OFF); nilThdSleepSeconds(10); defStep = 3; sprintf(systemState, "%s 3", defStepS); setRelay(COMPRESSOR, ON); while (defrostFlag == TRUE) { if (tempEvaporator >= defUpperTemp) defrostFlag = FALSE; //Cancel defrost if we have reached the programmed upper temperature defrostRunTime = millis() - defrostStartTime; if(defrostRunTime >= (defTime * 60000)) defrostFlag = FALSE; //Cancel defrost if we have reached the timeout for the defrost cycle nilThdSleepMilliseconds(500); } defStep = 4; sprintf(systemState, "%s 4", defStepS); setRelay(COMPRESSOR, OFF); nilThdSleepSeconds(10); defStep = 5; sprintf(systemState, "%s 5", defStepS); setRelay(FANHIGH, ON); nilThdSleepSeconds(5); defStep = 6; sprintf(systemState, "%s 6", defStepS); setRelay(VALVE, ON); nilThdSleepSeconds(10); setRelay(COMPRESSOR, ON); defStep = 0; defrostFlag = FALSE; lastDefrost = millis(); //Save the last defrost end time } // Sleep for 30S. nilThdSleepSeconds(30); } } |
Last part of HeatpumpController.ino
// Declare stacks for threads
NIL_WORKING_AREA(waThreadMain, 1024); NIL_WORKING_AREA(waThreadEth, 1024); NIL_WORKING_AREA(waThreadDS18B20, 128); NIL_WORKING_AREA(waThreadBAC, 256); NIL_WORKING_AREA(waThreadDHT11, 128); NIL_WORKING_AREA(waThreadOLED, 128); NIL_WORKING_AREA(waThreadDefrost, 32); NIL_WORKING_AREA(waThreadNTC, 64);//128 NIL_WORKING_AREA(waThreadSetBACTime, 256); /* * Threads static table, one entry per thread. A thread's priority is determined by its position in the table with highest priority first. * These threads start with a null argument. A thread's name may also be null to save RAM since the name is currently not used. */ NIL_THREADS_TABLE_BEGIN() NIL_THREADS_TABLE_ENTRY("", threadEthernet, NULL, waThreadEth, sizeof(waThreadEth)) NIL_THREADS_TABLE_ENTRY("", threadMain, NULL, waThreadMain, sizeof(waThreadMain)) NIL_THREADS_TABLE_ENTRY("", threadDisplayOLED, NULL, waThreadOLED, sizeof(waThreadOLED)) NIL_THREADS_TABLE_ENTRY("", threadReadBAC1000, NULL, waThreadBAC, sizeof(waThreadBAC)) NIL_THREADS_TABLE_ENTRY("", threadReadSensors, NULL, waThreadDS18B20, sizeof(waThreadDS18B20)) NIL_THREADS_TABLE_ENTRY("", threadReadDHT11, NULL, waThreadDHT11, sizeof(waThreadDHT11)) NIL_THREADS_TABLE_ENTRY("", threadDefrost, NULL, waThreadDefrost, sizeof(waThreadDefrost)) NIL_THREADS_TABLE_ENTRY("", threadReadNTC, NULL, waThreadNTC, sizeof(waThreadNTC)) NIL_THREADS_TABLE_ENTRY("", threadSetBACTime, NULL, waThreadSetBACTime, sizeof(waThreadSetBACTime)) NIL_THREADS_TABLE_END() //Format and output string to serial debug port void SerialPrint2(char *format, ...) { char buff[128]; va_list args; va_start(args, format); vsnprintf(buff, sizeof(buff), format, args); va_end(args); buff[sizeof(buff) / sizeof(buff[0]) - 1] = '\0'; Serial.print(buff); } |
3. 24C34EEPROM.ino
#define AT24C32_I2C_ADDRESS 0x50 // the I2C address of Tiny RTC AT24C32 EEPROM void EEPROM_write_byte(unsigned int uiAddress, byte bData) { int iCount = 0; do { Wire.beginTransmission(AT24C32_I2C_ADDRESS); Wire.write((byte)(uiAddress >> 8)); // MSB Wire.write((byte)uiAddress); // LSB Wire.write(bData); } while (Wire.endTransmission() != 0 && ++iCount<10); } void EEPROM_read_byte(unsigned int uiAddress, byte *pbData) { int iCount = 0; do { Wire.beginTransmission(AT24C32_I2C_ADDRESS); Wire.write((byte)(uiAddress >> 8)); // MSB Wire.write((byte)uiAddress); // LSB } while (Wire.endTransmission() != 0 && ++iCount<10); Wire.requestFrom(AT24C32_I2C_ADDRESS, 1); *pbData = Wire.read(); } ////////////////////////////////////////////////////////////////////////////////////// // read long from EEPROM, give starting address unsigned long EEPROM_readlong(int address) { byte data[1]; unsigned long dword = 0; EEPROM_read_byte(address, data); dword = data[0]; dword = dword << 8; EEPROM_read_byte(address+1, data); dword = dword + data[0]; dword = dword << 8; EEPROM_read_byte(address+2, data); dword = dword + data[0]; dword = dword << 8; EEPROM_read_byte(address+3, data); dword = dword + data[0]; return dword; } void EEPROM_writelong(int address, unsigned long dword) { byte data; data = dword & 0xff; EEPROM_write_byte(address + 3, data); dword = dword >> 8; data = dword & 0xff; delay(100); EEPROM_write_byte(address + 2, data); dword = dword >> 8; data = dword & 0xff; delay(100); EEPROM_write_byte(address + 1, data); dword = dword >> 8; data = dword & 0xff; delay(100); EEPROM_write_byte(address, data); } |
4. BAC1000.h - header file for BAC1000 thermometer
#pragma once #ifndef _BAC1000_h #define _BAC1000_h //RS485 settings #define RS485TxControl 10 //RS485 Direction control #define RS485Transmit HIGH #define RS485Receive LOW #define RS485Serial Serial1 //Commands #define READALL 0xa0 //Read all data from thermostat #define TRANSMITALL 0xa1 //Send all data to thermostat #define POWERONOFF 0xa2 //Power on/off #define FANSPEED 0x03 //Control fan speed relays #define MODESETTING 0xa4 //Mode setting #define KEYLOCK 0xa5 //Lock/unlock keypad #define TEMPSETTING 0xa6 //Set desired temperature #define TEMPCALIB 0xa7 //Temperature calibration #define SETTIME 0xa8 //Set thermostat clock #define AUTOMANUAL 0xa9 //Manual/auto programmable #define DATAACK 0x50 //Command accepted ok //Masks for status #define UNITONOFF 0x10 //Unit on - 1, off - 0 #define HEATCOOL 0x60 //Operation mode - 00 - Cooling, 01 - Heating, 10 - Ventilation #define FANS 0x03 //Fan speed - 00 - Auto, 01 - High, 10 - Medium, 11 - Low #define POWERON 0x10 //Power unit on, power off with 00 int bacRoomTemp; int bacSetTemp; bool powerStateBAC; int operatingModeBAC; const char* mode[] = { "Cool", "Heat", "Ventilate" }; bool BACRead = FALSE; bool waitRead = FALSE; bool sendBAC1000Command(int bacCmd, int idl, int idh, int data1, int data2, int data3, int data4); #endif |
5. BAC1000.ino
#include <Wire.h> //For BAC1000 thermometer #include "BAC1000.h" NIL_THREAD(threadReadBAC1000, arg) { while (TRUE) { { sendBAC1000Command(READALL, 0, 1, 0, 0, 0, 0); // Sleep for 30 seconds. nilThdSleepSeconds(30); } } } //Reset BAC time every hour NIL_THREAD(threadSetBACTime, arg) { while (TRUE) { { DEBUG_PRINTLN("Set BAC Time"); sendBAC1000Command(SETTIME, 0, 1, tm.Second, tm.Minute, tm.Hour, 1); // Sleep for 1 hour nilThdSleepSeconds(3600); } } } void setupRS485(void) { //Setup for RS485 pinMode(RS485TxControl, OUTPUT); digitalWrite(RS485TxControl, RS485Receive); // Init Transceiver RS485Serial.begin(2540); // set the data rate } //Send command to BAC1000. Retry 5 times // Command, Device ID low, ID high, Data(4) bool sendBAC1000Command(int bacCmd, int idl, int idh, int data1, int data2, int data3, int data4) { int retryCount = 0; bool result; //Wait for current read to end before trying to read again do{ if (rtosStarted == TRUE) nilThdSleepSeconds(2); else delay(2000); }while (waitRead == TRUE); waitRead = TRUE; for (retryCount = 0; retryCount < 2; retryCount++) { result = sendBAC1000Cmd(bacCmd, idl, idh, data1, data2, data3, data4); if (result == FALSE) { DEBUG_PRINTLN("BAC command fail"); if (rtosStarted == TRUE) nilThdSleepSeconds(5); else delay(5000); } else { DEBUG_PRINTLN("BAC read ok"); break; } } waitRead = FALSE; return result; } bool sendBAC1000Cmd(int bacCmd, int idl, int idh, int data1, int data2, int data3, int data4) { int byteReceived; int byteSend; int i = 0; char checksum; char frameRX[8]; char frameTX[7] = { bacCmd, idl, idh, data1, data2, data3, data4 }; //char frame[7] = { 0xA8, 0, 1, 0, 0x0, 9, 1 }; DEBUG_PRINTLN("Send BAC command frame"); digitalWrite(RS485TxControl, RS485Transmit); // Enable RS485 Transmit for (int i = 0; i < 7; i++) { RS485Serial.write(frameTX[i]); // Send bytes to thermostat } RS485Serial.write(bac1000Checksum(frameTX)); // Send byte to Remote RS485Serial.flush(); //Wait for end of send before disabling RS485 digitalWrite(RS485TxControl, RS485Receive); // Disable RS485 Transmit while (RS485Serial.available()) //Look for data { byteReceived = RS485Serial.read(); // Read received byte frameRX[i] = byteReceived; i++; //Serial.print(byteReceived); // Show on Serial Monitor //Serial.print(" "); } if (frameRX[0] == DATAACK) { //Data received ok switch (bacCmd) { case READALL: char buf[20]; char checksum1; checksum = frameRX[7]; frameRX[7] = '\0'; checksum1 = bac1000Checksum(frameRX); if (checksum1 != checksum) { //sprintf(buf, " Frame=%x %x %x %x %x %x %x %x", frameRX[0], frameRX[1], frameRX[2], frameRX[3], frameRX[4], frameRX[5], frameRX[6], frameRX[7]); //Serial.println(buf); //sprintf(buf, " o=%x n=%x", checksum, checksum1); DEBUG_PRINTLN("BAC CS Failure"); //Serial.println(buf); return FALSE; } bacRoomTemp = frameRX[6]; bacSetTemp = frameRX[5] / 2; desiredRoomTemp = bacSetTemp; //Read and update BAC power state if necessary powerStateBAC = frameRX[3] & UNITONOFF; if(operatingState != powerStateBAC){ operatingState = powerStateBAC; EEPROM_write_byte(LASTSYSTEMSTATE, operatingState); } //Read and update operating mode if necessary operatingModeBAC = (frameRX[3] & HEATCOOL) >> 5; if (operatingMode != operatingModeBAC) { operatingMode = operatingModeBAC; EEPROM_write_byte(LASTSYSTEMMODE, operatingMode); } BACRead = TRUE; //Flag the BAC has been successfully read //char buf[20]; //sprintf(buf, "Room temp: %d", frameRX[6]); //Serial.println(buf); //sprintf(buf, "Set temp: %d", frameRX[5] / 2); //Serial.println(buf); //sprintf(buf, "Thermometer power: %s", powerStateBAC == 0 ? "Off" : "On"); //Serial.println(buf); //sprintf(buf, "Mode: %s", mode[operatingModeBAC]); //Serial.println(buf); break; } return TRUE; } else { return FALSE; } } //CheckSum = (COMMAND + ID0 + ID1 + Data0 + Data1 + Data2 + Data3) & 0xFF ^ 0xA5; unsigned char bac1000Checksum(char *ptr) { unsigned char chk = 0; for (int i = 0; i < 7; i++) { chk += ptr[i]; } chk = chk & 0xff; chk = chk ^ 0xa5; return chk; } |
6. DHT11.ino
#include <dht.h> #include <math.h> dht DHT11; NIL_THREAD(threadReadDHT11, arg) { while (TRUE) { int chk = DHT11.read11(DHT11PIN); //Serial.print("Read sensor: "); switch (chk) { case DHTLIB_OK: humidity = (float)DHT11.humidity; dht11Temperature = (float)DHT11.temperature; dewPointTemp = dewPoint(DHT11.temperature, DHT11.humidity); break; case DHTLIB_ERROR_CHECKSUM: Serial.println("DHT11 Checksum error"); break; case DHTLIB_ERROR_TIMEOUT: Serial.println("DHT11 Time out error"); break; default: Serial.println("DHT11 Unknown error"); break; } //Serial.print("Humidity (%): "); //Serial.println(humidity, 2); //Serial.print("Temperature (°C): "); //Serial.println(dht11Temperature, 2); //Serial.print("Dew Point (°C): "); //Serial.println(dewPointTemp); // Sleep for 10 seconds. nilThdSleepSeconds(10); } } // dewPoint function NOAA // reference (1) : Algorithms - Schlatter and Baker // reference (2) : About the Weather Station // double dewPoint(double celsius, double humidity) { // (1) Saturation Vapor Pressure = ESGG(T) double RATIO = 373.15 / (273.15 + celsius); double RHS = -7.90298 * (RATIO - 1); RHS += 5.02808 * log10(RATIO); RHS += -1.3816e-7 * (pow(10, (11.344 * (1 - 1 / RATIO))) - 1); RHS += 8.1328e-3 * (pow(10, (-3.49149 * (RATIO - 1))) - 1); RHS += log10(1013.246); // factor -3 is to adjust units - Vapor Pressure SVP * humidity double VP = pow(10, RHS - 3) * humidity; // (2) DEWPOINT = F(Vapor Pressure) double T = log(VP / 0.61078); // temp var return (241.88 * T) / (17.558 - T); } |
7. DS18B20.ino - you will need to put in your own addresses for your sensors
#include <OneWire.h> #include <DallasTemperature.h> // Initialize OneWire OneWire one_wire(DS18B20); DallasTemperature sensors(&one_wire); // OneWire Sensor addresses //28 71 94 2B 06 00 00 2C //28 81 15 31 04 00 00 34 DeviceAddress compressor = { 0x28, 0x71, 0x94, 0x2B, 0x6, 0x0, 0x0, 0x2C }; DeviceAddress evaporator = { 0x28, 0x81, 0x15, 0x31, 0x4, 0x0, 0x0, 0x34 }; NIL_THREAD(threadReadSensors, arg) { while (TRUE) { //Serial.println("Read DS18B20 sensors"); sensors.requestTemperatures(); tempCompressor = sensors.getTempC(compressor); tempEvaporator = sensors.getTempC(evaporator); //Serial.println(tempCompressor); //Serial.println(tempEvaporator); if (tempCompressor != lastTempCompressor) { lastTempCompressor = tempCompressor; } if (tempEvaporator != lastTempEvaporator) { lastTempEvaporator = tempEvaporator; } // Sleep for 10 seconds. nilThdSleepSeconds(10); } } |
8. DS1307.ino
#include <Time.h> #include <DS1307RTC.h> const char *monthName[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; uint64_t makeTimeMilli(tmElements_t tm) { return ((uint64_t)makeTime(tm) * 1000); } void writeTime(tmElements_t tmw) { // Configure the RTC RTC.write(tmw); } void readTime() { if (RTC.read(tm)) { //Serial.print("Ok, Time = "); //print2digits(tm.Hour); //Serial.write(':'); //print2digits(tm.Minute); //Serial.write(':'); //print2digits(tm.Second); //Serial.print(", Date (D/M/Y) = "); //Serial.print(tm.Day); //Serial.write('/'); //Serial.print(tm.Month); //Serial.write('/'); //Serial.print(tmYearToCalendar(tm.Year)); //Serial.println(); } else { if (RTC.chipPresent()) { Serial.println("The DS1307 is stopped. Please run the SetTime"); Serial.println("example to initialize the time and begin running."); Serial.println(); // get the date and time the compiler was run if (getDate(__DATE__) && getTime(__TIME__)) { // and configure the RTC with this info RTC.write(tm); } } else { Serial.println("DS1307 read error! Please check the circuitry."); Serial.println(); } } } void print2digits(int number) { if (number >= 0 && number < 10) { Serial.write('0'); } Serial.print(number); } bool getTime(const char *str) { int Hour, Min, Sec; if (sscanf(str, "%d:%d:%d", &Hour, &Min, &Sec) != 3) return false; tm.Hour = Hour; tm.Minute = Min; tm.Second = Sec; return true; } bool getDate(const char *str) { char Month[12]; int Day, Year; uint8_t monthIndex; if (sscanf(str, "%s %d %d", Month, &Day, &Year) != 3) return false; for (monthIndex = 0; monthIndex < 12; monthIndex++) { if (strcmp(Month, monthName[monthIndex]) == 0) break; } if (monthIndex >= 12) return false; tm.Day = Day; tm.Month = monthIndex + 1; tm.Year = CalendarYrToTm(Year); return true; } |
9. Ethernet.ino (part 1)
//For ethernet #include <SPI.h> #include <Ethernet.h> #include "HeatpumpController.h" // Enter a MAC address and IP address for your controller below. // The IP address will be dependent on your local network: // Enter a MAC address and IP address for your controller below. // The IP address will be dependent on your local network: byte mac[] = { 0x00, 0xAD, 0xBE, 0xEF, 0xFD, 0x13 }; byte ip[] = { 192, 168, 0, 190 }; // ip in lan (that's what you need to use in your browser. byte gateway[] = { 192, 168, 0, 1 }; // internet access via router byte subnet[] = { 255, 255, 255, 0 }; //subnet mask #define ETHPORT 8081 EthernetServer server(ETHPORT); // Initialize the Ethernet server library void returnSetupPage(EthernetClient cl, bool save); void saveSettings(char * buf); void returnWebPage(EthernetClient cl); void XML_response(EthernetClient cl); void StrClear(char *str, char length); char StrContains(char *str, char *sfind); void setupEthernet(void) { Ethernet.begin(mac, ip); server.begin(); DEBUG_PRINT("server is at "); DEBUG_PRINTLN(Ethernet.localIP()); } NIL_THREAD(threadEthernet, arg) { // listen for incoming clients #define REQ_BUF_SZ 120 // size of buffer used to capture HTTP requests char HTTP_req[REQ_BUF_SZ] = { 0 }; // buffered HTTP request stored as null terminated string char req_index = 0; // index into HTTP_req buffer while (TRUE) { EthernetClient client = server.available(); // try to get client if (client) { // got client? boolean currentLineIsBlank = true; while (client.connected()) { if (client.available()) { // client data available to read char c = client.read(); // read 1 byte (character) from client buffer first part of HTTP request in HTTP_req array (string) // leave last element in array as 0 to null terminate string (REQ_BUF_SZ - 1) if (req_index < (REQ_BUF_SZ - 1)) { HTTP_req[req_index] = c; // save HTTP request character req_index++; } // last line of client request is blank and ends with \n // respond to client only after last line received if (c == '\n' && currentLineIsBlank) { // display received HTTP request on serial port //DEBUG_PRINTST("Received HTTP:-%s", HTTP_req); // send a standard http response header client.println("HTTP/1.1 200 OK"); // remainder of header follows below, depending on if web page or XML page is requested // Ajax request - send XML file if (StrContains(HTTP_req, "ajax_inputs")) { // send rest of HTTP header client.println("Content-Type: text/xml"); client.println("Connection: keep-alive"); client.println(); // send XML file containing input states XML_response(client); } else if (StrContains(HTTP_req, "setup")) { returnSetupPage(client, FALSE); } else if (StrContains(HTTP_req, "savesettings")) { saveSettings(HTTP_req); returnSetupPage(client, TRUE); } else if (StrContains(HTTP_req, "settime")) { setTime(HTTP_req); returnSetupPage(client, TRUE); } else if (StrContains(HTTP_req, "defrostOn")) { Serial.println("START defrost forced"); defrostFlag = TRUE; returnSetupPage(client, FALSE); } else if (StrContains(HTTP_req, "defrostOff")) { Serial.println("STOP defrost forced"); defrostFlag = FALSE; returnSetupPage(client, FALSE); } else { // web page request send rest of HTTP header returnWebPage(client); } // reset buffer index and all buffer elements to 0 req_index = 0; StrClear(HTTP_req, REQ_BUF_SZ); break; } // every line of text received from the client ends with \r\n if (c == '\n') { // last character on line of received text // starting new line with next character read currentLineIsBlank = true; } else if (c != '\r') { // a text character was received from client currentLineIsBlank = false; } } // end if (client.available()) } // end while (client.connected()) nilThdSleepSeconds(1); // give the web browser time to receive the data client.stop(); // close the connection } // end if (client) // Sleep nilThdSleepSeconds(10); } } |
10. Ethernet.ino (part 2)
//Save settings received from web page //GET /savesettings?defTime=5&defTemp=-2 HTTP/1.1 void saveSettings(char * buf) { char str1[150]; char *s = str1, *t = NULL; char sbuf[50]; char keyname[10]; int val; bool res; str1[0] = '\0'; const char *start = strchr(buf, '?') + 1; strncat(str1, start, strcspn(start, " ")); DEBUG_PRINTST("Extracted %s\n", str1); while ((t = strtok(s, "&")) != NULL) { s = NULL; sprintf(sbuf, "%s", t); sscanf(sbuf, "%32[^=]=%d", keyname, &val); SerialPrint2("Name=%s, value=%d\n", keyname, val); //Running time of defrost cycle if (StrContains(keyname, "defTime")) { if (defTime != val) EEPROM_write_byte(DEFROSTTIME, val); defTime = val; } //Temperature of condensor to start defrost cycle else if (StrContains(keyname, "defStartT")) { if (defTemp != val) EEPROM_write_byte(DEFROSTTEMP, val + DEFROST_OFFSET); defTemp = val; } else if (StrContains(keyname, "hys")) { if (hysterisis != val) EEPROM_write_byte(HYSTERISIS, val); hysterisis = val; } //Temperature to stop defrost. Overrides timer else if (StrContains(keyname, "defStopT")) { if (defUpperTemp != val) EEPROM_write_byte(DEFROSTMAXTEMP, val); defUpperTemp = val; } //Desired room temperature else if (StrContains(keyname, "roomTemp")) { if (desiredRoomTemp != val) { res = sendBAC1000Command(TEMPSETTING, 0, 1, 0, 0, val * 2, 0); bacSetTemp = val; desiredRoomTemp = val; EEPROM_write_byte(DESIREDROOMTEMP, desiredRoomTemp); } } //Maximum water temperature else if (StrContains(keyname, "waterTemp")) { if (maxWaterTemp != val) EEPROM_write_byte(MAXWATERTEMP, val); maxWaterTemp = val; } //Min time between defrost cycles else if (StrContains(keyname, "defCycle")) { if (timeBetweenDefrost != val) EEPROM_write_byte(TIMEBETWEENDEFROST, val); timeBetweenDefrost = val; } //System power state - on or off else if (StrContains(keyname, "sysState")) { if (operatingState != val) { res = sendBAC1000Command(POWERONOFF, 0, 1, (val << 4), 0, 0, 0); operatingState = val; powerStateBAC = val; EEPROM_write_byte(LASTSYSTEMSTATE, operatingState); } } //System operating mode heat/cool else if (StrContains(keyname, "sysMode")) { if (operatingMode != val) { res = sendBAC1000Command(MODESETTING, 0, 1, (val << 5), 0, 0, 0); operatingMode = val; operatingModeBAC = val; EEPROM_write_byte(LASTSYSTEMMODE, operatingState); } } } } //Set RTC time void setTime(char * buf) { tmElements_t tm; char str1[150]; char *s = str1, *t = NULL; char sbuf[50]; char keyname[10]; int val; bool res; str1[0] = '\0'; const char *start = strchr(buf, '?') + 1; strncat(str1, start, strcspn(start, " ")); DEBUG_PRINTST("Extracted %s\n", str1); while ((t = strtok(s, "&")) != NULL) { s = NULL; sprintf(sbuf, "%s", t); sscanf(sbuf, "%32[^=]=%d", keyname, &val); DEBUG_PRINTST("Name=%s, value=%d\n", keyname, val); if (StrContains(keyname, "hour")) { tm.Hour = val; } else if (StrContains(keyname, "minute")) { tm.Minute = val; } else if (StrContains(keyname, "day")) { tm.Day = val; } else if (StrContains(keyname, "month")) { tm.Month = val; } else if (StrContains(keyname, "year")) { tm.Year = val; } RTC.write(tm); } } void returnSetupPage(EthernetClient cl, bool save) { DEBUG_PRINTLN("Setup page"); // send a standard http response header cl.println(F("Content-Type: text/html")); cl.println(F("Connection: close")); // the connection will be closed after completion of the response cl.println(); cl.println(F("<!DOCTYPE HTML>")); cl.println(F("<html>")); cl.println(F("<!DOCTYPE HTML><html><head>")); cl.println(F("<link rel='stylesheet' type='text/css' href='http://www.tortosaforum.com/heatpump/heatpumpcss.css' />")); cl.println(F("<title>Heat pump - SETUP</title></head><body>")); cl.println(F("<H1>Heat Pump Controller - SETUP</H1>")); //cl.println(F("<form action='savesettings' method='post'><table border='1' width='50%'>")); cl.println(F("<form action='savesettings'><table border='1' width='30%'>")); cl.println(F("<tr><th>Setting</th><th>Value</th></tr>")); cl.print(F("<tr><td>Defrost run time</td><td><input type='number' name='defTime' min='1' max='10' value='")); cl.print(defTime); cl.println(F("'></td></tr>")); cl.print(F("<tr><td>Defrost start temperature</td><td><input type='number' name='defStartT' min='-15' max='5' value='")); cl.print(defTemp); cl.println(F("'></td></tr>")); cl.print(F("<tr><td>Defrost stop temperature</td><td><input type='number' name='defStopT' min='0' max='15' value='")); cl.print(defUpperTemp); cl.println(F("'></td></tr>")); cl.print(F("<tr><td>Time between defrost cycles</td><td><input type='number' name='defCycle' min='15' max='60' value='")); cl.print(timeBetweenDefrost); cl.println(F("'></td></tr>")); cl.print(F("<tr><td>Max water temperature</td><td><input type='number' name='waterTemp' min='1' max='40' value='")); cl.print(maxWaterTemp); cl.println(F("'></td></tr>")); cl.print(F("<tr><td>Hysterisis</td><td><input type='number' name='hys' min='1' max='15' value='")); cl.print(hysterisis); cl.println(F("'></td></tr>")); cl.print(F("<tr><td>Desired room temperature</td><td><input type='number' name='roomTemp' min='1' max='40' value='")); cl.print(bacSetTemp); cl.println(F("'></td></tr>")); cl.print(F("<tr><td>System state</td><td><input type='radio' name='sysState' value='1'")); if (operatingState == TRUE) cl.println(F(" checked>ON<input type='radio' name='sysState' value='0'>Off</td></tr>")); else cl.println(F(">On<input type='radio' name='sysState' value='0' checked>Off</td></tr>")); cl.println(F("<tr><td>Mode</td><td><input type='radio' name='sysMode' value='0'")); if (operatingModeBAC == 0) cl.println(F(" checked>Cool<input type='radio' name='sysMode' value='1'>Heat</td></tr>")); else cl.println(F(">Cool<input type='radio' name='sysMode' value='1' checked>Heat</td></tr>")); cl.println(F("</table><br><input type='submit' value='Save'></form>")); cl.println(F("<BR><BR>")); //Table to set time/date cl.println(F("<form action='settime'><table border='1' width='30%'>")); cl.println(F("<tr><th>Setting</th><th>Value</th></tr>")); cl.print(F("<tr><td>Hour</td><td><input type='number' name='hour' id='hour' min='1' max='23'></td></tr>")); cl.print(F("<tr><td>Minute</td><td><input type='number' name='minute' id='minute' min='1' max='59'></td></tr>")); cl.print(F("<tr><td>Day</td><td><input type='number' name='day' id='day' min='1' max='31'></td></tr>")); cl.print(F("<tr><td>Month</td><td><input type='number' name='month' id='month' min='1' max='12'></td></tr>")); cl.print(F("<tr><td>Year</td><td><input type='number' name='year' id='year' min='2015' max='2030'></td></tr>")); cl.println(F("</table><br><input type='submit' value='Set'></form>")); cl.println(F("<BR><BR>")); cl.println(F("<script>")); cl.println(F("var d = new Date();")); cl.println(F("document.getElementById('hour').valu e = d.getHours();")); cl.println(F("document.getElementById('minute').va lue = d.getMinutes();")); cl.println(F("document.getElementById('day').value = d.getDate();")); cl.println(F("var month = d.getMonth() + 1;")); cl.println(F("document.getElementById('month').val ue = month;")); cl.println(F("document.getElementById('year').valu e = d.getFullYear();")); cl.println(F("</script>")); cl.println(F("<a href=\"/?defrostOn\"\">Start Defrost</a>")); cl.println(F("<a href=\"/?defrostOff\"\">Stop Defrost</a>")); if (save == TRUE) cl.println(F("<script>alert('Settings saved');</script>")); if(defrostFlag == TRUE) cl.println(F("<p>Defrost running..</p>")); else cl.println(F("<p>Defrost not running..</p>")); cl.println(F("</body></html>")); } |
11. Ethernet.ino (part 3)
//Return initial web page - then updated by AJAX XML void returnWebPage(EthernetClient cl) { cl.println(F("Content-Type: text/html")); cl.println(F("Connection: keep-alive")); cl.println(); cl.println(F("<!DOCTYPE html>")); cl.println(F("<html><head>")); cl.println(F("<link rel='stylesheet' type='text/css' href='http://www.tortosaforum.com/heatpump/heatpumpcss.css' />")); cl.println(F("<title>Heat Pump Controller status</title>")); cl.println(F("<script>")); cl.println(F("function GetArduinoInputs(){")); cl.println(F("nocache = '&nocache=' + Math.random() * 1000000;")); cl.println(F("var request = new XMLHttpRequest();")); cl.println(F("request.onreadystatechange = function(){")); cl.println(F("if (this.readyState == 4) {")); cl.println(F("if (this.status == 200) {")); cl.println(F("if (this.responseXML != null) {")); cl.println(F("document.getElementById('roomTemp'). innerHTML=this.responseXML.getElementsByTagName('r oomTemp')[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('bacSetTemp' ).innerHTML=this.responseXML.getElementsByTagName( 'bacSetTemp')[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('compressor' ).innerHTML=this.responseXML.getElementsByTagName( 'compressor')[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('evaporator' ).innerHTML=this.responseXML.getElementsByTagName( 'evaporator')[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('flow').inne rHTML=this.responseXML.getElementsByTagName('flow' )[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('return').in nerHTML=this.responseXML.getElementsByTagName('ret urn')[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('humidity'). innerHTML=this.responseXML.getElementsByTagName('h umidity')[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('dewpoint'). innerHTML=this.responseXML.getElementsByTagName('d ewpoint')[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('bacMode').i nnerHTML=this.responseXML.getElementsByTagName('ba cMode')[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('bacPowerSta te').innerHTML=this.responseXML.getElementsByTagNa me('bacPowerState')[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('defrost').i nnerHTML=this.responseXML.getElementsByTagName('de frost')[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('systemTime' ).innerHTML=this.responseXML.getElementsByTagName( 'systemTime')[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('defrostStep ').innerHTML=this.responseXML.getElementsByTagName ('defrostStep')[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('hysterisis' ).innerHTML=this.responseXML.getElementsByTagName( 'hysterisis')[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('compStat'). innerHTML=this.responseXML.getElementsByTagName('c ompStat')[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('fanLow').in nerHTML=this.responseXML.getElementsByTagName('fan Low')[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('fanHigh').i nnerHTML=this.responseXML.getElementsByTagName('fa nHigh')[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('valve').inn erHTML=this.responseXML.getElementsByTagName('valv e')[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('pump').inne rHTML=this.responseXML.getElementsByTagName('pump' )[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('state').inn erHTML=this.responseXML.getElementsByTagName('stat e')[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('maxWaterTem p').innerHTML=this.responseXML.getElementsByTagNam e('maxWaterTemp')[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('ambient').i nnerHTML=this.responseXML.getElementsByTagName('am bient')[0].childNodes[0].nodeValue;")); cl.println(F("document.getElementById('s4').innerH TML=this.responseXML.getElementsByTagName('s4')[0].childNodes[0].nodeValue;")); cl.println(F("}}}}")); cl.println(F("request.open('GET', 'ajax_inputs' + nocache, true);")); cl.println(F("request.send(null);")); cl.println(F("setTimeout('GetArduinoInputs()', 5000);}")); cl.println(F("</script></head>")); cl.println(F("<body onload='GetArduinoInputs()'>")); cl.println(F("<h1>Heat Pump Controller status</h1>")); ////////////////////////////////////////////////////////////////////////////////////////////// cl.println(F("<table border = '1' width = '30%'>")); cl.println(F("<tr><th>Parameter</th><th>Reading</th><th>Setting</th></tr>")); cl.print(F("<tr><td>Room temperature</td><td><span id='roomTemp'>...</span></td><td><span id='bacSetTemp'>...</span></td></tr>")); cl.print(F("<tr><td>Flow temperature</td><td><span id='flow'>...</span></td><td><span id='maxWaterTemp'>...</span></td></tr>")); cl.print(F("<tr><td>Return temperature</td><td><span id='return'>...</span></td><td> </td></tr>")); cl.print(F("<tr><td>Evaporator</td><td><span id='evaporator'>...</span></td><td>")); cl.print(defTemp); cl.print(F("</td></tr>")); cl.print(F("<tr><td>Compressor</td><td><span id='compressor'>...</span></td><td> </td></tr>")); cl.println(F("</table>")); ////////////////////////////////////////////////////////////////////////////////////////////// cl.println(F("<table border = '1' width = '30%'>")); cl.println(F("<tr><th>Relay</th><th>Status</th></tr>")); cl.print(F("<tr><td>Compressor</td><td><span id='compStat'>...</span></td></tr>")); cl.print(F("<tr><td>Fan low</td><td><span id='fanLow'>...</span></td></tr>")); cl.print(F("<tr><td>Fan high</td><td><span id='fanHigh'>...</span></td></tr>")); cl.print(F("<tr><td>3W valve</td><td><span id='valve'>...</span></td></tr>")); cl.print(F("<tr><td>Circulating</td><td><span id='pump'>...</span></td></tr>")); cl.println(F("</table>")); cl.println(F("<p>State: <span id='state'>...</span></p>")); cl.println(F("<p>Ambient: <span id='ambient'>...</span></p>")); cl.println(F("<p>S4: <span id='s4'>...</span></p>")); cl.println(F("<p>Humidity: <span id='humidity'>...</span></p>")); cl.println(F("<p>Dew point: <span id='dewpoint'>...</span></p>")); cl.println(F("<p>Hysterisis: <span id='hysterisis'>...</span></p>")); cl.println(F("<p>BAC Mode: <span id='bacMode'>...</span></p>")); cl.println(F("<p>BAC Power: <span id='bacPowerState'>...</span></p>")); cl.println(F("<p>Defrost running: <span id='defrost'>...</span></p>")); cl.println(F("<p>Defrost step: <span id='defrostStep'>...</span></p>")); cl.println(F("<p></p><p>System time: <span id='systemTime'>...</span></p>")); cl.println(F("</body></html>")); } |
12. Ethernet.ino (part 4)
// send the XML file with status void XML_response(EthernetClient cl) { char buf[20]; cl.print(F("<?xml version = \"1.0\" ?>")); cl.print(F("<inputs>")); cl.print(F("<systemTime>")); sprintf(buf, "%02d:%02d:%02d %02d/%02d", tm.Hour, tm.Minute, tm.Second, tm.Day, tm.Month); cl.print(buf); cl.print(F("</systemTime>")); cl.print(F("<bacSetTemp>")); cl.print(bacSetTemp); cl.print(F("</bacSetTemp>")); cl.print(F("<maxWaterTemp>")); cl.print(maxWaterTemp); cl.print(F("</maxWaterTemp>")); cl.print(F("<roomTemp>")); cl.print(bacRoomTemp); cl.print(F("</roomTemp>")); cl.print(F("<compressor>")); cl.print(tempCompressor); cl.print(F("</compressor>")); cl.print(F("<flow>")); cl.print(flowTemp); cl.print(F("</flow>")); cl.print(F("<return>")); cl.print(returnTemp); cl.print(F("</return>")); cl.print(F("<evaporator>")); cl.print(tempEvaporator); cl.print(F("</evaporator>")); cl.print(F("<humidity>")); cl.print(humidity); cl.print(F("</humidity>")); cl.print(F("<dewpoint>")); cl.print(dewPointTemp); cl.print(F("</dewpoint>")); cl.print(F("<bacPowerState>")); sprintf(buf, "%s", powerStateBAC == FALSE ? sOff : sOn); cl.print(buf); cl.print(F("</bacPowerState>")); cl.print(F("<bacMode>")); sprintf(buf, "%s", mode[operatingModeBAC]); cl.print(buf); cl.print(F("</bacMode>")); cl.print(F("<defrost>")); sprintf(buf, "%s", defrostFlag == TRUE ? "YES" : "NO"); cl.print(buf); cl.print(F("</defrost>")); cl.print(F("<defrostStep>")); cl.print(defStep); cl.print(F("</defrostStep>")); cl.print(F("<hysterisis>")); cl.print(hysterisis); cl.print(F("</hysterisis>")); cl.print(F("<defCycle>")); cl.print(timeBetweenDefrost); cl.print(F("</defCycle>")); cl.print(F("<state>")); cl.print(systemState); cl.print(F("</state>")); cl.print(F("<ambient>")); cl.print(ambientTemp); cl.print(F("</ambient>")); cl.print(F("<s4>")); cl.print(ambientTemp); cl.print(F("</s4>")); cl.print(F("<compStat>")); sprintf(buf, "%s", compStat == OFF ? sOff : sOn); cl.print(buf); cl.print(F("</compStat>")); cl.print(F("<fanLow>")); sprintf(buf, "%s", fanLow == OFF ? sOff : sOn); cl.print(buf); cl.print(F("</fanLow>")); cl.print(F("<fanHigh>")); sprintf(buf, "%s", fanHigh == OFF ? sOff : sOn); cl.print(buf); cl.print(F("</fanHigh>")); cl.print(F("<valve>")); sprintf(buf, "%s", valve == OFF ? sOff : sOn); cl.print(buf); cl.print(F("</valve>")); cl.print(F("<pump>")); sprintf(buf, "%s", pump == OFF ? sOff : sOn); cl.print(buf); cl.print(F("</pump>")); cl.println(F("</inputs>")); } // sets every element of str to 0 (clears array) void StrClear(char *str, char length) { for (int i = 0; i < length; i++) { str[i] = 0; } } // searches for the string sfind in the string str // returns 1 if string found // returns 0 if string not found char StrContains(char *str, char *sfind) { char found = 0; char index = 0; char len; len = strlen(str); if (strlen(sfind) > len) { return 0; } while (index < len) { if (str[index] == sfind[found]) { found++; if (strlen(sfind) == found) { return 1; } } else { found = 0; } index++; } return 0; } |
13. NTCThermistor.ino (still sort of in development/test)
/////////////////////////////////////////////////////////////////////////////////////////////////// // NTC Thermistor /////////////////////////////////////////////////////////////////////////////////////////////////// #include <math.h> // enumerating 3 major temperature scales enum { T_KELVIN = 0, T_CELSIUS, T_FAHRENHEIT }; // manufacturer data for episco k164 10k thermistor // simply delete this if you don't need it // or use this idea to define your own thermistors #define EPISCO_K164_10k 4300.0f,298.15f,10000.0f // B,T0,R0 #define NTCRES 3380.0f,298.15f,10000.0f NIL_THREAD(threadReadNTC, arg) { while (TRUE) { //while (readAnalog == TRUE) { // nilThdSleepMilliseconds(100); //} for (int s = 2; s < 6; s++) { ntcSensor[s - 2] = int(Thermistor(analogRead(s))); nilThdSleepMilliseconds(200); // DEBUG_PRINTST("Sensor %d: %d\n", s, ntcSensor[s - 2]); } //for (int s = 0; s < 4; s++) { // ntcSensor[s] = int(Thermistor(analogRead(s))); // //ntcSensor[s] = int(Temperature(s, T_CELSIUS, NTCRES, 9850)); // //ntcSensor[s] = thermNTC(s); // //ntcSensor[s] = tempTable(s); // nilThdSleepMilliseconds(200); //} // Sleep for 10 seconds. nilThdSleepSeconds(10); } } // resistance at 25 degrees C #define THERMISTORNOMINAL 10000 // temp. for nominal resistance (almost always 25 C) #define TEMPERATURENOMINAL 25 // how many samples to take and average, more takes longer but is more 'smooth' #define NUMSAMPLES 5 // The beta coefficient of the thermistor (usually 3000-4000) #define BCOEFFICIENT 3950 // the value of the 'other' resistor #define SERIESRESISTOR 10500 int samples[NUMSAMPLES]; float thermNTC(int pin) { uint8_t i; float average; // take N samples in a row, with a slight delay for (i = 0; i < NUMSAMPLES; i++) { samples[i] = analogRead(pin); delay(10); } // average all the samples out average = 0; for (i = 0; i < NUMSAMPLES; i++) { average += samples[i]; } average /= NUMSAMPLES; Serial.println(average); // convert the value to resistance average = 1023 / average - 1; average = SERIESRESISTOR / average; float steinhart; steinhart = average / THERMISTORNOMINAL; // (R/Ro) steinhart = log(steinhart); // ln(R/Ro) steinhart /= BCOEFFICIENT; // 1/B * ln(R/Ro) steinhart += 1.0 / (TEMPERATURENOMINAL + 273.15); // + (1/To) steinhart = 1.0 / steinhart; // Invert steinhart -= 273.15; // convert to C return(steinhart); } // Temperature function outputs float , the actual // temperature // Temperature function inputs // 1.AnalogInputNumber - analog input to read from // 2.OuputUnit - output in celsius, kelvin or fahrenheit // 3.Thermistor B parameter - found in datasheet // 4.Manufacturer T0 parameter - found in datasheet (kelvin) // 5. Manufacturer R0 parameter - found in datasheet (ohms) // 6. Your balance resistor resistance in ohms float Temperature(int AnalogInputNumber, int OutputUnit, float B, float T0, float R0, float R_Balance) { float R, T; // R=1024.0f*R_Balance/float(analogRead(AnalogInputNumber)))-R_Balance; R = R_Balance*(1024.0f / float(analogRead(AnalogInputNumber)) - 1); T = 1.0f / (1.0f / T0 + (1.0f / B)*log(R / R0)); switch (OutputUnit) { case T_CELSIUS: T -= 273.15f; break; case T_FAHRENHEIT: T = 9.0f*(T - 273.15f) / 5.0f + 32.0f; break; default: break; }; return T; } /////////////////////////// // Stuff for NTC thermistor /////////////////////////// // 666 ohms = 100C //double Thermistor(int RawADC) { // double Temp; // Temp = log(9850.0*((1024.0 / RawADC - 1))); // Temp = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * Temp * Temp))* Temp); // Temp = Temp - 273.15; // Convert Kelvin to Celcius // return Temp - 18; //} double Thermistor(int RawADC) { double Temp; Temp = log(10000.0*((1024.0 / RawADC - 1))); //Temp = log(10000.0 / (1024.0 / RawADC - 1)); // for pull-up configuration Temp = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * Temp * Temp))* Temp); Temp = Temp - 273.15; // Convert Kelvin to Celcius return Temp; } |
14. OLED.ino
/* Universal 8 bit Graphics Library, u8glib - Universal Graphics Library for 8 Bit Embedded Systems - Google Project Hosting */ #include <Wire.h> // Comes with Arduino IDE #include <U8glib.h> U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_DEV_0 | U8G_I2C_OPT_NO_ACK | U8G_I2C_OPT_FAST); // Fast I2C / TWI void failureBAC1000(void) { u8g.setFont(u8g_font_unifont); u8g.drawStr(0, 10, "Fail to read BAC1000"); } // graphic commands to redraw the complete screen should be placed here void draw(void) { char line1[20]; // used to store strings u8g.setFont(u8g_font_unifont); readTime(); //Get the time from the DS1307 if (lcd_page == LCD_PAGE0) { sprintf(line1, "%02d:%02d:%02d %02d/%02d", tm.Hour, tm.Minute, tm.Second, tm.Day, tm.Month); u8g.drawStr(0, 10, line1); char t[3]; char u[3]; dtostrf(tempCompressor, 2, 0, t); dtostrf(tempEvaporator, 2, 0, u); sprintf(line1, "Cp:%s Ev:%s", t, u); u8g.drawStr(0, 22, line1); //Dewpoint dtostrf(dewPointTemp, 5, 2, t); sprintf(line1, "Dp:%s", t); //u8g.drawBitmapP(0, 25, 1, 8, dropletIco); u8g.drawStr(0, 34, line1); if (defrostFlag == FALSE) sprintf(line1, "Machine:%s", operatingState == FALSE ? "OFF" : "ON"); else sprintf(line1, "Machine:%s Def:%d", operatingState == FALSE ? "OFF" : "ON", defrostStartTime - (int)(defrostRunTime / 1000)); u8g.drawStr(0, 46, line1); sprintf(line1, "%s", systemState); u8g.drawStr(0, 58, line1); } } NIL_THREAD(threadDisplayOLED, arg) { while (TRUE) { wdt_reset(); //Reset watchdog timer u8g.firstPage(); // picture loop do { draw(); nilThdSleepMilliseconds(50); } while (u8g.nextPage()); // rebuild the picture after some delay nilThdSleepMilliseconds(100); } } |
And that's it for the moment.
It still lacks control of the heatpump in cooling mode when the temperature reaches the dewpoint that's to come later (when the summer approaches!) Hope this helps or inspires someone. By the way, as a development environment I use Visual Studio 2015 and Visual Micro extension for Arduino. Acuario |
All times are GMT -5. The time now is 05:26 AM. |
Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.
Ad Management by RedTyger