daily pastebin goal
82%
SHARE
TWEET

smartbits x10 bridge

a guest Nov 5th, 2015 503 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /**
  2.  *  X10 Bridge.
  3.  *
  4.  *  This SmartApp allows integration of X10 appliance modules, lamp modules,
  5.  *  switches and dimmers with SmartThings. It uses an open-source Mochad
  6.  *  TCP Gateway running on a Linux computer and either CM15A or CM19A X10
  7.  *  interface module to communicate with the X10 devices. Please visit
  8.  *  https://github.com/statusbits/smartthings/blob/master/X10Bridge for more
  9.  *  information.
  10.  *
  11.  *  Copyright (c) 2014 geko@statusbits.com
  12.  *
  13.  *  This program is free software: you can redistribute it and/or modify it
  14.  *  under the terms of the GNU General Public License as published by the Free
  15.  *  Software Foundation, either version 3 of the License, or (at your option)
  16.  *  any later version.
  17.  *
  18.  *  This program is distributed in the hope that it will be useful, but
  19.  *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  20.  *  or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  21.  *  for more details.
  22.  *
  23.  *  You should have received a copy of the GNU General Public License along
  24.  *  with this program.  If not, see <http://www.gnu.org/licenses/>.
  25.  *
  26.  *  The latest version of this file can be found at:
  27.  *  https://github.com/statusbits/smartthings/blob/master/X10Bridge/X10Bridge.app.groovy
  28.  *
  29.  *  Useful links:
  30.  *  - X10 Bridge project page: https://github.com/statusbits/smartthings/blob/master/X10Bridge
  31.  *  - Mochad project page: http://sourceforge.net/projects/mochad/
  32.  *
  33.  *  Revision History
  34.  *  ----------------
  35.  *  2014-09-05  V1.0.0  Released into SmartThings community.
  36.  *  2014-08-18  V0.9.0  Initial check-in.
  37.  */
  38.  
  39. definition(
  40.     name: "X10 Bridge",
  41.     namespace: "statusbits",
  42.     author: "geko@statusbits.com",
  43.     description: "Connect X10 switches and dimmers to SmartThings. Requires Mochad TCP gateway.",
  44.     category: "My Apps",
  45.     iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
  46.     iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
  47.     //oauth: true
  48. )
  49.  
  50. preferences {
  51.     page name:"setupInit"
  52.     page name:"setupMenu"
  53.     page name:"setupMochad"
  54.     page name:"setupAddSwitch"
  55.     page name:"setupActionAdd"
  56.     page name:"setupListDevices"
  57.     page name:"setupTestConnection"
  58.     page name:"setupActionTest"
  59. }
  60.  
  61. private def setupInit() {
  62.     TRACE("setupInit()")
  63.  
  64.     if (state.setup) {
  65.         // already initialized, go to setup menu
  66.         return setupMenu()
  67.     }
  68.  
  69.     // initialize app state and show welcome page
  70.     state.setup = [:]
  71.     state.setup.installed = false
  72.     state.devices = [:]
  73.     return setupWelcome()
  74. }
  75.  
  76. // Show setup welcome page
  77. private def setupWelcome() {
  78.     TRACE("setupWelcome()")
  79.  
  80.     def textPara1 =
  81.         "X10 Bridge allows you integrate X10 switches and dimmers into " +
  82.         "SmartThings. Please note that it requires a Linux box running " +
  83.         "Mochad server installed on the local network and accessible from " +
  84.         "the SmartThings hub.\n\n" +
  85.         "Mochad is a free, open-source X10 gateway software for Linux. " +
  86.         "Please visit [insert link] for X10 Bridge setup instructions."
  87.  
  88.     def textPara2 = "${app.name}. ${textVersion()}\n${textCopyright()}"
  89.  
  90.     def textPara3 =
  91.         "Please read the License Agreement below. By tapping the 'Next' " +
  92.         "button at the top of the screen, you agree and accept the terms " +
  93.         "and conditions of the License Agreement."
  94.  
  95.     def textLicense =
  96.         "This program is free software: you can redistribute it and/or " +
  97.         "modify it under the terms of the GNU General Public License as " +
  98.         "published by the Free Software Foundation, either version 3 of " +
  99.         "the License, or (at your option) any later version.\n\n" +
  100.         "This program is distributed in the hope that it will be useful, " +
  101.         "but WITHOUT ANY WARRANTY; without even the implied warranty of " +
  102.         "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU " +
  103.         "General Public License for more details.\n\n" +
  104.         "You should have received a copy of the GNU General Public License " +
  105.         "along with this program. If not, see <http://www.gnu.org/licenses/>."
  106.  
  107.     def pageProperties = [
  108.         name        : "setupInit",
  109.         title       : "Welcome!",
  110.         nextPage    : "setupMenu",
  111.         install     : false,
  112.         uninstall   : state.setup.installed
  113.     ]
  114.  
  115.     return dynamicPage(pageProperties) {
  116.         section {
  117.             paragraph textPara1
  118.             paragraph textPara2
  119.             paragraph textPara3
  120.         }
  121.         section("License") {
  122.             paragraph textLicense
  123.         }
  124.     }
  125. }
  126.  
  127. // Show "Main Menu" page
  128. private def setupMenu() {
  129.     TRACE("setupMenu()")
  130.  
  131.     // if Mochad is not configured, then do it now
  132.     if (!settings.containsKey('mochadIpAddress')) {
  133.         return setupMochad()
  134.     }
  135.  
  136.     def pageProperties = [
  137.         name        : "setupMenu",
  138.         title       : "Setup Menu",
  139.         nextPage    : null,
  140.         install     : true,
  141.         uninstall   : state.setup.installed
  142.     ]
  143.  
  144.     state.setup.deviceType = null
  145.  
  146.     return dynamicPage(pageProperties) {
  147.         section {
  148.             href "setupMochad", title:"Configure Mochad Gateway", description:"Tap to open"
  149.             href "setupAddSwitch", title:"Add X10 Switch", description:"Tap to open"
  150.             if (state.devices.size() > 0) {
  151.                 href "setupListDevices", title:"List Installed Devices", description:"Tap to open"
  152.             }
  153.         }
  154.         section("Utilities") {
  155.             href "setupTestConnection", title:"Test Mochad Connection", description:"Tap to open"
  156.         }
  157.         section([title:"Options", mobileOnly:true]) {
  158.             label title:"Assign a name", required:false
  159.             //mode title:"Set for specific mode(s)", required:false
  160.         }
  161.         section("About") {
  162.             paragraph "${app.name}. ${textVersion()}\n${textCopyright()}"
  163.         }
  164.     }
  165. }
  166.  
  167. // Show "Configure Mochad" setup page
  168. private def setupMochad() {
  169.     TRACE("setupMochad()")
  170.  
  171.     def textPara1 =
  172.         "X10 Bridge uses Mochad TCP gateway running on a Linux computer to " +
  173.         "communicate with X10 devices. The gateway computer must be " +
  174.         "connected to your local network and assigned a static (or " +
  175.         "reserved) IP address, so it does not change when the computer is " +
  176.         "rebooted.\n\n" +
  177.         "Enter IP address and TCP port of your Mochad gateway, then tap " +
  178.         "Done to continue."
  179.  
  180.     def textPara2 =
  181.         "Mochad works with two types of X10 interfaces - CM15A and CM19A. " +
  182.         "CM15A can transmit X10 commands using both power line (PL) and " +
  183.         "radio frequency (RF) protocols, while CM19A can only transmit X10 " +
  184.         "commands using RF protocol and requires either TM751 or RR501 X10 " +
  185.         "transceiver."
  186.  
  187.     def inputIpAddress = [
  188.         name        : "mochadIpAddress",
  189.         type        : "string",
  190.         title       : "What is your gateway IP Address?"
  191.     ]
  192.  
  193.     def inputTcpPort = [
  194.         name        : "mochadTcpPort",
  195.         type        : "number",
  196.         title       : "What is your gateway TCP Port?",
  197.         defaultValue: "1099"
  198.     ]
  199.  
  200.     def inputProtocol = [
  201.         name        : "mochadProtocol",
  202.         type        : "enum",
  203.         title       : "What X10 protocol do you use?",
  204.         metadata    : [values:["PL", "RF"]],
  205.         defaultValue: "PL"
  206.     ]
  207.  
  208.     def pageProperties = [
  209.         name        : "setupMochad",
  210.         title       : "Configure Mochad Gateway",
  211.         nextPage    : "setupMenu",
  212.         install     : false,
  213.         uninstall   : state.installed
  214.     ]
  215.  
  216.     return dynamicPage(pageProperties) {
  217.         section {
  218.             paragraph textPara1
  219.             input inputIpAddress
  220.             input inputTcpPort
  221.         }
  222.         section("Choose X10 Protocol") {
  223.             paragraph textPara2
  224.             input inputProtocol
  225.         }
  226.     }
  227. }
  228.  
  229. // Show "Mochad Connection Test" setup page
  230. private def setupTestConnection() {
  231.     TRACE("setupTestConnection()")
  232.  
  233.     def textHelp =
  234.         "You can execute any Mochad command to verify that your hub can " +
  235.         "communicate with the gateway. Tap Next to continue."
  236.  
  237.     def inputCommand = [
  238.         name        : "mochadCommand",
  239.         type        : "text",
  240.         title       : "Enter Mochad command",
  241.         autoCorrect : false
  242.     ]
  243.  
  244.     def pageProperties = [
  245.         name        : "setupTestConnection",
  246.         title       : "Test Mochad Connection",
  247.         nextPage    : "setupActionTest",
  248.         install     : false,
  249.         uninstall   : state.installed
  250.     ]
  251.  
  252.     return dynamicPage(pageProperties) {
  253.         section {
  254.             paragraph textHelp
  255.             input inputCommand
  256.         }
  257.     }
  258. }
  259.  
  260. // Execute Mochad connection test
  261. private def setupActionTest() {
  262.     TRACE("setupActionTest()")
  263.  
  264.     def pageProperties = [
  265.         name        : "setupActionTest",
  266.         title       : "Mochad Connection Test",
  267.         nextPage    : "setupMenu",
  268.         install     : false,
  269.         uninstall   : state.installed
  270.     ]
  271.  
  272.     if (settings.mochadCommand) {
  273.         def networkId = makeNetworkId(settings.mochadIpAddress, settings.mochadTcpPort)
  274.         socketSend("${settings.mochadCommand}\r\n", networkId)
  275.     }
  276.  
  277.     return dynamicPage(pageProperties) {
  278.         section {
  279.             paragraph "Executing Mochad command:\n  ${settings.mochadCommand}"
  280.             paragraph "Tap Done to continue."
  281.         }
  282.     }
  283. }
  284.  
  285. // Show "Add X10 Switch" setup page
  286. private def setupAddSwitch() {
  287.     TRACE("setupAddSwitch()")
  288.  
  289.     def textHelpName =
  290.         "Give your X10 switch a descriptive name, for example 'Kitchen " +
  291.         "Lights'."
  292.  
  293.     def textHelpAddr =
  294.         "Each X10 device is assigned an address, consisting of two parts - " +
  295.         "a House Code (letters A through P) and a Unit Code (numbers 1 " +
  296.         "through 16), for example 'D12'. Please check your device and " +
  297.         "enter its X10 device address below."
  298.  
  299.     def inputDeviceName = [
  300.         name        : "setupDevName",
  301.         type        : "string",
  302.         title       : "What is your switch name?",
  303.         required    : true,
  304.         defaultValue:"X10 Switch"
  305.     ]
  306.  
  307.     def inputHouseCode = [
  308.         name        : "setupHouseCode",
  309.         type        : "enum",
  310.         title       : "What is your switch House Code?",
  311.         metadata    : [values:x10HouseCodes()],
  312.         required    : true
  313.     ]
  314.  
  315.     def inputUnitCode = [
  316.         name        : "setupUnitCode",
  317.         type        : "enum",
  318.         title       : "What is your switch Unit Code?",
  319.         metadata    : [values:x10UnitCodes()],
  320.         required    : true
  321.     ]
  322.  
  323.     def pageProperties = [
  324.         name        : "setupAddSwitch",
  325.         title       : "Add X10 Switch",
  326.         nextPage    : "setupActionAdd",
  327.         install     : false,
  328.         uninstall   : state.setup.installed
  329.     ]
  330.  
  331.     // Set new device type
  332.     state.setup.deviceType = "switch"
  333.  
  334.     return dynamicPage(pageProperties) {
  335.         section {
  336.             paragraph textHelpName
  337.             input inputDeviceName
  338.         }
  339.         section("X10 Address") {
  340.             paragraph textHelpAddr
  341.             input inputHouseCode
  342.             input inputUnitCode
  343.         }
  344.     }
  345. }
  346.  
  347. private def setupActionAdd() {
  348.     TRACE("setupActionAdd()")
  349.  
  350.     String devAddr = settings.setupHouseCode + settings.setupUnitCode
  351.     if (state.devices.containsKey(devAddr)) {
  352.         log.error "X10 address ${devAddr} is in use"
  353.     } else {
  354.         switch (state.setup.deviceType) {
  355.         case "switch":
  356.             addSwitch(devAddr)
  357.             break;
  358.         }
  359.     }
  360.  
  361.     return setupMenu()
  362. }
  363.  
  364. private def setupListDevices() {
  365.     TRACE("setupListDevices()")
  366.  
  367.     def textNoDevices =
  368.         "You have not configured any X10 devices yet. Tap Done to continue."
  369.  
  370.     def pageProperties = [
  371.         name        : "setupListDevices",
  372.         title       : "Installed Devices",
  373.         nextPage    : "setupMenu",
  374.         install     : false,
  375.         uninstall   : state.setup.installed
  376.     ]
  377.  
  378.     if (state.devices.size() == 0) {
  379.         return dynamicPage(pageProperties) {
  380.             section {
  381.                 paragraph textNoDevices
  382.             }
  383.         }
  384.     }
  385.  
  386.     def switches = getDeviceListAsText('switch')
  387.     return dynamicPage(pageProperties) {
  388.         section {
  389.             paragraph "Tap Done to continue."
  390.         }
  391.         section("Switches") {
  392.             paragraph switches
  393.         }
  394.     }
  395. }
  396.  
  397. def installed() {
  398.     TRACE("installed()")
  399.  
  400.     initialize()
  401. }
  402.  
  403. def updated() {
  404.     TRACE("updated()")
  405.  
  406.     unsubscribe()
  407.     initialize()
  408. }
  409.  
  410. def uninstalled() {
  411.     TRACE("uninstalled()")
  412.  
  413.     // delete all child devices
  414.     def devices = getChildDevices()
  415.     devices?.each {
  416.         try {
  417.             deleteChildDevice(it.deviceNetworkId)
  418.         } catch (e) {
  419.             log.error "Cannot delete device ${it.deviceNetworkId}. Error: ${e}"
  420.         }
  421.     }
  422. }
  423.  
  424. // Handle Location events
  425. def onLocation(evt) {
  426.     TRACE("onLocation(${evt})")
  427.  
  428.     if (evt.eventSource == 'HUB') {
  429.         if (evt.description == 'ping') {
  430.             // ignore ping event
  431.             return
  432.         }
  433.  
  434.         // Parse Hub event
  435.         def hubEvent = stringToMap(evt.description)
  436.         log.debug "hubEvent: ${hubEvent}"
  437.     }
  438. }
  439.  
  440. // Handle SmartApp touch event.
  441. def onAppTouch(evt) {
  442.     TRACE("onAppTouch(${evt})")
  443.     STATE()
  444. }
  445.  
  446. // Excecute X10 'on' command on behalf of child device
  447. def x10_on(nid) {
  448.     TRACE("x10_on(${nid})")
  449.  
  450.     def s = nid?.tokenize(':')
  451.     if (s.size < 2 || s[0].toUpperCase() != 'X10') {
  452.         log.debug "Invalid device network ID ${nid}"
  453.         return
  454.     }
  455.  
  456.     socketSend("${settings.mochadProtocol} ${s[1]} on\r\n", state.networkId)
  457. }
  458.  
  459. // Excecute X10 'off' command on behalf of child device
  460. def x10_off(nid) {
  461.         TRACE("x10_off(${nid})")
  462.  
  463.     def s = nid?.tokenize(':')
  464.     if (s.size < 2 || s[0].toUpperCase() != 'X10') {
  465.         log.debug "Invalid device network ID ${nid}"
  466.         return
  467.     }
  468.  
  469.     socketSend("${settings.mochadProtocol} ${s[1]} off\r\n", state.networkId)
  470. }
  471.  
  472. // Excecute X10 'dim' command on behalf of child device
  473. def x10_dim(nid) {
  474.         TRACE("x10_dim(${nid})")
  475.  
  476.     def s = nid?.tokenize(':')
  477.     if (s.size < 2 || s[0].toUpperCase() != 'X10') {
  478.         log.debug "Invalid device network ID ${nid}"
  479.         return
  480.     }
  481.  
  482.     socketSend("${settings.mochadProtocol} ${s[1]} dim\r\n", state.networkId)
  483. }
  484.  
  485. // Excecute X10 'bright' command on behalf of child device
  486. def x10_bright(nid) {
  487.         TRACE("x10_bright(${nid})")
  488.  
  489.     def s = nid?.tokenize(':')
  490.     if (s.size < 2 || s[0].toUpperCase() != 'X10') {
  491.         log.debug "Invalid device network ID ${nid}"
  492.         return
  493.     }
  494.  
  495.     socketSend("${settings.mochadProtocol} ${s[1]} bright\r\n", state.networkId)
  496. }
  497.  
  498. private def initialize() {
  499.     log.trace "${app.name}. ${textVersion()}. ${textCopyright()}"
  500.     STATE()
  501.  
  502.     state.setup.installed = true
  503.     state.networkId = makeNetworkId(settings.mochadIpAddress, settings.mochadTcpPort)
  504.     updateDeviceList()
  505.  
  506.     // Subscribe to location events with filter disabled
  507.     subscribe(location, null, onLocation, [filterEvents:false])
  508.  
  509.     // for debugging
  510.     //subscribe(app, onAppTouch)
  511. }
  512.  
  513. private def addSwitch(addr) {
  514.     TRACE("addSwitch(${addr})")
  515.  
  516.     def dni = "X10:${addr}".toUpperCase()
  517.     if (getChildDevice(dni)) {
  518.         log.error "Child device ${dni} already exist"
  519.         return false
  520.     }
  521.  
  522.     def devFile = "X10 Switch"
  523.     def devParams = [
  524.         name            : settings.setupDevName,
  525.         label           : settings.setupDevName,
  526.         completedSetup  : true
  527.     ]
  528.  
  529.     log.trace "Creating child device ${devParams}"
  530.     try {
  531.         def dev = addChildDevice("statusbits", devFile, dni, null, devParams)
  532.         dev.refresh()
  533.     } catch (e) {
  534.         log.error "Cannot create child device. Error: ${e}"
  535.         return false
  536.     }
  537.  
  538.     // save device in the app state
  539.     state.devices[addr] = [
  540.         'dni'   : dni,
  541.         'type'  : 'switch',
  542.     ]
  543.  
  544.     STATE()
  545.     return true
  546. }
  547.  
  548. // Purge devices that were removed manually
  549. private def updateDeviceList() {
  550.     TRACE("updateDeviceList()")
  551.  
  552.     state.devices.each { k,v ->
  553.         if (!getChildDevice(v.dni)) {
  554.             log.trace "Removing deleted device ${v.dni}"
  555.             state.devices.remove(k)
  556.         }
  557.     }
  558.  
  559.     // refresh all devices
  560.     def devices = getChildDevices()
  561.     devices?.each {
  562.         it.refresh()
  563.     }
  564. }
  565.  
  566. private def getDeviceMap() {
  567.     def devices = [:]
  568.     state.devices.each { k,v ->
  569.         if (!devices.containsKey(v.type)) {
  570.             devices[v.type] = []
  571.         }
  572.         devices[v.type] << k
  573.     }
  574.  
  575.     return devices
  576. }
  577.  
  578. private def getDeviceListAsText(type) {
  579.     String s = ""
  580.     state.devices.each { k,v ->
  581.         if (v.type == type) {
  582.             def dev = getChildDevice(v.dni)
  583.             if (dev) {
  584.                 s += "${k} - ${dev.displayName}\n"
  585.             }
  586.         }
  587.     }
  588.  
  589.     return s
  590. }
  591.  
  592. private def socketSend(message, networkId) {
  593.     TRACE("socketSend(${message}, ${networkId})")
  594.  
  595.     def hubAction = new physicalgraph.device.HubAction(message,
  596.             physicalgraph.device.Protocol.LAN, networkId)
  597.  
  598.     TRACE("hubAction:\n${hubAction.getProperties()}")
  599.     sendHubCommand(hubAction)
  600. }
  601.  
  602. // Returns device Network ID in 'AAAAAAAA:PPPP' format
  603. private String makeNetworkId(ipaddr, port) {
  604.     TRACE("createNetworkId(${ipaddr}, ${port})")
  605.  
  606.     String hexIp = ipaddr.tokenize('.').collect {
  607.         String.format('%02X', it.toInteger())
  608.     }.join()
  609.  
  610.     String hexPort = String.format('%04X', port)
  611.     return "${hexIp}:${hexPort}"
  612. }
  613.  
  614. private def x10HouseCodes() {
  615.     return ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P"]
  616. }
  617.  
  618. private def x10UnitCodes() {
  619.     return ["1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16"]
  620. }
  621.  
  622. private def textVersion() {
  623.     return "Version 1.0.0"
  624. }
  625.  
  626. private def textCopyright() {
  627.     return "Copyright (c) 2014 Statusbits.com"
  628. }
  629.  
  630. private def TRACE(message) {
  631.     //log.debug message
  632. }
  633.  
  634. private def STATE() {
  635.     //log.debug "state: ${state}"
  636.     //log.debug "settings: ${settings}"
  637. }
RAW Paste Data
Top