SimpleBattery.cc

00001 /* -*- mode:c++ -*- ********************************************************
00002  * Energy Framework for Omnet++, version 0.9
00003  *
00004  * Author:  Laura Marie Feeney
00005  *
00006  * Copyright 2009 Swedish Institute of Computer Science.
00007  *
00008  * This software is provided `as is' and without any express or implied
00009  * warranties, including, but not limited to, the implied warranties of
00010  * merchantability and fitness for a particular purpose.
00011  *
00012  ***************************************************************************/
00013 /*
00014  * A simple linear model of battery consumption.  Simple Battery
00015  * receives DrawMsg's from one or more devices, updates residual
00016  * capacity (total current * voltage * time), publishes HostState
00017  * notification on battery depletion, and provides time series and
00018  * summary information to Battery Stats module.
00019  */
00020 
00021 #include "FWMath.h"
00022 #include "SimpleBattery.h"
00023 #include "BatteryStats.h"
00024 
00025 Define_Module(SimpleBattery)
00026 ;
00027 
00028 void SimpleBattery::initialize(int stage) {
00029   BaseBattery::initialize(stage);
00030 
00031   if (stage == 0) {
00032 
00033     voltage = par("voltage");
00034     nominalCapmAh = par("nominal");
00035     if (nominalCapmAh <= 0) {
00036       error("invalid nominal capacity value");
00037     }
00038     capmAh = par("capacity");
00039 
00040     // Publish capacity to BatteryStats every publishTime (if > 0) and
00041     // whenever capacity has changed by publishDelta (if < 100%).
00042     publishTime = 0;
00043     publishTime = par("publishTime");
00044     if (publishTime > 0) {
00045       publish = new cMessage("publish", PUBLISH);
00046       publish->setSchedulingPriority(2000);
00047       scheduleAt(simTime() + publishTime, publish);
00048     }
00049     publishDelta = 1;
00050     publishDelta = par("publishDelta");
00051     if (publishDelta < 0 || publishDelta > 1) {
00052       error("invalid publishDelta value");
00053     }
00054 
00055     resolution = par("resolution");
00056 
00057     debugEV<< "capacity = " << capmAh << "mA-h (nominal = " << nominalCapmAh <<
00058     ") at " << voltage << "V" << endl;
00059     debugEV << "publishDelta = " << publishDelta * 100 << "%, publishTime = "
00060     << publishTime << "s, resolution = " << resolution << "sec"
00061     << endl;
00062 
00063     capacity = capmAh * 60 * 60 * voltage; // use mW-sec internally
00064     nominalCapacity = nominalCapmAh * 60 * 60 * voltage;
00065     batteryState = new BatteryState(nominalCapacity);
00066 
00067     residualCapacity = lastPublishCapacity = capacity;
00068 
00069     lifetime = -1; // -1 means not dead
00070 
00071     // DISable by default (use BatteryStats for data collection)
00072     residualVec.disable();
00073 
00074     residualVec.setName("residualCapacity");
00075     residualVec.record(residualCapacity);
00076 
00077     timeout = new cMessage("auto-update", AUTO_UPDATE);
00078     timeout->setSchedulingPriority(500);
00079     scheduleAt(simTime() + resolution, timeout);
00080 
00081     // publish battery depletion on hostStateCat
00082     scopeHost = (this->findHost())->getId();
00083     hostStateCat = utility->getCategory(&hostState);
00084 
00085     // periodically publish residual capacity on batteryCat
00086     if (publishDelta < 1 || publishTime> 0)
00087     batteryCat = utility->getCategory(batteryState);
00088 
00089     numDevices = hasPar("numDevices") ? par("numDevices") : 0;
00090     if (numDevices == 0) {
00091       EV << "Warning: no devices attached to battery\n";
00092     }
00093     registeredDevices = 0;
00094 
00095     devices = new DeviceEntry[numDevices];
00096     lastUpdateTime = simTime();
00097   }
00098 
00099   if (stage == 1) {
00100     hostState.set(HostState::ACTIVE);
00101     utility->publishBBItem(hostStateCat, &hostState, scopeHost);
00102 
00103     if (publishDelta < 1 || publishTime> 0 ) {
00104       batteryState->set(residualCapacity);
00105       utility->publishBBItem(batteryCat, batteryState, scopeHost);
00106     }
00107   }
00108 }
00109 
00110 int SimpleBattery::registerDevice(const std::string& name, int numAccts)
00111 {
00112   int deviceID = registeredDevices++;
00113 
00114   if(registeredDevices > numDevices) {
00115     error("To much devices registered with battery. Please adjust the Batteries numDevices parameter!");
00116   }
00117 
00118   if (!devices[deviceID].name.empty()) {
00119     error("device already registered!");
00120   }
00121 
00122   devices[deviceID].name = name;
00123 
00124   if (numAccts < 1) {
00125     error("number of activities must be at least 1");
00126   }
00127   devices[deviceID].numAccts = numAccts;
00128 
00129   double *accts = new double[numAccts];
00130   for (int i = 0; i < numAccts; i++) {
00131     accts[i] = 0.0;
00132   }
00133   devices[deviceID].accts = accts;
00134 
00135   simtime_t *times = new simtime_t[numAccts];
00136   for (int i = 0; i < numAccts; i++) {
00137     times[i] = 0.0;
00138   }
00139   devices[deviceID].times = times;
00140 
00141   debugEV<< "initialized device " << devices[deviceID].name << " as device "
00142   << deviceID << " with " << devices[deviceID].numAccts <<
00143   " accounts" << endl;
00144 
00145   return deviceID;
00146 }
00147 
00148 void SimpleBattery::draw(int deviceID, DrawAmount& amount, int activity)
00149 {
00150   if(deviceID < 0 || deviceID > registeredDevices) {
00151     error("Unknown device ID!");
00152   }
00153 
00154   if (amount.getType() == DrawAmount::CURRENT) {
00155 
00156     if (devices[deviceID].name.empty()) {
00157       error("drawMsg from unregistered device");
00158     }
00159 
00160     double current = amount.getValue();
00161     if (activity < 0 && current != 0)
00162       error("invalid CURRENT message");
00163 
00164     debugEV << simTime() << " device " << deviceID <<
00165     " (" << devices[deviceID].name << ") draw current " << current <<
00166     "mA, activity = " << activity << endl;
00167 
00168     // update the residual capacity (finish previous current draw)
00169     deductAndCheck();
00170 
00171     // set the new current draw in the device vector
00172     devices[deviceID].draw = current;
00173     devices[deviceID].currentActivity = activity;
00174   }
00175 
00176   else if (amount.getType() == DrawAmount::ENERGY) {
00177 
00178     if (devices[deviceID].name.empty()) {
00179       error("drawMsg from unregistered device");
00180     }
00181     double energy = amount.getValue();
00182     if (!(activity >=0 && activity < devices[deviceID].numAccts)) {
00183       error("invalid activity specified");
00184     }
00185 
00186     debugEV << simTime() << " device " << deviceID <<
00187     " (" << devices[deviceID].name << ") deduct " << energy <<
00188     " mW-s, activity = " << activity << endl;
00189 
00190     // deduct a fixed energy cost
00191     devices[deviceID].accts[activity] += energy;
00192     residualCapacity -= energy;
00193 
00194     // update the residual capacity (ongoing current draw), mostly
00195     // to check whether to publish (or perish)
00196     deductAndCheck();
00197   }
00198   else {
00199     error("Unknown power type!");
00200   }
00201 }
00202 
00203 void SimpleBattery::handleMessage(cMessage *msg) {
00204   if (msg->isSelfMessage()) {
00205 
00206     switch (msg->getKind()) {
00207     case AUTO_UPDATE:
00208       // update the residual capacity (ongoing current draw)
00209       scheduleAt(simTime() + resolution, timeout);
00210       deductAndCheck();
00211       break;
00212 
00213     case PUBLISH:
00214       // publish the state to the BatteryStats module
00215       utility->publishBBItem(batteryCat, batteryState, scopeHost);
00216       lastPublishCapacity = residualCapacity;
00217 
00218       scheduleAt(simTime() + publishTime, publish);
00219       break;
00220 
00221     default:
00222       error("battery receives mysterious timeout");
00223       break;
00224     }
00225   } else {
00226     error("unexpected message");
00227     delete msg;
00228   }
00229 }
00230 
00231 void SimpleBattery::handleHostState(const HostState& state)
00232 {
00233   //does nothing yet
00234 }
00235 
00236 void SimpleBattery::deductAndCheck() {
00237   // already depleted, devices should have stopped sending drawMsg,
00238   // but we catch any leftover messages in queue
00239   if (residualCapacity <= 0) {
00240     return;
00241   }
00242 
00243   simtime_t now = simTime();
00244 
00245   // If device[i] has never drawn current (e.g. because the device
00246   // hasn't been used yet or only uses ENERGY) the currentActivity is
00247   // still -1.  If the device is not drawing current at the moment,
00248   // draw has been reset to 0, so energy is also 0.  (It might perhaps
00249   // be wise to guard more carefully against fp issues later.)
00250 
00251   for (int i = 0; i < numDevices; i++) {
00252     int currentActivity = devices[i].currentActivity;
00253     if (currentActivity > -1) {
00254       double energy = devices[i].draw * voltage * (now - lastUpdateTime).dbl();
00255       if (energy > 0) {
00256         devices[i].accts[currentActivity] += energy;
00257         devices[i].times[currentActivity] += (now - lastUpdateTime);
00258         residualCapacity -= energy;
00259       }
00260     }
00261   }
00262 
00263   lastUpdateTime = now;
00264 
00265   debugEV<< simTime() << ": residual capacity = "
00266   << residualCapacity << endl;
00267 
00268   // battery is depleted
00269   if (residualCapacity <= 0.0 ) {
00270 
00271     EV << "battery depleted at t = " << now << "s" << endl;
00272 
00273     lifetime = now;
00274 
00275     // announce hostState
00276     hostState.set(HostState::FAILED);
00277     utility->publishBBItem(hostStateCat, &hostState, scopeHost);
00278 
00279     // final battery level announcement
00280     if (publishDelta < 1 || publishTime> 0) {
00281       batteryState->set(0);
00282       utility->publishBBItem(batteryCat, batteryState, scopeHost);
00283       // cancelEvent(publish);
00284     }
00285 
00286     // no more resolution-based timeouts
00287     cancelEvent(timeout);
00288   }
00289 
00290   // battery is not depleted, continue
00291   else {
00292 
00293     batteryState->set(residualCapacity);
00294 
00295     // publish the battery capacity if it changed by more than delta
00296     if ((lastPublishCapacity - residualCapacity)/capacity >= publishDelta) {
00297       utility->publishBBItem(batteryCat, batteryState, scopeHost);
00298       lastPublishCapacity = residualCapacity;
00299     }
00300   }
00301   residualVec.record(residualCapacity);
00302 }
00303 
00304   // the three functions below should be supported in all battery
00305   // modules.  in SimpleBattery, they're trivial.  a more accurate model
00306   // would require substantially more complex functionality here
00307 
00308   // Return open circuit battery voltage.  in SimpleBattery, the open
00309   // circuit voltage is fixed
00310 
00311 double SimpleBattery::getVoltage() {
00312   Enter_Method_Silent();
00313   return voltage;
00314 }
00315 
00316 // Return host's own state-of-charge estimate of residual capacity
00317 // (absolute value in mW-s).  In SimpleBattery, the state-of-charge
00318 // estimate is the same as the residual capacity.
00319 
00320 // Use getAbs() rather than residualCapacity, which can become negative
00321 
00322 double SimpleBattery::estimateResidualAbs() {
00323   Enter_Method_Silent();
00324   double value = std::max(0.0, residualCapacity);
00325   return value;
00326 }
00327 
00328 // Return host's own estimate of residual capacity (relative to
00329 // nominal capacity).
00330 
00331 double SimpleBattery::estimateResidualRelative() {
00332   Enter_Method_Silent();
00333   double value = std::max(0.0, residualCapacity);
00334   return value / nominalCapacity;
00335 }
00336 
00337 void SimpleBattery::finish() {
00338   // do a final update of battery capacity
00339   deductAndCheck();
00340 
00341   for (int i = 0; i < numDevices; i++) {
00342 
00343     double total = 0;
00344     for (int j = 0; j < devices[i].numAccts; j++) {
00345       total += devices[i].accts[j];
00346     }
00347     debugEV<< "device " << i << " (" << devices[i].name << ") consumed "
00348     << total << " mW-s at" << endl;
00349 
00350     for (int j = 0; j < devices[i].numAccts; j++) {
00351       debugEV << "activity " << j << ": " << devices[i].accts[j] << " mWs and "
00352       << devices[i].times[j] << "sec" << endl;
00353     }
00354 
00355     // check that total time in all states matches simulation time
00356     simtime_t sum = 0;
00357     for (int j = 0; j < devices[i].numAccts; j++)
00358     sum += devices[i].times[j];
00359     if (FWMath::round(sum.dbl() * 1000000) - FWMath::round(simTime().dbl() * 1000000) != 0)
00360     {
00361       EV << "WARNING: device " << devices[i].name << " total time " << sum
00362       << " != sim time " << simTime() << " (may not matter)" << endl;
00363     }
00364   }
00365 
00366   cModule *statsModule = getParentModule()->getSubmodule("batteryStats");
00367   if (statsModule) {
00368     BatteryStats *batteryStats = check_and_cast<BatteryStats *>(statsModule);
00369     batteryStats->summary(capacity, residualCapacity, lifetime);
00370     batteryStats->detail(devices, numDevices);
00371   }
00372   else {
00373     error("No batteryStats module found, please check your Host.ned");
00374   }
00375 
00376   BaseBattery::finish();
00377 }
00378 
00379 SimpleBattery::~SimpleBattery() {
00380   cancelAndDelete(timeout);
00381   if (publishTime> 0)
00382     cancelAndDelete(publish);
00383 
00384   delete [] devices; // it's ok, batteryStats is done with device info
00385 
00386   delete batteryState;
00387 }
00388