Advertisement
Guest User

Untitled

a guest
Feb 24th, 2020
92
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.95 KB | None | 0 0
  1. """Support for Redmond Kettler G200S"""
  2.  
  3. #!/usr/local/bin/python3
  4. # coding: utf-8
  5.  
  6. import pexpect
  7. from time import sleep
  8. import time
  9. import colorsys
  10. from datetime import datetime
  11. from textwrap import wrap
  12. import re
  13. from datetime import timedelta
  14. import voluptuous as vol
  15. import logging
  16.  
  17. from homeassistant.core import callback
  18. from homeassistant.const import (CONF_DEVICE, CONF_MAC, CONF_PASSWORD, CONF_SCAN_INTERVAL,)
  19. from homeassistant.helpers.discovery import async_load_platform
  20. from homeassistant.helpers.event import async_track_time_interval
  21. import homeassistant.helpers.config_validation as cv
  22. from homeassistant.helpers.typing import ConfigType, HomeAssistantType
  23. from homeassistant.helpers.dispatcher import async_dispatcher_send
  24. import homeassistant.util.color as color_util
  25.  
  26. CONF_MIN_TEMP = 40
  27. CONF_MAX_TEMP = 100
  28. CONF_TARGET_TEMP = 100
  29.  
  30. SCAN_INTERVAL = timedelta(seconds=60)
  31.  
  32. REQUIREMENTS = ['pexpect']
  33.  
  34. _LOGGER = logging.getLogger(__name__)
  35.  
  36. SUPPORTED_DOMAINS = ["water_heater", "sensor", "switch"]
  37.  
  38. DOMAIN = "r4s_kettler"
  39.  
  40. CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({vol.Required(CONF_DEVICE): cv.string, vol.Required(CONF_MAC): cv.string, vol.Required(CONF_PASSWORD): cv.string, vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): cv.time_period,})}, extra=vol.ALLOW_EXTRA,)
  41.  
  42.  
  43.  
  44. async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
  45.  
  46. hass.data[DOMAIN] = {}
  47.  
  48. kwargs = dict(config[DOMAIN])
  49. dev = kwargs.get(CONF_DEVICE)
  50. mac = kwargs.get(CONF_MAC)
  51. key = kwargs.get(CONF_PASSWORD)
  52. scan_delta = kwargs.get(CONF_SCAN_INTERVAL)
  53.  
  54. if len(key) != 16:
  55. _LOGGER.error("key value is empty or wrong")
  56. return False
  57.  
  58. mac_validation = bool(re.match('^' + '[\:\-]'.join(['([0-9a-f]{2})']*6) + '$', mac.lower()))
  59. if not mac_validation:
  60. _LOGGER.error("mac value is empty or wrong")
  61. return False
  62.  
  63. kettler = hass.data[DOMAIN]["kettler"] = RedmondKettler(hass, mac, key, dev)
  64. try:
  65. await kettler.firstConnect()
  66. except:
  67. _LOGGER.warning("Connect to Kettler %s through device %s failed", mac, dev)
  68.  
  69. async_track_time_interval(hass, kettler.async_update, scan_delta)
  70.  
  71. for platform in SUPPORTED_DOMAINS:
  72. hass.async_create_task(async_load_platform(hass, platform, DOMAIN, {}, config))
  73.  
  74. return True
  75.  
  76.  
  77.  
  78. class RedmondKettler:
  79.  
  80. def __init__(self, hass, addr, key, device):
  81. self.hass = hass
  82. self._mac = addr
  83. self._key = key
  84. self._device = device
  85. self._mntemp = CONF_MIN_TEMP
  86. self._mxtemp = CONF_MAX_TEMP
  87. self._tgtemp = CONF_TARGET_TEMP
  88. self._temp = 0
  89. self._time_upd = '00:00'
  90. self._mode = '00' # '00' - boil, '01' - heat to temp, '03' - backlight
  91. self._status = '00' #may be '00' - OFF or '02' - ON
  92. self._iter = 0
  93. self._connected = False
  94. self._is_busy = False
  95. self.child = None
  96.  
  97.  
  98.  
  99. def theKettlerIsOn(self):
  100. if self._status == '02':
  101. if self._mode == '00' or self._mode == '01':
  102. return True
  103. return False
  104.  
  105. def iterase(self): # counter
  106. self._iter+=1
  107. if self._iter >= 100: self._iter = 0
  108.  
  109. def hexToDec(self, chr):
  110. return int(str(chr), 16)
  111.  
  112. def decToHex(self, num):
  113. char = str(hex(int(num))[2:])
  114. if len(char) < 2:
  115. char = '0' + char
  116. return char
  117.  
  118.  
  119.  
  120. async def async_update(self, now, **kwargs) -> None:
  121. try:
  122. await self.modeUpdate()
  123. except:
  124. _LOGGER.warning("Update failed")
  125. return
  126. async_dispatcher_send(self.hass, DOMAIN)
  127.  
  128.  
  129.  
  130. def sendResponse(self):
  131. answ = False
  132. self.child.sendline("char-write-cmd 0x000c 0100") #send packet to receive messages in future
  133. self.child.expect(r'\[LE\]>')
  134. answ = True
  135. return answ
  136.  
  137. def sendAuth(self):
  138. answer = False
  139. try:
  140. self.child.sendline("char-write-req 0x000e 55" + self.decToHex(self._iter) + "ff" + self._key + "aa") #send authorise key
  141. self.child.expect("value: ") # wait for response
  142. self.child.expect("\r\n") # wait for end string
  143. connectedStr = self.child.before[0:].decode("utf-8") # answer from device
  144. answ = connectedStr.split()[3] # parse: 00 - no 01 - yes
  145. self.child.expect(r'\[LE\]>')
  146. if answ == '02':
  147. answer = True
  148. self.iterase()
  149. except:
  150. answer = False
  151. _LOGGER.error('error auth')
  152. return answer
  153.  
  154.  
  155.  
  156. def sendOff(self):
  157. answ = False
  158. try:
  159. self.child.sendline("char-write-cmd 0x000e 55" + self.decToHex(self._iter) + "04aa") # OFF
  160. # self.child.expect("value: ")
  161. # self.child.expect("\r\n")
  162. self.child.expect(r'\[LE\]>')
  163. self.iterase()
  164. answ = True
  165. except:
  166. answ = False
  167. _LOGGER.error('error mode OFF')
  168. return answ
  169.  
  170.  
  171.  
  172. def sendStatus(self):
  173. answ = False
  174. self.child.sendline("char-read-hnd 0x000b") # status of device
  175. self.child.expect("Characteristic value/descriptor: ")
  176. self.child.expect("\r\n")
  177. statusStr = self.child.before[0:].decode("utf-8") # answer from device example 55 xx 06 00 00 00 00 01 2a 1e 00 00 00 00 00 00 80 00 00 aa
  178. answer = statusStr.split()
  179. self._status = str(answer[11])
  180. self._temp = self.hexToDec(str(answer[8]))
  181. self._mode = str(answer[3])
  182. tgtemp = str(answer[5])
  183. if tgtemp != '00':
  184. self._tgtemp = self.hexToDec(tgtemp)
  185. else:
  186. self._tgtemp = 100
  187. self.child.expect(r'\[LE\]>')
  188. self.iterase()
  189. answ = True
  190. return answ
  191.  
  192. def sendMode(self, mode, temp): # 00 - boil 01 - heat to temp 03 - backlight (boil by default) temp - in HEX
  193. answ = False
  194. self.child.sendline("char-write-cmd 0x000e 55" + self.decToHex(self._iter) + "05" + mode + "00" + temp + "00aa") # set Properties
  195. # self.child.expect("value: ")
  196. # self.child.expect("\r\n")
  197. self.child.expect(r'\[LE\]>')
  198. self.iterase()
  199. answ = True
  200. return answ
  201.  
  202.  
  203. ### composite methods
  204. def connect(self):
  205. answ = False
  206. if self._is_busy:
  207. self.disconnect()
  208. try:
  209. self._is_busy = True
  210. self.child = pexpect.spawn("gatttool --adapter=" + self._device + " -I -t random -b " + self._mac, ignore_sighup=False)
  211. self.child.expect(r'\[LE\]>', timeout=1)
  212. self.child.sendline("connect")
  213. self.child.expect(r'Connection successful.*\[LE\]>', timeout=1)
  214. self._is_busy = False
  215. answ = True
  216. except:
  217. _LOGGER.error('error connect')
  218. return answ
  219.  
  220. def disconnect(self):
  221. self._is_busy = True
  222. if self.child != None:
  223. self.child.sendline("exit")
  224. self.child = None
  225. self._is_busy = False
  226.  
  227.  
  228.  
  229.  
  230. async def modeOn(self, mode = "00", temp = "00"):
  231. if not self._is_busy:
  232. self._is_busy = True
  233. answ = False
  234. try:
  235. if self.connect():
  236. if self.sendResponse():
  237. if self.sendAuth():
  238. if self.sendMode(mode, temp):
  239. if self.sendOn():
  240. if self.sendStatus():
  241. answ = True
  242. except:
  243. _LOGGER.error('error composite modeOn')
  244. self.disconnect()
  245. return answ
  246. else:
  247. _LOGGER.info('device is busy now')
  248. return False
  249.  
  250.  
  251.  
  252. def sendOn(self):
  253. answ = False
  254. try:
  255. self.child.sendline("char-write-cmd 0x000e 55" + self.decToHex(self._iter) + "03aa") # ON
  256. self.child.expect(r'\[LE\]>')
  257. self.iterase()
  258. answ = True
  259. except:
  260. answ = False
  261. _LOGGER('error mode ON')
  262. return answ
  263.  
  264.  
  265. async def modeOff(self):
  266. if not self._is_busy:
  267. self._is_busy = True
  268. answ = False
  269. try:
  270. if self.connect():
  271. if self.sendResponse():
  272. if self.sendAuth():
  273. if self.sendOff():
  274. if self.sendStatus():
  275. answ = True
  276. except:
  277. _LOGGER.error('error composite modeOff')
  278. self.disconnect()
  279. return answ
  280. else:
  281. _LOGGER.info('device is busy now')
  282. return False
  283.  
  284. async def firstConnect(self):
  285. self._is_busy = True
  286. iter = 0
  287. answ = False
  288. if self.connect():
  289. while iter < 10: #10 attempts to auth
  290. answer = False
  291. if self.sendResponse():
  292. if self.sendAuth():
  293. answer = True
  294. break
  295. sleep(1)
  296. iter += 1
  297. if answer:
  298. if self.sendStatus():
  299. self._time_upd = time.strftime("%H:%M")
  300. answ = True
  301. if answ:
  302. self._connected = True
  303. self.disconnect()
  304.  
  305. async def modeUpdate(self):
  306. if not self._is_busy:
  307. self._is_busy = True
  308. answ = False
  309. if self.connect():
  310. if self.sendResponse():
  311. if self.sendAuth():
  312. if self.sendStatus():
  313. self._time_upd = time.strftime("%H:%M")
  314. answ = True
  315. self.disconnect()
  316. return answ
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement