00001
00010 #include <stdlib.h>
00011 #include <stdio.h>
00012 #include <errno.h>
00013 #include <math.h>
00014
00015
00016 #include "battery.h"
00017 #include "inverter.h"
00018 #include "solar.h"
00019
00020
00021 #include "central_dg_control.h"
00022
00023 #define DEFAULT 1.0;
00024
00025 CLASS *central_dg_control::oclass = NULL;
00026 central_dg_control *central_dg_control::defaults = NULL;
00027
00028 static PASSCONFIG passconfig = PC_BOTTOMUP|PC_POSTTOPDOWN;
00029 static PASSCONFIG clockpass = PC_BOTTOMUP;
00030
00031
00032 central_dg_control::central_dg_control(MODULE *module)
00033 {
00034 if (oclass==NULL)
00035 {
00036 oclass = gl_register_class(module,"central_dg_control",sizeof(central_dg_control),PC_PRETOPDOWN|PC_BOTTOMUP|PC_POSTTOPDOWN|PC_AUTOLOCK);
00037 if (oclass==NULL)
00038 throw "unable to register class central_dg_control";
00039 else
00040 oclass->trl = TRL_PROOF;
00041
00042 if (gl_publish_variable(oclass,
00043 PT_char32,"controlled_dgs", PADDR(controlled_objects), PT_DESCRIPTION, "the group ID of the dg objects the controller controls.",
00044 PT_object,"feederhead_meter", PADDR(feederhead_meter), PT_DESCRIPTION, "the name of the meter.",
00045
00046 PT_enumeration,"control_mode_0",PADDR(control_mode_setting[0]),
00047 PT_KEYWORD,"NO_CONTROL",(enumeration)NO_CONTROL,
00048 PT_KEYWORD,"CONSTANT_PF",(enumeration)CONSTANT_PF,
00049 PT_KEYWORD,"PEAK_SHAVING",(enumeration)PEAK_SHAVING,
00050 PT_enumeration,"control_mode_1",PADDR(control_mode_setting[1]),
00051 PT_KEYWORD,"NO_CONTROL",(enumeration)NO_CONTROL,
00052 PT_KEYWORD,"CONSTANT_PF",(enumeration)CONSTANT_PF,
00053 PT_KEYWORD,"PEAK_SHAVING",(enumeration)PEAK_SHAVING,
00054 PT_enumeration,"control_mode_2",PADDR(control_mode_setting[2]),
00055 PT_KEYWORD,"NO_CONTROL",(enumeration)NO_CONTROL,
00056 PT_KEYWORD,"CONSTANT_PF",(enumeration)CONSTANT_PF,
00057 PT_KEYWORD,"PEAK_SHAVING",(enumeration)PEAK_SHAVING,
00058 PT_enumeration,"control_mode_3",PADDR(control_mode_setting[3]),
00059 PT_KEYWORD,"NO_CONTROL",(enumeration)NO_CONTROL,
00060 PT_KEYWORD,"CONSTANT_PF",(enumeration)CONSTANT_PF,
00061 PT_KEYWORD,"PEAK_SHAVING",(enumeration)PEAK_SHAVING,
00062
00063
00064
00065 PT_double, "peak_S[W]", PADDR(S_peak),
00066 PT_double, "pf_low[unit]", PADDR(pf_low),
00067 PT_double, "pf_high[unit]", PADDR(pf_high),
00068
00069 NULL)<1) GL_THROW("unable to publish properties in %s",__FILE__);
00070
00071 defaults = this;
00072
00073 memset(this,0,sizeof(central_dg_control));
00074
00075
00076 }
00077 }
00078
00079 int central_dg_control::create(void)
00080 {
00081
00082 control_mode_setting[0] = NO_CONTROL;
00083 control_mode_setting[1] = control_mode_setting[2] = control_mode_setting[3] = NO_SETTING;
00084
00085
00086 pPower_Meas[0] = pPower_Meas[1] = pPower_Meas[2] = NULL;
00087
00088 return 1;
00089 }
00090
00091
00092 int central_dg_control::init(OBJECT *parent)
00093 {
00094 FINDLIST *inverter_list;
00095 FINDLIST *battery_list;
00096 FINDLIST *solar_list;
00097 int index = 0;
00098 OBJECT *obj = 0;
00099 OBJECT *thisobj = OBJECTHDR(this);
00100 all_inverter_S_rated = 0;
00101 all_battery_S_rated = 0;
00102 all_solar_S_rated = 0;
00103 int inverter_filled_to = -1;
00104
00106
00108 if(controlled_objects[0] == '\0'){
00109 gl_error("No group id given for controlled DG objects.");
00110 return 0;
00111
00112 }
00113
00114 inverter_list = gl_find_objects(FL_NEW,FT_CLASS,SAME,"inverter",AND,FT_GROUPID,SAME,controlled_objects.get_string(),FT_END);
00115 if(inverter_list == NULL){
00116 gl_error("No inverters with given group id found.");
00117
00118
00119
00120
00121 return 0;
00122 }
00123
00124 battery_list = gl_find_objects(FL_NEW,FT_CLASS,SAME,"battery",AND,FT_PARENT,FT_CLASS,SAME,"inverter",AND,FT_PARENT,FT_GROUPID,SAME,controlled_objects.get_string(),FT_END);
00125 if(battery_list == NULL){
00126 gl_error("No batteries with inverter parents with given group id found.");
00127
00128
00129
00130 return 0;
00131 }
00132
00133 solar_list = gl_find_objects(FL_NEW,FT_CLASS,SAME,"solar",AND,FT_PARENT,FT_CLASS,SAME,"inverter",AND,FT_PARENT,FT_GROUPID,SAME,controlled_objects.get_string(),FT_END);
00134 if(solar_list == NULL){
00135 gl_error("no solars with inverter parents with given group id found.");
00136
00137
00138
00139 return 0;
00140 }
00141
00142
00143 inverter_set = (inverter **)gl_malloc((battery_list->hit_count*sizeof(battery*))+(solar_list->hit_count*sizeof(solar*)));
00144 if(inverter_set == NULL){
00145 gl_error("Failed to allocate inverter array.");
00146
00147
00148
00149 return 0;
00150 }
00151
00152 battery_set = (battery **)gl_malloc(battery_list->hit_count*sizeof(battery*));
00153 if(battery_set == NULL){
00154 gl_error("Failed to allocate battery array.");
00155
00156
00157
00158 return 0;
00159 }
00160
00161 solar_set = (solar **)gl_malloc(solar_list->hit_count*sizeof(solar*));
00162 if(solar_set == NULL){
00163 gl_error("Failed to allocate solar array.");
00164
00165
00166
00167 return 0;
00168 }
00169
00170 battery_inverter_set = (inverter ***)gl_malloc(battery_list->hit_count*sizeof(inverter**));
00171 if(battery_inverter_set == NULL){
00172 gl_error("Failed to allocate battery array.");
00173
00174
00175
00176 return 0;
00177 }
00178
00179 solar_inverter_set = (inverter ***)gl_malloc(solar_list->hit_count*sizeof(inverter**));
00180 if(solar_inverter_set == NULL){
00181 gl_error("Failed to allocate solar array.");
00182
00183
00184
00185 return 0;
00186 }
00187
00188 battery_count = battery_list->hit_count;
00189 solar_count = solar_list->hit_count;
00190 inverter_count = battery_count + solar_count;
00191
00192
00193 while(obj = gl_find_next(battery_list,obj)){
00194 if(index >= battery_count){
00195 break;
00196 }
00197 battery_set[index] = OBJECTDATA(obj,battery);
00198 if(battery_set[index] == NULL){
00199 gl_error("Unable to map object as battery.");
00200
00201
00202
00203 return 0;
00204 }
00205 inverter_set[inverter_filled_to + 1] = OBJECTDATA(obj->parent, inverter);
00206 if(inverter_set[inverter_filled_to + 1] == NULL){
00207 gl_error("Unable to map object as inverter.");
00208
00209
00210
00211 return 0;
00212 }
00213 inverter_filled_to++;
00214 battery_inverter_set[index] = &inverter_set[inverter_filled_to];
00215 if (battery_inverter_set[index] == NULL) {
00216 gl_error("Unable to map battery parent object as inverter.");
00217
00218
00219
00220 return 0;
00221 }
00222
00223 all_battery_S_rated += (*(battery_inverter_set[index]))->bp_rated;
00224 ++index;
00225 }
00226
00227
00228 index = 0;
00229 while(obj = gl_find_next(solar_list,obj)){
00230 if(index >= solar_count){
00231 break;
00232 }
00233 solar_set[index] = OBJECTDATA(obj,solar);
00234 if(solar_set[index] == NULL){
00235 gl_error("Unable to map object as solar.");
00236
00237
00238
00239 return 0;
00240 }
00241 inverter_set[inverter_filled_to + 1] = OBJECTDATA(obj->parent, inverter);
00242 if(inverter_set[inverter_filled_to + 1] == NULL){
00243 gl_error("Unable to map object as inverter.");
00244
00245
00246
00247 return 0;
00248 }
00249 inverter_filled_to++;
00250 solar_inverter_set[index] = &inverter_set[inverter_filled_to];
00251 if (solar_inverter_set[index] == NULL) {
00252 gl_error("Unable to map solar parent object as inverter.");
00253
00254
00255
00256 return 0;
00257 }
00258
00259 all_solar_S_rated += solar_set[index]->Rated_kVA*1000.0;
00260 ++index;
00261 }
00262
00263 all_inverter_S_rated = all_solar_S_rated + all_battery_S_rated;
00264
00265
00266 if (feederhead_meter != NULL)
00267 {
00268
00269 if (gl_object_isa(feederhead_meter,"meter","powerflow") == true)
00270 {
00271
00272 pPower_Meas[0] = new gld_property(feederhead_meter,"measured_power_A");
00273
00274
00275 if ((pPower_Meas[0]->is_valid() != true) || (pPower_Meas[0]->is_complex() != true))
00276 {
00277 GL_THROW("central_dg_control:%d - %s - failed to map feaderhead_meter power property!",thisobj->id,(thisobj->name ? thisobj->name : "Unnamed"));
00278
00279
00280
00281
00282 }
00283
00284
00285 pPower_Meas[1] = new gld_property(feederhead_meter,"measured_power_B");
00286
00287
00288 if ((pPower_Meas[1]->is_valid() != true) || (pPower_Meas[1]->is_complex() != true))
00289 {
00290 GL_THROW("central_dg_control:%d - %s - failed to map feaderhead_meter power property!",thisobj->id,(thisobj->name ? thisobj->name : "Unnamed"));
00291
00292 }
00293
00294
00295 pPower_Meas[2] = new gld_property(feederhead_meter,"measured_power_C");
00296
00297
00298 if ((pPower_Meas[2]->is_valid() != true) || (pPower_Meas[2]->is_complex() != true))
00299 {
00300 GL_THROW("central_dg_control:%d - %s - failed to map feaderhead_meter power property!",thisobj->id,(thisobj->name ? thisobj->name : "Unnamed"));
00301
00302 }
00303 }
00304 else
00305 {
00306 GL_THROW("central_dg_control:%d - %s - feederhead_meter is empty!",thisobj->id,(thisobj->name ? thisobj->name : "Unnamed"));
00307
00308
00309
00310 }
00311 }
00312 else
00313 {
00314 GL_THROW("central_dg_control:%d - %s - feederhead_meter is empty!",thisobj->id,(thisobj->name ? thisobj->name : "Unnamed"));
00315
00316 }
00317
00318 P_disp_3p = 0.0;
00319 Q_disp_3p = 0.0;
00320 return 1;
00321
00322 }
00323
00324 TIMESTAMP central_dg_control::presync(TIMESTAMP t0, TIMESTAMP t1)
00325 {
00326 int i;
00327
00328
00329
00330
00331
00332
00333 if(t0!=t1) {
00334 for(i = 0; i < inverter_count; i++)
00335 {
00336 inverter_set[i]->P_Out = inverter_set[i]->P_Out_t0;
00337 inverter_set[i]->Q_Out = inverter_set[i]->Q_Out_t0;
00338 inverter_set[i]->power_factor = inverter_set[i]->power_factor_t0;
00339 }
00340 }
00341
00342 TIMESTAMP t2 = TS_NEVER;
00343 return t2;
00344 }
00345
00346 TIMESTAMP central_dg_control::sync(TIMESTAMP t0, TIMESTAMP t1)
00347 {
00348
00349
00350 complex temp_complex_array[3];
00351
00352 if (t0!=t1) {
00353 return t1;
00354 }
00355 int i;
00356 int n;
00357
00358
00359
00360 temp_complex_array[0] = pPower_Meas[0]->get_complex();
00361 temp_complex_array[1] = pPower_Meas[1]->get_complex();
00362 temp_complex_array[2] = pPower_Meas[2]->get_complex();
00363
00364 P[0] = temp_complex_array[0].Re();
00365 P[1] = temp_complex_array[1].Re();
00366 P[2] = temp_complex_array[2].Re();
00367 Q[0] = temp_complex_array[0].Im();
00368 Q[1] = temp_complex_array[1].Im();
00369 Q[2] = temp_complex_array[2].Im();
00370 P_3p = P[0] + P[1] + P[2];
00371 Q_3p = Q[0] + Q[1] + Q[2];
00372 S_3p = complex(P_3p, Q_3p);
00373 double potential_pf = 0.0;
00374 double Q_disp_so_far = 0.0;
00375 double total_avail_soc = 0.0;
00376 double P_ref_3p;
00377 double Q_avail_3p = 0.0;
00378 double this_Q;
00379 double P_disp_3p_no_control = 0;
00380 double Q_disp_3p_no_control = 0;
00381
00382
00383
00384
00385 double pf_angle_goal = ((pf_low/fabs(pf_low))*acos(fabs(pf_low)) + (pf_high/fabs(pf_high))*acos(fabs(pf_high)))/2.0;
00386 double pf_goal;
00387
00388 if (pf_angle_goal < 0)
00389 {
00390 pf_goal = -cos(pf_angle_goal);
00391 }
00392 else
00393 {
00394 pf_goal = cos(pf_angle_goal);
00395 }
00396
00397 P_gen[0] = P_gen[1] = P_gen[2] = 0.0;
00398 Q_gen[0] = Q_gen[1] = Q_gen[2] = 0.0;
00399
00400
00401 for(i = 0; i < inverter_count; i++)
00402 {
00403 P_disp_3p_no_control += inverter_set[i]->P_Out;
00404 Q_disp_3p_no_control += inverter_set[i]->Q_Out;
00405 }
00406
00407
00408 P_gen_solar[0] = P_gen_solar[1] = P_gen_solar[2] = P_gen_solar_3p = 0.0;
00409 Q_gen_solar[0] = Q_gen_solar[1] = Q_gen_solar[2] = Q_gen_solar_3p =0.0;
00410
00411
00412
00413 for(i = 0; i < solar_count; i++)
00414 {
00415 P_gen_solar_3p += (*(solar_inverter_set[i]))->VA_Out.Re();
00416 Q_gen_solar_3p += (*(solar_inverter_set[i]))->VA_Out.Im();
00417 }
00418
00419
00420 P_gen_battery[0] = P_gen_battery[1] = P_gen_battery[2] = P_gen_battery_3p = 0.0;
00421 Q_gen_battery[0] = Q_gen_battery[1] = Q_gen_battery[2] = Q_gen_battery_3p = 0.0;
00422
00423 for(i = 0; i < battery_count; i++)
00424 {
00425 P_gen_battery_3p += (*(battery_inverter_set[i]))->VA_Out.Re();
00426 Q_gen_battery_3p += (*(battery_inverter_set[i]))->VA_Out.Im();
00427 }
00428
00429 P_gen_3p = P_gen_solar_3p + P_gen_battery_3p;
00430 Q_gen_3p = Q_gen_solar_3p + Q_gen_battery_3p;
00431
00432
00433
00434
00435
00436
00437 i=3;
00438 while (i>-1)
00439 {
00440 if (control_mode_setting[i]!=NULL)
00441 {
00442 switch(control_mode_setting[i])
00443 {
00444
00445 case NO_CONTROL:
00446 break;
00447
00448 case CONSTANT_PF:
00449
00450
00451
00452 pf_meas_3p = (Q_3p == 0 ? 1.0 : Q_3p)/fabs(Q_3p == 0 ? 1.0 : Q_3p)*(S_3p.Mag() == 0 ? 1.0 : fabs(P_3p))/(S_3p.Mag() == 0 ? 1.0 : S_3p.Mag());
00453
00454
00455
00456
00457
00458 if (pf_low > 0.0) {
00459 if (pf_meas_3p < 0.0||(pf_meas_3p > 0.0 && pf_meas_3p > pf_low)) {
00460 Q_disp_3p = Q_3p + Q_gen_3p - fabs(P_3p)*tan(acos(pf_goal));
00461 }
00462 else if (pf_meas_3p < pf_high) {
00463 Q_disp_3p = Q_3p + Q_gen_3p - fabs(P_3p)*tan(acos(pf_goal));
00464 }
00465
00466 else {
00467 break;
00468 }
00469 }
00470 else if (pf_high > 0.0) {
00471 if (pf_meas_3p < 0.0 && pf_meas_3p > pf_low) {
00472 Q_disp_3p = Q_3p + Q_gen_3p - fabs(P_3p)*tan(acos(pf_goal));
00473 }
00474 else if (pf_meas_3p >= 0.0 && pf_meas_3p < pf_high) {
00475 Q_disp_3p = Q_3p + Q_gen_3p - fabs(P_3p)*tan(acos(pf_goal));
00476 }
00477
00478 else {
00479 break;
00480 }
00481 }
00482
00483 else {
00484 if (pf_meas_3p < 0.0 && pf_low < pf_meas_3p) {
00485 Q_disp_3p = Q_3p + Q_gen_3p - fabs(P_3p)*tan(acos(pf_goal));
00486 }
00487 else if ((pf_meas_3p < 0.0 && pf_meas_3p < pf_high)||(pf_meas_3p > 0)) {
00488 Q_disp_3p = Q_3p + Q_gen_3p - fabs(P_3p)*tan(acos(pf_goal));
00489 }
00490
00491 else {
00492 break;
00493 }
00494 }
00495
00496
00497
00498
00499
00500
00501
00502
00503
00504 Q_avail_3p = all_battery_S_rated*sin(acos(P_gen_battery_3p/all_battery_S_rated));
00505
00506
00507
00508
00509 for (n=0; n< solar_count; n++) {
00510 if ((*(solar_inverter_set[n]))->VA_Out.Re() > 0.0) {
00511 Q_avail_3p +=(*(solar_inverter_set[n]))->p_rated*3.0*sin(acos((*(solar_inverter_set[n]))->VA_Out.Re()/((*(solar_inverter_set[n]))->p_rated*3.0)));
00512 }
00513
00514 }
00515
00516
00517 if (fabs(Q_avail_3p) >= fabs(Q_disp_3p)) {
00518 for (n=0; n < inverter_count; n++) {
00519
00520 if ((inverter_set[n])->four_quadrant_control_mode==2 && (inverter_set[n])->VA_Out.Re() > 0.0) {
00521
00522
00523 this_Q = (inverter_set[n])->p_rated*3.0*sin(acos((inverter_set[n])->VA_Out.Re()/((inverter_set[n])->p_rated)*3.0))/Q_avail_3p*Q_disp_3p;
00524
00525 (inverter_set[n])->power_factor = -(this_Q/fabs(this_Q))*fabs((inverter_set[n])->VA_Out.Re())/complex((inverter_set[n])->VA_Out.Re(),this_Q).Mag();
00526 }
00527
00528 else if ((inverter_set[n])->four_quadrant_control_mode==1)
00529 {
00530
00531 (inverter_set[n])->Q_Out = (inverter_set[n])->p_rated*3.0*sin(acos((inverter_set[n])->VA_Out.Re()/((inverter_set[n])->p_rated)*3.0))/Q_avail_3p*Q_disp_3p;
00532 }
00533 }
00534
00535 } else {
00536 for (n=0; n < inverter_count; n++) {
00537
00538 if ((inverter_set[n])->four_quadrant_control_mode==2 && (inverter_set[n])->VA_Out.Re() > 0.0) {
00539
00540 this_Q = (inverter_set[n])->p_rated*3.0*sin(acos((inverter_set[n])->VA_Out.Re()/((inverter_set[n])->p_rated)*3.0));
00541 (inverter_set[n])->power_factor = -(this_Q/fabs(this_Q))*fabs((inverter_set[n])->VA_Out.Re())/complex((inverter_set[n])->VA_Out.Re(),this_Q).Mag();
00542 }
00543 else if ((inverter_set[n])->four_quadrant_control_mode==1)
00544 {
00545
00546 (inverter_set[n])->Q_Out = (inverter_set[n])->p_rated*3.0*sin(acos((inverter_set[n])->VA_Out.Re()/((inverter_set[n])->p_rated)*3.0));
00547 }
00548 }
00549
00550 }
00551
00552
00553 return t1;
00554 break;
00555
00556 case PEAK_SHAVING:
00557 if ((S_3p-P_disp_3p_no_control).Mag() > S_peak) {
00558 P_ref_3p = S_peak*cos(asin(Q_3p/S_peak));
00559 P_disp_3p = P_3p - P_ref_3p;
00560 for (n=0; n<battery_count; n++) {
00561 total_avail_soc += (battery_set[n])->soc;
00562 }
00563 for (n=0; n<battery_count; n++) {
00564 (*(battery_inverter_set[n]))->P_Out = battery_set[n]->soc/total_avail_soc*P_disp_3p;
00565 }
00566
00567 return t1;
00568 }
00569 break;
00570 }
00571
00572 }
00573 i--;
00574 }
00575
00576
00577
00578 return TS_NEVER;
00579 }
00580
00581
00582 TIMESTAMP central_dg_control::postsync(TIMESTAMP t0, TIMESTAMP t1)
00583 {
00584 TIMESTAMP t2 = TS_NEVER;
00585
00586
00587 return t2;
00588 }
00589
00591
00593
00594 EXPORT int create_central_dg_control(OBJECT **obj, OBJECT *parent)
00595 {
00596 try
00597 {
00598 *obj = gl_create_object(central_dg_control::oclass);
00599 if (*obj!=NULL)
00600 {
00601 central_dg_control *my = OBJECTDATA(*obj,central_dg_control);
00602 gl_set_parent(*obj,parent);
00603 return my->create();
00604 }
00605 else
00606 return 0;
00607 }
00608 CREATE_CATCHALL(central_dg_control);
00609 }
00610
00611 EXPORT int init_central_dg_control(OBJECT *obj, OBJECT *parent)
00612 {
00613 try
00614 {
00615 if (obj!=NULL)
00616 return OBJECTDATA(obj,central_dg_control)->init(parent);
00617 else
00618 return 0;
00619 }
00620 INIT_CATCHALL(central_dg_control);
00621 }
00622
00623 EXPORT TIMESTAMP sync_central_dg_control(OBJECT *obj, TIMESTAMP t1, PASSCONFIG pass)
00624 {
00625 TIMESTAMP t2 = TS_NEVER;
00626 central_dg_control *my = OBJECTDATA(obj,central_dg_control);
00627 try
00628 {
00629 switch (pass) {
00630 case PC_PRETOPDOWN:
00631 t2 = my->presync(obj->clock,t1);
00632 break;
00633 case PC_BOTTOMUP:
00634 t2 = my->sync(obj->clock,t1);
00635 break;
00636 case PC_POSTTOPDOWN:
00637 t2 = my->postsync(obj->clock,t1);
00638 break;
00639 default:
00640 GL_THROW("invalid pass request (%d)", pass);
00641 break;
00642 }
00643 if (pass==clockpass)
00644 obj->clock = t1;
00645 }
00646 SYNC_CATCHALL(central_dg_control);
00647 return t2;
00648 }