├── node ├── .gitignore ├── sfx │ ├── 0.mp3 │ ├── 1.mp3 │ ├── 2.mp3 │ ├── 3.mp3 │ ├── 4.mp3 │ ├── 5.mp3 │ ├── 6.mp3 │ ├── 7.mp3 │ ├── 8.mp3 │ ├── fanfare.mp3 │ └── scratch.mp3 ├── config.example.js ├── list-serial-ports.js ├── package.json ├── index.js └── lib │ ├── spotify-api.js │ └── controls.js ├── arduino ├── WiiChuck.zip └── wii-nunchuck-node-spotify │ └── wii-nunchuck-node-spotify.ino └── readme.md /node/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | config.js -------------------------------------------------------------------------------- /node/sfx/0.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/arduino-wii-nunchuk-node-spotify/master/node/sfx/0.mp3 -------------------------------------------------------------------------------- /node/sfx/1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/arduino-wii-nunchuk-node-spotify/master/node/sfx/1.mp3 -------------------------------------------------------------------------------- /node/sfx/2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/arduino-wii-nunchuk-node-spotify/master/node/sfx/2.mp3 -------------------------------------------------------------------------------- /node/sfx/3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/arduino-wii-nunchuk-node-spotify/master/node/sfx/3.mp3 -------------------------------------------------------------------------------- /node/sfx/4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/arduino-wii-nunchuk-node-spotify/master/node/sfx/4.mp3 -------------------------------------------------------------------------------- /node/sfx/5.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/arduino-wii-nunchuk-node-spotify/master/node/sfx/5.mp3 -------------------------------------------------------------------------------- /node/sfx/6.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/arduino-wii-nunchuk-node-spotify/master/node/sfx/6.mp3 -------------------------------------------------------------------------------- /node/sfx/7.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/arduino-wii-nunchuk-node-spotify/master/node/sfx/7.mp3 -------------------------------------------------------------------------------- /node/sfx/8.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/arduino-wii-nunchuk-node-spotify/master/node/sfx/8.mp3 -------------------------------------------------------------------------------- /arduino/WiiChuck.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/arduino-wii-nunchuk-node-spotify/master/arduino/WiiChuck.zip -------------------------------------------------------------------------------- /node/sfx/fanfare.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/arduino-wii-nunchuk-node-spotify/master/node/sfx/fanfare.mp3 -------------------------------------------------------------------------------- /node/sfx/scratch.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangespaceman/arduino-wii-nunchuk-node-spotify/master/node/sfx/scratch.mp3 -------------------------------------------------------------------------------- /node/config.example.js: -------------------------------------------------------------------------------- 1 | // 2 | var config = { 3 | say: { 4 | voice: "Daniel" 5 | }, 6 | serial: { 7 | port: "/dev/cu.usbserial-A6004cT5", 8 | baudRate: 9600 9 | }, 10 | spotify: { 11 | user: "x", 12 | clientId: "x", 13 | clientSecret: "x" 14 | } 15 | }; 16 | 17 | module.exports = config; -------------------------------------------------------------------------------- /node/list-serial-ports.js: -------------------------------------------------------------------------------- 1 | /* 2 | * List available serial ports 3 | */ 4 | 5 | var serialport = require("serialport"); 6 | 7 | serialport.list(function (err, ports) { 8 | console.log("Available serial ports:"); 9 | ports.forEach(function(port) { 10 | console.log(port.comName); 11 | // console.log(port.pnpId); 12 | // console.log(port.manufacturer); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arduino-wii-nunchuk-node-spotify", 3 | "version": "1.0.0", 4 | "description": "Control Spotify using an Arduino and a Wii Nunchuk", 5 | "main": "index.js", 6 | "dependencies": { 7 | "player": "^0.6.1", 8 | "say": "^0.6.0", 9 | "serialport": "^2.0.6", 10 | "spotify-node-applescript": "andrehaveman/spotify-node-applescript#1.0.0-alpha-1", 11 | "spotify-web-api-node": "^2.2.0" 12 | }, 13 | "devDependencies": {}, 14 | "scripts": { 15 | "test": "echo \"Error: no test specified\" && exit 1" 16 | }, 17 | "author": "", 18 | "license": "ISC" 19 | } 20 | -------------------------------------------------------------------------------- /node/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Arduino 3 | * Wii Nunchuk 4 | * Node 5 | * Spotify 6 | * ...thing 7 | */ 8 | 9 | var serialport = require("serialport"); 10 | var controls = require("./lib/controls"); 11 | 12 | try { 13 | var config = require("./config.js"); 14 | } catch (e) { 15 | console.log("ERROR: config.js file is missing"); 16 | console.log("To see available serial ports, run the command:"); 17 | console.log(" $ node list-serial-ports.js"); 18 | process.exit(1); 19 | } 20 | 21 | var serialPort = new serialport.SerialPort(config.serial.port, { 22 | baudrate: config.serial.baudRate, 23 | parser: serialport.parsers.readline("\n") 24 | }, false); 25 | 26 | serialPort.open(function (err) { 27 | if (err) { 28 | console.log(err); 29 | } else { 30 | console.log("Arduino serial port connection established"); 31 | 32 | serialPort.on("data", controls.processSerialData); 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Arduino Wii Nunchuk Node Spotify 2 | 3 | ## Post 4 | 5 | - [https://f90.co.uk/labs/arduino-wii-nunchuk-node-spotify/](https://f90.co.uk/labs/arduino-wii-nunchuk-node-spotify/) 6 | 7 | ## Setup 8 | 9 | To set up the controller: 10 | 11 | 1. Upload the Arduino sketch to the board 12 | 13 | *Note that it may be necessary to add the WiiChuck.zip file in the repo to your Arduino library, instructions for how to do this can be found [here](https://www.arduino.cc/en/Guide/Libraries#toc4)* 14 | 15 | 2. Set up the Arduino board (see the photos in the post above) 16 | 3. Clone this repo from Github 17 | 4. Install dependencies: 18 | 19 | ``` 20 | $ npm install 21 | ``` 22 | 23 | 5. Duplicate `config.example.js`, call is `example.js` and enter the relevant data 24 | 25 | - Change the default voice by editing the value of `say` 26 | - Update the location of the SerialPort (see below) 27 | - Update the Spotify user credentials. Developer API details can be set up at [https://developer.spotify.com/my-applications](https://developer.spotify.com/my-applications) 28 | 29 | 6. Run the app: 30 | 31 | ``` 32 | $ node index.js 33 | ``` 34 | 35 | 36 | ### Serial port 37 | 38 | To check the correct serial port, you can run the `list-serial-ports.js` script: 39 | 40 | ``` 41 | $ node list-serial-ports.js 42 | ``` 43 | -------------------------------------------------------------------------------- /node/lib/spotify-api.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Arduino 3 | * Wii Nunchuk 4 | * Node 5 | * Spotify 6 | * ...thing 7 | */ 8 | 9 | var SpotifyWebApi = require("spotify-web-api-node"); 10 | var config = require("../config.js"); 11 | 12 | // connect 13 | var spotifyApi = new SpotifyWebApi({ 14 | clientId: config.spotify.clientId, 15 | clientSecret: config.spotify.clientSecret 16 | }); 17 | 18 | // get playlists 19 | // (includes access token logic) 20 | function getPlaylists () { 21 | 22 | return new Promise(function(resolve, reject) { 23 | 24 | spotifyApi.clientCredentialsGrant() 25 | .then(function (data) { 26 | console.log("Spotify API access token retreived successfully"); 27 | 28 | // Save the access token so that it's used in future calls 29 | spotifyApi.setAccessToken(data.body["access_token"]); 30 | 31 | spotifyApi.getUserPlaylists(config.spotify.user, {limit: 50}) 32 | .then(function (data) { 33 | console.log("Retrieved playlists"); 34 | var playlists = data.body.items.map(function (playlist) { 35 | return { 36 | id: playlist.id, 37 | uri: playlist.uri, 38 | owner: playlist.owner.id, 39 | name: playlist.name 40 | } 41 | }); 42 | resolve(playlists); 43 | }, 44 | function (err) { 45 | console.log("Something went wrong when retrieving playlists", err); 46 | reject(err); 47 | }); 48 | 49 | }, function (err) { 50 | console.log("Something went wrong when retrieving an access token", err); 51 | reject(err); 52 | }); 53 | }); 54 | } 55 | 56 | function getFirstTrack (playlist) { 57 | return new Promise(function(resolve, reject) { 58 | spotifyApi.getPlaylistTracks(playlist.owner, playlist.id, {"limit": 1}) 59 | .then(function (data) { 60 | resolve(data.body.items[0].track); 61 | }, 62 | function (err) { 63 | console.log("Something went wrong when retrieving the first track", err); 64 | reject(err); 65 | }); 66 | }); 67 | } 68 | 69 | module.exports = { 70 | getPlaylists: getPlaylists, 71 | getFirstTrack: getFirstTrack 72 | }; -------------------------------------------------------------------------------- /node/lib/controls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Arduino 3 | * Wii Nunchuk 4 | * Node 5 | * Spotify 6 | * ...thing 7 | */ 8 | 9 | var exec = require('child_process').exec; 10 | var spotify = require("spotify-node-applescript"); 11 | var say = require("say"); 12 | var config = require("../config.js"); 13 | var spotifyApi = require("./spotify-api"); 14 | 15 | // current output state, to avoid multiple simultaneous commands 16 | var isActive = false; 17 | 18 | // update playlists every hour 19 | var playlists; 20 | getPlaylists(); 21 | 22 | // method called when serial data is received 23 | function processSerialData (data) { 24 | console.log("data received:", data); 25 | var dataArray = data.trim().split(", "); 26 | 27 | // if we're currently playing a sound, don't continue 28 | if (isActive) return; 29 | 30 | switch (dataArray[0]) { 31 | case "PLAY": 32 | play(dataArray[1], dataArray[2], dataArray[3]); 33 | break; 34 | case "SFX": 35 | sfx(dataArray[1]); 36 | break; 37 | case "TOGGLE": 38 | toggle(); 39 | break; 40 | default: 41 | console.log("Unexpected data", data); 42 | break; 43 | } 44 | } 45 | 46 | // on play 47 | function play (x, y, z) { 48 | isActive = true; 49 | var ave = (parseInt(x, 10) + parseInt(y) + parseInt(z)) / 3; 50 | var playlist = Math.round(ave) % playlists.length; 51 | 52 | var selectedPlaylist = playlists[playlist]; 53 | var firstTrackRequest = spotifyApi.getFirstTrack(selectedPlaylist); 54 | 55 | firstTrackRequest.then(function (firstTrack) { 56 | toggle(true); 57 | playSound("./sfx/fanfare.mp3") 58 | .then(function () { 59 | say.speak(config.say.voice, "You have chosen playlist " + playlist + ", " + selectedPlaylist.name, function () { 60 | isActive = false; 61 | spotify.playTrackInContext(firstTrack.uri, selectedPlaylist.uri); 62 | console.log("PLAYING:"); 63 | console.log(selectedPlaylist.name); 64 | console.log(firstTrack.name); 65 | console.log("---"); 66 | }); 67 | }); 68 | }); 69 | } 70 | 71 | // on sfx 72 | function sfx (dir) { 73 | var file = "./sfx/" + dir + ".mp3"; 74 | playSound(file) 75 | .then(function () { 76 | isActive = false; 77 | }); 78 | } 79 | 80 | // on toggle play/pause 81 | function toggle (dontPlay) { 82 | spotify.getState(function (err, state) { 83 | if (err) { 84 | console.log(err); 85 | } 86 | if (state && state.state === "playing") { 87 | spotify.pause(); 88 | playSound("./sfx/scratch.mp3") 89 | .then(function () { 90 | isActive = false; 91 | }); 92 | } else if (state && state.state === "paused" && !dontPlay) { 93 | spotify.play(); 94 | } 95 | }); 96 | } 97 | 98 | // play an mp3 sound effect 99 | function playSound (path) { 100 | isActive = true; 101 | return new Promise(function (resolve, reject) { 102 | exec("/usr/bin/afplay " + path, function (error, stdout, stderr) { 103 | if (error) { 104 | return reject(error); 105 | } 106 | return resolve(true); 107 | }); 108 | }); 109 | } 110 | 111 | // retrieve spotify playlists 112 | function getPlaylists () { 113 | var playlistRequest = spotifyApi.getPlaylists(); 114 | playlistRequest.then(function (data) { 115 | playlists = data; 116 | }); 117 | setTimeout(getPlaylists, 1000 * 60 * 60); 118 | } 119 | 120 | // api 121 | module.exports = { 122 | processSerialData: processSerialData 123 | }; -------------------------------------------------------------------------------- /arduino/wii-nunchuck-node-spotify/wii-nunchuck-node-spotify.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * Arduino 3 | * Wii Nunchuk 4 | * Node 5 | * Spotify 6 | * ...thing 7 | */ 8 | 9 | #include 10 | #include "Wire.h" 11 | #include "WiiChuck.h" 12 | 13 | WiiChuck chuck = WiiChuck(); 14 | 15 | /* 16 | * init vars 17 | */ 18 | 19 | // accelerometer 20 | int x = 0; 21 | int y = 0; 22 | int z = 0; 23 | 24 | // joystick 25 | int joyX = 0; 26 | int joyY = 0; 27 | int dir = 0; 28 | 29 | // debounce 30 | long lastDebounceTime = 0; 31 | long debounceDelay = 1000; 32 | 33 | // serial output 34 | String output; 35 | 36 | // play/pause 37 | const int buttonPin = 3; 38 | int buttonState = 0; 39 | 40 | // led 41 | const int rLedPin = 11; 42 | const int gLedPin = 10; 43 | const int bLedPin = 9; 44 | 45 | 46 | /* 47 | * setup 48 | */ 49 | void setup() { 50 | Serial.begin(9600); 51 | chuck.begin(); 52 | chuck.update(); 53 | pinMode(buttonPin, INPUT); 54 | pinMode(rLedPin, OUTPUT); 55 | pinMode(gLedPin, OUTPUT); 56 | pinMode(bLedPin, OUTPUT); 57 | } 58 | 59 | 60 | /* 61 | * loop 62 | */ 63 | void loop() { 64 | 65 | delay(10); 66 | 67 | chuck.update(); 68 | 69 | buttonState = digitalRead(buttonPin); 70 | 71 | // reset output values for each loop 72 | output = ""; 73 | 74 | readAccel(); 75 | readStick(); 76 | 77 | // check for C button - used to play sound effect based on up/down/left/right 78 | if (chuck.buttonC) { 79 | output = "SFX, " + String(dir); 80 | } 81 | 82 | // check for Z button - used to calculate playlist number based on XYZ 83 | if (chuck.buttonZ) { 84 | output = "PLAY, " + String(x) + ", " + String(y) + ", " + String(z); 85 | } 86 | 87 | // check for button press - toggle music 88 | if (buttonState == HIGH) { 89 | output = "TOGGLE"; 90 | } 91 | 92 | // update LED light based on XYZ 93 | analogWrite(rLedPin, x); 94 | analogWrite(gLedPin, y); 95 | analogWrite(bLedPin, z); 96 | 97 | // output? 98 | if (output.length() > 0 && (millis() - lastDebounceTime) > debounceDelay) { 99 | Serial.print(output); 100 | Serial.println(); 101 | lastDebounceTime = millis(); 102 | } 103 | } 104 | 105 | 106 | // read accelerometer data and calculate average 107 | void readAccel() { 108 | x = (int)chuck.readAccelX(); 109 | y = (int)chuck.readAccelY(); 110 | z = (int)chuck.readAccelZ(); 111 | 112 | x = abs(x); 113 | y = abs(y); 114 | z = abs(z); 115 | 116 | if (x > 250) { 117 | x = 250; 118 | } 119 | if (y > 250) { 120 | y = 250; 121 | } 122 | if (z > 250) { 123 | z = 250; 124 | } 125 | } 126 | 127 | 128 | // read joystick data and calculate direction 129 | void readStick() { 130 | 131 | joyX = chuck.readJoyX(); 132 | joyY = chuck.readJoyY(); 133 | 134 | // high value, based on joystick extremes 135 | // values above this limit dictates joystick direction 136 | int limit = 120; 137 | 138 | dir = 0; // default direction, none 139 | 140 | if (joyX > -limit && joyX < limit && joyY >= limit) { dir = 1; } // up 141 | if (joyX > -limit && joyX < limit && joyY <= -limit) { dir = 5; } // down 142 | if (joyX >= limit && joyY > -limit && joyY < limit) { dir = 3; } // right 143 | if (joyX <= -limit && joyY > -limit && joyY < limit) { dir = 7; } // left 144 | 145 | if (joyX >= limit && joyY >= limit) { dir = 2; } // up-right 146 | if (joyX >= limit && joyY <= -limit) { dir = 4; } // down-right 147 | if (joyX <= -limit && joyY <= -limit) { dir = 6; } // down-left 148 | if (joyX <= -limit && joyY >= limit) { dir = 8; } // up-left 149 | } 150 | 151 | --------------------------------------------------------------------------------