Example: LogicMachine as gateway to RESOL via VBus protocol

Task

This examples will show how to integrate RESOL solar automation solution into common KNX/ModBus/BACnet/EnOCean/DALI visualization through LogicMachine.
For integration you need to have VBus LAN adapter connected to your RESOL system.
Direct commands are not yet implemented in this example.

User Libraries

Add the following scripts in Scripting –> User libraries.

luavbuspacket.lua

Source code    
  1. local luavbuspacket = {}
  2. luavbuspacket_mt = { __index = luavbuspacket }
  3.  
  4. luavbuspacket.srcAddress = 0 -- source address (2 byte)
  5. luavbuspacket.dstAddress = 0 -- destination address (2 byte)
  6. luavbuspacket.protoVersion = 1 -- protocol version 0x10 o 0x20 or 0x30 (1 byte)
  7. --- packet version
  8. luavbuspacket.version = 0 -- version (1 byte)
  9. luavbuspacket.command = 0 -- command (2 bytes)
  10. luavbuspacket.payloads = 0 -- number of payloads (2 bytes)
  11. luavbuspacket.payloadsData = nil -- 4-byte payloads
  12. luavbuspacket.dataPointID = nil -- ID of the data point (2 bytes)
  13. luavbuspacket.dataPointValue = nil -- value of data point ( 4 bytes)
  14. luavbuspacket.checksum = 0 -- CRC (1 bytes)
  15. luavbuspacket.profile = nil -- vbus profile
  16. luavbuspacket.profileData = nil -- vbus profile data
  17.  
  18.  
  19.  
  20. luavbuspacket.mt = {} -- metatable
  21.  
  22. --- Creates a new instance of luavbuspacket
  23. -- @author Luca Palazzo
  24. -- @return instance of luavbuspacket
  25. --
  26. function luavbuspacket.new ( o )
  27. luavbuspacket.log ( DEBUG3, string.format ( "luavbuspacket.new( %s )", o ) )
  28. local _vp = o or {}
  29. luavbuspacket.log ( DEBUG4, string.format ( "luavbuspacket.new(): %s", _vp ) )
  30.  
  31. setmetatable(_vp, luavbuspacket_mt )
  32. luavbuspacket_mt.__tostring = luavbuspacket.tostring
  33. return _vp
  34. end
  35.  
  36. --- Stringify luavbuspacket object. Use in __tostring metatable field too
  37. -- @author Luca Palazzo
  38. -- @param packet the instance of luavbuspacket to stringify
  39. -- @return string-fied version of luavbuspacket
  40. function luavbuspacket.tostring( packet )
  41. luavbuspacket.log ( DEBUG3, "luavbuspacket.tostring( packet )" )
  42. -- if ( packet == nil or packet.srcAddress == nil ) then
  43. -- return "nil packet"
  44. -- end
  45. vbus_packet_string = string.format( "VBus frame source address 0x%04x destination address 0x%04x version 0x%02x command 0x%02x checksum 0x%04x payloads %d", packet.srcAddress, packet.dstAddress, packet.version, packet.command, packet.checksum, packet.payloads )
  46. luavbuspacket.log ( DEBUG3, vbus_packet_string )
  47. return vbus_packet_string
  48. end
  49.  
  50. function luavbuspacket.profileprint( packet )
  51. luavbuspacket.log ( DEBUG3, "luavbuspacket.profilestring( packet )" )
  52. vbus_packet_string = ""
  53. if ( packet == nil ) then
  54. return "no packet"
  55. end
  56.  
  57. if ( packet.profile == nil ) then
  58. return "no profile"
  59. end
  60.  
  61. if ( packet.profile.objects == nil ) then
  62. return "no objects"
  63. end
  64.  
  65. for k, v in pairs( packet.profile.objects ) do
  66. local offset = tonumber(v[1])
  67. local length = tonumber(v[2])
  68. local unknown = tonumber(v[3])
  69. local name = v[4]
  70. local factor = tonumber(v[5])
  71. local unit = v[6]
  72. if (unit==nil) then
  73. unit = ""
  74. end
  75. luavbuspacket.log( DEBUG4, string.format ("Offset %d length %d", offset, length ) )
  76.  
  77. local value = 0
  78. packet_length = string.len(packet.payloadsData)
  79. -- PacketDump(data)
  80. for index = 1, length, 1 do
  81. if ( offset+index > packet_length ) then
  82. luavbuspacket.log ( DEBUG3, string.format ("Buffer overrun (length %d)", packet_length ) )
  83. break
  84. end
  85. luavbuspacket.log ( DEBUG4, string.format ("Getting byte %d at offset %d with value 0x%02x", index, offset, packet.payloadsData:byte(offset+index) ) )
  86. -- print ("Adding "..data:byte(offset+index+1)*(256^index))
  87. value = value + packet.payloadsData:byte(offset+index)*(256^(index-1))
  88. -- print ( "Value: " .. value .. " index " .. index )
  89. end
  90.  
  91.  
  92. luavbuspacket.log ( DEBUG2, string.format ("luavbus:packetParseV1(): name %s Value %02.2f %s", name, value*factor, unit ) )
  93. vbus_packet_string = vbus_packet_string .. string.format ("%s: %02.2f %s ", name, value*factor, unit )
  94.  
  95. end
  96. -- vbus_packet_string = string.format( "VBus frame source address 0x%04x destination address 0x%04x version 0x%02x command 0x%02x checksum 0x%04x", packet.srcAddress, packet.dstAddress, packet.version, packet.command, packet.checksum )
  97. luavbuspacket.log ( DEBUG4, vbus_packet_string )
  98. return vbus_packet_string
  99. end
  100.  
  101. function luavbuspacket.getProfileData( self )
  102. luavbuspacket.log ( DEBUG3, "luavbuspacket.getProfileData()" )
  103.  
  104. if ( self.profile == nil ) then
  105. luavbuspacket.log ( WARN, "Packet ha no profile" )
  106. return nil
  107. end
  108.  
  109. self.profileData = {}
  110.  
  111. for k, object in pairs( self.profile.objects ) do
  112. local offset = tonumber(object[1])
  113. local length = tonumber(object[2])
  114. local unknown = tonumber(object[3])
  115. local name = object[4]
  116. local factor = tonumber(object[5])
  117. local unit = object[6]
  118. local value = 0
  119.  
  120. if (unit==nil) then
  121. unit = ""
  122. end
  123. luavbuspacket.log( DEBUG4, string.format ("Offset %d length %d", offset, length ) )
  124.  
  125. packet_length = string.len(self.payloadsData)
  126. for index = 1, length, 1 do
  127. if ( offset+index > packet_length ) then
  128. luavbuspacket.log ( WARN, string.format ("Buffer overrun (length %d)", packet_length ) )
  129. break
  130. end
  131. luavbuspacket.log ( DEBUG2, string.format ("Getting byte %d at offset %d with value 0x%02x", index, offset, self.payloadsData:byte(offset+index) ) )
  132. -- print ("Adding "..data:byte(offset+index+1)*(256^index))
  133. value = value + self.payloadsData:byte(offset+index)*(256^(index-1))
  134. -- print ( "Value: " .. value .. " index " .. index )
  135. end
  136. data = {}
  137. data.name = name
  138. data.factor = factor
  139. data.unit = unit
  140. data.value = value*factor
  141. table.insert(self.profileData, data)
  142. luavbuspacket.log ( DEBUG2, string.format ("luavbuspacket.getProfileData(): name %s value %.02f",data.name, data.value ) )
  143.  
  144. end
  145. end
  146.  
  147. function luavbuspacket.log ( level, ... )
  148. local prefix = ""
  149. local log_string = ""
  150. if ( log_level ~= nil and level ~= nil and level <= log_level ) then
  151. if ( level == ERR ) then
  152. log_string = "ERROR"
  153. elseif ( level == WARN ) then
  154. log_string = "WARN"
  155. elseif ( level == INFO ) then
  156. log_string = "INFO"
  157. elseif ( level == DEBUG1 ) then
  158. log_string = "DEBUG1"
  159. elseif ( level == DEBUG2 ) then
  160. log_string = "DEBUG2"
  161. elseif ( level == DEBUG3 ) then
  162. log_string = "DEBUG3"
  163. elseif ( level == DEBUG4 ) then
  164. log_string = "DEBUG4"
  165. elseif ( level == DEBUG5 ) then
  166. log_string = "DEBUG5"
  167. else
  168. log_string = "UNKNOWN"
  169. end
  170. print ( log_string .. ": " .. ... )
  171. end
  172. end
  173.  
  174. return luavbuspacket

