HID / Joystick / GamePad events via C App

I created a Flow for the basics of Visca control for PTZ cameras, but it soon became apparent that I really needed a joystick for a more human feel to the camera movements.

Having looked for a few hours, I came across a bit of C Code written by Jason White some years ago.

It took me less than 30 seconds to get the code up and running on an Odroid C4, running Diet-Pi and Node-RED, installed from the dietpi-software menu.

Starting the app from within Node-RED gave me all the events I needed.

When connected with a basic Thrustmaster joystick, this flow takes the event data from a slightly tweaked version of Jason's code and turns it into :-

  • Main Axis

    • Raw X&Y data
    • Compass position (8 point)
    • Direction in degrees
    • Pan and Tilt % (as in, Tilt = % of forward or back travel)
  • Throttle position

    • Value between 0 and 65534
    • % value
  • Thumb joystick (Binary)

    • 4 compass points and Center
  • All other button events

    • This thrustmaster has 4 buttons, but the app and flow will output whatever is available
  • Any other Axis data should be available for you to do something with, just by changing / copying the main function node (I've added notes in the function to help me remember what each section does, so that should help anyone expand the features)

The tweaked version of the C-Code is saved in the Comment node. Just save it to your machine and compile it.

(I run it as root because I'm lazy and I know the machine is safe, you may have to grant permissions to the HID device path if using a different user to start Node-RED)

Joystick Flow

[{"id":"cb77dfef0d6416f5","type":"debug","z":"e66541cd9d074485","name":"","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"msg","x":950,"y":380,"wires":[]},{"id":"d500de06a0c0486b","type":"exec","z":"e66541cd9d074485","command":"./opt/joystick/joystick /dev/input/js0","addpay":"","append":"","useSpawn":"true","timer":"","winHide":false,"oldrc":false,"name":"","x":660,"y":400,"wires":[["cb77dfef0d6416f5","80278a50dea8e506"],[],["48f0a027df21a672","6b96ba9bc46c219c"]]},{"id":"d87d614be93c7a79","type":"inject","z":"e66541cd9d074485","name":"Start Joystick app","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":270,"y":380,"wires":[["d500de06a0c0486b","f1e0644ab051bc4b"]]},{"id":"5644b13cd10ddaee","type":"inject","z":"e66541cd9d074485","name":"Stop Joystick app","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Now","payloadType":"str","x":300,"y":640,"wires":[["977ad04d20a3cd2f"]]},{"id":"977ad04d20a3cd2f","type":"exec","z":"e66541cd9d074485","command":"sudo pidof joystick","addpay":false,"append":"","useSpawn":"true","timer":"","winHide":false,"oldrc":false,"name":"","x":630,"y":640,"wires":[["5f350b2f32c8643d"],[],[]]},{"id":"5f350b2f32c8643d","type":"exec","z":"e66541cd9d074485","command":"sudo kill","addpay":true,"append":"","useSpawn":"true","timer":"","oldrc":false,"name":"","x":940,"y":620,"wires":[[],[],[]]},{"id":"80278a50dea8e506","type":"function","z":"e66541cd9d074485","name":"Derive Angles etc","func":"// set variables\nvar throttle = 0;\nvar button =[];\n\nvar thumb = [];\nvar pan=0;\nvar tilt =0;\nvar axis0Thresshold = 10;\nvar NS = \"\";\nvar EW = \"\";\nvar y = 0;\nvar x = 0;\n\n\n\n// Not sure if the next bit helps or not\n//  msg.button = \"\";\n//  msg.throttle = \"\";\n//  msg.axis = \"\";\n\n\nvar data = msg.payload.split(\":\");\nmsg.payload = data;\n\nif (msg.payload[0] == \"axis\"){\n    // do stuff with Axis\n\n// Axis 0 - Main movement\n\nif (msg.payload[1]==\"0\"){\n    msg.topic=\"main\";\n    \n    // test to change to numbers\n    msg.payload[2] = Number(msg.payload[2]);\n    msg.payload[3] = Number(msg.payload[3]);\n    \n    \n   // if (Number(msg.payload[2])==0 && Number(msg.payload[3]) ==0){\n    if (msg.payload[2]==0 && msg.payload[3] ==0){\n        \n        msg.topic = \"rested\";\n    }\n    else {\n        msg.topic =\"moving\";\n    }\n    \n// Convert Axis[0] to Compass points or direction for example \"downleft\"\n\n        // North\nif (msg.payload[3]  <= -axis0Thresshold ){\n    NS = \"up\"\n}\n\n        // SOUTH\nif (msg.payload[3]  >= axis0Thresshold ){\n    NS = \"down\"\n}\n\n\n        // EAST\nif (msg.payload[2]  >= axis0Thresshold ){\n    EW = \"right\"\n}\n\n        // WEST\nif (msg.payload[2]  <= -axis0Thresshold ){\n    EW = \"left\"\n}\n\nif (msg.payload[2] == 0 && msg.payload[3] ==0){\n    \n    \n    NS = \"center\";\n    EW = \"\";\n}\n\n\n    \n msg.compass = NS+EW;\n \n // Pan Speed\n pan = msg.payload[2];\n\n \n    if (pan <=0.000001){\n    pan = - pan ;\n \n    }\n    \n         pan = (pan / 32767)*100;\n         \n       msg.pan =  pan.toFixed(0);\n\n    \n \n    \n// Tilt Speed \ntilt = msg.payload[3];\n\n    if (tilt <= 0.000001){\n        \n        tilt = - tilt;\n    }\n\n    tilt = (tilt / 32767) * 100;\n\n     msg.tilt=tilt.toFixed(0);\n \n  //set Degree of main axis\n\n\nif (msg.payload[0] == \"axis\"){\n    \n    msg.payload[2] = Number(msg.payload[2]);\n    msg.payload[3] = Number(msg.payload[3]);\n    \n    x = msg.payload[2];\n    y = msg.payload[3];\n\n\n\n        // Checking if the joystick is at center (0,0) and returns a 'stand still'\n        // command by setting the return to 99999\n        if (x == 0 && y == 0) {\n\n            msg.degree = \"center\";\n\n            // Returns a value based on the quadrant and does some math to deliver the angle\n            // of the coordinates\n        }\n        \n        else if (x >= 0 && y > 0) {\n            // south correct\n            msg.degree =  (90 - (Math.atan(y / x) * 180 / Math.PI));\n\n        } \n        \n        else if (x > 0 && y <= 0) {\n            // correct - East\n            msg.degree =  (90 - (Math.atan(y / x) * 180 / Math.PI));\n\n        }\n        \n        else if (x <= 0 && y < 0) {\n        // north?\n            msg.degree =  (270 - (Math.atan(y / x) * 180 / Math.PI));\n\n        }\n        \n        else if (x < 0 && y >= 0) {\n            // correct West\n            msg.degree =  (270 - (Math.atan(y / x) * 180 / Math.PI));\n\n        } \n        \n        else {\n            msg.degree =  \"ERROR\";\n\n        }\n\n}\n\n\n        // Can't work out why the North position reports 360, when the rest is perfect\nif (msg.degree == 360){\n    msg.degree = 180;\n                    }\n  \n    // end of Axis 0 (Main stick)  \n    \n}\n\n\n // Axis 1 = Throttle & E/W Thumb\n    if (msg.payload[1] == \"1\"){\n        // X value of Axis 1 = Throttle, Convert to Percentage\n        throttle = msg.payload[2];\n      throttle = throttle   - 32767;\n      throttle = -throttle\n      msg.throttlevalue= throttle;\n      throttle = (throttle / 65534)*100;\n        msg.payload[2] = throttle.toFixed(0); // Change this to set decimal point\n        msg.throttle = msg.payload[2];\n       \n       // Y value of Axis 1 = E & W of Thumb \n        thumb[0] = Number(msg.payload[3]);\n          \n                flow.set(\"JSThumbEW\",thumb[0]);\n            thumb[1] = flow.get(\"JSThumbNS\")||0;\n    }\n    \n  // Axis 2 = Thumb N/S  \n  if (msg.payload[1] == \"2\"){\n      \n      // X value is Thumb N/S\n      thumb[0] = flow.get(\"JSThumbEW\")||0;\n      thumb[1] = Number(msg.payload[2])\n      \n            flow.set(\"JSThumbNS\",thumb[1]);\n       \n      \n  }\n    \n// Thumb stop\nif (thumb[0] ==0 && thumb[1]==0){\n    thumb = \"STOP\"; // can be a String or a Boolean\n}\n    \n    \n// End of Axis \"IF\"    \n}\n\n\n\n\n\n//  Button events\n\n\nif (msg.payload[0] == \"button\"){\n    // do stuff with Buttons\n    msg.topic = \"button\";\n    button[0] = Number(msg.payload[1]);\n    button[1] = msg.payload[2];\n    \n    if (msg.payload[2] == \"pressed\"){\n    button[2] = true;}\n    else {\n        button[2] = false;\n    }\n    \n\n\n        flow.set(\"JSbutton\"+msg.payload[1],button[2]); // button[1] for String, button[2] for Boolean\n}\n\nmsg.button = button;\n\nmsg.thumb = thumb;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1110,"y":460,"wires":[["b5349a609d15da2e","713a8098152ca0d6","c137459b95ea9faf","7bd4c5ffb77330a6","3094cdb5f71e9c85","62f7eefc098f7bf0","fe76e69c61cf33a4","bbbd4129bcbd57ef","a3ba16b4fa441e6d"]]},{"id":"b5349a609d15da2e","type":"debug","z":"e66541cd9d074485","name":"Everything","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"msg","x":1470,"y":300,"wires":[]},{"id":"713a8098152ca0d6","type":"debug","z":"e66541cd9d074485","name":"Topic","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"topic","statusType":"msg","x":1450,"y":220,"wires":[]},{"id":"38c6a484edda4978","type":"debug","z":"e66541cd9d074485","name":"Throttle %","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"throttle","statusType":"msg","x":990,"y":80,"wires":[]},{"id":"73153d1ef7353f44","type":"debug","z":"e66541cd9d074485","name":"Button","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"button","statusType":"msg","x":2130,"y":280,"wires":[]},{"id":"c137459b95ea9faf","type":"switch","z":"e66541cd9d074485","name":"Throttle","property":"throttle","propertyType":"msg","rules":[{"t":"nempty"}],"checkall":"true","repair":false,"outputs":1,"x":800,"y":240,"wires":[["934f234194014ac6","38c6a484edda4978","67d7542fba3da305"]]},{"id":"934f234194014ac6","type":"change","z":"e66541cd9d074485","name":"","rules":[{"t":"set","p":"payload.distance","pt":"msg","to":"throttle","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":990,"y":240,"wires":[["02443e36a20853de"]]},{"id":"d67f2c7c785313bc","type":"debug","z":"e66541cd9d074485","name":"tilt speed ","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"tilt","statusType":"msg","x":1200,"y":880,"wires":[]},{"id":"ee7e31c3317302ef","type":"debug","z":"e66541cd9d074485","name":"pan speed","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"pan","statusType":"msg","x":1010,"y":1020,"wires":[]},{"id":"7bd4c5ffb77330a6","type":"switch","z":"e66541cd9d074485","name":"Tilt Speed","property":"tilt","propertyType":"msg","rules":[{"t":"nempty"}],"checkall":"true","repair":false,"outputs":1,"x":1050,"y":840,"wires":[["5dcc2cb7b89e1e2f","d67f2c7c785313bc"]]},{"id":"3094cdb5f71e9c85","type":"switch","z":"e66541cd9d074485","name":"Pan Speed","property":"pan","propertyType":"msg","rules":[{"t":"nempty"}],"checkall":"true","repair":false,"outputs":1,"x":870,"y":980,"wires":[["612cda0e7637fd73","ee7e31c3317302ef"]]},{"id":"5dcc2cb7b89e1e2f","type":"change","z":"e66541cd9d074485","name":"Tilt ","rules":[{"t":"set","p":"payload","pt":"msg","to":"tilt","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1200,"y":840,"wires":[["273752c327504260"]]},{"id":"612cda0e7637fd73","type":"change","z":"e66541cd9d074485","name":"pan","rules":[{"t":"set","p":"payload","pt":"msg","to":"pan","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1010,"y":980,"wires":[["f53100ea2109d3a8"]]},{"id":"62f7eefc098f7bf0","type":"switch","z":"e66541cd9d074485","name":"Button?","property":"payload[0]","propertyType":"msg","rules":[{"t":"eq","v":"button","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":1900,"y":420,"wires":[["130ffcb2fc6f166b","73153d1ef7353f44"]]},{"id":"130ffcb2fc6f166b","type":"switch","z":"e66541cd9d074485","name":"Which Button","property":"button[0]","propertyType":"msg","rules":[{"t":"eq","v":"0","vt":"num"},{"t":"eq","v":"1","vt":"num"},{"t":"eq","v":"2","vt":"num"},{"t":"eq","v":"3","vt":"num"}],"checkall":"true","repair":false,"outputs":4,"x":2080,"y":420,"wires":[["3042a0a42f1c73cd"],["6b3ea4ab7c462d06"],["90f59ceb9ffca156"],["b1f984105c63337f"]]},{"id":"50cd6988aa9ba4fc","type":"debug","z":"e66541cd9d074485","name":"Trigger","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"button[2]","statusType":"msg","x":2560,"y":280,"wires":[]},{"id":"76df5fea1e85b5df","type":"debug","z":"e66541cd9d074485","name":"Hazzard Button","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"button[2]","statusType":"msg","x":2600,"y":400,"wires":[]},{"id":"43384027662dadc4","type":"debug","z":"e66541cd9d074485","name":"Index Finger","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"button[2]","statusType":"msg","x":2730,"y":460,"wires":[]},{"id":"ad3b266d295ce261","type":"debug","z":"e66541cd9d074485","name":"Thumb Right","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"button[2]","statusType":"msg","x":2610,"y":540,"wires":[]},{"id":"48f0a027df21a672","type":"debug","z":"e66541cd9d074485","name":"","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"msg","x":610,"y":500,"wires":[]},{"id":"f1e0644ab051bc4b","type":"ui_dropdown","z":"e66541cd9d074485","name":"Joystick App control","label":"Joystick App","tooltip":"","place":"Select option","group":"73c5347dac61dd36","order":26,"width":0,"height":0,"passthru":false,"multiple":false,"options":[{"label":"Start","value":true,"type":"bool"},{"label":"Stop","value":"SIGTERM","type":"str"}],"payload":"","topic":"topic","topicType":"msg","className":"","x":160,"y":540,"wires":[["b616511e0b21e1b9"]]},{"id":"6b96ba9bc46c219c","type":"change","z":"e66541cd9d074485","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.signal","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":140,"y":480,"wires":[["f1e0644ab051bc4b"]]},{"id":"b616511e0b21e1b9","type":"switch","z":"e66541cd9d074485","name":"","property":"payload","propertyType":"msg","rules":[{"t":"true"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":370,"y":540,"wires":[["d500de06a0c0486b"],["977ad04d20a3cd2f"]]},{"id":"fe76e69c61cf33a4","type":"switch","z":"e66541cd9d074485","name":"Thumb?","property":"thumb","propertyType":"msg","rules":[{"t":"nempty"}],"checkall":"true","repair":false,"outputs":1,"x":1340,"y":680,"wires":[["022ef97b770f2dfb","1cca1619e34a3dca","639ec059c9f433b4","36c0b6cae5206384"]]},{"id":"022ef97b770f2dfb","type":"debug","z":"e66541cd9d074485","name":"Thumb","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"thumb","statusType":"msg","x":1780,"y":620,"wires":[]},{"id":"78be11ea8613ac34","type":"switch","z":"e66541cd9d074485","name":"N/S Thumb","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"-32767","vt":"num"},{"t":"eq","v":"32767","vt":"num"}],"checkall":"true","repair":false,"outputs":2,"x":1770,"y":780,"wires":[["50e775f7bb8c78a4","676c8bbc0f55c8d2"],["b80582406eaeccb4","f96e9a47ffca73f9"]]},{"id":"1d5bc1b3e13cf1d3","type":"switch","z":"e66541cd9d074485","name":"E/W Thumb","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"-32767","vt":"num"},{"t":"eq","v":"32767","vt":"num"}],"checkall":"true","repair":false,"outputs":2,"x":1760,"y":840,"wires":[["306af5f6ef40dfec"],["67be6fb1f66c49bb"]]},{"id":"50e775f7bb8c78a4","type":"debug","z":"e66541cd9d074485","name":"North","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"thumb","statusType":"auto","x":2020,"y":740,"wires":[]},{"id":"b80582406eaeccb4","type":"debug","z":"e66541cd9d074485","name":"South","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"thumb","statusType":"auto","x":2120,"y":780,"wires":[]},{"id":"306af5f6ef40dfec","type":"debug","z":"e66541cd9d074485","name":"West","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"thumb","statusType":"auto","x":1960,"y":840,"wires":[]},{"id":"67be6fb1f66c49bb","type":"debug","z":"e66541cd9d074485","name":"East","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"thumb","statusType":"auto","x":2020,"y":900,"wires":[]},{"id":"1cca1619e34a3dca","type":"change","z":"e66541cd9d074485","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"thumb[1]","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1570,"y":780,"wires":[["78be11ea8613ac34"]]},{"id":"639ec059c9f433b4","type":"change","z":"e66541cd9d074485","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"thumb[0]","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1570,"y":840,"wires":[["1d5bc1b3e13cf1d3"]]},{"id":"36c0b6cae5206384","type":"switch","z":"e66541cd9d074485","name":"","property":"thumb","propertyType":"msg","rules":[{"t":"false"},{"t":"eq","v":"STOP","vt":"str"},{"t":"eq","v":"stop","vt":"str"},{"t":"eq","v":"centre","vt":"str"}],"checkall":"true","repair":false,"outputs":4,"x":1530,"y":680,"wires":[["0deb3b5e0263c8a8","b2eb717e3bddd61c"],["0deb3b5e0263c8a8","b2eb717e3bddd61c"],["0deb3b5e0263c8a8","b2eb717e3bddd61c"],["0deb3b5e0263c8a8","b2eb717e3bddd61c"]]},{"id":"0deb3b5e0263c8a8","type":"debug","z":"e66541cd9d074485","name":"Stop","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"thumb","targetType":"msg","statusVal":"thumb","statusType":"auto","x":1990,"y":660,"wires":[]},{"id":"6b3ea4ab7c462d06","type":"switch","z":"e66541cd9d074485","name":"","property":"button[2]","propertyType":"msg","rules":[{"t":"true"},{"t":"false"}],"checkall":"true","repair":false,"outputs":2,"x":2330,"y":400,"wires":[["76df5fea1e85b5df","384baf8f0b236a6c"],["76df5fea1e85b5df","384baf8f0b236a6c"]]},{"id":"8d311efa663a9050","type":"inject","z":"e66541cd9d074485","name":"clear","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"str","x":1690,"y":740,"wires":[["67be6fb1f66c49bb","306af5f6ef40dfec","50e775f7bb8c78a4","b80582406eaeccb4","0deb3b5e0263c8a8"]]},{"id":"94596b6eb041c562","type":"debug","z":"e66541cd9d074485","name":"Compass points","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"compass","statusType":"msg","x":1640,"y":480,"wires":[]},{"id":"bbbd4129bcbd57ef","type":"switch","z":"e66541cd9d074485","name":"Main Axis Compass","property":"compass","propertyType":"msg","rules":[{"t":"nempty"}],"checkall":"true","repair":false,"outputs":1,"x":1390,"y":480,"wires":[["94596b6eb041c562","d19f7d4c3b395b6c"]]},{"id":"90f59ceb9ffca156","type":"switch","z":"e66541cd9d074485","name":"","property":"button[2]","propertyType":"msg","rules":[{"t":"true"},{"t":"false"}],"checkall":"true","repair":false,"outputs":2,"x":2330,"y":460,"wires":[["43384027662dadc4"],["43384027662dadc4"]]},{"id":"b1f984105c63337f","type":"switch","z":"e66541cd9d074485","name":"","property":"button[2]","propertyType":"msg","rules":[{"t":"true"},{"t":"false"}],"checkall":"true","repair":false,"outputs":2,"x":2330,"y":540,"wires":[["ad3b266d295ce261","890c705085e28fab"],["890c705085e28fab"]]},{"id":"3042a0a42f1c73cd","type":"switch","z":"e66541cd9d074485","name":"","property":"button[2]","propertyType":"msg","rules":[{"t":"true"},{"t":"false"}],"checkall":"true","repair":false,"outputs":2,"x":2330,"y":260,"wires":[["50cd6988aa9ba4fc"],[]]},{"id":"8958bce7aaa238df","type":"comment","z":"e66541cd9d074485","name":"joystick.c source code","info":"The applicatrion that is doing the magic is based on Jason White's code\n\nhttps://gist.github.com/jasonwhite/c5b2048c15993d285130\n\nChanged parts are the final section that formats the output, replacing spaces with \":\" for delimiting\n\n\nVersion used here is\n\n\n/**\n * Author: Jason White\n *\n * Description:\n * Reads joystick/gamepad events and displays them.\n *\n * Compile:\n * gcc joystick.c -o joystick\n *\n * Run:\n * ./joystick [/dev/input/jsX]\n * or to use the default ./joystick\n * \n *\n * See also:\n * https://www.kernel.org/doc/Documentation/input/joystick-api.txt\n */\n#include <fcntl.h>\n#include <stdio.h>\n#include <unistd.h>\n#include <linux/joystick.h>\n\n\n/**\n * Reads a joystick event from the joystick device.\n *\n * Returns 0 on success. Otherwise -1 is returned.\n */\nint read_event(int fd, struct js_event *event)\n{\n    ssize_t bytes;\n\n    bytes = read(fd, event, sizeof(*event));\n\n    if (bytes == sizeof(*event))\n        return 0;\n\n    /* Error, could not read full event. */\n    return -1;\n}\n\n/**\n * Returns the number of axes on the controller or 0 if an error occurs.\n */\nsize_t get_axis_count(int fd)\n{\n    __u8 axes;\n\n    if (ioctl(fd, JSIOCGAXES, &axes) == -1)\n        return 0;\n\n    return axes;\n}\n\n/**\n * Returns the number of buttons on the controller or 0 if an error occurs.\n */\nsize_t get_button_count(int fd)\n{\n    __u8 buttons;\n    if (ioctl(fd, JSIOCGBUTTONS, &buttons) == -1)\n        return 0;\n\n    return buttons;\n}\n\n/**\n * Current state of an axis.\n */\nstruct axis_state {\n    short x, y;\n};\n\n/**\n * Keeps track of the current axis state.\n *\n * NOTE: This function assumes that axes are numbered starting from 0, and that\n * the X axis is an even number, and the Y axis is an odd number. However, this\n * is usually a safe assumption.\n *\n * Returns the axis that the event indicated.\n */\nsize_t get_axis_state(struct js_event *event, struct axis_state axes[3])\n{\n    size_t axis = event->number / 2;\n\n    if (axis < 3)\n    {\n        if (event->number % 2 == 0)\n            axes[axis].x = event->value;\n        else\n            axes[axis].y = event->value;\n    }\n\n    return axis;\n}\n\nint main(int argc, char *argv[])\n{\n    const char *device;\n    int js;\n    struct js_event event;\n    struct axis_state axes[3] = {0};\n    size_t axis;\n\n    if (argc > 1)\n        device = argv[1];\n    else\n        device = \"/dev/input/js0\";\n\n    js = open(device, O_RDONLY);\n\n    if (js == -1)\n        perror(\"Could not open joystick\");\n\n    /* This loop will exit if the controller is unplugged. */\n    while (read_event(js, &event) == 0)\n    {\n        switch (event.type)\n        {\n            case JS_EVENT_BUTTON:\n                printf(\"button:%u:%s:\\n\", event.number, event.value ? \"pressed\" : \"released\");\n                break;\n            case JS_EVENT_AXIS:\n                axis = get_axis_state(&event, axes);\n                if (axis < 3)\n                    printf(\"axis:%zu:%6d:%6d:\\n\", axis, axes[axis].x, axes[axis].y);\n                break;\n            default:\n                /* Ignore init events. */\n                break;\n        }\n        \n        fflush(stdout);\n    }\n\n    close(js);\n    return 0;\n}\n","x":520,"y":300,"wires":[]},{"id":"a26c2d2a68e557be","type":"debug","z":"e66541cd9d074485","name":"Degree","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"degree","statusType":"msg","x":1620,"y":540,"wires":[]},{"id":"a3ba16b4fa441e6d","type":"switch","z":"e66541cd9d074485","name":"Main Axis Degree","property":"degree","propertyType":"msg","rules":[{"t":"nnull"}],"checkall":"true","repair":false,"outputs":1,"x":1390,"y":540,"wires":[["a26c2d2a68e557be"]]},{"id":"67d7542fba3da305","type":"debug","z":"e66541cd9d074485","name":"Throttle full value","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"throttlevalue","statusType":"msg","x":1010,"y":160,"wires":[]},{"id":"73c5347dac61dd36","type":"ui_group","name":"Movement","tab":"c34e359e.fa03f8","order":2,"disp":true,"width":"7","collapse":true},{"id":"c34e359e.fa03f8","type":"ui_tab","name":"PTZ","icon":"dashboard","order":3,"disabled":false,"hidden":false}]

Flow Info

Created 3 years, 2 months ago
Rating: not yet rated

Owner

Actions

Rate:

Node Types

Core
  • change (x6)
  • comment (x1)
  • debug (x21)
  • exec (x3)
  • function (x1)
  • inject (x3)
  • switch (x16)
Other

Tags

  • HID
  • Joystick
  • Gampad
  • C-Code
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option