1 // electrical.cxx - a flexible, generic electrical system model.
3 // Written by Curtis Olson, started September 2002.
5 // Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24 #include <simgear/structure/exception.hxx>
25 #include <simgear/misc/sg_path.hxx>
27 #include <Main/fg_props.hxx>
28 #include <Main/globals.hxx>
30 #include "electrical.hxx"
33 FGElectricalComponent::FGElectricalComponent() :
42 FGElectricalSupplier::FGElectricalSupplier ( SGPropertyNode *node ) {
45 // cout << "Creating a supplier" << endl;
46 name = node->getStringValue("name");
47 string _model = node->getStringValue("kind");
48 // cout << "_model = " << _model << endl;
49 if ( _model == "battery" ) {
51 amp_hours = node->getFloatValue("amp-hours", 40.0);
52 percent_remaining = node->getFloatValue("percent-remaining", 1.0);
53 charge_amps = node->getFloatValue("charge-amps", 7.0);
54 } else if ( _model == "alternator" ) {
55 model = FG_ALTERNATOR;
56 rpm_src = node->getStringValue("rpm-source");
57 rpm_threshold = node->getFloatValue("rpm-threshold", 600.0);
58 ideal_amps = node->getFloatValue("amps", 60.0);
59 } else if ( _model == "external" ) {
61 ideal_amps = node->getFloatValue("amps", 60.0);
65 ideal_volts = node->getFloatValue("volts");
68 for ( i = 0; i < node->nChildren(); ++i ) {
69 SGPropertyNode *child = node->getChild(i);
70 // cout << " scanning: " << child->getName() << endl;
71 if ( !strcmp(child->getName(), "prop") ) {
72 string prop = child->getStringValue();
73 // cout << " Adding prop = " << prop << endl;
75 fgSetFloat( prop.c_str(), ideal_amps );
79 _rpm_node = fgGetNode( rpm_src.c_str(), true);
83 float FGElectricalSupplier::apply_load( float amps, float dt ) {
84 if ( model == FG_BATTERY ) {
85 // calculate amp hours used
86 float amphrs_used = amps * dt / 3600.0;
88 // calculate percent of total available capacity
89 float percent_used = amphrs_used / amp_hours;
90 percent_remaining -= percent_used;
91 if ( percent_remaining < 0.0 ) {
92 percent_remaining = 0.0;
93 } else if ( percent_remaining > 1.0 ) {
94 percent_remaining = 1.0;
96 // cout << "battery percent = " << percent_remaining << endl;
97 return amp_hours * percent_remaining;
98 } else if ( model == FG_ALTERNATOR ) {
99 // scale alternator output for rpms < 600. For rpms >= 600
100 // give full output. This is just a WAG, and probably not how
101 // it really works but I'm keeping things "simple" to start.
102 float rpm = _rpm_node->getFloatValue();
103 float factor = rpm / rpm_threshold;
104 if ( factor > 1.0 ) {
107 // cout << "alternator amps = " << amps * factor << endl;
108 float available_amps = ideal_amps * factor;
109 return available_amps - amps;
110 } else if ( model == FG_EXTERNAL ) {
111 // cout << "external amps = " << 0.0 << endl;
112 return available_amps - amps;
114 SG_LOG( SG_ALL, SG_ALERT, "unknown supplier type" );
121 float FGElectricalSupplier::get_output_volts() {
122 if ( model == FG_BATTERY ) {
123 // cout << "battery amps = " << amps << endl;
124 float x = 1.0 - percent_remaining;
125 float tmp = -(3.0 * x - 1.0);
126 float factor = (tmp*tmp*tmp*tmp*tmp + 32) / 32;
127 // cout << "battery % = " << percent_remaining <<
128 // " factor = " << factor << endl;
129 // percent_remaining -= 0.001;
130 return ideal_volts * factor;
131 } else if ( model == FG_ALTERNATOR ) {
132 // scale alternator output for rpms < 600. For rpms >= 600
133 // give full output. This is just a WAG, and probably not how
134 // it really works but I'm keeping things "simple" to start.
135 float rpm = _rpm_node->getFloatValue();
136 float factor = rpm / rpm_threshold;
137 if ( factor > 1.0 ) {
140 // cout << "alternator amps = " << amps * factor << endl;
141 return ideal_volts * factor;
142 } else if ( model == FG_EXTERNAL ) {
143 // cout << "external amps = " << 0.0 << endl;
146 SG_LOG( SG_ALL, SG_ALERT, "unknown supplier type" );
153 float FGElectricalSupplier::get_output_amps() {
154 if ( model == FG_BATTERY ) {
155 // cout << "battery amp_hours = " << amp_hours << endl;
157 // This is a WAG, but produce enough amps to burn the entire
158 // battery in one minute.
159 return amp_hours * 60.0;
160 } else if ( model == FG_ALTERNATOR ) {
161 // scale alternator output for rpms < 600. For rpms >= 600
162 // give full output. This is just a WAG, and probably not how
163 // it really works but I'm keeping things "simple" to start.
164 float rpm = _rpm_node->getFloatValue();
165 float factor = rpm / rpm_threshold;
166 if ( factor > 1.0 ) {
169 // cout << "alternator amps = " << ideal_amps * factor << endl;
170 return ideal_amps * factor;
171 } else if ( model == FG_EXTERNAL ) {
172 // cout << "external amps = " << 0.0 << endl;
175 SG_LOG( SG_ALL, SG_ALERT, "unknown supplier type" );
182 FGElectricalBus::FGElectricalBus ( SGPropertyNode *node ) {
185 name = node->getStringValue("name");
187 for ( i = 0; i < node->nChildren(); ++i ) {
188 SGPropertyNode *child = node->getChild(i);
189 if ( !strcmp(child->getName(), "prop") ) {
190 string prop = child->getStringValue();
197 FGElectricalOutput::FGElectricalOutput ( SGPropertyNode *node ) {
199 load_amps = 0.1; // arbitrary default value
201 name = node->getStringValue("name");
202 SGPropertyNode *draw = node->getNode("rated-draw");
203 if ( draw != NULL ) {
204 load_amps = draw->getFloatValue();
206 // cout << "rated draw = " << output_amps << endl;
209 for ( i = 0; i < node->nChildren(); ++i ) {
210 SGPropertyNode *child = node->getChild(i);
211 if ( !strcmp(child->getName(), "prop") ) {
212 string prop = child->getStringValue();
219 FGElectricalSwitch::FGElectricalSwitch( SGPropertyNode *node ) :
222 circuit_breaker( false )
224 bool initial_state = true;
226 for ( i = 0; i < node->nChildren(); ++i ) {
227 SGPropertyNode *child = node->getChild(i);
228 string cname = child->getName();
229 string cval = child->getStringValue();
230 if ( cname == "prop" ) {
231 switch_node = fgGetNode( cval.c_str(), true );
232 // cout << "switch node = " << cval << endl;
233 } else if ( cname == "initial-state" ) {
234 if ( cval == "off" || cval == "false" ) {
235 initial_state = false;
237 // cout << "initial state = " << initial_state << endl;
238 } else if ( cname == "rating-amps" ) {
239 rating_amps = atof( cval.c_str() );
240 circuit_breaker = true;
241 // cout << "initial state = " << initial_state << endl;
245 switch_node->setBoolValue( initial_state );
246 // cout << " value = " << switch_node->getBoolValue() << endl;
250 FGElectricalConnector::FGElectricalConnector ( SGPropertyNode *node,
251 FGElectricalSystem *es ) {
255 for ( i = 0; i < node->nChildren(); ++i ) {
256 SGPropertyNode *child = node->getChild(i);
257 string cname = child->getName();
258 string cval = child->getStringValue();
259 // cout << " " << cname << " = " << cval << endl;
260 if ( cname == "input" ) {
261 FGElectricalComponent *s = es->find( child->getStringValue() );
264 if ( s->get_kind() == FG_SUPPLIER ) {
265 s->add_output( this );
266 } else if ( s->get_kind() == FG_BUS ) {
267 s->add_output( this );
269 SG_LOG( SG_ALL, SG_ALERT,
270 "Attempt to connect to something that can't provide an output: "
271 << child->getStringValue() );
274 SG_LOG( SG_ALL, SG_ALERT, "Can't find named source: "
275 << child->getStringValue() );
277 } else if ( cname == "output" ) {
278 FGElectricalComponent *s = es->find( child->getStringValue() );
281 if ( s->get_kind() == FG_BUS ) {
282 s->add_input( this );
283 } else if ( s->get_kind() == FG_OUTPUT ) {
284 s->add_input( this );
285 } else if ( s->get_kind() == FG_SUPPLIER &&
286 ((FGElectricalSupplier *)s)->get_model()
287 == FGElectricalSupplier::FG_BATTERY ) {
288 s->add_output( this );
290 SG_LOG( SG_ALL, SG_ALERT,
291 "Attempt to connect to something that can't provide an input: "
292 << child->getStringValue() );
295 SG_LOG( SG_ALL, SG_ALERT, "Can't find named source: "
296 << child->getStringValue() );
298 } else if ( cname == "switch" ) {
299 // cout << "Switch = " << child->getStringValue() << endl;
300 FGElectricalSwitch s( child );
307 // set all switches to the specified state
308 void FGElectricalConnector::set_switches( bool state ) {
309 // cout << "setting switch state to " << state << endl;
310 for ( unsigned int i = 0; i < switches.size(); ++i ) {
311 switches[i].set_state( state );
316 // return true if all switches are true, false otherwise. A connector
317 // could have multiple switches, but they all need to be true(closed)
318 // for current to get through.
319 bool FGElectricalConnector::get_state() {
321 for ( i = 0; i < switches.size(); ++i ) {
322 if ( ! switches[i].get_state() ) {
331 FGElectricalSystem::FGElectricalSystem ( SGPropertyNode *node ) :
338 for ( i = 0; i < node->nChildren(); ++i ) {
339 SGPropertyNode *child = node->getChild(i);
340 string cname = child->getName();
341 string cval = child->getStringValue();
342 if ( cname == "name" ) {
344 } else if ( cname == "number" ) {
345 num = child->getIntValue();
346 } else if ( cname == "path" ) {
349 SG_LOG( SG_SYSTEMS, SG_WARN,
350 "Error in electrical system config logic" );
351 if ( name.length() ) {
352 SG_LOG( SG_SYSTEMS, SG_WARN, "Section = " << name );
359 FGElectricalSystem::~FGElectricalSystem () {
363 void FGElectricalSystem::init () {
364 config_props = new SGPropertyNode;
366 _volts_out = fgGetNode( "/systems/electrical/volts", true );
367 _amps_out = fgGetNode( "/systems/electrical/amps", true );
369 // allow the electrical system to be specified via the
370 // aircraft-set.xml file (for backwards compatibility) or through
371 // the aircraft-systems.xml file. If a -set.xml entry is
372 // specified, that overrides the system entry.
373 SGPropertyNode *path_n = fgGetNode("/sim/systems/electrical/path");
375 if ( path.length() ) {
376 SG_LOG( SG_ALL, SG_ALERT,
377 "NOTICE: System manager configuration specifies an " <<
378 "electrical system model from: " << path << " but it is " <<
379 "being overridden by the one specified in the -set.xml " <<
380 "file: " << path_n->getStringValue() );
383 path = path_n->getStringValue();
386 if ( path.length() ) {
387 SGPath config( globals->get_fg_root() );
388 config.append( path );
390 SG_LOG( SG_ALL, SG_ALERT, "Reading electrical system model from "
393 readProperties( config.str(), config_props );
398 SG_LOG( SG_ALL, SG_ALERT,
399 "Detected an internal inconsistancy in the electrical");
400 SG_LOG( SG_ALL, SG_ALERT,
401 " system specification file. See earlier errors for" );
402 SG_LOG( SG_ALL, SG_ALERT,
406 } catch (const sg_exception& exc) {
407 SG_LOG( SG_ALL, SG_ALERT, "Failed to load electrical system model: "
412 SG_LOG( SG_ALL, SG_WARN,
413 "No xml-based electrical model specified for this model!");
417 _amps_out->setDoubleValue(0);
424 void FGElectricalSystem::bind () {
428 void FGElectricalSystem::unbind () {
432 void FGElectricalSystem::update (double dt) {
437 // cout << "Updating electrical system, dt = " << dt << endl;
441 // zero out the voltage before we start, but don't clear the
442 // requested load values.
443 for ( i = 0; i < suppliers.size(); ++i ) {
444 suppliers[i]->set_volts( 0.0 );
446 for ( i = 0; i < buses.size(); ++i ) {
447 buses[i]->set_volts( 0.0 );
449 for ( i = 0; i < outputs.size(); ++i ) {
450 outputs[i]->set_volts( 0.0 );
452 for ( i = 0; i < connectors.size(); ++i ) {
453 connectors[i]->set_volts( 0.0 );
456 // for each "external" supplier, propagate the electrical current
457 for ( i = 0; i < suppliers.size(); ++i ) {
458 FGElectricalSupplier *node = (FGElectricalSupplier *)suppliers[i];
459 if ( node->get_model() == FGElectricalSupplier::FG_EXTERNAL ) {
461 // cout << "Starting propagation: " << suppliers[i]->get_name()
463 load = propagate( suppliers[i], dt,
464 node->get_output_volts(),
465 node->get_output_amps(),
468 if ( node->apply_load( load, dt ) < 0.0 ) {
469 cout << "Error drawing more current than available!" << endl;
474 // for each "alternator" supplier, propagate the electrical
476 for ( i = 0; i < suppliers.size(); ++i ) {
477 FGElectricalSupplier *node = (FGElectricalSupplier *)suppliers[i];
478 if ( node->get_model() == FGElectricalSupplier::FG_ALTERNATOR) {
480 // cout << "Starting propagation: " << suppliers[i]->get_name()
482 load = propagate( suppliers[i], dt,
483 node->get_output_volts(),
484 node->get_output_amps(),
487 if ( node->apply_load( load, dt ) < 0.0 ) {
488 cout << "Error drawing more current than available!" << endl;
493 // for each "battery" supplier, propagate the electrical
495 for ( i = 0; i < suppliers.size(); ++i ) {
496 FGElectricalSupplier *node = (FGElectricalSupplier *)suppliers[i];
497 if ( node->get_model() == FGElectricalSupplier::FG_BATTERY ) {
499 // cout << "Starting propagation: " << suppliers[i]->get_name()
501 load = propagate( suppliers[i], dt,
502 node->get_output_volts(),
503 node->get_output_amps(),
505 // cout << "battery load = " << load << endl;
507 if ( node->apply_load( load, dt ) < 0.0 ) {
508 cout << "Error drawing more current than available!" << endl;
514 = fgGetFloat("/systems/electrical/suppliers/alternator") / 60.0;
516 // impliment an extremely simplistic voltage model (assumes
517 // certain naming conventions in electrical system config)
518 // FIXME: we probably want to be able to feed power from all
519 // engines if they are running and the master-alt is switched on
521 if ( fgGetBool("/controls/engines/engine[0]/master-bat") ) {
524 if ( fgGetBool("/controls/engines/engine[0]/master-alt") ) {
525 if ( fgGetFloat("/engines/engine[0]/rpm") > 800 ) {
526 float alt_contrib = 28.0;
527 if ( alt_contrib > volts ) {
530 } else if ( fgGetFloat("/engines/engine[0]/rpm") > 200 ) {
531 float alt_contrib = 20.0;
532 if ( alt_contrib > volts ) {
537 _volts_out->setFloatValue( volts );
539 // impliment an extremely simplistic amps model (assumes certain
540 // naming conventions in the electrical system config) ... FIXME:
541 // make this more generic
543 if ( fgGetBool("/controls/engines/engine[0]/master-bat") ) {
544 if ( fgGetBool("/controls/engines/engine[0]/master-alt") &&
545 fgGetFloat("/engines/engine[0]/rpm") > 800 )
547 amps += 40.0 * alt_norm;
549 amps -= 15.0; // normal load
550 if ( fgGetBool("/controls/switches/flashing-beacon") ) {
553 if ( fgGetBool("/controls/switches/nav-lights") ) {
560 _amps_out->setFloatValue( amps );
564 bool FGElectricalSystem::build () {
565 SGPropertyNode *node;
568 int count = config_props->nChildren();
569 for ( i = 0; i < count; ++i ) {
570 node = config_props->getChild(i);
571 string name = node->getName();
572 // cout << name << endl;
573 if ( name == "supplier" ) {
574 FGElectricalSupplier *s =
575 new FGElectricalSupplier( node );
576 suppliers.push_back( s );
577 } else if ( name == "bus" ) {
579 new FGElectricalBus( node );
580 buses.push_back( b );
581 } else if ( name == "output" ) {
582 FGElectricalOutput *o =
583 new FGElectricalOutput( node );
584 outputs.push_back( o );
585 } else if ( name == "connector" ) {
586 FGElectricalConnector *c =
587 new FGElectricalConnector( node, this );
588 connectors.push_back( c );
590 SG_LOG( SG_ALL, SG_ALERT, "Unknown component type specified: "
600 // propagate the electrical current through the network, returns the
601 // total current drawn by the children of this node.
602 float FGElectricalSystem::propagate( FGElectricalComponent *node, double dt,
603 float input_volts, float input_amps,
607 float total_load = 0.0;
609 // determine the current to carry forward
611 if ( !fgGetBool("/systems/electrical/serviceable") ) {
613 } else if ( node->get_kind() == FGElectricalComponent::FG_SUPPLIER ) {
614 // cout << s << "is a supplier (" << node->get_name() << ")" << endl;
615 FGElectricalSupplier *supplier = (FGElectricalSupplier *)node;
616 if ( supplier->get_model() == FGElectricalSupplier::FG_BATTERY ) {
617 // cout << s << " (and is a battery)" << endl;
618 float battery_volts = supplier->get_output_volts();
619 if ( battery_volts < (input_volts - 0.1) ) {
620 // special handling of a battery charge condition
621 // cout << s << " (and is being charged) in v = "
622 // << input_volts << " current v = " << battery_volts
624 supplier->apply_load( -supplier->get_charge_amps(), dt );
625 return supplier->get_charge_amps();
629 } else if ( node->get_kind() == FGElectricalComponent::FG_BUS ) {
630 // cout << s << "is a bus (" << node->get_name() << ")" << endl;
632 } else if ( node->get_kind() == FGElectricalComponent::FG_OUTPUT ) {
633 // cout << s << "is an output (" << node->get_name() << ")" << endl;
636 // draw current if we have voltage
637 total_load = node->get_load_amps();
639 } else if ( node->get_kind() == FGElectricalComponent::FG_CONNECTOR ) {
640 // cout << s << "is a connector (" << node->get_name() << ")" << endl;
641 if ( ((FGElectricalConnector *)node)->get_state() ) {
646 // cout << s << " input_volts = " << volts << endl;
648 SG_LOG( SG_ALL, SG_ALERT, "unkown node type" );
653 // if this node has found a stronger power source, update the
654 // value and propagate to all children
655 if ( volts > node->get_volts() ) {
656 node->set_volts( volts );
657 for ( i = 0; i < node->get_num_outputs(); ++i ) {
658 FGElectricalComponent *child = node->get_output(i);
659 // send current equal to load
660 total_load += propagate( child, dt,
661 volts, child->get_load_amps(),
665 // if not an output node, register the downstream current draw
666 // (sum of all children) with this node. If volts are zero,
667 // current draw should be zero.
668 if ( node->get_kind() != FGElectricalComponent::FG_OUTPUT ) {
669 node->set_load_amps( total_load );
672 node->set_available_amps( input_amps - total_load );
674 // publish values to specified properties
675 for ( i = 0; i < node->get_num_props(); ++i ) {
676 fgSetFloat( node->get_prop(i).c_str(), node->get_volts() );
680 cout << s << node->get_name() << " -> (volts) " << node->get_volts()
682 cout << s << node->get_name() << " -> (load amps) " << total_load
684 cout << s << node->get_name() << " -> (input amps) " << input_amps
686 cout << s << node->get_name() << " -> (extra amps) "
687 << node->get_available_amps() << endl;
692 // cout << s << "no further propagation" << endl;
698 // search for the named component and return a pointer to it, NULL otherwise
699 FGElectricalComponent *FGElectricalSystem::find ( const string &name ) {
704 for ( i = 0; i < suppliers.size(); ++i ) {
705 s = suppliers[i]->get_name();
706 // cout << " " << s << endl;
713 for ( i = 0; i < buses.size(); ++i ) {
714 s = buses[i]->get_name();
715 // cout << " " << s << endl;
721 // then search outputs
722 for ( i = 0; i < outputs.size(); ++i ) {
723 s = outputs[i]->get_name();
724 // cout << " " << s << endl;