Example: LogicMachine as gateway to IrrigationCaddy sprinkler system

Task

This examples will show how to integrate/control IrrigationCaddy sprinkler system into common KNX/ModBus/BACnet/EnOCean/DALI network through LogicMachine.

IrrigationCaddy user library

Add this program to Scripting –> User Libraries

Source code    
  1. --------------------------------------------------------------------------------
  2. -- LUA IrrigationCaddy control module.
  3. -- A module to control and monitor IrrigationCaddy controller
  4. --
  5. -- @module irrigationcaddy
  6. -- @return #irrigationcaddy
  7.  
  8. ---@type luavbus
  9.  
  10. local irrigationcaddy = {}
  11.  
  12. irrigationcaddy.__index = irrigationcaddy
  13. ---
  14. -- IP or hostname of irrigationcaddy
  15. --
  16. -- @field [parent=#irrigationcaddy] #string IP or hostname
  17. irrigationcaddy.ip = ""
  18. ---
  19. -- user of irrigationcaddy
  20. --
  21. -- @field [parent=#irrigationcaddy] #string username
  22. irrigationcaddy.login = nil
  23. ---
  24. -- Password of irrigationcaddy
  25. --
  26. -- @field [parent=#irrigationcaddy] #string password
  27. irrigationcaddy.password = nil
  28.  
  29. -- Status
  30. ---
  31. -- Statusof irrigationcadd
  32. --
  33. -- @field [parent=#irrigationcaddy] #number 0 disable 1 enabled
  34. irrigationcaddy.allowRun = 0
  35. irrigationcaddy.zoneNumber = 0
  36. irrigationcaddy.progNumber = 0
  37. irrigationcaddy.progSecLeft = 0
  38. irrigationcaddy.zoneSecLeft = 0
  39.  
  40. json = require('cjson')
  41. socket = require('socket')
  42. require('socket.http')
  43. require("ltn12")
  44.  
  45. --- Creates and issue HTTP post query
  46. -- @author Luca Palazzo
  47. -- @return res result of HTTP query, err in case of errors
  48. --
  49. function irrigationcaddy.query ( uri, reqbody )
  50. irrigationcaddy.log(DEBUG3, "irrigationcaddy.query("..uri.."): query irrigationcaddy" )
  51.  
  52. url = string.format('http://%s/%s', irrigationcaddy.ip, uri)
  53. if ( reqbody ~= nil and url ~= nil ) then
  54. irrigationcaddy.log(DEBUG3, "irrigationcaddy.query("..url.."): query with POST" )
  55.  
  56. res, err, header = socket.http.request{
  57. url = url,
  58. method = "POST",
  59. headers = {
  60. ["Content-Type"] = "application/x-www-form-urlencoded",
  61. ['Content-Length'] = string.len(reqbody)
  62. },
  63. source = ltn12.source.string(reqbody),
  64. sink = ltn12.sink.table(respbody)
  65. }
  66. elseif ( url ~= nil ) then
  67. irrigationcaddy.log(DEBUG3, "irrigationcaddy.query("..url.."): query without POST" )
  68. res, err = socket.http.request(url)
  69. else
  70. print ( "Nil request" )
  71. return nil
  72. end
  73. irrigationcaddy.log(DEBUG3, "irrigationcaddy.query("..url.."): url ".. url )
  74.  
  75. return res, err
  76.  
  77. end
  78.  
  79. --- Creates a new instance of irrigationcaddy
  80. -- @author Luca Palazzo
  81. -- @return instance of irrigationcaddy
  82. --
  83. function irrigationcaddy.new ( ip, login, password )
  84. local _irrigationcaddy = {}
  85. setmetatable(_irrigationcaddy,irrigationcaddy)
  86. if ( ip ~= nil and login ~= nil and password ~= nil ) then
  87. irrigationcaddy.log(DEBUG3, "irrigationcaddy.new("..ip..", "..login..", "..password.."): creating new instance of irrigationcaddy" )
  88. _irrigationcaddy.ip = ip
  89. _irrigationcaddy.login = login
  90. _irrigationcaddy.password = password
  91. elseif ( ip ~= nil ) then
  92. irrigationcaddy.log(DEBUG3, "irrigationcaddy.new("..ip.."): creating new instance of irrigatincaddy" )
  93. _irrigationcaddy.ip = ip
  94. else
  95. irrigationcaddy.log(DEBUG3, "irrigationcaddy.new(): creating new instance of irrigationcaddy" )
  96. end
  97. return _irrigationcaddy
  98. end
  99.  
  100.  
  101. --- Prints the status of irrigationcaddy
  102. -- @author Luca Palazzo
  103. -- @return status string of irrigationcaddy
  104. --
  105. function irrigationcaddy.print ( ... )
  106. irrigationcaddy.log(DEBUG3, "irrigationcaddy.print(): printing irrigationcaddy status" )
  107. status_string = string.format ( "Status %s program %s zone %s prog left %s zone left %s", irrigationcaddy.allowRun, irrigationcaddy.progNumber, irrigationcaddy.zoneNumber, irrigationcaddy.progSecLeft, irrigationcaddy.zoneSecLeft )
  108.  
  109. irrigationcaddy.log(DEBUG3, string.format ( "irrigationcaddy.print(): status %s", status_string ) )
  110.  
  111. return status_string
  112.  
  113. end
  114.  
  115. --- Updates the status from irrigationcaddy
  116. -- @author Luca Palazzo
  117. -- @return true on successfull update, false in case of error
  118. --
  119. function irrigationcaddy.update ( ... )
  120. irrigationcaddy.log(DEBUG3, "irrigationcaddy.update(): getting irrigationcaddy status" )
  121.  
  122. res,err = irrigationcaddy.query ( "status.json" )
  123.  
  124. if res then
  125. jsonstatus = json.decode(res)
  126. status = jsonstatus['allowRun']
  127. irrigationcaddy.allowRun = jsonstatus['allowRun']
  128. irrigationcaddy.zoneNumber = jsonstatus['zoneNumber']
  129. irrigationcaddy.progNumber = jsonstatus['progNumber']
  130. irrigationcaddy.progSecLeft = jsonstatus['progSecLeft']
  131. irrigationcaddy.zoneSecLeft = jsonstatus['zoneSecLeft']
  132. irrigationcaddy.log ( 5, string.format ( "irrigationcaddy.update(): Status %s program %s zone %si prog left %s zone left %s", irrigationcaddy.allowRun, irrigationcaddy.progNumber, irrigationcaddy.zoneNumber, irrigationcaddy.progSecLeft, irrigationcaddy.zoneSecLeft ) )
  133. return true
  134. end
  135.  
  136. irrigationcaddy.log ( ERR, "Error getting JSON" )
  137. return false
  138.  
  139. end
  140.  
  141. --- Gets the calendar scheduled action of irrigationcaddy
  142. -- @author Luca Palazzo
  143. -- @return a table containing
  144. --
  145. function irrigationcaddy.get_calendar ( calendarperiod )
  146. irrigationcaddy.log(DEBUG3, "irrigationcaddy.get_calendar(): getting irrigationcaddy calendar status" )
  147.  
  148. local cal_start_time = os.time();
  149. if ( calendarperiod == nil or type(calendarperiod) ~= "number" ) then
  150. local cal_end_time = cal_start_time + 3600*24*2;
  151. else
  152. cal_end_time = calendarperiod
  153. end
  154.  
  155.  
  156. uri = string.format('calendar.json?start=%d&end=%d', cal_start_time, cal_end_time)
  157. res, err = irrigationcaddy.query( uri, reqbody )
  158.  
  159. if res then
  160. calendar = json.decode(res)
  161. if ( type(calendar) ~= 'table' ) then
  162. irrigationcaddy.log(DEBUG1, string.format ( "irrigationcaddy.get_calendar(): calendar not a table" ) )
  163. return false
  164. end
  165. for k, v in pairs ( calendar ) do
  166. if ( calendar[k]["title"] == nil ) then
  167. irrigationcaddy.log(DEBUG1, string.format ( "irrigationcaddy.get_calendar(): nil calndar voice" ) )
  168. else
  169. irrigationcaddy.log(DEBUG3, string.format ( "irrigationcaddy.get_calendar(): Program: %s next start %s ends %s ", calendar[k]["title"], calendar[k]["start"], calendar[k]["end"] ) )
  170. end
  171. end
  172. return calendar, err
  173. else
  174. log ( "Error getting JSON" )
  175. return nil, err
  176. end
  177. end
  178.  
  179. --- Gets the time of irrigationcaddy
  180. -- @author Luca Palazzo
  181. -- @return a timestamp, or nil and err in case of error
  182. --
  183. function irrigationcaddy.time ()
  184. irrigationcaddy.log(DEBUG3, "irrigationcaddy.time(): getting time from device" )
  185.  
  186. uri = string.format('dateTime.json')
  187.  
  188. res, err = irrigationcaddy.query( uri, reqbody )
  189.  
  190. if res then
  191. time = json.decode(res)
  192. -- log ( dump ( calendar ) )
  193. if ( type(time) == 'table' ) then
  194. for k, v in pairs ( time ) do
  195. irrigationcaddy.log(DEBUG3, string.format ( "irrigationcaddy.time(): %s %s", tostring(k), tostring(v) ) )
  196. end
  197. ictime = os.time({day=time.day,month=time.month,year="20"..time.year,hour=time.hr,min=time.min,sec=time.sec})
  198. irrigationcaddy.log(DEBUG3, string.format ( "irrigationcaddy.time(): caddy's timestamp %d", ictime ) )
  199. return ictime, false
  200. else
  201. irrigationcaddy.log(DEBUG3, string.format ( "irrigationcaddy.time(): time not a table" ) )
  202. return nil
  203. end
  204. end
  205.  
  206. log ( "Error getting JSON" )
  207. return nil, err
  208.  
  209. end
  210.  
  211. --- Gets the boot time of irrigationcaddy
  212. -- @author Luca Palazzo
  213. -- @return a timestamp, or nil and err in case of error
  214. --
  215. function irrigationcaddy.boottime ()
  216. irrigationcaddy.log(DEBUG3, "irrigationcaddy.boottime(): getting last boot from device" )
  217.  
  218. uri = string.format('bootTime.json')
  219.  
  220. res, err = irrigationcaddy.query( uri, reqbody )
  221.  
  222.  
  223. if res then
  224. boottime = json.decode(res)
  225. -- log ( dump ( calendar ) )
  226. if ( type(boottime) == 'table' ) then
  227. for k, v in pairs ( boottime ) do
  228. irrigationcaddy.log(DEBUG3, string.format ( "irrigationcaddy.boottime(): %s %s", tostring(k), tostring(v) ) )
  229. end
  230. icboottime = os.time({day=boottime.day,month=boottime.month,year="20"..boottime.year,hour=boottime.hr,min=boottime.min,sec=boottime.sec})
  231. irrigationcaddy.log(DEBUG3, string.format ( "irrigationcaddy.time(): caddy boot's timestamp %d", icboottime ) )
  232.  
  233. return boottime
  234. else
  235. irrigationcaddy.log(DEBUG3, string.format ( "irrigationcaddy.boottime(): boot time not a table" ) )
  236. return nil
  237. end
  238. end
  239. irrigationcaddy.log( INFO, "Error getting JSON" )
  240. return nil, err
  241. end
  242.  
  243.  
  244. --- Disable the system
  245. -- @author Luca Palazzo
  246. -- @return true in case of successful disable, false in case of error
  247. --
  248. function irrigationcaddy.disable ( ... )
  249. irrigationcaddy.log(DEBUG3, "irrigationcaddy.disable(): disabling" )
  250.  
  251. uri = string.format('stopSprinklers.htm')
  252. reqbody = "stop=off"
  253.  
  254. res, err = irrigationcaddy.query( uri, reqbody )
  255.  
  256. irrigationcaddy.log(DEBUG3, "irrigationcaddy.disable(): return resource " .. res .. " error " .. err )
  257. if ( not err ) then
  258. return true
  259. end
  260.  
  261. return false
  262. end
  263.  
  264. --- Enable the system
  265. -- @author Luca Palazzo
  266. -- @return true in case of successful disable, false in case of error
  267. --
  268. function irrigationcaddy.enable ( ... )
  269. irrigationcaddy.log(DEBUG3, "irrigationcaddy.disable(): enabling" )
  270.  
  271. uri = string.format('runSprinklers.htm')
  272. reqbody = "run=run"
  273.  
  274. res, err = irrigationcaddy.query( uri, reqbody )
  275.  
  276. if ( not err ) then
  277. return true
  278. end
  279.  
  280. return false
  281.  
  282. end
  283.  
  284. ERR, WARN, INFO, DEBUG1, DEBUG2, DEBUG3, DEBUG4, DEBUG5 = 1, 2, 3, 4, 5, 6, 7, 8
  285.  
  286. function irrigationcaddy.log ( level, ... )
  287. local prefix = ""
  288. local log_string = ""
  289. if ( log_level ~= nil and level ~= nil and level <= log_level ) then
  290. if ( level == ERR ) then
  291. log_string = "ERROR"
  292. elseif ( level == WARN ) then
  293. log_string = "WARN"
  294. elseif ( level == INFO ) then
  295. log_string = "INFO"
  296. elseif ( level == DEBUG1 ) then
  297. log_string = "DEBUG1"
  298. elseif ( level == DEBUG2 ) then
  299. log_string = "DEBUG2"
  300. elseif ( level == DEBUG3 ) then
  301. log_string = "DEBUG3"
  302. elseif ( level == DEBUG4 ) then
  303. log_string = "DEBUG4"
  304. elseif ( level == DEBUG5 ) then
  305. log_string = "DEBUG5"
  306. else
  307. log_string = "UNKNOWN"
  308. end
  309. print ( log_string .. ": " .. ... )
  310. end
  311. end
  312.  
  313. return irrigationcaddy

TestIrrigationCaddy user library

Source code    
  1. irrigationcaddy = require("irrigationcaddy")
  2. log_level = ERR
  3. irrigationcaddy.ip = "192.168.168.73"
  4. -- irrigationcaddy.disable()
  5. icboottimestamp = irrigationcaddy.boottime()
  6. schedules = irrigationcaddy.get_calendar()
  7. -- irrigationcaddy.enable()
  8. irrigationcaddy.update()
  9. ictimestamp = irrigationcaddy.time()
  10.  
  11. irrigationcaddy.print()

Event-based script for updating status

Source code    
  1. old_print = print
  2. debug_level = 0
  3. print = function(...)
  4.  if ( verbose ) then
  5.    log(...)
  6.   end
  7. end
  8.  
  9. irrigationcaddy = require("user.IrrigationCaddy")
  10. syslog_level = ERR
  11. verbose = false
  12.  
  13.  
  14. irrigationcaddy.ip = "192.168.1.100"
  15.  
  16. irrigationcaddy.update()
  17.  
  18. print ( string.format ( "IC status %s", irrigationcaddy.allowRun ) )
  19.  
  20. ic = grp.getvalue ( '0/1/40' )
  21. ic_status = grp.getvalue ( '0/1/41' )
  22.  
  23. if ( ic_status ~= irrigationcaddy.allowRun ) then
  24. print ( string.format ( "IC status (%s) different from device (%s)", tostring(ic_status), tostring(irrigationcaddy.allowRun) ) )
  25. grp.write ( '0/1/41', irrigationcaddy.allowRun, dt.boolean )
  26. end

Event-based script for updating the configuration

Source code    
  1. irrigationcaddy = require("user.IrrigationCaddy")
  2. debug_level = 5
  3. verbose = true
  4.  
  5.  
  6. irrigationcaddy.ip = "192.168.168.73"
  7.  
  8. ic = grp.getvalue ( '0/1/40' )
  9. ic_status = grp.getvalue ( '0/1/41' )
  10.  
  11. print ( string.format ( 'Evento da %s per %s tipo %s', event['src'], event['dst'], event['type']) )
  12.  
  13. group = event['dst']
  14.  
  15. if ( group == '0/1/40' ) then
  16.     if ( ic_status ~= ic ) then
  17.    print ( string.format ( "Changing IC status (%s)  to %s", tostring(ic_status), tostring(ic) ) )
  18.    if ( ic == false ) then
  19.      irrigationcaddy.disable()
  20.    elseif ( ic == true ) then
  21.      irrigationcaddy.enable()
  22.    end
  23.    grp.write( '0/1/49', true, dt.boolean )
  24.     end
  25. end

Script that triggers the update

Where 0/1/49 is triggering group address

Source code    
  1. log ( 'Triggering IC update' )
  2. grp.write('0/1/49', true, dt.boolean )
  3. return

 
Please consult LogicMachine forum IrrigationCaddy thread for more functionality and updated scripts.

 
Created by Luca Palazzo