dual-control-tools.nas 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. ###############################################################################
  2. ## $Id: dual-control-tools.nas,v 1.3 2009/05/24 12:52:12 mfranz Exp $
  3. ##
  4. ## Nasal module for dual control over the multiplayer network.
  5. ##
  6. ## Copyright (C) 2007 - 2009 Anders Gidenstam (anders(at)gidenstam.org)
  7. ## This file is licensed under the GPL license version 2 or later.
  8. ##
  9. ###############################################################################
  10. ## MP properties
  11. var lat_mpp = "position/latitude-deg";
  12. var lon_mpp = "position/longitude-deg";
  13. var alt_mpp = "position/altitude-ft";
  14. var heading_mpp = "orientation/true-heading-deg";
  15. var pitch_mpp = "orientation/pitch-deg";
  16. var roll_mpp = "orientation/roll-deg";
  17. var rudder_mpp = "controls/flight/rudder";
  18. var aileron_mpp = "controls/flight/aileron";
  19. var elevator_mpp = "controls/flight/elevator";
  20. # Import components from the mp_broadcast module.
  21. var Binary = mp_broadcast.Binary;
  22. var MessageChannel = mp_broadcast.MessageChannel;
  23. ###############################################################################
  24. # Utility classes
  25. ############################################################
  26. # Translate a property into another.
  27. # Factor and offsets are only used for numeric values.
  28. # src - source : property node
  29. # dest - destination : property node
  30. # factor - : double
  31. # offset - : double
  32. var Translator = {};
  33. Translator.new = func (src = nil, dest = nil, factor = 1, offset = 0) {
  34. obj = { parents : [Translator],
  35. src : src,
  36. dest : dest,
  37. factor : factor,
  38. offset : offset };
  39. if (obj.src == nil or obj.dest == nil) {
  40. print("Translator[");
  41. print(" ", debug.string(obj.src));
  42. print(" ", debug.string(obj.dest));
  43. print("]");
  44. fail();
  45. }
  46. return obj;
  47. }
  48. Translator.update = func () {
  49. var v = me.src.getValue();
  50. if (is_num(v)) {
  51. me.dest.setValue(me.factor * v + me.offset);
  52. } else {
  53. if (typeof(v) == "scalar")
  54. me.dest.setValue(v);
  55. }
  56. }
  57. ############################################################
  58. # Detects flanks on two insignals encoded in a property.
  59. # - positive signal up/down flank
  60. # - negative signal up/down flank
  61. # n - source : property node
  62. # on_positive_flank - action : func (v)
  63. # on_negative_flank - action : func (v)
  64. var EdgeTrigger = {};
  65. EdgeTrigger.new = func (n, on_positive_flank, on_negative_flank) {
  66. obj = { parents : [EdgeTrigger],
  67. old : 0,
  68. node : n,
  69. pos_flank : on_positive_flank,
  70. neg_flank : on_negative_flank };
  71. if (obj.node == nil) {
  72. print("EdgeTrigger[");
  73. print(" ", debug.string(obj.node));
  74. print("]");
  75. fail();
  76. }
  77. return obj;
  78. }
  79. EdgeTrigger.update = func {
  80. # NOTE: float MP properties get interpolated.
  81. # This detector relies on that steady state is reached between
  82. # flanks.
  83. var val = me.node.getValue();
  84. if (!is_num(val)) return;
  85. if (me.old == 1) {
  86. if (val < me.old) {
  87. me.pos_flank(0);
  88. }
  89. } elsif (me.old == 0) {
  90. if (val > me.old) {
  91. me.pos_flank(1);
  92. } elsif (val < me.old) {
  93. me.neg_flank(1);
  94. }
  95. } elsif (me.old == -1) {
  96. if (val > me.old) {
  97. me.neg_flank(0);
  98. }
  99. }
  100. me.old = val;
  101. }
  102. ############################################################
  103. # StableTrigger: Triggers an action when a MPP property
  104. # becomes stable (i.e. doesn't change for
  105. # MIN_STABLE seconds).
  106. # src - MP prop : property node
  107. # action - action to take when the value becomes stable : [func(v)]
  108. # An action is triggered when value has stabilized.
  109. var StableTrigger = {};
  110. StableTrigger.new = func (src, action) {
  111. obj = { parents : [StableTrigger],
  112. src : src,
  113. action : action,
  114. old : 0,
  115. stable_since : 0,
  116. wait : 0,
  117. MIN_STABLE : 0.01 };
  118. # Error checking.
  119. var bad = (obj.src == nil) or (action = nil);
  120. if (bad) {
  121. print("StableTrigger[");
  122. print(" ", debug.string(obj.src));
  123. print(" ", debug.string(obj.action));
  124. print("]");
  125. fail();
  126. }
  127. return obj;
  128. }
  129. StableTrigger.update = func () {
  130. var v = me.src.getValue();
  131. if (!is_num(v)) return;
  132. var t = getprop("/sim/time/elapsed-sec"); # NOTE: simulated time.
  133. if ((me.old == v) and
  134. ((t - me.stable_since) > me.MIN_STABLE) and (me.wait == 1)) {
  135. # Trigger action.
  136. me.action(v);
  137. me.wait = 0;
  138. } elsif (me.old == v) {
  139. # Wait. This is either before the signal is stable or after the action.
  140. } else {
  141. me.stable_since = t;
  142. me.wait = 1;
  143. me.old = me.src.getValue();
  144. }
  145. }
  146. ############################################################
  147. # Selects the most recent value of two properties.
  148. # src1 - : property node
  149. # src2 - : property node
  150. # dest - : property node
  151. # threshold - : double
  152. var MostRecentSelector = {};
  153. MostRecentSelector.new = func (src1, src2, dest, threshold) {
  154. obj = { parents : [MostRecentSelector],
  155. old1 : 0,
  156. old2 : 0,
  157. src1 : src1,
  158. src2 : src2,
  159. dest : dest,
  160. thres : threshold };
  161. if (obj.src1 == nil or obj.src2 == nil or obj.dest == nil) {
  162. print("MostRecentSelector[");
  163. print(" ", debug.string(obj.src1));
  164. print(" ", debug.string(obj.src2));
  165. print(" ", debug.string(obj.dest));
  166. print("]");
  167. }
  168. return obj;
  169. }
  170. MostRecentSelector.update = func {
  171. var v1 = me.src1.getValue();
  172. var v2 = me.src2.getValue();
  173. if (!is_num(v1) and !is_num(v2)) return;
  174. elsif (!is_num(v1)) me.dest.setValue(v2);
  175. elsif (!is_num(v2)) me.dest.setValue(v1);
  176. else {
  177. if (abs (v2 - me.old2) > me.thres) {
  178. me.old2 = v2;
  179. me.dest.setValue(me.old2);
  180. }
  181. if (abs (v1 - me.old1) > me.thres) {
  182. me.old1 = v1;
  183. me.dest.setValue(me.old1);
  184. }
  185. }
  186. }
  187. ############################################################
  188. # Adds two input properties.
  189. # src1 - : property node
  190. # src2 - : property node
  191. # dest - : property node
  192. var Adder = {};
  193. Adder.new = func (src1, src2, dest) {
  194. obj = { parents : [DeltaAccumulator],
  195. src1 : src1,
  196. src2 : src2,
  197. dest : dest };
  198. if (obj.src1 == nil or obj.src2 == nil or obj.dest == nil) {
  199. print("Adder[");
  200. print(" ", debug.string(obj.src1));
  201. print(" ", debug.string(obj.src2));
  202. print(" ", debug.string(obj.dest));
  203. print("]");
  204. fail();
  205. }
  206. return obj;
  207. }
  208. Adder.update = func () {
  209. var v1 = me.src1.getValue();
  210. var v2 = me.src2.getValue();
  211. if (!is_num(v1) or !is_num(v2)) return;
  212. me.dest.setValue(v1 + v2);
  213. }
  214. ############################################################
  215. # Adds the delta of src to dest.
  216. # src - : property node
  217. # dest - : property node
  218. var DeltaAdder = {};
  219. DeltaAdder.new = func (src, dest) {
  220. obj = { parents : [DeltaAdder],
  221. old : 0,
  222. src : src,
  223. dest : dest };
  224. if (obj.src == nil or obj.dest == nil) {
  225. print("DeltaAdder[", debug.string(obj.src), ", ",
  226. debug.string(obj.dest), "]");
  227. fail();
  228. }
  229. return obj;
  230. }
  231. DeltaAdder.update = func () {
  232. var v = me.src.getValue();
  233. if (!is_num(v)) return;
  234. me.dest.setValue((v - me.old) + me.dest.getValue());
  235. me.old = v;
  236. }
  237. ############################################################
  238. # Switch encoder: Encodes upto 32 boolean properties in one
  239. # int property.
  240. # inputs - list of property nodes
  241. # dest - where the bitmask is stored : property node
  242. var SwitchEncoder = {};
  243. SwitchEncoder.new = func (inputs, dest) {
  244. obj = { parents : [SwitchEncoder],
  245. inputs : inputs,
  246. dest : dest };
  247. # Error checking.
  248. var bad = (obj.dest == nil);
  249. foreach (var i; inputs) {
  250. if (i == nil) { bad = 1; }
  251. }
  252. if (bad) {
  253. print("SwitchEncoder[");
  254. foreach (var i; inputs) {
  255. print(" ", debug.string(i));
  256. }
  257. print(" ", debug.string(obj.dest));
  258. print("]");
  259. fail();
  260. }
  261. return obj;
  262. }
  263. SwitchEncoder.update = func () {
  264. var v = 0;
  265. var b = 1;
  266. forindex (i; me.inputs) {
  267. if (me.inputs[i].getBoolValue()) {
  268. v = v + b;
  269. }
  270. b *= 2;
  271. }
  272. me.dest.setIntValue(v);
  273. }
  274. ############################################################
  275. # Switch decoder: Decodes a bitmask in an int property.
  276. # src - : property node
  277. # actions - list of actions : [func(b)]
  278. # Actions are triggered when their input bit change.
  279. # Due to interpolation the decoder needs to wait for a
  280. # stable input value.
  281. var SwitchDecoder = {};
  282. SwitchDecoder.new = func (src, actions) {
  283. obj = { parents : [SwitchDecoder],
  284. wait : 0,
  285. old : 0,
  286. old_stable : 0,
  287. stable_since : 0,
  288. reset : 1,
  289. src : src,
  290. actions : actions,
  291. MIN_STABLE : 0.1 };
  292. # Error checking.
  293. var bad = (obj.src == nil);
  294. foreach (var a; obj.actions) {
  295. if (a == nil) { bad = 1; }
  296. }
  297. if (bad) {
  298. print("SwitchDecoder[");
  299. print(" ", debug.string(obj.src));
  300. foreach (var a; obj.actions) {
  301. print(" ", debug.string(a));
  302. }
  303. print("]");
  304. fail();
  305. }
  306. return obj;
  307. }
  308. SwitchDecoder.update = func () {
  309. var t = getprop("/sim/time/elapsed-sec"); # NOTE: simulated time.
  310. var v = me.src.getValue();
  311. if (!is_num(v)) return;
  312. if ((me.old == v) and ((t - me.stable_since) > me.MIN_STABLE) and
  313. (me.wait == 1)) {
  314. var ov = me.old_stable;
  315. # Use this to improve.
  316. #<cptf> here's the boring version: var bittest = func(u, b) { while (b) { u = int(u / 2); b -= 1; } u != int(u / 2) * 2; }
  317. forindex (i; me.actions) {
  318. var m = math.mod(v, 2);
  319. var om = math.mod(ov, 2);
  320. if ((m != om or me.reset)) { me.actions[i](m?1:0); }
  321. v = (v - m)/2;
  322. ov = (ov - om)/2;
  323. }
  324. me.old_stable = me.src.getValue();
  325. me.wait = 0;
  326. me.reset = 0;
  327. } elsif (me.old == v) {
  328. # Wait. This is either before the bitmask is stable or after
  329. # it has been processed.
  330. } else {
  331. me.stable_since = t;
  332. me.wait = 1;
  333. me.old = me.src.getValue();
  334. }
  335. }
  336. ############################################################
  337. # Time division multiplexing encoder: Transmits a list of
  338. # properties over a MP enabled string property.
  339. # inputs - input properties : [property node]
  340. # dest - MP string prop : property node
  341. # Note: TDM can have high latency so it is best used for
  342. # non-time critical properties.
  343. var TDMEncoder = {};
  344. TDMEncoder.new = func (inputs, dest) {
  345. obj = { parents : [TDMEncoder],
  346. inputs : inputs,
  347. channel : MessageChannel.new(dest,
  348. func (msg) {
  349. print("This should not happen!");
  350. }),
  351. MIN_INT : 0.25,
  352. last_time : 0,
  353. next_item : 0,
  354. old : [] };
  355. # Error checking.
  356. var bad = (dest == nil) or (obj.channel == nil);
  357. foreach (var i; inputs) {
  358. if (i == nil) { bad = 1; }
  359. }
  360. if (bad) {
  361. print("TDMEncoder[");
  362. foreach (var i; inputs) {
  363. print(" ", debug.string(i));
  364. }
  365. print(" ", debug.string(dest));
  366. print("]");
  367. }
  368. setsize(obj.old, size(obj.inputs));
  369. return obj;
  370. }
  371. TDMEncoder.update = func () {
  372. var t = getprop("/sim/time/elapsed-sec"); # NOTE: simulated time.
  373. if (t > me.last_time + me.MIN_INT) {
  374. var n = size(me.inputs);
  375. while (1) {
  376. var v = me.inputs[me.next_item].getValue();
  377. if ((n <= 0) or (me.old[me.next_item] != v)) {
  378. # Set the MP properties to send the next item.
  379. me.channel.send(Binary.encodeByte(me.next_item) ~
  380. Binary.encodeDouble(v));
  381. me.old[me.next_item] = v;
  382. me.last_time = t;
  383. me.next_item += 1;
  384. if (me.next_item >= size(me.inputs)) { me.next_item = 0; }
  385. return;
  386. } else {
  387. # Search for changed property.
  388. n -= 1;
  389. me.next_item += 1;
  390. if (me.next_item >= size(me.inputs)) { me.next_item = 0; }
  391. }
  392. }
  393. }
  394. }
  395. ############################################################
  396. # Time division multiplexing decoder: Receives a list of
  397. # properties over a MP enabled string property.
  398. # src - MP string prop : property node
  399. # actions - list of actions : [func(v)]
  400. # An action is triggered when its value is received.
  401. # Note: TDM can have high latency so it is best used for
  402. # non-time critical properties.
  403. var TDMDecoder = {};
  404. TDMDecoder.new = func (src, actions) {
  405. obj = { parents : [TDMDecoder],
  406. actions : actions };
  407. obj.channel = MessageChannel.new(src,
  408. func (msg) {
  409. obj.process(msg);
  410. });
  411. # Error checking.
  412. var bad = (src == nil) or (obj.channel == nil);
  413. foreach (var a; actions) {
  414. if (a == nil) { bad = 1; }
  415. }
  416. if (bad) {
  417. print("TDMDecoder[");
  418. print(" ", debug.string(src));
  419. foreach (var a; actions) {
  420. print(" ", debug.string(a));
  421. }
  422. print("]");
  423. fail();
  424. }
  425. return obj;
  426. }
  427. TDMDecoder.process = func (msg) {
  428. var v1 = Binary.decodeByte(msg);
  429. var v2 = Binary.decodeDouble(substr(msg, 1));
  430. # Trigger action.
  431. me.actions[v1](v2);
  432. }
  433. TDMDecoder.update = func {
  434. me.channel.update();
  435. }
  436. ###############################################################################
  437. # Internal utility functions
  438. var is_num = func (v) {
  439. return num(v) != nil;
  440. }
  441. # fail causes a Nasal runtime error so we get a backtrace.
  442. var fail = func {
  443. error_detected_in_calling_context();
  444. }
  445. ###############################################################################
  446. ###############################################################################
  447. # Copilot selection dialog.
  448. #
  449. # Usage: dual_control_tools.copilot_dialog.show(<copilot type string>);
  450. #
  451. var COPILOT_DLG = 0;
  452. var copilot_dialog = {};
  453. ############################################################
  454. copilot_dialog.init = func (copilot_type, x = nil, y = nil) {
  455. me.x = x;
  456. me.y = y;
  457. me.bg = [0, 0, 0, 0.3]; # background color
  458. me.fg = [[1.0, 1.0, 1.0, 1.0]];
  459. #
  460. # "private"
  461. me.title = "Copilot selection";
  462. me.basenode = props.globals.getNode("/sim/remote", 1);
  463. me.dialog = nil;
  464. me.namenode = props.Node.new({"dialog-name" : me.title });
  465. me.listeners = [];
  466. me.copilot_type = copilot_type;
  467. }
  468. ############################################################
  469. copilot_dialog.create = func {
  470. if (me.dialog != nil)
  471. me.close();
  472. me.dialog = gui.Widget.new();
  473. me.dialog.set("name", me.title);
  474. if (me.x != nil)
  475. me.dialog.set("x", me.x);
  476. if (me.y != nil)
  477. me.dialog.set("y", me.y);
  478. me.dialog.set("layout", "vbox");
  479. me.dialog.set("default-padding", 0);
  480. var titlebar = me.dialog.addChild("group");
  481. titlebar.set("layout", "hbox");
  482. titlebar.addChild("empty").set("stretch", 1);
  483. titlebar.addChild("text").set("label", "Copilots online");
  484. var w = titlebar.addChild("button");
  485. w.set("pref-width", 16);
  486. w.set("pref-height", 16);
  487. w.set("legend", "");
  488. w.set("default", 0);
  489. w.set("key", "esc");
  490. w.setBinding("nasal", "dual_control_tools.copilot_dialog.destroy(); ");
  491. w.setBinding("dialog-close");
  492. me.dialog.addChild("hrule");
  493. var content = me.dialog.addChild("group");
  494. content.set("layout", "vbox");
  495. content.set("halign", "center");
  496. content.set("default-padding", 5);
  497. # Generate the dialog contents.
  498. me.players = me.find_copilot_players();
  499. var i = 0;
  500. var tmpbase = me.basenode.getNode("dialog", 1);
  501. var selected = me.basenode.getNode("pilot-callsign").getValue();
  502. foreach (var p; me.players) {
  503. var tmp = tmpbase.getNode("b[" ~ i ~ "]", 1);
  504. tmp.setBoolValue(streq(selected, p));
  505. var w = content.addChild("checkbox");
  506. w.node.setValues({"label" : p,
  507. "halign" : "left",
  508. "property" : tmp.getPath()});
  509. w.setBinding
  510. ("nasal",
  511. "dual_control_tools.copilot_dialog.select_action(" ~ i ~ ");");
  512. i = i + 1;
  513. }
  514. me.dialog.addChild("hrule");
  515. # Display the dialog.
  516. fgcommand("dialog-new", me.dialog.prop());
  517. fgcommand("dialog-show", me.namenode);
  518. }
  519. ############################################################
  520. copilot_dialog.close = func {
  521. fgcommand("dialog-close", me.namenode);
  522. }
  523. ############################################################
  524. copilot_dialog.destroy = func {
  525. COPILOT_DLG = 0;
  526. me.close();
  527. foreach(var l; me.listeners)
  528. removelistener(l);
  529. delete(gui.dialog, "\"" ~ me.title ~ "\"");
  530. }
  531. ############################################################
  532. copilot_dialog.show = func (copilot_type) {
  533. # print("Showing MPCopilots dialog!");
  534. if (!COPILOT_DLG) {
  535. COPILOT_DLG = int(getprop("/sim/time/elapsed-sec"));
  536. me.init(copilot_type);
  537. me.create();
  538. me._update_(COPILOT_DLG);
  539. }
  540. }
  541. ############################################################
  542. copilot_dialog._redraw_ = func {
  543. if (me.dialog != nil) {
  544. me.close();
  545. me.create();
  546. }
  547. }
  548. ############################################################
  549. copilot_dialog._update_ = func (id) {
  550. if (COPILOT_DLG != id) return;
  551. me._redraw_();
  552. settimer(func { me._update_(id); }, 4.1);
  553. }
  554. ############################################################
  555. copilot_dialog.select_action = func (n) {
  556. var selected = me.basenode.getNode("pilot-callsign").getValue();
  557. var bs = me.basenode.getNode("dialog").getChildren();
  558. # Assumption: There are two true b:s or none. The one not matching selected
  559. # is the new selection.
  560. var i = 0;
  561. me.basenode.getNode("pilot-callsign").setValue("");
  562. foreach (var b; bs) {
  563. if (!b.getValue() and (i == n)) {
  564. b.setValue(1);
  565. me.basenode.getNode("pilot-callsign").setValue(me.players[i]);
  566. } else {
  567. b.setValue(0);
  568. }
  569. i = i + 1;
  570. }
  571. dual_control.main.reset();
  572. me._redraw_();
  573. }
  574. ############################################################
  575. # Return a list containing all nearby copilot players of the right type.
  576. copilot_dialog.find_copilot_players = func {
  577. var mpplayers =
  578. props.globals.getNode("/ai/models").getChildren("multiplayer");
  579. var res = [];
  580. foreach (var pilot; mpplayers) {
  581. if ((pilot.getNode("valid") != nil) and
  582. (pilot.getNode("valid").getValue()) and
  583. (pilot.getNode("sim/model/path") != nil)) {
  584. var type = pilot.getNode("sim/model/path").getValue();
  585. if (type == me.copilot_type) {
  586. append(res, pilot.getNode("callsign").getValue());
  587. }
  588. }
  589. }
  590. # debug.dump(res);
  591. return res;
  592. }
  593. ###############################################################################