Source: DroneCommand.js

  1. const DroneCommandArgument = require('./DroneCommandArgument');
  2. const Enum = require('./util/Enum');
  3. const { characteristicSendUuids } = require('./CharacteristicEnums');
  4. /**
  5. * Buffer types
  6. *
  7. * @property {number} ACK - Acknowledgment of previously received data
  8. * @property {number} DATA - Normal data (no ack requested)
  9. * @property {number} NON_ACK - Same as DATA
  10. * @property {number} HIGH_PRIO - Not sure about this one could be LLD
  11. * @property {number} LOW_LATENCY_DATA - Treated as normal data on the network, but are given higher priority internally
  12. * @property {number} DATA_WITH_ACK - Data requesting an ack. The receiver must send an ack for this data unit!
  13. *
  14. * @type {Enum}
  15. */
  16. const bufferType = new Enum({
  17. ACK: 0x02,
  18. DATA: 0x02,
  19. NON_ACK: 0x02,
  20. HIGH_PRIO: 0x02,
  21. LOW_LATENCY_DATA: 0x03,
  22. DATA_WITH_ACK: 0x04,
  23. });
  24. const bufferCharTranslationMap = new Enum({
  25. ACK: 'ACK_COMMAND',
  26. DATA: 'SEND_NO_ACK',
  27. NON_ACK: 'SEND_NO_ACK',
  28. HIGH_PRIO: 'SEND_HIGH_PRIORITY',
  29. LOW_LATENCY_DATA: 'SEND_NO_ACK',
  30. DATA_WITH_ACK: 'SEND_WITH_ACK',
  31. });
  32. /**
  33. * Drone command
  34. *
  35. * Used for building commands to be sent to the drone. It
  36. * is also used for the sensor readings.
  37. *
  38. * Arguments are automatically mapped on the object. This
  39. * means that it is easy to set command arguments. Default
  40. * arguments values are 0 or their enum equivalent by default.
  41. *
  42. * @example
  43. * const parser = new CommandParser();
  44. * const backFlip = parser.getCommand('minidrone', 'Animations', 'Flip', {direction: 'back'});
  45. * const frontFlip = backFlip.clone();
  46. *
  47. * backFlip.direction = 'front';
  48. *
  49. * drone.runCommand(backFlip);
  50. */
  51. class DroneCommand {
  52. /**
  53. * Creates a new DroneCommand instance
  54. * @param {object} project - Project node from the xml spec
  55. * @param {object} class_ - Class node from the xml spec
  56. * @param {object} command - Command node from the xml spec
  57. */
  58. constructor(project, class_, command) {
  59. this._project = project;
  60. this._projectId = Number(project.$.id);
  61. this._projectName = String(project.$.name);
  62. this._class = class_;
  63. this._classId = Number(class_.$.id);
  64. this._className = String(class_.$.name);
  65. this._command = command;
  66. this._commandId = Number(command.$.id);
  67. this._commandName = String(command.$.name);
  68. this._deprecated = command.$.deprecated === 'true';
  69. this._description = String(command._).trim();
  70. this._arguments = (command.arg || []).map(x => new DroneCommandArgument(x));
  71. // NON_ACK, ACK or HIGH_PRIO. Defaults to ACK
  72. this._buffer = command.$.buffer || 'DATA_WITH_ACK';
  73. this._timeout = command.$.timeout || 'POP';
  74. this._mapArguments();
  75. }
  76. /**
  77. * The project id
  78. * @returns {number} - project id
  79. */
  80. get projectId() {
  81. return this._projectId;
  82. }
  83. /**
  84. * The project name (minidrone, common, etc)
  85. * @returns {string} - project name
  86. */
  87. get projectName() {
  88. return this._projectName;
  89. }
  90. /**
  91. * The class id
  92. * @returns {number} - class id
  93. */
  94. get classId() {
  95. return this._classId;
  96. }
  97. /**
  98. * The class name
  99. * @returns {string} - class name
  100. */
  101. get className() {
  102. return this._className;
  103. }
  104. /**
  105. * The command id
  106. * @returns {number} - command id
  107. */
  108. get commandId() {
  109. return this._commandId;
  110. }
  111. /**
  112. * The command name
  113. * @returns {string} - command name
  114. */
  115. get commandName() {
  116. return this._commandName;
  117. }
  118. /**
  119. * Array containing the drone arguments
  120. * @returns {DroneCommandArgument[]} - arguments
  121. */
  122. get arguments() {
  123. return this._arguments;
  124. }
  125. /**
  126. * Returns if the command has any arguments
  127. * @returns {boolean} - command has any arguments
  128. */
  129. hasArguments() {
  130. return this.arguments.length > 0;
  131. }
  132. /**
  133. * Get the argument names. These names are also mapped to the instance
  134. * @returns {string[]} - argument names
  135. */
  136. get argumentNames() {
  137. return this.arguments.map(x => x.name);
  138. }
  139. /**
  140. * Get the command description
  141. * @returns {string} - command description
  142. */
  143. get description() {
  144. return this._description;
  145. }
  146. /**
  147. * Get if the command has been deprecated
  148. * @returns {boolean} - deprecated
  149. */
  150. get deprecated() {
  151. return this._deprecated;
  152. }
  153. /**
  154. * Get the send characteristic uuid based on the buffer type
  155. * @returns {string} - uuid as a string
  156. */
  157. get sendCharacteristicUuid() {
  158. const t = bufferCharTranslationMap[this.bufferType] || 'SEND_WITH_ACK';
  159. return 'fa' + characteristicSendUuids[t];
  160. }
  161. /**
  162. * Checks if the command has a certain argument
  163. * @param {string} key - Argument name
  164. * @returns {boolean} - If the argument exists
  165. */
  166. hasArgument(key) {
  167. return this.arguments.findIndex(x => x.name === key) !== -1;
  168. }
  169. /**
  170. * Clones the instance
  171. * @returns {DroneCommand} - Cloned instance
  172. */
  173. clone() {
  174. const command = new this.constructor(this._project, this._class, this._command);
  175. for (let i = 0; i < this.arguments.length; i++) {
  176. command.arguments[i].value = this.arguments[i].value;
  177. }
  178. return command;
  179. }
  180. /**
  181. * Converts the command to it's buffer representation
  182. * @returns {Buffer} - Command buffer
  183. * @throws TypeError
  184. */
  185. toBuffer() {
  186. const bufferLength = 6 + this.arguments.reduce((acc, val) => val.getValueSize() + acc, 0);
  187. const buffer = new Buffer(bufferLength);
  188. buffer.fill(0);
  189. buffer.writeUInt16LE(this.bufferFlag, 0);
  190. // Skip command counter (offset 1) because it's set in DroneConnection::runCommand
  191. buffer.writeUInt16LE(this.projectId, 2);
  192. buffer.writeUInt16LE(this.classId, 3);
  193. buffer.writeUInt16LE(this.commandId, 4); // two bytes
  194. let bufferOffset = 6;
  195. for (const arg of this.arguments) {
  196. const valueSize = arg.getValueSize();
  197. switch (arg.type) {
  198. case 'u8':
  199. case 'u16':
  200. case 'u32':
  201. case 'u64':
  202. buffer.writeUIntLE(Math.floor(arg.value), bufferOffset, valueSize);
  203. break;
  204. case 'i8':
  205. case 'i16':
  206. case 'i32':
  207. case 'i64':
  208. case 'enum':
  209. buffer.writeIntLE(Math.floor(arg.value), bufferOffset, valueSize);
  210. break;
  211. case 'string':
  212. buffer.write(arg.value, bufferOffset, valueSize, 'ascii');
  213. break;
  214. case 'float':
  215. buffer.writeFloatLE(arg.value, bufferOffset);
  216. break;
  217. case 'double':
  218. buffer.writeDoubleLE(arg.value, bufferOffset);
  219. break;
  220. default:
  221. throw new TypeError(`Can't encode buffer: unknown data type "${arg.type}" for argument "${arg.name}" in ${this.getToken()}`);
  222. }
  223. bufferOffset += valueSize;
  224. }
  225. return buffer;
  226. }
  227. /**
  228. * Maps the arguments to the class
  229. * @returns {void}
  230. * @private
  231. */
  232. _mapArguments() {
  233. for (const arg of this.arguments) {
  234. const init = {
  235. enumerable: false,
  236. get: () => arg,
  237. set: v => {
  238. arg.value = v;
  239. },
  240. };
  241. Object.defineProperty(this, arg.name, init);
  242. }
  243. }
  244. /**
  245. * Returns a string representation of a DroneCommand
  246. * @param {boolean} debug - If extra debug information should be shown
  247. * @returns {string} - String representation if the instance
  248. * @example
  249. * const str = command.toString();
  250. *
  251. * str === 'minidrone PilotingSettingsState PreferredPilotingModeChanged mode="medium"(1)';
  252. * @example
  253. * const str = command.toString(true);
  254. *
  255. * str === 'minidrone PilotingSettingsState PreferredPilotingModeChanged (enum)mode="medium"(1)';
  256. */
  257. toString(debug = false) {
  258. const argStr = this.arguments.map(x => x.toString(debug)).join(' ').trim();
  259. return `${this.getToken()} ${argStr}`.trim();
  260. }
  261. /**
  262. * Get the command buffer type
  263. * @returns {string} - Buffer type
  264. */
  265. get bufferType() {
  266. return this._buffer.toUpperCase();
  267. }
  268. /**
  269. * Get the command buffer flag based on it's type
  270. * @returns {number} - Buffer flag
  271. */
  272. get bufferFlag() {
  273. return bufferType[this.bufferType];
  274. }
  275. /**
  276. * Indicates the required action to be taken in case the command times out
  277. * The value of this attribute can be either POP, RETRY or FLUSH, defaulting to POP
  278. * @returns {string} - Action name
  279. */
  280. get timeoutAction() {
  281. return this._timeout;
  282. }
  283. /**
  284. * Get the token representation of the command. This
  285. * is useful for registering sensors for example
  286. * @returns {string} - Command token
  287. * @example
  288. * const backFlip = parser.getCommand('minidrone', 'Animations', 'Flip', {direction: 'back'});
  289. *
  290. * backFlip.getToken() === 'minidrone-Animations-Flip';
  291. */
  292. getToken() {
  293. return [this.projectName, this.className, this.commandName].join('-');
  294. }
  295. }
  296. module.exports = DroneCommand;