-------------------------------------------------------------------------------
-- Copyright (c) 2012 Sierra Wireless and others.
-- All rights reserved. This program and the accompanying materials
-- are made available under the terms of the Eclipse Public License v1.0
-- which accompanies this distribution, and is available at
-- http://www.eclipse.org/legal/epl-v10.html
--
-- Contributors:
--     Julien Desgats     for Sierra Wireless - initial API and implementation
-------------------------------------------------------------------------------

require'pack'
local u = require 'unittest'
local c = string.char

local t = u.newtestsuite("Modbus serializer")
local core

function t :setup()
    core = require 'modbus.serializer'
end

function t :test_commands()
    local cxt = u.assert(core.initContext('RTU'))
    local body, resplen
    -- commands are the exemples from MODBUS APPLICATION PROTOCOL SPECIFICATION v1.1b
    -- with 0x02 as slave address and appropriate CRC
    body, resplen = cxt:readCoils(2, 0x13, 19)
    u.assert_equal(c(0x02,0x01,0x00,0x13,0x00,0x13,0x8c,0x31), body)
    u.assert_equal(8, resplen)
    u.assert_equal(c(0xcd,0x6b,0x05), cxt:receiveResponse(c(0x02,0x01,0x03,0xcd,0x6b,0x05,0x42,0xb1)))

    body, resplen = cxt:readDiscreteInputs(2, 0xc4, 0x16)
    u.assert_equal(c(0x02,0x02,0x00,0xc4,0x00,0x16,0xb8,0x0a), body)
    u.assert_equal(8, resplen)
    u.assert_equal(c(0xac,0xdb,0x35), cxt:receiveResponse(c(0x02,0x02,0x03,0xac,0xdb,0x35,0x22,0xbb)))

    -- response are converted to native endinanness
    body, resplen = cxt:readHoldingRegisters(2, 0x6b, 3)
    u.assert_equal(c(0x02,0x03,0x00,0x6b,0x00,0x03,0x74,0x24), body)
    u.assert_equal(11, resplen)
    u.assert_equal(string.pack('HHH',555,0,100), cxt:receiveResponse(c(0x02,0x03,0x06,0x02,0x2b,0x00,0x00,0x00,0x64,0x11,0x8a)))

    body, resplen = cxt:readInputRegisters(2, 8, 1)
    u.assert_equal(c(0x02,0x04,0x00,0x08,0x00,0x01,0xb0,0x3b), body)
    u.assert_equal(7, resplen)
    u.assert_equal(string.pack('H',10), cxt:receiveResponse(c(0x02,0x04,0x02,0x00,0x0a,0x7d,0x37)))

    -- TODO: test that values other than 0xff00 and 0x0000 are rejected
    body, resplen = cxt:writeSingleCoil(2, 0xac, true)
    u.assert_equal(c(0x02,0x05,0x00,0xac,0xff,0x00,0x4c,0x28), body)
    u.assert_equal(8, resplen)
    u.assert_equal('ok', cxt:receiveResponse(c(0x02,0x05,0x00,0xac,0xff,0x00,0x4c,0x28)))

    body, resplen = cxt:writeSingleRegister(2, 1, 3)
    u.assert_equal(c(0x02,0x06,0x00,0x01,0x00,0x03,0x98,0x38), body)
    u.assert_equal(8, resplen)
    u.assert_equal('ok', cxt:receiveResponse(c(0x02,0x06,0x00,0x01,0x00,0x03,0x98,0x38)))

    body, resplen = cxt:writeMultipleCoils(2, 0x13, 10, c(0xcd,0x01))
    u.assert_equal(c(0x02,0x0f,0x00,0x13,0x00,0x0a,0x02,0xcd,0x01,0x66,0x3b), body)
    u.assert_equal(8, resplen)
    u.assert_equal('ok', cxt:receiveResponse(c(0x02,0x0f,0x00,0x13,0x00,0x0a,0x24,0x3a)))

    body, resplen = cxt:writeMultipleRegisters(2, 1, string.pack('HH',10,258))
    u.assert_equal(c(0x02,0x10,0x00,0x01,0x00,0x02,0x04,0x00,0x0a,0x01,0x02,0x9d,0x74), body)
    u.assert_equal(8, resplen)
    u.assert_equal('ok', cxt:receiveResponse(c(0x02,0x10,0x00,0x01,0x00,0x02,0x10,0x3b)))

    body, resplen = cxt:sendRawData(2, c(1,2,3,4))
    u.assert_equal(c(0x02,0x5a,0x01,0x02,0x03,0x04,0xf8,0xfb), body)
    u.assert_equal(256, resplen) -- maximum frame size
    -- CRC is still in the returned buffer (which makes the result non portable across protocols), is it the expected behavior ?
    u.assert_equal(c(0x02,0x5a,0x00,0x01,0xe0,0x4f), cxt:receiveResponse(c(0x02,0x5a,0x00,0x01,0xe0,0x4f)))

    -- custom query (read coils)
    body, resplen = cxt:customRequest(0x02, 0x01, c(0x00,0x13,0x00,0x13))
    u.assert_equal(c(0x02,0x01,0x00,0x13,0x00,0x13,0x8c,0x31), body)
    u.assert_equal(256, resplen) -- maximum frame size
    u.assert_equal(c(0x02,0x01,0x03,0xcd,0x6b,0x05,0x42,0xb1), cxt:receiveResponse(c(0x02,0x01,0x03,0xcd,0x6b,0x05,0x42,0xb1)))
