00001
00020 #include <stdlib.h>
00021 #include <stdio.h>
00022 #include <errno.h>
00023 #include <math.h>
00024
00025 #include "triplex_meter.h"
00026 #include "timestamp.h"
00027
00028
00029 #define TO_HOURS(t) (((double)t) / (3600 * TS_SECOND))
00030
00031
00032 EXPORT int64 triplex_meter_reset(OBJECT *obj)
00033 {
00034 triplex_meter *pMeter = OBJECTDATA(obj,triplex_meter);
00035 pMeter->measured_demand = 0;
00036 return 0;
00037 }
00038
00040
00042
00043 CLASS* triplex_meter::oclass = NULL;
00044 CLASS* triplex_meter::pclass = NULL;
00045
00046
00047 triplex_meter::triplex_meter(MODULE *mod) : triplex_node(mod)
00048 {
00049
00050 if (oclass==NULL)
00051 {
00052
00053 pclass = triplex_node::oclass;
00054
00055
00056 oclass = gl_register_class(mod,"triplex_meter",sizeof(triplex_meter),PC_PRETOPDOWN|PC_BOTTOMUP|PC_POSTTOPDOWN|PC_UNSAFE_OVERRIDE_OMIT);
00057 if (oclass==NULL)
00058 GL_THROW("unable to register object class implemented by %s",__FILE__);
00059
00060
00061 if (gl_publish_variable(oclass,
00062 PT_INHERIT, "triplex_node",
00063 PT_double, "measured_real_energy[Wh]", PADDR(measured_real_energy),
00064 PT_double, "measured_reactive_energy[VAh]",PADDR(measured_reactive_energy),
00065 PT_complex, "measured_power[VA]", PADDR(measured_power),
00066 PT_complex, "indiv_measured_power_1[VA]", PADDR(indiv_measured_power[0]),
00067 PT_complex, "indiv_measured_power_2[VA]", PADDR(indiv_measured_power[1]),
00068 PT_complex, "indiv_measured_power_N[VA]", PADDR(indiv_measured_power[2]),
00069 PT_double, "measured_demand[W]", PADDR(measured_demand),
00070 PT_double, "measured_real_power[W]", PADDR(measured_real_power),
00071 PT_double, "measured_reactive_power[W]", PADDR(measured_reactive_power),
00072
00073
00074 PT_complex, "measured_voltage_1[V]", PADDR(measured_voltage[0]),
00075 PT_complex, "measured_voltage_2[V]", PADDR(measured_voltage[1]),
00076 PT_complex, "measured_voltage_N[V]", PADDR(measured_voltage[2]),
00077 PT_complex, "measured_current_1[A]", PADDR(measured_current[0]),
00078 PT_complex, "measured_current_2[A]", PADDR(measured_current[1]),
00079 PT_complex, "measured_current_N[A]", PADDR(measured_current[2]),
00080 #ifdef SUPPORT_OUTAGES
00081 PT_int16, "sustained_count", PADDR(sustained_count),
00082 PT_int16, "momentary_count", PADDR(momentary_count),
00083 PT_int16, "total_count", PADDR(total_count),
00084 PT_int16, "s_flag", PADDR(s_flag),
00085 PT_int16, "t_flag", PADDR(t_flag),
00086 PT_complex, "pre_load", PADDR(pre_load),
00087 #endif
00088
00089 PT_double, "monthly_bill[$]", PADDR(monthly_bill),
00090 PT_double, "previous_monthly_bill[$]", PADDR(previous_monthly_bill),
00091 PT_double, "previous_monthly_energy[kWh]", PADDR(previous_monthly_energy),
00092 PT_double, "monthly_fee[$]", PADDR(monthly_fee),
00093 PT_double, "monthly_energy[kWh]", PADDR(monthly_energy),
00094 PT_enumeration, "bill_mode", PADDR(bill_mode),
00095 PT_KEYWORD,"NONE",BM_NONE,
00096 PT_KEYWORD,"UNIFORM",BM_UNIFORM,
00097 PT_KEYWORD,"TIERED",BM_TIERED,
00098 PT_KEYWORD,"HOURLY",BM_HOURLY,
00099 PT_object, "power_market", PADDR(power_market),
00100 PT_int32, "bill_day", PADDR(bill_day),
00101 PT_double, "price[$/kWh]", PADDR(price),
00102 PT_double, "first_tier_price[$/kWh]", PADDR(tier_price[0]),
00103 PT_double, "first_tier_energy[kWh]", PADDR(tier_energy[0]),
00104 PT_double, "second_tier_price[$/kWh]", PADDR(tier_price[1]),
00105 PT_double, "second_tier_energy[kWh]", PADDR(tier_energy[1]),
00106 PT_double, "third_tier_price[$/kWh]", PADDR(tier_price[2]),
00107 PT_double, "third_tier_energy[kWh]", PADDR(tier_energy[2]),
00108
00109 NULL)<1) GL_THROW("unable to publish properties in %s",__FILE__);
00110 }
00111 }
00112
00113 int triplex_meter::isa(char *classname)
00114 {
00115 return strcmp(classname,"triplex_meter")==0 || triplex_node::isa(classname);
00116 }
00117
00118
00119 int triplex_meter::create()
00120 {
00121 int result = triplex_node::create();
00122 measured_real_energy = measured_reactive_energy = 0;
00123 measured_power = 0;
00124 measured_demand = 0;
00125 last_t = dt = next_time = 0;
00126 previous_energy_total = 0;
00127
00128 hourly_acc = 0.0;
00129 monthly_bill = 0.0;
00130 monthly_energy = 0.0;
00131 previous_monthly_energy = 0.0;
00132 bill_mode = BM_NONE;
00133 power_market = 0;
00134 price_prop = 0;
00135 bill_day = 15;
00136 last_bill_month = -1;
00137 price = 0.0;
00138 tier_price[0] = tier_price[1] = tier_price[2] = 0;
00139 tier_energy[0] = tier_energy[1] = tier_energy[2] = 0;
00140
00141
00142 return result;
00143 }
00144
00145
00146 int triplex_meter::init(OBJECT *parent)
00147 {
00148 #ifdef SUPPORT_OUTAGES
00149 sustained_count=0;
00150 momentary_count=0;
00151 total_count=0;
00152 s_flag=0;
00153 t_flag=0;
00154 pre_load=0;
00155 #endif
00156
00157 if(power_market != 0){
00158 price_prop = gl_get_property(power_market, "next.P");
00159 if(price_prop == 0){
00160 GL_THROW("triplex_meter::power_market object \'%s\' does not publish \'next.P\'", (power_market->name ? power_market->name : "(anon)"));
00161 }
00162 }
00163 check_prices();
00164
00165 return triplex_node::init(parent);
00166 }
00167
00168
00169 int triplex_meter::check_prices(){
00170 if(bill_mode == BM_UNIFORM){
00171 if(price < 0.0){
00172 GL_THROW("triplex_meter price is negative!");
00173 }
00174 } else if(bill_mode == BM_TIERED){
00175 if(tier_price[1] == 0){
00176 tier_price[1] = tier_price[0];
00177 tier_energy[1] = tier_energy[0];
00178 }
00179 if(tier_price[2] == 0){
00180 tier_price[2] = tier_price[1];
00181 tier_energy[2] = tier_energy[1];
00182 }
00183 if(tier_energy[2] < tier_energy[1] || tier_energy[1] < tier_energy[0]){
00184 GL_THROW("triplex_meter energy tiers quantity trend improperly");
00185 }
00186 for(int i = 0; i < 3; ++i){
00187 if(tier_price[i] < 0.0 || tier_energy[i] < 0.0)
00188 GL_THROW("triplex_meter tiers cannot have negative values");
00189 }
00190 } if(bill_mode == BM_HOURLY){
00191 if(power_market == 0 || price_prop == 0){
00192 GL_THROW("triplex_meter cannot use hourly energy prices without a power market that publishes the next price");
00193 }
00194
00195 }
00196 return 0;
00197 }
00198
00199
00200 TIMESTAMP triplex_meter::postsync(TIMESTAMP t0, TIMESTAMP t1)
00201 {
00202 TIMESTAMP rv = TS_NEVER;
00203 TIMESTAMP hr = TS_NEVER;
00204
00205
00206
00207 measured_voltage[0].SetPolar(voltageA.Mag(),voltageA.Arg());
00208 measured_voltage[1].SetPolar(voltageB.Mag(),voltageB.Arg());
00209 measured_voltage[2].SetPolar(voltageC.Mag(),voltageC.Arg());
00210
00211 if ((solver_method == SM_NR && NR_cycle == true)||solver_method == SM_FBS)
00212 {
00213 if (t1 > last_t)
00214 {
00215 dt = t1 - last_t;
00216 last_t = t1;
00217 }
00218 else
00219 dt = 0;
00220
00221 measured_current[0] = current_inj[0];
00222 measured_current[1] = current_inj[1];
00223 measured_current[2] = -(measured_current[1]+measured_current[0]);
00224
00225
00226 if (dt > 0)
00227 {
00228 measured_real_energy += measured_real_power * TO_HOURS(dt);
00229 measured_reactive_energy += measured_reactive_power * TO_HOURS(dt);
00230 }
00231
00232 indiv_measured_power[0] = measured_voltage[0]*(~measured_current[0]);
00233 indiv_measured_power[1] = complex(-1,0) * measured_voltage[1]*(~measured_current[1]);
00234 indiv_measured_power[2] = measured_voltage[2]*(~measured_current[2]);
00235
00236 measured_power = indiv_measured_power[0] + indiv_measured_power[1] + indiv_measured_power[2];
00237
00238 measured_real_power = (indiv_measured_power[0]).Re()
00239 + (indiv_measured_power[1]).Re()
00240 + (indiv_measured_power[2]).Re();
00241
00242 measured_reactive_power = (indiv_measured_power[0]).Im()
00243 + (indiv_measured_power[1]).Im()
00244 + (indiv_measured_power[2]).Im();
00245
00246 if (measured_real_power>measured_demand)
00247 measured_demand=measured_real_power;
00248
00249
00250
00251 if (bill_mode == BM_UNIFORM || bill_mode == BM_TIERED)
00252 {
00253 process_bill(t1);
00254
00255
00256 if (monthly_bill == previous_monthly_bill)
00257 {
00258 DATETIME t_next;
00259 gl_localtime(t1,&t_next);
00260
00261 t_next.day = bill_day;
00262
00263 if (t_next.month != 12)
00264 t_next.month += 1;
00265 else
00266 {
00267 t_next.month = 1;
00268 t_next.year += 1;
00269 }
00270
00271 next_time = gl_mktime(&t_next);
00272 }
00273 }
00274
00275 if(bill_mode == BM_HOURLY && power_market != NULL && price_prop != NULL){
00276 double *pprice = (gl_get_double(power_market, price_prop));
00277 double seconds;
00278 price = *pprice;
00279
00280 if (dt != last_t)
00281 seconds = (double)(dt);
00282 else
00283 seconds = 0;
00284
00285 hourly_acc += seconds/3600 * price * measured_real_power/1000;
00286
00287 process_bill(t1);
00288
00289 if (monthly_bill == previous_monthly_bill)
00290 {
00291 DATETIME t_next;
00292 gl_localtime(t1,&t_next);
00293
00294 t_next.day = bill_day;
00295
00296 if (t_next.month != 12)
00297 t_next.month += 1;
00298 else
00299 {
00300 t_next.month = 1;
00301 t_next.year += 1;
00302 }
00303
00304 next_time = gl_mktime(&t_next);
00305 }
00306 }
00307 }
00308 rv = triplex_node::postsync(t1);
00309
00310 if (next_time != 0 && next_time < rv)
00311 return -next_time;
00312 else
00313 return rv;
00314
00315
00316 }
00317
00318 double triplex_meter::process_bill(TIMESTAMP t1){
00319 DATETIME dtime;
00320 gl_localtime(t1,&dtime);
00321
00322 monthly_energy = measured_real_energy/1000 - previous_energy_total;
00323 monthly_bill = monthly_fee;
00324 switch(bill_mode){
00325 case BM_NONE:
00326 break;
00327 case BM_UNIFORM:
00328 monthly_bill += monthly_energy * price;
00329 break;
00330 case BM_TIERED:
00331 if(monthly_energy < tier_energy[0])
00332 monthly_bill += price * monthly_energy;
00333 else if(monthly_energy < tier_energy[1])
00334 monthly_bill += price*tier_energy[0] + tier_price[0]*(monthly_energy - tier_energy[0]);
00335 else if(monthly_energy < tier_energy[2])
00336 monthly_bill += price*tier_energy[0] + tier_price[0]*(monthly_energy - tier_energy[0]) + tier_price[1]*(monthly_energy - tier_energy[1]);
00337 else
00338 monthly_bill += price*tier_energy[0] + tier_price[0]*(monthly_energy - tier_energy[0]) + tier_price[1]*(monthly_energy - tier_energy[1]) + tier_price[2]*(monthly_energy - tier_energy[2]);
00339 break;
00340 case BM_HOURLY:
00341 monthly_bill += hourly_acc;
00342 break;
00343 }
00344
00345 if (dtime.day == bill_day && dtime.hour == 0 && dtime.month != last_bill_month)
00346 {
00347 previous_monthly_bill = monthly_bill;
00348 previous_monthly_energy = monthly_energy;
00349 previous_energy_total = measured_real_energy/1000;
00350 last_bill_month = dtime.month;
00351 hourly_acc = 0;
00352 }
00353
00354
00355
00356 return monthly_bill;
00357 }
00358
00360
00362
00363 EXPORT int isa_triplex_meter(OBJECT *obj, char *classname)
00364 {
00365 return OBJECTDATA(obj,triplex_meter)->isa(classname);
00366 }
00367
00368 EXPORT int create_triplex_meter(OBJECT **obj, OBJECT *parent)
00369 {
00370 try
00371 {
00372 *obj = gl_create_object(triplex_meter::oclass);
00373 if (*obj!=NULL)
00374 {
00375 triplex_meter *my = OBJECTDATA(*obj,triplex_meter);
00376 gl_set_parent(*obj,parent);
00377 return my->create();
00378 }
00379 }
00380 catch (const char *msg)
00381 {
00382 gl_error("create_triplex_meter: %s", msg);
00383 }
00384 return 0;
00385 }
00386
00387 EXPORT int init_triplex_meter(OBJECT *obj)
00388 {
00389 triplex_meter *my = OBJECTDATA(obj,triplex_meter);
00390 try {
00391 return my->init(obj->parent);
00392 }
00393 catch (const char *msg)
00394 {
00395 GL_THROW("%s (triplex_meter:%d): %s", my->get_name(), my->get_id(), msg);
00396 return 0;
00397 }
00398 }
00399
00400 EXPORT TIMESTAMP sync_triplex_meter(OBJECT *obj, TIMESTAMP t0, PASSCONFIG pass)
00401 {
00402 triplex_meter *pObj = OBJECTDATA(obj,triplex_meter);
00403 try {
00404 TIMESTAMP t1;
00405 switch (pass) {
00406 case PC_PRETOPDOWN:
00407 return pObj->presync(t0);
00408 case PC_BOTTOMUP:
00409 return pObj->sync(t0);
00410 case PC_POSTTOPDOWN:
00411 t1 = pObj->postsync(obj->clock,t0);
00412 obj->clock = t0;
00413 return t1;
00414 default:
00415 throw "invalid pass request";
00416 }
00417 throw "invalid pass request";
00418 } catch (const char *error) {
00419 GL_THROW("%s (triplex_meter:%d): %s", pObj->get_name(), pObj->get_id(), error);
00420 return 0;
00421 } catch (...) {
00422 GL_THROW("%s (triplex_meter:%d): %s", pObj->get_name(), pObj->get_id(), "unknown exception");
00423 return 0;
00424 }
00425 }
00426