====== 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