diff --git a/.gitignore b/.gitignore deleted file mode 100644 index d62ddbb..0000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.pdf -*.txt diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b59b0fd --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +MIT License + +Copyright 2017 Electric Imp + +SPDX-License-Identifier: MIT + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/Modbus485Master/.gitignore b/Modbus485Master/.gitignore deleted file mode 100644 index 6a0eacc..0000000 --- a/Modbus485Master/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -**/.build_api_key.json -build/* -builds/* -**/.imptest -agent.nut -device.nut -settings.wrench -lib/* diff --git a/Modbus485Master/example/example.device.nut b/Modbus485Master/example/example.device.nut deleted file mode 100644 index 8a53fbf..0000000 --- a/Modbus485Master/example/example.device.nut +++ /dev/null @@ -1,30 +0,0 @@ -#require "CRC16.class.nut:1.0.0" -#require "ModbusRTU.class.nut:1.0.0" -#require "ModbusMaster.class.nut:1.0.0" -#require "Modbus485Master.class.nut:1.0.0" - -// this example demonstrates how to write and read values into/from holding registers -const DEVICE_ADDRESS = 0x01; -// instantiate the the Modbus485Master object -modbus <- Modbus485Master(hardware.uart2, hardware.pinL); - -// write values into 3 holding registers starting at address 9 -modbus.write(DEVICE_ADDRESS, MODBUSRTU_TARGET_TYPE.HOLDING_REGISTER, 9, 3, [188, 80, 18], function(error, res) { - if (error) { - server.error(error); - } else { - // read values from 3 holding registers starting at address 9 - modbus.read(DEVICE_ADDRESS, MODBUSRTU_TARGET_TYPE.HOLDING_REGISTER, 9, 3, function(error, res) { - if (error) { - server.error(error); - } else { - // 188 - server.log(res[0]); - // 80 - server.log(res[1]); - // 18 - server.log(res[2]); - } - }); - } -}); diff --git a/Modbus485Slave/example/device.example.nut b/Modbus485Slave/example/device.example.nut deleted file mode 100644 index bcbdd63..0000000 --- a/Modbus485Slave/example/device.example.nut +++ /dev/null @@ -1,29 +0,0 @@ -#require "CRC16.class.nut:1.0.0" -#require "ModbusSlave.class.nut:1.0.0" -#require "Modbus485Slave.class.nut:1.0.0" - -modbus <- Modbus485Slave(hardware.uart2, hardware.pinL, 1, { debug = true }); - -modbus.onError(function(error) { - server.error(error); -}); - -// a holding register read example -modbus.onRead(function(slaveID, functionCode, startingAddress, quantity) { - server.log("slaveID : " + slaveID); - server.log("functionCode : " + functionCode); - server.log("startingAddress : " + startingAddress); - server.log("Quantity : " + quantity); - return [18, 29, 30, 59, 47]; -}.bindenv(this)); - -modbus.onWrite(function(slaveID, functionCode, startingAddress, quantity, values) { - server.log("slaveID : " + slaveID); - server.log("functionCode : " + functionCode); - server.log("startingAddress : " + startingAddress); - server.log("Quantity : " + quantity); - server.log("Values : \n"); - foreach (index, value in values) { - server.log("\t" + index + " : " + value); - } -}.bindenv(this)); diff --git a/ModbusMaster/ModbusMaster.class.nut b/ModbusMaster/ModbusMaster.device.lib.nut similarity index 91% rename from ModbusMaster/ModbusMaster.class.nut rename to ModbusMaster/ModbusMaster.device.lib.nut index 1e333db..3dcbec1 100644 --- a/ModbusMaster/ModbusMaster.class.nut +++ b/ModbusMaster/ModbusMaster.device.lib.nut @@ -1,9 +1,30 @@ -// Copyright (c) 2017 Electric Imp -// This file is licensed under the MIT License -// http://opensource.org/licenses/MIT +// MIT License +// +// Copyright 2017-19 Electric Imp +// Copyright 2020-23 KORE Wireless +// +// SPDX-License-Identifier: MIT +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. class ModbusMaster { - static VERSION = "1.0.0"; + static VERSION = "1.0.2"; _debug = null; // diff --git a/ModbusMaster/README.md b/ModbusMaster/README.md index 3997dad..e53bd67 100644 --- a/ModbusMaster/README.md +++ b/ModbusMaster/README.md @@ -5,9 +5,9 @@ This class is the abstract class for the following classes, it should NOT be ins **Please proceed to one of the following libraries** -# [Modbus485Master](../Modbus485Master/) +# [ModbusSerialMaster](../ModbusSerialMaster/) -This library allows an imp to communicate with other devices via the Modbus-RS485 protocol. +This library allows an imp to communicate with other devices via Modbus-RS485 or Modbus-RS232 protocol. # [ModbusTCPMaster](../ModbusTCPMaster/) @@ -15,4 +15,4 @@ This library enables an imp to communicate with other devices via TCP/IP . # License -The ModbusRTUMaster library is licensed under the [MIT License](https://github.com/electricimp/thethingsapi/tree/master/LICENSE). +The ModbusRTUMaster library is licensed under the [MIT License](../LICENSE). diff --git a/ModbusRTU/.gitignore b/ModbusRTU/.gitignore deleted file mode 100644 index 2b513e8..0000000 --- a/ModbusRTU/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -**/.build_api_key.json -build/* -builds/* -**/.imptest -agent.nut -device.nut -settings.wrench diff --git a/ModbusRTU/.imptest b/ModbusRTU/.imptest new file mode 100644 index 0000000..30f3a03 --- /dev/null +++ b/ModbusRTU/.imptest @@ -0,0 +1,14 @@ +{ + "modelId": "KB2yibqKCKCh" /* I */, + "devices": [ + "5000d8c46a56cdae" /* I */ + ], + "agentFile": false, + "deviceFile": "ModbusRTU.device.lib.nut", + "stopOnFailure": false, + "timeout": 10, + "tests": [ + "*.test.nut", + "tests/**/*.test.nut" + ] +} \ No newline at end of file diff --git a/ModbusRTU/ModbusRTU.class.nut b/ModbusRTU/ModbusRTU.device.lib.nut similarity index 83% rename from ModbusRTU/ModbusRTU.class.nut rename to ModbusRTU/ModbusRTU.device.lib.nut index 9d135d0..6c1eb6f 100644 --- a/ModbusRTU/ModbusRTU.class.nut +++ b/ModbusRTU/ModbusRTU.device.lib.nut @@ -1,6 +1,27 @@ -// Copyright (c) 2017 Electric Imp -// This file is licensed under the MIT License -// http://opensource.org/licenses/MIT +// MIT License +// +// Copyright 2017-19 Electric Imp +// Copyright 2020-23 KORE Wireless +// +// SPDX-License-Identifier: MIT +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. enum MODBUSRTU_SUB_FUNCTION_CODE { RETURN_QUERY_DATA = 0x0000, @@ -64,7 +85,7 @@ enum MODBUSRTU_OBJECT_ID { } class ModbusRTU { - static VERSION = "1.0.0"; + static VERSION = "1.0.2"; // resLen and reqLen are the length of the PDU static FUNCTION_CODES = { readCoils = { @@ -245,8 +266,26 @@ class ModbusRTU { // function to create PDU for read // static function createReadPDU(targetType, startingAddress, quantity) { - local PDU = blob(targetType.reqLen); - PDU.writen(targetType.fcode, 'b'); + local pduType; + switch(targetType) { + case MODBUSRTU_TARGET_TYPE.COIL: + pduType = FUNCTION_CODES.read_coils; + break; + case MODBUSRTU_TARGET_TYPE.DISCRETE_INPUT: + pduType = FUNCTION_CODES.readInputs; + break; + case MODBUSRTU_TARGET_TYPE.INPUT_REGISTER: + pduType = FUNCTION_CODES.readInputRegs; + break; + case MODBUSRTU_TARGET_TYPE.HOLDING_REGISTER: + pduType = FUNCTION_CODES.readHoldingRegs; + break; + default: + throw "Incorrect targetType specified"; + } + + local PDU = blob(pduType.reqLen); + PDU.writen(pduType.fcode, 'b'); PDU.writen(swap2(startingAddress), 'w'); PDU.writen(swap2(quantity), 'w'); return PDU; @@ -256,8 +295,20 @@ class ModbusRTU { // function to create PDU for write // static function createWritePDU(targetType, startingAddress, numBytes, quantity, values) { + local pduType; + switch(targetType) { + case MODBUSRTU_TARGET_TYPE.COIL: + pduType = FUNCTION_CODES.writeSingleCoil; + break; + case MODBUSRTU_TARGET_TYPE.HOLDING_REGISTER: + pduType = FUNCTION_CODES.writeSingleReg; + break; + default: + throw "Incorrect targetType specified"; + } + local PDU = blob(); - PDU.writen(targetType.fcode, 'b'); + PDU.writen(pduType.fcode, 'b'); PDU.writen(swap2(startingAddress), 'w'); if (quantity > 1) { PDU.writen(swap2(quantity), 'w'); diff --git a/ModbusRTU/README.md b/ModbusRTU/README.md index 790219a..a0b0458 100644 --- a/ModbusRTU/README.md +++ b/ModbusRTU/README.md @@ -2,11 +2,18 @@ This library creates and parses Modbus Protocol Data Units (PDU). It depends on Electric Imp's [CRC16 library](https://github.com/electricimp/CRC16) to calculate the [CRC-16](https://en.wikipedia.org/wiki/Cyclic_redundancy_check) value of a string or blob. +**Note** You will not usually work with this library directly, but load it as a dependency for one of our other Modbus libraries, which target specific use cases: + +* [ModbusSerialMaster](../ModbusMaster) +* [ModbusTCPMaster](../ModbusSerialMaster) + +We recommend you work with one of these libraries unless your use case very specifically needs to perform PDU operations not provided by them. + **To use this library, add the following statements to the top of your device code:** ``` #require "CRC16.class.nut:1.0.0" -#require "ModbusRTU.class.nut:1.0.0" +#require "ModbusRTU.device.lib.nut:1.0.1" ``` ## ModbusRTU Class Usage @@ -23,12 +30,12 @@ This method creates a PDU for *readData* operations. It takes the following para | *startingAddress* | Integer | Yes | N/A | The address from which it begins reading values | | *quantity* | Integer | Yes | N/A | The number of consecutive addresses the values are read from | -| Type | Value | Access | -| --- | --- | --- | -| Coil | *MODBUSRTU_TARGET_TYPE.COIL* | Read-Write | -| Discrete Input | *MODBUSRTU_TARGET_TYPE.DISCRETE_INPUT* | Read Only | -| Input Register | *MODBUSRTU_TARGET_TYPE.INPUT_REGISTER* | Read Only | -| Holding Register | *MODBUSRTU_TARGET_TYPE.HOLDING_REGISTER* | Read-Write | +| Type | Value | +| --- | --- | +| Coil | *MODBUSRTU_TARGET_TYPE.COIL* | +| Discrete Input | *MODBUSRTU_TARGET_TYPE.DISCRETE_INPUT* | +| Input Register | *MODBUSRTU_TARGET_TYPE.INPUT_REGISTER* | +| Holding Register | *MODBUSRTU_TARGET_TYPE.HOLDING_REGISTER* | #### Example @@ -52,6 +59,11 @@ This method creates a PDU for *writeData* operations. It takes the following par | *quantity* | Integer | Yes | N/A | The number of consecutive addresses the values are written into | | *values* | Integer, array ([integer, boolean]), boolean, blob | Yes | N/A | The values written into Coils or Registers | +| Type | Value | +| --- | --- | +| Coil | *MODBUSRTU_TARGET_TYPE.COIL* | +| Holding Register | *MODBUSRTU_TARGET_TYPE.HOLDING_REGISTER* | + #### Example ```squirrel @@ -230,4 +242,4 @@ local result = parse({ ## License -The ModbusRTU library is licensed under the [MIT License](https://github.com/electricimp/Modbus/tree/master/LICENSE). +The ModbusRTU library is licensed under the [MIT License](../LICENSE). diff --git a/ModbusRTU/lib/CRC16.class.nut b/ModbusRTU/lib/CRC16.class.nut deleted file mode 100644 index 18aedff..0000000 --- a/ModbusRTU/lib/CRC16.class.nut +++ /dev/null @@ -1,33 +0,0 @@ -const CRC16_LOOKUP_LOW = "\x00\xC0\xC1\x01\xC3\x03\x02\xC2\xC6\x06\x07\xC7\x05\xC5\xC4\x04\xCC\x0C\x0D\xCD\x0F\xCF\xCE\x0E\x0A\xCA\xCB\x0B\xC9\x09\x08\xC8\xD8\x18\x19\xD9\x1B\xDB\xDA\x1A\x1E\xDE\xDF\x1F\xDD\x1D\x1C\xDC\x14\xD4\xD5\x15\xD7\x17\x16\xD6\xD2\x12\x13\xD3\x11\xD1\xD0\x10\xF0\x30\x31\xF1\x33\xF3\xF2\x32\x36\xF6\xF7\x37\xF5\x35\x34\xF4\x3C\xFC\xFD\x3D\xFF\x3F\x3E\xFE\xFA\x3A\x3B\xFB\x39\xF9\xF8\x38\x28\xE8\xE9\x29\xEB\x2B\x2A\xEA\xEE\x2E\x2F\xEF\x2D\xED\xEC\x2C\xE4\x24\x25\xE5\x27\xE7\xE6\x26\x22\xE2\xE3\x23\xE1\x21\x20\xE0\xA0\x60\x61\xA1\x63\xA3\xA2\x62\x66\xA6\xA7\x67\xA5\x65\x64\xA4\x6C\xAC\xAD\x6D\xAF\x6F\x6E\xAE\xAA\x6A\x6B\xAB\x69\xA9\xA8\x68\x78\xB8\xB9\x79\xBB\x7B\x7A\xBA\xBE\x7E\x7F\xBF\x7D\xBD\xBC\x7C\xB4\x74\x75\xB5\x77\xB7\xB6\x76\x72\xB2\xB3\x73\xB1\x71\x70\xB0\x50\x90\x91\x51\x93\x53\x52\x92\x96\x56\x57\x97\x55\x95\x94\x54\x9C\x5C\x5D\x9D\x5F\x9F\x9E\x5E\x5A\x9A\x9B\x5B\x99\x59\x58\x98\x88\x48\x49\x89\x4B\x8B\x8A\x4A\x4E\x8E\x8F\x4F\x8D\x4D\x4C\x8C\x44\x84\x85\x45\x87\x47\x46\x86\x82\x42\x43\x83\x41\x81\x80\x40"; -const CRC16_LOOKUP_HIGH = "\x00\xC1\x81\x40\x01\xC0\x80\x41\x01\xC0\x80\x41\x00\xC1\x81\x40\x01\xC0\x80\x41\x00\xC1\x81\x40\x00\xC1\x81\x40\x01\xC0\x80\x41\x01\xC0\x80\x41\x00\xC1\x81\x40\x00\xC1\x81\x40\x01\xC0\x80\x41\x00\xC1\x81\x40\x01\xC0\x80\x41\x01\xC0\x80\x41\x00\xC1\x81\x40\x01\xC0\x80\x41\x00\xC1\x81\x40\x00\xC1\x81\x40\x01\xC0\x80\x41\x00\xC1\x81\x40\x01\xC0\x80\x41\x01\xC0\x80\x41\x00\xC1\x81\x40\x00\xC1\x81\x40\x01\xC0\x80\x41\x01\xC0\x80\x41\x00\xC1\x81\x40\x01\xC0\x80\x41\x00\xC1\x81\x40\x00\xC1\x81\x40\x01\xC0\x80\x41\x01\xC0\x80\x41\x00\xC1\x81\x40\x00\xC1\x81\x40\x01\xC0\x80\x41\x00\xC1\x81\x40\x01\xC0\x80\x41\x01\xC0\x80\x41\x00\xC1\x81\x40\x00\xC1\x81\x40\x01\xC0\x80\x41\x01\xC0\x80\x41\x00\xC1\x81\x40\x01\xC0\x80\x41\x00\xC1\x81\x40\x00\xC1\x81\x40\x01\xC0\x80\x41\x00\xC1\x81\x40\x01\xC0\x80\x41\x01\xC0\x80\x41\x00\xC1\x81\x40\x01\xC0\x80\x41\x00\xC1\x81\x40\x00\xC1\x81\x40\x01\xC0\x80\x41\x01\xC0\x80\x41\x00\xC1\x81\x40\x00\xC1\x81\x40\x01\xC0\x80\x41\x00\xC1\x81\x40\x01\xC0\x80\x41\x01\xC0\x80\x41\x00\xC1\x81\x40"; - -class CRC16 { - static version = [1,0,0]; - static defaultInitValue = 0xFFFF; - - // Calculate the CRC16 of data [string or blob] for a given range - static function calculate(data, start = null, end = null, initValue = null) { - //Start is inclusive - if(start == null) start = 0; - - // End is exclusive - if(end == null) end = data.len(); - - // Check if we should use a non-default value - if(initValue == null) initValue = defaultInitValue; - - // index is a convenience varaiable - local index; - local lo = initValue & 0xFF; - local hi = (initValue >> 8 ) & 0xFF; - - // Loop through the data - for(local i = start; i < end; i++) { - index = lo ^ data[i]; - lo = hi ^ CRC16_LOOKUP_HIGH[index]; - hi = CRC16_LOOKUP_LOW[index]; - } - - return (hi << 8) | lo; - } -} diff --git a/ModbusRTU/tests/device.test.nut b/ModbusRTU/tests/device.test.nut index b4e17e0..1f6bc2d 100644 --- a/ModbusRTU/tests/device.test.nut +++ b/ModbusRTU/tests/device.test.nut @@ -1,7 +1,36 @@ +// MIT License +// +// Copyright 2017 Electric Imp +// +// SPDX-License-Identifier: MIT +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +// ----------------------------------------------------------------------------- + +// This test can run on any Imp. + +@include "github:electricimp/CRC16/CRC16.class.nut"; + const MINIMUM_RESPONSE_LENGTH = 5; const DEVICE_ADDRESS = 0x01; - function parse (fakeBuffer, params){ local length = fakeBuffer.len(); if (length < MINIMUM_RESPONSE_LENGTH){ @@ -16,7 +45,6 @@ function parse (fakeBuffer, params){ return result; } - class DeviceTestCase extends ImpTestCase { function setUp() { diff --git a/ModbusSerialMaster/.imptest b/ModbusSerialMaster/.imptest new file mode 100644 index 0000000..4ec53c2 --- /dev/null +++ b/ModbusSerialMaster/.imptest @@ -0,0 +1,14 @@ +{ + "modelId": "KB2yibqKCKCh" /* I */, + "devices": [ + "5000d8c46a56cdae" /* I */ + ], + "agentFile": false, + "deviceFile": false, + "stopOnFailure": false, + "timeout": 10, + "tests": [ + "*.test.nut", + "tests/**/*.test.nut" + ] +} \ No newline at end of file diff --git a/Modbus485Master/Modbus485Master.class.nut b/ModbusSerialMaster/ModbusSerialMaster.device.lib.nut similarity index 88% rename from Modbus485Master/Modbus485Master.class.nut rename to ModbusSerialMaster/ModbusSerialMaster.device.lib.nut index 9fd59b5..e108e9b 100644 --- a/Modbus485Master/Modbus485Master.class.nut +++ b/ModbusSerialMaster/ModbusSerialMaster.device.lib.nut @@ -1,9 +1,33 @@ -// Copyright (c) 2017 Electric Imp -// This file is licensed under the MIT License -// http://opensource.org/licenses/MIT +// MIT License +// +// Copyright 2017-19 Electric Imp +// Copyright 2020-23 KORE Wireless +// +// SPDX-License-Identifier: MIT +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. -class Modbus485Master extends ModbusMaster { +class ModbusSerialMaster extends ModbusMaster { + + static VERSION = "2.0.1"; static MINIMUM_RESPONSE_LENGTH = 5; + _uart = null; _rts = null; _timeout = null; @@ -29,7 +53,7 @@ class Modbus485Master extends ModbusMaster { // @item {float} timeout - 1.0 second by default // @item {bool} debug - false by default. If enabled, the outgoing and incoming ADU will be printed for debugging purpose // - constructor(uart, rts, params = {}) { + constructor(uart, rts = null, params = {}) { base.constructor(("debug" in params) ? params.debug : false); if (!("CRC16" in getroottable())) { throw "Must include CRC16 library v1.0.0+"; @@ -44,10 +68,14 @@ class Modbus485Master extends ModbusMaster { _timeout = ("timeout" in params) ? params.timeout : 1.0; _receiveBuffer = blob(); _uart = uart; - _rts = rts; _queue = []; - _uart.configure(baudRate, dataBits, parity, stopBits, NO_CTSRTS, _uartCallback.bindenv(this)); - _rts.configure(DIGITAL_OUT, 0); + if (rts != null) { + _rts = rts; + _uart.configure(baudRate, dataBits, parity, stopBits, NO_CTSRTS, _uartCallback.bindenv(this)); + _rts.configure(DIGITAL_OUT, 0); + } else { + _uart.configure(baudRate, dataBits, parity, stopBits, 0x00, _uartCallback.bindenv(this)); + } } // @@ -314,13 +342,18 @@ class Modbus485Master extends ModbusMaster { } _callback = properties.callback; local frame = _createADU(PDU); - local rw = _rts.write.bindenv(_rts); local uw = _uart.write.bindenv(_uart); local uf = _uart.flush.bindenv(_uart); - rw(1); - uw(frame); - uf(); - rw(0); + if (_rts != null) { + local rw = _rts.write.bindenv(_rts); + rw(1); + uw(frame); + uf(); + rw(0); + } else { + uw(frame); + uf(); + } _log(frame, "Outgoing ADU : "); _responseTimer = _responseTimeoutFactory(_timeout); } diff --git a/Modbus485Master/README.md b/ModbusSerialMaster/README.md similarity index 62% rename from Modbus485Master/README.md rename to ModbusSerialMaster/README.md index 707dfb7..2cfeb44 100644 --- a/Modbus485Master/README.md +++ b/ModbusSerialMaster/README.md @@ -1,56 +1,56 @@ -# Modbus485Master +# ModbusSerialMaster # -This library allows an imp to communicate with other devices via the Modbus-RS485 protocol. +This library allows an imp to communicate with other devices via the Modbus-RS485 or Modbus-RS232 protocol. **To use this library, add the following statements to the top of your device code:** ``` #require "CRC16.class.nut:1.0.0" -#require "ModbusRTU.class.nut:1.0.0" -#require "ModbusMaster.class.nut:1.0.0" -#require "Modbus485Master.class.nut:1.0.0" +#require "ModbusRTU.device.lib.nut:1.0.1" +#require "ModbusMaster.device.lib.nut:1.0.1" +#require "ModbusSerialMaster.device.lib.nut:2.0.0" ``` -## Hardware Setup +## Hardware Setup ## -The following instructions are applicable to Electric Imp’s [impAccelerator™ Fieldbus Gateway](https://electricimp.com/docs/hardware/resources/reference-designs/fieldbusgateway/). +The following instructions are applicable to Electric Imp’s [impAccelerator™ Fieldbus Gateway](https://developer.electricimp.com/hardware/resources/reference-designs/fieldbusgateway). 1. Connect the antenna to the Fieldbus Gateway 2. Wire RS485 A on the Fieldbus Gateway to port A / positive(+) on the other device 3. Wire RS485 B on the Fieldbus Gateway to port B / negative(-) on the other device 4. Wire both devices’ ground ports together -5. Fit [jumper J2](https://electricimp.com/docs/hardware/resources/reference-designs/fieldbusgateway/#rs-485) on the Fieldbus Gateway motherboard to enable RS485 +5. Fit [jumper J2](https://developer.electricimp.com/hardware/resources/reference-designs/fieldbusgateway#rs-485) on the Fieldbus Gateway motherboard to enable RS485 6. Power up the Fieldbus Gateway 7. Configure the Fieldbus Gateway for Internet access using BlinkUp™ -## Modbus485Master Class Usage +## ModbusSerialMaster Class Usage ## This is the main library class. It implements most of the functions listed in the [Modbus specification](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf). -### Constructor: Modbus485Master(*uart, rts[, params]*) +### Constructor: ModbusSerialMaster(*uart[, rts][, params]*) ### -Instantiate a new Modbus485Master object and set the configuration of the UART bus over which it operates. The parameters *uart* and *rts* are, respectively, the imp UART in use and an imp GPIO pin which will be used to control flow. The *params* parameter is optional and takes a table containing the following keys: +Instantiates a new ModbusSerialMaster object and configures the UART bus over which it operates. The *uart* parameter is an imp UART object. The optional *rts* parameter should be used for RS485 communications when you are using an imp GPIO pin for control flow. The *params* parameter is optional and takes a table containing the following keys: -| Key | Default | Notes | -| ------ | ----------- | ------------------------------------------------------------------------------- | -| baudRate | 19200 | The baud rate of the UART connection | -| dataBits | 8 | The word size on the UART connection in bits (7 or 8 bits) | -| parity | *PARITY_NONE* | Parity configuration of the UART connection | -| stopBits | 1 | Number of stop bits (1 or 2) on the UART connection | -| timeout | 1.0 | The maximum time allowed for one request | -| debug | `false` | If enabled, the outgoing and incoming ADU will be printed for debugging purpose | +| Key | Default | Notes | +| --- | --- | --- | +| *baudRate* | 19200 | The baud rate of the UART connection | +| *dataBits* | 8 | The word size on the UART connection in bits (7 or 8 bits) | +| *parity* | *PARITY_NONE* | Parity configuration of the UART connection | +| *stopBits* | 1 | Number of stop bits (1 or 2) on the UART connection | +| *timeout* | 1.0 | The maximum time allowed for one request | +| *debug* | `false` | If enabled, the outgoing and incoming ADU will be printed for debugging purpose | -#### Example +#### Example #### ```squirrel -modbus <- Modbus485Master(hardware.uart2, hardware.pinL); +modbus <- ModbusSerialMaster(hardware.uart2, hardware.pinL); ``` -## Modbus485Master Class Methods +## ModbusSerialMaster Class Methods ## -### read(*deviceAddress, targetType, startingAddress, quantity[, callback]*) +### read(*deviceAddress, targetType, startingAddress, quantity[, callback]*) ### -Function Code : 01, 02, 03, 04 +Function Codes: 01, 02, 03, 04 This is a generic method used to read values from a single coil, register, or multiple coils and registers. It takes the following parameters: @@ -60,7 +60,7 @@ This is a generic method used to read values from a single coil, register, or mu | *targetType* | Constant | Yes | N/A | Refer to the ‘Target Type’ table, below | | *startingAddress* | Integer | Yes | N/A | The address from which it begins reading values | | *quantity* | Integer | Yes | N/A | The number of consecutive addresses the values are read from | -| *callback* | Function | No | Null | The function to be fired when it receives response regarding this request. It takes two parameters, *error* and *result* | +| *callback* | Function | No | `null` | The function to be fired when it receives response regarding this request. It takes two parameters, *error* and *result* | | Target Type | Value | Access | | --- | --- | --- | @@ -69,37 +69,37 @@ This is a generic method used to read values from a single coil, register, or mu | Input Register | *MODBUSRTU_TARGET_TYPE.INPUT_REGISTER* | Read-Only | | Holding Register | *MODBUSRTU_TARGET_TYPE.HOLDING_REGISTER* | Read-Write | -#### Example +#### Example #### ```squirrel // Read from a single coil -modbus.read(0x01, MODBUSRTU_TARGET_TYPE.DISCRETE_INPUT, 0x01, 1, function(error, result) { - if (error) { - server.error(error); - } else { - server.log(result); - } +modbus.read(0x01, MODBUSRTU_TARGET_TYPE.COIL, 0x0001, 1, function(error, result) { + if (error) { + server.error(error); + } else { + server.log(result); + } }.bindenv(this)); // Read from multiple registers -modbus.read(0x01, MODBUSRTU_TARGET_TYPE.INPUT_REGISTER, 0x01 , 5, function(error, results) { - if (error) { - server.error(error); - } else { - foreach(key, value in results) { - server.log(key + " : " + value); - } +modbus.read(0x01, MODBUSRTU_TARGET_TYPE.INPUT_REGISTER, 0x7000 , 2, function(error, results) { + if (error) { + server.error(error); + } else { + foreach(key, value in results) { + server.log(key + " : " + value); } + } }.bindenv(this)); ``` -### write(*deviceAddress, targetType, startingAddress, quantity, values[, callback]*) +### write(*deviceAddress, targetType, startingAddress, quantity, values[, callback]*) ### -Function Code : 05, 06, 15, 16 +Function Codes: 05, 06, 15, 16 This is a generic method used to write values to multiple coils and registers. It takes the following parameters: -#### Parameters +#### Parameters #### | Parameter | Data Type | Required | Default Value | Description | | --- | --- | --- | --- | --- | @@ -107,59 +107,57 @@ This is a generic method used to write values to multiple coils and registers. I | *targetType* | Constant | Yes | N/A | Refer to the ‘Target Type’ table, above | | *startingAddress* | Integer | Yes | N/A | The address from which it begins writing values | | *quantity* | Integer | Yes | N/A | The number of consecutive addresses the values are written into | -| *values* | Integer, array of integers, bool, Blob | Yes | N/A | The values written into Coils or Registers. Please view Notes below | -| *callback* | Function | No | Null | The function to be fired when it receives response regarding this request. It takes two parameters, *error* and *result* | +| *values* | Integer, array of integers, bool, blob | Yes | N/A | The values written into Coils or Registers. Please view Notes below | +| *callback* | Function | No | `null` | The function to be fired when it receives response regarding this request. It takes two parameters, *error* and *result* | -#### Notes +#### Notes #### 1. Integer, blob and array[integer] are applicable to *MODBUSRTU_TARGET_TYPE.HOLDING_REGISTER*. Use an array[integer] only applicable when *quantity* is greater than one. 2. Integer, bool, blob and array[integer, bool] are applicable to *MODBUSRTU_TARGET_TYPE.COIL*. Use array[integer, bool] only applicable when *quantity* is greater than one. The integer value set to coils can be either 0x0000 or 0xFF00. Other values are ignored. -#### Example +#### Example #### ```squirrel // Write to a single coil -modbus.write(0x01, MODBUSRTU_TARGET_TYPE.COIL, 0x01, 1, true, function(error, result) { - if (error) { - server.error(error); - } else { - server.log(result); - } +modbus.write(0x01, MODBUSRTU_TARGET_TYPE.COIL, 8192, 1, true, function(error, result) { + if (error) { + server.error(error); + } else { + server.log(result); + } }.bindenv(this)); // Write to multiple registers -modbus.write(0x01, MODBUSRTU_TARGET_TYPE.HOLDING_REGISTER, 0x01, 5, [false, true, false, true, true], function(error, results) { - if (error) { - server.error(error); - } else { - foreach(key, value in results) { - server.log(key + " : " + value); - } - } +modbus.write(0x01, MODBUSRTU_TARGET_TYPE.HOLDING_REGISTER, 9, 3, [188, 80, 18], function(error, result) { + if (error) { + server.error(error); + } else { + server.log(result); + } }.bindenv(this)); ``` -### readExceptionStatus(*deviceAddress[, callback]*) +### readExceptionStatus(*deviceAddress[, callback]*) ### -Function Code : 07 +Function Code: 07 This method reads the contents of eight Exception Status outputs in a remote device (address passed into the first parameter. If a callback is supplied, it will be triggered when a response regarding this request is received. The callback takes two parameters: *error* and *result*. -#### Example +#### Example #### ```squirrel modbus.readExceptionStatus(0x01, function(error, result) { - if (error) { - server.error(error); - } else { - server.log(result); - } + if (error) { + server.error(error); + } else { + server.log(result); + } }.bindenv(this)); ``` -### diagnostics(*deviceAddress, subFunctionCode, data[, callback]*) +### diagnostics(*deviceAddress, subFunctionCode, data[, callback]*) ### -Function Code : 08 +Function Code: 08 This method provides a series of tests for checking the communication system between a client (Master) device and a server (Slave), or for checking various internal error conditions within a server. It takes the following parameters: @@ -168,7 +166,7 @@ This method provides a series of tests for checking the communication system bet | *deviceAddress* | Integer| Yes | N/A | The unique address that identifies a device | | *subFunctionCode* | Constant | Yes | N/A | Refer to the ‘Sub-function Code’ table, below | | *data* | Blob | Yes | N/A | The data field required by Modbus request | -| *callback* | Function | No | Null | The function to be fired when it receives response regarding this request. It takes two parameters, *error* and *result* | +| *callback* | Function | No | `null` | The function to be fired when it receives response regarding this request. It takes two parameters, *error* and *result* | | Sub-function Code | Value (Hex) | | --- | --- | @@ -188,7 +186,7 @@ This method provides a series of tests for checking the communication system bet | *MODBUSRTU_SUB_FUNCTION_CODE.RETURN_BUS_CHARACTER_OVERRUN_COUNT* | 0x0012 | | *MODBUSRTU_SUB_FUNCTION_CODE.CLEAR_OVERRUN_COUNTER_AND_FLAG* | 0x0014 | -#### Example +#### Example #### ```squirrel local data = blob(2); @@ -196,36 +194,36 @@ data.writen(0xFF00, 'w'); data.swap2(); modbus.diagnostics(0x01, MODBUSRTU_SUB_FUNCTION_CODE.RESTART_COMMUNICATION_OPTION, data, function(error, result) { - if (error) { - server.error(error); - } else { - server.log(result); - } + if (error) { + server.error(error); + } else { + server.log(result); + } }.bindenv(this)); ``` -### reportSlaveID(*deviceAddress[, callback]*) +### reportSlaveID(*deviceAddress[, callback]*) ### -Function Code : 17 +Function Code: 17 This method reads the description of the type, the current status and other information specific to a remote device whose address is specified in the method’s first parameter. The second, optional parameter is a function that will be fired when a response regarding this request is received. It takes two parameters, *error* and *result*. -#### Example +#### Example #### ```squirrel modbus.reportSlaveID(0x01, function(error, result) { - if (error) { - server.error(error); - } else { - server.log("Run indicator : " + result.runIndicator); - server.log(result.slaveId); - } + if (error) { + server.error(error); + } else { + server.log("Run indicator : " + result.runIndicator); + server.log(result.slaveId); + } }.bindenv(this)); ``` -### maskWriteRegister(*deviceAddress, referenceAddress, AND_Mask, OR_Mask[, callback]*) +### maskWriteRegister(*deviceAddress, referenceAddress, AND_Mask, OR_Mask[, callback]*) ### -Function Code : 22 +Function Code: 22 This method modifies the contents of a specified holding register using a combination of an AND mask, an OR mask and the register’s current contents. The function can be used to set or clear individual bits in the register. It takes the following parameters: @@ -235,25 +233,25 @@ This method modifies the contents of a specified holding register using a combin | *referenceAddress* | Integer | Yes | N/A | The address of the holding register the value is written into | | *AND_mask* | Integer | Yes | N/A | The AND mask | | *OR_mask* | Integer | Yes | N/A | The OR mask | -| *callback* | Function | No | Null | The function to be fired when it receives response regarding this request. It takes two parameters, *error* and *result* | +| *callback* | Function | No | `null` | The function to be fired when it receives response regarding this request. It takes two parameters, *error* and *result* | -#### Example +#### Example #### ```squirrel modbus.maskWriteRegister(0x01, 0x10, 0xFFFF, 0x0000, function(error, result) { - if (error) { - server.error(error); - } else { - server.log(result); - } + if (error) { + server.error(error); + } else { + server.log(result); + } }.bindenv(this)); ``` -### readWriteMultipleRegisters(*deviceAddress, readingStartAddress, readQuantity, writeStartAddress, writeQuantity, writeValue[, callback]*) +### readWriteMultipleRegisters(*deviceAddress, readingStartAddress, readQuantity, writeStartAddress, writeQuantity, writeValue[, callback]*) ### -Function Code : 23 +Function Code: 23 -This method performs a combination of one read operation and one write operation in a single Modbus transaction. The write operation is performed before the read ^. It takes the following parameters: +This method performs a combination of one read operation and one write operation in a single Modbus transaction. The write operation is performed before the read. It takes the following parameters: | Parameter | Data Type | Required | Default Value | Description | | --- | --- | --- | --- | --- | @@ -263,19 +261,27 @@ This method performs a combination of one read operation and one write operation | *writeStartAddress* | Integer | Yes | N/A | The address from which it begins writing values | | *writeQuantity* | Integer | Yes | N/A | The number of consecutive addresses values are written into | | *writeValue* | Blob | Yes | N/A | The value written into the holding register | -| *callback* | Function | No | Null | The function to be fired when it receives response regarding this request. It takes two parameters, *error* and *result* | +| *callback* | Function | No | `null` | The function to be fired when it receives response regarding this request. It takes two parameters, *error* and *result* | **Note** The actual order of operation is determined by the implementation of user's device. - -#### Example +#### Example #### ```squirrel +modbus.readWriteMultipleRegisters(0x01, 9, 3, 9, 3, [188, 80, 18], function(error, result) { + if (error) { + errorMessage(error, resolve, reject); + } else { + foreach(key, value in results) { + server.log(key + " : " + value); + } + } +}.bindenv(this)); ``` -### readDeviceIdentification(*deviceAddress, readDeviceIdCode, objectId[, callback]*) +### readDeviceIdentification(*deviceAddress, readDeviceIdCode, objectId[, callback]*) ### -Function Code : 43/14 +Function Code: 43/14 This method lets you read the identification and additional information relative to the physical and functional description of a remote device. It takes the following parameters: @@ -284,7 +290,7 @@ This method lets you read the identification and additional information relative | *deviceAddress* | Integer | Yes | N/A | The unique address that identifies a device | | *readDeviceIdCode* | Constant| Yes | N/A | Refer to the ‘Read Device ID Code’ table, below | | *objectId* | Constant | Yes | N/A | Refer to the ‘Object ID’ table, below | -| *callback* | Function | No | Null | The function to be fired when it receives response regarding this request. It takes two parameters, *error* and *result* | +| *callback* | Function | No | `null` | The function to be fired when it receives response regarding this request. It takes two parameters, *error* and *result* | | Read Device ID Code | Description | | --- | --- | @@ -303,44 +309,44 @@ This method lets you read the identification and additional information relative | *MODBUSRTU_OBJECT_ID.MODEL_NAME* | Regular | | *MODBUSRTU_OBJECT_ID.USER_APPLICATION_NAME* | Regular | -#### Example +#### Example #### ```squirrel modbus.readDeviceIdentification(0x01, MODBUSRTU_READ_DEVICE_CODE.BASIC, MODBUSRTU_OBJECT_ID.VENDOR_NAME, function(error, objects) { - if (error) { - server.error("Error: " + error + ", Objects: " + objects); - } else { - local info = "DeviceId: "; - foreach (id, val in objects) { - info += format("[%d] %s, ", id, val.tostring()); - } - server.log(info); + if (error) { + server.error("Error: " + error + ", Objects: " + objects); + } else { + local info = "DeviceId: "; + foreach (id, val in objects) { + info += format("[%d] %s, ", id, val.tostring()); } + server.log(info); + } }.bindenv(this)); ``` -## Exception Codes +## Exception Codes ## The table below enumerates all the exception codes that can be possibly encountered. Refer to [Modbus specification](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) for more detailed description on Modbus-specific exceptions. -| Value (Dec) | Description | -| ------------- | ----------------------- | -| 1 | Illegal Function | -| 2 | Illegal Data Address | -| 3 | Illegal Data Value | -| 4 | Slave Device Fail | -| 5 | Acknowledge | -| 6 | Slave Device Busy | -| 7 | Negative Acknowledge | -| 8 | Memory Parity Error | -| 80 | Response Timeout | -| 81 | Invalid CRC | -| 82 | Invalid Argument Length | -| 83 | Invalid Device Address | -| 87 | Invalid Target Type | -| 88 | Invalid Values | -| 89 | Invalid Quantity | - -## License - -The Modbus485Master library is licensed under the [MIT License](https://github.com/electricimp/Modbus/tree/master/LICENSE). +| Value (Dec) | Description | +| --- | --- | +| 1 | Illegal Function | +| 2 | Illegal Data Address | +| 3 | Illegal Data Value | +| 4 | Slave Device Fail | +| 5 | Acknowledge | +| 6 | Slave Device Busy | +| 7 | Negative Acknowledge | +| 8 | Memory Parity Error | +| 80 | Response Timeout | +| 81 | Invalid CRC | +| 82 | Invalid Argument Length | +| 83 | Invalid Device Address | +| 87 | Invalid Target Type | +| 88 | Invalid Values | +| 89 | Invalid Quantity | + +## License ## + +The ModbusSerialMaster library is licensed under the [MIT License](../LICENSE). diff --git a/ModbusSerialMaster/example/example.device.nut b/ModbusSerialMaster/example/example.device.nut new file mode 100644 index 0000000..ae17722 --- /dev/null +++ b/ModbusSerialMaster/example/example.device.nut @@ -0,0 +1,61 @@ +// MIT License +// +// Copyright 2017-19 Electric Imp +// Copyright 2020-23 KORE Wireless +// +// SPDX-License-Identifier: MIT +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +#require "CRC16.class.nut:1.0.0" +#require "ModbusRTU.device.lib.nut:1.0.2" +#require "ModbusMaster.device.lib.nut:1.0.2" +#require "ModbusSerialMaster.device.lib.nut:2.0.1" + +// Hardware used: Fieldbus Gateway and Kojo +// Click PLC C0-02DR-D connectied via RS485 ports + +// This example demonstrates how to write and read values +// into/from holding registers. + +const DEVICE_ADDRESS = 0x01; +// instantiate the the Modbus485Master object +local params = {"baudRate" : 38400, "parity" : PARITY_ODD}; +modbus <- ModbusSerialMaster(hardware.uart2, hardware.pinL, params); + +// write values into 3 holding registers starting at address 9 +modbus.write(DEVICE_ADDRESS, MODBUSRTU_TARGET_TYPE.HOLDING_REGISTER, 9, 3, [188, 80, 18], function(error, res) { + if (error) { + server.error(error); + } else { + // read values from 3 holding registers starting at address 9 + modbus.read(DEVICE_ADDRESS, MODBUSRTU_TARGET_TYPE.HOLDING_REGISTER, 9, 3, function(error, res) { + if (error) { + server.error(error); + } else { + // 188 + server.log(res[0]); + // 80 + server.log(res[1]); + // 18 + server.log(res[2]); + } + }); + } +}); diff --git a/Modbus485Master/tests/device.test.nut b/ModbusSerialMaster/tests/device.test.nut similarity index 90% rename from Modbus485Master/tests/device.test.nut rename to ModbusSerialMaster/tests/device.test.nut index 2f511df..c6a9abc 100644 --- a/Modbus485Master/tests/device.test.nut +++ b/ModbusSerialMaster/tests/device.test.nut @@ -1,5 +1,42 @@ -const DEVICE_ADDRESS = 1; +// MIT License +// +// Copyright 2017 Electric Imp +// +// SPDX-License-Identifier: MIT +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// ----------------------------------------------------------------------------- + +// Hardware setup: +// * Fieldbus Gateway +// * Koyo Click PLC C0-02DR-D +// * Connected via RS485 ports + +@include "github:electricimp/CRC16/CRC16.class.nut"; +@include __PATH__ + "/../../ModbusRTU/ModbusRTU.device.lib.nut"; +@include __PATH__ + "/../../ModbusMaster/ModbusMaster.device.lib.nut"; +@include __PATH__ + "/../ModbusSerialMaster.device.lib.nut"; + +// Function to allow tests with expected error messages to pass +// With the hardware setup above it is expected that only **testReportSlaveID** +// will need this helper to pass testing function errorMessage(error, resolve, reject) { switch (error) { case MODBUSRTU_EXCEPTION.ILLEGAL_FUNCTION: @@ -13,13 +50,17 @@ function errorMessage(error, resolve, reject) { } } +const DEVICE_ADDRESS = 0x01; + class DeviceTestCase extends ImpTestCase { _PASS_MESSAGE = "Pass"; _modbus = null; function setUp() { - _modbus = Modbus485Master(hardware.uart2, hardware.pinL); - return "Modbus485Master"; + // These are the default settings for the Koyo Click PLC C0-02DR-D + local params = {"baudRate" : 38400, "parity" : PARITY_ODD}; + _modbus = ModbusSerialMaster(hardware.uart2, hardware.pinL, params); + return "ModbusSerialMaster"; } function testReadCoils() { diff --git a/ModbusSerialSlave/.imptest b/ModbusSerialSlave/.imptest new file mode 100644 index 0000000..4ec53c2 --- /dev/null +++ b/ModbusSerialSlave/.imptest @@ -0,0 +1,14 @@ +{ + "modelId": "KB2yibqKCKCh" /* I */, + "devices": [ + "5000d8c46a56cdae" /* I */ + ], + "agentFile": false, + "deviceFile": false, + "stopOnFailure": false, + "timeout": 10, + "tests": [ + "*.test.nut", + "tests/**/*.test.nut" + ] +} \ No newline at end of file diff --git a/Modbus485Slave/Modbus485Slave.class.nut b/ModbusSerialSlave/ModbusSerialSlave.device.lib.nut similarity index 76% rename from Modbus485Slave/Modbus485Slave.class.nut rename to ModbusSerialSlave/ModbusSerialSlave.device.lib.nut index 2e5af05..c7e9cdd 100644 --- a/Modbus485Slave/Modbus485Slave.class.nut +++ b/ModbusSerialSlave/ModbusSerialSlave.device.lib.nut @@ -1,8 +1,30 @@ -// Copyright (c) 2017 Electric Imp -// This file is licensed under the MIT License -// http://opensource.org/licenses/MIT +// MIT License +// +// Copyright 2017-19 Electric Imp +// Copyright 2020-23 KORE Wireless +// +// SPDX-License-Identifier: MIT +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. -class Modbus485Slave extends ModbusSlave { +class ModbusSerialSlave extends ModbusSlave { + static VERSION = "2.0.1"; static MIN_REQUEST_LENGTH = 4; _slaveID = null; _uart = null; @@ -14,6 +36,7 @@ class Modbus485Slave extends ModbusSlave { // // Constructor for Modbus485Slave // + // @param {integer} slaveID - The slave id // @param {object} uart - The UART object // @param {object} rts - The pin used as RTS // @param {table} params - The table contains all the arugments the constructor expects @@ -23,7 +46,7 @@ class Modbus485Slave extends ModbusSlave { // @item {integer} stopBits - 1 bit by default // @item {bool} debug - false by default. If enabled, the outgoing and incoming ADU will be printed for debugging purpose // - constructor(uart, rts, slaveID, params = {}) { + constructor(slaveID, uart, rts = null, params = {}) { if (!("CRC16" in getroottable())) { throw "Must include CRC16 library v1.0.0+"; } @@ -43,7 +66,7 @@ class Modbus485Slave extends ModbusSlave { // 4.5 characters time in microseconds _minInterval = 45000000.0 / baudRate; _uart.configure(baudRate, dataBits, parity, stopBits, TIMING_ENABLED, _onReceive.bindenv(this)); - _rts.configure(DIGITAL_OUT, 0); + if (_rts != null) _rts.configure(DIGITAL_OUT, 0); } // @@ -144,13 +167,18 @@ class Modbus485Slave extends ModbusSlave { // the concrete function to send a packet via RS485 // function _send(ADU) { - local rw = _rts.write.bindenv(_rts); local uw = _uart.write.bindenv(_uart); local uf = _uart.flush.bindenv(_uart); - rw(1); - uw(ADU); - uf(); - rw(0); + if (_rts != null) { + local rw = _rts.write.bindenv(_rts); + rw(1); + uw(ADU); + uf(); + rw(0); + } else { + uw(ADU); + uf(); + } _log(ADU, "Outgoing ADU : "); // return ADU to help with testing return ADU; diff --git a/Modbus485Slave/README.md b/ModbusSerialSlave/README.md similarity index 66% rename from Modbus485Slave/README.md rename to ModbusSerialSlave/README.md index 2674b66..7058136 100644 --- a/Modbus485Slave/README.md +++ b/ModbusSerialSlave/README.md @@ -1,34 +1,34 @@ -# Modbus485Slave +# ModbusSerialSlave # -This library empowers an imp to communicate with the Modbus Master via the RS485 protocol. +This library empowers an imp to communicate with the Modbus Master via the Modbus-RS485 or Modbus-RS232 protocol. **To use this library, add the following statements to the top of your device code:** ```squirrel #require "CRC16.class.nut:1.0.0" -#require "ModbusSlave.class.nut:1.0.0" -#require "Modbus485Slave.class.nut:1.0.0" +#require "ModbusSlave.device.lib.nut:1.0.1" +#require "ModbusSerialSlave.device.lib.nut:2.0.0" ``` -## Hardware Setup +## Hardware Setup ## -The following instructions are applicable to Electric Imp’s [impAccelerator™ Fieldbus Gateway](https://electricimp.com/docs/hardware/resources/reference-designs/fieldbusgateway/). +The following instructions are applicable to Electric Imp’s [impAccelerator™ Fieldbus Gateway](https://developer.electricimp.com/hardware/resources/reference-designs/fieldbusgateway). 1. Connect the antenna to the Fieldbus Gateway 2. Wire RS485 A on the Fieldbus Gateway to port A / positive(+) on the other device 3. Wire RS485 B on the Fieldbus Gateway to port B / negative(-) on the other device 4. Wire both devices’ ground ports together -5. Fit [jumper J2](https://electricimp.com/docs/hardware/resources/reference-designs/fieldbusgateway/#rs-485) on the Fieldbus Gateway motherboard to enable RS485 +5. Fit [jumper J2](https://developer.electricimp.com/hardware/resources/reference-designs/fieldbusgateway#rs-485) on the Fieldbus Gateway motherboard to enable RS485 6. Power up the Fieldbus Gateway 7. Configure the Fieldbus Gateway for Internet access using BlinkUp™ -## Modbus485Slave Class Usage +## ModbusSerialSlave Class Usage ## This is the main library class. -### Constructor: Modbus485Slave(*uart, rts, slaveID[, params]*) +### Constructor: ModbusSerialSlave(*slaveID, uart[, rts][, params]*) ### -Instantiate a new Modbus485Slave object and set the configuration of the UART bus over which it operates. The parameters *uart* and *rts* are, respectively, the imp UART in use and an imp GPIO pin which will be used to control flow. The *slaveID* parameter takes an ID by which the master identifies this slave. The *params* parameter is optional and takes a table containing the following keys: +Instantiates a new ModbusSerialSlave object and configures the UART bus over which it operates. The *slaveID* parameter takes an ID by which the master identifies this slave. The *uart* parameter is an imp UART object. The optional *rts* parameter should be used for RS485 communications when you are using an imp GPIO pin for control flow. The *params* parameter is optional and takes a table containing the following keys: | Key | Default | Notes | | --- | --- | --- | @@ -38,23 +38,23 @@ Instantiate a new Modbus485Slave object and set the configuration of the UART bu | stopBits | 1 | Number of stop bits (1 or 2) on the UART connection | | debug | `false` | If enabled, the outgoing and incoming ADU will be printed for debugging purpose | -#### Example +#### Example #### ```squirrel -modbus <- Modbus485Slave(hardware.uart2, hardware.pinL, 1); +modbus <- Modbus485Slave(1, hardware.uart2, hardware.pinL); ``` -### setSlaveID(*slaveID*) +### setSlaveID(*slaveID*) ### This method changes the slave ID. Its single parameter takes the new slave ID. -#### Example +#### Example #### ```squirrel modbus.setSlaveID(2); ``` -### onWrite(*callback*) +### onWrite(*callback*) ### This method sets the callback function that will be triggered when there is a write request. The callback takes the following parameters: @@ -66,7 +66,7 @@ This method sets the callback function that will be triggered when there is a wr | *quantity* | Integer | The quantity of the values | | *values* | Integer, bool, array | The values to be written | -#### Accepted Return Value Types +#### Accepted Return Value Types #### The callback function can return a value, which will be processed and sent back to the Master as a response. @@ -76,38 +76,38 @@ The callback function can return a value, which will be processed and sent back | `null` | If `null` is returned, it is the same as returning `true` | | Integer | Any acceptable Modbus Exception Code can be returned | -#### Example +#### Example #### ```squirrel // Accept this write request modbus.onWrite(function(slaveID, functionCode, startingAddress, quantity, values) { - server.log("slaveID : " + slaveID); - server.log("functionCode : " + functionCode); - server.log("startingAddress : " + startingAddress); - server.log("Quantity : " + quantity); - server.log("Values : \n"); - foreach (index, value in values) { - server.log("\t" + index + " : " + value); - } - return true; + server.log("slaveID : " + slaveID); + server.log("functionCode : " + functionCode); + server.log("startingAddress : " + startingAddress); + server.log("Quantity : " + quantity); + server.log("Values : \n"); + foreach (index, value in values) { + server.log("\t" + index + " : " + value); + } + return true; }.bindenv(this)); // Decline this write request modbus.onWrite(function(slaveID, functionCode, startingAddress, quantity, values) { - server.log("slaveID : " + slaveID); - server.log("functionCode : " + functionCode); - server.log("startingAddress : " + startingAddress); - server.log("Quantity : " + quantity); - server.log("Values : \n"); - foreach (index, value in values) { - server.log("\t" + index + " : " + value); - } - // reject this request with the exception code of 2 - return 2; + server.log("slaveID : " + slaveID); + server.log("functionCode : " + functionCode); + server.log("startingAddress : " + startingAddress); + server.log("Quantity : " + quantity); + server.log("Values : \n"); + foreach (index, value in values) { + server.log("\t" + index + " : " + value); + } + // reject this request with the exception code of 2 + return 2; }.bindenv(this)); ``` -### onRead(*callback*) +### onRead(*callback*) ### This method sets the callback function that will be called when there is a read request. The callback takes the following parameters: @@ -118,7 +118,7 @@ This method sets the callback function that will be called when there is a read | *startingAddress* | Integer | The address at which it starts writing values | | *quantity* | Integer | The quantity of the values | -#### Accepted Return Value Type +#### Accepted Return Value Type #### The callback function can return a value, which will be processed and sent back to the Master as a response. @@ -129,37 +129,37 @@ The callback function can return a value, which will be processed and sent back | Integer | 1 or 0 when it is a coil or discrete input read. Any number when it is a holding register or input register read | | Array | Array of 1, 0, `true`, `false` when it is a coil or discrete input read. Array of integers when it is a holding register or input register read | -#### Example +#### Example #### ```squirrel // A coil read example modbus.onRead(function(slaveID, functionCode, startingAddress, quantity){ - server.log("slaveID : " + slaveID); - server.log("functionCode : " + functionCode); - server.log("startingAddress : " + startingAddress); - server.log("Quantity : " + quantity); - return [true,false,false,true,true]; + server.log("slaveID : " + slaveID); + server.log("functionCode : " + functionCode); + server.log("startingAddress : " + startingAddress); + server.log("Quantity : " + quantity); + return [true,false,false,true,true]; }.bindenv(this)); // A holding register read example modbus.onRead(function(slaveID, functionCode, startingAddress, quantity){ - server.log("slaveID : " + slaveID); - server.log("functionCode : " + functionCode); - server.log("startingAddress : " + startingAddress); - server.log("Quantity : " + quantity); - return [18,29,30, 59, 47]; + server.log("slaveID : " + slaveID); + server.log("functionCode : " + functionCode); + server.log("startingAddress : " + startingAddress); + server.log("Quantity : " + quantity); + return [18,29,30, 59, 47]; }.bindenv(this)); ``` -### onError(*callback*) +### onError(*callback*) ### This method sets the callback function that will be called when there is an error. The callback takes a single parameter into which a description of the error is passed. -#### Example +#### Example #### ```squirrel modbus.onError(function(error){ - server.error(error); + server.error(error); }.bindenv(this)); ``` @@ -178,7 +178,7 @@ The following table lists the function codes that the slave can support and proc | 0x0F | Write Multiple Coils | | 0x10 | Write Multiple Registers | -## Exception Codes +## Exception Codes ## The table below enumerates all the exception codes that can be possibly encountered. Refer to the [Modbus specification](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf) for more detailed description on Modbus-specific exceptions. @@ -193,6 +193,6 @@ The table below enumerates all the exception codes that can be possibly encounte | 7 | Negative Acknowledge | | 8 | Memory Parity Error | -# License +# License ## -The Modbus485Slave library is licensed under the [MIT License](https://github.com/electricimp/Mdobus/tree/master/LICENSE). +The ModbusSerialSlave library is licensed under the [MIT License](../LICENSE). diff --git a/ModbusSerialSlave/example/device.example.nut b/ModbusSerialSlave/example/device.example.nut new file mode 100644 index 0000000..8c5bba9 --- /dev/null +++ b/ModbusSerialSlave/example/device.example.nut @@ -0,0 +1,61 @@ +// MIT License +// +// Copyright 2017-19 Electric Imp +// Copyright 2020-23 KORE Wireless +// +// SPDX-License-Identifier: MIT +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +#require "CRC16.class.nut:1.0.0" +#require "ModbusSlave.device.lib.nut:1.0.2" +#require "ModbusSerialSlave.device.lib.nut:2.0.1" + +// Hardware used: Fieldbus Gateway and Kojo +// Click PLC C0-02DR-D connectied via RS485 ports + +// This example demonstrates a holding register read. + +const SLAVE_ID = 0x01; + +local params = {"baudRate" : 38400, "parity" : PARITY_ODD, "debug" : true}; +modbus <- ModbusSerialSlave(SLAVE_ID, hardware.uart2, hardware.pinL, params); + +modbus.onError(function(error) { + server.error(error); +}); + +modbus.onRead(function(slaveID, functionCode, startingAddress, quantity) { + server.log("slaveID : " + slaveID); + server.log("functionCode : " + functionCode); + server.log("startingAddress : " + startingAddress); + server.log("Quantity : " + quantity); + return [18, 29, 30, 59, 47]; +}.bindenv(this)); + +modbus.onWrite(function(slaveID, functionCode, startingAddress, quantity, values) { + server.log("slaveID : " + slaveID); + server.log("functionCode : " + functionCode); + server.log("startingAddress : " + startingAddress); + server.log("Quantity : " + quantity); + server.log("Values : \n"); + foreach (index, value in values) { + server.log("\t" + index + " : " + value); + } +}.bindenv(this)); diff --git a/Modbus485Slave/tests/device.test.nut b/ModbusSerialSlave/tests/device.test.nut similarity index 94% rename from Modbus485Slave/tests/device.test.nut rename to ModbusSerialSlave/tests/device.test.nut index 6cd6cba..f6cc30b 100644 --- a/Modbus485Slave/tests/device.test.nut +++ b/ModbusSerialSlave/tests/device.test.nut @@ -1,5 +1,38 @@ -// it is a bit hard to write test cases if the hardware is involved, -// so the idea is to create a fake buffer and simulate the parsing requests and creating responses +// MIT License +// +// Copyright 2017 Electric Imp +// +// SPDX-License-Identifier: MIT +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +// ---------------------------------------------------------------------------------- + +// It is a bit hard to write test cases if the hardware is involved, +// so the idea is to create a fake buffer and simulate the parsing +// requests and creating responses. + +// Hardware used: Fieldbus Gateway + +@include "github:electricimp/CRC16/CRC16.class.nut"; +@include __PATH__ + "/../../ModbusSlave/ModbusSlave.device.lib.nut"; +@include __PATH__ + "/../ModbusSerialSlave.device.lib.nut"; const SLAVE_ID = 1; const MIN_REQUEST_LENGTH = 4; @@ -20,8 +53,8 @@ class DeviceTestCase extends ImpTestCase { _modbus = null; function setUp() { - _modbus = Modbus485Slave(hardware.uart2, hardware.pinL, SLAVE_ID); - return "Modbus485Slave"; + _modbus = ModbusSerialSlave(SLAVE_ID, hardware.uart2, hardware.pinL); + return "ModbusSerialSlave"; } function testSetSlaveID() { diff --git a/ModbusSlave/ModbusSlave.class.nut b/ModbusSlave/ModbusSlave.device.lib.nut similarity index 90% rename from ModbusSlave/ModbusSlave.class.nut rename to ModbusSlave/ModbusSlave.device.lib.nut index 0de705e..fdfbc4e 100644 --- a/ModbusSlave/ModbusSlave.class.nut +++ b/ModbusSlave/ModbusSlave.device.lib.nut @@ -1,6 +1,27 @@ -// Copyright (c) 2017 Electric Imp -// This file is licensed under the MIT License -// http://opensource.org/licenses/MIT +// MIT License +// +// Copyright 2017-19 Electric Imp +// Copyright 2020-23 KORE Wireless +// +// SPDX-License-Identifier: MIT +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. enum MODBUSSLAVE_TARGET_TYPE { COIL, @@ -21,7 +42,7 @@ enum MODBUSSLAVE_EXCEPTION { } class ModbusSlave { - static VERSION = "1.0.0"; + static VERSION = "1.0.2"; static FUNCTION_CODES = { readCoil = { fcode = 0x01, diff --git a/ModbusSlave/README.md b/ModbusSlave/README.md index 8d6689a..f308d77 100644 --- a/ModbusSlave/README.md +++ b/ModbusSlave/README.md @@ -5,10 +5,10 @@ This is the base class for the following classes, and it should NOT be instantia **Please proceed to one of the following libraries** -# [Modbus485Slave](../Modbus485Slave/) +# [ModbusSerialSlave](../ModbusSerialSlave/) -This library empowers an imp to communicate the Modbus Master via the RS485 protocol. +This library empowers an imp to communicate the Modbus Master via the RS485 or RS232 protocol. # License -The ModbusRTUMaster library is licensed under the [MIT License](https://github.com/electricimp/thethingsapi/tree/master/LICENSE). +The ModbusRTUMaster library is licensed under the [MIT License](../LICENSE). diff --git a/ModbusTCPMaster/.imptest b/ModbusTCPMaster/.imptest new file mode 100644 index 0000000..4ec53c2 --- /dev/null +++ b/ModbusTCPMaster/.imptest @@ -0,0 +1,14 @@ +{ + "modelId": "KB2yibqKCKCh" /* I */, + "devices": [ + "5000d8c46a56cdae" /* I */ + ], + "agentFile": false, + "deviceFile": false, + "stopOnFailure": false, + "timeout": 10, + "tests": [ + "*.test.nut", + "tests/**/*.test.nut" + ] +} \ No newline at end of file diff --git a/ModbusTCPMaster/ModbusTCPMaster.class.nut b/ModbusTCPMaster/ModbusTCPMaster.class.nut deleted file mode 100644 index 09ad6f2..0000000 --- a/ModbusTCPMaster/ModbusTCPMaster.class.nut +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) 2017 Electric Imp -// This file is licensed under the MIT License -// http://opensource.org/licenses/MIT - -class ModbusTCPMaster extends ModbusMaster { - static MAX_TRANSACTION_COUNT = 65535; - _transactions = null; - _wiz = null; - _transactionCount = null; - _connection = null; - _connectionSettings = null; - _shouldRetry = null; - _connectCallback = null; - _reconnectCallback = null; - - // - // Constructor for ModbusTCPMaster - // - // @param {object} wiz - The W5500 object - // @param {bool} debug - false by default. If enabled, the outgoing and incoming ADU will be printed for debugging purpose - // - constructor(wiz, debug = false) { - base.constructor(debug); - _wiz = wiz; - _transactionCount = 1; - _transactions = {}; - } - - // - // configure and open a TCP connection - // - // @param {table} networkSettings - The network settings table. It entails sourceIP, subnet, gatewayIP - // @param {table} connectionSettings - The connection settings table. It entails device IP and port - // @param {function} connectCallback - The function to be fired when the connection is established - // @param {function} reconnectCallback - The function to be fired when the connection is reestablished - // - function connect(connectionSettings, connectCallback = null, reconnectCallback = null) { - _shouldRetry = true; - _connectCallback = connectCallback; - _reconnectCallback = reconnectCallback; - _connectionSettings = connectionSettings; - _wiz.onReady(function() { - local destIP = connectionSettings.destIP; - local destPort = connectionSettings.destPort; - _wiz.openConnection(destIP, destPort, _onConnect.bindenv(this)); - }.bindenv(this)); - } - - // - // close the existing TCP connection - // - // @param {function} callback - The function to be fired when the connection is closed - // - function disconnect(callback = null) { - _shouldRetry = false; - _connection.close(callback); - } - - // - // The callback function to be fired when the connection is established - // - function _onConnect(error, conn) { - if (error) { - return _callbackHandler(error, null, _connectCallback); - } - _connection = conn; - _connection.onReceive(_parseADU.bindenv(this)); - _connection.onDisconnect(_onDisconnect.bindenv(this)); - _callbackHandler(null, conn, _connectCallback); - } - - // - // The callback function to be fired when the connection is dropped - // - function _onDisconnect(conn) { - _connection = null; - if (_shouldRetry) { - if (_reconnectCallback != null) { - _connectCallback = _reconnectCallback; - } - _wiz.openConnection(_connectionSettings.destIP, _connectionSettings.destPort, _onConnect.bindenv(this)); - } - } - - // - // The callback function to be fired it receives a packet - // - function _parseADU(error, ADU) { - if (error) { - return _callbackHandler(error, null, _connectCallback); - } - ADU.seek(0); - local header = ADU.readblob(7); - local transactionID = swap2(header.readn('w')); - local PDU = ADU.readblob(ADU.len() - 7); - local params = null; - try { - params = _transactions[transactionID]; - } catch (error) { - return _callbackHandler(format("Error parsing the response, transactionID %d does not exist", transactionID), null, _connectCallback); - } - local callback = params.callback; - params.PDU <- PDU; - try { - local result = ModbusRTU.parse(params); - _callbackHandler(null, result, callback); - } catch (error) { - _callbackHandler(error, null, callback); - } - _transactions.rawdelete(transactionID); - _log(ADU, "Incoming ADU: "); - } - - // - // create an ADU - // - function _createADU(PDU) { - local ADU = blob(); - ADU.writen(swap2(_transactionCount), 'w'); - ADU.writen(swap2(0x0000), 'w'); - ADU.writen(swap2(PDU.len() + 1), 'w'); - ADU.writen(0x00, 'b'); - ADU.writeblob(PDU); - return ADU; - } - - // - // send the ADU via Ethernet - // - function _send(PDU, properties) { - _transactions[_transactionCount] <- properties; - local ADU = _createADU(PDU); - // with or without success in transmission of data, the transaction count would be advanced - _transactionCount = (_transactionCount + 1) % MAX_TRANSACTION_COUNT; - _connection.transmit(ADU, function(error) { - if (error) { - _callbackHandler(error, null, properties.callback); - } else { - _log(ADU, "Outgoing ADU: "); - } - }.bindenv(this)); - } - - // - // fire the callback - // - function _callbackHandler(error, result, callback) { - if (callback) { - if (error) { - callback(error, null); - } else { - callback(null, result); - } - } - } -} diff --git a/ModbusTCPMaster/ModbusTCPMaster.device.lib.nut b/ModbusTCPMaster/ModbusTCPMaster.device.lib.nut new file mode 100644 index 0000000..b974edf --- /dev/null +++ b/ModbusTCPMaster/ModbusTCPMaster.device.lib.nut @@ -0,0 +1,293 @@ +// MIT License +// +// Copyright 2017-19 Electric Imp +// Copyright 2020-23 KORE Wireless +// +// SPDX-License-Identifier: MIT +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +class ModbusTCPMaster extends ModbusMaster { + static VERSION = "1.1.1"; + static MAX_TRANSACTION_COUNT = 65535; + _transactions = null; + _wiz = null; + _transactionCount = null; + _connection = null; + _connectionSettings = null; + _shouldRetry = null; + _connectCallback = null; + _reconnectCallback = null; + // NOTE: Passing methods' parameters via class fields is not a good pattern. + // But the base class ModbusMaster must be redesigned to get rid of this pattern. + _deviceAddress = null; + + // + // Constructor for ModbusTCPMaster + // + // @param {object} wiz - The W5500 object + // @param {bool} debug - false by default. If enabled, the outgoing and incoming ADU will be printed for debugging purpose + // + constructor(wiz, debug = false) { + base.constructor(debug); + _wiz = wiz; + _transactionCount = 1; + _transactions = {}; + } + + // + // configure and open a TCP connection + // + // @param {table} networkSettings - The network settings table. It entails sourceIP, subnet, gatewayIP + // @param {table} connectionSettings - The connection settings table. It entails device IP and port + // @param {function} connectCallback - The function to be fired when the connection is established + // @param {function} reconnectCallback - The function to be fired when the connection is reestablished + // + function connect(connectionSettings, connectCallback = null, reconnectCallback = null) { + _shouldRetry = true; + _connectCallback = connectCallback; + _reconnectCallback = reconnectCallback; + _connectionSettings = connectionSettings; + _wiz.onReady(function() { + local destIP = connectionSettings.destIP; + local destPort = connectionSettings.destPort; + _wiz.openConnection(destIP, destPort, _onConnect.bindenv(this)); + }.bindenv(this)); + } + + // + // close the existing TCP connection + // + // @param {function} callback - The function to be fired when the connection is closed + // + function disconnect(callback = null) { + _shouldRetry = false; + _connection.close(callback); + } + + // + // This function performs a combination of one read operation and one write operation in a single MODBUS transaction. + // The write operation is performed before the read. + // + // @param {integer} readingStartAddress - The address from which it begins reading values + // @param {integer} readQuantity - The number of consecutive addresses values are read from + // @param {integer} writeStartAddress - The address from which it begins writing values + // @param {integer} writeQuantity - The number of consecutive addresses values are written into + // @param {blob} writeValue - The value written into the holding register + // @param {function} callback - The function to be fired when it receives response regarding this request + // @param {integer} deviceAddress - The unique address that identifies a device + // + function readWriteMultipleRegisters(readingStartAddress, readQuantity, writeStartAddress, writeQuantity, writeValue, callback = null, deviceAddress = 0) { + _deviceAddress = deviceAddress; + base.readWriteMultipleRegisters(readingStartAddress, readQuantity, writeStartAddress, writeQuantity, writeValue, callback); + } + + // + // This function modifies the contents of a specified holding register using a combination of an AND mask, + // an OR mask, and the register's current contents. + // The function can be used to set or clear individual bits in the register. + // + // @param {integer} referenceAddress - The address of the holding register the value is written into + // @param {integer} AND_mask - The AND mask + // @param {integer} OR_mask - The OR mask + // @param {function} callback - The function to be fired when it receives response regarding this request + // @param {integer} deviceAddress - The unique address that identifies a device + // + function maskWriteRegister(referenceAddress, AND_Mask, OR_Mask, callback = null, deviceAddress = 0) { + _deviceAddress = deviceAddress; + base.maskWriteRegister(referenceAddress, AND_Mask, OR_Mask, callback); + } + + // + // This function reads the description of the type, the current status, and other information specific to a remote device. + // + // @param {function} callback - The function to be fired when it receives response regarding this request + // @param {integer} deviceAddress - The unique address that identifies a device + // + function reportSlaveID(callback = null, deviceAddress = 0) { + _deviceAddress = deviceAddress; + base.reportSlaveID(callback); + } + + // + // This function allows reading the identification and additional information relative to the physical + // and functional description of a remote device. + // + // @param {enum} readDeviceIdCode - read device id code + // @param {enum} objectId - object id + // @param {function} callback - The function to be fired when it receives response regarding this request + // @param {integer} deviceAddress - The unique address that identifies a device + // + function readDeviceIdentification(readDeviceIdCode, objectId, callback = null, deviceAddress = 0) { + _deviceAddress = deviceAddress; + base.readDeviceIdentification(readDeviceIdCode, objectId, callback); + } + + // + // This function provides a series of tests for checking the communication system between a client (Master) device + // and a server (Slave), or for checking various internal error conditions within a server. + // + // @param {integer} subFunctionCode - The Sub-function Code + // @param {blob} data - The data field required by Modbus request + // @param {function} callback - The function to be fired when it receives response regarding this request + // @param {integer} deviceAddress - The unique address that identifies a device + // + function diagnostics(subFunctionCode, data, callback = null, deviceAddress = 0) { + _deviceAddress = deviceAddress; + base.diagnostics(subFunctionCode, data, callback); + } + + // + // This function reads the contents of eight Exception Status outputs in a remote device + // + // @param {function} callback - The function to be fired when it receives response regarding this request + // @param {integer} deviceAddress - The unique address that identifies a device + // + function readExceptionStatus(callback = null, deviceAddress = 0) { + _deviceAddress = deviceAddress; + base.readExceptionStatus(callback); + } + + // + // This is the generic function to read values from a single coil register or multiple coils registers. + // + // @param {enum} targetType - The Target Type + // @param {integer} startingAddress - The address from which it begins reading values + // @param {integer} quantity - The number of consecutive addresses the values are read from + // @param {function} callback - The function to be fired when it receives response regarding this request + // @param {integer} deviceAddress - The unique address that identifies a device + // + function read(targetType, startingAddress, quantity, callback, deviceAddress = 0) { + _deviceAddress = deviceAddress; + base.read(targetType, startingAddress, quantity, callback); + } + + // + // This is the generic function to write values into coils or holding registers. + // + // @param {enum} targetType - The Target Type + // @param {integer} startingAddress - The address from which it begins writing values + // @param {integer} quantity - The number of consecutive addresses the values are written into + // @param {integer, Array[integer,Bool], Bool, blob} values - The values written into Coils or Registers + // @param {function} callback - The function to be fired when it receives response regarding this request + // @param {integer} deviceAddress - The unique address that identifies a device + // + function write(targetType, startingAddress, quantity, values, callback = null, deviceAddress = 0) { + _deviceAddress = deviceAddress; + base.write(targetType, startingAddress, quantity, values, callback); + } + + // + // The callback function to be fired when the connection is established + // + function _onConnect(error, conn) { + if (error) { + return _callbackHandler(error, null, _connectCallback); + } + _connection = conn; + _connection.onReceive(_parseADU.bindenv(this)); + _connection.onDisconnect(_onDisconnect.bindenv(this)); + _callbackHandler(null, conn, _connectCallback); + } + + // + // The callback function to be fired when the connection is dropped + // + function _onDisconnect(conn) { + _connection = null; + if (_shouldRetry) { + if (_reconnectCallback != null) { + _connectCallback = _reconnectCallback; + } + _wiz.openConnection(_connectionSettings.destIP, _connectionSettings.destPort, _onConnect.bindenv(this)); + } + } + + // + // The callback function to be fired it receives a packet + // + function _parseADU(error, ADU) { + if (error) { + return _callbackHandler(error, null, _connectCallback); + } + ADU.seek(0); + local header = ADU.readblob(7); + local transactionID = swap2(header.readn('w')); + local PDU = ADU.readblob(ADU.len() - 7); + local params = null; + try { + params = _transactions[transactionID]; + } catch (error) { + return _callbackHandler(format("Error parsing the response, transactionID %d does not exist", transactionID), null, _connectCallback); + } + local callback = params.callback; + params.PDU <- PDU; + try { + local result = ModbusRTU.parse(params); + _callbackHandler(null, result, callback); + } catch (error) { + _callbackHandler(error, null, callback); + } + _transactions.rawdelete(transactionID); + _log(ADU, "Incoming ADU: "); + } + + // + // create an ADU + // + function _createADU(PDU) { + local ADU = blob(); + ADU.writen(swap2(_transactionCount), 'w'); + ADU.writen(swap2(0x0000), 'w'); + ADU.writen(swap2(PDU.len() + 1), 'w'); + ADU.writen(_deviceAddress, 'b'); + ADU.writeblob(PDU); + return ADU; + } + + // + // send the ADU via Ethernet + // + function _send(PDU, properties) { + _transactions[_transactionCount] <- properties; + local ADU = _createADU(PDU); + // with or without success in transmission of data, the transaction count would be advanced + _transactionCount = (_transactionCount + 1) % MAX_TRANSACTION_COUNT; + _connection.transmit(ADU, function(error) { + if (error) { + _callbackHandler(error, null, properties.callback); + } else { + _log(ADU, "Outgoing ADU: "); + } + }.bindenv(this)); + } + + // + // fire the callback + // + function _callbackHandler(error, result, callback) { + if (callback) { + if (error) { + callback(error, null); + } else { + callback(null, result); + } + } + } +} diff --git a/ModbusTCPMaster/README.md b/ModbusTCPMaster/README.md index 93ed972..2f6edf3 100644 --- a/ModbusTCPMaster/README.md +++ b/ModbusTCPMaster/README.md @@ -5,19 +5,19 @@ This library allows an imp to communicate with other devices via TCP/IP. It requ **To use this library, add the following statements to the top of your device code:** ``` -#require "ModbusRTU.class.nut:1.0.0" -#require "ModbusMaster.class.nut:1.0.0" -#require "ModbusTCPMaster.class.nut:1.0.0" +#require "ModbusRTU.device.lib.nut:1.0.1" +#require "ModbusMaster.device.lib.nut:1.0.1" +#require "ModbusTCPMaster.device.lib.nut:1.1.0" #require "W5500.device.nut:1.0.0" ``` -The following instructions are applicable to Electric Imp’s [impAccelerator™ Fieldbus Gateway](https://electricimp.com/docs/hardware/resources/reference-designs/fieldbusgateway/). +The following instructions are applicable to Electric Imp’s [impAccelerator™ Fieldbus Gateway](https://developer.electricimp.com/hardware/resources/reference-designs/fieldbusgateway). 1. Connect the antenna to the Fieldbus Gateway 2. Wire RS485 A on the Fieldbus Gateway to port A / positive(+) on the other device 3. Wire RS485 B on the Fieldbus Gateway to port B / negative(-) on the other device 4. Wire both devices’ ground ports together -5. Fit [jumper J2](https://electricimp.com/docs/hardware/resources/reference-designs/fieldbusgateway/#rs-485) on the Fieldbus Gateway motherboard to enable RS485 +5. Fit [jumper J2](https://developer.electricimp.com/hardware/resources/reference-designs/fieldbusgateway#rs-485) on the Fieldbus Gateway motherboard to enable RS485 6. Power up the Fieldbus Gateway 7. Configure the Fieldbus Gateway for Internet access using BlinkUp™ @@ -27,7 +27,7 @@ This is the main library class. It implements most of the functions listed in th ### Constructor: ModbusTCPMaster(*wiz[, debug]*) -Instantiate a new ModbusTCPMaster object. It takes one required parameter: *wiz*, the [Wiznet W5500](https://github.com/electricimp/Wiznet_5500) object that is driving the Ethernet link, and one optional boolean parameter: *debug* which, if enabled, prints the outgoing and incoming ADU for debugging purposes. The defualt value of *debug* is `false`. +Instantiate a new ModbusTCPMaster object. It takes one required parameter: *wiz*, the [Wiznet W5500](https://github.com/electricimp/Wiznet_5500) object that is driving the Ethernet link, and one optional boolean parameter: *debug* which, if enabled, prints the outgoing and incoming ADU for debugging purposes. The default value of *debug* is `false`. #### Example @@ -89,7 +89,7 @@ This method closes the existing TCP connection. It takes one optional parameter: modbus.disconnect(); ``` -### read(*targetType, startingAddress, quantity[, callback]*) +### read(*targetType, startingAddress, quantity[, callback][, deviceAddress]*) Function Code : 01, 02, 03, 04 @@ -101,6 +101,7 @@ This is a generic method used to read values from a single coil, register, or mu | *startingAddress* | Integer | Yes | N/A | The address from which it begins reading values | | *quantity* | Integer | Yes | N/A | The number of consecutive addresses the values are read from | | *callback* | Function | No | Null | The function to be fired when it receives response regarding this request. It takes two parameters, *error* and *result* | +| *deviceAddress* | Integer | No | 0x00 | The unique address that identifies a device | | Target Type | Value | Access | | --- | --- | --- | @@ -133,7 +134,7 @@ modbus.read(MODBUSRTU_TARGET_TYPE.INPUT_REGISTER, 0x01 , 5, function(error, resu }.bindenv(this)); ``` -### write(*targetType, startingAddress, quantity, values[, callback]*) +### write(*targetType, startingAddress, quantity, values[, callback][, deviceAddress]*) Function Code : 05, 06, 15, 16 @@ -146,6 +147,7 @@ This is a generic method used to write values into coils or registers. It has th | *quantity* | Integer | Yes | N/A | The number of consecutive addresses the values are written into | | *values* | Integer, array, bool, blob | Yes | N/A | The values written into Coils or Registers. Please view ‘Notes’, below | | *callback* | Function | No | Null | The function to be fired when it receives response regarding this request. It takes two parameters, *error* and *result* | +| *deviceAddress* | Integer | No | 0x00 | The unique address that identifies a device | #### Notes: @@ -177,7 +179,7 @@ modbus.write(MODBUSRTU_TARGET_TYPE.HOLDING_REGISTER, 0x01, 5, [false, true, fals }.bindenv(this)); ``` -### readExceptionStatus(*[callback]*) +### readExceptionStatus(*[callback][, deviceAddress]*) Function Code : 07 @@ -195,7 +197,7 @@ modbus.readExceptionStatus(function(error, result) { }.bindenv(this)); ``` -### diagnostics(*subFunctionCode, data[, callback]*) +### diagnostics(*subFunctionCode, data[, callback][, deviceAddress]*) Function Code : 08 @@ -206,6 +208,7 @@ This method provides a series of tests for checking the communication system bet | *subFunctionCode* | Constant | Yes | N/A | Refer to the ‘Sub-function Code’ table, below | | *data* | Blob | Yes | N/A | The data field required by Modbus request | | *callback* | Function | No | Null | The function to be fired when it receives response regarding this request. It takes two parameters, *error* and *result* | +| *deviceAddress* | Integer | No | 0x00 | The unique address that identifies a device | | Sub-function Code | Value (Hex) | | --- | --- | @@ -241,11 +244,11 @@ modbus.diagnostics(MODBUSRTU_SUB_FUNCTION_CODE.RESTART_COMMUNICATION_OPTION, dat }.bindenv(this)); ``` -### reportSlaveID(*[callback]*) +### reportSlaveID(*[callback][, deviceAddress]*) Function Code : 17 -This method reads the description of the type, the current status, and other information specific to a remote device. The optional callback function will, if supplied, be fired when a response regarding this request is received. The callback takes two parameters, *error* and *result*. +This method reads the description of the type, the current status, and other information specific to a remote device whose address is specified in the method’s last parameter. The optional callback function will, if supplied, be fired when a response regarding this request is received. The callback takes two parameters, *error* and *result*. #### Example @@ -260,7 +263,7 @@ modbus.reportSlaveID(function(error, result) { }.bindenv(this)); ``` -### maskWriteRegister(*referenceAddress, AND_Mask, OR_Mask[, callback]*) +### maskWriteRegister(*referenceAddress, AND_Mask, OR_Mask[, callback][, deviceAddress]*) Function Code : 22 @@ -272,6 +275,7 @@ This method modifies the contents of a specified holding register using a combin | *AND_mask* | Integer | Yes | N/A | The AND mask | | *OR_mask* | Integer | Yes | N/A | The OR mask | | *callback* | Function | No | Null | The function to be fired when it receives response regarding this request. It takes two parameters, *error* and *result* | +| *deviceAddress* | Integer | No | 0x00 | The unique address that identifies a device | #### Example @@ -285,11 +289,11 @@ modbus.maskWriteRegister(0x10, 0xFFFF, 0x0000, function(error, result) { }.bindenv(this)); ``` -### readWriteMultipleRegisters(*readingStartAddress, readQuantity, writeStartAddress, writeQuantity, writeValue, [callback]*) +### readWriteMultipleRegisters(*readingStartAddress, readQuantity, writeStartAddress, writeQuantity, writeValue[, callback][, deviceAddress]*) Function Code : 23 -This method performs a combination of one read operation and one write operation in a single Modbus transaction. The write operation is performed before the read ^. It takes the following parameters: +This method performs a combination of one read operation and one write operation in a single Modbus transaction. The write operation is performed before the read. It takes the following parameters: | Parameter | Data Type | Required | Default Value | Description | | --- | --- | --- | --- | --- | @@ -299,6 +303,7 @@ This method performs a combination of one read operation and one write operation | *writeQuantity* | Integer | Yes | N/A | The number of consecutive addresses values are written into | | *writeValue* | Blob | Yes | N/A | The value written into the holding register | | *callback* | Function | No | Null | The function to be fired when it receives response regarding this request. It takes two parameters, *error* and *result* | +| *deviceAddress* | Integer | No | 0x00 | The unique address that identifies a device | **Note** The actual order of operation is determined by the implementation of user's device. @@ -317,7 +322,7 @@ modbus.readWriteMultipleRegisters(0x0A, 2, 0x0A, 2, [28, 88], function(error, re ``` -### readDeviceIdentification(*readDeviceIdCode, objectId, [callback]*) +### readDeviceIdentification(*readDeviceIdCode, objectId[, callback][, deviceAddress]*) Function Code : 43/14 @@ -328,6 +333,7 @@ This method lets you read the identification and additional information relative | *readDeviceIdCode* | Constant| Yes | N/A | Refer to the ‘Read Device ID’ table, below | | *objectId* | Constant | Yes | N/A | Refer to the ‘Object ID’ table, below | | *callback* | Function | No | Null | The function to be fired when it receives response regarding this request. It takes two parameters, *error* and *result* | +| *deviceAddress* | Integer | No | 0x00 | The unique address that identifies a device | | Read Device ID Code | Description | | --- | --- | @@ -386,4 +392,4 @@ The table below enumerates all the exception codes that can be possibly encounte ## License -The ModbusTCPMaster library is licensed under the [MIT License](https://github.com/electricimp/Modbus/tree/master/LICENSE). +The ModbusTCPMaster library is licensed under the [MIT License](../LICENSE). diff --git a/ModbusTCPMaster/example/device.example.nut b/ModbusTCPMaster/example/device.example.nut index 7d7ac09..839dd48 100644 --- a/ModbusTCPMaster/example/device.example.nut +++ b/ModbusTCPMaster/example/device.example.nut @@ -1,7 +1,31 @@ +// MIT License +// +// Copyright 2017-2020 Electric Imp +// +// SPDX-License-Identifier: MIT +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + #require "W5500.device.nut:1.0.0" -#require "ModbusRTU.class.nut:1.0.0" -#require "ModbusMaster.class.nut:1.0.0" -#require "ModbusTCPMaster.class.nut:1.0.0" +#require "ModbusRTU.device.lib.nut:1.0.2" +#require "ModbusMaster.device.lib.nut:1.0.2" +#require "ModbusTCPMaster.device.lib.nut:1.1.1" // this example shows how to use readWriteMultipleRegisters diff --git a/ModbusTCPMaster/tests/device.test.nut b/ModbusTCPMaster/tests/device.test.nut index b1231c9..5ae3834 100644 --- a/ModbusTCPMaster/tests/device.test.nut +++ b/ModbusTCPMaster/tests/device.test.nut @@ -1,5 +1,35 @@ +// MIT License +// +// Copyright 2017-2020 Electric Imp +// +// SPDX-License-Identifier: MIT +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + + const PASS_MESSAGE = "Pass"; +@include "https://raw.githubusercontent.com/electricimp/Wiznet_5500/master/W5500.device.lib.nut"; +@include __PATH__ + "/../../ModbusRTU/ModbusRTU.device.lib.nut"; +@include __PATH__ + "/../../ModbusMaster/ModbusMaster.device.lib.nut"; +@include __PATH__ + "/../ModbusTCPMaster.device.lib.nut"; + function errorMessage(error, resolve, reject) { switch(error) { case MODBUSRTU_EXCEPTION.ILLEGAL_FUNCTION: @@ -13,6 +43,8 @@ function errorMessage(error, resolve, reject) { } } +const DEVICE_ADDRESS = 0x00; + class DeviceTestCase extends ImpTestCase { _wiz = null; @@ -22,8 +54,10 @@ class DeviceTestCase extends ImpTestCase { function setUp() { local spi = hardware.spi0; spi.configure(CLOCK_IDLE_LOW | MSB_FIRST | USE_CS_L, 1000); + local wiz = W5500(hardware.pinXC, spi, null, hardware.pinXA); wiz.configureNetworkSettings("192.168.1.30", "255.255.255.0", "192.168.1.1"); + _modbus = ModbusTCPMaster(wiz); _connection = Promise(function(resolve, reject) { _modbus.connect({ @@ -50,7 +84,7 @@ class DeviceTestCase extends ImpTestCase { this.assertTrue(result.len() == 2); resolve(PASS_MESSAGE); } - }.bindenv(this)); + }.bindenv(this), DEVICE_ADDRESS); }.bindenv(this)); }.bindenv(this)); } @@ -65,7 +99,7 @@ class DeviceTestCase extends ImpTestCase { this.assertTrue((result == 0 || result == 0xFF) ? true : false); resolve(PASS_MESSAGE); } - }.bindenv(this)); + }.bindenv(this), DEVICE_ADDRESS); }.bindenv(this)); }.bindenv(this)); } @@ -82,7 +116,7 @@ class DeviceTestCase extends ImpTestCase { this.assertTrue(data.tostring() == result.tostring()); resolve(PASS_MESSAGE); } - }.bindenv(this)); + }.bindenv(this), DEVICE_ADDRESS); }.bindenv(this)); }.bindenv(this)); } @@ -97,7 +131,7 @@ class DeviceTestCase extends ImpTestCase { this.assertTrue(result); resolve(PASS_MESSAGE); } - }.bindenv(this)); + }.bindenv(this), DEVICE_ADDRESS); }.bindenv(this)); }.bindenv(this)); } @@ -119,7 +153,7 @@ class DeviceTestCase extends ImpTestCase { } resolve(message); } - }.bindenv(this)); + }.bindenv(this), DEVICE_ADDRESS); }.bindenv(this)); }.bindenv(this)); } @@ -138,7 +172,7 @@ class DeviceTestCase extends ImpTestCase { } resolve(message); } - }.bindenv(this)); + }.bindenv(this), DEVICE_ADDRESS); }.bindenv(this)); }.bindenv(this)); } @@ -156,7 +190,7 @@ class DeviceTestCase extends ImpTestCase { this.assertTrue(result); resolve(PASS_MESSAGE); } - }.bindenv(this)); + }.bindenv(this), DEVICE_ADDRESS); }.bindenv(this)); }.bindenv(this)); } @@ -174,7 +208,7 @@ class DeviceTestCase extends ImpTestCase { this.assertTrue(result); resolve(PASS_MESSAGE); } - }.bindenv(this)); + }.bindenv(this), DEVICE_ADDRESS); }.bindenv(this)); }.bindenv(this)); } @@ -192,7 +226,7 @@ class DeviceTestCase extends ImpTestCase { this.assertTrue(result); resolve(PASS_MESSAGE); } - }.bindenv(this)); + }.bindenv(this), DEVICE_ADDRESS); }.bindenv(this)); }.bindenv(this)); } @@ -210,7 +244,7 @@ class DeviceTestCase extends ImpTestCase { this.assertTrue(result); resolve(PASS_MESSAGE); } - }.bindenv(this)); + }.bindenv(this), DEVICE_ADDRESS); }.bindenv(this)); }.bindenv(this)); } @@ -228,7 +262,7 @@ class DeviceTestCase extends ImpTestCase { this.assertTrue(result); resolve(PASS_MESSAGE); } - }.bindenv(this)); + }.bindenv(this), DEVICE_ADDRESS); }.bindenv(this)); }.bindenv(this)); } @@ -246,7 +280,7 @@ class DeviceTestCase extends ImpTestCase { this.assertTrue(result); resolve(PASS_MESSAGE); } - }.bindenv(this)); + }.bindenv(this), DEVICE_ADDRESS); }.bindenv(this)); }.bindenv(this)); } @@ -264,7 +298,7 @@ class DeviceTestCase extends ImpTestCase { this.assertTrue(result); resolve(PASS_MESSAGE); } - }.bindenv(this)); + }.bindenv(this), DEVICE_ADDRESS); }.bindenv(this)); }.bindenv(this)); } @@ -284,7 +318,7 @@ class DeviceTestCase extends ImpTestCase { this.assertTrue(result); resolve(PASS_MESSAGE); } - }.bindenv(this)); + }.bindenv(this), DEVICE_ADDRESS); }.bindenv(this)); }.bindenv(this)); } @@ -305,7 +339,7 @@ class DeviceTestCase extends ImpTestCase { this.assertTrue(result.len() == quantity); resolve(message); } - }.bindenv(this)); + }.bindenv(this), DEVICE_ADDRESS); }.bindenv(this)); }.bindenv(this)); } @@ -326,7 +360,7 @@ class DeviceTestCase extends ImpTestCase { this.assertTrue(result.len() == quantity); resolve(message); } - }.bindenv(this)); + }.bindenv(this), DEVICE_ADDRESS); }.bindenv(this)); }.bindenv(this)); } @@ -347,7 +381,7 @@ class DeviceTestCase extends ImpTestCase { this.assertTrue(result.len() == quantity); resolve(message); } - }.bindenv(this)); + }.bindenv(this), DEVICE_ADDRESS); }.bindenv(this)); }.bindenv(this)); } @@ -368,7 +402,7 @@ class DeviceTestCase extends ImpTestCase { this.assertTrue(result.len() == quantity); resolve(message); } - }.bindenv(this)); + }.bindenv(this), DEVICE_ADDRESS); }.bindenv(this)); }.bindenv(this)); } @@ -385,7 +419,7 @@ class DeviceTestCase extends ImpTestCase { } else { reject(error); } - }.bindenv(this)); + }.bindenv(this), DEVICE_ADDRESS); }.bindenv(this)); }.bindenv(this)); } @@ -403,13 +437,13 @@ class DeviceTestCase extends ImpTestCase { resolve(PASS_MESSAGE); } isSuccess = true; - }.bindenv(this)); + }.bindenv(this), DEVICE_ADDRESS); _modbus.write(MODBUSRTU_TARGET_TYPE.COIL, address, quantity, value, function(error, result) { if (isSuccess) { resolve(PASS_MESSAGE); } isSuccess = true; - }.bindenv(this)); + }.bindenv(this), DEVICE_ADDRESS); }.bindenv(this)); }.bindenv(this)); } diff --git a/README.md b/README.md index 530705b..61f00ae 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,15 @@ Electric Imp’s Modbus support is delivered through the following four librarie ### [ModbusRTU](./ModbusRTU/) -This library creates and parses Modbus Protocol Data Unit (PDU). This library is a prerequisite for the Modbus485Master and ModbusTCPMaster libraries. +This library creates and parses Modbus Protocol Data Unit (PDU). This library is a prerequisite for the ModbusSerialMaster and ModbusTCPMaster libraries. -### [Modbus485Master](./Modbus485Master/) +### [ModbusSerialMaster](./ModbusSerialMaster/) -This library allows an imp to communicate with other Modbus devices via the Modbus-RS485 protocol. +This library allows an imp to communicate with other devices via Modbus-RS485 or Modbus-RS232 protocol. -### [Modbus485Slave](./Modbus485Slave/) +### [ModbusSerialSlave](./ModbusSerialSlave/) -This library empowers an imp to communicate with a Modbus Master via the RS485 protocol. +This library empowers an imp to communicate with a Modbus Master via the RS485 or RS232 protocol. ### [ModbusTCPMaster](./ModbusTCPMaster/) @@ -18,4 +18,4 @@ This library enables an imp to communicate with other Modbus devices via TCP/IP. ## License -The Modbus libraries are licensed under the [MIT License](https://github.com/electricimp/Modbus/tree/master/LICENSE). +The Modbus libraries are licensed under the [MIT License](./LICENSE).