Created
April 1, 2021 21:06
-
-
Save Hypfer/fcfa39996bd7522bbe2c5f18acb1fcf4 to your computer and use it in GitHub Desktop.
Control your Valetudo-enabled Vacuum Robot using a gamepad
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* This small JS script enables you to control your Valetudo-enabled robot using a gamepad | |
* You will need to install node-hid as well as needle by running npm install node-hid and npm install needle | |
* | |
* Then, please edit VALETUDO_HOST so that it matches your setup. | |
* | |
* Keep in mind that there are no sanity checks and there's also no error handling. | |
* Your robot needs to implement the ManualControlCapability and your controller needs to be already connected | |
* | |
* Keymap: | |
* | |
* Movement is done via the D-Pad, because that was much easier to implement. Updates are sent every 60ms | |
* | |
* Start starts manual control | |
* Select stops manual control | |
* | |
* X stop | |
* Y sends the robot back to the dock | |
* | |
*/ | |
const VALETUDO_HOST = "http://localhost:3197"; | |
const HID = require('node-hid'); | |
const needle = require("needle"); | |
//VendorID and productId of the xbox360 controller. Works with ds4win and the dualshock 4 | |
const controller = new HID.HID(1118, 654); | |
let state = {}; | |
setInterval(() => { | |
handleControllerState(state); | |
}, 60) | |
controller.on("data", data => { | |
state = parseControllerState(data); | |
}) | |
/** | |
* @param state | |
*/ | |
function handleControllerState(state) { | |
if (state["button:X"] === 1) { | |
basicAction("stop"); | |
} else if(state["button:Y"] === 1) { | |
basicAction("home"); | |
} else if (state["button:Select"] === 1) { | |
toggle(false); | |
} else if (state["button:Start"] === 1) { | |
toggle(true); | |
} else if (state["button:Right"] === 1) { | |
move("rotate_clockwise"); | |
} else if (state["button:Left"] === 1) { | |
move("rotate_counterclockwise"); | |
} else if (state["button:Up"] === 1) { | |
move("forward") | |
} else if (state["button:Down"] === 1) { | |
move("backward") | |
} | |
} | |
function toggle(enabled) { | |
needle.put(VALETUDO_HOST + '/api/v2/robot/capabilities/ManualControlCapability', { | |
"action": enabled === true ? "enable" : "disable", | |
}, { json: true }, function(error, response) { | |
if(error) { | |
console.error(error); | |
} | |
}); | |
} | |
function basicAction(action) { | |
needle.put(VALETUDO_HOST + '/api/v2/robot/capabilities/BasicControlCapability', { | |
"action": action, | |
}, { json: true }, function(error, response) { | |
if(error) { | |
console.error(error); | |
} | |
}); | |
} | |
function move(action) { | |
needle.put(VALETUDO_HOST + '/api/v2/robot/capabilities/ManualControlCapability', { | |
"action": "move", | |
"movementCommand": action | |
}, { json: true }, function(error, response) { | |
if(error) { | |
console.error(error); | |
} | |
}); | |
} | |
/** | |
* This is a very poor and hacked function to fetch the keypresses from the raw HID events. | |
* Feel free to extend it so that it actually becomes a proper parse. | |
* | |
* @param {Buffer} data | |
*/ | |
function parseControllerState(data) { | |
const state = {}; | |
state["button:Start"] = data[10] >> 7 & 1; | |
state["button:Select"] = data[10] >> 6 & 1; | |
state["button:A"] = data[10] & 1; | |
state["button:B"] = data[10] >> 1 & 1; | |
state["button:X"] = data[10] >> 2 & 1; | |
state["button:Y"] = data[10] >> 3 & 1; | |
state["button:L1"] = data[10] >> 4 & 1; | |
state["button:R1"] = data[10] >> 5 & 1; | |
switch(data[11]) { | |
case 0: | |
state['button:Up'] = 0; | |
state['button:Right'] = 0; | |
state['button:Down'] = 0; | |
state['button:Left'] = 0; | |
break; | |
case 4: | |
state['button:Up'] = 1; | |
state['button:Right'] = 0; | |
state['button:Down'] = 0; | |
state['button:Left'] = 0; | |
break; | |
case 8: | |
state['button:Up'] = 1; | |
state['button:Right'] = 1; | |
state['button:Down'] = 0; | |
state['button:Left'] = 0; | |
break; | |
case 12: | |
state['button:Up'] = 0; | |
state['button:Right'] = 1; | |
state['button:Down'] = 0; | |
state['button:Left'] = 0; | |
break; | |
case 16: | |
state['button:Up'] = 0; | |
state['button:Right'] = 1; | |
state['button:Down'] = 1; | |
state['button:Left'] = 0; | |
break; | |
case 20: | |
state['button:Up'] = 0; | |
state['button:Right'] = 0; | |
state['button:Down'] = 1; | |
state['button:Left'] = 0; | |
break; | |
case 24: | |
state['button:Up'] = 0; | |
state['button:Right'] = 0; | |
state['button:Down'] = 1; | |
state['button:Left'] = 1; | |
break; | |
case 28: | |
state['button:Up'] = 0; | |
state['button:Right'] = 0; | |
state['button:Down'] = 0; | |
state['button:Left'] = 1; | |
break; | |
case 32: | |
state['button:Up'] = 1; | |
state['button:Right'] = 0; | |
state['button:Down'] = 0; | |
state['button:Left'] = 1; | |
break; | |
} | |
return state; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment