====== Helvar light control communication protocol ====== Please refer to the [[https://aca.im/driver_docs/Helvar/HelvarNet-Overview.pdf|HelvarNet communication protocol]] for usage. This protocol is using ASCII messaging. According to the documentation the routers in the lighting system support either ASCII or RAW format. **Message Format** Any message sent to, or received from, a router can be in either ASCII or raw binary form (see Command Format for more information). Messages must not exceed the maximum length of 1500 bytes. The format of the data contained within messages is defined by the protocol. A query reply message from the router will be in the same format as the query command message sent i.e. if a query message is sent in ASCII form then the reply will also be in ASCII. The usage of this protocol is as follows: *Your application logic, e.g. recipe selection boxes on the dashboards chooses scenario id and writes it to the register *Lua script monitors scenes ids written and converts them to the strings according to the HelvarNet protocol and application logic Please be informed this protocol relays your HelvarNet string to the TCP address:port. It does not provide your application logic. Prepare proper dictionaries, templates or mapping rules to compose messages in your application. For example, in the command Recall Group 1234, Block 5, Scene 6, Fade Time 32 s, the string is sent as follows, including the delimiters and the start character '>' and stop character '#': ">V:1,C:11,G:1234,B:5,S:6,F:3200#" *So the string like ">V:1,C:11,G:1234,B:5,S:6,F:3200#" should be written to the register named CMD0 in the project. *Once the string is written to the CMD0 register, it is sent to the remote device to the TCP port assigned in the connection settings. *The response (which can be multipart) is stored in the same register -- delimiters PARTIAL_ETX = '$' FULL_ETX = '#' function onScanStart () now = os.time() end function createDevices () addDevice{name = "CMD", shift = 0, base = 10, xtraFields = {} } end function readRegister (reg, device, unitId) if (not readBuffer) then readBuffer = 'no data' end return readBuffer end function writeRegister (reg, device, unitId, newValue) sendString_(newValue) -- expects string to be written externally local buf = {} local tryCount = 0 repeat tryCount = tryCount + 1 ; DEBUG('tryCount = ' .. tryCount) local currentFrame = readUntil(PARTIAL_ETX, FULL_ETX) if currentFrame then table.insert(buf, currentFrame) lastChar = currentFrame[#currentFrame] end until (lastChar == FULL_ETX or tryCount > 3) if (lastChar == FULL_ETX) then readBuffer = buf return true end readBuffer = 'read bad data!' return true end -------------------------- Helpers -------------------------- function sendString_(s) -- new sendString version local sendBuf = {} for i = 1, #s do table.insert(sendBuf, s : byte(i) ) end sendBytes(sendBuf) end function readUntil(endByte1, endByte2) -- read input buffer untils endByte(s) or timeout DEBUG("Entered readUntil.") local ONE_BYTE = 1 local buf, rb = {}, nil repeat rb = readBytes(ONE_BYTE) if rb then rb = rb[ONE_BYTE] ; DEBUG('read byte: ' .. string.char(rb)) table.insert(buf, rb) end until (not rb or (rb == endByte1 or rb == endByte2)) DEBUG("Exit on rb = " .. tostring(rb) ) DEBUG('buf = ' .. table.concat(buf, ' ')) if (#buf > 0) and (buf[#buf] == endByte1 or buf[#buf] == endByte2) then DEBUG('Got right frame : ') return buf else DEBUG('Got wrong frame : ') ; return false end end