Electrical.nas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. # Electrical System
  2. #
  3. # Gary Neely aka 'Buckaroo'
  4. #
  5. # General-purpose electrical system for voltage propogation, loosely based on the Flightgear
  6. # system but implemented in nasal rather than C. See my related document for details and
  7. # configuration notes.
  8. #
  9. # For most purposes you will not need to edit this file. You might want to change the update
  10. # period, and if you choose a different component location you might want to change the
  11. # component path.
  12. #
  13. # Version 1.11. Released under GPL v2.
  14. #
  15. # Version 1.11 - fixed problem with 'switch' scheme propogating when no input volts present
  16. var ELECTRICAL_UPDATE = 0.3; # Update interval in seconds. Set to 0 for once/frame
  17. var component_path = "/sim/systems/electrical"; # Location of component and connector lists
  18. var component_list = props.globals.getNode(component_path).getChildren("component"); # Get list of components
  19. var components = {}; # Component hash
  20. var components_lastvolts= {}; # Quick test hash (see update section)
  21. var suppliers = [];
  22. var connector_list = props.globals.getNode(component_path).getChildren("connector"); # Get list of connectors
  23. var connectors = []; # Connector list
  24. var electrical_update = func {
  25. # Update suppliers:
  26. # Mostly this is to update alternators based on the status
  27. # of their source-prop, typically engine RPM. But it might
  28. # also update the status of an external power souce, or a
  29. # weakened or discharged battery. Currently these last two
  30. # are stubs; fill in code if desired.
  31. foreach(var supplier; suppliers) {
  32. var volts = 0;
  33. var ideal_volts = components[supplier].getNode("volts").getValue(); # Not very realistic, but good enough
  34. var kind = components[supplier].getNode("kind").getValue();
  35. if (kind == "alternator") {
  36. var min = components[supplier].getNode("source-min").getValue();
  37. var source_val = getprop(components[supplier].getNode("source-prop").getValue());
  38. if (min == nil) { min = 0; } # Minimum value may not yet be initialized
  39. if (min == 0 or source_val >= min) { # Alternator has good volts if source is up to speed
  40. volts = ideal_volts;
  41. }
  42. else {
  43. volts = source_val / min * ideal_volts; # Otherwise it delivers some weak fractional voltage
  44. }
  45. }
  46. elsif (kind == "external") { # Simple conditions for ground service
  47. if (getprop("/gear/gear[0]/wow") and getprop("velocities/groundspeed-kt") < 0.1) {
  48. volts = ideal_volts;
  49. }
  50. }
  51. else { #kind == "battery" # Stub: currently batteries always show good volts.
  52. volts = ideal_volts;
  53. }
  54. setprop(components[supplier].getNode("prop").getValue(),volts);
  55. }
  56. # The lastvolts hash keeps a record of the last
  57. # setting of outputs. If a component can receive volts
  58. # from 2 or more suppliers, we want the greatest of these
  59. # otherwise one set to 0 might over-write one set to 28.
  60. foreach(var i; keys(components_lastvolts)) { # Reset lastvolts for each component
  61. components_lastvolts[i] = -1;
  62. }
  63. # Propogate power:
  64. foreach(var connector; connectors) {
  65. var closed = 1; # Circuit begins closed
  66. var last_test = 0;
  67. var switches = connector.getChildren("switch"); # Begin testing all associated switches
  68. foreach(var switch; switches) {
  69. last_test = getprop(switch.getValue()); # Save the last test for 'variable' outputs
  70. if (last_test == nil or last_test == 0) {
  71. closed = 0; # Stop tests if any switch is open
  72. break;
  73. }
  74. }
  75. var input_component = components[connector.getNode("input").getValue()];
  76. var output = connector.getNode("output").getValue();
  77. var input_volts = 0; # Default to an open circuit
  78. if (closed) { # Switches all tested positive
  79. input_volts = getprop(input_component.getChildren("prop")[0].getValue());
  80. if (input_volts == nil) { input_volts = 0; } # Non-suppliers may not yet be initialized
  81. if (input_volts > 0 and connector.getNode("variable") != nil) { # Indicates special variable control switch output
  82. if (connector.getNode("scale") != nil) { # Indicates variable output scales input volts
  83. input_volts = input_volts * last_test * connector.getNode("scale").getValue();
  84. }
  85. else {
  86. input_volts = last_test; # Output value takes on value of last switch output
  87. }
  88. }
  89. var scheme = connector.getNode("scheme").getValue(); # Scheme stuff:
  90. if (scheme == 0) {} # Output is simply input volts
  91. elsif (scheme == 1 and input_volts) {
  92. input_volts = last_test; # Propagate the last value of the last switch
  93. }
  94. elsif (scheme == 2) {
  95. input_volts = input_volts * last_test; # Propagate volts * the value of the last switch
  96. }
  97. elsif (scheme == 3) {
  98. if (connector.getNode("scalar-value") != nil) {
  99. input_volts = connector.getNode("scalar-value").getValue(); # Propagate a real number
  100. }
  101. elsif (connector.getNode("scalar-prop") != nil) {
  102. input_volts = getprop(connector.getNode("scalar-prop").getValue()); # Propagate a real number from a property
  103. }
  104. else { input_volts = 1; } # Should never see this if validations worked
  105. }
  106. # Factoring option:
  107. if (input_volts > 0) {
  108. if (connector.getNode("factor") != nil) {
  109. input_volts = input_volts * connector.getNode("factor").getValue();
  110. }
  111. elsif (connector.getNode("factor-prop") != nil) {
  112. input_volts = input_volts * getprop(connector.getNode("factor-prop").getValue());
  113. }
  114. }
  115. }
  116. if (input_volts > components_lastvolts[output]) { # Update only if input > previous inputs
  117. var output_component = components[output];
  118. var output_props = output_component.getChildren("prop");
  119. for (var i=0; i<size(output_props); i+=1) { # Set component's outputs to new volts
  120. setprop(output_props[i].getValue(),input_volts);
  121. }
  122. components_lastvolts[output] = input_volts; # Record best input volts for this component
  123. }
  124. } # foreach connectors
  125. settimer(electrical_update, ELECTRICAL_UPDATE); # You go back, Jack, do it again...
  126. }
  127. var electrical_init = func {
  128. print("Initializing electrical system...");
  129. # Validate and build component list:
  130. for(var i=0; i<size(component_list); i+=1) {
  131. # Validations:
  132. if (component_list[i].getNode("name") == nil) {
  133. print("Error: missing name for component ",i);
  134. continue; # Skip component if no name
  135. }
  136. var name = component_list[i].getNode("name").getValue();
  137. if (contains(components,name)) {
  138. print("Error: duplicate component '",name,"'");
  139. continue; # Skip if duplicate
  140. }
  141. if (component_list[i].getNode("kind") == nil) {
  142. print("Error: missing kind for component '",name,"'");
  143. continue; # Skip if no kind
  144. }
  145. var kind = component_list[i].getNode("kind").getValue();
  146. if (kind != "battery" and
  147. kind != "alternator" and
  148. kind != "external" and
  149. kind != "output") {
  150. print("Error: bad kind for component '",name,"'");
  151. continue; # Skip if bad kind
  152. }
  153. var supplier = 0;
  154. if (kind != "output") { supplier = 1; }
  155. if (supplier and component_list[i].getNode("volts") == nil) {
  156. print("Error: missing volts for supplier '",name,"'");
  157. continue; # Skip if supplier has no ideal volts
  158. }
  159. if (kind == "alternator" and component_list[i].getNode("source-prop") == nil) {
  160. print("Error: missing source-prop for alternator '",name,"'");
  161. continue; # Skip if alternator and no source-prop
  162. }
  163. if (kind == "alternator" and component_list[i].getNode("source-min") == nil) {
  164. print("Error: missing source-min for alternator '",name,"'");
  165. continue; # Skip if alternator and no source-min
  166. }
  167. if (size(component_list[i].getChildren("prop")) == 0) {
  168. print("Error: missing prop component(s) for ",name);
  169. continue; # Skip if no properties
  170. }
  171. # All validations passed,
  172. components[name] = component_list[i]; # Add component to hash
  173. components_lastvolts[name] = 0; # Setup quickie test hash
  174. if (supplier) {
  175. append(suppliers,name); # Append supplier name to suppliers list
  176. }
  177. }
  178. # Validate and build connector list:
  179. for(var i=0; i<size(connector_list); i+=1) {
  180. # Validations:
  181. var name = "";
  182. if (connector_list[i].getNode("name") != nil and
  183. size(connector_list[i].getNode("name").getValue()) > 0) { # Optional name field; used only in error reporting
  184. name = " ("~connector_list[i].getNode("name").getValue()~")"; # form: ' (<name>) '
  185. }
  186. if (connector_list[i].getNode("input") == nil) {
  187. print("Error: missing input for connector ",i,name);
  188. continue; # Skip connector if no input
  189. }
  190. var input = connector_list[i].getNode("input").getValue();
  191. if (!contains(components,input)) {
  192. print("Error: connector ",i,name," input: '",input,"' has no match in component list");
  193. continue; # Skip connector if input matches no component
  194. }
  195. if (connector_list[i].getNode("output") == nil) {
  196. print("Error: missing output for connector ",i,name);
  197. continue; # Skip connector if no output
  198. }
  199. var output = connector_list[i].getNode("output").getValue();
  200. if (!contains(components,output)) {
  201. print("Error: connector ",i,name," output: '",output,"' has no match in component list");
  202. continue; # Skip connector if output matches no component
  203. }
  204. if (input == output) {
  205. print("Error: connector ",i,name," tried to tie to itself");
  206. continue; # Skip connector if output matches input
  207. }
  208. if (components[output].getNode("kind").getValue() != "output") {
  209. print("Error: connector ",i,name," tried to tie to a source");
  210. continue; # Skip connector if output is a source
  211. }
  212. if (connector_list[i].getNode("scheme") == nil) { connector_list[i].getNode("scheme",1); connector_list[i].getNode("scheme").setValue(0); }
  213. elsif (connector_list[i].getNode("scheme").getValue() == "volts") { connector_list[i].getNode("scheme").setValue(0); }
  214. elsif (connector_list[i].getNode("scheme").getValue() == "switch") { connector_list[i].getNode("scheme").setValue(1); }
  215. elsif (connector_list[i].getNode("scheme").getValue() == "switch-volts") { connector_list[i].getNode("scheme").setValue(2); }
  216. elsif (connector_list[i].getNode("scheme").getValue() == "scalar") { connector_list[i].getNode("scheme").setValue(3); }
  217. else {
  218. print("Error: connector ",i,name," has an invalid scheme");
  219. continue; # Skip connector if bad scheme
  220. }
  221. if (connector_list[i].getNode("scheme").getValue() == 3) {
  222. if (connector_list[i].getNode("scalar-value") == nil and
  223. connector_list[i].getNode("scalar-prop") == nil) {
  224. print("Error: connector ",i,name," scalar missing scalar-value or scalar-prop");
  225. continue; # Skip connector if bad scheme
  226. }
  227. if (connector_list[i].getNode("scalar-value") != nil and
  228. connector_list[i].getNode("scalar-prop") != nil) {
  229. print("Error: connector ",i,name," scalar has both scalar-value and scalar-prop");
  230. continue; # Skip connector if bad scheme
  231. }
  232. }
  233. # All validations passed,
  234. append(connectors,connector_list[i]); # Add connector to list
  235. }
  236. print("...Done.");
  237. }
  238. electrical_init();
  239. settimer(electrical_update, 3);