electrical-loads.nas 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. # Electrical Load Simulation, v1.1
  2. # by Martin Litzenberger 'litzi'
  3. # this model simulates load distribution among electrical consumers
  4. # in a network.
  5. # it is, however, not a physically correct model. it does not solve
  6. # Ohms equations. current is equally shared across all consumers
  7. # regardless of the individual voltage drop between
  8. # supply and the consumer.
  9. # it builds upon the electrical voltage simulation v1.1
  10. # by Gary Neely 'Buckaroo' (see in 'electrical.nas').
  11. # the network must be defined in System/electrical.xml
  12. DEBUG = 0;
  13. # in global scope
  14. var conns =[];
  15. var nodes = {};
  16. var load_path = "loads";
  17. # some helper functions
  18. var isin = func(a, b) {
  19. foreach(var i; a) if (i==b) return 1;
  20. return 0;
  21. }
  22. var copy_by_element = func (x) {
  23. var y = [];
  24. foreach(var i; x)
  25. append(y, i);
  26. return y;
  27. }
  28. # class Node..
  29. var Node = {
  30. new: func(n) {
  31. var m = { parents: [Node] };
  32. m.in = [];
  33. m.out = []; # holds my in- and output connector indizes
  34. m.supp =[];
  35. m.name = n;
  36. m.I = 0;
  37. m.draw = nil;
  38. m.vmin = nil;
  39. m.propload = nil;
  40. m.prop = components[n].getNode("prop").getValue();
  41. m.kind = components[n].getNode("kind").getValue();
  42. m.consumer = 0;
  43. m.supplier = 0;
  44. if (components[n].getNode("rated-draw") != nil)
  45. m.draw = components[n].getNode("rated-draw").getValue();
  46. else
  47. m.draw = nil;
  48. if (components[n].getNode("volts-min") != nil)
  49. m.vmin = components[n].getNode("volts-min").getValue();
  50. else
  51. m.vmin = 0;
  52. if (m.draw != nil) {
  53. # check what kind of node i am, a consumer need special setup
  54. m.kind = "consumer";
  55. m.consumer=1;
  56. m.propload = string.replace(m.prop, "outputs", load_path); ;
  57. setprop(m.propload,0);
  58. } else if (m.kind == "alternator" or m.kind == "battery") {
  59. # prepare an output prop for the suppliers
  60. m.propload = string.replace(m.prop, "suppliers", load_path); ;
  61. setprop(m.propload,0);
  62. m.supplier=1;
  63. }
  64. return m;
  65. },
  66. recurse: func (p, history, it) {
  67. # p holds the connection path consumer-connection1-connection2-..-supplier
  68. # history holds the history of all nodes that we have already passed
  69. if (me.supplier and it>0) {
  70. # path closed at a supplier, done.
  71. append(p, me.name);
  72. nodes[p[0]].addsupp(p);
  73. if (DEBUG) debug.dump(p);
  74. } else {
  75. if (it>5) return;
  76. it += 1;
  77. foreach (var i; me.out) {
  78. # check if we are not in a loop?
  79. # note: to recurse we need by-element copy of the lists, otherwise they will be referenced as objects!
  80. if ( ! isin(history, conns[i].in ) )
  81. nodes[conns[i].in].recurse( append(copy_by_element(p), i), append(copy_by_element(history), me.name), it );
  82. }
  83. }
  84. },
  85. update: func () {
  86. # calc draw per supplier of a consumer
  87. if (me.consumer) {
  88. var n = 0;
  89. var isactive = [];
  90. var v = getprop(me.prop) or 0;
  91. var I0 = (v >= me.vmin) ? me.draw : 0;
  92. foreach (var p; me.supp) {
  93. append(isactive, 1);
  94. # check state of all connectors for each supplier
  95. if ( size(p)>2 ) {
  96. foreach (var k; p[1:-2]) {
  97. if (conns[k].active == 0) {
  98. isactive[-1] = 0;
  99. break;
  100. }
  101. }
  102. }
  103. n += isactive[-1];
  104. }
  105. setprop(me.propload, -I0);
  106. # calculate output per connected node
  107. me.I = (n==0) ? 0 : I0/n;
  108. # update all the connected suppliers' current
  109. for (var j=0; j < size(me.supp); j+=1)
  110. nodes[me.supp[j][-1]].I += me.I*isactive[j];
  111. } else if (me.supplier)
  112. setprop(me.propload, me.I);
  113. },
  114. get: func ()
  115. return me.I,
  116. addin: func(i)
  117. append(me.in, i),
  118. addout: func(i)
  119. append(me.out, i),
  120. addsupp: func(o)
  121. append(me.supp, o),
  122. resetcurrent: func()
  123. if (me.supplier) me.I=0,
  124. };
  125. var loads_init = func () {
  126. print("electrical loads init: analysing network...");
  127. # init the network analysis, get all output nodes and create objects
  128. foreach(var o; keys(components))
  129. nodes[o] = Node.new(o);
  130. # init the network analysis, get all connections
  131. for(var i=0; i<size(connector_list); i+=1) {
  132. var myout = connector_list[i].getNode("output").getValue();
  133. var myin = connector_list[i].getNode("input").getValue();
  134. var switches = connector_list[i].getChildren("switch");
  135. var mysw = [];
  136. foreach(var switch; switches)
  137. append(mysw, switch.getValue());
  138. # init all connections as active
  139. append(conns, {"in": myin, "out": myout, "active": 1, "sw": mysw});
  140. nodes[myin].addin(i);
  141. nodes[myout].addout(i);
  142. }
  143. if (DEBUG) print("paths [Consumer,via-conn1,...via-connN,Supplier]");
  144. foreach(var o; keys(nodes))
  145. if (nodes[o].kind == "consumer")
  146. nodes[o].recurse([o,], [o,], 0);
  147. print("...Done.");
  148. };
  149. var loads_update = func () {
  150. # update the state of all connectors in the network
  151. for(var i=0; i<size(connector_list); i+=1) {
  152. # check if the current through this connector is 0?
  153. var vin = getprop( nodes[conns[i].in].prop ) or 0;
  154. var vout = getprop( nodes[conns[i].out].prop ) or 0;
  155. # is the voltage drop good to drive a current?
  156. if (vin >= vout) {
  157. # Begin testing all associated switches
  158. var test = 1;
  159. foreach(var switch; conns[i].sw) {
  160. test = getprop(switch);
  161. if (test == nil or test == 0) {
  162. test=0;
  163. break;
  164. }
  165. }
  166. conns[i].active = test;
  167. } else {
  168. conns[i].active = 0;
  169. }
  170. }
  171. if (DEBUG) debug.dump(conns);
  172. settimer(loads_update_consumers, 0.333);
  173. };
  174. # update all node objects to propagate the current
  175. var loads_update_consumers = func () {
  176. foreach(var o; keys(nodes)) {
  177. nodes[o].resetcurrent();
  178. }
  179. foreach(var o; keys(nodes)) {
  180. if (nodes[o].consumer) nodes[o].update();
  181. }
  182. settimer(loads_update_suppliers,0.333);
  183. };
  184. var loads_update_suppliers = func () {
  185. foreach(var o; keys(nodes)) {
  186. if (nodes[o].supplier) nodes[o].update();
  187. if (DEBUG) print(nodes[o].name,"\t",nodes[o].I);
  188. }
  189. # check loads
  190. var Itotal=0;
  191. foreach(var o; keys(nodes)) {
  192. if (nodes[o].supplier or nodes[o].consumer) Itotal += getprop(nodes[o].propload);
  193. }
  194. if (abs(Itotal) > 0.001 and DEBUG) print("Electrical: Warning total current mismatch:",Itotal);
  195. };
  196. # battery drain/charge simulation, 1sec update rate
  197. var batt = {
  198. init: func () {
  199. me.root = props.globals.initNode("/sim/systems/electrical/battery");
  200. me.capa = me.root.getNode("capacity-amph").getValue();
  201. # get the nodes for current draw and source
  202. me.source = nodes[ me.root.getNode("source-component").getValue() ];
  203. me.draw = nodes[ me.root.getNode("draw-component").getValue() ];
  204. me.charge = me.root.getNode("charge-amph");
  205. me.maxdraw = me.draw.draw;
  206. },
  207. update: func () {
  208. # ampsec to amph
  209. var newcharge = me.charge.getValue() - me.totalcurrent()/3600;
  210. me.charge.setValue( math.clamp(newcharge, 0, me.capa) );
  211. # battery draw depends on charge level!
  212. var chargenorm = newcharge / me.capa;
  213. me.draw.draw = me.maxdraw * ( (chargenorm < 0.5) ? 1.0 : (2 - 2*chargenorm));
  214. },
  215. totalcurrent: func () {
  216. # calc. total current from/into battery
  217. return getprop(me.source.propload) + getprop(me.draw.propload);
  218. }
  219. };
  220. # main timer loop:
  221. var loads_timer = maketimer(0.333, func loads_update() );
  222. var batt_timer = maketimer(1, func batt.update() );
  223. setlistener("/sim/signals/fdm-initialized", func () {
  224. loads_init();
  225. batt.init();
  226. loads_timer.start();
  227. batt_timer.start();
  228. }
  229. );