Integrate X-10 devices with SmartThings

This flow supports connecting X-10 devices to SmartThings, it uses mcohad as its backend

I have tried a few different ways of integrating X-10 devices with Smartthings, but every solution required tinkering and making changes outside the ST applications making maintenance difficult. All other solutions work only one way either control devices or respond to remotes, this can do both. In this solution all X-10 settings and device mappings are done locally on SmartThings. Only a couple of one times settings need to be made on the Node Red flow.

See this page for details https://community.smartthings.com/t/release-node-red-bridge-for-x-10-devices-switches-modules-remotes-motion-sensors/116909

Supported Devices and Functionality

  • X-10 Switches and Modules - on/off is supported
  • X-10 Motion Senors and Remote buttions - on/off funtions supported from devices sending housecode/unitcode on/off commands
  • Dimmer functions are fully supported for usign X-10 remotes controlling Smartthings device
  • Controlling X-10 dimming functions from Smarttings is an interesting excerice in randomness. It sort of works, the number don't mean anything its just a slider and up/down arrows. X-10 dimming was designed for physical devices with up/down buttons not deterministic software sending out percentages, its also very variable, same device on different bulbs will exhibit different behaviour.

Please note: Dimmer functions and security devices are not supported

Requires Mochad and CM15A or CM19A USB Devices

Mochad exposes a TCP interface to X-10 commands, and can run on any linux system including Raspberry PI. It uses CM15A or CM19A USB devices to communicate with X-10 over RF or powerline. Using these devices you are not limited to a single house code, you can use any of the 15 house code with any x-10 device. https://bfocht.github.io/mochad/ https://bfocht.github.io/mochad/mochad_reference.html

