/*
	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		//1st tests, support WILL BE DROPPED OUT SOON! small OLEDs support
#define DISPLAY_1602 		2 		//if only 1st character appears: patch 1602 library "inline size_t LiquidCrystal_I2C::write(uint8_t value)"  "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 RS485_NONE		3

#define EEV_SUPPORT
//#define	EEV_ONLY				//NO target, no relays. Oly EEV, Tae, Tbe, current sensor and may be additional T sensors

#define HUMAN_AUTOINFO	10000			//print stats to console

#define WATCHDOG          			//only if u know what to do

//-----------------------TEMPERATURES-----------------------
#define T_SETPOINT_MAX 		45.0;  		//defines max temperature that ordinary user can set
#define T_HOTCIRCLE_DELTA_MIN 	2.0;		//useful for "water heater vith intermediate heat exchanger" scheme, Target == sensor in water, hot side CP will be switched on if "target - hot_out > this option"
#define T_SUMP_MIN 		9.0;		//will not start if T lower
#define T_SUMP_MAX 		110.0;		//will stop if T higher
#define T_SUMP_HEAT_THRESHOLD 	16.0;		//sump heater will be powered on if T lower
#define T_BEFORE_CONDENSER_MAX 	108.0;      	//discharge MAX, system stops if discharge higher
#define T_AFTER_EVAPORATOR_MIN 	-7.0;		//suction MIN, stop if lower, anti-freeze and anti-liquid at suction protection
#define T_COLD_MIN 		-8.0;		//cold loop anti-freeze: stop if inlet or outlet temperature lower
#define T_HOTOUT_MAX 		50.0;		//hot loop: stop if outlet temperature higher than this
#define T_WORKINGOK_SUMP_MIN 	30.0;        	//used in compressor alive checker: need to be not very high to normal start after deep freeze

//-----------------------TUNING OPTIONS -----------------------
#define MAX_WATTS		1170.0		//user for power protection

#define DEFFERED_STOP_HOTCIRCLE	3000000		//50 mins

#define POWERON_PAUSE     	300000    	//5 mins
#define MINCYCLE_POWEROFF 	300000    	//5 mins
#define MINCYCLE_POWERON  	3600000  	//60 mins
#define POWERON_HIGHTIME	10000		//10 sec, defines time after start when power consumption can be 2 times greater than normal

//EEV
#define EEV_MAXPULSES		480

#define EEV_PULSE_FCLOSE_MILLIS	20		//fast close, set waiting pos., close on danger
#define EEV_PULSE_CLOSE_MILLIS	50000		//precise close
#define EEV_PULSE_WOPEN_MILLIS	20		//waiting pos. set
#define EEV_PULSE_FOPEN_MILLIS	1300		//fast open, fast search 
#define EEV_PULSE_OPEN_MILLIS	60000		//precise open

#define EEV_STOP_HOLD		500		//0.1..1sec for Sanhua
#define EEV_CLOSE_ADD_PULSES	8		//read below, close algo
#define EEV_OPEN_AFTER_CLOSE	47		//0 - close to zero position, than close on EEV_CLOSE_ADD_PULSES
						//N - close to zero position, than close on EEV_CLOSE_ADD_PULSES, than open on EEV_OPEN_AFTER_CLOSE pulses
#define EEV_MINWORKPOS		52		//position will be not less during normal work
#define EEV_PRECISE_START	8.6		//T difference, threshold: make slower pulses if less
#define EEV_EMERG_DIFF		2.5		//if dangerous condition: diff =< (target_diff - EEV_EMERG_DIFF) occured, ex: target diff 5.0, emerg. diff 2.0, if calculated nowtime diff <= 3.0 then EEV will be closed
#define EEV_HYSTERESIS		0.6		//must be less than EEV_PRECISE_START, ex: target difference = 4.0, hysteresis = 0.1, when difference in range 4.0..4.1 no EEV pulses will be done; 
#define EEV_CLOSEEVERY		86400000	//86400000: every 24 hours, done while HP is NOT working
#define EEV_TARGET_TEMP_DIFF	4.0		//target difference between Before Evaporator and After Evaporator
//#define EEV_DEBUG				//used to debug during fine tuning, "RS485_HUMAN" only

#define MAGIC     		0x46   		//change if u want to reinit T sensors
//-----------------------USER OPTIONS END -----------------------

//#define INPUTS_AS_INPUTS	2  		//
//#define RS485_MACHINE 	3 		//?? or part of Python?

//-----------------------changelog-----------------------
/*
v1.0:
- Displays support
- 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

v1.1, 15 Apr 2019:
- HUMAN_AUTOINFO time
- EEV_ONLY mode
- EEV_Support
- EEV auto poweron/poweroff every 10 sec
- EEV_recalibration_time to stop HP and recalibrate EEV from zero level ex: every 24 hours

v1.2, 16 Apr 2019:
- "Type F" support

v1.3, 30 Apr 2019:
- EEV changed "overheating" to "delta T"
- EEV algo v1.1

v1.4, 02 Jun 2019:
- minor fixes
- EEV more asyncy
- T options to header

//TODO:
- wclose and fclose to EEV
- liquid ref. protection: start cold circle and sump heater if tsump =< tco/tci+1
- periodical start of hot side circle
- valve_4way
- emergency jumper support
- inputs support
- ? rewite re-init proc from MAGIC to emergency jumper removal at board start
- ? EEV target to EEPROM
- ? list T and other things on screen with buttons
- ? EEV define maximum working position
- ? few devices at same lane for RS485_HUMAN
*/
//-----------------------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.4";

