00001
00076 #include <stdlib.h>
00077 #include <stdio.h>
00078 #include <errno.h>
00079 #include <math.h>
00080 #include <ctype.h>
00081
00082 #include <gridlabd.h>
00083
00084 #include "evcharger.h"
00085
00089 typedef struct s_evdemandprofilelist {
00090 EVDEMAND *data;
00091 struct s_evdemandprofilelist *next;
00092 } EVPROFILEITEM;
00093
00094 EVPROFILEITEM *first_demand_profile = NULL;
00095
00098 EVDEMAND *find_demand_profile(char *name)
00099 {
00100 EVPROFILEITEM *item = first_demand_profile;
00101 while (item!=NULL)
00102 {
00103 if (strcmp(name,item->data->name)==0)
00104 break;
00105 else
00106 item = item->next;
00107 }
00108 return item?item->data:NULL;
00109 }
00112 EVPROFILEITEM *add_demand_profile(EVDEMAND *data)
00113 {
00114 EVPROFILEITEM *item = new EVPROFILEITEM;
00115 if (item==NULL)
00116 GL_THROW( "evcharter.cpp: add_demand_profile() memory allocation failed" );
00117 item->data = data;
00118 item->next = first_demand_profile;
00119 return first_demand_profile = item;
00120 }
00121 EVDEMAND *load_demand_profile(char *filename)
00122 {
00123 char filepath[1024];
00124 if (gl_findfile(filename,NULL,R_OK,filepath,sizeof(filepath))==NULL)
00125 {
00126
00127 GL_THROW( "unable to find demand profile" );
00128 }
00129 EVDEMAND *data = new EVDEMAND;
00130 if (data==NULL)
00131 GL_THROW( "unable to allocate memory for new demand profile" );
00132 memset(data,0,sizeof(EVDEMAND));
00133
00134 int daytype = WEEKDAY;
00135 int linenum=0;
00136 int hour=0;
00137
00138 strcpy(data->name,filepath);
00139 FILE *fp = fopen(filepath,"r");
00140 if (fp==NULL)
00141 {
00142
00143 GL_THROW( "unable to read demand profile in '%s' at line %d", filename, linenum );
00144 }
00145 char line[1024];
00146 double *hdr[8]; memset(hdr,0,sizeof(hdr));
00147 while (!feof(fp) && fgets(line,sizeof(line),fp)!=NULL)
00148 {
00149 linenum++;
00150
00151
00152 if (isspace(line[0]))
00153 continue;
00154 if (line[0]=='#')
00155 continue;
00156 if (line[0]=='[')
00157 {
00158 char blockname[8];
00159 if (sscanf(line,"[%7s]",blockname)==1)
00160 {
00161 if (strcmp(blockname,"WEEKDAY")==0)
00162 daytype = WEEKDAY;
00163 else if (strcmp(blockname,"WEEKEND")==0)
00164 daytype = WEEKEND;
00165 else
00166 {
00167 GL_THROW("%s(%d): '%s' is an invalid daytype name",filename,linenum,blockname);
00168
00169 }
00170 data->n_daytypes++;
00171 }
00172 else
00173 {
00174 GL_THROW("%s(%d): daytype name syntax is not recognized",filename,linenum,blockname);
00175
00176 }
00177 }
00178 else if (isalpha(line[0]))
00179 {
00180 char dir[4], trip[5];
00181 int event;
00182 int nhdr = 0;
00183 int offset=0;
00184 while (sscanf(line+offset,"%3s%4s",dir,trip)==2)
00185 {
00186 if (nhdr>sizeof(hdr)/sizeof(hdr[0]))
00187 {
00188 gl_output("%s(%d): too many heading items listed (max is %d)",filename,linenum,sizeof(hdr)/sizeof(hdr[0]));
00189 GL_THROW( "too many heading items in demand profile" );
00190 }
00191 if (strcmp(dir,"ARR")==0)
00192 event = ARRIVE;
00193 else if (strcmp(dir,"DEP")==0)
00194 event = DEPART;
00195 else
00196 {
00197 GL_THROW("%s(%d): '%s' is not an appropriate event direction (e.g., ARR, DEP)",filename,linenum,dir);
00198
00199 }
00200 if (strcmp(trip,"HOME")==0)
00201 hdr[nhdr++] = data->home[daytype][event];
00202 else if (strcmp(trip,"WORK")==0)
00203 hdr[nhdr++] = data->work[daytype][event];
00204 else if (strcmp(trip,"SHRT")==0)
00205 hdr[nhdr++] = data->shorttrip[daytype][event];
00206 else if (strcmp(trip,"LONG")==0)
00207 hdr[nhdr++] = data->longtrip[daytype][event];
00208 else
00209 {
00210 GL_THROW("%s(%d): '%s' is not an appropriate trip location (e.g., HOME, WORK)",filename,linenum,dir);
00211
00212 }
00213 offset += 8;
00214 }
00215 }
00216 else if (isdigit(line[0]))
00217 {
00218 double prob;
00219 int offset = 0;
00220 int col = 0;
00221 while (sscanf(line+offset,"%lf",&prob)==1)
00222 {
00223 hdr[col++][hour] = prob;
00224 while (line[offset]!='\0' && line[offset++]!=',') {}
00225 }
00226 hour++;
00227 }
00228 else
00229 {
00230 }
00231 }
00232 if (ferror(fp))
00233 {
00234 gl_error("%s(%d): %s",filename,linenum,strerror(errno));
00235 GL_THROW( "unable to read demand profile" );
00236 }
00237 fclose(fp);
00238 return data;
00239 }
00240 EVDEMAND *get_demand_profile(char *name)
00241 {
00242 EVDEMAND *profile = find_demand_profile(name);
00243 if (profile==NULL)
00244 {
00245 profile = load_demand_profile(name);
00246 if (profile!=NULL)
00247 add_demand_profile(profile);
00248 }
00249 return profile;
00250 }
00251
00253
00255 CLASS* evcharger::oclass = NULL;
00256 CLASS* evcharger::pclass = NULL;
00257 evcharger *evcharger::defaults = NULL;
00258
00259 evcharger::evcharger(MODULE *module) : residential_enduse(module)
00260 {
00261
00262 if (oclass==NULL)
00263 {
00264
00265 oclass = gl_register_class(module,"evcharger",sizeof(evcharger),PC_BOTTOMUP|PC_AUTOLOCK);
00266 if (oclass==NULL)
00267 throw "unable to register class evcharger";
00268 else
00269 oclass->trl = TRL_DEMONSTRATED;
00270
00271
00272
00273 if (gl_publish_variable(oclass,
00274 PT_INHERIT, "residential_enduse",
00275 PT_enumeration,"charger_type",PADDR(charger_type),
00276 PT_KEYWORD,"LOW",(enumeration)CT_LOW,
00277 PT_KEYWORD,"MEDIUM",(enumeration)CT_MEDIUM,
00278 PT_KEYWORD,"HIGH",(enumeration)CT_HIGH,
00279 PT_enumeration,"vehicle_type",PADDR(vehicle_type),
00280 PT_KEYWORD,"ELECTRIC",(enumeration)VT_ELECTRIC,
00281 PT_KEYWORD,"HYBRID",(enumeration)VT_HYBRID,
00282 PT_enumeration,"state",PADDR(vehicle_state),
00283 PT_KEYWORD,"UNKNOWN",(enumeration)VS_UNKNOWN,
00284 PT_KEYWORD,"HOME",(enumeration)VS_HOME,
00285 PT_KEYWORD,"WORK",(enumeration)VS_WORK,
00286
00287
00288
00289 PT_double,"p_go_home[unit/h]",PADDR(demand.home),
00290 PT_double,"p_go_work[unit/h]",PADDR(demand.work),
00291
00292
00293
00294 PT_double,"work_dist[mile]",PADDR(distance.work),
00295
00296
00297
00298 PT_double,"capacity[kWh]",PADDR(capacity),
00299 PT_double,"charge[unit]",PADDR(charge),
00300 PT_bool,"charge_at_work",PADDR(charge_at_work),
00301 PT_double,"charge_throttle[unit]", PADDR(charge_throttle),
00302 PT_double,"charger_efficiency[unit]", PADDR(charging_efficiency),PT_DESCRIPTION, "Efficiency of the charger in terms of energy in to battery stored",
00303 PT_double,"power_train_efficiency[mile/kWh]", PADDR(mileage), PT_DESCRIPTION, "Miles per kWh of battery charge",
00304 PT_double,"mileage_classification[mile]", PADDR(mileage_classification), PT_DESCRIPTION, "Miles expected range on battery only",
00305 PT_char1024,"demand_profile", PADDR(demand_profile),
00306 NULL)<1)
00307 GL_THROW("unable to publish properties in %s",__FILE__);
00308 }
00309 }
00310
00311 evcharger::~evcharger()
00312 {
00313 }
00314
00315 int evcharger::create()
00316 {
00317 int res = residential_enduse::create();
00318
00319
00320 load.name = oclass->name;
00321 load.power = load.admittance = load.current = load.total = complex(0,0,J);
00322 vehicle_type = VT_HYBRID;
00323
00324 charging_efficiency = 1.0;
00325 mileage_classification = 0.0;
00326 mileage = 0.0;
00327
00328 return res;
00329 }
00330
00331
00332 static double fuse[] = {15,35,70};
00333 static double amps[] = {12,28,55};
00334 static bool hiV[] = {false,true,true};
00335
00336 int evcharger::init(OBJECT *parent)
00337 {
00338 int retval;
00339
00340 if(parent != NULL){
00341 if((parent->flags & OF_INIT) != OF_INIT){
00342 char objname[256];
00343 gl_verbose("evcharger::init(): deferring initialization on %s", gl_name(parent, objname, 255));
00344 return 2;
00345 }
00346 }
00347 static double sizes[] = {20,30,30,40,40,40,50,50,50,50,60,60,60,70,70,80};
00348
00349 if (mileage==0) mileage = gl_random_uniform(RNGSTATE,0.8,1.2);
00350
00351
00352 if ((mileage_classification > 0.0) && (mileage > 0.0))
00353 {
00354 capacity=mileage_classification/mileage;
00355 }
00356
00357 if (capacity==0) capacity = gl_random_sampled(RNGSTATE,sizeof(sizes)/sizeof(sizes[0]),sizes);
00358 if (power_factor==0) power_factor = 0.95;
00359 if (charge==0) charge = gl_random_uniform(RNGSTATE,0.25,1.0);
00360 if (charge_throttle==0) charge_throttle = 1.0;
00361 if (mileage==0) mileage = gl_random_uniform(RNGSTATE,0.8,1.2);
00362 if (distance.work==0) distance.work = gl_random_lognormal(RNGSTATE,3,1);
00363
00364
00365
00366
00367 OBJECT *hdr = OBJECTHDR(this);
00368 hdr->flags |= OF_SKIPSAFE;
00369
00370
00371 if ((charging_efficiency > 1.0) || (charging_efficiency < 0.0))
00372 {
00373 GL_THROW("Please specify a charging efficiency between 0.0 and 1.0!");
00374
00375
00376
00377
00378 }
00379
00380
00381 if (strcmp(demand_profile,"")!=0)
00382 pDemand = get_demand_profile(demand_profile);
00383
00384 retval = residential_enduse::init(parent);
00385
00386 update_state();
00387
00388 return retval;
00389 }
00390
00391 int evcharger::isa(char *classname)
00392 {
00393 return (strcmp(classname,"evcharger")==0 || residential_enduse::isa(classname));
00394 }
00395
00396 double evcharger::update_state(double dt )
00397 {
00398 double temp_voltage_magnitude;
00399
00400 OBJECT *obj = OBJECTHDR(this);
00401 if (obj->clock>TS_ZERO && dt>0)
00402 {
00403 DATETIME now;
00404 if (!gl_localtime(obj->clock,&now))
00405 GL_THROW( "unable to obtain current time" );
00406
00407
00408
00409
00410
00411 int hour = now.hour;
00412 int daytype = 0;
00413 if(pDemand != NULL){
00414 pDemand->n_daytypes>0 ? (now.weekday>0&&now.weekday<6) : 0;
00415
00416 demand.home = pDemand->home[daytype][DEPART][hour];
00417 demand.work = pDemand->home[daytype][ARRIVE][hour];
00418 }
00419
00420
00421 switch (vehicle_state) {
00422 case VS_HOME:
00423 if (gl_random_bernoulli(RNGSTATE,demand.home*dt/3600))
00424 {
00425 gl_debug("%s (%s:%d) leaves for work with %.0f%% charge",obj->name?obj->name:"anonymous",obj->oclass->name,obj->id,charge*100);
00426 vehicle_state = VS_WORK;
00427 }
00428 break;
00429 case VS_WORK:
00430 if (gl_random_bernoulli(RNGSTATE,demand.work*dt/3600))
00431 {
00432 vehicle_state = VS_HOME;
00433 if (charge_at_work)
00434 charge -= (distance.work / mileage)/capacity;
00435 else
00436 charge -= 2 * (distance.work / mileage)/capacity;
00437 if (charge<0.25 && vehicle_type==VT_HYBRID)
00438 charge = 0.25;
00439 if (charge<=0)
00440 gl_warning("%s (%s:%d) vehicle has run out of charge while away from home", obj->name?obj->name:"anonymous",obj->oclass->name,obj->id);
00441 else
00442 gl_debug("%s (%s:%d) returns from work with %.0f%% charge",obj->name?obj->name:"anonymous",obj->oclass->name,obj->id, charge*100);
00443 }
00444 break;
00445
00446
00447
00448
00449
00450
00451
00452
00453
00454 default:
00455 GL_THROW( "invalid state" );
00456
00457
00458
00459
00460
00461
00462 break;
00463 }
00464 }
00465
00466
00467 switch (vehicle_state) {
00468 case VS_HOME:
00469 if (charge<1.0 && obj->clock>0)
00470 {
00471
00472 double charge_kw;
00473 switch (charger_type) {
00474 case CT_LOW:
00475 case CT_MEDIUM:
00476 case CT_HIGH:
00477
00478 temp_voltage_magnitude = (pCircuit->pV->get_complex()).Mag();
00479
00480 charge_kw = amps[(int)charger_type] * temp_voltage_magnitude * charge_throttle /1000;
00481 break;
00482 default:
00483 GL_THROW( "invalid charger_type" );
00484
00485
00486
00487
00488
00489
00490
00491 break;
00492 }
00493
00494
00495 double d_charge_kWh = charge_kw*dt/3600*charging_efficiency;
00496
00497
00498 charge += d_charge_kWh/capacity;
00499 if (charge>1.0)
00500 {
00501 gl_warning("%s (%s:%d) overcharge by %.0f%% truncated",obj->name?obj->name:"anonymous",obj->oclass->name,obj->id, (charge-1)*100);
00502
00503 charge = 1.0;
00504 }
00505 else if (charge<0.0)
00506 {
00507 gl_warning("%s (%s:%d) 100%% discharged", obj->name?obj->name:"anonymous",obj->oclass->name,obj->id);
00508
00509 charge = 0.0;
00510 }
00511 else if (charge>0.999)
00512 {
00513 gl_debug("%s (%s:%d) charge complete",obj->name?obj->name:"anonymous",obj->oclass->name,obj->id);
00514 charge = 1.0;
00515 }
00516
00517
00518
00519 if (charge_kw>0)
00520 {
00521 dt = (capacity*(1-charge))/charge_kw*3600;
00522 if (dt<1)
00523 {
00524 charge = 1.0;
00525 dt = -1;
00526 }
00527 }
00528
00529 else
00530 dt = -1;
00531
00532 load.power.SetPowerFactor(charge_kw,power_factor,J);
00533 }
00534 else
00535 {
00536 load.power = complex(0,0,J);
00537 dt = -1;
00538 }
00539 break;
00540 case VS_WORK:
00541 load.power = complex(0,0,J);
00542 dt = -1;
00543 break;
00544
00545
00546
00547
00548
00549
00550
00551 default:
00552 GL_THROW( "invalid state" );
00553
00554
00555
00556
00557
00558
00559
00560 break;
00561 }
00562
00563 if (!isfinite(charge))
00564 GL_THROW( "charge state error (not a number)" );
00565
00566
00567
00568
00569
00570 load.total = load.power;
00571 load.heatgain = load.total.Mag() * heat_fraction;
00572
00573
00574 return dt<3600&&dt>=0?dt:3600;
00575 }
00576
00577 TIMESTAMP evcharger::sync(TIMESTAMP t0, TIMESTAMP t1)
00578 {
00579 OBJECT *obj = OBJECTHDR(this);
00580
00581 if (t0>TS_ZERO && t1>t0)
00582 load.energy += (load.total * gl_tohours(t1-t0));
00583 double dt = update_state(gl_toseconds(t1-t0));
00584 if (dt==0)
00585 gl_warning("%s (%s:%d) didn't advance the clock",obj->name?obj->name:"anonymous",obj->oclass->name,obj->id);
00586
00587
00588 return dt<0 ? TS_NEVER : (TIMESTAMP)(t1+dt*TS_SECOND);
00589 }
00590
00591 void evcharger::load_demand_profile(void)
00592 {
00593
00594 }
00595
00597
00599
00600 EXPORT int create_evcharger(OBJECT **obj, OBJECT *parent)
00601 {
00602 try
00603 {
00604 *obj = gl_create_object(evcharger::oclass);
00605 if (*obj!=NULL)
00606 {
00607 evcharger *my = OBJECTDATA(*obj,evcharger);;
00608 gl_set_parent(*obj,parent);
00609 my->create();
00610 return 1;
00611 }
00612 else
00613 return 0;
00614 }
00615 CREATE_CATCHALL(evcharger);
00616 }
00617
00618 EXPORT int init_evcharger(OBJECT *obj)
00619 {
00620 try {
00621 evcharger *my = OBJECTDATA(obj,evcharger);
00622 return my->init(obj->parent);
00623 }
00624 INIT_CATCHALL(evcharger);
00625 }
00626
00627 EXPORT int isa_evcharger(OBJECT *obj, char *classname)
00628 {
00629 if(obj != 0 && classname != 0){
00630 return OBJECTDATA(obj,evcharger)->isa(classname);
00631 } else {
00632 return 0;
00633 }
00634 }
00635
00636 EXPORT TIMESTAMP sync_evcharger(OBJECT *obj, TIMESTAMP t0)
00637 {
00638 try {
00639 evcharger *my = OBJECTDATA(obj, evcharger);
00640 TIMESTAMP t1 = my->sync(obj->clock, t0);
00641 obj->clock = t0;
00642 return t1;
00643 }
00644 SYNC_CATCHALL(evcharger);
00645 }
00646