Is there a Joystick Node?

Here is a demo in node-red...

[{"id":"1d2a0be.ace77f4","type":"ui_template","z":"64ce1d00.d049e4","group":"dce9e7a2.d20c78","name":"joy.js","order":4,"width":0,"height":0,"format":"<script id=\"joy\">\n    console.log(\"joystick code\")\n /*\n * Name          : joy.js\n * @author       : Roberto D'Amico (Bobboteck)\n * Last modified : 09.06.2020\n * Revision      : 1.1.6\n *\n * Modification History:\n * Date         Version     Modified By\t\tDescription\n * 2020-06-09\t1.1.6\t\tRoberto D'Amico\tFixed Issue #10 and #11\n * 2020-04-20\t1.1.5\t\tRoberto D'Amico\tCorrect: Two sticks in a row, thanks to @liamw9534 for the suggestion\n * 2020-04-03               Roberto D'Amico Correct: InternalRadius when change the size of canvas, thanks to @vanslipon for the suggestion\n * 2020-01-07\t1.1.4\t\tRoberto D'Amico Close #6 by implementing a new parameter to set the functionality of auto-return to 0 position\n * 2019-11-18\t1.1.3\t\tRoberto D'Amico\tClose #5 correct indication of East direction\n * 2019-11-12   1.1.2       Roberto D'Amico Removed Fix #4 incorrectly introduced and restored operation with touch devices\n * 2019-11-12   1.1.1       Roberto D'Amico Fixed Issue #4 - Now JoyStick work in any position in the page, not only at 0,0\n * \n * The MIT License (MIT)\n *\n *  This file is part of the JoyStick Project (https://github.com/bobboteck/JoyStick).\n *\tCopyright (c) 2015 Roberto D'Amico (Bobboteck).\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n * \n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * @desc Principal object that draw a joystick, you only need to initialize the object and suggest the HTML container\n * @costructor\n * @param container {String} - HTML object that contains the Joystick\n * @param parameters (optional) - object with following keys:\n *\ttitle {String} (optional) - The ID of canvas (Default value is 'joystick')\n * \twidth {Int} (optional) - The width of canvas, if not specified is setted at width of container object (Default value is the width of container object)\n * \theight {Int} (optional) - The height of canvas, if not specified is setted at height of container object (Default value is the height of container object)\n * \tinternalFillColor {String} (optional) - Internal color of Stick (Default value is '#00AA00')\n * \tinternalLineWidth {Int} (optional) - Border width of Stick (Default value is 2)\n * \tinternalStrokeColor {String}(optional) - Border color of Stick (Default value is '#003300')\n * \texternalLineWidth {Int} (optional) - External reference circonference width (Default value is 2)\n * \texternalStrokeColor {String} (optional) - External reference circonference color (Default value is '#008000')\n * \tautoReturnToCenter {Bool} (optional) - Sets the behavior of the stick, whether or not, it should return to zero position when released (Default value is True and return to zero)\n */\nvar JoyStick = (function (container, parameters) {\n    parameters = parameters || {};\n    var title = (typeof parameters.title === \"undefined\" ? \"joystick\" : parameters.title),\n        width = (typeof parameters.width === \"undefined\" ? 0 : parameters.width),\n        height = (typeof parameters.height === \"undefined\" ? 0 : parameters.height),\n        internalFillColor = (typeof parameters.internalFillColor === \"undefined\" ? \"#00AA00\" : parameters.internalFillColor),\n        internalLineWidth = (typeof parameters.internalLineWidth === \"undefined\" ? 2 : parameters.internalLineWidth),\n        internalStrokeColor = (typeof parameters.internalStrokeColor === \"undefined\" ? \"#003300\" : parameters.internalStrokeColor),\n        externalLineWidth = (typeof parameters.externalLineWidth === \"undefined\" ? 2 : parameters.externalLineWidth),\n        externalStrokeColor = (typeof parameters.externalStrokeColor === \"undefined\" ? \"#008000\" : parameters.externalStrokeColor),\n        autoReturnToCenter = (typeof parameters.autoReturnToCenter === \"undefined\" ? true : parameters.autoReturnToCenter);\n\n    // Create Canvas element and add it in the Container object\n    var objContainer = document.getElementById(container);\n    var canvas = document.createElement(\"canvas\");\n    canvas.id = title;\n    if (width === 0) { width = objContainer.clientWidth; }\n    if (height === 0) { height = objContainer.clientHeight; }\n    canvas.width = width;\n    canvas.height = height;\n    objContainer.appendChild(canvas);\n    var context = canvas.getContext(\"2d\");\n\n    var pressed = 0; // Bool - 1=Yes - 0=No\n    var circumference = 2 * Math.PI;\n    var internalRadius = (canvas.width - ((canvas.width / 2) + 10)) / 2;\n    var maxMoveStick = internalRadius + 5;\n    var externalRadius = internalRadius + 30;\n    var centerX = canvas.width / 2;\n    var centerY = canvas.height / 2;\n    var directionHorizontalLimitPos = canvas.width / 10;\n    var directionHorizontalLimitNeg = directionHorizontalLimitPos * -1;\n    var directionVerticalLimitPos = canvas.height / 10;\n    var directionVerticalLimitNeg = directionVerticalLimitPos * -1;\n    // Used to save current position of stick\n    var movedX = centerX;\n    var movedY = centerY;\n\n    // Check if the device support the touch or not\n    if (\"ontouchstart\" in document.documentElement) {\n        canvas.addEventListener(\"touchstart\", onTouchStart, false);\n        canvas.addEventListener(\"touchmove\", onTouchMove, false);\n        canvas.addEventListener(\"touchend\", onTouchEnd, false);\n    }\n    else {\n        canvas.addEventListener(\"mousedown\", onMouseDown, false);\n        canvas.addEventListener(\"mousemove\", onMouseMove, false);\n        canvas.addEventListener(\"mouseup\", onMouseUp, false);\n    }\n    // Draw the object\n    drawExternal();\n    drawInternal();\n\n    /******************************************************\n     * Private methods\n     *****************************************************/\n\n    /**\n     * @desc Draw the external circle used as reference position\n     */\n    function drawExternal() {\n        context.beginPath();\n        context.arc(centerX, centerY, externalRadius, 0, circumference, false);\n        context.lineWidth = externalLineWidth;\n        context.strokeStyle = externalStrokeColor;\n        context.stroke();\n    }\n\n    /**\n     * @desc Draw the internal stick in the current position the user have moved it\n     */\n    function drawInternal() {\n        context.beginPath();\n        if (movedX < internalRadius) { movedX = maxMoveStick; }\n        if ((movedX + internalRadius) > canvas.width) { movedX = canvas.width - (maxMoveStick); }\n        if (movedY < internalRadius) { movedY = maxMoveStick; }\n        if ((movedY + internalRadius) > canvas.height) { movedY = canvas.height - (maxMoveStick); }\n        context.arc(movedX, movedY, internalRadius, 0, circumference, false);\n        // create radial gradient\n        var grd = context.createRadialGradient(centerX, centerY, 5, centerX, centerY, 200);\n        // Light color\n        grd.addColorStop(0, internalFillColor);\n        // Dark color\n        grd.addColorStop(1, internalStrokeColor);\n        context.fillStyle = grd;\n        context.fill();\n        context.lineWidth = internalLineWidth;\n        context.strokeStyle = internalStrokeColor;\n        context.stroke();\n    }\n\n    /**\n     * @desc Events for manage touch\n     */\n    function onTouchStart(event) {\n        pressed = 1;\n    }\n\n    // function onTouchMove(event) {\n    //     // Prevent the browser from doing its default thing (scroll, zoom)\n    //     event.preventDefault();\n    //     if (pressed === 1 && event.targetTouches[0].target === canvas) {\n    //         movedX = event.targetTouches[0].pageX;\n    //         movedY = event.targetTouches[0].pageY;\n    //         // Manage offset\n    //         if (canvas.offsetParent.tagName.toUpperCase() === \"BODY\") {\n    //             movedX -= canvas.offsetLeft;\n    //             movedY -= canvas.offsetTop;\n    //         }\n    //         else {\n    //             movedX -= canvas.offsetParent.offsetLeft;\n    //             movedY -= canvas.offsetParent.offsetTop;\n    //         }\n    //         // Delete canvas\n    //         context.clearRect(0, 0, canvas.width, canvas.height);\n    //         // Redraw object\n    //         drawExternal();\n    //         drawInternal();\n    //     }\n    // }\n  function onTouchMove(event)\n  {\n    // Prevent the browser from doing its default thing (scroll, zoom)\n    event.preventDefault();\n    if(pressed === 1 && event.targetTouches[0].target === canvas)\n    {\n      movedX = event.targetTouches[0].offsetX;\n      movedY = event.targetTouches[0].offsetY;\n      // Delete canvas\n      context.clearRect(0, 0, canvas.width, canvas.height);\n      // Redraw object\n      drawExternal();\n      drawInternal();\n    }\n  } \n    function onTouchEnd(event) {\n        pressed = 0;\n        // If required reset position store variable\n        if (autoReturnToCenter) {\n            movedX = centerX;\n            movedY = centerY;\n        }\n        // Delete canvas\n        context.clearRect(0, 0, canvas.width, canvas.height);\n        // Redraw object\n        drawExternal();\n        drawInternal();\n        //canvas.unbind('touchmove');\n    }\n\n    /**\n     * @desc Events for manage mouse\n     */\n    function onMouseDown(event) {\n        pressed = 1;\n    }\n\n    // function onMouseMove(event) {\n    //     if (pressed === 1) {\n    //         movedX = event.pageX;\n    //         movedY = event.pageY;\n    //         // Manage offset\n    //         if (canvas.offsetParent.tagName.toUpperCase() === \"BODY\") {\n    //             movedX -= canvas.offsetLeft;\n    //             movedY -= canvas.offsetTop;\n    //         }\n    //         else {\n    //             movedX -= canvas.offsetParent.offsetLeft;\n    //             movedY -= canvas.offsetParent.offsetTop;\n    //         }\n    //         // Delete canvas\n    //         context.clearRect(0, 0, canvas.width, canvas.height);\n    //         // Redraw object\n    //         drawExternal();\n    //         drawInternal();\n    //     }\n    // }\n  function onMouseMove(event) \n  {\n    if(pressed === 1)\n    {\n      movedX = event.offsetX;\n      movedY = event.offsetY;\n      // Delete canvas\n      context.clearRect(0, 0, canvas.width, canvas.height);\n      // Redraw object\n      drawExternal();\n      drawInternal();\n    }\n  }\n\n    function onMouseUp(event) {\n        pressed = 0;\n        // If required reset position store variable\n        if (autoReturnToCenter) {\n            movedX = centerX;\n            movedY = centerY;\n        }\n        // Delete canvas\n        context.clearRect(0, 0, canvas.width, canvas.height);\n        // Redraw object\n        drawExternal();\n        drawInternal();\n        //canvas.unbind('mousemove');\n    }\n\n    /******************************************************\n     * Public methods\n     *****************************************************/\n\n    /**\n     * @desc The width of canvas\n     * @return Number of pixel width \n     */\n    this.GetWidth = function () {\n        return canvas.width;\n    };\n\n    /**\n     * @desc The height of canvas\n     * @return Number of pixel height\n     */\n    this.GetHeight = function () {\n        return canvas.height;\n    };\n\n    /**\n     * @desc The X position of the cursor relative to the canvas that contains it and to its dimensions\n     * @return Number that indicate relative position\n     */\n    this.GetPosX = function () {\n        return movedX;\n    };\n\n    /**\n     * @desc The Y position of the cursor relative to the canvas that contains it and to its dimensions\n     * @return Number that indicate relative position\n     */\n    this.GetPosY = function () {\n        return movedY;\n    };\n\n    /**\n     * @desc Normalizzed value of X move of stick\n     * @return Integer from -100 to +100\n     */\n    this.GetX = function () {\n        return (100 * ((movedX - centerX) / maxMoveStick)).toFixed();\n    };\n\n    /**\n     * @desc Normalizzed value of Y move of stick\n     * @return Integer from -100 to +100\n     */\n    this.GetY = function () {\n        return ((100 * ((movedY - centerY) / maxMoveStick)) * -1).toFixed();\n    };\n\n    /**\n     * @desc Get the direction of the cursor as a string that indicates the cardinal points where this is oriented\n     * @return String of cardinal point N, NE, E, SE, S, SW, W, NW and C when it is placed in the center\n     */\n    this.GetDir = function () {\n        var result = \"\";\n        var orizontal = movedX - centerX;\n        var vertical = movedY - centerY;\n\n        if (vertical >= directionVerticalLimitNeg && vertical <= directionVerticalLimitPos) {\n            result = \"C\";\n        }\n        if (vertical < directionVerticalLimitNeg) {\n            result = \"N\";\n        }\n        if (vertical > directionVerticalLimitPos) {\n            result = \"S\";\n        }\n\n        if (orizontal < directionHorizontalLimitNeg) {\n            if (result === \"C\") {\n                result = \"W\";\n            }\n            else {\n                result += \"W\";\n            }\n        }\n        if (orizontal > directionHorizontalLimitPos) {\n            if (result === \"C\") {\n                result = \"E\";\n            }\n            else {\n                result += \"E\";\n            }\n        }\n\n        return result;\n    };\n});\n</script>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"global","x":1730,"y":100,"wires":[[]]},{"id":"f4c1920a.492c5","type":"ui_template","z":"64ce1d00.d049e4","group":"dea21ffd.1483","name":"joystick 1","order":4,"width":"6","height":"12","format":"<div id=\"joy1\" style=\"width:200px;height:200px;margin:50px\"></div>\nPosizione X:<input id=\"joy1PosizioneX\" type=\"text\" /><br />\nPosizione Y:<input id=\"joy1PosizioneY\" type=\"text\" /><br />\nDirezione:<input id=\"joy1Direzione\" type=\"text\" /><br />\nX :<input id=\"joy1X\" type=\"text\" /></br>\nY :<input id=\"joy1Y\" type=\"text\" />\n\n<script>\n\n(function(scope) {\n    console.log(\"joy1 demo scope\");\n    var Joy1PosMemory = {};\n    var Joy1 = new JoyStick('joy1');\n    setInterval(function(){ \n        var Joy1Current = {};\n        Joy1Current.inX = Joy1.GetPosX(); \n        Joy1Current.inY = Joy1.GetPosY(); \n        Joy1Current.dir =Joy1.GetDir();\n        Joy1Current.x =Joy1.GetX();\n        Joy1Current.y =Joy1.GetY();\n        if (\n            Joy1Current.inX != Joy1PosMemory.inX || \n            Joy1Current.inY != Joy1PosMemory.inY || \n            Joy1Current.dir != Joy1PosMemory.dir || \n            Joy1Current.x != Joy1PosMemory.x || \n            Joy1Current.y != Joy1PosMemory.y  \n        ) {\n            ///value has changed - send msg to node-red\n            scope.send ({topic: \"joy1\", payload: Joy1Current} );\n        }\n        Joy1PosMemory = {...Joy1Current};//copy current to memory var\n    }, 50);\n\n    //************************ these can be removed *****************\n    var joy1IinputPosX = document.getElementById(\"joy1PosizioneX\");\n    var joy1InputPosY = document.getElementById(\"joy1PosizioneY\");\n    var joy1Direzione = document.getElementById(\"joy1Direzione\");\n    var joy1X = document.getElementById(\"joy1X\");\n    var joy1Y = document.getElementById(\"joy1Y\");\n\n    setInterval(function(){ joy1IinputPosX.value=Joy1.GetPosX(); }, 50);\n    setInterval(function(){ joy1InputPosY.value=Joy1.GetPosY(); }, 50);\n    setInterval(function(){ joy1Direzione.value=Joy1.GetDir(); }, 50);\n    setInterval(function(){ joy1X.value=Joy1.GetX(); }, 50);\n    setInterval(function(){ joy1Y.value=Joy1.GetY(); }, 50);\n    //***************************************************************\n})(scope);\n\n\n\n</script>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":1740,"y":140,"wires":[["c2dc33fb.97d72"]]},{"id":"c2dc33fb.97d72","type":"debug","z":"64ce1d00.d049e4","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1770,"y":200,"wires":[]},{"id":"dce9e7a2.d20c78","type":"ui_group","name":"Object detection","tab":"5132060d.4cde48","order":1,"disp":true,"width":"7","collapse":false},{"id":"dea21ffd.1483","type":"ui_group","name":"Pump Status","tab":"dd590918.d334c8","order":1,"disp":true,"width":"6","collapse":false},{"id":"5132060d.4cde48","type":"ui_tab","name":"Home","icon":"dashboard","disabled":false,"hidden":false},{"id":"dd590918.d334c8","type":"ui_tab","name":"Pool Control","icon":"dashboard","disabled":false,"hidden":false}]
3 Likes