From c9b4a1614109d5c11835b00d1c36c152606fdfff Mon Sep 17 00:00:00 2001 From: gonzho000 <48511877+gonzho000@users.noreply.github.com> Date: Mon, 1 Apr 2019 06:02:55 +0300 Subject: [PATCH] initial --- CHPC_PCB_v1_a1.8.9.ino | 1598 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1598 insertions(+) create mode 100644 CHPC_PCB_v1_a1.8.9.ino diff --git a/CHPC_PCB_v1_a1.8.9.ino b/CHPC_PCB_v1_a1.8.9.ino new file mode 100644 index 0000000..bb3561c --- /dev/null +++ b/CHPC_PCB_v1_a1.8.9.ino @@ -0,0 +1,1598 @@ +/* + Cheap Heat Pump Controller (CHPC) firmware. + Copyright (C) 2018-2019 Gonzho (gonzho@web.de) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + See https://github.com/gonzho000/chpc/ for more details +*/ + + + +//-----------------------USER OPTIONS----------------------- +#define BOARD_TYPE_G //Type "G" +//#define BOARD_TYPE_F //Type "F" + +//#define DISPLAY_096 1 +#define DISPLAY_1602 2 // patch "inline size_t LiquidCrystal_I2C::write(uint8_t value)" if only 1st character appears: "return 1" instead of "return 0" +//#define DISPLAY_NONE -1 + +//#define INPUTS_AS_BUTTONS 1 //pulldown resistors required! +//#define RS485_PYTHON 1 +#define RS485_HUMAN 2 + +//#define WATCHDOG //only if u know what to do +//-----------------------TUNING OPTIONS ----------------------- +#define MAX_WATTS 1170.0 //user for power protection + +#define DEFFERED_STOP_HOTCIRCLE 3000000 //5 mins + +#define POWERON_PAUSE 300000; //5 mins +#define MINCYCLE_POWEROFF 300000; //5 mins +#define MINCYCLE_POWERON 3600000; //60 mins + +#define MAGIC 0x39 //change if u want to reinit T sensors +//-----------------------USER OPTIONS END ----------------------- + +//#define EEV_SUPPORT +//???#define HUMAN_AUTOINFO 20 //!!!send periodical info, seconds, default = 20 +//#define INPUTS_AS_INPUTS 2 //!!! +//define RS485_MACHINE 3 //?? or part of Python? + + + +//-----------------------changelog----------------------- +/* +v1.0: +- Displays support +- no more softserial +- define TYPE F/G and rearrange ports +- multi-DS18b20 support on lane +- skip non-important DS18B20 during init +- rewrite Main Cycle to unification: some sensors can be absent, ex: T_hot_out can be absent because i'ts used as target +- 2 on-board buttons support: +/- aim +- DISPLAY: indication: real and aim +- RS485_HUMAN: remote commands +,-,G,0x20/?/Enter +- buttons: < > increase_decrease t +- simpliest thermostat scheme: only T target +- rename all procs +- RS485_PYTHON: print to console inspite of mode diring init proc +- faster wattage overload processing +- write aim value to EE if needed, period: 15 mins (eq. 1041 days) +- deferred stop of hot side circle +- 80 microseconds at 9600 + +//TODO: +- HUMAN_AUTOINFO time +- current sensor optional +- few devices at same lane for RS485_HUMAN +- EEV_Support +- EEV_recalibration_time to stop HP and recalibrate EEV from zero level ex: every 24 hours +- valve_4way +- rewite re-init proc from MAGIC to emergency jumper removal at board start +- emergency jumper support +? periodical start of hot side circle +- Liquid ref. T protection +*/ +//-----------------------changelog END----------------------- + +// DS18B20 pins: GND DATA VDD + +//Connections: +//DS18B20 Pinout (Left to Right, pins down, flat side toward you) +//- Left = Ground +//- Center = Signal (Pin N of arduino): (with 3.3K to 4.7K resistor to +5 or 3.3 ) +//- Right = +5 or +3.3 V +// + + +// +// high volume scheme: +---- +5V (12V not tested) +// | +// +----+ +// 1MOhm piezo +// +----+ +// |(C) +// pin -> 1.6 kOhms -> (B) 2n2222 < front here +// |(E) +// +--- GND +// + +/* +scheme SCT-013-000: + +2 pins used: tip and sleeve, center (ring) not used http://cms.35g.tw/coding/wp-content/uploads/2014/09/SCT-013-000_UNO-1.jpg +pins are interchangeable due to AC + +32 Ohms (22+10) between sensor pins (35 == ideal) + +Pin1: +- via elect. cap. to GND +- via ~10K..470K resistor to GND +- via ~10K..470K resistor to +5 (same as prev.) +if 10K+10K used: current is 25mA +use 100K+100K for 3 phases + +Pin2: +- to analog pin +- via 32..35 Ohms resistor to Pin1 + ++5 -------------------------+ + | + | + # R1 10K+ + | + | + |~2.5 at this point + +---------------+--------------------------------------+----+ + | | | | + #_ elect. cap. # R2 10K+ (same as R1) SCT-013-000 $ # R3 = 35 Ohms (ideal case), 32 used + | | | | +GND --------+---------------+ +----+--------> to Analog pin + + +WARNING: calibrate 3 sensors together, from different sellers, due to case of incorrectly worked 1 of 3 sensor + +P(watts)=220*220/R(Ohms) +*/ + +// +//MAX 485 voltage - 5V +// +// use resistor at RS-485 GND +// 1st test: 10k result lot of issues +// 2nd test: 1k, issues +// 3rd test: 100, see discussions + + +//16-ch Multiplexer EN pin: active LOW, connect to GND + +//used pins: +//!!! ACTUALISE +//2: Z +//3: S3 +//4: S2 +//5: S1 +//6: S0 +//7: relay 2 +//8: relay 3 +//9: speaker +//10: relay 4 +//11-13: rs485 +//A0: relay 1 +//A1: power monitor + +/* +relay 1: heat pump +relay 2: hot side pump +relay 3: cold side pump +relay 4: (future) heatpump sump heater + +t0: room +t1: heatpump sump +t2: cold in +t3: cold out +t4: hot in +t5: hot out +t6: before condenser +t7: condenser-evaporator +t8: after evaporator +t9: outer +tA: warm floor + +wattage1 + +*/ + +String fw_version = "1.0"; + +#ifdef DISPLAY_096 + #define DISPLAY DISPLAY_096 + #include <Wire.h> + #include "SSD1306Ascii.h" + #include "SSD1306AsciiWire.h" + #define I2C_ADDRESS 0x3C + SSD1306AsciiWire oled; +#endif + +#ifdef DISPLAY_1602 + #define DISPLAY DISPLAY_1602 + #include <Wire.h> + #include "LiquidCrystal_I2C.h" + LiquidCrystal_I2C lcd(0x3f,16,2); // set the LCD address to 0x27 for a 16 chars and 2 line display +#endif + +#ifdef DISPLAY_NONE + #define DISPLAY DISPLAY_NONE +#endif + +#ifndef DISPLAY + #define DISPLAY -1 +#endif + +// + +#ifdef INPUTS_AS_BUTTONS + #define INPUTS INPUTS_AS_BUTTONS +#endif + +#ifdef INPUTS_AS_INPUTS + #define INPUTS INPUTS_AS_INPUTS +#endif + +// + +#ifdef RS485_PYTHON + #define RS485 RS485_PYTHON + char ishuman = 0; +#endif + +#ifdef RS485_HUMAN + #define RS485 RS485_HUMAN + char ishuman = 1; +#endif + +//hardware resources +#define OW_BUS_ALLTSENSORS 12 +#define SerialTxControl 13 //RS485 Direction control DE and RE to this pin +#define speakerOut 6 +#define em_pin1 A6 +#define EMERGENCY_PIN A7 + +#ifdef BOARD_TYPE_G + String hw_version = "Type G v1.0.x"; + #define RELAY_HEATPUMP 8 + #define RELAY_HOTSIDE_CIRCLE 9 + #define RELAY_COLDSIDE_CIRCLE 7 + #define RELAY_SUMP_HEATER 10 + #define RELAY_4WAY_VALVE 11 + #ifdef INPUTS_AS_BUTTONS + #define BUT_RIGHT A0 + #define BUT_LEFT A3 + #endif +#elif BOARD_TYPE_F + //!!!! +#endif +//---------------------------memory debug +#ifdef __arm__ + // should use uinstd.h to define sbrk but Due causes a conflict + extern "C" char* sbrk(int incr); +#else // __ARM__ + extern char *__brkval; +#endif // __arm__ + +int freeMemory() { + char top; + #ifdef __arm__ + return &top - reinterpret_cast<char*>(sbrk(0)); + #elif defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151) + return &top - __brkval; + #else // __arm__ + return __brkval ? &top - __brkval : &top - __malloc_heap_start; + #endif // __arm__ +} +//---------------------------memory debug END + + +#include <avr/wdt.h> +#include <EEPROM.h> +//#include <FastCRC.h> +/*FastCRC16 CRC16; +union _crc { + unsigned int integer; + char bytes[2]; +} crc; +*/ + +#include <SoftwareSerial.h> + +#define SerialRX 0 //RX connected to RO - Receiver Output +#define SerialTX 1 //TX connected to DI - Driver Output Pin +#define RS485Transmit HIGH +#define RS485Receive LOW + +const char devID = 0x44; //0x3B == ; +const char hostID = 0x30; + +SoftwareSerial RS485Serial(SerialRX, SerialTX); // RX, TX + +#include <OneWire.h> +#include <DallasTemperature.h> +//library's DEVICE_DISCONNECTED_C -127.0 + +OneWire ow_ALLTSENSORS(OW_BUS_ALLTSENSORS); +DallasTemperature s_allTsensors(&ow_ALLTSENSORS); + +typedef struct { + DeviceAddress addr; + bool e; //enabled + double T; +} st_tsens; + +DeviceAddress dev_addr; //temp + +st_tsens Tae ; +st_tsens Tbe ; +st_tsens Ttarget; +st_tsens Tsump; +st_tsens Tci ; +st_tsens Tco ; +st_tsens Thi ; +st_tsens Tho ; +st_tsens Tbc ; +st_tsens Tac ; +st_tsens Touter; +st_tsens Ts1 ; +st_tsens Ts2 ; + +#define BIT_Tae 0 +#define BIT_Tbe 1 +#define BIT_Ttarget 2 +#define BIT_Tsump 3 +#define BIT_Tci 4 +#define BIT_Tco 5 +#define BIT_Thi 6 +#define BIT_Tho 7 +#define BIT_Tbc 8 +#define BIT_Tac 9 +#define BIT_Touter 10 +#define BIT_Ts1 11 +#define BIT_Ts2 12 + +unsigned int used_sensors = 0 ; //bit array + +double T_setpoint = 26.5; +double T_setpoint_lastsaved = T_setpoint; +const double cT_setpoint_max = 45.0; +const double cT_heat_delta_min = 2.0; +const double cT_sump_min = 12.0; +const double cT_sump_max = 101.0; +const double cT_sump_heat_threshold = 16.0; +//const double cT_sump_outerT_threshold = 18.0; //?? seems to be not useful +const double cT_before_condenser_max = 99.0; +const double cT_after_evaporator_min = -7.0; // working evaporation presure ~= -10, it is constant due to large evaporator volume // waterhouse v1: -12 is too high +const double cT_cold_min = -8.0; +const double cT_hotout_max = 50.0; +//const double cT_workingOK_cold_delta_min = 0.5; // 0.7 - 1st try, 2nd try 0.5 +const double cT_workingOK_hot_delta_min = 0.5; +const double cT_workingOK_sump_min = 40.0; //need to be not very high to normal start after deep freeze +const double c_wattage_max = MAX_WATTS; //FUNAI: 1000W seems to be normal working wattage INCLUDING 1(one) CR25/4 at 3rd speed + //PH165X1CY : 920 Watts, 4.2 A +const double c_workingOK_wattage_min = c_wattage_max/2.5; // + +int heatpump_state = 0; +int hotside_circle_state = 0; +int coldside_circle_state = 0; +int sump_heater_state = 0; + +const long poweron_pause = POWERON_PAUSE ; //default 5 mins +const long mincycle_poweroff = MINCYCLE_POWEROFF; //default 5 mins +const long mincycle_poweron = MINCYCLE_POWERON ; //default 60 mins +int _1st_start_sleeped = 0; +//??? TODO: periodical start ? +//const long floor_circle_maxhalted = 6000000; //circle NOT works max 100 minutes +const long deffered_stop_hotcircle = DEFFERED_STOP_HOTCIRCLE; + +//main cycle vars +unsigned long millis_prev = 0; +unsigned long millis_now = 0; +unsigned long millis_cycle = 5000; + +unsigned long millis_last_heatpump_on = 0; +unsigned long millis_last_heatpump_off = 0; + +unsigned long millis_notification = 0; +unsigned long millis_notification_interval = 33000; + +unsigned long millis_displ_update = 0; +unsigned long millis_displ_update_interval = 10000; + +unsigned long millis_escinput = 0; +unsigned long millis_charinput = 0; + +unsigned long millis_lasteesave = 0; + +int skipchars = 0; + +#define ERR_HZ 2500 + + +char inData[50]; // Allocate some space for the string, do not change that size! +char inChar= -1; // space to store the character read +byte index = 0; // Index into array; where to store the character + +//-------------temporary variables +char temp[10]; +int i = 0; +int z = 0; +int x = 0; +double tempdouble = 0.0; +int tempint = 0; + +String outString; +//-------------EEPROM +int eeprom_magic_read = 0x00; +int eeprom_addr = 0x00; +//initial values, saved to EEPROM and can be modified later +//CHANGE eeprom_magic after correction! +const int eeprom_magic = MAGIC; + +//-------------ERROR states +#define ERR_OK 0 +#define ERR_T_SENSOR 1 +#define ERR_HOT_PUMP 2 +#define ERR_COLD_PUMP 3 +#define ERR_HEATPUMP 4 +#define ERR_WATTAGE 5 + +int errorcode = 0; + +//--------------------------- for wattage +#define ADC_BITS 10 //10 fo regular arduino +#define ADC_COUNTS (1<<ADC_BITS) +int em_calibration = 62.5; +int em_samplesnum = 2960; // Calculate Irms only 1480 == full 14 periods for 50Hz +//double Irms = 0; //for tests with original procedure +int supply_voltage = 0; +int em_i = 0; +//phase 1 +int sampleI_1 = 0; +double filteredI_1 = 0; +double offsetI_1 = ADC_COUNTS>>1; //Low-pass filter output +double sqI_1,sumI_1 = 0; //sq = squared, sum = Sum, inst = instantaneous +double async_Irms_1 = 0; +double async_wattage = 0; +//--------------------------- for wattage END + +//!!! +#include <Stepper.h> +const int stepsPerRevolution = 200; // change this to fit the number of steps per revolution +Stepper myStepper(stepsPerRevolution, 2, 3, 4, 5); +//!!! + +//--------------------------- functions +long ReadVcc() { + // Read 1.1V reference against AVcc + // set the reference to Vcc and the measurement to the internal 1.1V reference + #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) + ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); + #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) + ADMUX = _BV(MUX5) | _BV(MUX0); + #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) + ADMUX = _BV(MUX3) | _BV(MUX2); + #else + ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); + #endif + + delay(2); // Wait for Vref to settle + ADCSRA |= _BV(ADSC); // Start conversion + while (bit_is_set(ADCSRA,ADSC)); // measuring + + uint8_t low = ADCL; // must read ADCL first - it then locks ADCH + uint8_t high = ADCH; // unlocks both + + long result = (high<<8) | low; + //constant NOT same as in battery controller! + result = 1126400L / result; // Calculate Vcc (in mV); (me: !!) 1125300 (!!) = 1.1*1023*1000 + return result; // Vcc in millivolts +} + +char CheckAddrExists(void) { + for (i = 0; i < 8; i++) { if (dev_addr[i] != Tae.addr[i]) break; } + if (i == 8) return 1; + for (i = 0; i < 8; i++) { if (dev_addr[i] != Tbe.addr[i]) break; } + if (i == 8) return 1; + for (i = 0; i < 8; i++) { if (dev_addr[i] != Ttarget.addr[i]) break;} + if (i == 8) return 1; + for (i = 0; i < 8; i++) { if (dev_addr[i] != Tsump.addr[i]) break;} + if (i == 8) return 1; + for (i = 0; i < 8; i++) { if (dev_addr[i] != Tci.addr[i]) break;} + if (i == 8) return 1; + for (i = 0; i < 8; i++) { if (dev_addr[i] != Tco.addr[i]) break;} + if (i == 8) return 1; + for (i = 0; i < 8; i++) { if (dev_addr[i] != Thi.addr[i]) break;} + if (i == 8) return 1; + for (i = 0; i < 8; i++) { if (dev_addr[i] != Tho.addr[i]) break;} + if (i == 8) return 1; + for (i = 0; i < 8; i++) { if (dev_addr[i] != Tbc.addr[i]) break;} + if (i == 8) return 1; + for (i = 0; i < 8; i++) { if (dev_addr[i] != Tac.addr[i]) break;} + if (i == 8) return 1; + for (i = 0; i < 8; i++) { if (dev_addr[i] != Touter.addr[i]) break;} + if (i == 8) return 1; + for (i = 0; i < 8; i++) { if (dev_addr[i] != Ts1.addr[i]) break;} + if (i == 8) return 1; + for (i = 0; i < 8; i++) { if (dev_addr[i] != Ts2.addr[i]) break;} + if (i == 8) return 1; + return 0; +} + +void InitS_and_D(void) { + #ifdef DISPLAY_096 + Wire.begin(); + oled.begin(&Adafruit128x64, I2C_ADDRESS); + oled.setFont(Adafruit5x7); + #endif + #ifdef DISPLAY_1602 + lcd.init(); // initialize the lcd + lcd.backlight(); // not really needed + #endif + RS485Serial.begin(9600); +} + +void PrintS_and_D (String str, int printSerial = 1) { + char *outChar=&str[0]; + //#ifdef RS485_HUMAN + if (ishuman != 0) { + if (printSerial == 1) { + digitalWrite(SerialTxControl, RS485Transmit); + delay(1); + RS485Serial.print(outChar); + RS485Serial.println(); + RS485Serial.flush(); + digitalWrite(SerialTxControl, RS485Receive); + } + } + //#endif + if (str == "") { + return; + } + #ifdef DISPLAY_096 + oled.clear(); + oled.println(str); + #endif + #ifdef DISPLAY_1602 + lcd.backlight(); + lcd.clear(); + lcd.print(str); + #endif +} + +void _PrintHelp(void) { + //sprintf(tweetMsg, "first variable = %d, 2nd variable = %ul", var1, var2) + PrintS_and_D( "CHPC, https://github.com/gonzho000/chpc/ fw: " + fw_version + " board: "+ hw_version); + PrintS_and_D(F("Commands:")); + PrintS_and_D(F("(?) help")); + PrintS_and_D(F("(+) increase aim T")); + PrintS_and_D(F("(-) decrease aim T")); + PrintS_and_D(F("(G) get stats")); +} + +void PrintS_and_D_double (double double_to_print) { + dtostrf(double_to_print,1,2,temp); + PrintS_and_D(temp); +} + +int Inc_T (void) { + if (T_setpoint + 0.5 > cT_setpoint_max) { + PrintS_and_D(F("ERR: Max T!")); + delay (200); + return 0; + } + T_setpoint += 0.5; + PrintS_and_D_double(T_setpoint); + return 1; +} + +int Dec_T (void) { + if (T_setpoint - 0.5 < 1.0) { + PrintS_and_D(F("ERR: Min T!")); + delay (200); + return 0; + } + T_setpoint -= 0.5; + PrintS_and_D_double(T_setpoint); + return 1; +} + + +void print_Serial_SaD (double num) { //global string + double + RS485Serial.print(outString); + RS485Serial.println(num); +} + +void PrintStats_Serial (void) { + #ifdef RS485_HUMAN + digitalWrite(SerialTxControl, RS485Transmit); + delay(1); + if (Tae.e == 1) {outString = "Tae: " ; print_Serial_SaD(Tae.T); } + if (Tbe.e == 1) {outString= "Tbe: " ; print_Serial_SaD(Tbe.T); } + if (Ttarget.e == 1) {outString = "Ttarget: "; print_Serial_SaD(Ttarget.T); } + if (Tsump.e == 1) {outString = "Tsump: " ; print_Serial_SaD(Tsump.T); } + if (Tci.e == 1) {outString = "Tci: " ; print_Serial_SaD(Tci.T); } + if (Tco.e == 1) {outString = "Tco: " ; print_Serial_SaD(Tco.T); } + if (Thi.e == 1) {outString = "Thi: " ; print_Serial_SaD(Thi.T); } + if (Tho.e == 1) {outString = "Tho: " ; print_Serial_SaD(Tho.T); } + if (Tbc.e == 1) {outString = "Tbc: " ; print_Serial_SaD(Tbc.T); } + if (Tac.e == 1) {outString = "Tac: " ; print_Serial_SaD(Tac.T); } + if (Touter.e == 1) {outString = "Touter: " ; print_Serial_SaD(Touter.T); } + if (Ts1.e == 1) {outString = "Ts1: " ; print_Serial_SaD(Ts1.T); } + if (Ts2.e == 1) {outString = "Ts2: " ; print_Serial_SaD(Ts2.T); } + outString = "Err: " + String(errorcode) + "\n\rWatts:" + String(async_wattage) + "\n\rAim: "; print_Serial_SaD(T_setpoint); + RS485Serial.println(); + RS485Serial.flush(); + digitalWrite(SerialTxControl, RS485Receive); + #endif +} + +void ReadEECheckAddr(unsigned char *to_addr) { + for (i=0 ; i<8 ; i++) { + to_addr[i] = EEPROM.read(eeprom_addr); + eeprom_addr++; + } + i = 0; + CheckIsInvalidCRCAddr(to_addr); + if (i != 0) { + while (1) { + PrintS_and_D(F("Err:EEPROM, reinit!")); + delay(1000); + } + } +} + +void CheckIsInvalidCRCAddr(unsigned char *addr) { + if (OneWire::crc8( addr, 7) != addr[7] ) { + i+= 1; + } +} + +void CopyAddrStoreEE(unsigned char *addr_to, int bit_offset) { //get result from dev_addr, autoincrement eeprom_addr + //dev_addr and z from globals used + for (i=0 ; i<8 ; i++) { //no matter + if (z == 0) { + dev_addr[i] = 0x00; + } + addr_to[i] = dev_addr[i]; + EEPROM.write(eeprom_addr, dev_addr[i]); + eeprom_addr++; + } + bitWrite(used_sensors, bit_offset, z); +} + +void WriteFloatEEPROM(int addr, float val) { + byte *x = (byte *)&val; + for(byte u = 0; u < 4; u++) EEPROM.write(u+addr, x[u]); +} + +float ReadFloatEEPROM(int addr) { + byte x[4]; + for(byte u = 0; u < 4; u++) x[u] = EEPROM.read(u+addr); + float *y = (float *)&x; + return y[0]; +} + +void SaveSetpointEE(void) { + if( (T_setpoint_lastsaved != T_setpoint) && + ( ((unsigned long)(millis_now - millis_lasteesave) > 15*60*1000 ) || (millis_lasteesave == 0) ) ) { + eeprom_addr = 1; + WriteFloatEEPROM(eeprom_addr, T_setpoint); + millis_lasteesave = millis_now; + T_setpoint_lastsaved = T_setpoint; + //PrintS_and_D("Deb: EEsave!"); //!!! + } +} + +void PrintAddr(unsigned char *str) { + outString = ""; + for (i = 0; i < 8; i++) { + if (str[i] < 0x10) outString += "0"; + outString += String(str[i], HEX); + } + PrintS_and_D(outString); +} + +unsigned char FindAddr(String what, int required = 0) { + i = 1; + while (RS485Serial.available() > 0) { + inChar = RS485Serial.read(); + delay(1); + } + inChar = 0x00; + while (1) { + while (!s_allTsensors.getAddress(dev_addr, 0)) { + if (required == 0) { + PrintS_and_D(F("Press > to skip")); + delay(500); + while (RS485Serial.available() > 0) { + inChar = RS485Serial.read(); + if (inChar == 0x3E) { + PrintS_and_D("Skipped: " + what); + return 0; + } + } + #ifdef INPUTS_AS_BUTTONS + i = digitalRead(BUT_RIGHT); + if (i == 1) { + PrintS_and_D("Skipped: " + what); + delay(4000); + return 0; + } + #endif + } + PrintS_and_D("Insert " + what); + delay(1000); + } + if ( OneWire::crc8( dev_addr, 7) != dev_addr[7]) { + RS485Serial.print("Invalid CRC!\n"); + delay(200); + continue; + } else if (CheckAddrExists() == 1) { + PrintS_and_D(F("USED! Remove!")); + delay(1000); + continue; + } else { + break; + } + } + while (1) { + PrintAddr(dev_addr); + delay(1000); + if (s_allTsensors.getAddress(dev_addr, 0)) { + PrintS_and_D("Remove " + what); + delay(1000); + } else { + delay(100); + break; + } + } + return i; +} + +double GetT (unsigned char *str) { + tempdouble = -127.0; + for ( i = 0; i < 8; i++) { + #ifdef WATCHDOG + wdt_reset(); + #endif + if ( (tempdouble == 85.0) || (tempdouble == -127.0) ) { + if ( tempdouble == 85.0 ) { //initial value in dallas register after poweron + delay (375); //375 actual for 11 bits resolution, 2-3 retries OK for 12-bits resolution + } else { + delay (37); + } + tempdouble = s_allTsensors.getTempC(str); + } else { + break; + } + } + return tempdouble; +} + +void Get_Temperatures(void) { + if (Tae.e) Tae.T = GetT(Tae.addr); + if (Tbe.e) Tbe.T = GetT(Tbe.addr); + Ttarget.T = GetT(Ttarget.addr); + if (Tsump.e) Tsump.T = GetT(Tsump.addr); + if (Tci.e) Tci.T = GetT(Tci.addr); + if (Tco.e) Tco.T = GetT(Tco.addr); + if (Thi.e) Thi.T = GetT(Thi.addr); + if (Tho.e) Tho.T = GetT(Tho.addr); + if (Tbc.e) Tbc.T = GetT(Tbc.addr); + if (Tac.e) Tac.T = GetT(Tac.addr); + if (Touter.e) Touter.T = GetT(Touter.addr); + if (Ts1.e) Ts1.T = GetT(Ts1.addr); + if (Ts2.e) Ts2.T = GetT(Ts2.addr); + s_allTsensors.requestTemperatures(); //global request + PrintStats_Serial(); //!!! debug + //---------DEBUG and self-test !!!-------- + /*PrintS_and_D(""); + PrintS_and_D_double(Tae.T); + PrintS_and_D_double(Tbe.T); + PrintS_and_D_double(Ttarget.T); + PrintS_and_D_double(Tsump.T); + PrintS_and_D("");*/ + /* + PrintS_and_D("Sensor 1 "); + PrintS_and_D_double(tr_sens_1); + PrintS_and_D(",\tci "); + PrintS_and_D_double(tr_cold_in); + PrintS_and_D(",\tcout "); + PrintS_and_D_double(tr_cold_out); + PrintS_and_D(",\thin "); + PrintS_and_D_double(tr_hot_in); + PrintS_and_D(",\tho "); + PrintS_and_D_double(tr_hot_out); + PrintS_and_D(",\tbcond "); + PrintS_and_D_double(tr_before_condenser); + PrintS_and_D(",\t outer "); + PrintS_and_D_double(tr_outer); + PrintS_and_D(",\t Sensor 2 "); + PrintS_and_D_double(tr_sens_2); + */ + + //---------DEBUG END-------- +} + +//--------------------------- functions END + + +void setup(void) { + pinMode (RELAY_HEATPUMP, OUTPUT); + pinMode (RELAY_HOTSIDE_CIRCLE, OUTPUT); + pinMode (RELAY_COLDSIDE_CIRCLE, OUTPUT); + pinMode (RELAY_SUMP_HEATER, OUTPUT); + + digitalWrite (RELAY_HEATPUMP, LOW); + digitalWrite (RELAY_HOTSIDE_CIRCLE, LOW); + digitalWrite (RELAY_COLDSIDE_CIRCLE, LOW); + digitalWrite (RELAY_SUMP_HEATER, LOW); + + #ifdef WATCHDOG + wdt_disable(); + delay(2000); + wdt_enable (WDTO_8S); + #endif + + InitS_and_D(); + pinMode(SerialTxControl, OUTPUT); + digitalWrite(SerialTxControl, RS485Receive); + //digitalWrite(SerialTxControl, RS485Transmit); + //RS485Serial.println("starting..."); //!!!debug + delay(100); + PrintS_and_D("ID: 0x" + String(devID, HEX)); + //Print_Lomem(C_ID); + delay(2000); + //PrintS_and_D("setpoint (C):"); + //PrintS_and_D(setpoint); + + //PrintS_and_D(String(freeMemory())); //!!! debug + + s_allTsensors.begin(); + s_allTsensors.setWaitForConversion(false); //ASYNC mode, request before get, see Dallas library for details + + eeprom_magic_read = EEPROM.read(eeprom_addr); + #ifdef INPUTS_AS_BUTTONS + pinMode (BUT_RIGHT, INPUT); + //digitalWrite (BUT_RIGHT, LOW); + pinMode (BUT_LEFT, INPUT); + //digitalWrite (BUT_LEFT, LOW); + #endif + //EEPROM content: + //0x00 - magic, + //0x01 .. 0x04 Target value, + //0x05 and 0x06 if sensor enabled or not, used_sensors HI and LO + //0x07 .. 0x0e 1st addr, etc.. + + // tr_after_evaporator(0); tr_before_evaporator(1); tr_target(2); tr_sump(3); + // tr_cold_in(4); tr_cold_out(5); tr_hot_in(6); tr_hot_out(7); + // tr_before_condenser(8); tr_after_condenser(9); tr_outer(10); tr_sens_1(11); + // tr_sens_2(12); + + eeprom_addr = 0x00; + if (eeprom_magic_read == eeprom_magic) { + eeprom_addr += 1; + T_setpoint = ReadFloatEEPROM(eeprom_addr); + eeprom_addr += 4; + PrintS_and_D("EEPROM->T " + String(T_setpoint)); + + z = EEPROM.read(eeprom_addr); //high + eeprom_addr += 1; + i = EEPROM.read(eeprom_addr); //lo + eeprom_addr += 1; + used_sensors= word (z,i); + + Tae.e = bitRead(used_sensors, BIT_Tae ); + Tbe.e = bitRead(used_sensors, BIT_Tbe ); + Ttarget.e = bitRead(used_sensors, BIT_Ttarget ); + Tsump.e = bitRead(used_sensors, BIT_Tsump ); + Tci.e = bitRead(used_sensors, BIT_Tci ); + Tco.e = bitRead(used_sensors, BIT_Tco ); + Thi.e = bitRead(used_sensors, BIT_Thi ); + Tho.e = bitRead(used_sensors, BIT_Tho ); + Tbc.e = bitRead(used_sensors, BIT_Tbc ); + Tac.e = bitRead(used_sensors, BIT_Tac ); + Touter.e = bitRead(used_sensors, BIT_Touter ); + Ts1.e = bitRead(used_sensors, BIT_Ts1 ); + Ts2.e = bitRead(used_sensors, BIT_Ts2 ); + #ifdef EEV_SUPPORT + if (Tae.e != 1 || Tbe.e != 1) { + while (1) { + PrintS_and_D("ERR: no Tae or Tbe for EEV!"); + delay (1000); + } + } + #endif + + ReadEECheckAddr(Tae.addr); //eeprom_addr incremeneted here + ReadEECheckAddr(Tbe.addr); //eeprom_addr incremeneted here + ReadEECheckAddr(Ttarget.addr); //eeprom_addr incremeneted here + ReadEECheckAddr(Tsump.addr); //eeprom_addr incremeneted here + ReadEECheckAddr(Tci.addr); //eeprom_addr incremeneted here + ReadEECheckAddr(Tco.addr); //eeprom_addr incremeneted here + ReadEECheckAddr(Thi.addr); //eeprom_addr incremeneted here + ReadEECheckAddr(Tho.addr); //eeprom_addr incremeneted here + ReadEECheckAddr(Tbc.addr); //eeprom_addr incremeneted here + ReadEECheckAddr(Tac.addr); //eeprom_addr incremeneted here + ReadEECheckAddr(Touter.addr); //eeprom_addr incremeneted here + ReadEECheckAddr(Ts1.addr); //eeprom_addr incremeneted here + ReadEECheckAddr(Ts2.addr); //eeprom_addr incremeneted here + + i = 0; + if (Tae.e == 1) CheckIsInvalidCRCAddr(Tae.addr ); + if (Tbe.e == 1) CheckIsInvalidCRCAddr(Tbe.addr ); + if (Ttarget.e == 1) CheckIsInvalidCRCAddr(Ttarget.addr); + if (Tsump.e == 1) CheckIsInvalidCRCAddr(Tsump.addr); + if (Tci.e == 1) CheckIsInvalidCRCAddr(Tci.addr ); + if (Tco.e == 1) CheckIsInvalidCRCAddr(Tco.addr ); + if (Thi.e == 1) CheckIsInvalidCRCAddr(Thi.addr ); + if (Tho.e == 1) CheckIsInvalidCRCAddr(Tho.addr ); + if (Tbc.e == 1) CheckIsInvalidCRCAddr(Tbc.addr ); + if (Tac.e == 1) CheckIsInvalidCRCAddr(Tac.addr ); + if (Touter.e == 1) CheckIsInvalidCRCAddr(Touter.addr); + if (Ts1.e == 1) CheckIsInvalidCRCAddr(Ts1.addr ); + if (Ts2.e == 1) CheckIsInvalidCRCAddr(Ts2.addr ); + if (i != 0) { + while ( 1 ) { PrintS_and_D(F("EEPROM err1!")); delay (1000); } + } + } else { + eeprom_addr += 1; + ishuman += 1; + WriteFloatEEPROM(eeprom_addr, T_setpoint); + PrintS_and_D(F("init EEPROM")); + eeprom_addr += 4; + eeprom_addr += 2; //used sensors, skip + //Ttarget -needed, other - optional + #ifdef EEV_SUPPORT + z = FindAddr("Tae", 1); //holds result in dev_addr, returns "is used" + #else + z = FindAddr("Tae"); //holds result in dev_addr, returns "is used" + #endif + Tae.e = z; + CopyAddrStoreEE (Tae.addr, BIT_Tae); //dev_addr and z used by proc, autoincrement eeprom_addr, store bit + + #ifdef EEV_SUPPORT + z = FindAddr("Tbe", 1); + #else + z = FindAddr("Tbe"); + #endif + Tbe.e = z; + CopyAddrStoreEE (Tbe.addr, BIT_Tbe); //dev_addr and z used by proc, autoincrement eeprom_addr, store bit + + z = FindAddr("Ttarget", 1); + Ttarget.e = z; + CopyAddrStoreEE (Ttarget.addr, BIT_Ttarget); //dev_addr and z used by proc, autoincrement eeprom_addr, store bit + + z = FindAddr("Tsump"); + Tsump.e = z; + CopyAddrStoreEE (Tsump.addr, BIT_Tsump); //dev_addr and z used by proc, autoincrement eeprom_addr, store bit + + z = FindAddr("Tci"); + Tci.e = z; + CopyAddrStoreEE (Tci.addr, BIT_Tci); //dev_addr and z used by proc, autoincrement eeprom_addr, store bit + + z = FindAddr("Tco"); + Tco.e = z; + CopyAddrStoreEE (Tco.addr, BIT_Tco); //dev_addr and z used by proc, autoincrement eeprom_addr, store bit + + z = FindAddr("Thi"); + Thi.e = z; + CopyAddrStoreEE (Thi.addr, BIT_Thi); //dev_addr and z used by proc, autoincrement eeprom_addr, store bit + + z = FindAddr("Tho"); + Tho.e = z; + CopyAddrStoreEE (Tho.addr, BIT_Tho); //dev_addr and z used by proc, autoincrement eeprom_addr, store bit + + z = FindAddr("Tbc"); + Tbc.e = z; + CopyAddrStoreEE (Tbc.addr, BIT_Tbc); //dev_addr and z used by proc, autoincrement eeprom_addr, store bit + + z = FindAddr("Tac"); + Tac.e = z; + CopyAddrStoreEE (Tac.addr, BIT_Tac); //dev_addr and z used by proc, autoincrement eeprom_addr, store bit + + z = FindAddr("Touter"); + Touter.e = z; + CopyAddrStoreEE (Touter.addr, BIT_Touter); //dev_addr and z used by proc, autoincrement eeprom_addr, store bit + + z = FindAddr("Ts1"); + Ts1.e = z; + CopyAddrStoreEE (Ts1.addr, BIT_Ts1); //dev_addr and z used by proc, autoincrement eeprom_addr, store bit + + z = FindAddr("Ts2"); + Ts2.e = z; + CopyAddrStoreEE (Ts2.addr, BIT_Ts2); //dev_addr and z used by proc, autoincrement eeprom_addr, store bit + + // + //final, off-the-sequence + EEPROM.write(0+1+4+0,highByte(used_sensors)); + EEPROM.write(0+1+4+1,lowByte(used_sensors)); + EEPROM.write(0x00, eeprom_magic); + ishuman -= 1; + } + T_setpoint_lastsaved = T_setpoint; + //s_allTsensors.setResolution(ad_Tae, 12); + /*PrintAddr(Tae.addr); + PrintAddr(Tbe.addr); + PrintAddr(Ttarget.addr); + PrintAddr(Tsump.addr); + PrintAddr(Tci.addr); + PrintAddr(Tco.addr); + PrintAddr(Thi.addr); + PrintAddr(Tho.addr); + PrintAddr(Tbc.addr); + PrintAddr(Tac.addr); + PrintAddr(Touter.addr); + PrintAddr(Ts1.addr); + PrintAddr(Ts2.addr);*/ + + Get_Temperatures(); + + tone(speakerOut, 2250); + delay (1500); // like ups power on + noTone(speakerOut); + + //!!! + myStepper.setSpeed(40); + Serial.begin(9600); + //!!! + + outString.reserve(200); + //PrintS_and_D(String(freeMemory())); //!!! debug +} + + +void loop(void) { + digitalWrite(SerialTxControl, RS485Receive); + millis_now = millis(); + + //----------------------------- self-test !!! + + digitalWrite(RELAY_HEATPUMP,HIGH); + //delay(300); + digitalWrite(RELAY_HOTSIDE_CIRCLE,HIGH); + //delay(300); + digitalWrite(RELAY_COLDSIDE_CIRCLE,HIGH); + //delay(300); + digitalWrite(RELAY_SUMP_HEATER,HIGH); + /*delay(2000); + digitalWrite(RELAY_HEATPUMP,LOW); + delay(300); + digitalWrite(RELAY_HOTSIDE_CIRCLE,LOW); + delay(300); + digitalWrite(RELAY_COLDSIDE_CIRCLE,LOW); + delay(300); + digitalWrite(RELAY_SUMP_HEATER,LOW); + */ + + // step one revolution in one direction: + Serial.println("clockwise"); + myStepper.step(1000); + delay(500); + + // step one revolution in the other direction: + Serial.println("counterclockwise"); + myStepper.step(-1000); + delay(500); + //----------------------------- self-test END + + + //--------------------async fuction start + if (em_i == 0) { + supply_voltage = ReadVcc(); + } + if (em_i < em_samplesnum) { + sampleI_1 = analogRead(em_pin1); + + // Digital low pass filter extracts the 2.5 V or 1.65 V dc offset, then subtract this - signal is now centered on 0 counts. + offsetI_1 = (offsetI_1 + (sampleI_1-offsetI_1)/1024); + filteredI_1 = sampleI_1 - offsetI_1; + + // Root-mean-square method current + // 1) square current values + sqI_1 = filteredI_1 * filteredI_1; + // 2) sum + sumI_1 += sqI_1; + + em_i += 1; + } else { + em_i = 0; + double I_RATIO = em_calibration *((supply_voltage/1000.0) / (ADC_COUNTS)); + async_Irms_1 = I_RATIO * sqrt(sumI_1 / em_samplesnum); + async_wattage = async_Irms_1*220.0; + + //Reset accumulators + sumI_1 = 0; + + //----------------------------- self-test !!! + /* + PrintS_and_D("Async impl. results 1: "); + PrintS_and_D(async_wattage); // Apparent power + PrintS_and_D(" "); + PrintS_and_D(async_Irms_1); // Irms + PrintS_and_D(" voltage: "); + PrintS_and_D(supply_voltage); + */ + //----------------------------- self-test END + + } + //--------------------async fuction END + + if ( heatpump_state == 1 && async_wattage > c_wattage_max ){ + PrintS_and_D(F("Overload stop.")); + millis_last_heatpump_on = millis_now; + heatpump_state = 0; + digitalWrite(RELAY_HEATPUMP, heatpump_state); + } + + //-------------------buttons processing + #ifdef INPUTS_AS_BUTTONS + z = digitalRead(BUT_LEFT); + i = digitalRead(BUT_RIGHT); + if ( (z == 1) && ( i == 1) ) { + // + } else if ( (z == 1) || ( i == 1) ) { + if ( z == 1 ) { + x = Dec_T(); + } + if ( i == 1 ) { + x = Inc_T(); + } + if (x == 1) { + PrintS_and_D("New aim: " + String(T_setpoint)); + delay(100); + } + } + #endif + //-------------------buttons processing END + + //-------------------display + #if (DISPLAY == 2) || (DISPLAY == 1) + if( ((unsigned long)(millis_now - millis_displ_update) > millis_displ_update_interval ) || (millis_displ_update == 0) ) { + outString = "A:" + String(T_setpoint, 1) + " Real:"; + if (Ttarget.T != -127.0){ + outString += String(Ttarget.T, 1); + } else { + outString += "ERR"; + } + PrintS_and_D(outString, 1); //do not print serial + millis_displ_update = millis_now; + } + #endif + //-------------------display END + + //-------------------check cycle + if( ((unsigned long)(millis_now - millis_prev) > millis_cycle ) || (millis_prev == 0) ) { + millis_prev = millis_now; + Get_Temperatures(); // wdt_reset here due to 85.0'C filtration + SaveSetpointEE(); + + //--------------------important logic + //check T sensors + if ( ( errorcode == ERR_OK ) && ( (Tae.e == 1 && Tae.T == -127 ) || + (Tbe.e == 1 && Tbe.T == -127 ) || + Ttarget.T == -127 || + (Tsump.e == 1 && Tsump.T == -127 ) || + (Tci.e == 1 && Tci.T == -127 ) || + (Tco.e == 1 && Tco.T == -127 ) || + (Thi.e == 1 && Thi.T == -127 ) || + (Tho.e == 1 && Tho.T == -127 ) || + (Tbc.e == 1 && Tbc.T == -127 ) || + (Tac.e == 1 && Tac.T == -127 ) || + (Touter.e == 1 && Touter.T == -127 ) || + (Ts1.e == 1 && Ts1.T == -127 ) || + (Ts2.e == 1 && Ts2.T == -127 ) ) ) { + errorcode = ERR_T_SENSOR; + PrintS_and_D("ERR:T.sens." + String(errorcode)); + } + //auto-clean sensor error on sensor appear + // add 1xor enable here! + if ( ( errorcode == ERR_T_SENSOR ) && ( ( (Tae.e == 1 && Tae.T != -127 ) ||(Tae.e ^1) ) && + ( (Tbe.e == 1 && Tbe.T != -127 ) ||(Tbe.e ^1) ) && + Ttarget.T != -127 && + ( (Tsump.e == 1 && Tsump.T != -127 ) ||(Tsump.e ^1) ) && + ( (Tci.e == 1 && Tci.T != -127 ) ||(Tci.e ^1) ) && + ( (Tco.e == 1 && Tco.T != -127 ) ||(Tco.e ^1) ) && + ( (Thi.e == 1 && Thi.T != -127 ) ||(Thi.e ^1) ) && + ( (Tho.e == 1 && Tho.T != -127 ) ||(Tho.e ^1) ) && + ( (Tbc.e == 1 && Tbc.T != -127 ) ||(Tbc.e ^1) ) && + ( (Tac.e == 1 && Tac.T != -127 ) ||(Tac.e ^1) ) && + ( (Touter.e == 1 && Touter.T != -127 ) ||(Touter.e ^1) ) && + ( (Ts1.e == 1 && Ts1.T != -127 ) ||(Ts1.e ^1) ) && + ( (Ts2.e == 1 && Ts2.T != -127 ) ||(Ts2.e ^1) ) ) ) { + errorcode = ERR_OK; + } + + //process errors + //beep N times error + if ( errorcode != ERR_OK ) { + if ( ((unsigned long)(millis_now - millis_notification) > millis_notification_interval) || millis_notification == 0 ) { + millis_notification = millis_now; + PrintS_and_D("Error:" + String(errorcode)); + for ( i = 0; i < errorcode; i++) { + tone(speakerOut, ERR_HZ); delay (500); + noTone(speakerOut); delay (500); + } + } + } + + //process heatpump sump heater + if (Tsump.e == 1) { + if ( Tsump.T < cT_sump_heat_threshold && sump_heater_state == 0 && Tsump.T != -127) { + sump_heater_state = 1; + } else if (Tsump.T >= cT_sump_heat_threshold && sump_heater_state == 1) { + sump_heater_state = 0; + } else if (Tsump.T == -127) { + sump_heater_state = 0; + } + digitalWrite(RELAY_SUMP_HEATER, sump_heater_state); + } + + + + //main logic + if (_1st_start_sleeped == 0) { + PrintS_and_D("!!!!sleep disabled!!!!"); + _1st_start_sleeped = 1; + if ( (millis_now < poweron_pause) && (_1st_start_sleeped == 0) ) { + PrintS_and_D("Wait: " + String(((poweron_pause-millis_now))/1000) + " s."); + return; + } else { + _1st_start_sleeped = 1; + } + } + + //process_heatpump: + //start if + // (last_on > N or not_started_yet) + // and (no errors) + // and (t hot out < t target + heat_delta_min) + // and (sump t > min'C) + // and (sump t < max'C) + // and (t watertank < target) + // and (t after evaporator > after evaporator min) + // and (t cold in > cold min) + // and (t cold out > cold min) + if ( heatpump_state == 0 && + (((unsigned long)(millis_now - millis_last_heatpump_on) > mincycle_poweroff) || (millis_last_heatpump_on == 0) ) && + //( tr_hot_out < (tr_sens_1 + cT_heat_delta_min) ) && + errorcode == 0 && + ( (Tsump.e == 1 && Tsump.T > cT_sump_min) || (Tsump.e^1)) && + ( (Tsump.e == 1 && Tsump.T < cT_sump_max) || (Tsump.e^1)) && + Ttarget.T < T_setpoint && //was room here, change to advanced algo with room temperature + ( (Tae.e == 1 && Tae.T > cT_after_evaporator_min) || (Tae.e^1)) && + ( (Tbc.e == 1 && Tbc.T < cT_before_condenser_max) || (Tbc.e^1)) && + ( (Tci.e == 1 && Tci.T > cT_cold_min) || (Tci.e^1)) && + ( (Tco.e == 1 && Tco.T > cT_cold_min) || (Tco.e^1)) ) { + PrintS_and_D(F("Start")); + millis_last_heatpump_off = millis_now; + heatpump_state = 1; + } + + //stop if + // ( (last_off > N) and (t watertank > target) ) + if ( heatpump_state == 1 && ((unsigned long)(millis_now - millis_last_heatpump_off) > mincycle_poweron) && (Ttarget.T > T_setpoint)) { + PrintS_and_D(F("Normal stop")); + millis_last_heatpump_on = millis_now; + heatpump_state = 0; + } + + //process_hot_side_pump: + //start if (heatpump_enabled) + //stop if (heatpump_disabled and (t hot out or in < t target + heat delta min) ) + if ( (heatpump_state == 1) && (hotside_circle_state == 0) ) { + PrintS_and_D(F("Hot WP ON")); + hotside_circle_state = 1; + } + + if ( (heatpump_state == 0) && (hotside_circle_state == 1) ) { + if ( (deffered_stop_hotcircle != 0 && ((unsigned long)(millis_now - millis_last_heatpump_on) > deffered_stop_hotcircle) ) ) { + if ( (Tho.e == 1 && Tho.T < (Ttarget.T + cT_heat_delta_min)) || + (Thi.e == 1 && Thi.T < (Ttarget.T + cT_heat_delta_min)) ) { + PrintS_and_D(F("Hot WP OFF 1")); + hotside_circle_state = 0; + } else { + PrintS_and_D(F("Hot WP OFF 2")); + hotside_circle_state = 0; + } + } + } + + //heat if we can, just in case, ex. if lost power + if ( (hotside_circle_state == 0) && + ( Tho.e == 1 && Tho.T > (Ttarget.T + cT_heat_delta_min) ) || + ( Thi.e == 1 && Thi.T > (Ttarget.T + cT_heat_delta_min) ) ) { + PrintS_and_D(F("Hot WP ON")); + hotside_circle_state = 1; + } + + //process_cold_side_pump: + //start if (heatpump_enabled) + //stop if (heatpump_disbled) + if ( (heatpump_state == 1) && (coldside_circle_state == 0) ) { + PrintS_and_D(F("Cold WP ON")); + coldside_circle_state = 1; + } + + if ( (heatpump_state == 0) && (coldside_circle_state == 1) ) { + PrintS_and_D(F("Cold WP OFF")); + coldside_circle_state = 0; + } + + + //protective_cycle: + //stop if + // (error) + // (t hot out > hot out max) + // (sump t > max'C) + // or (t after evaporator < after evaporator min) + // or (t cold in < cold min) + // or (t cold out < cold min) + // + if ( heatpump_state == 1 && + ( errorcode != 0 || + (Tho.e == 1 && Tho.T > cT_hotout_max) || + (Tsump.e == 1 && Tsump.T > cT_sump_max) || + (Tae.e == 1 && Tae.T < cT_after_evaporator_min) || + (Tbc.e == 1 && Tbc.T > cT_before_condenser_max) || + (Tci.e == 1 && Tci.T < cT_cold_min ) || + (Tco.e == 1 && Tco.T < cT_cold_min) ) ) { + PrintS_and_D(F("Protective stop")); + millis_last_heatpump_on = millis_now; + heatpump_state = 0; + digitalWrite(RELAY_HEATPUMP, heatpump_state); + } + + //alive_check_cycle_after_5_mins: + //error if + // or (t cold in - t cold out < t workingok min) + // or (t hot out - t hot in < t workingok min) + // or (sump t < 25'C) + // or wattage too low + /* + if ( heatpump_state == 1 && ((unsigned long)(millis_now - millis_last_heatpump_off) > 300000) ) { + //cold side processing simetimes works incorrectly, after long period of inactivity, due to T inertia on cold tube sensor, commented out + //if ( ( errorcode == ERR_OK ) && ( tr_cold_in - tr_cold_out < cT_workingOK_cold_delta_min ) ) { + // errorcode = ERR_COLD_PUMP; + //} + //if ( ( errorcode == ERR_OK ) && ( Tho.e == 1 && Thi.e == 1 && (Tho.T - Thi.T < cT_workingOK_hot_delta_min )) ) { + // errorcode = ERR_HOT_PUMP; + //} + if ( ( errorcode == ERR_OK ) && ( Tsump.e == 1 && Tsump.T < cT_workingOK_sump_min ) ) { + errorcode = ERR_HEATPUMP; + } + if ( ( errorcode == ERR_OK ) && ( async_wattage < c_workingOK_wattage_min ) ) { + errorcode = ERR_WATTAGE; + } + }*/ + + //disable pump by error + if ( errorcode != ERR_OK ) { + millis_last_heatpump_on = millis_now; + heatpump_state = 0; + digitalWrite(RELAY_HEATPUMP, heatpump_state); + //PrintS_and_D("Error stop: " + String(errorcode, HEX)); + } + + //!!! self-test + ///heatpump_state = 1; + + //write states to relays + digitalWrite (RELAY_HEATPUMP, heatpump_state); + digitalWrite (RELAY_HOTSIDE_CIRCLE, hotside_circle_state); + digitalWrite (RELAY_COLDSIDE_CIRCLE, coldside_circle_state); + digitalWrite (RELAY_SUMP_HEATER, sump_heater_state); + } + + if (RS485Serial.available() > 0) { + //RS485Serial.println("some on serial.."); //!!!debug + #ifdef RS485_HUMAN + if (RS485Serial.available()) { + inChar = RS485Serial.read(); + //RS485Serial.print(inChar); //!!!debug + if ( inChar == 0x1B ) { + skipchars += 3; + inChar = 0x00; + millis_escinput = millis(); + } + if ( skipchars != 0 ) { + millis_charinput = millis(); + //if (millis_escinput + 2 > millis_charinput) + if ((unsigned long)(millis_charinput - millis_escinput) < 16*2 ) { //2 chars for 2400 + if (inChar != 0x7e) { + skipchars -= 1; + } + if (inChar == 0x7e) { + skipchars = 0; + } + if (inChar >= 0x30 && inChar <= 0x35) { + skipchars += 1; + } + inChar = 0x00; + } else { + skipchars = 0; + } + } + + //- RS485_HUMAN: remote commands +,-,G,0x20/?/Enter + switch (inChar) { + case 0x00: + break; + case 0x20: + _PrintHelp(); + break; + case 0x3F: + _PrintHelp(); + break; + case 0x0D: + _PrintHelp(); + break; + case 0x2B: + Inc_T(); + break; + case 0x2D: + Dec_T(); + break; + case 0x47: + PrintStats_Serial(); + break; + case 0x67: + PrintStats_Serial(); + break; + } + } + #endif + + #ifdef RS485_PYTHON + index = 0; + while (RS485Serial.available() > 0) { // Don't read unless you know there is data + if(index < 49) { // size of the array minus 1 + inChar = RS485Serial.read(); // Read a character + inData[index] = inChar; // Store it + index++; // Increment where to write next + inData[index] = '\0'; // clear next symbol, null terminate the string + delayMicroseconds(80); //80 microseconds - the best choice at 9600, "no answer"disappeared + //40(20??) microseconds seems to be good, 9600, 49 symbols + // + } else { //too long message! read it to nowhere + inChar = RS485Serial.read(); + delayMicroseconds(80); + //break; //do not break if symbols!! + } + } + + //!!!debug, be carefull, can cause strange results + + /*if (inData[0] != 0x00) { + RS485Serial.println("-"); + RS485Serial.println(inData); + RS485Serial.println("-"); + }*/ + //or this debug + /* + digitalWrite(SerialTxControl, RS485Transmit); + delay(10); + RS485Serial.println(inData); + RS485Serial.flush(); + RS485Serial.println(index); + */ + + //ALL lines must be terminated with \n! + if ( (inData[0] == hostID) && (inData[1] == devID) ) { + // COMMANDS: + // G (0x47): (G)et main data + // TNN.NN (0x54): set aim (T)emperature + digitalWrite(SerialTxControl, RS485Transmit); + delay(1); + //PrintS_and_D(freeMemory()); + outString = ""; + outString += devID; + outString += hostID; + outString += "A "; //where A is Answer, space after header + + if ( (inData[2] == 0x47 ) ) { + //PrintS_and_D("G"); + //WARNING: this procedure can cause "NO answer" effect if no or few T sensors connected + outString += "{"; + outString += "\"A1\":" + String(T_setpoint); //(A)im (target) + if (Ts1.e == 1) { + outString += "\"TS1\":" + String(Ts1.T); + } + if (Tsump.e == 1) { + outString += ",\"TS\":" + String(Tsump.T); + } + if (Tho.e == 1) { + outString += ",\"THO\":" + String(Tho.T); + } + if (Tae.e == 1) { + outString += ",\"TAE\":" + String(Tae.T); + } + char *outChar=&outString[0]; + RS485Serial.write(outChar); //dirty hack to transfer long string + RS485Serial.flush(); + delay (1); //lot of errors without delay + outString = ""; + if (Tbe.e == 1) { + outString += ",\"TBE\":" + String(Tbe.T); + } + if (Touter.e == 1) { + outString += ",\"TO\":" + String(Touter.T); + } + if (Tco.e == 1) { + outString += ",\"TCO\":" + String(Tco.T); + } + outString += ",\"W1\":" + String(async_wattage); + outString += ",\"RP\":" + String(heatpump_state*RELAY_HEATPUMP); + if (Tci.e == 1) { + outString += ",\"TCI\":" + String(Tci.T); + } + RS485Serial.write(outChar); //dirty hack to transfer long string + RS485Serial.flush(); + delay (1); //lot of errors without delay + outString = ""; + if (Thi.e == 1) { + outString += ",\"THI\":" + String(Thi.T); + } + outString += ",\"RSH\":" + String(sump_heater_state*RELAY_SUMP_HEATER); + outString += ",\"RH\":" + String(hotside_circle_state*RELAY_HOTSIDE_CIRCLE); + outString += ",\"RC\":" + String(coldside_circle_state*RELAY_COLDSIDE_CIRCLE); + if (Tbc.e == 1) { + outString += ",\"TBC\":" + String(Tbc.T); + } + RS485Serial.write(outChar); //dirty hack to transfer long string + RS485Serial.flush(); + delay (1); //lot of errors without delay + outString = ""; + if (Ts2.e == 1) { + outString += ",\"TS2\":" + String(Ts2.T); + } + if (Tac.e == 1) { + outString += ",\"TAC\":" + String(Tac.T); + } + outString += ",\"TT\":" + String(Ttarget.T); + outString += ",\"E1\":" + String(errorcode); + outString += "}"; + } else if ( (inData[2] == 0x54 ) ) { //format NN.NN, text + if ( isDigit(inData[ 3 ]) && isDigit(inData[ 4 ]) && (inData[ 5 ] == 0x2e) && isDigit(inData[ 6 ]) && isDigit(inData[ 7 ]) && ( ! isDigit(inData[ 8 ])) ) { + + tone(speakerOut, 2250); + delay (100); // like ups power on + noTone(speakerOut); + + char * carray = &inData[ 3 ]; + tempdouble = atof(carray); + if (tempdouble > cT_setpoint_max) { + outString += "{\"err\":\"too hot!\"}"; + } else if (tempdouble < 1.0) { + outString += "{\"err\":\"too cold!\"}"; + } else { + T_setpoint = tempdouble; + outString += "{\"result\":\"ok, new value is: "; + outString += String(T_setpoint); + outString += "\"}"; + } + } else { + outString += "{\"err\":\"NaN, format: NN.NN\"}"; + } + } else { + //default, just for example + outString += "{\"result\":\"no_command\"}"; + } + //crc.integer = CRC16.xmodem((uint8_t& *) outString, outString.length()); + //outString += (crc, HEX); + outString += "\n"; + char *outChar=&outString[0]; + RS485Serial.write(outChar); + } + + index = 0; + for (i=0;i<49;i++) { //clear buffer + inData[i]=0; + } + RS485Serial.flush(); + digitalWrite(SerialTxControl, RS485Receive); + delay(1); + #endif + } + +}