#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

#ifdef RS485_NONE
	char ishuman = 0;
#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.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 	A3
		#define BUT_LEFT  	A2
	#endif
	#ifdef EEV_SUPPORT
		#define EEV_1		2
		#define EEV_2		4
		#define EEV_3		3
		#define EEV_4		5
	#endif
#endif
#ifdef BOARD_TYPE_F
	String hw_version = "Type F v1.x";
	#define RELAY_HEATPUMP        	7
	#define RELAY_COLDSIDE_CIRCLE  	8
	#define LATCH_595 		10
	#define CLK_595 		11
	#define DATA_595 		9
	//595.0: relay 3 RELAY_HOTSIDE_CIRCLE, 595.1: relay 4 RELAY_SUMP_HEATER, 595.2: relay 5 RELAY_4WAY_VALVE, 595.3: uln 6, 595.4: uln 7, 595.5: uln 8, 595.6: uln 9, 595.7: uln 10 
	#ifdef EEV_SUPPORT
		#define EEV_1		5
		#define EEV_2		3
		#define EEV_3		4
		#define EEV_4		2
	#endif
	#ifdef INPUTS_AS_BUTTONS		//not sure
		#define BUT_RIGHT 	A3
		#define BUT_LEFT  	A2
	#endif
	
#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  = 0x41;
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;
double T_EEV_setpoint 			= EEV_TARGET_TEMP_DIFF;  
double T_EEV_dt				= 0.0;		//real, used during run
const double cT_setpoint_max 		= T_SETPOINT_MAX;  
const double cT_hotcircle_delta_min 	= T_HOTCIRCLE_DELTA_MIN;
const double cT_sump_min 		= T_SUMP_MIN;
const double cT_sump_max 		= T_SUMP_MAX;
const double cT_sump_heat_threshold 	= T_SUMP_HEAT_THRESHOLD;
//const double cT_sump_outerT_threshold	= 18.0;    	//?? seems to be not useful
const double cT_before_condenser_max 	= T_BEFORE_CONDENSER_MAX;      
const double cT_after_evaporator_min 	= T_AFTER_EVAPORATOR_MIN;      	// working evaporation presure ~= -10, it is constant due to large evaporator volume     // waterhouse v1: -12 is too high
const double cT_cold_min 		= T_COLD_MIN;
const double cT_hotout_max 		= T_HOTOUT_MAX;
//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 	= T_WORKINGOK_SUMP_MIN;        	//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;     //

bool heatpump_state    		= 0;
bool hotside_circle_state  	= 0;
bool coldside_circle_state 	= 0;
bool 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
bool _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;

int EEV_cur_pos		= 0;	
int EEV_apulses		= 0;		//for async
bool EEV_adonotcare	= 0;		
const unsigned char EEV_steps[4] = {0b1010, 0b0110, 0b0101, 0b1001};
char EEV_cur_step 	= 0;
bool EEV_fast		= 0;		

//main cycle vars
unsigned long millis_prev	= 0;
unsigned long millis_now	= 0;
unsigned long millis_cycle	= 1000;

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;  

unsigned long millis_last_printstats = 0;

unsigned long millis_eev_last_close 	=  0;
unsigned long millis_eev_last_on  	=  0;
unsigned long millis_eev_last_step 	= 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;
int y	= 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)
float 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

//--------------------------- 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;	
	
	/*
	//incorrect way: 
	for (i = 0; i < 8; i++) {	
		if (	(dev_addr[i] != Tae.addr[i]) 	&&
			(dev_addr[i] != Tbe.addr[i]) 	&&
			(dev_addr[i] != Ttarget.addr[i]) &&
			(dev_addr[i] != Tsump.addr[i]) 	&&
			(dev_addr[i] != Tci.addr[i]) 	&&
			(dev_addr[i] != Tco.addr[i]) 	&&
			(dev_addr[i] != Thi.addr[i]) 	&&
			(dev_addr[i] != Tho.addr[i]) 	&&
			(dev_addr[i] != Tbc.addr[i]) 	&&
			(dev_addr[i] != Tac.addr[i]) 	&&
			(dev_addr[i] != Touter.addr[i]) &&
			(dev_addr[i] != Ts1.addr[i]) 	&&
			(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 (String str) {
	#ifdef RS485_HUMAN
		char *outChar=&str[0];  
		digitalWrite(SerialTxControl, RS485Transmit);
		delay(1);    
		RS485Serial.print(outChar);
		RS485Serial.println();
		RS485Serial.flush();
		digitalWrite(SerialTxControl, RS485Receive);  
	#endif
}

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 Print_D2 () {
	#ifdef   DISPLAY_1602
		lcd.setCursor(0, 1);
		lcd.print(outString);
	#endif
}



void _PrintHelp(void) {   
	PrintS( "CHPC, https://github.com/gonzho000/chpc/ fw: " + fw_version  + " board: "+ hw_version);
	PrintS(F("Commands: \n (?) help\n (+) increase aim T\n (-) decrease aim T\n \n"));		
	#ifdef EEV_SUPPORT
		PrintS(F("(<) decrease EEV T diff \n(>) increase EEV T diff"));
	#endif
	PrintS(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("Max!"));	
		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("Min!"));	
		delay (200);
		return 0;
	}
	T_setpoint -= 0.5;
	PrintS_and_D_double(T_setpoint);	
	return 1;
}

int Inc_E (void) {		///!!!!!! unprotected
	T_EEV_setpoint += 0.25;
	PrintS_and_D_double(T_EEV_setpoint);
	return 1;	
}

int Dec_E (void) {		///!!!!!! unprotected
	T_EEV_setpoint -= 0.25;
	PrintS_and_D_double(T_EEV_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); 
		#ifdef EEV_SUPPORT
			outString = "EEV_pos:" + String (EEV_cur_pos);
			RS485Serial.print(outString);
		#endif
		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) { 
			//PrintAddr(to_addr);
			PrintS_and_D(F("Err:EEPROM, reinit!"));
			delay(5000);
		}
	}
}

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;
		}
}

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]) {
			PrintS_and_D(F("Invalid CRC! Remove and insert same sensor!\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("OK! 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
		#ifdef EEV_SUPPORT
			eevise();
		#endif
		tempdouble = s_allTsensors.getTempC(str);
		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);
			}
		} else {
			break;
		}
	}
	return tempdouble;
}

void Get_Temperatures(void) {
	if (Tae.e) 	Tae.T 	= GetT(Tae.addr);
	if (Tbe.e) 	Tbe.T 	= GetT(Tbe.addr);
	if (Ttarget.e) 	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
	//---------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--------  
}

#ifdef EEV_SUPPORT
void on_EEV(){	//1 = do not take care of position
	x = EEV_steps[EEV_cur_step];
	digitalWrite	(EEV_1,	bitRead(x, 0));
	digitalWrite	(EEV_2,	bitRead(x, 1));
	digitalWrite	(EEV_3,	bitRead(x, 2));
	digitalWrite	(EEV_4,	bitRead(x, 3));
}

void off_EEV(){	//1 = do not take care of position
	digitalWrite	(EEV_1,	0);
	digitalWrite	(EEV_2,	0);
	digitalWrite	(EEV_3,	0);
	digitalWrite	(EEV_4,	0);
	//PrintS_and_D("off_EEV");
}

#endif

void halifise(void){
	#ifdef BOARD_TYPE_F
		/*#define LATCH_595 = 10;
		#define CLK_595 = 11;
		#DEFINE DATA_595 = 9;
		//595.0: relay 3 RELAY_HOTSIDE_CIRCLE, 595.1: relay 4 RELAY_SUMP_HEATER, 595.2: relay 5 RELAY_4WAY_VALVE, 595.3: uln 6, 595.4: uln 7, 595.5: uln 8, 595.6: uln 9, 595.7: uln 10 
		*/
		digitalWrite(LATCH_595, 0);
		//7
		digitalWrite(CLK_595, 0);
		digitalWrite(DATA_595, 0);
		digitalWrite(CLK_595, 1);
		__asm__ __volatile__ ("nop\n\t");
		//6
		digitalWrite(CLK_595, 0);
		digitalWrite(DATA_595, 0);
		digitalWrite(CLK_595, 1);
		__asm__ __volatile__ ("nop\n\t");
		//5
		digitalWrite(CLK_595, 0);
		digitalWrite(DATA_595, 0);
		digitalWrite(CLK_595, 1);
		__asm__ __volatile__ ("nop\n\t");
		//4
		digitalWrite(CLK_595, 0);
		digitalWrite(DATA_595, 0);
		digitalWrite(CLK_595, 1);
		__asm__ __volatile__ ("nop\n\t");
		//3
		digitalWrite(CLK_595, 0);
		digitalWrite(DATA_595, 0);	
		digitalWrite(CLK_595, 1);
		__asm__ __volatile__ ("nop\n\t");
		//2
		digitalWrite(CLK_595, 0);
		digitalWrite(DATA_595, 0); //4way valve here
		digitalWrite(CLK_595, 1);
		__asm__ __volatile__ ("nop\n\t");
		//1
		digitalWrite(CLK_595, 0);
		digitalWrite(DATA_595, sump_heater_state);
		digitalWrite(CLK_595, 1);
		__asm__ __volatile__ ("nop\n\t");
		//0
		digitalWrite(CLK_595, 0);
		digitalWrite(DATA_595, hotside_circle_state);
		digitalWrite(CLK_595, 1);
		__asm__ __volatile__ ("nop\n\t");
		digitalWrite(CLK_595, 0);
		//
		digitalWrite(LATCH_595, 1);
		
		
	#endif
	#ifdef BOARD_TYPE_G
		digitalWrite	(RELAY_SUMP_HEATER, sump_heater_state);
		digitalWrite	(RELAY_HOTSIDE_CIRCLE,	hotside_circle_state);
	#endif
}