[
    {
        "id": "4464d37e.3c1aec",
        "type": "tab",
        "label": "Smartthings X10 Connector",
        "disabled": false,
        "info": ""
    },
    {
        "id": "c3587cb2.f21c1",
        "type": "debug",
        "z": "4464d37e.3c1aec",
        "name": "",
        "active": false,
        "console": "false",
        "complete": "payload",
        "x": 630,
        "y": 840,
        "wires": []
    },
    {
        "id": "2b917033.c2468",
        "type": "function",
        "z": "4464d37e.3c1aec",
        "name": "Parse mochad Messages",
        "func": "msg.headers = {};\nmsg.headers['Content-Type'] = 'application/json';\nmsg.headers['X10NodeRed'] = 'DeviceUpdate';\n \nvar lastCommand = global.get(\"LastCommand\");\nlastCommand = lastCommand.toLowerCase().trim();\n//node.warn(lastCommand);\n\nconst inLines = new Buffer(msg.payload, 'hex');  \n//node.warn(inLines.toString());\nvar lastCodeSeen = context.get(\"lastCodeSeen\");\nif(typeof lastCodeSeen == \"undefined\") {\n    lastCodeSeen = {\n        housecode: \"x\",\n        unitcode: \"0\"\n    };\n    context.set(\"lastCodeSeen\", lastCodeSeen);\n}\n\ntry {\n\n    var eventData, m;\n    \n    if (m = /^\\d{2}\\/\\d{2}\\s+(?:\\d{2}:){2}\\d{2}\\s(Tx|Rx)\\s+(RF|PL)\\s+House:\\s+([a-pA-P])\\s+Func:\\s+All\\s+(units|lights)\\s+(on|off)$/m.exec(inLines)) {\n        eventData = {\n            protocol: m[2].toLowerCase().trim(),\n            direction: m[1].toLowerCase().trim(),\n            housecode: m[3].toLowerCase().trim(),\n            unitcode: \"*\" + m[4].trim(),\n            state: m[5].toLowerCase().trim()\n        };\n        \n        msg.payload = JSON.stringify(eventData);\n        msg.headers['eventData'] = msg.payload;\n        return msg; //{payload: JSON.stringify(eventData)};\n    }\n    if (m = /^\\d{2}\\/\\d{2}\\s+(?:\\d{2}:){2}\\d{2}\\s(Tx|Rx)\\s+(RFSEC)\\s+Addr:\\s+([xX:0-9a-fA-F]+)(\\s+)Func:\\s+(.+)$/m.exec(inLines)) {\n        eventData = {\n            protocol: m[2].toLowerCase().trim(),\n            direction: m[1].toLowerCase().trim(),\n            housecode: m[3].toLowerCase().trim(),\n            unitcode: \"*\" + m[4].trim(),\n            state: m[5].toLowerCase().trim()\n        };\n        msg.payload = JSON.stringify(eventData);\n        msg.headers['eventData'] = msg.payload;\n        return msg; //{payload: JSON.stringify(eventData)};\n    }\n    if (m = /^\\d{2}\\/\\d{2}\\s+(?:\\d{2}:){2}\\d{2}\\s(Tx|Rx)\\s+(RF|PL)\\s+HouseUnit:\\s+([a-pA-P])(\\d{1,2})\\s+Func:\\s+(On|Off)/m.exec(inLines)) {\n        eventData = {\n            protocol: m[2].toLowerCase().trim(),\n            direction: m[1].toLowerCase().trim(),\n            housecode: m[3].toLowerCase().trim(),\n            unitcode: parseInt(m[4].trim(), 10),\n            state: m[5].toLowerCase().trim()\n        };\n        msg.payload = JSON.stringify(eventData);\n        msg.headers['eventData'] = msg.payload;\n        return msg; //{payload: JSON.stringify(eventData)};\n    } else if (m = /^\\d{2}\\/\\d{2}\\s+(?:\\d{2}:){2}\\d{2}\\s(?:Rx)\\s+(?:RF|PL)\\s+HouseUnit:\\s+([a-pA-P])(\\d{1,2})/m.exec(inLines)) {\n        lastCodeSeen.housecode = m[1].toLowerCase().trim();\n        lastCodeSeen.unitcode = parseInt(m[2].trim(), 10);\n        context.set(\"lastCodeSeen\", lastCodeSeen);\n    }\n    if (lastCodeSeen.housecode && lastCodeSeen.unitcode && (m = /\\d{2}\\/\\d{2}\\s+(?:\\d{2}:){2}\\d{2}\\s(Rx|Tx)\\s+(RF|PL)\\s+House:\\s+([a-pA-P])\\s+Func:\\s+(On|Off|Dim|Bright)$/m.exec(inLines))) {\n        eventData = {\n            protocol: m[2].toLowerCase().trim(),\n            direction: m[1].toLowerCase().trim(),\n            housecode: m[3].toLowerCase().trim(),\n            unitcode: null,\n            state: m[4].toLowerCase().trim()\n        };\n        if (eventData.housecode === lastCodeSeen.housecode) {\n            eventData.unitcode = lastCodeSeen.unitcode;\n            msg.payload = JSON.stringify(eventData);\n            msg.headers['eventData'] = msg.payload;\n            return msg; //{payload: JSON.stringify(eventData)};\n        }\n    }\n} \ncatch(err) {\n   return null; //ignore the exception, its probably a message we don't understand\n   //console.log(\"exception in parsing: \" + err.message); \n} \n  \nreturn null;\n\n",
        "outputs": 1,
        "noerr": 0,
        "x": 530,
        "y": 200,
        "wires": [
            [
                "e17e31b.225a8d",
                "cf234cdd.853a6"
            ]
        ]
    },
    {
        "id": "5d10e7f6.374118",
        "type": "http in",
        "z": "4464d37e.3c1aec",
        "name": "/register",
        "url": "/register",
        "method": "get",
        "upload": false,
        "swaggerDoc": "",
        "x": 80,
        "y": 751,
        "wires": [
            [
                "4327084f.3cead8",
                "5e1c0897.06a348"
            ]
        ]
    },
    {
        "id": "c6067ee5.9af4c",
        "type": "http response",
        "z": "4464d37e.3c1aec",
        "name": "Send respone to ST",
        "statusCode": "",
        "headers": {},
        "x": 640,
        "y": 760,
        "wires": []
    },
    {
        "id": "881d9c1.084236",
        "type": "http request",
        "z": "4464d37e.3c1aec",
        "name": "http request to ST",
        "method": "POST",
        "ret": "txt",
        "url": "",
        "tls": "",
        "x": 850,
        "y": 320,
        "wires": [
            []
        ]
    },
    {
        "id": "cf234cdd.853a6",
        "type": "debug",
        "z": "4464d37e.3c1aec",
        "name": "",
        "active": false,
        "console": "false",
        "complete": "true",
        "x": 810,
        "y": 200,
        "wires": []
    },
    {
        "id": "4f44c0f8.a6329",
        "type": "file in",
        "z": "4464d37e.3c1aec",
        "name": "Read ST setting after restart",
        "filename": "/home/pi/.red-st-connect.conf",
        "format": "utf8",
        "chunk": false,
        "sendError": false,
        "x": 366.5,
        "y": 80,
        "wires": [
            [
                "688833ed.0bd46c"
            ]
        ]
    },
    {
        "id": "4d352fb.84e77d",
        "type": "inject",
        "z": "4464d37e.3c1aec",
        "name": "Startup",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "repeat": "",
        "crontab": "",
        "once": true,
        "x": 91,
        "y": 81,
        "wires": [
            [
                "4f44c0f8.a6329",
                "ba3b8ce1.2ec8a"
            ]
        ]
    },
    {
        "id": "5e1c0897.06a348",
        "type": "file",
        "z": "4464d37e.3c1aec",
        "name": "Persist ST settings to File",
        "filename": "/home/pi/.red-st-connect.conf",
        "appendNewline": false,
        "createDir": true,
        "overwriteFile": "true",
        "x": 316.5,
        "y": 719,
        "wires": []
    },
    {
        "id": "c08f6eac.3fa3b",
        "type": "comment",
        "z": "4464d37e.3c1aec",
        "name": "setup config file path",
        "info": "If you are not running as pi or /home/pi is\nnot writable change the location of where\nthis flow will read and write ST settings\nsettigns will be stored in /home/pi/.red-st-connect.conf",
        "x": 348,
        "y": 43,
        "wires": []
    },
    {
        "id": "688833ed.0bd46c",
        "type": "function",
        "z": "4464d37e.3c1aec",
        "name": "Set ST Settings in global context",
        "func": "try {\n    var STsettings = JSON.parse(msg.payload);\n    if (typeof obj !== 'string' ) \n        global.set(\"STsettings\", STsettings);\n    global.set(\"LastCommand\", \" \");\n} catch (e) {\n    // ignore errors\n}\n\nmsg.payload = \"Smartthings settings: \" + JSON.stringify (STsettings);\n\nreturn msg\n\n\n\n",
        "outputs": 1,
        "noerr": 0,
        "x": 674,
        "y": 80,
        "wires": [
            []
        ]
    },
    {
        "id": "e17e31b.225a8d",
        "type": "function",
        "z": "4464d37e.3c1aec",
        "name": "Setup http request",
        "func": "\n// if you want to hard code the url to ST uncomment these two lines and\n// delete or comment out the rest of the lines in this node\n// msg.url. = \"http://ip_address_of_Smartthings_hub:39500/\";\n// return msg;\n\nvar STsettings = global.get(\"STsettings\");\nif(typeof STsettings == \"undefined\") {\n    node.error(\"Can't continue. Don't know where Smartthings hub is.\");\n    return null;\n}\nnode.log(\"Please ignore the message properties override warning\");\nmsg.url =\"http://\" + STsettings.ip_for_st + \":\" + STsettings.port_for_st;\nreturn msg;\n\n\n\n\n\n",
        "outputs": 1,
        "noerr": 0,
        "x": 630,
        "y": 260,
        "wires": [
            [
                "cf234cdd.853a6",
                "881d9c1.084236"
            ]
        ]
    },
    {
        "id": "68e49f7f.e8d8d",
        "type": "http in",
        "z": "4464d37e.3c1aec",
        "name": "/push",
        "url": "/push",
        "method": "get",
        "upload": false,
        "swaggerDoc": "",
        "x": 61,
        "y": 500,
        "wires": [
            [
                "94402c6e.c2c01",
                "6fee4319.991d1c"
            ]
        ]
    },
    {
        "id": "4327084f.3cead8",
        "type": "function",
        "z": "4464d37e.3c1aec",
        "name": "Registration response",
        "func": "msg.headers = {};\nmsg.headers['Content-Type'] = 'application/json';\nmsg.headers['X10NodeRed'] = 'Registered';\nmsg.payload =  {\"X10NodeRed\":\"Registered\"};\nreturn msg;\n",
        "outputs": 1,
        "noerr": 0,
        "x": 310,
        "y": 792,
        "wires": [
            [
                "c6067ee5.9af4c",
                "c3587cb2.f21c1"
            ]
        ]
    },
    {
        "id": "c607a82b.95bf28",
        "type": "comment",
        "z": "4464d37e.3c1aec",
        "name": "setup mochad host ip",
        "info": "This is the mochad listener you should \nsetup the mochad host address here\n",
        "x": 320,
        "y": 160,
        "wires": []
    },
    {
        "id": "6fee4319.991d1c",
        "type": "function",
        "z": "4464d37e.3c1aec",
        "name": "parse send message",
        "func": "var jsonText = JSON.stringify(msg.payload); \nvar obj = JSON.parse(jsonText);\n\nif (typeof obj !== 'string' ) {\n    var device = obj.device.replace(/-/g, \" \");\n    msg.payload = device + \" \" + obj.action+\"\\n\";\n    global.set(\"LastCommand\", msg.payload);\n    return msg;\n}",
        "outputs": 1,
        "noerr": 0,
        "x": 260,
        "y": 420,
        "wires": [
            [
                "e63fe7b.f57b318",
                "b90d5c01.36a4b",
                "ba3b8ce1.2ec8a"
            ]
        ]
    },
    {
        "id": "9a1b3cff.df2c6",
        "type": "comment",
        "z": "4464d37e.3c1aec",
        "name": "setup config file path",
        "info": "If you are not running as pi or /home/pi is\nnot writable change the location of where\nthis flow will read and write ST settings\nsettigns will be stored in /home/pi/.red-st-connect.conf",
        "x": 310,
        "y": 676,
        "wires": []
    },
    {
        "id": "e63fe7b.f57b318",
        "type": "function",
        "z": "4464d37e.3c1aec",
        "name": "OK response",
        "func": "msg.headers = {};\nmsg.headers['Content-Type'] = 'application/json';\nmsg.payload =  {\"X10NodeRed\":\"OK\"};\nreturn msg;\n",
        "outputs": 1,
        "noerr": 0,
        "x": 450,
        "y": 600,
        "wires": [
            [
                "c6067ee5.9af4c"
            ]
        ]
    },
    {
        "id": "b90d5c01.36a4b",
        "type": "debug",
        "z": "4464d37e.3c1aec",
        "name": "",
        "active": false,
        "console": "false",
        "complete": "payload",
        "x": 490,
        "y": 420,
        "wires": []
    },
    {
        "id": "94402c6e.c2c01",
        "type": "debug",
        "z": "4464d37e.3c1aec",
        "name": "",
        "active": false,
        "console": "false",
        "complete": "payload",
        "x": 230,
        "y": 600,
        "wires": []
    },
    {
        "id": "a1cc9291.4bc21",
        "type": "function",
        "z": "4464d37e.3c1aec",
        "name": "Rate Limiter",
        "func": "var interval = (500); // minimum interval between messages (ms)\ncontext.lastTime = context.lastTime || 0;\n\nvar now = Date.now();\n\nif (now-context.lastTime > interval) {\n  context.lastTime = now;\n  return msg;\n} else {\n  return null;\n}",
        "outputs": 1,
        "noerr": 0,
        "x": 370,
        "y": 260,
        "wires": [
            [
                "2b917033.c2468"
            ]
        ]
    },
    {
        "id": "ba3b8ce1.2ec8a",
        "type": "tcp request",
        "z": "4464d37e.3c1aec",
        "server": "localhost",
        "port": "1099",
        "out": "sit",
        "splitc": " ",
        "name": "mochad",
        "x": 280,
        "y": 200,
        "wires": [
            [
                "a1cc9291.4bc21"
            ]
        ]
    }
]
enishoca

Flow Info

created 4 months ago
updated 2 months ago

Node Types

Core
  • comment (x3)
  • debug (x4)
  • file (x1)
  • file in (x1)
  • function (x7)
  • http in (x2)
  • http request (x1)
  • http response (x1)
  • inject (x1)
  • tcp request (x1)
Other
  • tab (x1)

Tags

  • smartthings
  • X-10
  • mochad
  • TCP
  • HTTP
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option