luavbus.lua

Source code    
  1. local luavbus = {}
  2.  
  3. socket = require ("socket")
  4.  
  5. for _, searcher in ipairs(package.searchers or package.loaders) do
  6. name = "cjson"
  7. local loader = searcher(name)
  8. if type(loader) == 'function' then
  9. package.preload[name] = loader
  10. cjson = require (name)
  11. break
  12. end
  13. name = "json"
  14. local loader = searcher(name)
  15. if type(loader) == 'function' then
  16. package.preload[name] = loader
  17. cjson = require (name)
  18. break
  19. end
  20. end
  21.  
  22. for _, searcher in ipairs(package.searchers or package.loaders) do
  23. name = "vbusprofiles"
  24. local loader = searcher(name)
  25. if type(loader) == 'function' then
  26. package.preload[name] = loader
  27. vbusprofiles = require (name)
  28. break
  29. end
  30. name = "user.vbusprofiles"
  31. local loader = searcher(name)
  32. if type(loader) == 'function' then
  33. package.preload[name] = loader
  34. vbusprofiles = require (name)
  35. break
  36. end
  37. end
  38.  
  39. for _, searcher in ipairs(package.searchers or package.loaders) do
  40. name = "luavbuspacket"
  41. local loader = searcher(name)
  42. if type(loader) == 'function' then
  43. package.preload[name] = loader
  44. luavbuspacket = require (name)
  45. break
  46. end
  47. name = "user.luavbuspacket"
  48. local loader = searcher(name)
  49. if type(loader) == 'function' then
  50. package.preload[name] = loader
  51. luavbuspacket = require (name)
  52. break
  53. end
  54. end
  55.  
  56. local SERIAL, TCPIP = 0, 1
  57.  
  58. luavbus.connection_type = TCPIP
  59. luavbus.device = nil
  60. luavbus.remote_host = nil
  61. luavbus.remote_port = nil
  62. luavbus.buffer_size = 256
  63. luavbus.buffer = nil
  64. luavbus.password = nil
  65. luavbus.datacount = 0
  66. luavbus.socket = nil
  67. luavbus.buffer = ""
  68. luavbus.profiles = {}
  69.  
  70. --- Creates a new instance of luavbus
  71. -- @author Luca Palazzo
  72. -- @return instance of luavbuspacket
  73. --
  74. function luavbus.new ( vbus_device, vbus_connection_type, vbus_password )
  75. _v = {}
  76. if ( vbus_device == nil ) then
  77. luavbus.log ( DEBUG1, "No device specified" )
  78. return nil
  79. end
  80.  
  81. if ( vbus_connection_type ~= nil ) then
  82. luavbus.log ( DEBUG1, "Setting connectiont type to " .. vbus_connection_type )
  83. self.connection_type = vbus_connection_type
  84. end
  85.  
  86. if ( vbus_password ~= nil ) then
  87. luavbus.log ( DEBUG1, "Setting password to " .. vbus_password )
  88. self.password = vbus_password
  89. end
  90.  
  91. self.buffer = ""
  92. setmetatable(_v, self)
  93. _v.__index = _v
  94. return _v
  95. end
  96.  
  97. --- Connect to the device
  98. -- @author Luca Palazzo
  99. -- @return the socket or nil on failure
  100. --
  101. function luavbus:connect ()
  102. luavbus.log ( 5, "luavbus:connect(): starting connection to the device" )
  103. if ( self.connection_type == SERIAL ) then
  104. luavbus.log ( DEBUG1, "Serial connection not yet implemented")
  105. return nil
  106. else
  107. luavbus.log ( DEBUG1, "Using TCPIP connection")
  108. end
  109.  
  110. if (self.remote_host == nil or self.remote_port == nil ) then
  111. luavbus.log ( ERR, "TCPIP connection parameters missing")
  112. return nil
  113. end
  114.  
  115. self.socket = socket.connect(self.remote_host,self.remote_port)
  116. self.socket:settimeout(0.5)
  117. self.socket:setoption("keepalive",true)
  118. if ( self.socket == nil ) then
  119. luavbus.log ( ERR, "Error opening socket" )
  120. return nil
  121. end
  122. -- debug ( 5, string.format ( "Socket: %s", tostring ( self.socket )))
  123. luavbus.log ( DEBUG3, "luavbus:connect(): waiting for +HELLO" )
  124. incoming = self:getResponse()
  125. incoming = incoming:sub(1, -2)
  126. luavbus.log ( DEBUG3, string.format ( "luavbus:connect(): received %s ", incoming ) )
  127. if ( incoming ~= "+HELLO" ) then
  128. luavbus.log ( ERR, string.format ("Error in incoming string: %s|", incoming) )
  129. end
  130.  
  131. luavbus.log ( DEBUG3, "luavbus:connect(): sending PASS" )
  132. self:sendCommand( "PASS ".. self.password )
  133. incoming = self:getResponse()
  134. if ( incoming == nil ) then
  135. luavbus.log ( WARN, "luavbus:connect(): error getting answer from device to command PASS" )
  136. return nil
  137. end
  138. incoming = incoming:sub(1, -2)
  139.  
  140. luavbus.log ( DEBUG3, "luavbus:connect(): waiting for +OK" )
  141. if ( incoming ~= "+OK: Password accepted" ) then
  142. luavbus.log ( 1, "Error in incoming string: ".. incoming)
  143. end
  144. luavbus.log ( DEBUG3, string.format ( "luavbus:connect(): received %s ", incoming ) )
  145.  
  146. self:sendCommand("DATA")
  147.  
  148. return self.socket
  149.  
  150. end
  151.  
  152. --- Get the response from device
  153. -- @author Luca Palazzo
  154. -- @return a string containing thre response or nil
  155. --
  156. function luavbus:getResponse ()
  157. luavbus.log ( DEBUG3, string.format ( "luavbus:getResponse()" ) )
  158. retries = 0
  159. repeat
  160. incoming, error, partial = self.socket:receive (512);
  161. if ( incoming == nil) then
  162. luavbus.log ( DEBUG1, string.format ( "luavbus:getResponse(): incoming nil (%s)", tostring(incoming) ) )
  163. end
  164. if ( error == 'timeout' and partial ) then
  165. luavbus.log ( DEBUG3, "luavbus:getResponse(): partial string." )
  166. incoming = partial
  167. break
  168. end
  169. retries = retries + 1
  170. if ( retries > 3 ) then
  171. break
  172. end
  173. until incoming ~= nil
  174. return incoming
  175. end
  176.  
  177. --- Sends data to the device
  178. -- @author Luca Palazzo
  179. -- @return the socket or nil on failure
  180. --
  181. function luavbus:sendCommand ( command )
  182. res = self.socket:send( command )
  183. return res
  184. end
  185.  
  186. --- Waits for data from device until timeout. In case of filter it search for only the filter
  187. -- @author Luca Palazzo
  188. -- @param filter a table containing the filters in form of "filter = { srcAddress = 0x7e11, dstAddress = 0x0010 }", nil in case of no filter
  189. -- @param timeout the time to wait to return the packtes received and mateching di filter
  190. -- @return a table containing received packets
  191. --
  192. function luavbus:waitData ( filter, timeout )
  193. local i = 0
  194. local datacount = 0
  195.  
  196. if ( timeout == nil ) then
  197. luavbus.log ( DEBUG2, string.format ( "luavbus:waitData(): using default timeout of 5s" ) )
  198. timeout = 5
  199. end
  200. self:readDevicesProfiles()
  201. local found = false
  202. local tries = 0
  203. local packets = {}
  204. -- while 1 do
  205. start_time = os.time()
  206. luavbus.log ( DEBUG2, string.format ( "luavbus:waitData(): start_time %d", start_time ) )
  207.  
  208. repeat
  209. -- posix.sleep(1)
  210. if ( ( os.time() - start_time ) > timeout ) then
  211. luavbus.log ( DEBUG2, string.format ( "luavbus:waitData(): timed out" ) )
  212. return packets
  213. end
  214.  
  215. incoming = self:getResponse()
  216. -- client:receive (buffersize);
  217. if ( incoming == nil ) then
  218. luavbus.log ( DEBUG2, string.format ( "luavbus:waitData(): nil incoming data, socket %s", self.socket ) )
  219. socket.select(nil, nil, 1)
  220. tries = tries + 1
  221. if ( tries > 3) then
  222. luavbus.log ( DEBUG2, "luavbus:waitData(): retries exhausted exiting")
  223. return packets
  224. end
  225. else
  226.  
  227.  
  228. tries = 0
  229. i = i + 1
  230. if datacount > 1 then
  231. break
  232. end
  233.  
  234. luavbus.log ( DEBUG1, string.format ( "Incoming dump: %s ", self.packetdump(incoming) ) )
  235. vbus_frames = self:packetExtract(incoming)
  236. if ( vbus_frames ~= nil and type(vbus_frames) == "table" ) then
  237. luavbus.log ( DEBUG2, string.format ( "luavbus:waitData(): multi packets %d", #vbus_frames ) )
  238. for k,vbus_frame in pairs(vbus_frames) do
  239. luavbus.log ( DEBUG2, string.format ( "luavbus:waitData(): vbus_frame dump %s", self:packetdump(vbus_frame) ) )
  240. vbus_packet = self:packetParse(vbus_frame)
  241. luavbus.log ( DEBUG2, string.format ( "luavbus:waitData(): vbus_packet %s", vbus_packet ) )
  242.  
  243. end
  244.  
  245. local found = 0
  246. if ( vbus_packet ~= nil) then
  247. for k,v in pairs(self.profiles) do
  248. luavbus.log ( DEBUG2, string.format( "Comparing with profile ID %s Name %s ", v.id, v.name) )
  249. if vbus_packet.srcAddress == tonumber(v.id) and vbus_packet.dstAddress == 0x10 then
  250. luavbus.log ( DEBUG2, string.format( "Found ID %s Name %s ", v.id, v.name) )
  251. definition = v
  252. found = true
  253. vbus_packet.profile = v
  254. vbus_packet:getProfileData()
  255. break
  256. end
  257. end
  258.  
  259. if ( filter == nil or type(filter) ~= "table") then
  260. luavbus.log ( DEBUG2, string.format ( "luavbus:waitData(): returning any VBus packets" ) )
  261. luavbus.log ( DEBUG2, string.format ( "Packet source 0x%04x destination 0x%04x command 0x%04x payloads %d checksum 0x%02x", vbus_packet.srcAddress, vbus_packet.dstAddress, vbus_packet.command, vbus_packet.payloads, vbus_packet.checksum ) )
  262. luavbus.log ( DEBUG2, tostring(vbus_packet) )
  263. table.insert(packets, vbus_packet)
  264.  
  265. -- debug ( 5, string.format ( "Packet source %s destination %s command %s payloads %s checksum %s", tostring(vbus_packet.srcAddress), tostring(vbus_packet.dstAddress), tostring(vbus_packet.command), tostring(vbus_packet.payloads), tostring(vbus_packet.checksum) ) )
  266.  
  267. else
  268. local filtercount = 0
  269. local filtermask = 0
  270. local mask = 0
  271. for _ in pairs(filter) do filtercount = filtercount + 1 end
  272. luavbus.log ( 5, string.format ( "luavbus:waitData(): returning packets filtered. Number of filters %d", filtercount ) )
  273. if ( filter.srcAddress ~= nil ) then
  274. filtermask = bit32.bor(filtermask, 1)
  275. if ( vbus_packet.srcAddress == filter.srcAddress ) then
  276. luavbus.log ( 5, string.format ( "luavbus:waitData(): filter by srcAddress found 0x%04x", filter.srcAddress ) )
  277. mask = bit32.bor( mask, 1 )
  278. end
  279. end
  280. if ( filter.dstAddress ~= nil ) then
  281. filtermask = bit32.bor(filtermask, 2)
  282. if ( vbus_packet.dstAddress == filter.dstAddress ) then
  283. luavbus.log ( DEBUG2, string.format ( "luavbus:waitData(): filter by dstAddress found 0x%04x", filter.dstAddress ) )
  284. mask = bit32.bor( mask, 2 )
  285. end
  286. end
  287. if ( filter.command ~= nil ) then
  288. filtermask = bit32.bor(filtermask, 4)
  289. if ( vbus_packet.command == filter.command ) then
  290. luavbus.log ( DEBUG2, string.format ( "luavbus:waitData(): filter by command found 0x%04x", filter.command ) )
  291. mask = bit32.bor( mask, 4 )
  292. end
  293. end
  294. if ( mask == filtermask ) then
  295. luavbus.log ( DEBUG2, string.format ( "luavbus:waitData(): packet matchs all filters' condition (%d == %d)", mask, filtermask ) )
  296. table.insert(packets, vbus_packet)
  297. return packets
  298. else
  299. luavbus.log ( DEBUG2, string.format ( "luavbus:waitData(): packet didn't match all filters' condition (%d != %d)", mask, filtermask ) )
  300.  
  301. end
  302. end
  303. end
  304. end
  305. end
  306. -- end
  307. until stop == true
  308. return nil
  309. end
  310.  
  311. function luavbus:packetExtract ( data )
  312. luavbus.log ( DEBUG2, "luavbus:packetExtract(data)" )
  313. local buffer_length
  314. local index
  315. local sync_index
  316. local sync_found = 0
  317. -- local vbus_packet
  318.  
  319. if ( data == nil ) then
  320. return nil
  321. end
  322.  
  323. self.buffer = self.buffer..data
  324. buffer = self.buffer
  325. buffer_length = string.len(buffer)
  326. vbus_packet = nil
  327. vbus_packets = {}
  328.  
  329. index = 1
  330. repeat
  331. -- for index = 1, length, 1 do
  332. luavbus.log ( DEBUG4, string.format("Index %d length %d ",index, buffer_length))
  333. luavbus.log ( DEBUG4, string.format("Byte[%d]=0x%02x ",index, buffer:byte(index)))
  334. if buffer:byte(index) == 0xaa then
  335. luavbus.log ( DEBUG3, "Found sync byte at "..index )
  336.  
  337. if sync_found == 1 then
  338.  
  339. destination = buffer:byte(sync_index+2)+buffer:byte(sync_index+3)*256
  340. source = buffer:byte(sync_index+4)+buffer:byte(sync_index+5)*256
  341. command = buffer:byte(sync_index+6)+buffer:byte(sync_index+6)*256
  342. payloads = buffer:byte(sync_index+8)
  343. checksum = buffer:byte(sync_index+9)
  344.  
  345.  
  346. if ( payloads ~= nil ) then
  347. luavbus.log ( DEBUG1, string.format ( "First sync already found at %d. Expected payload %d", sync_index, payloads ) )
  348. end
  349. expected_length = payloads*6+9
  350. if ( buffer_length < expected_length) then
  351. luavbus.log ( DEBUG2, string.format ( "Buffer doesn't contain yet the entire packet %d. Expected size %d", buffer_length, expected_length ) )
  352. break
  353. end
  354. -- luavbus.log ( DEBUG3, "First sync already found. This is the end of packet. The packet starts from " .. sync_index .. " and ends at " .. index )
  355. -- vbus_packet = buffer:sub(sync_index,index-1)
  356. vbus_packet = buffer:sub(sync_index,sync_index+expected_length)
  357. table.insert(vbus_packets, vbus_packet)
  358. buffer = buffer:sub( index, string.len(buffer) )
  359. luavbus.log (DEBUG4, "Packet start "..sync_index.." end "..index-1 )
  360. luavbus.log (DEBUG3, string.format ( "luavbus:packetExtract(): vbus_packet dump %s", self.packetdump(vbus_packet) ) )
  361. sync_found = 0
  362. buffer_length = buffer_length - index + 1
  363. index = 0
  364.  
  365.  
  366.  
  367. luavbus.log ( DEBUG2, string.format ( "Buffer %s, payload %s", self.packetdump(buffer), tostring(payloads) ) )
  368.  
  369. header_length = 9
  370. buffer_length = string.len(buffer)
  371.  
  372.  
  373. if ( buffer_length < 9 ) then
  374. luavbus.log ( DEBUG2, string.format ( "Buffer (%d) is shorter than minimum frame size, needs to read some more bytes", buffer_length ) )
  375. break
  376.  
  377. else
  378.  
  379. payloads = buffer:byte(sync_index+8)
  380.  
  381. expected_packet_length = payloads*6+header_length-sync_index
  382. luavbus.log ( DEBUG2, string.format ( "Sync found at position %d, payload %d, buffer length %d ", index, payloads, buffer_length ) )
  383. if ( expected_packet_length > buffer:len() ) then
  384. luavbus.log ( DEBUG2, string.format ( "Buffer does not contain the entire packet (buffer length: %d, expected packet length: %d", buffer_length, expected_packet_length ) )
  385. elseif ( expected_packet_length == buffer:len() ) then
  386. luavbus.log ( DEBUG2, string.format ( "Buffer contains exactly the entire packer(buffer length: %d, expected packet length: %d", buffer_length, expected_packet_length ) )
  387. elseif ( expected_packet_length < buffer:len() ) then
  388. luavbus.log ( DEBUG2, string.format ( "Buffer contains something more the entire packer(buffer length: %d, expected packet length: %d", buffer_length, expected_packet_length ) )
  389. end
  390. end
  391. else
  392. if ( index ~= 1 ) then
  393. luavbus.log ( DEBUG3, "First sync not at position 1, cutting trailing garbage up to index " .. index-1 )
  394. buffer = buffer:sub( index, buffer_length )
  395. buffer_length = buffer_length - index + 1
  396. index = 0
  397. sync_index = 1
  398. else
  399. sync_index = index
  400. sync_found = 1
  401. end
  402. end
  403. end
  404. -- end
  405. index = index + 1
  406. until index == buffer_length
  407. self.buffer = buffer
  408. luavbus.log ( DEBUG2, string.format ( "luavbus:packetExtract(): remaining buffer %s", self.packetdump(buffer) ) )
  409. for k,v in pairs(vbus_packets) do
  410. luavbus.log ( DEBUG1, string.format ( "luavbus:packetExtract(): vbus_packet %d dump %s", k, self.packetdump(v) ) )
  411. end
  412. luavbus.log ( DEBUG1, string.format ( "luavbus:packetExtract(): returning %d packets", #vbus_packets ) )
  413.  
  414. return vbus_packets
  415. end
  416.  
  417. function luavbus:packetHandle ( packet )
  418. luavbus.log ( 5, string.format ( "luavbus:packetHandle ( packet ): handling packet %s", self:packetdump(vbus_packet) ) )
  419. -- print "VBusPacketHandl(packet)"
  420. local version;
  421.  
  422. version = packet:byte(6)
  423.  
  424. luavbus.log ( DEBUG1, string.format ("Packet version 0x%02x length %d", version, string.len(packet) ) )
  425.  
  426. if version == 0x10 then
  427. self:packetHandleV1(packet)
  428. elseif version == 0x20 then
  429. self:packetHandleV2(packet)
  430. else
  431. luavbus.log ( WARN, string.format ("Unknown packet version 0x%02x", version ) )
  432. end
  433.  
  434. end
  435.  
  436. function luavbus:adjustSeptett ( data )
  437. local length
  438. local septett
  439. local data_converted = ""
  440. length = string.len(data)
  441. septett = data:byte(length-1)
  442. checksum = data:byte(length)
  443.  
  444.  
  445. if ( string.len(data) == 0 ) then
  446. luavbus.log ( ERR, "Error packet length 0" )
  447. return data
  448. end
  449. for index = 0, length-3, 1 do
  450. local shifted = bit32.lshift(1, index)
  451. -- print ( "Shifted "..shifted)
  452. if ( ( bit32.band( septett, shifted) ) > 0 ) then
  453. local orred = bit32.bor( data:byte(index+1), 0x80)
  454. -- io.write ( string.format ( "Orred 0x%02x 0x%02x", orred, data:byte(index+1) ) )
  455. data_converted = data_converted..string.char(orred)
  456. else
  457. data_converted = data_converted..string.char(data:byte(index+1))
  458. end
  459. end
  460. -- PacketDump(data_converted)
  461.  
  462. return data_converted
  463. end
  464.  
  465.  
  466.  
  467.  
  468. function luavbus:packetHandleV1 ( packet )
  469. luavbus.log ( DEBUG3, "luavbus:packetHandleV1 ( packet ): starting" )
  470. local destination
  471. local source
  472.  
  473. destination = packet:byte(2)+packet:byte(3)*256
  474. source = packet:byte(4)+packet:byte(5)*256
  475. command = packet:byte(7)+packet:byte(8)*256
  476. payloads = packet:byte(9)
  477. checksum = packet:byte(10)
  478.  
  479. luavbus.log ( DEBUG2, string.format ("Packet source 0x%04x destination 0x%04x command 0x%04x payloads %d checksum 0x%02x", source, destination, command, payloads, checksum ) )
  480.  
  481. calc_checksum = self:packetCalculateChecksum(packet:sub(2,9))
  482. if checksum ~= calc_checksum then
  483. luavbus.log ( ERR, string.format ( "luavbus:packetHandleV1: Error wrong checksum (frame 0x%02x!=0x%02x calced)", checksum, calc_checksum ) )
  484. luavbus.log ( DEBUG1, string.format ( "luavbus:packetHandleV1: Dump: %s", self.packetdump(packet) ) )
  485. end
  486.  
  487. self:readDevicesProfiles()
  488. local found = false
  489. for k,v in pairs(self.profiles) do
  490. if source == tonumber(v.id) then
  491. -- print ( "Found ID "..v.id.." Name "..v.name)
  492. definition = v
  493. found = true
  494. break
  495. end
  496.  
  497. end
  498.  
  499. if not found then
  500. print ( "Unable to get device profiles" )
  501. return
  502. else
  503. if destination == 0x10 then
  504. self:packetV1Parse(packet, definition.objects )
  505. else
  506. print ( "Useless packet" )
  507. end
  508. end
  509.  
  510. -- if source == 0x7e11 and destination == 0x0010 then
  511. -- VBusPacketHandleMXData(packet)
  512. -- end
  513. return
  514.  
  515. end
  516.  
  517. function luavbus:packetParse ( packet )
  518. luavbus.log ( DEBUG2, string.format ( "luavbus:packetParse ( packet ): parsing packet %s", self:packetdump(vbus_packet) ) )
  519. -- print "VBusPacketHandl(packet)"
  520. local version;
  521.  
  522. if ( packet == nil) then
  523. return nil
  524. end
  525. version = packet:byte(6)
  526.  
  527. luavbus.log ( DEBUG2, string.format ("luavbus:packetParse(): Packet version 0x%02x length %d", version, string.len(packet) ) )
  528.  
  529. if version == 0x10 then
  530. return self:packetParseV1(packet)
  531. elseif version == 0x20 then
  532. return self:packetParseV2(packet)
  533. else
  534. luavbus.log ( DEBUG2, string.format ("luavbus:packetParse(): Unknown packet version 0x%02x", version ) )
  535. end
  536. return nil
  537. end
  538.  
  539. function luavbus:packetParseV1 ( packet )
  540. luavbus.log ( DEBUG3, "luavbus:packetParseV1 ( packet )" )
  541.  
  542. vbp = luavbuspacket.new()
  543. luavbus.log (DEBUG4, string.format ( "luavbus:packetParseV1(): luavbuspacket address %s ", vbp ) )
  544.  
  545. -- mt = getmetatable ( vpb )
  546. -- mt.__tostring = nil
  547. -- setmetatable ( vpb, mt )
  548. -- vbp.__tostring = nil
  549. luavbus.log ( DEBUG3, string.format( "luavbus:packetParseV1 ( packet ): packet address %s ", vbp ) )
  550. -- vbp.__tostring = luavbuspacket.tostring
  551.  
  552. vbp.dstAddress = packet:byte(2)+packet:byte(3)*256
  553. vbp.srcAddress = packet:byte(4)+packet:byte(5)*256
  554. vbp.version = packet:byte(6)
  555. vbp.command = packet:byte(7)+packet:byte(8)*256
  556. vbp.payloads = packet:byte(9)
  557. vbp.checksum = packet:byte(10)
  558. vbp.payloadsData = ""
  559.  
  560. local data = ""
  561. payloads = packet:byte(9)
  562. checksum = packet:byte(10)
  563. self.datacount = self.datacount + 1
  564.  
  565. calc_checksum = self:packetCalculateChecksum(packet:sub(2,9))
  566. if checksum ~= calc_checksum then
  567. luavbus.log ( WARN, string.format ( "luavbus:packetParseV1(): Error wrong checksum (frame 0x%02x!=0x%02x calced)", checksum, calc_checksum ) )
  568. luavbus.log ( DEBUG1, string.format ( "luavbus:packetParseV1(): Dump: %s", self.packetdump(packet) ) )
  569. return nil
  570. end
  571. luavbus.log ( DEBUG3, string.format("luavbus:packetParseV1(): payloads: %d", payloads) )
  572. for payload_index = 0, payloads-1, 1 do
  573. local offset = 11+(payload_index*6);
  574. local payload = packet:sub(offset,offset+5)
  575. local septett = packet:byte(offset+4)
  576. local checksum = packet:byte(offset+5)
  577.  
  578. calc_checksum = self:packetCalculateChecksum(payload:sub(1,string.len(payload)-1))
  579.  
  580. if checksum ~= calc_checksum then
  581. if ( checksum == nil or calc_checksum == nil ) then
  582. luavbus.log ( WARN, string.format ( "luavbus:packetParseV1(): payload %d: septett error wrong checksum (%s!=0x%02x)", payload_index, tostring(checksum), tostring(calc_checksum) ) )
  583. end
  584.  
  585. luavbus.log ( DEBUG1, string.format ( "luavbus:packetParseV1(): payload %d: septett Error wrong checksum (frame 0x%02x!=0x%02x calced)", payload_index, checksum, calc_checksum ) )
  586. luavbus.log ( DEBUG1, string.format ( "Packet dump: %s", luavbus.packetdump(packet) ) )
  587. luavbus.log ( DEBUG1, string.format ( "Payload %d dump: %s", payload_index, luavbus.packetdump(payload) ) )
  588.  
  589. return nil
  590. end
  591.  
  592. if ( septett ~= 0x00 ) then
  593. luavbus.log ( DEBUG3, "Payload need to be adjusted with septett" )
  594. payload = self:adjustSeptett ( payload )
  595. else
  596. payload = payload:sub (1,4)
  597. end
  598. data = data..payload
  599. -- table.insert ( vbp.payloadsData, payload )
  600. vbp.payloadsData = vbp.payloadsData..payload
  601. end
  602. local printed = 0
  603.  
  604. -- debug ( 5, string.format ( "Packet source %s destination %s command %s payloads %s checksum %s", tostring(vbp.srcAddress), tostring(vbp.dstAddress), tostring(vbp.command), tostring(vbp.payloads), tostring(vbp.checksum) ) )
  605.  
  606. return vbp
  607. end
  608.  
  609. function luavbus:packetParseV2 ( packet )
  610. luavbus.log ( 5, "luavbus:packetParseV2 ( packet )" )
  611. local destination
  612. local source
  613. local command
  614. local payloads
  615. local checksum
  616.  
  617. if ( packet:len() < 10 ) then
  618. print ( "Wrong packet length"..packet:len())
  619. end
  620.  
  621. destination = packet:byte(2)+packet:byte(3)*256
  622. source = packet:byte(4)+packet:byte(5)*256
  623. command = packet:byte(7)+packet:byte(8)*256
  624. payloads = packet:byte(9)
  625. checksum = packet:byte(10)
  626. calc_checksum = self:packetCalculateChecksum(packet:sub(2,16))
  627. if checksum ~= calc_checksum then
  628. luavbus.log ( 5, string.format ( "luavbus:packetParseV2(): V2 Error wrong checksum (0x%02x!=0x%02x)", checksum, calc_checksum ) )
  629. self.packetdump(packet)
  630. end
  631. luavbus.log ( DEBUG3, string.format ("luavbus:packetParseV2(): Packet source 0x%04x destination 0x%04x command 0x%04x payloads %d checksum 0x%02x", source, destination, command, payloads, checksum ) )
  632. return
  633.  
  634. end
  635.  
  636. function luavbus:packetHandleV2 ( packet )
  637. luavbus.log ( 1, "luavbus:HandleV2 ( packet )" )
  638. local destination
  639. local source
  640. local command
  641. local payloads
  642. local checksum
  643.  
  644. if ( packet:len() < 10 ) then
  645. luavbus.log ( WARN, "Wrong packet length"..packet:len())
  646. end
  647.  
  648. destination = packet:byte(2)+packet:byte(3)*256
  649. source = packet:byte(4)+packet:byte(5)*256
  650. command = packet:byte(7)+packet:byte(8)*256
  651. payloads = packet:byte(9)
  652. checksum = packet:byte(10)
  653. calc_checksum = self:packetCalculateChecksum(packet:sub(2,16))
  654. if checksum ~= calc_checksum then
  655. luavbus.log ( DEBUG2, string.format ( "V2 Error wrong checksum (0x%02x!=0x%02x)", checksum, calc_checksum ) )
  656. self.packetdump(packet)
  657. end
  658. luavbus.log ( DEBUG2, string.format ("Packet source 0x%04x destination 0x%04x command 0x%04x payloads %d checksum 0x%02x", source, destination, command, payloads, checksum ) )
  659. return
  660.  
  661. end
  662.  
  663. function luavbus:readDevicesProfiles ( profiles_dir )
  664. if ( profiles_dir ~= nil ) then
  665. for profile_file in lfs.dir(profiles_dir) do
  666. if lfs.attributes(profiles_dir..profile_file,"mode") == "file" then
  667. if extension == "json" then
  668. print ( "File: "..path..filename )
  669. end
  670. device_profile = io.open(profiles_dir.."/"..profile_file, "rb")
  671. content = device_profile:read("*all")
  672. -- print ( "Content:" .. content )
  673. content = v
  674. decoded = cjson.decode(content)
  675. for k, v in pairs( decoded ) do
  676. print(k, v)
  677. end
  678. table.insert(self.profiles,decoded)
  679. device_profile:close()
  680. end
  681. end
  682. else
  683. for k,vbusprofile in pairs(vbusprofiles) do
  684. content = vbusprofile
  685. decoded = cjson.decode(content)
  686.  
  687. table.insert(self.profiles,decoded)
  688. end
  689. end
  690. end
  691.  
  692. function luavbus:packetCalculateChecksum ( data )
  693. local index
  694. local crc = 0x7f
  695.  
  696. -- print "VBusPacketChecksum(data)"
  697. for index = 1, string.len(data), 1 do
  698. crc = bit32.band( crc - data:byte(index),0x7f)
  699. -- print ( "CRC " .. crc )
  700. end
  701.  
  702. luavbus.log ( DEBUG2, string.format ( "luavbus:packetCalculateChecksum ( data ): checksum 0x%02x",crc ) )
  703. if crc == nil then
  704. luavbus.log ( WARN, "Error calculating checksum" )
  705. crc = nil
  706. end
  707. return crc
  708. end
  709.  
  710. function luavbus.packetdump(packet)
  711. local length
  712. local hexdump
  713. local dump_string = ""
  714. if ( packet == nil ) then
  715. dump_string = "luavbus.packetdump(): nil packet"
  716. return dump_string
  717. end
  718.  
  719. if ( type(packet) == "table" ) then
  720. luavbus.log ( DEBUG4, "luavbus.packetdump(): table packet " .. #packet)
  721. dump_string = "luavbus.packetdump(): table packet"
  722. dump_string = dump_string .. " length " .. #packet .. " "
  723. -- for index = 1, #packet, 1 do
  724. for i, v in ipairs(packet) do
  725. -- dump_string = dump_string .. string.format( '0x%02x ', packet[index]:byte( 1 ), packet[index]:byte( 1 ) )
  726. luavbus.log ( DEBUG4, "luavbus.packetdump(): index " .. i .. " value " .. v:byte())
  727. dump_string = dump_string .. string.format( '0x%02x ', v:byte( 1 ) )
  728.  
  729. end
  730.  
  731. return dump_string
  732. end
  733.  
  734. length = string.len ( packet )
  735.  
  736. luavbus.log ( DEBUG4, "luavbus.packetdump(): packet "..packet)
  737. dump_string = dump_string .. " length " .. length .. " "
  738. -- print ( "Packet length: "..length.."" )
  739. for i = 1, length, 1 do
  740. dump_string = dump_string .. string.format('0x%02X ',string.byte(packet,i) )
  741. end
  742. luavbus.log ( DEBUG4, dump_string )
  743. return dump_string
  744.  
  745.  
  746. end
  747.  
  748. ERR, WARN, INFO, DEBUG1, DEBUG2, DEBUG3, DEBUG4, DEBUG5 = 1, 2, 3, 4, 5, 6, 7, 8
  749.  
  750. function luavbus.log ( level, ... )
  751. local prefix = ""
  752. local log_string = ""
  753. if ( log_level ~= nil and level ~= nil and level <= log_level ) then
  754. if ( level == ERR ) then
  755. log_string = "ERROR"
  756. elseif ( level == WARN ) then
  757. log_string = "WARN"
  758. elseif ( level == INFO ) then
  759. log_string = "INFO"
  760. elseif ( level == DEBUG1 ) then
  761. log_string = "DEBUG1"
  762. elseif ( level == DEBUG2 ) then
  763. log_string = "DEBUG2"
  764. elseif ( level == DEBUG3 ) then
  765. log_string = "DEBUG3"
  766. elseif ( level == DEBUG4 ) then
  767. log_string = "DEBUG4"
  768. elseif ( level == DEBUG5 ) then
  769. log_string = "DEBUG5"
  770. else
  771. log_string = "UNKNOWN"
  772. end
  773. print ( log_string .. ": " .. ... )
  774. end
  775. end
  776.  
  777. return luavbus

vbusprofiles.lua you define your device profiles as you wish

Source code    
  1. vbusprofiles = {}
  2.  
  3. vbusprofiles[0] =
  4. '{ \
  5. "id": "0x7E11", \
  6. "name": "DeltaSol MX [Controller]", \
  7. "objects": [ \
  8. [ 0, 2, 0, "Temperature sensor 1", 0.1, "В°C" ], \
  9. [ 2, 2, 0, "Temperature sensor 2", 0.1, "В°C" ], \
  10. [ 4, 2, 0, "Temperature sensor 3", 0.1, "В°C" ], \
  11. [ 6, 2, 0, "Temperature sensor 4", 0.1, "В°C" ], \
  12. [ 8, 2, 0, "Temperature sensor 5", 0.1, "В°C" ], \
  13. [ 10, 2, 0, "Temperature sensor 6", 0.1, "В°C" ], \
  14. [ 12, 2, 0, "Temperature sensor 7", 0.1, "В°C" ], \
  15. [ 14, 2, 0, "Temperature sensor 8", 0.1, "В°C" ], \
  16. [ 16, 2, 0, "Temperature sensor 9", 0.1, "В°C" ], \
  17. [ 18, 2, 0, "Temperature sensor 10", 0.1, "В°C" ], \
  18. [ 20, 2, 0, "Temperature sensor 11", 0.1, "В°C" ], \
  19. [ 22, 2, 0, "Temperature sensor 12", 0.1, "В°C" ], \
  20. [ 24, 2, 0, "Temperature sensor 13", 0.1, "В°C" ], \
  21. [ 26, 2, 0, "Temperature sensor 14", 0.1, "В°C" ], \
  22. [ 28, 2, 0, "Temperature sensor 15", 0.1, "В°C" ], \
  23. [ 30, 2, 0, "Irridiation sensor 16", 1, "W/mВІ" ], \
  24. [ 32, 2, 0, "Temperature sensor 17", 0.1, "В°C" ], \
  25. [ 34, 2, 0, "Temperature sensor 18", 0.1, "В°C" ], \
  26. [ 36, 2, 0, "Temperature sensor 19", 0.1, "В°C" ], \
  27. [ 38, 2, 0, "Temperature sensor 20", 0.1, "В°C" ], \
  28. [ 40, 4, 0, "Volume flow rate sensor 13", 1, "l/h" ], \
  29. [ 44, 4, 0, "Volume flow rate sensor 14", 1, "l/h" ], \
  30. [ 48, 4, 0, "Volume flow rate sensor 15", 1, "l/h" ], \
  31. [ 52, 4, 0, "Volume flow rate sensor 17", 1, "l/h" ], \
  32. [ 56, 4, 0, "Volume flow rate sensor 18", 1, "l/h" ], \
  33. [ 60, 4, 0, "Volume flow rate sensor 19", 1, "l/h" ], \
  34. [ 64, 4, 0, "Volume flow rate sensor 20", 1, "l/h" ], \
  35. [ 68, 2, 0, "Pressure sensor 17", 0.01, "bar" ], \
  36. [ 70, 2, 0, "Pressure sensor 18", 0.01, "bar" ], \
  37. [ 72, 2, 0, "Pressure sensor 19", 0.01, "bar" ], \
  38. [ 74, 2, 0, "Pressure sensor 20", 0.01, "bar" ], \
  39. [ 76, 1, 0, "Pump speed relay 1", 1, "%" ], \
  40. [ 77, 1, 0, "Pump speed relay 2", 1, "%" ], \
  41. [ 78, 1, 0, "Pump speed relay 3", 1, "%" ], \
  42. [ 79, 1, 0, "Pump speed relay 4", 1, "%" ], \
  43. [ 80, 1, 0, "Pump speed relay 5", 1, "%" ], \
  44. [ 81, 1, 0, "Pump speed relay 6", 1, "%" ], \
  45. [ 82, 1, 0, "Pump speed relay 7", 1, "%" ], \
  46. [ 83, 1, 0, "Pump speed relay 8", 1, "%" ], \
  47. [ 84, 1, 0, "Pump speed relay 9", 1, "%" ], \
  48. [ 85, 1, 0, "Pump speed relay 10", 1, "%" ], \
  49. [ 86, 1, 0, "Pump speed relay 11", 1, "%" ], \
  50. [ 87, 1, 0, "Pump speed relay 12", 1, "%" ], \
  51. [ 88, 1, 0, "Pump speed relay 13", 1, "%" ], \
  52. [ 89, 1, 0, "Pump speed relay 14", 1, "%" ], \
  53. [ 90, 2, 0, "Unknown", 1 ], \
  54. [ 92, 4, 0, "System date", 1 ], \
  55. [ 96, 4, 0, "Error mask", 1 ] \
  56. ] \
  57. }'
  58.  
  59. return vbusprofiles

Resident program

Source code    
  1. for _, searcher in ipairs(package.searchers or package.loaders) do
  2. name = "luavbus"
  3. local loader = searcher(name)
  4. if type(loader) == 'function' then
  5. package.preload[name] = loader
  6. vbus = require (name)
  7. break
  8. end
  9. name = "user.luavbus"
  10. local loader = searcher(name)
  11. if type(loader) == 'function' then
  12. package.preload[name] = loader
  13. vbus = require (name)
  14. break
  15. end
  16. end
  17.  
  18. log_level = WARN
  19. if ( vbus == nil ) then
  20. log ( "VBus modules not available")
  21.  
  22. end
  23. vbus.new(nil, TCPIP, "vbus" )
  24.  
  25. vbus.remote_host = "192.168.168.19"
  26. vbus.remote_port = "7053"
  27. vbus.password = "vbus"
  28.  
  29. vbus:connect()
  30.  
  31. -- filter = { srcAddress = 0x7e11, dstAddress = 0x0010 }
  32. -- filter = { command = 0x200 }
  33.  
  34. packets = vbus:waitData(filter, 30 )
  35.  
  36. if (packets ~= nil ) then
  37. log( string.format ( "Returned %d packets", #packets ) )
  38. for key, packet in pairs(packets) do
  39. -- print ( string.format ( "Packet: %s", packet ) )
  40. -- print ( packet:profileprint() )
  41. if ( packet.profileData ~= nil) then
  42. for k, data in pairs ( packet.profileData ) do
  43. if ( data.factor < 1 ) then
  44. log( string.format ( "Name %s Value %.02f %s", data.name, data.value, data.unit ) )
  45. else
  46. log( string.format ( "Name %s Value %d %s", data.name, data.value, data.unit ) )
  47. end
  48. end
  49.  
  50. end
  51. end
  52. else
  53. log( "No packet!!!")
  54. end

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

 
Created by Luca Palazzo