RainEagle library plus script for polling data
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

293 lines
7.2 KiB

  1. import socket
  2. import sys
  3. import os
  4. import time
  5. import xml.etree.ElementTree as ET
  6. from pprint import pprint
  7. __all__ = ['Eagle']
  8. def et2d(et) :
  9. """ Etree to Dict
  10. converts an ETree to a Dict Tree
  11. lists are created for duplicate tag
  12. if there are multiple XML of the name name
  13. an list array is used
  14. attrib tags are converted to "tag_name" + "attrib_name"
  15. if an invalid arg is passed a empty dict is retrurned
  16. arg: ETree Element obj
  17. returns: a dict obj
  18. """
  19. d = dict()
  20. if not isinstance(et, ET.Element) :
  21. return d
  22. children = list(et)
  23. if et.attrib :
  24. for k, v in list(et.items()) :
  25. d[et.tag + "-" + k] = v
  26. if children :
  27. for child in children :
  28. if child.tag in d :
  29. if type(d[child.tag]) != list :
  30. t = d[child.tag]
  31. d[child.tag] = [t]
  32. if list(child) or child.attrib :
  33. if child.tag in d :
  34. d[child.tag].append(et2d(child))
  35. else :
  36. d[child.tag] = et2d(child)
  37. else :
  38. if child.tag in d :
  39. d[child.tag].append(child.text)
  40. else :
  41. d[child.tag] = child.text
  42. return d
  43. #
  44. # Simple Base class for ISY Class
  45. class Eagle(object) :
  46. """
  47. Class for talking to Rainforest Automation EAGLE (RFA-Z109)
  48. args:
  49. debug print debug messages if true
  50. addr address of device
  51. port port on device (default 5002)
  52. getmac connect to device at start up and get macid (default true)
  53. Currently there is very little error handling ( if any at all )
  54. """
  55. def __init__(self, **kwargs):
  56. self.debug = kwargs.get("debug", 0)
  57. if self.debug :
  58. print self.__class__.__name__, __name__
  59. self.addr = kwargs.get("addr", os.getenv('EAGLE_ADDR', None))
  60. self.port = kwargs.get("port", os.getenv('EAGLE_PORT', 5002))
  61. self.getmac = kwargs.get("getmac", True)
  62. self.soc = None
  63. self.macid = None
  64. # preload
  65. if self.getmac :
  66. self.device_info = self.list_devices()
  67. if self.debug :
  68. pprint(self.device_info)
  69. # self.macid = self.device_info['DeviceInfo']['DeviceMacId']
  70. if self.debug :
  71. print "DeviceMacId = ", self.macid
  72. # commands as class funtions
  73. def list_devices(self):
  74. comm_responce = self._send_comm("list_devices")
  75. if self.debug :
  76. print "comm_responce =", comm_responce
  77. etree = ET.fromstring('<S>' + comm_responce + '</S>' )
  78. rv = et2d(etree)
  79. if self.macid == None :
  80. self.macid = rv['DeviceInfo']['DeviceMacId']
  81. return rv
  82. # 3
  83. def get_device_data(self, macid=None) :
  84. """ Send the GET_DEVICE_DATA command to get a data dump """
  85. if macid == None :
  86. macid = self.macid
  87. comm_responce = self._send_comm("get_device_data", MacId=macid)
  88. etree = ET.fromstring('<S>' + comm_responce + '</S>' )
  89. rv = et2d(etree)
  90. return rv
  91. # 10
  92. def get_instantaneous_demand(self, macid=None) :
  93. """ Send the GET_INSTANTANEOUS_DEMAND command
  94. get the real time demand from the meter
  95. args:
  96. MacId 16 hex digits, MAC addr of EAGLE ZigBee radio
  97. """
  98. if macid == None :
  99. macid = self.macid
  100. comm_responce = self._send_comm("get_instantaneous_demand",
  101. MacId=macid)
  102. etree = ET.fromstring('<S>' + comm_responce + '</S>' )
  103. rv = et2d(etree)
  104. return rv
  105. # 11
  106. def get_demand_values(self, macid=None, interval="hour", frequency=None ) :
  107. """ Send the GET_DEMAND_VALUES command
  108. get a series of instantaneous demand values
  109. args:
  110. MacId 16 hex digits, MAC addr of EAGLE ZigBee radio
  111. Interval hour | day | week
  112. [Frequency] int seconds between samples
  113. """
  114. if macid == None :
  115. macid = self.macid
  116. kwargs = {"MacId": macid, "Interval": interval}
  117. if frequency :
  118. kwargs["Frequency"] = frequency
  119. comm_responce = self._send_comm("get_demand_values", **kwargs)
  120. etree = ET.fromstring('<S>' + comm_responce + '</S>' )
  121. rv = et2d(etree)
  122. return rv
  123. # 12
  124. def get_summation_values(self, macid=None, interval="day") :
  125. """ Send the GET_SUMMATION_VALUES command
  126. get a series of net summation values
  127. args:
  128. MacId 16 hex digits, MAC addr of EAGLE ZigBee radio
  129. Interval day | week | month | year
  130. """
  131. if macid == None :
  132. macid = self.macid
  133. comm_responce = self._send_comm("get_summation_values",
  134. MacId=macid, Interval=interval )
  135. etree = ET.fromstring('<S>' + comm_responce + '</S>' )
  136. rv = et2d(etree)
  137. return rv
  138. # 14
  139. def set_fast_poll(self, macid=None, frequency="0x04", duration="0xFF") :
  140. """ Send the SET_FAST_POLL command
  141. set the fast poll mode on the meter
  142. args:
  143. MacId 16 hex digits, MAC addr of EAGLE ZigBee radio
  144. Frequency 0x01 - 0xFF Freq to poll meter, in seconds
  145. Duration 0x00 - 0x0F Duration of fast poll mode, in minutes (max 15)
  146. """
  147. if macid == None :
  148. macid = self.macid
  149. if isinstance(frequency, int) :
  150. frequency = "{:#04x}".format(m)
  151. if isinstance(duration, int) :
  152. frequency = "{:#04x}".format(m)
  153. comm_responce = self._send_comm("get_instantaneous_demand",
  154. MacId=macid, Frequency=frequency, Duration=duration)
  155. etree = ET.fromstring('<S>' + comm_responce + '</S>' )
  156. rv = et2d(etree)
  157. return rv
  158. # 15
  159. def get_fast_poll_status(self, macid=None) :
  160. """ Send the GET_FAST_POLL_STATUS command
  161. get the current status of fast poll mode.
  162. args:
  163. MacId 16 hex digits, MAC addr of EAGLE ZigBee radio
  164. """
  165. if macid == None :
  166. macid = self.macid
  167. comm_responce = self._send_comm("get_fast_poll_status", MacId=macid)
  168. etree = ET.fromstring('<S>' + comm_responce + '</S>' )
  169. rv = et2d(etree)
  170. return rv
  171. # 17
  172. def get_history_data(self, macid=None, starttime="0x00000000", endtime=None, frequency=None ) :
  173. """ Send the GET_HISTORY_DATA command
  174. get a series of summation values over an interval of time
  175. """
  176. if macid == None :
  177. macid = self.macid
  178. kwargs = {"MacId": macid, "StartTime": starttime}
  179. if endtime :
  180. kwargs["EndTime"] = endtime
  181. if frequency :
  182. kwargs["Frequency"] = frequency
  183. comm_responce = self._send_comm("get_fast_poll_status", **kwargs)
  184. etree = ET.fromstring('<S>' + comm_responce + '</S>' )
  185. rv = et2d(etree)
  186. return rv
  187. # Support functions
  188. def connect(self) :
  189. self.soc = socket.create_connection( (self.addr, self.port), 10)
  190. def disconnect(self):
  191. try :
  192. if self.soc :
  193. self.soc.close()
  194. self.soc = False
  195. except IOError :
  196. pass
  197. def _send_comm(self, cmd, **kwargs):
  198. if cmd == "set_fast_poll" :
  199. command_tag = "RavenCommand"
  200. else :
  201. command_tag = "LocalCommand"
  202. commstr = "<{0}>\n ".format(command_tag)
  203. commstr += "<Name>{0!s}</Name>\n".format(cmd)
  204. for k, v in kwargs.items() :
  205. commstr += "<{0}>{1!s}</{0}>\n".format(k, v)
  206. commstr += "</{0}>\n".format(command_tag)
  207. self.connect()
  208. self.soc.sendall(commstr)
  209. if self.debug :
  210. print "commstr : \n", commstr
  211. # time.sleep(1)
  212. replystr = ""
  213. while 1 :
  214. buf = self.soc.recv(1000)
  215. if not buf:
  216. break
  217. replystr += buf
  218. self.disconnect()
  219. return replystr
  220. def to_unix_time(self, t) :
  221. """ converts time stored as
  222. offset in seconds from "Jan 1 00:00:00 2000"
  223. to unix's epoch of 1970
  224. """
  225. if isinstance(t, (int, long, float) ) :
  226. return t + 946684800
  227. if isinstance(t, str) and t.startswith('0x') :
  228. return 946684800 + int(t, 16)
  229. # Do nothing
  230. # (syntax check)
  231. #
  232. if __name__ == "__main__":
  233. import __main__
  234. print(__main__.__file__)
  235. print("syntax ok")
  236. exit(0)