end

function t :test_other_modes()
    local body, resplen

    local cxt = u.assert(core.initContext('ASCII'))
    -- prepared request
    body, resplen = cxt:readCoils(2, 0x13, 19)
    u.assert_equal(":020100130013D7\r\n", body)
    u.assert_equal(17, resplen)
    u.assert_equal(c(0xcd,0x6b,0x05), cxt:receiveResponse(":020103CD6B05BD\r\n"))

    -- custom request
    body, resplen = cxt:customRequest(0x02, 0x01, c(0x00,0x13,0x00,0x13))
    u.assert_equal(":020100130013D7\r\n", body)
    u.assert_equal(514, resplen)
    u.assert_equal(c(0x02,0x01,0x03,0xcd,0x6b,0x05,0xbd), cxt:receiveResponse(":020103CD6B05BD\r\n"))

    cxt = u.assert(core.initContext('TCP'))
    body, resplen = cxt:readCoils(2, 0x13, 19)
    u.assert_equal(c(0x00,0x01,0x00,0x00,0x00,0x06,0x02,0x01,0x00,0x13,0x00,0x13), body)
    u.assert_equal(12, resplen)
    u.assert_equal(c(0xcd,0x6b,0x05), cxt:receiveResponse(c(0x00,0x01,0x00,0x00,0x00,0x06,0x02,0x01,0x03,0xcd,0x6b,0x05)))

    -- custom request (also, frame id is incremented)
    body, resplen = cxt:customRequest(0x02, 0x01, c(0x00,0x13,0x00,0x13))
    u.assert_equal(c(0x00,0x02,0x00,0x00,0x00,0x06,0x02,0x01,0x00,0x13,0x00,0x13), body)
    u.assert_equal(260, resplen)
    u.assert_equal(c(0x02,0x01,0x03,0xcd,0x6b,0x05), cxt:receiveResponse(c(0x00,0x02,0x00,0x00,0x00,0x06,0x02,0x01,0x03,0xcd,0x6b,0x05)))
end

function t :test_error()
    local cxt = u.assert(core.initContext('RTU'))

    -- wrong slave id
    u.assert(cxt:readCoils(2, 0x13, 19))
    u.assert_nil(cxt:receiveResponse(c(0x03,0x01,0x03,0xcd,0x6b,0x05,0x42,0xb1)))

    -- wrong function code
    u.assert(cxt:readCoils(2, 0x13, 19))
    u.assert_nil(cxt:receiveResponse(c(0x02,0x02,0x03,0xcd,0x6b,0x05,0x42,0xb1)))

    -- wrong function code (for a custom request)
    u.assert(cxt:customRequest(0x02, 0x01, c(0x00,0x13,0x00,0x13)))
    u.assert_nil(cxt:receiveResponse(c(0x02,0x02,0x03,0xcd,0x6b,0x05,0x42,0xb1)))

    -- error flag
    u.assert(cxt:readCoils(2, 0x13, 19))
    u.assert_nil(cxt:receiveResponse(c(0x02,0x81,02)))

    -- CRC error
    u.assert(cxt:readCoils(2, 0x13, 19))
    u.assert_nil(cxt:receiveResponse(c(0x02,0x01,0x03,0xcd,0x6b,0x05,0x00,0x00)))

    -- wrong frame id
    cxt = u.assert(core.initContext('TCP'))
    u.assert(cxt:readCoils(2, 0x13, 19))
    u.assert_nil(cxt:receiveResponse(c(0x00,0xff,0x00,0x00,0x00,0x06,0x02,0x01,0x03,0xcd,0x6b,0x05)))
end
