00001
00013 #include <stdlib.h>
00014 #include <stdio.h>
00015 #include <errno.h>
00016 #include <math.h>
00017
00018 #include "range.h"
00019
00020 #define HEIGHT_PRECISION 0.01
00021
00023
00025 CLASS* range::oclass = NULL;
00026 CLASS* range::pclass = NULL;
00027
00030 range::range(MODULE *module) : residential_enduse(module){
00031
00032 if (oclass==NULL)
00033 {
00034 pclass = residential_enduse::oclass;
00035
00036 oclass = gl_register_class(module,"range",sizeof(range),PC_PRETOPDOWN|PC_BOTTOMUP|PC_POSTTOPDOWN);
00037 if (oclass==NULL)
00038 GL_THROW("unable to register object class implemented by %s",__FILE__);
00039
00040
00041 if (gl_publish_variable(oclass,
00042 PT_INHERIT, "residential_enduse",
00043 PT_double,"oven_volume[gal]",PADDR(oven_volume), PT_DESCRIPTION, "the volume of the oven",
00044 PT_double,"oven_UA[Btu/degF*h]",PADDR(oven_UA), PT_DESCRIPTION, "the UA of the oven (surface area divided by R-value)",
00045 PT_double,"oven_diameter[ft]",PADDR(oven_diameter), PT_DESCRIPTION, "the diameter of the oven",
00046
00047 PT_double,"oven_demand[gpm]",PADDR(oven_demand), PT_DESCRIPTION, "the hot food take out from the oven",
00048
00049 PT_double,"heating_element_capacity[kW]",PADDR(heating_element_capacity), PT_DESCRIPTION, "the power of the heating element",
00050 PT_double,"inlet_food_temperature[degF]",PADDR(Tinlet), PT_DESCRIPTION, "the inlet temperature of the food",
00051 PT_enumeration,"heat_mode",PADDR(heat_mode), PT_DESCRIPTION, "the energy source for heating the oven",
00052 PT_KEYWORD,"ELECTRIC",(enumeration)ELECTRIC,
00053 PT_KEYWORD,"GASHEAT",(enumeration)GASHEAT,
00054 PT_enumeration,"location",PADDR(location), PT_DESCRIPTION, "whether the range is inside or outside",
00055 PT_KEYWORD,"INSIDE",(enumeration)INSIDE,
00056 PT_KEYWORD,"GARAGE",(enumeration)GARAGE,
00057 PT_double,"oven_setpoint[degF]",PADDR(oven_setpoint), PT_DESCRIPTION, "the temperature around which the oven will heat its contents",
00058 PT_double,"thermostat_deadband[degF]",PADDR(thermostat_deadband), PT_DESCRIPTION, "the degree to heat the food in the oven, when needed",
00059 PT_double,"temperature[degF]",PADDR(Tw), PT_DESCRIPTION, "the outlet temperature of the oven",
00060 PT_double,"height[ft]",PADDR(h), PT_DESCRIPTION, "the height of the oven",
00061
00062 PT_double,"food_density",PADDR(food_density), PT_DESCRIPTION, "food density",
00063 PT_double,"specificheat_food",PADDR(specificheat_food),
00064
00065 PT_double,"queue_cooktop[unit]",PADDR(enduse_queue_cooktop), PT_DESCRIPTION, "number of loads accumulated",
00066
00067 PT_double,"queue_oven[unit]",PADDR(enduse_queue_oven), PT_DESCRIPTION, "number of loads accumulated",
00068
00069 PT_double,"queue_min[unit]",PADDR(queue_min),
00070 PT_double,"queue_max[unit]",PADDR(queue_max),
00071
00072 PT_double,"time_cooktop_operation",PADDR(time_cooktop_operation),
00073 PT_double,"time_cooktop_setting",PADDR(time_cooktop_setting),
00074
00075 PT_double,"cooktop_run_prob",PADDR(cooktop_run_prob),
00076 PT_double,"oven_run_prob",PADDR(oven_run_prob),
00077
00078 PT_double,"cooktop_coil_setting_1[kW]",PADDR(cooktop_coil_power[0]),
00079 PT_double,"cooktop_coil_setting_2[kW]",PADDR(cooktop_coil_power[1]),
00080 PT_double,"cooktop_coil_setting_3[kW]",PADDR(cooktop_coil_power[2]),
00081
00082 PT_double,"total_power_oven[kW]",PADDR(total_power_oven),
00083 PT_double,"total_power_cooktop[kW]",PADDR(total_power_cooktop),
00084 PT_double,"total_power_range[kW]",PADDR(total_power_range),
00085
00086 PT_double,"demand_cooktop[unit/day]",PADDR(enduse_demand_cooktop), PT_DESCRIPTION, "number of loads accumulating daily",
00087 PT_double,"demand_oven[unit/day]",PADDR(enduse_demand_oven), PT_DESCRIPTION, "number of loads accumulating daily",
00088
00089 PT_double,"stall_voltage[V]", PADDR(stall_voltage),
00090 PT_double,"start_voltage[V]", PADDR(start_voltage),
00091 PT_complex,"stall_impedance[Ohm]", PADDR(stall_impedance),
00092 PT_double,"trip_delay[s]", PADDR(trip_delay),
00093 PT_double,"reset_delay[s]", PADDR(reset_delay),
00094
00095
00096 PT_double,"time_oven_operation[s]", PADDR(time_oven_operation),
00097 PT_double,"time_oven_setting[s]", PADDR(time_oven_setting),
00098
00099 PT_enumeration,"state_cooktop", PADDR(state_cooktop),
00100 PT_KEYWORD,"CT_STOPPED",CT_STOPPED,
00101 PT_KEYWORD,"STAGE_6_ONLY",CT_STAGE_1_ONLY,
00102 PT_KEYWORD,"STAGE_7_ONLY",CT_STAGE_2_ONLY,
00103 PT_KEYWORD,"STAGE_8_ONLY",CT_STAGE_3_ONLY,
00104 PT_KEYWORD,"CT_STALLED",CT_STALLED,
00105 PT_KEYWORD,"CT_TRIPPED",CT_TRIPPED,
00106
00107 PT_double,"cooktop_energy_baseline[kWh]", PADDR(cooktop_energy_baseline),
00108 PT_double,"cooktop_energy_used", PADDR(cooktop_energy_used),
00109 PT_double,"Toff", PADDR(Toff),
00110 PT_double,"Ton", PADDR(Ton),
00111
00112 PT_double,"cooktop_interval_setting_1[s]", PADDR(cooktop_interval[0]),
00113 PT_double,"cooktop_interval_setting_2[s]", PADDR(cooktop_interval[1]),
00114 PT_double,"cooktop_interval_setting_3[s]", PADDR(cooktop_interval[2]),
00115 PT_double,"cooktop_energy_needed[kWh]",PADDR(cooktop_energy_needed),
00116
00117 PT_bool,"heat_needed",PADDR(heat_needed),
00118 PT_bool,"oven_check",PADDR(oven_check),
00119 PT_bool,"remainon",PADDR(remainon),
00120 PT_bool,"cooktop_check",PADDR(cooktop_check),
00121
00122 PT_double,"actual_load[kW]",PADDR(actual_load),PT_DESCRIPTION, "the actual load based on the current voltage across the coils",
00123 PT_double,"previous_load[kW]",PADDR(prev_load),PT_DESCRIPTION, "the actual load based on current voltage stored for use in controllers",
00124 PT_complex,"actual_power[kVA]",PADDR(range_actual_power), PT_DESCRIPTION, "the actual power based on the current voltage across the coils",
00125 PT_double,"is_range_on",PADDR(is_range_on),PT_DESCRIPTION, "simple logic output to determine state of range (1-on, 0-off)",
00126 NULL)<1)
00127 GL_THROW("unable to publish properties in %s",__FILE__);
00128 }
00129 }
00130
00131 range::~range()
00132 {
00133 }
00134
00135 int range::create()
00136 {
00137
00138 OBJECT *hdr = OBJECTHDR(this);
00139 int res = residential_enduse::create();
00140
00141
00142
00143
00144 oven_diameter = 1.5;
00145 Tinlet = 60.0;
00146 oven_demand = 0.0;
00147
00148 heat_needed = FALSE;
00149 heat_mode = ELECTRIC;
00150 is_range_on = 0;
00151 Tw = 0.0;
00152 time_oven_operation = 0;
00153 oven_check = false;
00154 remainon = false;
00155 cooktop_check = false;
00156 time_cooktop_operation = 0;
00157 oven_volume = 5;
00158 heating_element_capacity = 1;
00159 oven_setpoint = 100;
00160 Tw = 70;
00161 thermostat_deadband = 8;
00162 location = INSIDE;
00163 oven_UA = 2.9;
00164 oven_demand = 0.01;
00165 food_density = 5;
00166 specificheat_food = 1;
00167 time_oven_setting = 3600;
00168
00169
00170 enduse_queue_oven = 0.85;
00171
00172
00173
00174 location = gl_random_bernoulli(&hdr->rng_state,0.80) ? GARAGE : INSIDE;
00175
00176
00177 oven_setpoint = clip(gl_random_normal(&hdr->rng_state,130,10),100,160);
00178 thermostat_deadband = clip(gl_random_normal(&hdr->rng_state,5, 1),1,10);
00179
00180
00181 oven_setpoint = gl_random_normal(&hdr->rng_state,125,5);
00182 if (oven_setpoint<90) oven_setpoint = 90;
00183 if (oven_setpoint>160) oven_setpoint = 160;
00184
00185
00186 thermostat_deadband = fabs(gl_random_normal(&hdr->rng_state,2,1))+1;
00187 if (thermostat_deadband>10)
00188 thermostat_deadband = 10;
00189
00190 oven_UA = clip(gl_random_normal(&hdr->rng_state,2.0, 0.20),0.1,10) * oven_volume/50;
00191 if(oven_UA <= 1.0)
00192 oven_UA = 2.0;
00193
00194
00195 load.name = oclass->name;
00196
00197 load.breaker_amps = 30;
00198 load.config = EUC_IS220;
00199 load.power_fraction = 0.0;
00200 load.impedance_fraction = 1.0;
00201 load.heatgain_fraction = 0.0;
00202
00203 state_cooktop = CT_STOPPED;
00204 TSTAT_PRECISION= 0.01;
00205
00206 cooktop_energy_baseline = 0.5;
00207
00208 cooktop_coil_power[0] = 2;
00209 cooktop_coil_power[1] = 1.0;
00210 cooktop_coil_power[2] = 1.7;
00211
00212 cooktop_interval[0] = 240;
00213 cooktop_interval[1] = 900;
00214 cooktop_interval[2] = 120;
00215
00216 time_cooktop_setting = 2000;
00217
00218 enduse_queue_cooktop = 0.99;
00219
00220 return res;
00221
00222 }
00223
00226 int range::init(OBJECT *parent)
00227 {
00228
00229
00230 if(parent != NULL){
00231 if((parent->flags & OF_INIT) != OF_INIT){
00232 char objname[256];
00233 gl_verbose("range::init(): deferring initialization on %s", gl_name(parent, objname, 255));
00234 return 2;
00235 }
00236 }
00237 OBJECT *hdr = OBJECTHDR(this);
00238 hdr->flags |= OF_SKIPSAFE;
00239
00240 static double sTair = 74;
00241 static double sTout = 68;
00242 if (heat_fraction==0) heat_fraction = 0.2;
00243
00244 if(parent){
00245 pTair = gl_get_double_by_name(parent, "air_temperature");
00246 pTout = gl_get_double_by_name(parent, "outdoor_temperature");
00247 }
00248
00249 if(pTair == 0){
00250 pTair = &sTair;
00251 gl_warning("range parent lacks \'air_temperature\' property, using default");
00252 }
00253 if(pTout == 0){
00254 pTout = &sTout;
00255 gl_warning("range parent lacks \'outside_temperature\' property, using default");
00256 }
00257
00258
00259
00260 if(oven_volume <= 0.0){
00261
00262 if (oven_volume > 100.0)
00263 oven_volume = 100.0;
00264 else if (oven_volume < 20.0)
00265 oven_volume = 20.0;
00266 }
00267
00268 if (oven_setpoint<90 || oven_setpoint>160)
00269 GL_THROW("This model is experimental and not validated: oven thermostat is set to %f and is outside the bounds of 90 to 160 degrees Fahrenheit (32.2 - 71.1 Celsius).", oven_setpoint);
00270
00271
00272
00273
00274
00275 if (thermostat_deadband>10 || thermostat_deadband < 0.0)
00276 GL_THROW("oven deadband of %f is outside accepted bounds of 0 to 10 degrees (5.6 degC).", thermostat_deadband);
00277
00278
00279 if (oven_UA <= 0.0)
00280 GL_THROW("Range UA value is negative.");
00281
00282
00283
00284 if (heating_element_capacity <= 0.0)
00285 {
00286 if (oven_volume >= 50)
00287 heating_element_capacity = 4.500;
00288 else
00289 {
00290
00291 double randVal = gl_random_uniform(&hdr->rng_state,0,1);
00292 if (randVal < 0.33)
00293 heating_element_capacity = 3.200;
00294 else if (randVal < 0.67)
00295 heating_element_capacity = 3.500;
00296 else
00297 heating_element_capacity = 4.500;
00298 }
00299 }
00300
00301
00302
00303 if(Tw < Tinlet){
00304 Tw = gl_random_uniform(&hdr->rng_state,oven_setpoint - thermostat_deadband, oven_setpoint + thermostat_deadband);
00305 }
00306 current_model = NONE;
00307 load_state = STABLE;
00308
00309
00310 Tset_curtail = oven_setpoint - thermostat_deadband/2 - 10;
00311
00312
00313 area = (pi * pow(oven_diameter,2))/4;
00314 height = oven_volume/GALPCF / area;
00315 Cw = oven_volume/GALPCF * food_density * specificheat_food;
00316
00317 h = height;
00318
00319
00320 if(h == 0){
00321
00322 Tlower = Tinlet;
00323 Tupper = Tinlet + TSTAT_PRECISION;
00324 } else {
00325 Tlower = Tinlet;
00326 }
00327
00328
00329 switch(shape.type){
00330 case MT_UNKNOWN:
00331
00332 gl_warning("This device, %s, is considered very experimental and has not been validated.", get_name());
00333 break;
00334 case MT_ANALOG:
00335 if(shape.params.analog.energy == 0.0){
00336 GL_THROW("range does not support fixed energy shaping");
00337
00338
00339
00340
00341
00342
00343
00344 } else if (shape.params.analog.power == 0){
00345
00346 oven_demand = gl_get_loadshape_value(&shape) / 2.4449;
00347 } else {
00348 oven_demand = gl_get_loadshape_value(&shape);
00349 }
00350 break;
00351 case MT_PULSED:
00352
00353
00354 if(shape.params.pulsed.pulsetype == MPT_TIME){
00355 ;
00356 } else if(shape.params.pulsed.pulsetype == MPT_POWER){
00357 ;
00358 oven_demand = gl_get_loadshape_value(&shape) / 2.4449;
00359 }
00360 break;
00361 case MT_MODULATED:
00362 if(shape.params.modulated.pulsetype == MPT_TIME){
00363 GL_THROW("Amplitude modulated oven usage is nonsensical for residential ovens");
00364
00365
00366
00367
00368 } else if(shape.params.modulated.pulsetype == MPT_POWER){
00369
00370
00371 oven_demand = gl_get_loadshape_value(&shape) / 2.4449;
00372 }
00373 break;
00374 case MT_QUEUED:
00375 if(shape.params.queued.pulsetype == MPT_TIME){
00376 ;
00377 } else if(shape.params.queued.pulsetype == MPT_POWER){
00378 ;
00379 oven_demand = gl_get_loadshape_value(&shape) / 2.4449;
00380 }
00381 break;
00382 default:
00383 GL_THROW("range load shape has an unknown state!");
00384 break;
00385 }
00386 return residential_enduse::init(parent);
00387 }
00388
00389 int range::isa(char *classname)
00390 {
00391 return (strcmp(classname,"range")==0 || residential_enduse::isa(classname));
00392 }
00393
00394
00395 void range::thermostat(TIMESTAMP t0, TIMESTAMP t1){
00396 Ton = oven_setpoint - thermostat_deadband/2;
00397 Toff = oven_setpoint + thermostat_deadband/2;
00398 OBJECT *hdr = OBJECTHDR(this);
00399
00400 switch(range_state()){
00401
00402 case FULL:
00403
00404 if (enduse_queue_oven>1)
00405 {
00406
00407 oven_run_prob = double(gl_random_uniform(&hdr->rng_state,queue_min,queue_max));
00408
00409 if(Tw-TSTAT_PRECISION < Ton && (oven_run_prob > enduse_queue_oven || oven_run_prob >1 ) && (time_oven_operation <time_oven_setting))
00410
00411 {
00412
00413 heat_needed = TRUE;
00414 oven_check = true;
00415 remainon = true;
00416 if (time_oven_operation == 0)
00417 enduse_queue_oven --;
00418
00419 }
00420 }
00421
00422 if (Tw+TSTAT_PRECISION > Toff || (time_oven_operation >= time_oven_setting))
00423 {
00424 heat_needed = FALSE;
00425 oven_check = false;
00426
00427 }
00428 if (Tw-TSTAT_PRECISION < Ton && (time_oven_operation <time_oven_setting) && remainon == true)
00429 {
00430 heat_needed = TRUE;
00431 oven_check = true;
00432 }
00433 if (time_oven_operation >= time_oven_setting)
00434 {
00435 remainon = false;
00436 time_oven_operation = 0;
00437 }
00438 else
00439 {
00440 ;
00441 }
00442
00443
00444 break;
00445 case PARTIAL:
00446 case EMPTY:
00447 heat_needed = TRUE;
00448 break;
00449 default:
00450 GL_THROW("range thermostat() detected that the oven is in an unknown state");
00451 }
00452
00453 }
00454
00459 TIMESTAMP range::presync(TIMESTAMP t0, TIMESTAMP t1){
00460
00461 double nHours = (gl_tohours(t1) - gl_tohours(t0))/TS_SECOND;
00462 OBJECT *my = OBJECTHDR(this);
00463
00464
00465 update_T_and_or_h(nHours);
00466
00467 if(Tw > 212.0){
00468
00469 gl_warning("range:%i is using an experimental model and should not be considered reliable", my->id);
00470
00471
00472
00473
00474
00475 }
00476
00477
00478 switch(shape.type){
00479 case MT_UNKNOWN:
00480
00481 break;
00482 case MT_ANALOG:
00483 if(shape.params.analog.energy == 0.0){
00484 GL_THROW("range does not support fixed energy shaping");
00485
00486
00487
00488
00489
00490
00491
00492 } else if (shape.params.analog.power == 0){
00493
00494 oven_demand = gl_get_loadshape_value(&shape) / 2.4449;
00495 } else {
00496 oven_demand = gl_get_loadshape_value(&shape);
00497 }
00498 break;
00499 case MT_PULSED:
00500
00501
00502 if(shape.params.pulsed.pulsetype == MPT_TIME){
00503 ;
00504 } else if(shape.params.pulsed.pulsetype == MPT_POWER){
00505 ;
00506 oven_demand = gl_get_loadshape_value(&shape) / 2.4449;
00507 }
00508 break;
00509 case MT_MODULATED:
00510 if(shape.params.modulated.pulsetype == MPT_TIME){
00511 GL_THROW("Amplitude modulated oven usage is nonsensical for residential ovens");
00512
00513
00514
00515
00516 } else if(shape.params.modulated.pulsetype == MPT_POWER){
00517
00518
00519 oven_demand = gl_get_loadshape_value(&shape) / 2.4449;
00520 }
00521 break;
00522 case MT_QUEUED:
00523 if(shape.params.queued.pulsetype == MPT_TIME){
00524 ;
00525 } else if(shape.params.queued.pulsetype == MPT_POWER){
00526 ;
00527 oven_demand = gl_get_loadshape_value(&shape) / 2.4449;
00528 }
00529 break;
00530 default:
00531 GL_THROW("range load shape has an unknown state!");
00532 break;
00533 }
00534
00535 return TS_NEVER;
00536
00537 }
00538
00542 TIMESTAMP range::sync(TIMESTAMP t0, TIMESTAMP t1)
00543 {
00544 double internal_gain = 0.0;
00545 double nHours = (gl_tohours(t1) - gl_tohours(t0))/TS_SECOND;
00546 double Tamb = get_Tambient(location);
00547 double dt = gl_toseconds(t0>0?t1-t0:0);
00548
00549 if (oven_check == true || remainon == true)
00550 time_oven_operation +=dt;
00551
00552 if (remainon == false)
00553 time_oven_operation=0;
00554
00555 enduse_queue_oven += enduse_demand_oven * dt/3600/24;
00556
00557
00558 if (t0>TS_ZERO && t1>t0)
00559 {
00560
00561 load.energy += load.total * dt/3600.0;
00562 }
00563
00564 if(re_override == OV_ON){
00565 heat_needed = TRUE;
00566 } else if(re_override == OV_OFF){
00567 heat_needed = FALSE;
00568 }
00569
00570 if(Tw > 212.0 - thermostat_deadband){
00571 heat_needed = FALSE;
00572 is_range_on = 0;
00573 }
00574
00575 if (heat_needed == TRUE){
00576 load.total = heating_element_capacity * (heat_mode == GASHEAT ? 0.01 : 1.0);
00577 is_range_on = 1;
00578 } else {
00579 load.total = 0.0;
00580 is_range_on = 0;
00581 }
00582
00583 TIMESTAMP t2 = residential_enduse::sync(t0,t1);
00584
00585 set_time_to_transition();
00586
00587 if (location == INSIDE){
00588 if(this->current_model == ONENODE){
00589 internal_gain = oven_UA * (Tw - get_Tambient(location));
00590 }
00591
00592 } else {
00593 internal_gain = 0;
00594 }
00595
00596 dt = update_state(dt, t1);
00597
00598
00599
00600
00601 load.power = load.total * load.power_fraction;
00602 load.admittance = load.total * load.impedance_fraction;
00603 load.current = load.total * load.current_fraction;
00604 load.heatgain = internal_gain;
00605
00606 range_actual_power = load.power + (load.current + load.admittance * load.voltage_factor )* load.voltage_factor;
00607 actual_load = range_actual_power.Re();
00608 if (heat_needed == true)
00609 total_power_oven = actual_load;
00610 else
00611 total_power_oven =0;
00612
00613 if (actual_load != 0.0)
00614 {
00615 prev_load = actual_load;
00616 power_state = PS_ON;
00617 }
00618 else
00619 power_state = PS_OFF;
00620
00621
00622
00623 if(re_override == OV_NORMAL){
00624 if (time_to_transition < dt)
00625 {
00626 if (time_to_transition >= (1.0/3600.0))
00627 {
00628 TIMESTAMP t_to_trans = (t1+time_to_transition*3600.0/TS_SECOND);
00629 return -(t_to_trans);
00630 }
00631
00632 else
00633 return TS_NEVER;
00634 }
00635 else
00636 return (TIMESTAMP)(t1+dt);
00637 } else {
00638 return TS_NEVER;
00639 }
00640
00641
00642 }
00643
00644 double range::update_state(double dt1,TIMESTAMP t1)
00645 {
00646 OBJECT *hdr = OBJECTHDR(this);
00647 cooktop_energy_used += total_power_cooktop* dt1/3600;
00648 double temp_voltage_magnitude;
00649
00650
00651 temp_voltage_magnitude = (pCircuit->pV->get_complex()).Mag();
00652
00653 switch(state_cooktop) {
00654
00655 case CT_STOPPED:
00656
00657 if (enduse_queue_cooktop>1)
00658
00659 cooktop_run_prob = double(gl_random_uniform(&hdr->rng_state,queue_min,queue_max));
00660
00661 if (enduse_queue_cooktop > 1 && (cooktop_run_prob > enduse_queue_cooktop))
00662 {
00663 state_cooktop = CT_STAGE_1_ONLY;
00664 cooktop_energy_needed = cooktop_energy_baseline;
00665 cycle_duration_cooktop = 1000 * (cooktop_energy_needed - cooktop_energy_used) /cooktop_coil_power[0] * 60 * 60;
00666 cycle_time_cooktop = cooktop_interval[0];
00667 cooktop_check = true;
00668 enduse_queue_cooktop--;
00669
00670 }
00671
00672 else
00673 {
00674 state_cooktop = CT_STOPPED;
00675 state_time = 0;
00676 cycle_time_cooktop = 0;
00677 cooktop_energy_used = 0;
00678 cooktop_check = false;
00679
00680 }
00681
00682 break;
00683
00684 case CT_STAGE_1_ONLY:
00685
00686
00687 if (cooktop_energy_used >= cooktop_energy_needed || time_cooktop_operation >= time_cooktop_setting || cycle_time_cooktop <= 0)
00688 {
00689 if (cooktop_energy_used >= cooktop_energy_needed || time_cooktop_operation >= time_cooktop_setting)
00690 {
00691 state_cooktop = CT_STOPPED;
00692 cycle_time_cooktop = 0;
00693 cooktop_energy_used = 0;
00694 cooktop_check = false;
00695
00696 }
00697
00698 else if (cycle_time_cooktop <= 0)
00699 {
00700 state_cooktop = CT_STAGE_2_ONLY;
00701 double cycle_t = 1000 * (cooktop_energy_needed - cooktop_energy_used) / cooktop_coil_power[2] * 60 * 60;
00702 double interval = cooktop_interval[1];
00703 cooktop_check = true;
00704 if (cycle_t > interval)
00705 cycle_time_cooktop = interval;
00706 else
00707 cycle_time_cooktop = cycle_t;
00708
00709 }
00710
00711 else if (temp_voltage_magnitude<stall_voltage)
00712 {
00713 state_cooktop = CT_STALLED;
00714 state_time = 0;
00715 }
00716
00717 }
00718 break;
00719
00720 case CT_STAGE_2_ONLY:
00721
00722 if (cooktop_energy_used >= cooktop_energy_needed || time_cooktop_operation >= time_cooktop_setting || cycle_time_cooktop <= 0)
00723 {
00724 if (cooktop_energy_used >= cooktop_energy_needed || time_cooktop_operation >= time_cooktop_setting)
00725 {
00726 state_cooktop = CT_STOPPED;
00727 cycle_time_cooktop = 0;
00728 cooktop_energy_used = 0;
00729 cooktop_check = false;
00730 }
00731
00732 else if (cycle_time_cooktop <= 0)
00733 {
00734 state_cooktop = CT_STAGE_3_ONLY;
00735 cooktop_check = true;
00736 cycle_time_cooktop = time_cooktop_setting - cooktop_interval[0] - cooktop_interval[1];
00737 }
00738
00739 else if (temp_voltage_magnitude<stall_voltage)
00740 {
00741 state_cooktop = CT_STALLED;
00742 state_time = 0;
00743 }
00744
00745 }
00746 break;
00747
00748 case CT_STAGE_3_ONLY:
00749
00750 if (cooktop_energy_used >= cooktop_energy_needed || cycle_time_cooktop <= 0)
00751 {
00752 state_cooktop = CT_STOPPED;
00753 cycle_time_cooktop = 0;
00754 cooktop_energy_used = 0;
00755 cooktop_check = false;
00756
00757 }
00758
00759 else if (temp_voltage_magnitude<stall_voltage)
00760 {
00761 state_cooktop = CT_STALLED;
00762 state_time = 0;
00763 }
00764
00765
00766 break;
00767
00768 case CT_STALLED:
00769 if (temp_voltage_magnitude>start_voltage)
00770 {
00771 state_cooktop = CT_STAGE_1_ONLY;
00772 state_time = cycle_time_cooktop;
00773 cooktop_check = false;
00774 }
00775
00776 break;
00777
00778 case CT_TRIPPED:
00779 if (state_time>reset_delay)
00780 {
00781 if (temp_voltage_magnitude>start_voltage)
00782 state_cooktop = CT_STAGE_1_ONLY;
00783 else
00784 state_cooktop = CT_STALLED;
00785 state_time = 0;
00786 }
00787
00788 break;
00789 }
00790
00791
00792
00793
00794 state_time += dt1;
00795
00796
00797
00798 enduse_queue_cooktop += enduse_demand_cooktop * dt1/3600/24;
00799
00800 if (cooktop_check == true)
00801 time_cooktop_operation += 1;
00802 else
00803 time_cooktop_operation = 0;
00804
00805
00806
00807
00808
00809 switch(state_cooktop) {
00810 case CT_STOPPED:
00811
00812
00813 load.power = load.current = load.admittance = complex(0,0,J);
00814
00815
00816
00817 dt1 = (enduse_queue_cooktop>=1) ? 0 : ((1-enduse_queue_cooktop)*3600)/(enduse_queue_cooktop*24);
00818
00819 break;
00820
00821 case CT_STAGE_1_ONLY:
00822
00823
00824 cycle_time_cooktop -= dt1;
00825
00826 load.power = load.current = complex(0,0,J);
00827 load.admittance = complex((cooktop_coil_power[0])/1000,0,J);
00828
00829 dt1 = cycle_time_cooktop;
00830 break;
00831
00832 case CT_STAGE_2_ONLY:
00833
00834
00835 cycle_time_cooktop -= dt1;
00836
00837 load.power = load.current = complex(0,0,J);
00838 load.admittance = complex((cooktop_coil_power[1])/1000,0,J);
00839
00840 dt1 = cycle_time_cooktop;
00841 break;
00842
00843 case CT_STAGE_3_ONLY:
00844
00845
00846 cycle_time_cooktop -= dt1;
00847
00848 load.power = load.current = complex(0,0,J);
00849 load.admittance = complex((cooktop_coil_power[2])/1000,0,J);
00850
00851 dt1 = cycle_time_cooktop;
00852 break;
00853
00854 case CT_STALLED:
00855
00856
00857 load.power = load.current = complex(0,0,J);
00858 load.admittance = complex(1)/stall_impedance;
00859
00860
00861 dt1 = trip_delay;
00862
00863 break;
00864
00865 case CT_TRIPPED:
00866
00867
00868 load.power = load.current = load.admittance = complex(0,0,J);
00869
00870
00871 dt1 = reset_delay;
00872
00873 break;
00874
00875
00876 default:
00877
00878 throw "unexpected motor state";
00879
00880
00881
00882
00883 break;
00884 }
00885
00886 load.total += load.power + load.current + load.admittance;
00887 total_power_cooktop = (load.power.Re() + (load.current.Re() + load.admittance.Re()*load.voltage_factor)*load.voltage_factor)*1000;
00888
00889
00890
00891
00892
00893
00894
00895 load.heatgain = load.total.Mag() * heat_fraction;
00896
00897
00898 if (dt1 > 0 && dt1 < 1)
00899 dt1 = 1;
00900
00901 return dt1;
00902
00903 }
00904
00905 TIMESTAMP range::postsync(TIMESTAMP t0, TIMESTAMP t1){
00906 return TS_NEVER;
00907 }
00908 int range::commit(){
00909 Tw_old = Tw;
00910 Tupper_old = Tw;
00911 Tlower_old = Tlower;
00912 oven_demand_old = oven_demand;
00913 return 1;
00914 }
00915
00918 enumeration range::range_state(void)
00919 {
00920 if ( h >= height-HEIGHT_PRECISION )
00921 return FULL;
00922 else if ( h <= HEIGHT_PRECISION)
00923 return EMPTY;
00924 else
00925 return PARTIAL;
00926 }
00927
00930 void range::set_time_to_transition(void)
00931 {
00932
00933 set_current_model_and_load_state();
00934
00935 time_to_transition = -1;
00936
00937 switch (current_model) {
00938 case ONENODE:
00939 if (heat_needed == FALSE)
00940 time_to_transition = new_time_1node(Tw, Ton);
00941 else if (load_state == RECOVERING)
00942 time_to_transition = new_time_1node(Tw, Toff);
00943 else
00944 time_to_transition = -1;
00945 break;
00946
00947 }
00948 return;
00949 }
00950
00955 enumeration range::set_current_model_and_load_state(void)
00956 {
00957 double dhdt_now = dhdt(h);
00958 double dhdt_full = dhdt(height);
00959 double dhdt_empty = dhdt(0.0);
00960 current_model = NONE;
00961 load_state = STABLE;
00962
00963 enumeration oven_status = range_state();
00964
00965 switch(oven_status)
00966 {
00967 case EMPTY:
00968 if (dhdt_empty <= 0.0)
00969 {
00970 current_model = ONENODE;
00971 load_state = DEPLETING;
00972 Tw = Tupper = Tinlet + HEIGHT_PRECISION;
00973 Tlower = Tinlet;
00974 h = height;
00975
00976 }
00977 else if (dhdt_full > 0)
00978 {
00979
00980
00981 heat_needed = TRUE;
00982
00983 current_model = ONENODE;
00984 load_state = RECOVERING;
00985 }
00986 else
00987 load_state = STABLE;
00988 break;
00989
00990 case FULL:
00991
00992 if (dhdt_full < 0)
00993 {
00994
00995 bool cur_heat_needed = heat_needed;
00996 heat_needed = TRUE;
00997 double dhdt_full_temp = dhdt(height);
00998 if (dhdt_full_temp < 0)
00999 {
01000 current_model = ONENODE;
01001 load_state = DEPLETING;
01002 }
01003 else
01004 {
01005 current_model = ONENODE;
01006
01007 heat_needed = cur_heat_needed;
01008 load_state = heat_needed ? RECOVERING : DEPLETING;
01009 }
01010 }
01011 else if (dhdt_empty > 0)
01012 {
01013 current_model = ONENODE;
01014 load_state = RECOVERING;
01015 }
01016 else
01017 load_state = STABLE;
01018 break;
01019
01020 case PARTIAL:
01021
01022 current_model = ONENODE;
01023
01024 heat_needed = TRUE;
01025
01026 if (dhdt_now < 0 && (dhdt_now * dhdt_empty) >= 0)
01027 load_state = DEPLETING;
01028 else if (dhdt_now > 0 && (dhdt_now * dhdt_full) >= 0)
01029 load_state = RECOVERING;
01030 else
01031 {
01032 current_model = NONE;
01033 load_state = STABLE;
01034 }
01035 break;
01036 }
01037
01038 return load_state;
01039 }
01040
01041 void range::update_T_and_or_h(double nHours)
01042 {
01043
01044
01045
01046
01047
01048
01049
01050
01051
01052
01053
01054
01055
01056 switch (current_model)
01057 {
01058 case ONENODE:
01059
01060
01061 SingleZone:
01062 Tw = new_temp_1node(Tw, nHours);
01063 Tw = Tw;
01064 Tlower = Tinlet;
01065 break;
01066
01067
01068 if (h < ROUNDOFF)
01069 {
01070
01071
01072
01073 double vol_over = oven_volume/GALPCF * h/height;
01074 double energy_over = vol_over * food_density * specificheat_food * ( Tw - Tlower);
01075 double Tnew = Tlower + energy_over/Cw;
01076 Tw = Tlower = Tnew;
01077 h = 0;
01078 }
01079 else if (h > height)
01080 {
01081
01082 double vol_over = oven_volume/GALPCF * (h-height)/height;
01083 double energy_over = vol_over * food_density * specificheat_food * ( Tw - Tlower);
01084 double Tnew = Tw + energy_over/Cw;
01085 Tw = Tw = Tnew;
01086 Tlower = Tinlet;
01087 h = height;
01088 }
01089 else
01090 {
01091 Tw = Tw;
01092 }
01093 break;
01094
01095 default:
01096 break;
01097 }
01098
01099 if (heat_needed == TRUE)
01100 power_state = PS_ON;
01101 else
01102 power_state = PS_OFF;
01103
01104 return;
01105 }
01106
01107
01108
01109
01110
01111 double range::dhdt(double h)
01112 {
01113 if ( Tw - Tlower < ROUNDOFF)
01114 return 0.0;
01115
01116
01117 const double mdot = oven_demand * 60 * food_density / GALPCF;
01118 const double c1 = food_density * specificheat_food * area * ( Tw - Tlower);
01119
01120 if (oven_demand > 0.0)
01121 double aaa=1;
01122
01123
01124 if (c1 <= ROUNDOFF)
01125 return 0.0;
01126
01127 const double cA = -mdot / (food_density * area) + (actual_kW() * BTUPHPKW + oven_UA * (get_Tambient(location) - Tlower)) / c1;
01128 const double cb = (oven_UA / height) * ( Tw - Tlower) / c1;
01129
01130
01131 return cA - cb*h;
01132 }
01133
01134 double range::actual_kW(void)
01135 {
01136 OBJECT *obj = OBJECTHDR(this);
01137 static int trip_counter = 0;
01138 double actual_voltage;
01139
01140
01141 if (heat_needed && re_override != OV_OFF)
01142 {
01143 if(heat_mode == GASHEAT){
01144 return heating_element_capacity;
01145 }
01146
01147 if (pCircuit == NULL)
01148 {
01149 actual_voltage = default_line_voltage;
01150 }
01151 else
01152 {
01153 actual_voltage = (pCircuit->pV->get_complex()).Mag();
01154 }
01155
01156 if (actual_voltage > 2.0*default_line_voltage)
01157 {
01158 if (trip_counter++ > 10)
01159 GL_THROW("oven line voltage for range:%d is too high, exceeds twice nominal voltage.",obj->id);
01160
01161
01162
01163
01164
01165 else
01166 return 0.0;
01167 }
01168 double test = heating_element_capacity * (actual_voltage*actual_voltage) / (default_line_voltage*default_line_voltage);
01169 return test;
01170 }
01171 else
01172 return 0.0;
01173 }
01174
01175 inline double range::new_time_1node(double T0, double T1)
01176 {
01177 const double mdot_Cp = specificheat_food * oven_demand * 60 * food_density / GALPCF;
01178
01179 if (Cw <= ROUNDOFF)
01180 return -1.0;
01181
01182 const double c1 = ((actual_kW()*BTUPHPKW + oven_UA * get_Tambient(location)) + mdot_Cp*Tinlet) / Cw;
01183 const double c2 = -(oven_UA + mdot_Cp) / Cw;
01184
01185 if (fabs(c1 + c2*T1) <= ROUNDOFF || fabs(c1 + c2*T0) <= ROUNDOFF || fabs(c2) <= ROUNDOFF)
01186 return -1.0;
01187
01188 const double new_time = (log(fabs(c1 + c2 * T1)) - log(fabs(c1 + c2 * T0))) / c2;
01189 return new_time;
01190 }
01191
01192 inline double range::new_temp_1node(double T0, double delta_t)
01193 {
01194
01195 const double mdot_Cp = specificheat_food * oven_demand_old * 60 * food_density / GALPCF;
01196
01197
01198 if (Cw <= ROUNDOFF || (oven_UA+mdot_Cp) <= ROUNDOFF)
01199 return T0;
01200
01201 const double c1 = (oven_UA + mdot_Cp) / Cw;
01202 const double c2 = (actual_kW()*BTUPHPKW + mdot_Cp*Tinlet + oven_UA*get_Tambient(location)) / (oven_UA + mdot_Cp);
01203
01204
01205 return c2 - (c2 - T0) * exp(-c1 * delta_t);
01206 }
01207
01208
01209 double range::get_Tambient(enumeration loc)
01210 {
01211 double ratio;
01212 OBJECT *parent = OBJECTHDR(this)->parent;
01213
01214 switch (loc) {
01215 case GARAGE:
01216 ratio = 0.5;
01217 break;
01218 case INSIDE:
01219 default:
01220 ratio = 1.0;
01221 break;
01222 }
01223
01224
01225
01226
01227 return *pTair * ratio + *pTout *(1-ratio);
01228 }
01229
01230 void range::wrong_model(enumeration msg)
01231 {
01232 char *errtxt[] = {"model is not one-zone","model is not two-zone"};
01233 OBJECT *obj = OBJECTHDR(this);
01234 gl_warning("%s (range:%d): %s", obj->name?obj->name:"(anonymous object)", obj->id, errtxt[msg]);
01235 throw msg;
01236 }
01237
01239
01241
01242 EXPORT int create_range(OBJECT **obj, OBJECT *parent)
01243 {
01244 *obj = gl_create_object(range::oclass);
01245 if (*obj!=NULL)
01246 {
01247 range *my = OBJECTDATA(*obj,range);;
01248 gl_set_parent(*obj,parent);
01249 my->create();
01250 return 1;
01251 }
01252 return 0;
01253 }
01254
01255 EXPORT int init_range(OBJECT *obj)
01256 {
01257 range *my = OBJECTDATA(obj,range);
01258 return my->init(obj->parent);
01259 }
01260
01261 EXPORT int isa_range(OBJECT *obj, char *classname)
01262 {
01263 if(obj != 0 && classname != 0){
01264 return OBJECTDATA(obj,range)->isa(classname);
01265 } else {
01266 return 0;
01267 }
01268 }
01269
01270
01271 EXPORT TIMESTAMP sync_range(OBJECT *obj, TIMESTAMP t0, PASSCONFIG pass)
01272 {
01273 range *my = OBJECTDATA(obj, range);
01274 if (obj->clock <= ROUNDOFF)
01275 obj->clock = t0;
01276 try {
01277 TIMESTAMP t1 = TS_NEVER;
01278 switch (pass) {
01279 case PC_PRETOPDOWN:
01280 return my->presync(obj->clock, t0);
01281 case PC_BOTTOMUP:
01282 return my->sync(obj->clock, t0);
01283 case PC_POSTTOPDOWN:
01284 t1 = my->postsync(obj->clock, t0);
01285 obj->clock = t0;
01286 return t1;
01287 default:
01288 throw "invalid pass request";
01289 }
01290 }
01291 catch (int m)
01292 {
01293 gl_error("%s (range:%d) model zone exception (code %d) not caught", obj->name?obj->name:"(anonymous range)", obj->id, m);
01294 }
01295 catch (char *msg)
01296 {
01297 gl_error("%s (range:%d) %s", obj->name?obj->name:"(anonymous range)", obj->id, msg);
01298 }
01299 return TS_INVALID;
01300 }
01301
01302 EXPORT int commit_range(OBJECT *obj)
01303 {
01304 range *my = OBJECTDATA(obj,range);
01305 return my->commit();
01306 }
01307
01308 EXPORT TIMESTAMP plc_range(OBJECT *obj, TIMESTAMP t0)
01309 {
01310
01311 if (obj->clock <= ROUNDOFF)
01312 obj->clock = t0;
01313
01314 range *my = OBJECTDATA(obj,range);
01315 my->thermostat(obj->clock, t0);
01316
01317
01319 return TS_NEVER;
01320 }
01321