void eevise(void) {
	if (  	((( EEV_apulses < 0 ) && (EEV_fast == 1))						&&	 ((unsigned long)(millis_now - millis_eev_last_step) > (EEV_PULSE_FCLOSE_MILLIS))  	)	||
			((( EEV_apulses < 0 ) && (EEV_fast == 0))						&&	 ((unsigned long)(millis_now - millis_eev_last_step) > (EEV_PULSE_CLOSE_MILLIS)	) 	)	||
			((( EEV_apulses > 0 ) && 			(EEV_cur_pos < EEV_MINWORKPOS  	))	&&	 ((unsigned long)(millis_now - millis_eev_last_step) > (EEV_PULSE_WOPEN_MILLIS)	) 	)	||
			((( EEV_apulses > 0 ) && (EEV_fast == 1) &&  	(EEV_cur_pos >= EEV_MINWORKPOS	)) 	&&	 ((unsigned long)(millis_now - millis_eev_last_step) > (EEV_PULSE_FOPEN_MILLIS) )	)	||	
			((( EEV_apulses > 0 ) && (EEV_fast == 0) &&  	(EEV_cur_pos >= EEV_MINWORKPOS	)) 	&&	 ((unsigned long)(millis_now - millis_eev_last_step) > (EEV_PULSE_OPEN_MILLIS)  )	)	||
			(millis_eev_last_step == 0)
			) {
			if ( EEV_apulses != 0 ) {
				if ( EEV_apulses > 0 ) {
					if (EEV_cur_pos + 1 <= EEV_MAXPULSES) {
						EEV_cur_pos += 1;
						EEV_cur_step += 1;
						EEV_apulses -= 1;
					} else {
						EEV_apulses = 0;
						//PrintS_and_D("EEmax!");
					}
				}
				if ( EEV_apulses < 0 ) {
					if (	(EEV_cur_pos - 1 >= EEV_MINWORKPOS)	|| (EEV_adonotcare == 1) ) {
						EEV_cur_pos -= 1;
						EEV_cur_step -= 1;
						EEV_apulses += 1;
					} else {
						EEV_apulses = 0;
						//PrintS_and_D("EEmin!");
					}
				}
				if (EEV_cur_step  > 3) EEV_cur_step = 0;
				if (EEV_cur_step  < 0) EEV_cur_step = 3;
				x = EEV_steps[EEV_cur_step];
				digitalWrite	(EEV_1,	bitRead(x, 0));
				digitalWrite	(EEV_2,	bitRead(x, 1));
				digitalWrite	(EEV_3,	bitRead(x, 2));
				digitalWrite	(EEV_4,	bitRead(x, 3));
			}
			if (EEV_cur_pos < 0) { 
				EEV_cur_pos = 0;	
			}
			millis_eev_last_step = millis_now;
			#ifdef EEV_DEBUG 
				PrintS(String(EEV_cur_pos));	
			#endif
	}
}

//--------------------------- functions END

