00001
00010 #include <stdlib.h>
00011 #include <stdio.h>
00012 #include <errno.h>
00013 #include <math.h>
00014
00015 #include "generators.h"
00016 #include "solar.h"
00017
00018
00019 CLASS *solar::oclass = NULL;
00020 solar *solar::defaults = NULL;
00021
00022 static PASSCONFIG passconfig = PC_BOTTOMUP|PC_POSTTOPDOWN;
00023 static PASSCONFIG clockpass = PC_BOTTOMUP;
00024
00025
00026 solar::solar(MODULE *module)
00027 {
00028 if (oclass==NULL)
00029 {
00030 oclass = gl_register_class(module,"solar",sizeof(solar),PC_PRETOPDOWN|PC_BOTTOMUP|PC_POSTTOPDOWN);
00031 if (oclass==NULL)
00032 GL_THROW("unable to register object class implemented by %s", __FILE__);
00033
00034 if (gl_publish_variable(oclass,
00035 PT_enumeration,"generator_mode",PADDR(gen_mode_v),
00036 PT_KEYWORD,"UNKNOWN",UNKNOWN,
00037 PT_KEYWORD,"CONSTANT_V",CONSTANT_V,
00038 PT_KEYWORD,"CONSTANT_PQ",CONSTANT_PQ,
00039 PT_KEYWORD,"CONSTANT_PF",CONSTANT_PF,
00040 PT_KEYWORD,"SUPPLY_DRIVEN",SUPPLY_DRIVEN,
00041
00042 PT_enumeration,"generator_status",PADDR(gen_status_v),
00043 PT_KEYWORD,"OFFLINE",OFFLINE,
00044 PT_KEYWORD,"ONLINE",ONLINE,
00045
00046 PT_enumeration,"panel_type", PADDR(panel_type_v),
00047 PT_KEYWORD, "SINGLE_CRYSTAL_SILICON", SINGLE_CRYSTAL_SILICON,
00048 PT_KEYWORD, "MULTI_CRYSTAL_SILICON", MULTI_CRYSTAL_SILICON,
00049 PT_KEYWORD, "AMORPHOUS_SILICON", AMORPHOUS_SILICON,
00050 PT_KEYWORD, "THIN_FILM_GA_AS", THIN_FILM_GA_AS,
00051 PT_KEYWORD, "CONCENTRATOR", CONCENTRATOR,
00052
00053 PT_enumeration,"power_type",PADDR(power_type_v),
00054 PT_KEYWORD,"AC",AC,
00055 PT_KEYWORD,"DC",DC,
00056
00057 PT_double, "noct", PADDR(NOCT),
00058 PT_double, "Tcell", PADDR(Tcell),
00059 PT_double, "Tambient", PADDR(Tambient),
00060 PT_double, "Insolation", PADDR(Insolation),
00061 PT_double, "Rinternal", PADDR(Rinternal),
00062 PT_double, "Rated_Insolation", PADDR(Rated_Insolation),
00063 PT_double, "V_Max[V]", PADDR(V_Max),
00064 PT_complex, "Voc_Max[V]", PADDR(Voc_Max),
00065 PT_complex, "Voc[V]", PADDR(Voc),
00066 PT_double, "efficiency[unit]", PADDR(efficiency),
00067 PT_double, "area[m^2]", PADDR(area),
00068
00069
00070 PT_double, "Rated_kVA[kVA]", PADDR(Rated_kVA),
00071
00072 PT_complex, "V_Out[V]", PADDR(V_Out),
00073 PT_complex, "I_Out[A]", PADDR(I_Out),
00074 PT_complex, "VA_Out[VA]", PADDR(VA_Out),
00075
00076
00077
00078 PT_set, "phases", PADDR(phases),
00079 PT_KEYWORD, "A",(set)PHASE_A,
00080 PT_KEYWORD, "B",(set)PHASE_B,
00081 PT_KEYWORD, "C",(set)PHASE_C,
00082 PT_KEYWORD, "N",(set)PHASE_N,
00083 PT_KEYWORD, "S",(set)PHASE_S,
00084 NULL)<1) GL_THROW("unable to publish properties in %s",__FILE__);
00085
00086 defaults = this;
00087 memset(this,0,sizeof(solar));
00088
00089 NOCT = 45;
00090 Tcell = 21;
00091 Tambient = 25;
00092 Insolation = 0;
00093 Rinternal = 0.05;
00094 Rated_Insolation = 1200;
00095 V_Max = 40;
00096 Voc = complex(40,0);
00097 Voc_Max = complex(40,0);
00098 area = 10;
00099
00100 efficiency = 0;
00101 }
00102 }
00103
00104
00105 int solar::create(void)
00106 {
00107 memcpy(this,defaults,sizeof(*this));
00108
00109 return 1;
00110 }
00111
00112
00113
00114
00119 int solar::init_climate()
00120 {
00121 OBJECT *hdr = OBJECTHDR(this);
00122
00123
00124 static FINDLIST *climates = NULL;
00125 int not_found = 0;
00126 if (climates==NULL && not_found==0)
00127 {
00128 climates = gl_find_objects(FL_NEW,FT_CLASS,SAME,"climate",FT_END);
00129 if (climates==NULL)
00130 {
00131 not_found = 1;
00132 gl_warning("solarpanel: no climate data found, using static data");
00133
00134
00135 static double tout=59.0, rhout=0.75, solar=1000;
00136 pTout = &tout;
00137 pRhout = &rhout;
00138 pSolar = &solar;
00139 }
00140 else if (climates->hit_count>1)
00141 {
00142 gl_warning("solarpanel: %d climates found, using first one defined", climates->hit_count);
00143 }
00144 }
00145 if (climates!=NULL)
00146 {
00147 if (climates->hit_count==0)
00148 {
00149
00150 static double tout=59.0, rhout=0.75, solar=1000;
00151 pTout = &tout;
00152 pRhout = &rhout;
00153 pSolar = &solar;
00154
00155 }
00156 else
00157 {
00158
00159
00160 OBJECT *obj = gl_find_next(climates,NULL);
00161 if (obj->rank<=hdr->rank)
00162 gl_set_dependent(obj,hdr);
00163 pTout = (double*)GETADDR(obj,gl_get_property(obj,"temperature"));
00164 pRhout = (double*)GETADDR(obj,gl_get_property(obj,"humidity"));
00165 pSolar = (double*)GETADDR(obj,gl_get_property(obj,"solar_flux"));
00166
00167
00168
00169
00170
00171
00172
00173
00174
00175
00176
00177
00178
00179
00180
00181
00182
00183
00184
00185
00186
00187
00188
00189
00190
00191
00192
00193
00194
00195
00196
00197
00198
00199
00200
00201
00202
00203
00204
00205
00206
00207
00208 }
00209 }
00210 return 1;
00211 }
00212
00213
00214 int solar::init(OBJECT *parent)
00215 {
00216 OBJECT *obj = OBJECTHDR(this);
00217
00218 if (gen_mode_v == UNKNOWN)
00219 {
00220 gl_warning("Generator control mode is not specified! Using default: SUPPLY_DRIVEN");
00221 gen_mode_v = SUPPLY_DRIVEN;
00222 }
00223 if (gen_status_v == UNKNOWN)
00224 {
00225 gl_warning("Solar panel status is unknown! Using default: ONLINE");
00226 gen_status_v = ONLINE;
00227 }
00228 if (panel_type_v == UNKNOWN)
00229 {
00230 gl_warning("Solar panel type is unknown! Using default: SINGLE_CRYSTAL_SILICON");
00231 panel_type_v = SINGLE_CRYSTAL_SILICON;
00232 }
00233
00234 struct {
00235 complex **var;
00236 char *varname;
00237 }
00238
00239 map[] = {
00240
00241 {&pCircuit_V, "V_In"},
00242 {&pLine_I, "I_In"},
00243 };
00244
00245 static complex default_line_voltage[1], default_line_current[1];
00246 int i;
00247
00248
00249 if (parent != NULL && strcmp(parent->oclass->name,"inverter") == 0)
00250 {
00251
00252 for (i=0; i<sizeof(map)/sizeof(map[0]); i++)
00253 {
00254 *(map[i].var) = get_complex(parent,map[i].varname);
00255 }
00256 }
00257 else if (parent != NULL && strcmp(parent->oclass->name,"inverter") != 0)
00258 {
00259 throw("Solar panel must have an inverter as it's parent");
00260 }
00261 else
00262 {
00263
00265 gl_warning("solar panel:%d has no parent defined. Using static voltages.", obj->id);
00266
00267
00268 *(map[0].var) = &default_line_voltage[0];
00269 *(map[1].var) = &default_line_current[0];
00270
00271
00272 default_line_voltage[0] = complex(V_Max.Re()/sqrt(3.0),0);
00273 }
00274
00275 switch(panel_type_v)
00276 {
00277 case SINGLE_CRYSTAL_SILICON:
00278 efficiency = 0.2;
00279 break;
00280 case MULTI_CRYSTAL_SILICON:
00281 efficiency = 0.15;
00282 break;
00283 case AMORPHOUS_SILICON:
00284 efficiency = 0.07;
00285 break;
00286 case THIN_FILM_GA_AS:
00287 efficiency = 0.3;
00288 break;
00289 case CONCENTRATOR:
00290 efficiency = 0.15;
00291 break;
00292 default:
00293 efficiency = 0.10;
00294 break;
00295 }
00296
00297
00298
00299 Max_P = Rated_Insolation * efficiency * area;
00300
00301
00302 solar::init_climate();
00303
00304 return 1;
00305 }
00306
00307 TIMESTAMP solar::presync(TIMESTAMP t0, TIMESTAMP t1)
00308 {
00309 I_Out = complex(0,0);
00310
00311 TIMESTAMP t2 = TS_NEVER;
00312 return t2;
00313 }
00314
00315 TIMESTAMP solar::sync(TIMESTAMP t0, TIMESTAMP t1)
00316 {
00317
00318
00319
00320
00321 Insolation = pSolar[0];
00322
00323 if(0 == gl_convert("W/sf","W/m^2", &Insolation))
00324 {
00325 gl_error("climate::init unable to gl_convert() 'W/sf' to 'W/m^2'!");
00326 return 0;
00327 }
00328
00329 Tambient = *pTout;
00330 calculate_IV(Tambient, Insolation);
00331 pLine_I[0] = I_Out;
00332 pCircuit_V[0] = V_Out;
00333
00334
00335
00336
00337 TIMESTAMP t2 = TS_NEVER;
00338
00339 return t2;
00340 }
00341
00342
00343 TIMESTAMP solar::postsync(TIMESTAMP t0, TIMESTAMP t1)
00344 {
00345 TIMESTAMP t2 = TS_NEVER;
00346
00347 return t2;
00348 }
00349
00350 void solar::derate_panel(double Tamb, double Insol)
00351 {
00352 Tcell = Tamb + ((NOCT - 20)/0.8) * Insol/1000;
00353 Rinternal=.0001*Tcell*Tcell;
00354
00355 Voc = Voc_Max * (1 - (0.0037 * (Tcell - 25)));
00356
00357 if(100.00 > Insol)
00358 {
00359 VA_Out = 0;
00360 }
00361 else
00362 {
00363 VA_Out = complex(Max_P * (1 - (0.001 * (Tcell - 25))), 0);
00364 }
00365
00366
00367 VA_Out = complex(VA_Out.Re() * 0.97, VA_Out.Im()* 0.97);
00368 VA_Out = complex(VA_Out.Re() * 0.96, VA_Out.Im()* 0.96);
00369 }
00370
00371 void solar::calculate_IV(double Tamb, double Insol){
00372
00373 derate_panel(Tamb, Insol);
00374 V_Out = V_Max * (Voc / Voc_Max);
00375 if (V_Out.Mag() == 0.0)
00376 {
00377 I_Out = complex(0.0,0.0);
00378 }
00379 else
00380 {
00381 VA_Out = VA_Out * Insol/Rated_Insolation;
00382 I_Out = (VA_Out / V_Out);
00383 }
00384
00385
00386 }
00387
00388 complex *solar::get_complex(OBJECT *obj, char *name)
00389 {
00390 PROPERTY *p = gl_get_property(obj,name);
00391 if (p==NULL || p->ptype!=PT_complex)
00392 return NULL;
00393 return (complex*)GETADDR(obj,p);
00394 }
00395
00397
00399
00400 EXPORT int create_solar(OBJECT **obj, OBJECT *parent)
00401 {
00402 try
00403 {
00404 *obj = gl_create_object(solar::oclass);
00405 if (*obj!=NULL)
00406 {
00407 solar *my = OBJECTDATA(*obj,solar);
00408 gl_set_parent(*obj,parent);
00409 return my->create();
00410 }
00411 }
00412 catch (char *msg)
00413 {
00414 gl_error("create_solar: %s", msg);
00415 }
00416 return 0;
00417 }
00418
00419 EXPORT int init_solar(OBJECT *obj, OBJECT *parent)
00420 {
00421 try
00422 {
00423 if (obj!=NULL)
00424 return OBJECTDATA(obj,solar)->init(parent);
00425 }
00426 catch (char *msg)
00427 {
00428 gl_error("init_solar(obj=%d;%s): %s", obj->id, obj->name?obj->name:"unnamed", msg);
00429 }
00430 return 0;
00431 }
00432
00433 EXPORT TIMESTAMP sync_solar(OBJECT *obj, TIMESTAMP t1, PASSCONFIG pass)
00434 {
00435 TIMESTAMP t2 = TS_NEVER;
00436 solar *my = OBJECTDATA(obj,solar);
00437 try
00438 {
00439 switch (pass) {
00440 case PC_PRETOPDOWN:
00441 t2 = my->presync(obj->clock,t1);
00442 break;
00443 case PC_BOTTOMUP:
00444 t2 = my->sync(obj->clock,t1);
00445 break;
00446 case PC_POSTTOPDOWN:
00447 t2 = my->postsync(obj->clock,t1);
00448 break;
00449 default:
00450 GL_THROW("invalid pass request (%d)", pass);
00451 break;
00452 }
00453 if (pass==clockpass)
00454 obj->clock = t1;
00455 }
00456 catch (char *msg)
00457 {
00458 gl_error("sync_solar(obj=%d;%s): %s", obj->id, obj->name?obj->name:"unnamed", msg);
00459 }
00460 return t2;
00461 }