00001
00022 #include <stdlib.h>
00023 #include <stdio.h>
00024 #include <errno.h>
00025 #include <math.h>
00026
00027 #include "house_a.h"
00028 #include "waterheater.h"
00029
00030 #define TSTAT_PRECISION 0.01
00031 #define HEIGHT_PRECISION 0.01
00032
00034
00036 CLASS* waterheater::oclass = NULL;
00037 CLASS* waterheater::pclass = NULL;
00038
00041 waterheater::waterheater(MODULE *module) : residential_enduse(module){
00042
00043 if (oclass==NULL)
00044 {
00045 pclass = residential_enduse::oclass;
00046
00047 oclass = gl_register_class(module,"waterheater",sizeof(waterheater),PC_PRETOPDOWN|PC_BOTTOMUP|PC_POSTTOPDOWN);
00048 if (oclass==NULL)
00049 GL_THROW("unable to register object class implemented by %s",__FILE__);
00050
00051
00052 if (gl_publish_variable(oclass,
00053 PT_INHERIT, "residential_enduse",
00054 PT_double,"tank_volume[gal]",PADDR(tank_volume), PT_DESCRIPTION, "the volume of water in the tank when it is full",
00055 PT_double,"tank_UA[Btu.h/degF]",PADDR(tank_UA), PT_DESCRIPTION, "the UA of the tank (surface area divided by R-value)",
00056 PT_double,"tank_diameter[ft]",PADDR(tank_diameter), PT_DESCRIPTION, "the diameter of the water heater tank",
00057 PT_double,"water_demand[gpm]",PADDR(water_demand), PT_DESCRIPTION, "the hot water draw from the water heater",
00058 PT_double,"heating_element_capacity[kW]",PADDR(heating_element_capacity), PT_DESCRIPTION, "the power of the heating element",
00059 PT_double,"inlet_water_temperature[degF]",PADDR(Tinlet), PT_DESCRIPTION, "the inlet temperature of the water tank",
00060 PT_enumeration,"heat_mode",PADDR(heat_mode), PT_DESCRIPTION, "the energy source for heating the water heater",
00061 PT_KEYWORD,"ELECTRIC",ELECTRIC,
00062 PT_KEYWORD,"GASHEAT",GASHEAT,
00063 PT_enumeration,"location",PADDR(location), PT_DESCRIPTION, "whether the water heater is inside or outside",
00064 PT_KEYWORD,"INSIDE",INSIDE,
00065 PT_KEYWORD,"GARAGE",GARAGE,
00066 PT_double,"tank_setpoint[degF]",PADDR(tank_setpoint), PT_DESCRIPTION, "the temperature around which the water heater will heat its contents",
00067 PT_double,"thermostat_deadband[degF]",PADDR(thermostat_deadband), PT_DESCRIPTION, "the degree to heat the water tank, when needed",
00068 PT_double,"temperature[degF]",PADDR(Tw), PT_DESCRIPTION, "the outlet temperature of the water tank",
00069 PT_double,"height[ft]",PADDR(h), PT_DESCRIPTION, "the height of the hot water column within the water tank",
00070 PT_double,"demand[gpm]",PADDR(water_demand), PT_DESCRIPTION, "the water consumption",
00071 PT_double,"actual_load[kW]",PADDR(actual_load),PT_DESCRIPTION, "the actual load based on the current voltage across the coils",
00072 NULL)<1)
00073 GL_THROW("unable to publish properties in %s",__FILE__);
00074 }
00075 }
00076
00077 waterheater::~waterheater()
00078 {
00079 }
00080
00081 int waterheater::create()
00082 {
00083 int res = residential_enduse::create();
00084
00085
00086 tank_volume = 50.0;
00087 tank_UA = 0.0;
00088 tank_diameter = 1.5;
00089 Tinlet = 60.0;
00090 water_demand = 0.0;
00091 heating_element_capacity = 0.0;
00092 heat_needed = FALSE;
00093 location = GARAGE;
00094 heat_mode = ELECTRIC;
00095 tank_setpoint = 0.0;
00096 thermostat_deadband = 0.0;
00097
00098 Tw = 0.0;
00099
00100
00101 location = gl_random_bernoulli(0.80) ? GARAGE : INSIDE;
00102
00103
00104 tank_setpoint = clip(gl_random_normal(130,10),100,160);
00105 thermostat_deadband = clip(gl_random_normal(5, 1),1,10);
00106
00107
00108 tank_setpoint = gl_random_normal(125,5);
00109 if (tank_setpoint<90) tank_setpoint = 90;
00110 if (tank_setpoint>160) tank_setpoint = 160;
00111
00112
00113 thermostat_deadband = fabs(gl_random_normal(2,1))+1;
00114 if (thermostat_deadband>10)
00115 thermostat_deadband = 10;
00116
00117 tank_UA = clip(gl_random_normal(2.0, 0.20),0.1,10) * tank_volume/50;
00118 if(tank_UA <= 1.0)
00119 tank_UA = 2.0;
00120
00121
00122 load.name = oclass->name;
00123
00124 load.breaker_amps = 30;
00125 load.config = EUC_IS220;
00126 load.power_fraction = 0.0;
00127 load.impedance_fraction = 1.0;
00128 load.heatgain_fraction = 0.0;
00129 return res;
00130
00131 }
00132
00135 int waterheater::init(OBJECT *parent)
00136 {
00137 OBJECT *hdr = OBJECTHDR(this);
00138 hdr->flags |= OF_SKIPSAFE;
00139
00140 static double sTair = 74;
00141 static double sTout = 68;
00142
00143 if(parent){
00144 pTair = gl_get_double_by_name(parent, "air_temperature");
00145 pTout = gl_get_double_by_name(parent, "outdoor_temperature");
00146 }
00147
00148 if(pTair == 0){
00149 pTair = &sTair;
00150 gl_warning("waterheater parent lacks \'air_temperature\' property, using default");
00151 }
00152 if(pTout == 0){
00153 pTout = &sTout;
00154 gl_warning("waterheater parent lacks \'outside_temperature\' property, using default");
00155 }
00156
00157
00158
00159 if(tank_volume <= 0.0){
00160
00161 if (tank_volume > 100.0)
00162 tank_volume = 100.0;
00163 else if (tank_volume < 20.0)
00164 tank_volume = 20.0;
00165 } else {
00166 if (tank_volume > 100.0 || tank_volume < 20.0){
00167 gl_error("watertank volume of %f outside the volume bounds of 20 to 100 gallons.", tank_volume);
00168
00169
00170
00171 }
00172 }
00173
00174 if (tank_setpoint<90 || tank_setpoint>160)
00175 gl_error("watertank thermostat is set to %f and is outside the bounds of 90 to 160 degrees Fahrenheit (32.2 - 71.1 Celsius).", tank_setpoint);
00176
00177
00178
00179
00180 if (thermostat_deadband>10 || thermostat_deadband < 0.0)
00181 GL_THROW("watertank deadband of %f is outside accepted bounds of 0 to 10 degrees (5.6 degC).", thermostat_deadband);
00182
00183
00184 if (tank_UA <= 0.0)
00185 GL_THROW("Tank UA value is negative.");
00186
00187
00188
00189 if (heating_element_capacity <= 0.0)
00190 {
00191 if (tank_volume >= 50)
00192 heating_element_capacity = 4.500;
00193 else
00194 {
00195
00196 double randVal = gl_random_uniform(0,1);
00197 if (randVal < 0.33)
00198 heating_element_capacity = 3.200;
00199 else if (randVal < 0.67)
00200 heating_element_capacity = 3.500;
00201 else
00202 heating_element_capacity = 4.500;
00203 }
00204 }
00205
00206
00207
00208 if(Tw < Tinlet){
00209 Tw = gl_random_uniform(tank_setpoint - thermostat_deadband, tank_setpoint + thermostat_deadband);
00210 }
00211 current_model = NONE;
00212 load_state = STABLE;
00213
00214
00215 Tset_curtail = tank_setpoint - thermostat_deadband/2 - 10;
00216
00217
00218 area = (pi * pow(tank_diameter,2))/4;
00219 height = tank_volume/GALPCF / area;
00220 Cw = tank_volume/GALPCF * RHOWATER * Cp;
00221
00222 h = height;
00223
00224
00225 if(h == 0){
00226
00227 Tlower = Tinlet;
00228 Tupper = Tinlet + TSTAT_PRECISION;
00229 } else {
00230 Tlower = Tinlet;
00231 }
00232
00233
00234 switch(shape.type){
00235 case MT_UNKNOWN:
00236
00237 break;
00238 case MT_ANALOG:
00239 if(shape.params.analog.energy == 0.0){
00240 GL_THROW("waterheater does not support fixed energy shaping");
00241
00242
00243
00244
00245
00246
00247
00248 } else if (shape.params.analog.power == 0){
00249
00250
00251
00252
00253
00254
00255 water_demand = gl_get_loadshape_value(&shape) / 2.4449;
00256 } else {
00257 water_demand = gl_get_loadshape_value(&shape);
00258 }
00259 break;
00260 case MT_PULSED:
00261
00262
00263 if(shape.params.pulsed.pulsetype == MPT_TIME){
00264 ;
00265 } else if(shape.params.pulsed.pulsetype == MPT_POWER){
00266 ;
00267 water_demand = gl_get_loadshape_value(&shape) / 2.4449;
00268 }
00269 break;
00270 case MT_MODULATED:
00271 if(shape.params.modulated.pulsetype == MPT_TIME){
00272 GL_THROW("Amplitude modulated water usage is nonsensical for residential water heaters");
00273
00274
00275
00276
00277 } else if(shape.params.modulated.pulsetype == MPT_POWER){
00278
00279
00280 water_demand = gl_get_loadshape_value(&shape) / 2.4449;
00281 }
00282 break;
00283 case MT_QUEUED:
00284 if(shape.params.queued.pulsetype == MPT_TIME){
00285 ;
00286 } else if(shape.params.queued.pulsetype == MPT_POWER){
00287 ;
00288 water_demand = gl_get_loadshape_value(&shape) / 2.4449;
00289 }
00290 break;
00291 default:
00292 GL_THROW("waterheater load shape has an unknown state!");
00293 break;
00294 }
00295 return residential_enduse::init(parent);
00296 }
00297
00298 int waterheater::isa(char *classname)
00299 {
00300 return (strcmp(classname,"waterheater")==0 || residential_enduse::isa(classname));
00301 }
00302
00303
00304 void waterheater::thermostat(TIMESTAMP t0, TIMESTAMP t1){
00305 Ton = tank_setpoint - thermostat_deadband/2;
00306 Toff = tank_setpoint + thermostat_deadband/2;
00307
00308 switch(tank_state()){
00309 case FULL:
00310 if(Tw-TSTAT_PRECISION < Ton){
00311 heat_needed = TRUE;
00312 } else if (Tw+TSTAT_PRECISION > Toff){
00313 heat_needed = FALSE;
00314 } else {
00315 ;
00316 }
00317 break;
00318 case PARTIAL:
00319 case EMPTY:
00320 heat_needed = TRUE;
00321 break;
00322 default:
00323 GL_THROW("waterheater thermostat() detected that the water heater tank is in an unknown state");
00324 }
00325
00326 }
00327
00332 TIMESTAMP waterheater::presync(TIMESTAMP t0, TIMESTAMP t1){
00333
00334 double nHours = (gl_tohours(t1) - gl_tohours(t0))/TS_SECOND;
00335 OBJECT *my = OBJECTHDR(this);
00336
00337
00338 update_T_and_or_h(nHours);
00339
00340 if(Tw > 212.0){
00341
00342 gl_warning("waterheater:%i is boiling", my->id);
00343
00344
00345
00346
00347
00348 }
00349
00350
00351 switch(shape.type){
00352 case MT_UNKNOWN:
00353
00354 break;
00355 case MT_ANALOG:
00356 if(shape.params.analog.energy == 0.0){
00357 GL_THROW("waterheater does not support fixed energy shaping");
00358
00359
00360
00361
00362
00363
00364
00365 } else if (shape.params.analog.power == 0){
00366
00367
00368
00369
00370
00371
00372 water_demand = gl_get_loadshape_value(&shape) / 2.4449;
00373 } else {
00374 water_demand = gl_get_loadshape_value(&shape);
00375 }
00376 break;
00377 case MT_PULSED:
00378
00379
00380 if(shape.params.pulsed.pulsetype == MPT_TIME){
00381 ;
00382 } else if(shape.params.pulsed.pulsetype == MPT_POWER){
00383 ;
00384 water_demand = gl_get_loadshape_value(&shape) / 2.4449;
00385 }
00386 break;
00387 case MT_MODULATED:
00388 if(shape.params.modulated.pulsetype == MPT_TIME){
00389 GL_THROW("Amplitude modulated water usage is nonsensical for residential water heaters");
00390
00391
00392
00393
00394 } else if(shape.params.modulated.pulsetype == MPT_POWER){
00395
00396
00397 water_demand = gl_get_loadshape_value(&shape) / 2.4449;
00398 }
00399 break;
00400 case MT_QUEUED:
00401 if(shape.params.queued.pulsetype == MPT_TIME){
00402 ;
00403 } else if(shape.params.queued.pulsetype == MPT_POWER){
00404 ;
00405 water_demand = gl_get_loadshape_value(&shape) / 2.4449;
00406 }
00407 break;
00408 default:
00409 GL_THROW("waterheater load shape has an unknown state!");
00410 break;
00411 }
00412
00413 return TS_NEVER;
00414
00415 }
00416
00420 TIMESTAMP waterheater::sync(TIMESTAMP t0, TIMESTAMP t1)
00421 {
00422 double internal_gain = 0.0;
00423 double nHours = (gl_tohours(t1) - gl_tohours(t0))/TS_SECOND;
00424 double Tamb = get_Tambient(location);
00425
00426
00427
00428 if(override == OV_ON){
00429 heat_needed = TRUE;
00430 } else if(override == OV_OFF){
00431 heat_needed = FALSE;
00432 }
00433
00434 if(Tw > 212.0 - thermostat_deadband){
00435 heat_needed = FALSE;
00436 }
00437
00438 if (heat_needed == TRUE){
00439 load.total = heating_element_capacity * (heat_mode == GASHEAT ? 0.01 : 1.0);
00440 actual_load = load.total.Re() * load.voltage_factor;
00441 } else {
00442 load.total = 0.0;
00443 actual_load = 0.0;
00444 }
00445
00446 TIMESTAMP t2 = residential_enduse::sync(t0,t1);
00447
00448
00449
00450
00451
00452 set_time_to_transition();
00453
00454
00455 if (location == INSIDE){
00456 if(this->current_model == ONENODE){
00457 internal_gain = tank_UA * (Tw - get_Tambient(location));
00458 } else if(this->current_model == TWONODE){
00459 internal_gain = tank_UA * (Tw - Tamb) * h / height;
00460 internal_gain += tank_UA * (Tlower - Tamb) * (1 - h / height);
00461 }
00462 } else {
00463 internal_gain = 0;
00464 }
00465
00466
00467 load.power = load.total * load.power_fraction;
00468 load.admittance = load.total * load.impedance_fraction;
00469 load.current = load.total * load.current_fraction;
00470 load.heatgain = internal_gain;
00471
00472
00473
00474 if(override == OV_NORMAL){
00475 if (time_to_transition >= (1.0/3600.0))
00476 {
00477 TIMESTAMP t_to_trans = (t1+time_to_transition*3600.0/TS_SECOND);
00478 return -(t_to_trans);
00479 }
00480
00481 else
00482 return TS_NEVER;
00483 } else {
00484 return TS_NEVER;
00485 }
00486 }
00487
00488 TIMESTAMP waterheater::postsync(TIMESTAMP t0, TIMESTAMP t1){
00489 return TS_NEVER;
00490 }
00491
00492 int waterheater::commit(){
00493 Tw_old = Tw;
00494 Tupper_old = Tw;
00495 Tlower_old = Tlower;
00496 water_demand_old = water_demand;
00497 return 1;
00498 }
00499
00502 waterheater::WHQSTATE waterheater::tank_state(void)
00503 {
00504 if ( h >= height-HEIGHT_PRECISION )
00505 return FULL;
00506 else if ( h <= HEIGHT_PRECISION)
00507 return EMPTY;
00508 else
00509 return PARTIAL;
00510 }
00511
00514 void waterheater::set_time_to_transition(void)
00515 {
00516
00517 set_current_model_and_load_state();
00518
00519 time_to_transition = -1;
00520
00521 switch (current_model) {
00522 case ONENODE:
00523 if (heat_needed == FALSE)
00524 time_to_transition = new_time_1node(Tw, Ton);
00525 else if (load_state == RECOVERING)
00526 time_to_transition = new_time_1node(Tw, Toff);
00527 else
00528 time_to_transition = -1;
00529 break;
00530
00531 case TWONODE:
00532 switch (load_state) {
00533 case STABLE:
00534 time_to_transition = -1;
00535 break;
00536 case DEPLETING:
00537 time_to_transition = new_time_2zone(h, 0);
00538 break;
00539 case RECOVERING:
00540 time_to_transition = new_time_2zone(h, height);
00541 break;
00542 }
00543 }
00544 return;
00545 }
00546
00551 waterheater::WHQFLOW waterheater::set_current_model_and_load_state(void)
00552 {
00553 double dhdt_now = dhdt(h);
00554 double dhdt_full = dhdt(height);
00555 double dhdt_empty = dhdt(0.0);
00556 current_model = NONE;
00557 load_state = STABLE;
00558
00559 WHQSTATE tank_status = tank_state();
00560
00561 switch(tank_status)
00562 {
00563 case EMPTY:
00564 if (dhdt_empty <= 0.0)
00565 {
00566
00567
00568
00569
00570
00571
00572
00573 current_model = ONENODE;
00574 load_state = DEPLETING;
00575 Tw = Tupper = Tinlet + HEIGHT_PRECISION;
00576 Tlower = Tinlet;
00577 h = height;
00578
00579
00580
00581
00582
00583 }
00584 else if (dhdt_full > 0)
00585 {
00586
00587
00588 heat_needed = TRUE;
00589 current_model = TWONODE;
00590 load_state = RECOVERING;
00591 }
00592 else
00593 load_state = STABLE;
00594 break;
00595
00596 case FULL:
00597
00598
00599 if (dhdt_full < 0)
00600 {
00601
00602
00603 bool cur_heat_needed = heat_needed;
00604 heat_needed = TRUE;
00605 double dhdt_full_temp = dhdt(height);
00606 if (dhdt_full_temp < 0)
00607 {
00608 current_model = TWONODE;
00609 load_state = DEPLETING;
00610 }
00611 else
00612 {
00613 current_model = ONENODE;
00614
00615 heat_needed = cur_heat_needed;
00616 load_state = heat_needed ? RECOVERING : DEPLETING;
00617 }
00618 }
00619 else if (dhdt_empty > 0)
00620 {
00621 current_model = ONENODE;
00622 load_state = RECOVERING;
00623 }
00624 else
00625 load_state = STABLE;
00626 break;
00627
00628 case PARTIAL:
00629
00630
00631 current_model = TWONODE;
00632
00633
00634 heat_needed = TRUE;
00635
00636 if (dhdt_now < 0 && (dhdt_now * dhdt_empty) >= 0)
00637 load_state = DEPLETING;
00638 else if (dhdt_now > 0 && (dhdt_now * dhdt_full) >= 0)
00639 load_state = RECOVERING;
00640 else
00641 {
00642
00643 current_model = NONE;
00644 load_state = STABLE;
00645 }
00646 break;
00647 }
00648
00649 return load_state;
00650 }
00651
00652 void waterheater::update_T_and_or_h(double nHours)
00653 {
00654
00655
00656
00657
00658
00659
00660
00661
00662
00663
00664
00665
00666
00667 switch (current_model)
00668 {
00669 case ONENODE:
00670
00671
00672 SingleZone:
00673 Tw = new_temp_1node(Tw, nHours);
00674 Tw = Tw;
00675 Tlower = Tinlet;
00676 break;
00677
00678 case TWONODE:
00679
00680
00681 heat_needed = TRUE;
00682 switch (load_state)
00683 {
00684 case STABLE:
00685
00686 break;
00687 case DEPLETING:
00688
00689 case RECOVERING:
00690 try {
00691 h = new_h_2zone(h, nHours);
00692 } catch (WRONGMODEL m)
00693 {
00694 if (m==MODEL_NOT_2ZONE)
00695 {
00696 current_model = ONENODE;
00697 goto SingleZone;
00698 }
00699 else
00700 GL_THROW("unexpected exception in update_T_and_or_h(%+.1f hrs)", nHours);
00701 }
00702 break;
00703 }
00704
00705
00706 if (h < ROUNDOFF)
00707 {
00708
00709
00710
00711 double vol_over = tank_volume/GALPCF * h/height;
00712 double energy_over = vol_over * RHOWATER * Cp * ( Tw - Tlower);
00713 double Tnew = Tlower + energy_over/Cw;
00714 Tw = Tlower = Tnew;
00715 h = 0;
00716 }
00717 else if (h > height)
00718 {
00719
00720 double vol_over = tank_volume/GALPCF * (h-height)/height;
00721 double energy_over = vol_over * RHOWATER * Cp * ( Tw - Tlower);
00722 double Tnew = Tw + energy_over/Cw;
00723 Tw = Tw = Tnew;
00724 Tlower = Tinlet;
00725 h = height;
00726 }
00727 else
00728 {
00729
00730
00731
00732
00733 Tw = Tw;
00734 }
00735 break;
00736
00737 default:
00738 break;
00739 }
00740
00741 return;
00742 }
00743
00744
00745
00746
00747
00748
00749 double waterheater::dhdt(double h)
00750 {
00751 if ( Tw - Tlower < ROUNDOFF)
00752 return 0.0;
00753
00754
00755 const double mdot = water_demand * 60 * RHOWATER / GALPCF;
00756 const double c1 = RHOWATER * Cp * area * ( Tw - Tlower);
00757
00758 if (water_demand > 0.0)
00759 double aaa=1;
00760
00761
00762 if (c1 <= ROUNDOFF)
00763 return 0.0;
00764
00765 const double cA = -mdot / (RHOWATER * area) + (actual_kW() * BTUPHPKW + tank_UA * (get_Tambient(location) - Tlower)) / c1;
00766 const double cb = (tank_UA / height) * ( Tw - Tlower) / c1;
00767
00768
00769 return cA - cb*h;
00770 }
00771
00772 double waterheater::actual_kW(void)
00773 {
00774 OBJECT *obj = OBJECTHDR(this);
00775 const double nominal_voltage = 240.0;
00776 static int trip_counter = 0;
00777
00778
00779 if (heat_needed)
00780 {
00781 if(heat_mode == GASHEAT){
00782 return heating_element_capacity;
00783 }
00784 const double actual_voltage = pCircuit ? pCircuit->pV->Mag() : nominal_voltage;
00785 if (actual_voltage > 2.0*nominal_voltage)
00786 {
00787 if (trip_counter++ > 10)
00788 GL_THROW("Water heater line voltage for waterheater:%d is too high, exceeds twice nominal voltage.",obj->id);
00789
00790
00791
00792
00793
00794 else
00795 return 0.0;
00796 }
00797 double test = heating_element_capacity * (actual_voltage*actual_voltage) / (nominal_voltage*nominal_voltage);
00798 return test;
00799 }
00800 else
00801 return 0.0;
00802 }
00803
00804 inline double waterheater::new_time_1node(double T0, double T1)
00805 {
00806 const double mdot_Cp = Cp * water_demand * 60 * RHOWATER / GALPCF;
00807
00808 if (Cw <= ROUNDOFF)
00809 return -1.0;
00810
00811 const double c1 = ((actual_kW()*BTUPHPKW + tank_UA * get_Tambient(location)) + mdot_Cp*Tinlet) / Cw;
00812 const double c2 = -(tank_UA + mdot_Cp) / Cw;
00813
00814 if (fabs(c1 + c2*T1) <= ROUNDOFF || fabs(c1 + c2*T0) <= ROUNDOFF || fabs(c2) <= ROUNDOFF)
00815 return -1.0;
00816
00817 const double new_time = (log(fabs(c1 + c2 * T1)) - log(fabs(c1 + c2 * T0))) / c2;
00818 return new_time;
00819 }
00820
00821 inline double waterheater::new_temp_1node(double T0, double delta_t)
00822 {
00823
00824 const double mdot_Cp = Cp * water_demand_old * 60 * RHOWATER / GALPCF;
00825
00826
00827 if (Cw <= ROUNDOFF || (tank_UA+mdot_Cp) <= ROUNDOFF)
00828 return T0;
00829
00830 const double c1 = (tank_UA + mdot_Cp) / Cw;
00831 const double c2 = (actual_kW()*BTUPHPKW + mdot_Cp*Tinlet + tank_UA*get_Tambient(location)) / (tank_UA + mdot_Cp);
00832
00833
00834 return c2 - (c2 - T0) * exp(-c1 * delta_t);
00835 }
00836
00837
00838 inline double waterheater::new_time_2zone(double h0, double h1)
00839 {
00840 const double c0 = RHOWATER * Cp * area * ( Tw - Tlower);
00841 double dhdt0, dhdt1;
00842
00843 if (fabs(c0) <= ROUNDOFF || height <= ROUNDOFF)
00844 return -1.0;
00845
00846 const double cb = (tank_UA / height) * ( Tw - Tlower) / c0;
00847
00848 if (fabs(cb) <= ROUNDOFF)
00849 return -1.0;
00850 dhdt1 = fabs(dhdt(h1));
00851 dhdt0 = fabs(dhdt(h0));
00852 double last_timestep = (log(dhdt1) - log(dhdt0)) / -cb;
00853 return last_timestep;
00854 }
00855
00856 inline double waterheater::new_h_2zone(double h0, double delta_t)
00857 {
00858 if (delta_t <= ROUNDOFF)
00859 return h0;
00860
00861
00862 const double mdot = water_demand_old * 60 * RHOWATER / GALPCF;
00863 const double c1 = RHOWATER * Cp * area * ( Tw - Tlower);
00864
00865
00866 if (fabs(c1) <= ROUNDOFF)
00867 return height;
00868
00869
00870
00871 const double cA = -mdot / (RHOWATER * area) + (actual_kW()*BTUPHPKW + tank_UA * (get_Tambient(location) - Tlower)) / c1;
00872
00873 const double cb = (tank_UA / height) * ( Tw - Tlower) / c1;
00874
00875 if (fabs(cb) <= ROUNDOFF)
00876 return height;
00877
00878 return ((exp(cb * delta_t) * (cA + cb * h0)) - cA) / cb;
00879 }
00880
00881 double waterheater::get_Tambient(WHLOCATION loc)
00882 {
00883 double ratio;
00884 OBJECT *parent = OBJECTHDR(this)->parent;
00885
00886 switch (loc) {
00887 case GARAGE:
00888 ratio = 0.5;
00889 break;
00890 case INSIDE:
00891 default:
00892 ratio = 1.0;
00893 break;
00894 }
00895
00896
00897
00898
00899 return *pTair * ratio + *pTout *(1-ratio);
00900 }
00901
00902 void waterheater::wrong_model(WRONGMODEL msg)
00903 {
00904 char *errtxt[] = {"model is not one-zone","model is not two-zone"};
00905 OBJECT *obj = OBJECTHDR(this);
00906 gl_warning("%s (waterheater:%d): %s", obj->name?obj->name:"(anonymous object)", obj->id, errtxt[msg]);
00907 throw msg;
00908 }
00909
00911
00913
00914 EXPORT int create_waterheater(OBJECT **obj, OBJECT *parent)
00915 {
00916 *obj = gl_create_object(waterheater::oclass);
00917 if (*obj!=NULL)
00918 {
00919 waterheater *my = OBJECTDATA(*obj,waterheater);;
00920 gl_set_parent(*obj,parent);
00921 my->create();
00922 return 1;
00923 }
00924 return 0;
00925 }
00926
00927 EXPORT int init_waterheater(OBJECT *obj)
00928 {
00929 waterheater *my = OBJECTDATA(obj,waterheater);
00930 return my->init(obj->parent);
00931 }
00932
00933 EXPORT int isa_waterheater(OBJECT *obj, char *classname)
00934 {
00935 if(obj != 0 && classname != 0){
00936 return OBJECTDATA(obj,waterheater)->isa(classname);
00937 } else {
00938 return 0;
00939 }
00940 }
00941
00942
00943 EXPORT TIMESTAMP sync_waterheater(OBJECT *obj, TIMESTAMP t0, PASSCONFIG pass)
00944 {
00945 waterheater *my = OBJECTDATA(obj, waterheater);
00946 if (obj->clock <= ROUNDOFF)
00947 obj->clock = t0;
00948 try {
00949 TIMESTAMP t1 = TS_NEVER;
00950 switch (pass) {
00951 case PC_PRETOPDOWN:
00952 return my->presync(obj->clock, t0);
00953 case PC_BOTTOMUP:
00954 return my->sync(obj->clock, t0);
00955 case PC_POSTTOPDOWN:
00956 t1 = my->postsync(obj->clock, t0);
00957 obj->clock = t0;
00958 return t1;
00959 default:
00960 throw "invalid pass request";
00961 }
00962 }
00963 catch (int m)
00964 {
00965 gl_error("%s (waterheater:%d) model zone exception (code %d) not caught", obj->name?obj->name:"(anonymous waterheater)", obj->id, m);
00966 }
00967 catch (char *msg)
00968 {
00969 gl_error("%s (waterheater:%d) %s", obj->name?obj->name:"(anonymous waterheater)", obj->id, msg);
00970 }
00971 return TS_INVALID;
00972 }
00973
00974 EXPORT int commit_waterheater(OBJECT *obj)
00975 {
00976 waterheater *my = OBJECTDATA(obj,waterheater);
00977 return my->commit();
00978 }
00979
00980 EXPORT TIMESTAMP plc_waterheater(OBJECT *obj, TIMESTAMP t0)
00981 {
00982
00983 if (obj->clock <= ROUNDOFF)
00984 obj->clock = t0;
00985
00986 waterheater *my = OBJECTDATA(obj,waterheater);
00987 my->thermostat(obj->clock, t0);
00988
00989
00991 return TS_NEVER;
00992 }
00993