audio.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. var messageQueue = [];
  2. var info = {
  3. gpio: {
  4. enable: 3,
  5. source: 41
  6. },
  7. source: {
  8. bluetooth: 1,
  9. usb: 0
  10. },
  11. serial: {
  12. port: "/dev/ttyS2",
  13. baudrate: 115200
  14. },
  15. command: {
  16. getState: new Uint8Array([0xff, 0xfe, 0xe6, 0x1a, 0x00]),
  17. btPair: new Uint8Array([0xff, 0xfe, 0xe7, 0x19, 0x00]),
  18. volUp: new Uint8Array([0xff, 0xfe, 0xed, 0x13, 0x00]),
  19. volDown: new Uint8Array([0xff, 0xfe, 0xec, 0x14, 0x00]),
  20. playPause: new Uint8Array([0xff, 0xfe, 0xf0, 0x10, 0x00]),
  21. nextTrack: new Uint8Array([0xff, 0xfe, 0xef, 0x11, 0x00]),
  22. prevTrack: new Uint8Array([0xff, 0xfe, 0xee, 0x12, 0x00]),
  23. },
  24. audioProcessList: {},
  25. status: {
  26. source: -1,
  27. bluetooth: {
  28. paired: false,
  29. connected: false,
  30. playing: false
  31. }
  32. },
  33. files: {
  34. emptyTrack: '/usr/share/oboo/alarm/silence-1s.mp3'
  35. }
  36. }
  37. // helper functions
  38. function audioEnable (bEnable) {
  39. setGpio(info.gpio.enable, bEnable ? 1 : 0);
  40. }
  41. function selectSource (sourceVal) {
  42. setGpio(info.gpio.source, sourceVal);
  43. }
  44. function selectBtSource () {
  45. if (info.status.source !== info.source.bluetooth) {
  46. // switch to bluetooth source
  47. selectSource(info.source.bluetooth);
  48. // track that current source is bluetooth
  49. info.status.source = info.source.bluetooth;
  50. }
  51. }
  52. function selectUsbSource () {
  53. if (info.status.source !== info.source.usb) {
  54. // stop the bluetooth source if it's currently playing
  55. if (info.status.bluetooth.playing) {
  56. playPause();
  57. }
  58. // switch to bluetooth source
  59. selectSource(info.source.usb);
  60. // perform the activities required to initialize usb audio
  61. sleep(1); // wait for usb device to be recognized
  62. var ret = popen('mpg123 ' + info.files.emptyTrack)// play a dummy audio file
  63. // replay if there was an error
  64. if (ret.indexOf('error') >= 0) {
  65. var ret = popen('mpg123 ' + info.files.emptyTrack)// play a dummy audio file
  66. }
  67. print(ret);
  68. sleep(1); // wait for amp to accept input
  69. volumeSet(20);
  70. // track that current source is usb
  71. info.status.source = info.source.usb;
  72. }
  73. }
  74. function sendCommand (data) {
  75. serialWrite(data);
  76. }
  77. function sendByteArrayCommand (data) {
  78. serialWriteByteArray(data);
  79. }
  80. function getModuleState () {
  81. sendByteArrayCommand(info.command.getState);
  82. }
  83. function btPairCommand () {
  84. // var typedArray = new Uint8Array([0xff, 0xfe, 0xe7, 0x19, 0x00]);
  85. // sendByteArrayCommand(typedArray);
  86. sendByteArrayCommand(info.command.btPair);
  87. }
  88. function volumeUp () {
  89. // sendCommand("\xff\xfe\xed\x13\x00");
  90. // sendCommand("\\xff\\xfe\\xed\\x13\\x00");
  91. // var typedArray = new Uint8Array([0xff, 0xfe, 0xed, 0x13, 0x00]);
  92. // sendByteArrayCommand(typedArray);
  93. sendByteArrayCommand(info.command.volUp);
  94. }
  95. function volumeDown () {
  96. // sendCommand('\xff\xfe\xec\x14\x00');
  97. // var typedArray = new Uint8Array([0xff, 0xfe, 0xec, 0x14, 0x00]);
  98. // sendByteArrayCommand(typedArray);
  99. sendByteArrayCommand(info.command.volDown);
  100. }
  101. function volumeSet (vol) {
  102. // launchProcess("amixer set 'PCM' " + vol + '%');
  103. launchProcess("amixer set 'PCM',1 " + vol + '%');
  104. }
  105. function playPause () {
  106. sendByteArrayCommand(info.command.playPause);
  107. }
  108. function nextTrack () {
  109. sendByteArrayCommand(info.command.nextTrack);
  110. }
  111. function prevTrack () {
  112. sendByteArrayCommand(info.command.prevTrack);
  113. }
  114. function playAudioFile (filePath) {
  115. print('playing audio file');
  116. // play audio
  117. var ret = launchProcess('mpg123 ' + filePath);
  118. print('audio playback started, pid: ' + ret);
  119. }
  120. function playAudioFileLoop (filePath) {
  121. print('playing audio file in a LOOP');
  122. // start audio in loop
  123. var pid = launchProcess('mpg123 --loop -1 ' + filePath);
  124. print('audio loop started, pid: ' + pid);
  125. // add pid to process list
  126. info.audioProcessList[pid] = filePath;
  127. return pid;
  128. }
  129. function stopAudioFileByPid (pid) {
  130. // stop audio based on pid
  131. if (pid in info.audioProcessList) {
  132. print('killing audio process with pid ' + pid);
  133. // stop the process
  134. killProcess(pid);
  135. // remove from the process list
  136. delete info.audioProcessList[pid];
  137. }
  138. }
  139. // interaction functions
  140. function setBluetoothStatusBarIcon(bEnabled) {
  141. // /status { "cmd":"update", "elements": [ { "type": "bluetooth", "value": 1 } ] }
  142. publish('/status', JSON.stringify({
  143. cmd: 'update',
  144. elements: [
  145. {
  146. type: 'bluetooth',
  147. value: (bEnabled ? 1 : 0)
  148. }
  149. ]
  150. }));
  151. }
  152. // handle received serial data
  153. function handleReceivedData(e) {
  154. if (typeof e !== 'undefined' && e.constructor === Array && e.length >= 6) {
  155. // ensure first 5 bytes match expected sequence:
  156. // FF FE xx xx 01
  157. if (e[0] === 0xff &&
  158. e[1] === 0xfe &&
  159. e[4] === 0x01 )
  160. {
  161. print('received valid data from bt, last byte = ' + e[5]);
  162. var prevConnectedState = info.status.bluetooth.connected;
  163. // reset the status
  164. info.status.bluetooth.paired = false;
  165. info.status.bluetooth.connected = false;
  166. info.status.bluetooth.playing = false;
  167. // decode last byte
  168. if (e[5] & 0x01) {
  169. info.status.bluetooth.paired = true
  170. }
  171. if (e[5] & 0x02) {
  172. info.status.bluetooth.connected = true
  173. }
  174. if (e[5] & 0x04) {
  175. info.status.bluetooth.playing = true
  176. }
  177. if (e[5] & 0x08) {
  178. // currently pairing - don't need to track this though
  179. }
  180. print(JSON.stringify(info.status.bluetooth));
  181. // take action based on bluetooth status
  182. // set bluetooth status icon
  183. setBluetoothStatusBarIcon(info.status.bluetooth.connected);
  184. // switch to bluetooth source if playing or just connected
  185. if (info.status.bluetooth.playing ||
  186. (!prevConnectedState && info.status.bluetooth.connected)
  187. )
  188. {
  189. selectBtSource();
  190. }
  191. }
  192. }
  193. }
  194. // handle incoming messages
  195. function handleMessage (e) {
  196. if (typeof e.cmd !== 'undefined') {
  197. switch (e.cmd) {
  198. case 'source':
  199. if (typeof e.value !== 'undefined') {
  200. print('switching audio source to ' + e.value); //tmp debug
  201. if (e.value === 'usb') {
  202. selectUsbSource();
  203. }
  204. else if (e.value === 'bt' || e.value === 'bluetooth' ) {
  205. selectBtSource();
  206. }
  207. }
  208. break;
  209. case 'play':
  210. if (typeof e.value !== 'undefined') {
  211. print('playing audio file ' + e.value); //tmp debug
  212. playAudioFile(e.value);
  213. }
  214. break;
  215. case 'playLoop':
  216. if (typeof e.value !== 'undefined') {
  217. var ret = playAudioFileLoop(e.value);
  218. // respond with pid
  219. publish('/audioResp', JSON.stringify({
  220. action: 'play',
  221. source: e.value,
  222. pid: ret
  223. }))
  224. }
  225. break;
  226. case 'stop':
  227. if (typeof e.value !== 'undefined') {
  228. stopAudioFileByPid(e.value);
  229. }
  230. break;
  231. case 'test':
  232. // TODO: remove this when successfully tested
  233. if (typeof e.value !== 'undefined') {
  234. system(e.value);
  235. }
  236. break;
  237. case 'btPair':
  238. btPairCommand();
  239. break;
  240. case 'volUp':
  241. volumeUp();
  242. break;
  243. case 'volDown':
  244. volumeDown();
  245. break;
  246. case 'volSet':
  247. print('on volSet');
  248. if (e.value !== 'undefined') {
  249. print(e.value);
  250. volumeSet(e.value);
  251. }
  252. break;
  253. case 'playPause':
  254. playPause();
  255. break;
  256. case 'nextTrack':
  257. nextTrack();
  258. break;
  259. case 'prevTrack':
  260. prevTrack();
  261. break;
  262. default:
  263. print('Received unsupported command');
  264. break;
  265. }
  266. }
  267. }
  268. // base functions
  269. function setup() {
  270. // set bluetooth audio by default
  271. selectBtSource();
  272. // enable audio
  273. audioEnable(1);
  274. print('opening serial port');
  275. openSerialPort(info.serial.port, info.serial.baudrate);
  276. // connect to mqtt broker
  277. connect('localhost', 1883, null, function () {
  278. print('subscribing to topic');
  279. subscribe('/audio');
  280. }, false);
  281. // retrieve bluetooth module state
  282. // TODO: debug this
  283. // getModuleState();
  284. }
  285. function loop() {
  286. // print ('looping!');
  287. // look for new messages from bluetooth module
  288. var msg = serialReadByteArray();
  289. if (msg !== null && msg.length > 0) {
  290. print('returned array with length: ' + msg.length + ' values: ' + msg);
  291. handleReceivedData(msg);
  292. }
  293. // handle any incoming mqtt messages
  294. if (messageQueue.length > 0) {
  295. for (var i = 0; i < messageQueue.length; i++) {
  296. handleMessage(messageQueue[i]);
  297. }
  298. messageQueue = [];
  299. }
  300. }
  301. function onMessage(e) {
  302. if (typeof e.topic !== 'undefined' && typeof e.payload !== 'undefined' ) {
  303. print('message! topic: ' + e.topic + ', value: ' + JSON.stringify(e.payload));
  304. if (e.topic === '/audio') {
  305. messageQueue.push(e.payload);
  306. }
  307. }
  308. }