void setup(void) {
	pinMode		(RELAY_HEATPUMP, 	OUTPUT);
	pinMode		(RELAY_COLDSIDE_CIRCLE, OUTPUT);
	digitalWrite	(RELAY_HEATPUMP,	LOW);
	digitalWrite	(RELAY_COLDSIDE_CIRCLE,	LOW);
	
	#ifdef BOARD_TYPE_G
		pinMode		(RELAY_SUMP_HEATER, 	OUTPUT);
		pinMode		(RELAY_HOTSIDE_CIRCLE, 	OUTPUT);
		digitalWrite	(RELAY_SUMP_HEATER,	LOW);
		digitalWrite	(RELAY_HOTSIDE_CIRCLE,	LOW);
	#endif
	#ifdef BOARD_TYPE_F
		pinMode		(LATCH_595, 	OUTPUT);
		pinMode		(CLK_595, 	OUTPUT);
		pinMode		(DATA_595, 	OUTPUT);
		digitalWrite	(LATCH_595, 	LOW);
		digitalWrite	(CLK_595, 	LOW);
		digitalWrite	(DATA_595, 	LOW);
	#endif
	
	#ifdef WATCHDOG
		wdt_disable();
		delay(2000);
	#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(200);
	#ifdef EEV_SUPPORT
		pinMode	(EEV_1, 	OUTPUT);
		pinMode	(EEV_2, 	OUTPUT);
		pinMode	(EEV_3, 	OUTPUT);
		pinMode	(EEV_4, 	OUTPUT);
		off_EEV();
	#endif
	
	pinMode	(em_pin1, INPUT);
	
	//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
		//PrintS_and_D("k:Tae");
		ReadEECheckAddr(Tbe.addr);  //eeprom_addr incremeneted here
		//PrintS_and_D("k:Tbe");
		ReadEECheckAddr(Ttarget.addr);  //eeprom_addr incremeneted here
		//PrintS_and_D("k:Ttarget");
		ReadEECheckAddr(Tsump.addr);  //eeprom_addr incremeneted here
		//PrintS_and_D("k:Tsump");
		ReadEECheckAddr(Tci.addr);  //eeprom_addr incremeneted here
		//PrintS_and_D("k:Tci");
		ReadEECheckAddr(Tco.addr);  //eeprom_addr incremeneted here
		//PrintS_and_D("k:Tco");
		ReadEECheckAddr(Thi.addr);  //eeprom_addr incremeneted here
		//PrintS_and_D("k:Thi");
		ReadEECheckAddr(Tho.addr);  //eeprom_addr incremeneted here
		//PrintS_and_D("k:Tho");
		ReadEECheckAddr(Tbc.addr);  //eeprom_addr incremeneted here
		//PrintS_and_D("k:Tbc");
		ReadEECheckAddr(Tac.addr);  //eeprom_addr incremeneted here
		//PrintS_and_D("k:Tac");
		ReadEECheckAddr(Touter.addr);  //eeprom_addr incremeneted here
		//PrintS_and_D("k:Touter");
		ReadEECheckAddr(Ts1.addr);  //eeprom_addr incremeneted here
		//PrintS_and_D("k:Ts1");
		ReadEECheckAddr(Ts2.addr);  //eeprom_addr incremeneted here
		//PrintS_and_D("k:Ts2");
		
		/*
		//?code duplicated, see ReadEECheckAddr
		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
		
		#ifdef EEV_ONLY
			//z = FindAddr("Ttarget");
			z = 0;
		#else
			z = FindAddr("Ttarget", 1);
		#endif
		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);*/

	#ifdef WATCHDOG
		wdt_enable (WDTO_8S);
	#endif
	
	Get_Temperatures();
	
	tone(speakerOut, 2250);
	delay (1500); // like ups power on
	noTone(speakerOut);
		
	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:
	//!!! write self-test for EEV
	//----------------------------- self-test END
	#ifdef RS485_HUMAN
		if (((unsigned long)(millis_now - millis_last_printstats) > HUMAN_AUTOINFO)   ||   (millis_last_printstats == 0)  ) {
			PrintStats_Serial();
			millis_last_printstats = millis_now;
		}
	#endif
	//--------------------async fuctions 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(String(async_wattage));	           // Apparent power
		PrintS_and_D(" ");
		PrintS_and_D(String(async_Irms_1));	           // Irms
		PrintS_and_D(" voltage: ");
		PrintS_and_D(String(supply_voltage));
		*/
		//----------------------------- self-test END
		
	}
	#ifdef EEV_SUPPORT
		eevise();
	#endif
	//--------------------async fuctions END    
	
	if ( heatpump_state == 1   &&  async_wattage > c_wattage_max  ) {
		if (  ((unsigned long)(millis_now - millis_last_heatpump_off) > POWERON_HIGHTIME )	||	(async_wattage > c_wattage_max*2)) {
			#ifdef RS485_HUMAN 
				PrintS(("Overload." + String(async_wattage)));
			#endif
			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) ) {
				#ifndef EEV_ONLY
					if ( z == 1 ) {
						x = Dec_T();
					} 
					if ( i == 1 ) {
						x = Inc_T();
					}
					if (x == 1) {
						PrintS_and_D("New aim: " + String(T_setpoint));
						delay(300);
					}
				#else
					if ( z == 1 ) {
						T_EEV_setpoint -= 0.25;
					}
					if ( i == 1 ) {
						T_EEV_setpoint += 0.25;
					}
					PrintS_and_D("New EEV Td: " + String(T_EEV_setpoint));
					delay(300);
				#endif
			}
			
	#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) ) {
			//EEV_ONLY SUPPORT!!!!!!!
			#ifndef EEV_ONLY
				outString = "A:" + String(T_setpoint, 1) + " Real:";
				if (Ttarget.e == 1) {
					outString +=  String(Ttarget.T, 1);
				} else {
					outString +=  "ERR";
				}
				PrintS_and_D(outString, 1);    //do not print serial
				
				//2
				//#ifdef EEV_SUPPORT
				//	outString = "Tbe:" + String(Tbe.T, 1) + "Tae:" + String(Tbe.T, 1);
				//	Print_D2();
				//#endif
				if (Touter.e == 1){
					outString = "Outer:" + String(Touter.T, 1);
					Print_D2();
				}
			#else
				outString = "be:";
				if (Tbe.e == 1){
					outString += String(Tbe.T, 1);
				}
				outString += " ae:";
				if (Tae.e == 1){
					outString += String(Tae.T, 1);
				}
				PrintS_and_D(outString, 1);    //do not print serial
				
			#endif
			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.e == 1 && 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;
										#ifdef RS485_HUMAN 
											PrintS_and_D("ERR:T.sens." + String(errorcode)); 
										#endif
		}
		//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.e == 1 && Ttarget.T != -127) ||(Ttarget.e	^1) )	&&
							( (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;
				#ifdef RS485_HUMAN 
					PrintS_and_D("Error:" + String(errorcode));
				#endif
				for ( i = 0; i < errorcode; i++) {
					tone(speakerOut, ERR_HZ);  	delay (500);      
					noTone(speakerOut);      	delay (500);
				}
			}
		}

		//-------------- EEV cycle
		#ifdef EEV_SUPPORT
			/*
			//v1 algo
			if ( EEV_apulses == 0 )	{
				if ( 	((async_wattage < c_workingOK_wattage_min) && ((unsigned long)(millis_now - millis_eev_last_close) > EEV_CLOSEEVERY))		||  millis_eev_last_close == 0  ){
					PrintS_and_D("EEV: FULL closing");//!!!
					if ( millis_eev_last_close != 0 ) {
						EEV_apulses =  -(EEV_cur_pos + EEV_CLOSE_ADD_PULSES);
					} else {
						EEV_apulses =  -(EEV_MAXPULSES + EEV_CLOSE_ADD_PULSES);
					}
					EEV_adonotcare = 1;
					EEV_fast = 1;
					//delay(EEV_STOP_HOLD);
					millis_eev_last_close = millis_now;
				} else if (errorcode != 0 || async_wattage <= c_workingOK_wattage_min) {		//err or sleep
					PrintS_and_D("EEV: err or sleep");//!!!
					if (EEV_cur_pos <= 0 && EEV_OPEN_AFTER_CLOSE != 0) {				//set waiting pos
						EEV_apulses = +EEV_OPEN_AFTER_CLOSE;
						EEV_adonotcare = 0;
						EEV_fast = 1;
					}
					if (EEV_cur_pos > 0  && EEV_cur_pos != EEV_OPEN_AFTER_CLOSE && EEV_cur_pos <= EEV_MAXPULSES) {	//waiting pos. set
						PrintS_and_D("EEV: close");//!!!
						EEV_apulses =  -(EEV_cur_pos + EEV_CLOSE_ADD_PULSES);
						EEV_adonotcare = 1;
						EEV_fast = 1;
					}
				} else if (errorcode == 0 && async_wattage > c_workingOK_wattage_min) {
					T_EEV_dt = Tae.T - Tbe.T;
					PrintS_and_D("EEV: driving " + String(T_EEV_dt));//!!!
					if (EEV_cur_pos <= 0){
						PrintS_and_D("EEV: full close protection");	
						if (EEV_OPEN_AFTER_CLOSE != 0) {				//full close protection
							EEV_apulses = +EEV_OPEN_AFTER_CLOSE;
							EEV_adonotcare = 0;
							EEV_fast = 1;
						}
					} else if (EEV_cur_pos > 0) {
						if (T_EEV_dt  < (T_EEV_setpoint - EEV_EMERG_DIFF) ) {				//emerg!
							PrintS_and_D("EEV: emergency closing!");//!!!
							EEV_apulses = -EEV_EMERG_STEPS;
							EEV_adonotcare = 0;
							EEV_fast = 1;
						} else if (T_EEV_dt  < T_EEV_setpoint) {					//too
							PrintS_and_D("EEV: closing");//!!!
							//EEV_apulses = -EEV_NONPRECISE_STEPS;
							EEV_apulses = -1;
							EEV_adonotcare = 0;
							EEV_fast = 0;
						} else if (T_EEV_dt  > T_EEV_setpoint + EEV_HYSTERESIS + EEV_PRECISE_START) {	//very
							PrintS_and_D("EEV: fast opening");//!!!
							//EEV_apulses =  +EEV_NONPRECISE_STEPS;
							EEV_apulses =  +1;
							EEV_adonotcare = 0;
							EEV_fast = 1;
						} else if (T_EEV_dt > T_EEV_setpoint + EEV_HYSTERESIS) {			//too
							PrintS_and_D("EEV: opening");//!!!
							EEV_apulses =  +1;
							EEV_adonotcare = 0;
							EEV_fast = 0;
						} else if (T_EEV_dt  > T_EEV_setpoint) {					//ok
							PrintS_and_D("EEV: OK");//!!!
							//
						}
					}
					off_EEV();
				} 
				
			}
			*/
			//v1.1 algo
			if ( errorcode == 0 && async_wattage > c_workingOK_wattage_min && EEV_cur_pos > 0 )	{
				T_EEV_dt = Tae.T - Tbe.T;
				#ifdef EEV_DEBUG
					PrintS("EEV Td: " + String(T_EEV_dt));
				#endif
				if ( EEV_apulses >= 0 && EEV_cur_pos >= EEV_MINWORKPOS)	{
					if (T_EEV_dt  < (T_EEV_setpoint - EEV_EMERG_DIFF) ) {				//emerg!
						#ifdef EEV_DEBUG
							PrintS(F("EEV: 1 emergency closing!"));
						#endif
						EEV_apulses = -1;
						EEV_adonotcare = 0;
						EEV_fast = 1;
					} else if (T_EEV_dt  < T_EEV_setpoint) {					//too
						#ifdef EEV_DEBUG
							PrintS(F("EEV: 2 closing"));
						#endif
						//EEV_apulses = -EEV_NONPRECISE_STEPS;
						EEV_apulses = -1;
						EEV_adonotcare = 0;
						EEV_fast = 0;
					}
					//faster open when needed, condition copypasted (see EEV_apulses <= 0)
					if (T_EEV_dt  > T_EEV_setpoint + EEV_HYSTERESIS + EEV_PRECISE_START) {		//very
						#ifdef EEV_DEBUG
							PrintS(F("EEV: 3 enforce faster opening"));
						#endif
						//EEV_apulses =  +EEV_NONPRECISE_STEPS;
						//EEV_apulses =  +1;
						EEV_adonotcare = 0;
						EEV_fast = 1;
					}
				}
				if ( EEV_apulses <= 0 )	{
					if (T_EEV_dt  > T_EEV_setpoint + EEV_HYSTERESIS + EEV_PRECISE_START) {		//very
						#ifdef EEV_DEBUG
							PrintS(F("EEV: 4 fast opening"));
						#endif
						//EEV_apulses =  +EEV_NONPRECISE_STEPS;
						EEV_apulses =  +1;
						EEV_adonotcare = 0;
						EEV_fast = 1;
					} else if (T_EEV_dt > T_EEV_setpoint + EEV_HYSTERESIS) {			//too
						#ifdef EEV_DEBUG
							PrintS(F("EEV: 5 opening"));
						#endif
						EEV_apulses =  +1;
						EEV_adonotcare = 0;
						EEV_fast = 0;
					} else if (T_EEV_dt  > T_EEV_setpoint) {					//ok
						#ifdef EEV_DEBUG
							PrintS(F("EEV: 6 OK"));
						#endif
						//
					}
					//faster closing when needed, condition copypasted (see EEV_apulses >= 0)
					if (T_EEV_dt  < (T_EEV_setpoint - EEV_EMERG_DIFF) ) {				//emerg!
						#ifdef EEV_DEBUG
							PrintS(F("EEV: 7 enforce faster closing!"));
						#endif
						//EEV_apulses = -EEV_EMERG_STEPS;
						EEV_adonotcare = 0;
						EEV_fast = 1;
					}
				}
				off_EEV();
			}
			if ( EEV_apulses == 0 )	{
				if ( 	((async_wattage < c_workingOK_wattage_min) && ((unsigned long)(millis_now - millis_eev_last_close) > EEV_CLOSEEVERY))		||  millis_eev_last_close == 0  ){	//close every 24h by default
					#ifdef EEV_DEBUG
						PrintS(F("EEV: 10 FULL closing"));
					#endif
					if ( millis_eev_last_close != 0 ) {
						EEV_apulses =  -(EEV_cur_pos + EEV_CLOSE_ADD_PULSES);
					} else {
						EEV_apulses =  -(EEV_MAXPULSES + EEV_CLOSE_ADD_PULSES);
					}
					EEV_adonotcare = 1;
					EEV_fast = 1;
					//delay(EEV_STOP_HOLD);
					millis_eev_last_close = millis_now;
				} else if (errorcode != 0 || async_wattage < c_workingOK_wattage_min) {		//err or sleep
					if (EEV_cur_pos > 0  && EEV_cur_pos > EEV_OPEN_AFTER_CLOSE) {	//waiting pos. set
						#ifdef EEV_DEBUG
							PrintS(F("EEV: 11 close before open"));
						#endif
						EEV_apulses =  -(EEV_cur_pos + EEV_CLOSE_ADD_PULSES);
						EEV_adonotcare = 1;
						EEV_fast = 1;
					}
				}
				off_EEV();
			}
			if ( EEV_apulses == 0 && async_wattage < c_workingOK_wattage_min && EEV_cur_pos < EEV_OPEN_AFTER_CLOSE) {
				#ifdef EEV_DEBUG
					PrintS(F("EEV: 12 full close protection"));
				#endif
				if (EEV_OPEN_AFTER_CLOSE != 0) {				//full close protection
					EEV_apulses = EEV_OPEN_AFTER_CLOSE - EEV_cur_pos;
					EEV_adonotcare = 0;
					EEV_fast = 1;
				}
				off_EEV();
			}
			if ( EEV_apulses == 0 && async_wattage >= c_workingOK_wattage_min && EEV_cur_pos < EEV_MINWORKPOS) {
				#ifdef EEV_DEBUG
					PrintS(F("EEV: 13 open to work"));	
				#endif
				if (EEV_MINWORKPOS != 0) {					//full close protection
					EEV_apulses = EEV_MINWORKPOS - EEV_cur_pos;
					EEV_adonotcare = 0;
					EEV_fast = 1;
				}
				off_EEV();
			}
			if (	((unsigned long)(millis_now - millis_eev_last_on) > 10000)  ||  millis_eev_last_on == 0 ) {
				//PrintS_and_D("EEV: ON/OFF");
				on_EEV();
				//delay(30);
				//off_EEV();	//off_EEV called everywhere takes care of it
				millis_eev_last_on  =  millis_now;
			}
		#endif
		//-------------- EEV cycle END

		#ifndef EEV_ONLY
			//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;
				}
				halifise();
			}
	
	
	
			//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_hotcircle_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))	&&
				//t1_sump > t2_cold_in   && ???
				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))	) {
					#ifdef RS485_HUMAN
						PrintS(F("Start"));
					#endif
					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)) {
				#ifdef RS485_HUMAN 
					PrintS(F("Normal stop"));
				#endif
				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)  ) {    
				#ifdef RS485_HUMAN 
					PrintS(F("Hot WP ON"));
				#endif
				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_hotcircle_delta_min))	||
						(Thi.e == 1 && Thi.T < (Ttarget.T + cT_hotcircle_delta_min))	) {
						#ifdef RS485_HUMAN 
							PrintS(F("Hot WP OFF 1"));
						#endif
						hotside_circle_state  = 0;
					} else {
						#ifdef RS485_HUMAN 
							PrintS(F("Hot WP OFF 2"));
						#endif
						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_hotcircle_delta_min)  )  	||
				( Thi.e == 1 && Thi.T > (Ttarget.T + cT_hotcircle_delta_min)  )  ) {
					#ifdef RS485_HUMAN 
						PrintS(F("Hot WP ON"));
					#endif
					hotside_circle_state  = 1;
			}
			
			//process_cold_side_pump:
			//start if (heatpump_enabled)
			//stop if (heatpump_disbled)
			if (  (heatpump_state == 1)   &&  (coldside_circle_state  == 0)  ) {
				#ifdef RS485_HUMAN 
					PrintS(F("Cold WP ON"));
				#endif
				coldside_circle_state  = 1;
			}
			
			if (  (heatpump_state == 0)   &&  (coldside_circle_state  == 1)  ) {
				#ifdef RS485_HUMAN 
					PrintS(F("Cold WP OFF"));
				#endif
				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) )     ) {
						#ifdef RS485_HUMAN 
							PrintS(F("Protective stop"));
						#endif
						millis_last_heatpump_on = millis_now;
						heatpump_state = 0;
						digitalWrite(RELAY_HEATPUMP, heatpump_state);
			}
			
			//alive_check_cycle_after_5_mins:
			//error if
			//v1.3: not error, just poweroff all
			//      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;
					millis_last_heatpump_on = millis_now;
					heatpump_state = 0;
				}
				if ( ( errorcode == ERR_OK )     &&   ( async_wattage < c_workingOK_wattage_min )  ) {
					//errorcode = ERR_WATTAGE;
					millis_last_heatpump_on = millis_now;
					heatpump_state = 0;
				}
				digitalWrite(RELAY_HEATPUMP, heatpump_state);
			}
				
			//disable pump by error
			if ( errorcode != ERR_OK ) {
				millis_last_heatpump_on = millis_now;
				heatpump_state = 0;
				digitalWrite(RELAY_HEATPUMP, heatpump_state);
				#ifdef RS485_HUMAN 
					PrintS("Error stop: " + String(errorcode, HEX));
				#endif
			}
			
			//!!! self-test
			///heatpump_state = 1;
			
			//write states to relays
			digitalWrite	(RELAY_HEATPUMP, 	heatpump_state);
			digitalWrite	(RELAY_COLDSIDE_CIRCLE,	coldside_circle_state);
			halifise();
		#endif
	}
	
	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 0x3C:
						Dec_E();
						break;
					case 0x3E:
						Inc_E();
						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 += "\"E1\":" + String(errorcode);  
					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);
					#ifndef EEV_ONLY
						outString += ",\"A1\":" + String(T_setpoint);  //(A)im (target)
						outString += ",\"RP\":" + String(heatpump_state*RELAY_HEATPUMP);  
					#endif
					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);
					}
					#ifndef EEV_ONLY
						outString += ",\"RSH\":" + String(sump_heater_state*3);                  
						outString += ",\"RH\":" + String(hotside_circle_state*2);                  
						outString += ",\"RC\":" + String(coldside_circle_state*1);  
					#endif
					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);
					}
					if (Ttarget.e == 1) {
						outString += ",\"TT\":" + String(Ttarget.T);                                
					}
					#ifdef EEV_SUPPORT
						outString += ",\"EEVP\":" + String(EEV_cur_pos);
						outString += ",\"EEVA\":" + String(T_EEV_setpoint);
					#endif
					outString += "}";
				} else if ( (inData[2] == 0x54 ) || (inData[2] == 0x45 )) {  //(T)arget or (E)EV target 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 (inData[2] == 0x54 ){
							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 += "\"}";
							}
						}
						if (inData[2] == 0x45 ) {
							if (tempdouble > 10.0) {		//!!!!!!! hardcode !!!
								outString += "{\"err\":\"too hot!\"}";
							} else if (tempdouble < 0.1) {		//!!!!!!! hardcode !!!
								outString += "{\"err\":\"too cold!\"}";
							} else {
								T_EEV_setpoint = tempdouble;
								outString += "{\"result\":\"ok, new EEV value is: ";
								outString += String(T_EEV_setpoint);
								outString += "\"}";
							}
						}
					} else {
						outString += "{\"err\":\"NaN, format: NN.NN\"}";
					}
				} else {
					//default, just for example
					outString += "{\"err\":\"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
	}
    
}