Advertisement
Guest User

smartbits x10 bridge

a guest
Nov 5th, 2015
718
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.31 KB | None | 0 0
  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. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement