00001
00068 #include <stdlib.h>
00069 #include <stdio.h>
00070 #include <errno.h>
00071 #include <math.h>
00072 #include <ctype.h>
00073 #include "gridlabd.h"
00074 #include "solvers.h"
00075 #include "office.h"
00076 #include "../climate/climate.h"
00077
00078
00079 double office::warn_low_temp = 50.0;
00080 double office::warn_high_temp = 90.0;
00081 bool office::warn_control = true;
00082
00083
00084 CLASS *office::oclass = NULL;
00085
00086
00087 office *office::defaults = NULL;
00088
00089
00090 static PASSCONFIG passconfig = PC_PRETOPDOWN|PC_BOTTOMUP;
00091
00092
00093 static PASSCONFIG clockpass = PC_BOTTOMUP;
00094
00095
00096 office::office(MODULE *module)
00097 {
00098 if (oclass==NULL)
00099 {
00100 oclass = gl_register_class(module,"office",sizeof(office),passconfig);
00101 if (gl_publish_variable(oclass,
00102 PT_double, "floor_area[sf]", PADDR(zone.design.floor_area),
00103
00104 PT_double, "floor_height[ft]", PADDR(zone.design.floor_height),
00105 PT_double, "exterior_ua[Btu/degF/h]", PADDR(zone.design.exterior_ua),
00106 PT_double, "interior_ua[Btu/degF/h]", PADDR(zone.design.interior_ua),
00107 PT_double, "interior_mass[Btu/degF]", PADDR(zone.design.interior_mass),
00108
00109 PT_double, "glazing[sf]", PADDR(zone.design.window_area[0]), PT_SIZE, sizeof(zone.design.window_area)/sizeof(zone.design.window_area[0]),
00110 PT_double, "glazing.north[sf]", PADDR(zone.design.window_area[CP_N]),
00111 PT_double, "glazing.northeast[sf]", PADDR(zone.design.window_area[CP_NE]),
00112 PT_double, "glazing.east[sf]", PADDR(zone.design.window_area[CP_E]),
00113 PT_double, "glazing.southeast[sf]", PADDR(zone.design.window_area[CP_SE]),
00114 PT_double, "glazing.south[sf]", PADDR(zone.design.window_area[CP_S]),
00115 PT_double, "glazing.southwest[sf]", PADDR(zone.design.window_area[CP_SW]),
00116 PT_double, "glazing.west[sf]", PADDR(zone.design.window_area[CP_W]),
00117 PT_double, "glazing.northwest[sf]", PADDR(zone.design.window_area[CP_NW]),
00118 PT_double, "glazing.horizontal[sf]", PADDR(zone.design.window_area[CP_H]),
00119 PT_double, "glazing.coefficient[pu]", PADDR(zone.design.glazing_coeff),
00120
00121
00122 PT_double, "occupancy", PADDR(zone.current.occupancy),
00123 PT_double, "occupants", PADDR(zone.design.occupants),
00124 PT_char256, "schedule", PADDR(zone.design.schedule),
00125
00126 PT_double, "air_temperature[degF]", PADDR(zone.current.air_temperature),
00127 PT_double, "mass_temperature[degF]", PADDR(zone.current.mass_temperature),
00128 PT_double, "temperature_change[degF/h]", PADDR(zone.current.temperature_change),
00129 PT_double, "outdoor_temperature[degF]", PADDR(zone.current.out_temp),
00130
00131 PT_double, "Qh[Btu/h]", PADDR(Qh),
00132 PT_double, "Qs[Btu/h]", PADDR(Qs),
00133 PT_double, "Qi[Btu/h]", PADDR(Qi),
00134 PT_double, "Qz[Btu/h]", PADDR(Qz),
00135
00136
00137 PT_enumeration, "hvac_mode", PADDR(zone.hvac.mode),
00138 PT_KEYWORD, "HEAT", HC_HEAT,
00139 PT_KEYWORD, "AUX", HC_AUX,
00140 PT_KEYWORD, "COOL", HC_COOL,
00141 PT_KEYWORD, "ECON", HC_ECON,
00142 PT_KEYWORD, "VENT", HC_VENT,
00143 PT_KEYWORD, "OFF", HC_OFF,
00144 PT_double, "hvac.cooling.balance_temperature[degF]", PADDR(zone.hvac.cooling.balance_temperature),
00145 PT_double, "hvac.cooling.capacity[Btu/h]", PADDR(zone.hvac.cooling.capacity),
00146 PT_double, "hvac.cooling.capacity_perF[Btu/degF/h]",PADDR(zone.hvac.cooling.capacity_perF),
00147 PT_double, "hvac.cooling.design_temperature[degF]", PADDR(zone.hvac.cooling.design_temperature),
00148 PT_double, "hvac.cooling.efficiency[pu]",PADDR(zone.hvac.cooling.efficiency),
00149 PT_double, "hvac.cooling.cop[pu]", PADDR(zone.hvac.cooling.cop),
00150
00151 PT_double, "hvac.heating.balance_temperature[degF]",PADDR(zone.hvac.heating.balance_temperature),
00152 PT_double, "hvac.heating.capacity[Btu/h]",PADDR(zone.hvac.heating.capacity),
00153 PT_double, "hvac.heating.capacity_perF[Btu/degF/h]", PADDR(zone.hvac.heating.capacity_perF),
00154 PT_double, "hvac.heating.design_temperature[degF]", PADDR(zone.hvac.heating.design_temperature),
00155 PT_double, "hvac.heating.efficiency[pu]", PADDR(zone.hvac.heating.efficiency),
00156 PT_double, "hvac.heating.cop[pu]", PADDR(zone.hvac.heating.cop),
00157
00158
00159 PT_double, "lights.capacity[kW]", PADDR(zone.lights.capacity),
00160 PT_double, "lights.fraction[pu]", PADDR(zone.lights.fraction),
00161
00162
00163 PT_double, "plugs.capacity[kW]", PADDR(zone.plugs.capacity),
00164 PT_double, "plugs.fraction[pu]", PADDR(zone.plugs.fraction),
00165
00166
00167 PT_complex, "demand[kW]", PADDR(zone.total.demand),
00168 PT_complex, "total_load[kW]", PADDR(zone.total.power),
00169 PT_complex, "energy[kWh]", PADDR(zone.total.energy),
00170 PT_double, "power_factor", PADDR(zone.total.power_factor),
00171 PT_complex, "power[kW]", PADDR(zone.total.constant_power),
00172 PT_complex, "current[A]", PADDR(zone.total.constant_current),
00173 PT_complex, "admittance[1/Ohm]", PADDR(zone.total.constant_admittance),
00174
00175 PT_complex, "hvac.demand[kW]", PADDR(zone.hvac.enduse.demand),
00176 PT_complex, "hvac.load[kW]", PADDR(zone.hvac.enduse.power),
00177 PT_complex, "hvac.energy[kWh]", PADDR(zone.hvac.enduse.energy),
00178 PT_double, "hvac.power_factor", PADDR(zone.hvac.enduse.power_factor),
00179
00180 PT_complex, "lights.demand[kW]", PADDR(zone.lights.enduse.demand),
00181 PT_complex, "lights.load[kW]", PADDR(zone.lights.enduse.power),
00182 PT_complex, "lights.energy[kWh]", PADDR(zone.lights.enduse.energy),
00183 PT_double, "lights.power_factor", PADDR(zone.lights.enduse.power_factor),
00184 PT_double, "lights.heatgain_fraction", PADDR(zone.lights.enduse.heatgain_fraction),
00185 PT_double, "lights.heatgain[kW]", PADDR(zone.lights.enduse.heatgain),
00186
00187 PT_complex, "plugs.demand[kW]", PADDR(zone.plugs.enduse.demand),
00188 PT_complex, "plugs.load[kW]", PADDR(zone.plugs.enduse.power),
00189 PT_complex, "plugs.energy[kWh]", PADDR(zone.plugs.enduse.energy),
00190 PT_double, "plugs.power_factor", PADDR(zone.plugs.enduse.power_factor),
00191 PT_double, "plugs.heatgain_fraction", PADDR(zone.plugs.enduse.heatgain_fraction),
00192 PT_double, "plugs.heatgain[kW]", PADDR(zone.plugs.enduse.heatgain),
00193
00194 PT_double, "cooling_setpoint[degF]", PADDR(zone.control.cooling_setpoint),
00195 PT_double, "heating_setpoint[degF]", PADDR(zone.control.heating_setpoint),
00196 PT_double, "thermostat_deadband[degF]", PADDR(zone.control.setpoint_deadband),
00197 PT_double, "control.ventilation_fraction", PADDR(zone.control.ventilation_fraction),
00198 PT_double, "control.lighting_fraction", PADDR(zone.control.lighting_fraction),
00199 PT_double, "ACH", PADDR(zone.hvac.minimum_ach),
00200
00201 NULL)<1) throw("unable to publish properties in "__FILE__);
00202 defaults = this;
00203 memset(defaults,0,sizeof(office));
00204
00205
00206 zone.lights.enduse.power_factor = 1.0;
00207 zone.plugs.enduse.power_factor = 1.0;
00208 zone.hvac.enduse.power_factor = 1.0;
00209
00210
00211 static double Tout = 59, RHout=0.75, Solar[9]={0,0,0,0,0,0,0,0,0};
00212 zone.current.pTemperature = &Tout;
00213 zone.current.pHumidity = &RHout;
00214 zone.current.pSolar = Solar;
00215
00216
00217 zone.current.air_temperature = Tout;
00218 zone.current.mass_temperature = Tout;
00219
00220
00221 zone.control.heating_setpoint = 70;
00222 zone.control.cooling_setpoint = 75;
00223 zone.control.setpoint_deadband = 1;
00224 zone.control.ventilation_fraction = 1;
00225 zone.control.lighting_fraction = 0.5;
00226 }
00227 }
00228
00229
00230 int office::create(void)
00231 {
00232 memcpy(this,defaults,sizeof(*this));
00233 return 1;
00234 }
00235
00236
00237
00238
00239
00240
00241
00242
00243
00244
00245 static void occupancy_schedule(char *text, char occupied[24])
00246 {
00247 char days[8];
00248 memset(days,0,sizeof(days));
00249
00250 char hours[27];
00251 memset(hours,0,sizeof(hours));
00252
00253 char *p = text;
00254 char next=-1;
00255 char start=-1, stop=-1;
00256 char *target = days;
00257
00258 for (p=text; true; p++)
00259 {
00260 if (*p==';')
00261 {
00262 occupancy_schedule(p+1,occupied);
00263 }
00264 if (isdigit(*p))
00265 {
00266 if (next==-1) next = 0;
00267 next += (next*10+(*p-'0'));
00268 continue;
00269 }
00270 else if (*p=='*')
00271 {
00272 start = 0; next = -1;
00273 continue;
00274 }
00275 else if (*p==',' || *p==' ' || *p==';' || *p=='\0')
00276 {
00277 char n;
00278 stop = next;
00279 for (n=start; n<=(stop>=0?stop:(target==days?8:24)); n++)
00280 target[n]=1;
00281 if (*p==',')
00282 continue;
00283 else if (*p==' ')
00284 {
00285 target = hours;
00286 start = -1; stop = -1; next = -1;
00287 continue;
00288 }
00289 else if (*p==';' || *p=='\0')
00290 {
00291 char d,h;
00292 for (d=0; d<8; d++)
00293 for (h=0; h<24; h++)
00294 if (days[d] && hours[h])
00295 SET_OCCUPIED(d,h);
00296 if (*p=='\0')
00297 break;
00298 else
00299 {
00300 start = -1; stop = -1; next = -1;
00301 continue;
00302 }
00303 }
00304 else
00305 throw "office/occupancy_schedule(): invalid parser state";
00306
00307 }
00308 else if (*p=='-')
00309 {
00310 start = next;
00311 stop = -1;
00312 next = -1;
00313 continue;
00314 }
00315 else
00316 throw "office/occupancy_schedule(): schedule syntax error";
00317 }
00318 }
00319
00320
00321 int office::init(OBJECT *parent)
00322 {
00323 double oversize = 1.2;
00324 update_control_setpoints();
00325
00326
00327
00328 if (zone.design.floor_area == 0)
00329 zone.design.floor_area = 10000;
00330 if (zone.design.floor_height == 0)
00331 zone.design.floor_height = 9;
00332 if (zone.design.interior_mass == 0)
00333 zone.design.interior_mass = 40000;
00334 if (zone.design.interior_ua == 0)
00335 zone.design.interior_ua = 1;
00336 if (zone.design.exterior_ua == 0)
00337 zone.design.exterior_ua = .375;
00338
00339 if (zone.hvac.cooling.design_temperature == 0)
00340 zone.hvac.cooling.design_temperature = 93;
00341 if (zone.hvac.heating.design_temperature == 0)
00342 zone.hvac.heating.design_temperature = -6;
00343
00344
00345 if (zone.hvac.minimum_ach==0)
00346 zone.hvac.minimum_ach = 1.5;
00347 if (zone.control.economizer_cutin==0)
00348 zone.control.economizer_cutin=60;
00349 if (zone.control.auxiliary_cutin==0)
00350 zone.control.auxiliary_cutin=2;
00351
00352
00353 if (strcmp(zone.design.schedule,"")==0)
00354 strcpy(zone.design.schedule,"1-5 8-17");
00355 occupancy_schedule(zone.design.schedule,occupied);
00356
00357
00358 if (zone.hvac.heating.capacity==0)
00359 zone.hvac.heating.capacity = oversize*(zone.design.exterior_ua*(zone.control.heating_setpoint-zone.hvac.heating.design_temperature)
00360 - (zone.hvac.heating.design_temperature - zone.control.heating_setpoint) * (0.2402 * 0.0735 * zone.design.floor_height * zone.design.floor_area) * zone.hvac.minimum_ach);
00361 if (zone.hvac.cooling.capacity==0)
00362 zone.hvac.cooling.capacity = oversize*(-zone.design.exterior_ua*(zone.hvac.cooling.design_temperature-zone.control.cooling_setpoint)
00363 - (zone.design.window_area[0]+zone.design.window_area[1]+zone.design.window_area[2]+zone.design.window_area[3]+zone.design.window_area[4]
00364 +zone.design.window_area[5]+zone.design.window_area[6]+zone.design.window_area[7]+zone.design.window_area[8])*100*3.412*zone.design.glazing_coeff
00365 - (zone.hvac.cooling.design_temperature - zone.control.cooling_setpoint) * (0.2402 * 0.0735 * zone.design.floor_height * zone.design.floor_area) * zone.hvac.minimum_ach
00366 - (zone.lights.capacity + zone.plugs.capacity)*3.412);
00367 if (zone.hvac.cooling.cop==0)
00368 zone.hvac.cooling.cop=-3;
00369 if (zone.hvac.heating.cop==0)
00370 zone.hvac.heating.cop= 1.25;
00371
00372 OBJECT *hdr = OBJECTHDR(this);
00373
00374
00375 static FINDLIST *climates = gl_find_objects(FL_NEW,FT_CLASS,SAME,"climate",FT_END);
00376 if (climates==NULL)
00377 gl_warning("office: no climate data found, using static data");
00378 else if (climates->hit_count>1)
00379 gl_warning("house: %d climates found, using first one defined", climates->hit_count);
00380 if (climates->hit_count>0)
00381 {
00382 OBJECT *obj = gl_find_next(climates,NULL);
00383 if (obj->rank<=hdr->rank)
00384 gl_set_dependent(obj,hdr);
00385 zone.current.pTemperature = (double*)GETADDR(obj,gl_get_property(obj,"temperature"));
00386 zone.current.pHumidity = (double*)GETADDR(obj,gl_get_property(obj,"humidity"));
00387 zone.current.pSolar = (double*)GETADDR(obj,gl_get_property(obj,"solar_flux"));
00388 }
00389
00390
00391 struct {
00392 char *desc;
00393 bool test;
00394 } map[] = {
00395
00396 {"floor height is not valid", zone.design.floor_height<=0},
00397 {"interior mass is not valid", zone.design.interior_mass<=0},
00398 {"interior UA is not valid", zone.design.interior_ua<=0},
00399 {"exterior UA is not valid", zone.design.exterior_ua<=0},
00400 {"floor area is not valid",zone.design.floor_area<=0},
00401 {"control setpoint deadpoint is invalid", zone.control.setpoint_deadband<=0},
00402 {"heating and cooling setpoints conflict",TheatOn>=TcoolOff},
00403 {"cooling capacity is not negative", zone.hvac.cooling.capacity>=0},
00404 {"heating capacity is not positive", zone.hvac.heating.capacity<=0},
00405 {"cooling cop is not negative", zone.hvac.cooling.cop>=0},
00406 {"heating cop is not positive", zone.hvac.heating.cop<=0},
00407 {"minimum ach is not positive", zone.hvac.minimum_ach<=0},
00408 {"auxiliary cutin is not positive", zone.control.auxiliary_cutin<=0},
00409 {"economizer cutin is above cooling setpoint deadband", zone.control.economizer_cutin>=zone.control.cooling_setpoint-zone.control.setpoint_deadband},
00410 };
00411 int i;
00412 for (i=0; i<sizeof(map)/sizeof(map[0]); i++)
00413 {
00414 if (map[i].test)
00415 throw map[i].desc;
00416 }
00417 return 1;
00418 }
00419
00420
00421 TIMESTAMP office::presync(TIMESTAMP t0, TIMESTAMP t1)
00422 {
00423 DATETIME dt;
00424
00425
00426 Qz = 0;
00427
00428
00429 zone.current.out_temp = *(zone.current.pTemperature);
00430
00431
00432 if (t0>0)
00433 {
00434 int day;
00435 int hour;
00436
00437 gl_localtime(t0, &dt);
00438
00439 day = dt.weekday;
00440 hour = dt.hour;
00441
00442 if (zone.design.schedule[0]!='\0' )
00443 zone.current.occupancy = IS_OCCUPIED(day,hour);
00444 }
00445 return TS_NEVER;
00446 }
00447
00448
00449 TIMESTAMP office::sync(TIMESTAMP t0, TIMESTAMP t1)
00450 {
00451
00452 update_lighting(t0,t1);
00453 update_plugs(t0,t1);
00454
00455
00456 const double &Tout = (*(zone.current.pTemperature));
00457 const double &Ua = (zone.design.exterior_ua);
00458 const double &Cm = (zone.design.interior_mass);
00459 const double &Um = (zone.design.interior_ua);
00460 double &Ti = (zone.current.air_temperature);
00461 double &dTi = (zone.current.temperature_change);
00462 double &Tm = (zone.current.mass_temperature);
00463 HCMODE &mode = (zone.hvac.mode);
00464
00465
00466
00467 const double dt1 = t0>0 ? (double)(t1-t0)*TS_SECOND : 0;
00468 if (dt1>0)
00469 {
00470 const double dt = dt1/3600;
00471
00472
00473 if (c2!=0)
00474 {
00475
00476 const double e1 = k1*exp(r1*dt);
00477 const double e2 = k2*exp(r2*dt);
00478 Ti = e1 + e2 + Teq;
00479 Tm = ((r1-c1)*e1 + (r2-c1)*e2 + c6)/c2 + Teq;
00480
00481 if (warn_control)
00482 {
00483
00484 if (Ti<warn_low_temp || Ti>warn_high_temp)
00485 {
00486 OBJECT *obj = OBJECTHDR(this);
00487 DATETIME dt0, dt1;
00488 gl_localtime(t0,&dt0);
00489 gl_localtime(t1,&dt1);
00490 char ts0[64], ts1[64];
00491 gl_warning("office:%d (%s) air temperature excursion (%.1f degF) at between %s and %s",
00492 obj->id, obj->name?obj->name:"anonymous", Ti,
00493 gl_strtime(&dt0,ts0,sizeof(ts0))?ts0:"UNKNOWN", gl_strtime(&dt1,ts1,sizeof(ts1))?ts1:"UNKNOWN");
00494 }
00495
00496
00497 if (Tm<warn_low_temp || Tm>warn_high_temp)
00498 {
00499 OBJECT *obj = OBJECTHDR(this);
00500 DATETIME dt0, dt1;
00501 gl_localtime(t0,&dt0);
00502 gl_localtime(t1,&dt1);
00503 char ts0[64], ts1[64];
00504 gl_warning("office:%d (%s) mass temperature excursion (%.1f degF) at between %s and %s",
00505 obj->id, obj->name?obj->name:"anonymous", Tm,
00506 gl_strtime(&dt0,ts0,sizeof(ts0))?ts0:"UNKNOWN", gl_strtime(&dt1,ts1,sizeof(ts1))?ts1:"UNKNOWN");
00507 }
00508 }
00509
00510
00511 zone.total.energy += zone.total.power * dt;
00512 }
00513
00514 const double Ca = 0.2402 * 0.0735 * zone.design.floor_height * zone.design.floor_area;
00515
00516
00517 Qi = zone.lights.enduse.heatgain + zone.plugs.enduse.heatgain;
00518
00519
00520 Qs = 0;
00521 int i;
00522 for (i=0; i<9; i++)
00523 Qs += zone.design.window_area[i] * zone.current.pSolar[i]/10;
00524 Qs *= 3.412;
00525 if (Qs<0)
00526 throw "solar gain is negative?!?";
00527
00528
00529 Qh = update_hvac();
00530
00531 if (Ca<=0)
00532 throw "Ca must be positive";
00533 if (Cm<=0)
00534 throw "Cm must be positive";
00535
00536
00537 double f_air = 1.0;
00538 double Qa = Qh + f_air*(Qi + Qs);
00539 double Qm = (1-f_air)*(Qi + Qs);
00540
00541 c1 = -(Ua + Um)/Ca;
00542 c2 = Um/Ca;
00543 c3 = (Qa + Tout*Ua)/Ca;
00544 c6 = Qm/Cm;
00545 c7 = Qa/Ca;
00546 double p1 = 1/c2;
00547 if (Cm<=0)
00548 throw "Cm must be positive";
00549 c4 = Um/Cm;
00550 c5 = -c4;
00551 if (c2<=0)
00552 throw "Um must be positive";
00553 double p2 = -(c5+c1)/c2;
00554 double p3 = c1*c5/c2 - c4;
00555 double p4 = -c3*c5/c2 + c6;
00556 if (p3==0)
00557 throw "Teq is not finite";
00558 Teq = p4/p3;
00559
00560
00561 if (p1==0)
00562 throw "internal error (p1==0 -> Ca==0 which should have caught)";
00563 const double ra = 2*p1;
00564 const double rb = -p2/ra;
00565 const double rr = p2*p2-4*p1*p3;
00566 if (rr<0)
00567 throw "thermal solution does not exist";
00568 const double rc = sqrt(rr)/ra;
00569 r1 = rb+rc;
00570 r2 = rb-rc;
00571 if (r1>0 || r2>0)
00572 throw "thermal solution has runaway condition";
00573
00574
00575 dTi = c2*Tm + c1*Ti - (c1+c2)*Tout + c7;
00576 k1 = (r2*Ti - r2*Teq - dTi)/(r2-r1);
00577 k2 = (dTi - r1*k1)/r2;
00578
00579
00580 zone.total.power = zone.lights.enduse.power + zone.plugs.enduse.power + zone.hvac.enduse.power;
00581
00582 if (warn_control)
00583 {
00584
00585 if ((mode==HC_HEAT || mode==HC_AUX) && Teq<TheatOff)
00586 {
00587 OBJECT *obj = OBJECTHDR(this);
00588 DATETIME dt0, dt1;
00589 gl_localtime(t0,&dt0);
00590 gl_localtime(t1,&dt1);
00591 char ts0[64], ts1[64];
00592 gl_warning("office:%d (%s) %s heating undersized between %s and %s",
00593 obj->id, obj->name?obj->name:"anonymous", mode==HC_HEAT?"primary":"auxiliary",
00594 gl_strtime(&dt0,ts0,sizeof(ts0))?ts0:"UNKNOWN", gl_strtime(&dt1,ts1,sizeof(ts1))?ts1:"UNKNOWN");
00595 }
00596
00597
00598 else if (mode==HC_COOL && Teq>TcoolOff)
00599 {
00600 OBJECT *obj = OBJECTHDR(this);
00601 DATETIME dt0, dt1;
00602 gl_localtime(t0,&dt0);
00603 gl_localtime(t1,&dt1);
00604 char ts0[64], ts1[64];
00605 gl_warning("office:%d (%s) cooling undersized between %s and %s",
00606 obj->id, obj->name?obj->name:"anonymous", mode==HC_COOL?"COOL":"ECON",
00607 gl_strtime(&dt0,ts0,sizeof(ts0))?ts0:"UNKNOWN", gl_strtime(&dt1,ts1,sizeof(ts1))?ts1:"UNKNOWN");
00608 }
00609
00610
00611 else if (mode==HC_ECON && Teq>TcoolOff)
00612 {
00613 OBJECT *obj = OBJECTHDR(this);
00614 DATETIME dt;
00615 gl_localtime(t1,&dt);
00616 char ts[64];
00617 gl_warning("office:%d (%s) insufficient economizer control at %s",
00618 obj->id, obj->name?obj->name:"anonymous", gl_strtime(&dt,ts,sizeof(ts))?ts:"UNKNOWN");
00619 }
00620 }
00621 }
00622
00623
00624 if (Tevent == Teq)
00625 return -(t1+(TIMESTAMP)(3600*TS_SECOND));
00626
00627
00628 double dt2=(double)TS_NEVER;
00629 dt2 = e2solve(k1,r1,k2,r2,Teq-Tevent)*3600;
00630 if (isnan(dt2) || !isfinite(dt2) || dt2<0)
00631 {
00632 if (dTi==0)
00633
00634 return -(t1+(TIMESTAMP)(3600*TS_SECOND));
00635
00636
00637 dt2 = fabs(3600/dTi);
00638 if (dt2>3600)
00639 dt2 = 3600;
00640 return -(t1+(TIMESTAMP)(dt2*TS_SECOND));
00641 }
00642 if (dt2<TS_SECOND)
00643 return t1+1;
00644 else
00645 return t1+(TIMESTAMP)(dt2*TS_SECOND);
00646 }
00647
00648 void office::update_control_setpoints()
00649 {
00650 TcoolOn = zone.control.cooling_setpoint + zone.control.setpoint_deadband;
00651 TcoolOff = zone.control.cooling_setpoint - zone.control.setpoint_deadband;
00652 TheatOn = zone.control.heating_setpoint - zone.control.setpoint_deadband;
00653 TheatOff = zone.control.heating_setpoint + zone.control.setpoint_deadband;
00654 if (TcoolOff - TheatOff <= 0)
00655 throw (char *)"thermostat deadband causes heating/cooling turn-off points to overlap";
00656 zone.control.ventilation_fraction = zone.current.occupancy>0 ? zone.hvac.minimum_ach : 0;
00657 }
00658
00659 TIMESTAMP office::update_lighting(TIMESTAMP t0, TIMESTAMP t1)
00660 {
00661
00662 zone.lights.enduse.power.SetPowerFactor(zone.lights.capacity *
00663 zone.lights.fraction, zone.lights.enduse.power_factor, J);
00664
00665 if (t0>0 && t1>t0)
00666 zone.lights.enduse.energy += zone.lights.enduse.power * gl_tohours(t1-t0);
00667
00668 zone.lights.enduse.heatgain = zone.lights.enduse.power.Mag() *
00669 zone.lights.enduse.heatgain_fraction;
00670 return TS_NEVER;
00671 }
00672
00673 TIMESTAMP office::update_plugs(TIMESTAMP t0, TIMESTAMP t1)
00674 {
00675
00676 zone.plugs.enduse.power.SetPowerFactor(zone.plugs.capacity *
00677 zone.plugs.fraction, zone.plugs.enduse.power_factor, J);
00678
00679 if (t0>0 && t1>t0)
00680 zone.plugs.enduse.energy += zone.plugs.enduse.power * gl_tohours(t1-t0);
00681
00682 zone.plugs.enduse.heatgain = zone.plugs.enduse.power.Mag() *
00683 zone.plugs.enduse.heatgain_fraction;
00684 return TS_NEVER;
00685 }
00686
00687 double office::update_hvac()
00688 {
00689 const double &Ti = (zone.current.air_temperature);
00690 const double &dTi = (zone.current.temperature_change);
00691 const double &Tout = (*(zone.current.pTemperature));
00692 const double Trange = 40;
00693 const double Taux = zone.hvac.heating.balance_temperature-Trange;
00694 const double &Tecon = zone.hvac.cooling.balance_temperature;
00695 const double &TbalHeat = zone.hvac.heating.balance_temperature;
00696 const double &TmaxCool = zone.hvac.cooling.design_temperature;
00697 HCMODE &mode = zone.hvac.mode;
00698
00699
00700 double Qvent = 0;
00701 double Qactive = 0;
00702
00703 switch (mode) {
00704 case HC_OFF:
00705 cop = 0;
00706 Qactive = Qvent = 0;
00707 if (dTi<0 && Ti<TcoolOn)
00708 Tevent = TheatOn;
00709 else if (dTi>0 && Ti>TheatOn)
00710 Tevent = TcoolOn;
00711 else
00712 Tevent = Teq;
00713 break;
00714 case HC_HEAT:
00715 cop = 1.0 + (zone.hvac.heating.cop-1)*(Tout-Taux)/Trange;
00716 Qactive = zone.hvac.heating.capacity + zone.hvac.heating.capacity_perF*(zone.hvac.heating.balance_temperature-Tout);
00717 Qvent = ((*zone.current.pTemperature) - zone.current.air_temperature) * (0.2402 * 0.0735 * zone.design.floor_height * zone.design.floor_area) * zone.control.ventilation_fraction;
00718 Tevent = TheatOff;
00719 break;
00720 case HC_AUX:
00721 cop = 1.0;
00722 Qactive = zone.hvac.heating.capacity;
00723 Qvent = ((*zone.current.pTemperature) - zone.current.air_temperature) * (0.2402 * 0.0735 * zone.design.floor_height * zone.design.floor_area) * zone.control.ventilation_fraction;
00724 Tevent = TheatOff;
00725 break;
00726 case HC_COOL:
00727 cop = -1.0 - (zone.hvac.cooling.cop+1)*(Tout-TmaxCool)/(TmaxCool-Tecon);
00728 Qactive = zone.hvac.cooling.capacity - zone.hvac.cooling.capacity_perF*(Tout-zone.hvac.cooling.balance_temperature);
00729 Qvent = ((*zone.current.pTemperature) - zone.current.air_temperature) * (0.2402 * 0.0735 * zone.design.floor_height * zone.design.floor_area) * zone.control.ventilation_fraction;
00730 Tevent = TcoolOff;
00731 break;
00732 case HC_VENT:
00733 cop = 0;
00734 Qactive = 0;
00735 Qvent = ((*zone.current.pTemperature) - zone.current.air_temperature) * (0.2402 * 0.0735 * zone.design.floor_height * zone.design.floor_area) * zone.control.ventilation_fraction;
00736 if (dTi<0 && Ti<TcoolOn)
00737 Tevent = TheatOn;
00738 else if (dTi>0 && Ti>TheatOn)
00739 Tevent = TcoolOn;
00740 else
00741 Tevent = Teq;
00742 break;
00743 case HC_ECON:
00744 cop = 0.0;
00745 Qactive = 0;
00746 Qvent = ((*zone.current.pTemperature) - zone.current.air_temperature) * (0.2402 * 0.0735 * zone.design.floor_height * zone.design.floor_area) * zone.control.ventilation_fraction;
00747 Tevent = TcoolOff;
00748 break;
00749 default:
00750 throw "hvac mode is invalid";
00751 break;
00752 }
00753
00754
00755 if (Qactive!=0)
00756 zone.hvac.enduse.power.SetPowerFactor(Qactive/cop/1000,zone.hvac.enduse.power_factor);
00757 else
00758 zone.hvac.enduse.power = complex(0,0);
00759
00760
00761 if (Qvent!=0)
00762 zone.hvac.enduse.power += complex(.1,-0.01)/1000 * zone.design.floor_area;
00763
00764 zone.hvac.enduse.energy += zone.hvac.enduse.power;
00765 if (zone.hvac.enduse.power.Re()<0)
00766 throw "hvac unit is generating electricity";
00767 else if (!isfinite(zone.hvac.enduse.power.Re()) || !isfinite(zone.hvac.enduse.power.Im()))
00768 throw "hvac power is not finite";
00769 return Qvent + Qactive;
00770 }
00771
00772 TIMESTAMP office::plc(TIMESTAMP t0, TIMESTAMP t1)
00773 {
00774 const double &Tout = *(zone.current.pTemperature);
00775 const double &Tair = zone.current.air_temperature;
00776 const double &Tmass = zone.current.mass_temperature;
00777 const double &Tecon = zone.control.economizer_cutin;
00778 const double &Taux = zone.control.heating_setpoint-zone.control.auxiliary_cutin;
00779 const double &MinAch = zone.hvac.minimum_ach;
00780 HCMODE &mode = zone.hvac.mode;
00781 double &vent = zone.control.ventilation_fraction;
00782
00783
00784 update_control_setpoints();
00785
00786 if (Tair>TheatOff && Tair<TcoolOff )
00787 {
00788 if (vent>0)
00789 mode = HC_VENT;
00790 else
00791
00792 {
00793 mode = HC_OFF;
00794 }
00795 }
00796 else if (Tair<=TheatOn || (mode == HC_AUX || mode == HC_HEAT))
00797 {
00798 if (Tair<=Taux)
00799 mode = HC_AUX;
00800 else
00801 mode = HC_HEAT;
00802 }
00803 else if (Tair>=TcoolOn || (mode == HC_ECON || mode == HC_COOL))
00804 {
00805 if (Tout<Tecon)
00806 {
00807
00808 double Qgain = Qs + Qi - zone.design.exterior_ua*(Tair - Tout) - zone.design.interior_ua*(Tair-Tmass);
00809 vent = Qgain / ((Tair - Tout) * (0.2402 * 0.0735 * zone.design.floor_height * zone.design.floor_area));
00810 if (vent<MinAch)
00811 {
00812 vent = MinAch;
00813 mode = HC_ECON;
00814 }
00815 else if (vent>5.0)
00816 {
00817 if (Tout>Tair)
00818 vent = MinAch;
00819 mode = HC_COOL;
00820 }
00821 else
00822 {
00823 mode = HC_ECON;
00824 }
00825 }
00826 else
00827 mode = HC_COOL;
00828 }
00829
00830 return TS_NEVER;
00831 }
00832
00834
00836
00837 EXPORT int create_office(OBJECT **obj, OBJECT *parent)
00838 {
00839 try {
00840 *obj = gl_create_object(office::oclass);
00841 if (*obj!=NULL)
00842 {
00843 office *my = OBJECTDATA(*obj,office);
00844 gl_set_parent(*obj,parent);
00845 return my->create();
00846 }
00847 return 0;
00848 } catch (char *msg) {
00849 gl_error("create_office: %s", msg);
00850 return 0;
00851 }
00852 }
00853
00854 EXPORT int init_office(OBJECT *obj, OBJECT *parent)
00855 {
00856 try {
00857 if (obj!=NULL)
00858 return OBJECTDATA(obj,office)->init(parent);
00859 return 0;
00860 } catch (char *msg) {
00861 gl_error("init_%s(obj=%d;%s): %s", obj->oclass->name, obj->id, obj->name?obj->name:"unnamed", msg);
00862 return 0;
00863 }
00864 }
00865
00866 EXPORT TIMESTAMP sync_office(OBJECT *obj, TIMESTAMP t1, PASSCONFIG pass)
00867 {
00868 try {
00869 TIMESTAMP t2 = TS_NEVER;
00870 office *my = OBJECTDATA(obj,office);
00871 switch (pass) {
00872 case PC_PRETOPDOWN:
00873 t2 = my->presync(obj->clock,t1);
00874 break;
00875 case PC_BOTTOMUP:
00876 t2 = my->sync(obj->clock,t1);
00877 break;
00878 default:
00879 throw("invalid pass request");
00880 break;
00881 }
00882 if (pass==clockpass)
00883 obj->clock = t1;
00884 return t2;
00885 } catch (char *msg) {
00886 gl_error("sync_office(obj=%d;%s): %s", obj->id, obj->name?obj->name:"unnamed", msg);
00887 return TS_INVALID;
00888 }
00889 }
00890
00891 EXPORT TIMESTAMP plc_office(OBJECT *obj, TIMESTAMP t1)
00892 {
00893 try {
00894 return OBJECTDATA(obj,office)->plc(obj->clock,t1);
00895 } catch (char *msg) {
00896 gl_error("plc_%s(obj=%d;%s): %s", obj->oclass->name, obj->id, obj->name?obj->name:"unnamed", msg);
00897 return TS_INVALID;
00898 }
00899 }
00900