├── .dockerignore ├── .gitignore ├── License.txt ├── README.md ├── doc └── diagram.png ├── iot-edge-modbus-template.json ├── iot-edge-modbus.json ├── iotedgeModbus ├── .gitignore ├── .vscode │ └── launch.json ├── deployment.template.json └── modules │ └── iotedgeModbus │ ├── .gitignore │ ├── ComWrapper.cs │ ├── Dockerfile.amd64 │ ├── Dockerfile.amd64.debug │ ├── Dockerfile.arm32v7 │ ├── Dockerfile.windows-amd64 │ ├── Dockerfile.windows-arm32v7 │ ├── ModbusSlave.cs │ ├── Program.cs │ ├── SerialDevice.cs │ ├── comWrapper.c │ ├── comWrapper.h │ ├── iotedgeModbus.csproj │ └── module.json └── v1 ├── License.txt ├── README.md ├── doc ├── devbox_setup.md ├── media │ ├── gateway_modbus_command_data_flow.png │ └── gateway_modbus_upload_data_flow.png └── sample_modbus.md ├── modules ├── CMakeLists.txt └── modbus_read │ ├── CMakeLists.txt │ ├── README.md │ ├── devdoc │ └── modbus_read.md │ ├── inc │ ├── modbus_read.h │ └── modbus_read_common.h │ ├── src │ └── modbus_read.c │ └── tests │ ├── CMakeLists.txt │ └── modbus_read_ut │ ├── CMakeLists.txt │ ├── main.c │ └── modbus_read_ut.cpp └── samples ├── CMakeLists.txt └── modbus_sample ├── CMakeLists.txt ├── README.md └── src ├── main.c ├── modbus_lin.json └── modbus_win.json /.dockerignore: -------------------------------------------------------------------------------- 1 | V1* 2 | bin* 3 | Docker* 4 | obj* 5 | *.json 6 | .* 7 | README* -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # .NET Core 2 | project.lock.json 3 | project.fragment.lock.json 4 | artifacts/ 5 | **/Properties/launchSettings.json 6 | */modules/*/out 7 | */.vscode/settings.json 8 | 9 | *_i.c 10 | *_p.c 11 | *_i.h 12 | *.ilk 13 | *.meta 14 | *.obj 15 | *.pch 16 | *.pdb 17 | *.pgc 18 | *.pgd 19 | *.rsp 20 | *.sbr 21 | *.tlb 22 | *.tli 23 | *.tlh 24 | *.tmp 25 | *.tmp_proj 26 | *.log 27 | *.vspscc 28 | *.vssscc 29 | .builds 30 | *.pidb 31 | *.svclog 32 | *.scc 33 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Microsoft Azure IoT Edge Modbus 2 | Copyright (c) Microsoft Corporation 3 | 4 | MIT license 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 10 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 11 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 12 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 13 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 14 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Archived # 2 | 3 | This repository is archived and no longer being maintained. 4 | 5 | # Note # 6 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments 7 | 8 | # Azure IoT Edge Modbus Module GA # 9 | Using this module, developers can build Azure IoT Edge solutions with Modbus TCP/RTU(RTU is currently not available in Windows environment, please use Linux host + Linux container to play with RTU mode) connectivity. The Modbus module is an [Azure IoT Edge](https://github.com/Azure/iot-edge) module, capable of reading data from Modbus devices and publishing data to the Azure IoT Hub via the Edge framework. Developers can modify the module tailoring to any scenario. 10 | 11 | ![](./doc/diagram.png) 12 | 13 | There are prebuilt Modbus TCP module container images ready at [here](https://hub.docker.com/r/microsoft/azureiotedge-modbus-tcp) for you to quickstart the experience of Azure IoT Edge on your target device or simulated device. 14 | 15 | Visit http://azure.com/iotdev to learn more about developing applications for Azure IoT. 16 | 17 | ## Azure IoT Edge Compatibility ## 18 | Current version of the module is targeted for the [Azure IoT Edge GA](https://azure.microsoft.com/en-us/blog/azure-iot-edge-generally-available-for-enterprise-grade-scaled-deployments/). 19 | If you are using [v1 version of IoT Edge](https://github.com/Azure/iot-edge/tree/master/v1) (previously known as Azure IoT Gateway), please use v1 version of this module, all materials can be found in [v1](https://github.com/Azure/iot-edge-modbus/tree/master/v1) folder. 20 | 21 | Find more information about Azure IoT Edge at [here](https://docs.microsoft.com/en-us/azure/iot-edge/how-iot-edge-works). 22 | 23 | ## Target Device Setup ## 24 | 25 | ### Platform Compatibility ### 26 | Azure IoT Edge is designed to be used with a broad range of operating system platforms. Modbus module has been tested on the following platforms: 27 | 28 | - Windows 10 Enterprise (version 1709) x64 29 | - Windows 10 IoT Core (version 1709) x64 30 | - Linux x64 31 | - Linux arm32v7 32 | 33 | ### Device Setup ### 34 | - [Windows 10 Desktop](https://docs.microsoft.com/en-us/azure/iot-edge/quickstart) 35 | - [Windows 10 IoT Core](https://docs.microsoft.com/en-us/azure/iot-edge/how-to-install-iot-core) 36 | - [Linux](https://docs.microsoft.com/en-us/azure/iot-edge/quickstart-linux) 37 | 38 | ## Build Environment Setup ## 39 | Modbus module is a .NET Core 2.1 application, which is developed and built based on the guidelines in Azure IoT Edge document. 40 | Please follow [this link](https://docs.microsoft.com/en-us/azure/iot-edge/tutorial-csharp-module) to setup the build environment. 41 | 42 | Basic requirement: 43 | - Docker CE 44 | - .NET Core 2.1 SDK 45 | 46 | ## HowTo Build ## 47 | In this section, the Modbus module we be built as an IoT Edge module. 48 | 49 | Open the project in VS Code, and open VS Code command palette, type and run the command Edge: Build IoT Edge solution. 50 | Select the deployment.template.json file for your solution from the command palette. 51 | ***Note: Be sure to check [configuration section](https://github.com/Azure/iot-edge-modbus#configuration) to properly set each fields before deploying the module.*** 52 | 53 | In Azure IoT Hub Devices explorer, right-click an IoT Edge device ID, then select Create deployment for IoT Edge device. 54 | Open the config folder of your solution, then select the deployment.json file. Click Select Edge Deployment Manifest. 55 | Then you can see the deployment is successfully created with a deployment ID in VS Code integrated terminal. 56 | You can check your container status in the VS Code Docker explorer or by run the docker ps command in the terminal. 57 | 58 | ## Configuration ## 59 | Before running the module, proper configuration is required. Here is a sample configuration for your reference. 60 | ```json 61 | { 62 | "PublishInterval": "2000", 63 | "Version":"2", 64 | "SlaveConfigs": { 65 | "Slave01": { 66 | "SlaveConnection": "192.168.0.1", 67 | "HwId": "PowerMeter-0a:01:01:01:01:01", 68 | "RetryCount": "10", 69 | "RetryInterval": "50", 70 | "Operations": { 71 | "Op01": { 72 | "PollingInterval": "1000", 73 | "UnitId": "1", 74 | "StartAddress": "400001", 75 | "Count": "2", 76 | "DisplayName": "Voltage", 77 | "CorrelationId": "MessageType1" 78 | }, 79 | "Op02": { 80 | "PollingInterval": "1000", 81 | "UnitId": "1", 82 | "StartAddress": "400002", 83 | "Count": "2", 84 | "DisplayName": "Current", 85 | "CorrelationId": "MessageType1" 86 | } 87 | } 88 | }, 89 | "Slave02": { 90 | "SlaveConnection": "ttyS0", 91 | "HwId": "PowerMeter-0a:01:01:01:01:02", 92 | "BaudRate": "9600", 93 | "DataBits": "8", 94 | "StopBits": "1", 95 | "Parity": "ODD", 96 | "FlowControl": "NONE", 97 | "Operations": { 98 | "Op01": { 99 | "PollingInterval": "2000", 100 | "UnitId": "1", 101 | "StartAddress": "40001", 102 | "Count": "1", 103 | "DisplayName": "Power" 104 | }, 105 | "Op02": { 106 | "PollingInterval": "2000", 107 | "UnitId": "1", 108 | "StartAddress": "40003", 109 | "Count": "1", 110 | "DisplayName": "Status" 111 | } 112 | } 113 | } 114 | } 115 | } 116 | ``` 117 | Meaning of each field: 118 | 119 | * "PublishInterval" - Interval between each push to IoT Hub in millisecond 120 | * "Version" - Switch between the PP (Public Preview) and the latest Message Payload format. (valid value for PP: "1", all other values will switch to the latest format) 121 | * "SlaveConfigs" - Contains one or more Modbus slaves' configuration. In this sample, we have "Slave01" and "Slave02" two devices: 122 | * "Slave01", "Slave02" - User defined names for each Modbus slave, cannot have duplicates under "SlaveConfigs". 123 | * "SlaveConnection" - Ipv4 address or the serial port name of the Modbus slave. 124 | * "RetryCount" - Max retry attempt for reading data, default to 10 125 | * "RetryInterval" - Retry interval between each retry attempt, default to 50 milliseconds 126 | * "HwId" - Unique Id for each Modbus slave (user defined) 127 | * "BaudRate" - Serial port communication parameter. (valid values: ...9600, 14400,19200...) 128 | * "DataBits" - Serial port communication parameter. (valid values: 7, 8) 129 | * "StopBits" - Serial port communication parameter. (valid values: 1, 1.5, 2) 130 | * "Parity" - Serial port communication parameter. (valid values: ODD, EVEN, NONE) 131 | * "FlowControl" - Serial port communication parameter. (valid values: ONLY support NONE now) 132 | * "Operations" - Contains one or more Modbus read requests. In this sample, we have "Op01" and "Op02" two read requests in both Slave01 and Slave02: 133 | * "Op01", "Op02" - User defined names for each read request, cannot have duplicates under the same "Operations" section. 134 | * "PollingInterval": Interval between each read request in millisecond 135 | * "UnitId" - The unit id to be read 136 | * "StartAddress" - The starting address of Modbus read request, currently supports both 5-digit and 6-digit [format](https://en.wikipedia.org/wiki/Modbus#Coil.2C_discrete_input.2C_input_register.2C_holding_register_numbers_and_addresses) 137 | * "Count" - Number of registers/bits to be read 138 | * "DisplayName" - Alternative name for the "StartAddress" register(s)(user defined) 139 | * "CorrelationId" - The Operations with same id with be grouped together in their output message 140 | 141 | For more about Modbus, please refer to the [Wiki](https://en.wikipedia.org/wiki/Modbus) link. 142 | 143 | ## Module Endpoints and Routing ## 144 | There are two endpoints defined in Modbus TCP module: 145 | - "modbusOutput": This is a output endpoint for telemetries. All read operations defined in configuration will be composed as telemetry messages output to this endpoint. 146 | - "input1": This is an input endpoint for write commands. 147 | 148 | Input/Output message format and Routing rules are introduced below. 149 | 150 | ### Read from Modbus ### 151 | 152 | #### Telemetry Message #### 153 | Message Properties: 154 | ```json 155 | "content-type": "application/edge-modbus-json" 156 | ``` 157 | Latest Message Payload: 158 | ```json 159 | [ 160 | { 161 | "PublishTimestamp": "2018-04-17 12:28:53", 162 | "Content": [ 163 | { 164 | "HwId": "PowerMeter-0a:01:01:01:01:02", 165 | "Data": [ 166 | { 167 | "CorrelationId": "MessageType1", 168 | "SourceTimestamp": "2018-04-17 12:28:48", 169 | "Values": [ 170 | { 171 | "DisplayName": "Op02", 172 | "Address": "40003", 173 | "Value": "2785" 174 | }, 175 | { 176 | "DisplayName": "Op02", 177 | "Address": "40004", 178 | "Value": "18529" 179 | }, 180 | { 181 | "DisplayName": "Op01", 182 | "Address": "40001", 183 | "Value": "1840" 184 | }, 185 | { 186 | "DisplayName": "Op01", 187 | "Address": "40002", 188 | "Value": "31497" 189 | } 190 | ] 191 | }, 192 | { 193 | "CorrelationId": "MessageType1", 194 | "SourceTimestamp": "2018-04-17 12:28:50", 195 | "Values": [ 196 | { 197 | "DisplayName": "Op02", 198 | "Address": "40003", 199 | "Value": "21578" 200 | }, 201 | { 202 | "DisplayName": "Op02", 203 | "Address": "40004", 204 | "Value": "26979" 205 | }, 206 | { 207 | "DisplayName": "Op01", 208 | "Address": "40001", 209 | "Value": "13210" 210 | }, 211 | { 212 | "DisplayName": "Op01", 213 | "Address": "40002", 214 | "Value": "13549" 215 | } 216 | ] 217 | } 218 | ] 219 | } 220 | ] 221 | } 222 | ] 223 | ``` 224 | PP (Public Preview) Message Payload: 225 | ```json 226 | [ 227 | { 228 | "DisplayName":"RotaryOne", 229 | "HwId":"Wise4012E", 230 | "Address":"40001", 231 | "Value":"0", 232 | "SourceTimestamp":"2018-09-18 04:14:32" 233 | }, 234 | { 235 | "DisplayName":"SwitchOne", 236 | "HwId":"Wise4012E", 237 | "Address":"00001", 238 | "Value":"1", 239 | "SourceTimestamp":"2018-09-18 04:14:33" 240 | }, 241 | { 242 | "DisplayName":"RelayOne", 243 | "HwId":"Wise4012E", 244 | "Address":"00017", 245 | "Value":"0", 246 | "SourceTimestamp":"2018-09-18 04:14:33" 247 | } 248 | ] 249 | ``` 250 | 251 | #### Route to IoT Hub #### 252 | ```json 253 | { 254 | "routes": { 255 | "modbusToIoTHub":"FROM /messages/modules/modbus/outputs/modbusOutput INTO $upstream" 256 | } 257 | } 258 | ``` 259 | 260 | #### Route to other (filter) modules #### 261 | ```json 262 | { 263 | "routes": { 264 | "modbusToFilter":"FROM /messages/modules/modbus/outputs/modbusOutput INTO BrokeredEndpoint(\"/modules/filtermodule/inputs/input1\")" 265 | } 266 | } 267 | ``` 268 | 269 | ### Write to Modbus ### 270 | Modbus module use input endpoint "input1" to receive commands. Currently it supports writing back to a single register/cell in a Modbus slave. 271 | ***Note: Currently IoT Edge only supports send messages into one module from another module, direct C2D messages doesn't work.*** 272 | 273 | #### Command Message #### 274 | The content of command must be the following message format. 275 | 276 | Message Properties: 277 | ```json 278 | "command-type": "ModbusWrite" 279 | ``` 280 | 281 | Message Payload: 282 | ```json 283 | { 284 | "HwId":"PowerMeter-0a:01:01:01:01:01", 285 | "UId":"1", 286 | "Address":"40001", 287 | "Value":"15" 288 | } 289 | ``` 290 | 291 | #### Route from other (filter) modules #### 292 | The command should have a property "command-type" with value "ModbusWrite". Also, routing must be enabled by specifying rule like below. 293 | ```json 294 | { 295 | "routes": { 296 | "filterToModbus":"FROM /messages/modules/filtermodule/outputs/output1 INTO BrokeredEndpoint(\"/modules/modbus/inputs/input1\")" 297 | } 298 | } 299 | ``` 300 | 301 | ## HowTo Run ## 302 | 303 | ### Run as an IoT Edge module ### 304 | Please follow the [link](https://docs.microsoft.com/en-us/azure/iot-edge/tutorial-csharp-module) to deploy the module as an IoT Edge module. 305 | 306 | #### Configure Modbus RTU #### 307 | This is for Modbus RTU only, Modbus TCP could skip this section. 308 | 309 | In the **Container Create Option section**, enter the following for device mapping. 310 | ```json 311 | { 312 | "HostConfig": { 313 | "Devices": [ 314 | { 315 | "PathOnHost": "", 316 | "PathInContainer": "", 317 | "CgroupPermissions": "rwm" 318 | } 319 | ] 320 | } 321 | } 322 | ``` 323 | -------------------------------------------------------------------------------- /doc/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/iot-edge-modbus/ed85c4077be30fdcfd98e9c51db0ebe202586df6/doc/diagram.png -------------------------------------------------------------------------------- /iot-edge-modbus-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "PublishInterval": "push interval in millisecond", 3 | "SlaveConfigs": { 4 | "Slave01": { 5 | "SlaveConnection": "ipv4 address or the serial port name to Modbus device", 6 | "TcpPort": "tcp port of Modbus connection, default is 502", 7 | "RetryCount": "the max retry attempt when socket receive buffer is empty, default is 10", 8 | "RetryInterval": "the wait interval in millisecond between each retry, default is 50", 9 | "HwId": "unique HW Id defined by user", 10 | "BaudRate": "baud rate of serial communication (Modbus RTU only)", 11 | "DataBits": "data bits of serial communication (Modbus RTU only)", 12 | "StopBits": "stop bits of serial communication (Modbus RTU only)", 13 | "Parity": "parity of serial communication (Modbus RTU only)", 14 | "FlowControl": "flow control of serial communication (Modbus RTU only)", 15 | "Operations": { 16 | "Op01": { 17 | "PollingInterval": "polling interval in millisecond", 18 | "UnitId": "unit id of Modbus device", 19 | "StartAddress": "starting address of read request", 20 | "Count": "unit count of read request", 21 | "DisplayName": "alternative name defined by user", 22 | "CorrelationId": "id used to group output message" 23 | } 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /iot-edge-modbus.json: -------------------------------------------------------------------------------- 1 | { 2 | "PublishInterval": "5000", 3 | "SlaveConfigs": { 4 | "Slave01": { 5 | "SlaveConnection": "127.0.0.1", 6 | "TcpPort": "502", 7 | "RetryCount": "10", 8 | "RetryInterval": "100", 9 | "HwId": "PowerMeter-0a:01:01:01:01:01", 10 | "Operations": { 11 | "Op01": { 12 | "PollingInterval": "2000", 13 | "UnitId": "1", 14 | "StartAddress": "400001", 15 | "Count": "2", 16 | "CorrelationId": "MessageType1", 17 | "DisplayName": "Voltage" 18 | }, 19 | "Op02": { 20 | "PollingInterval": "2000", 21 | "UnitId": "1", 22 | "StartAddress": "400002", 23 | "Count": "2", 24 | "CorrelationId": "MessageType2", 25 | "DisplayName": "Current" 26 | } 27 | } 28 | }, 29 | "Slave02": { 30 | "SlaveConnection": "ttyS1", 31 | "RetryCount": "10", 32 | "RetryInterval": "50", 33 | "HwId": "PowerMeter-0a:01:01:01:01:02", 34 | "BaudRate": "9600", 35 | "DataBits": "8", 36 | "StopBits": "1", 37 | "Parity": "ODD", 38 | "FlowControl": "NONE", 39 | "Operations": { 40 | "Op01": { 41 | "PollingInterval": "2000", 42 | "UnitId": "1", 43 | "StartAddress": "40001", 44 | "Count": "2", 45 | "DisplayName": "Voltage" 46 | }, 47 | "Op02": { 48 | "PollingInterval": "2000", 49 | "UnitId": "1", 50 | "StartAddress": "40002", 51 | "Count": "2", 52 | "DisplayName": "Current" 53 | } 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /iotedgeModbus/.gitignore: -------------------------------------------------------------------------------- 1 | config/ 2 | .env -------------------------------------------------------------------------------- /iotedgeModbus/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "iotedgeModbus Remote Debug (.NET Core)", 6 | "type": "coreclr", 7 | "request": "attach", 8 | "processId": "${command:pickRemoteProcess}", 9 | "pipeTransport": { 10 | "pipeProgram": "docker", 11 | "pipeArgs": [ 12 | "exec", 13 | "-i", 14 | "modbus", 15 | "sh", 16 | "-c" 17 | ], 18 | "debuggerPath": "~/vsdbg/vsdbg", 19 | "pipeCwd": "${workspaceFolder}", 20 | "quoteArgs": true 21 | }, 22 | "sourceFileMap": { 23 | "/app": "${workspaceFolder}/modules/iotedgeModbus" 24 | }, 25 | "justMyCode": true 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /iotedgeModbus/deployment.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleContent": { 3 | "$edgeAgent": { 4 | "properties.desired": { 5 | "schemaVersion": "1.0", 6 | "runtime": { 7 | "type": "docker", 8 | "settings": { 9 | "minDockerVersion": "v1.25", 10 | "loggingOptions": "", 11 | "registryCredentials": {} 12 | } 13 | }, 14 | "systemModules": { 15 | "edgeAgent": { 16 | "type": "docker", 17 | "settings": { 18 | "image": "mcr.microsoft.com/azureiotedge-agent:1.0", 19 | "createOptions": "" 20 | } 21 | }, 22 | "edgeHub": { 23 | "type": "docker", 24 | "status": "running", 25 | "restartPolicy": "always", 26 | "settings": { 27 | "image": "mcr.microsoft.com/azureiotedge-hub:1.0", 28 | "createOptions": "{\"HostConfig\":{\"PortBindings\":{\"8883/tcp\":[{\"HostPort\":\"8883\"}],\"443/tcp\":[{\"HostPort\":\"443\"}]}}}" 29 | } 30 | } 31 | }, 32 | "modules": { 33 | "modbus": { 34 | "version": "1.0", 35 | "type": "docker", 36 | "status": "running", 37 | "restartPolicy": "always", 38 | "settings": { 39 | "image": "${MODULES.iotedgeModbus.amd64}", 40 | "createOptions": "" 41 | } 42 | } 43 | } 44 | } 45 | }, 46 | "$edgeHub": { 47 | "properties.desired": { 48 | "schemaVersion": "1.0", 49 | "routes": { 50 | "iotedgeModbusToIoTHub": "FROM /messages/modules/modbus/outputs/modbusOutput INTO $upstream" 51 | }, 52 | "storeAndForwardConfiguration": { 53 | "timeToLiveSecs": 7200 54 | } 55 | } 56 | }, 57 | "modbus": { 58 | "properties.desired":{ 59 | "PublishInterval": "5000", 60 | "SlaveConfigs": { 61 | "Slave01": { 62 | "SlaveConnection": "", 63 | "TcpPort": "502", 64 | "RetryCount": "10", 65 | "RetryInterval": "100", 66 | "HwId": "PowerMeter-0a:01:01:01:01:01", 67 | "Operations": { 68 | "Op01": { 69 | "PollingInterval": "2000", 70 | "UnitId": "1", 71 | "StartAddress": "400001", 72 | "Count": "2", 73 | "CorrelationId": "MessageType1", 74 | "DisplayName": "Voltage" 75 | }, 76 | "Op02": { 77 | "PollingInterval": "2000", 78 | "UnitId": "1", 79 | "StartAddress": "400002", 80 | "Count": "2", 81 | "CorrelationId": "MessageType2", 82 | "DisplayName": "Current" 83 | } 84 | } 85 | }, 86 | "Slave02": { 87 | "SlaveConnection": "", 88 | "RetryCount": "10", 89 | "RetryInterval": "50", 90 | "HwId": "PowerMeter-0a:01:01:01:01:02", 91 | "BaudRate": "9600", 92 | "DataBits": "8", 93 | "StopBits": "1", 94 | "Parity": "ODD", 95 | "FlowControl": "NONE", 96 | "Operations": { 97 | "Op01": { 98 | "PollingInterval": "2000", 99 | "UnitId": "1", 100 | "StartAddress": "40001", 101 | "Count": "2", 102 | "DisplayName": "Voltage" 103 | }, 104 | "Op02": { 105 | "PollingInterval": "2000", 106 | "UnitId": "1", 107 | "StartAddress": "40002", 108 | "Count": "2", 109 | "DisplayName": "Current" 110 | } 111 | } 112 | } 113 | } 114 | } 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /iotedgeModbus/modules/iotedgeModbus/.gitignore: -------------------------------------------------------------------------------- 1 | # .NET Core 2 | project.lock.json 3 | project.fragment.lock.json 4 | artifacts/ 5 | **/Properties/launchSettings.json 6 | 7 | *_i.c 8 | *_p.c 9 | *_i.h 10 | *.ilk 11 | *.meta 12 | *.obj 13 | *.pch 14 | *.pdb 15 | *.pgc 16 | *.pgd 17 | *.rsp 18 | *.sbr 19 | *.tlb 20 | *.tli 21 | *.tlh 22 | *.tmp 23 | *.tmp_proj 24 | *.log 25 | *.vspscc 26 | *.vssscc 27 | .builds 28 | *.pidb 29 | *.svclog 30 | *.scc 31 | .vs 32 | 33 | [Bb]in/ 34 | [Oo]bj/ -------------------------------------------------------------------------------- /iotedgeModbus/modules/iotedgeModbus/ComWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace tempSerialPort 5 | { 6 | public static class ComWrapper 7 | { 8 | public static void OutPutByteArray(byte[] arr) 9 | { 10 | int count = 0; 11 | foreach (byte x in arr) 12 | { 13 | Console.Write($"{x.ToString()} "); 14 | count++; 15 | if (count == 4) 16 | { 17 | Console.WriteLine(""); 18 | count = 0; 19 | } 20 | } 21 | } 22 | public static byte[] StructureToByteArray(T obj) 23 | { 24 | int size = Marshal.SizeOf(obj); 25 | byte[] arr = new byte[size]; 26 | 27 | IntPtr ptr = Marshal.AllocHGlobal(size); 28 | Marshal.StructureToPtr(obj, ptr, true); 29 | Marshal.Copy(ptr, arr, 0, size); 30 | Marshal.FreeHGlobal(ptr); 31 | return arr; 32 | } 33 | public static T ByteArrayToStructure(byte[] bytes) where T : struct 34 | { 35 | var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); 36 | try 37 | { 38 | return (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); 39 | } 40 | finally 41 | { 42 | handle.Free(); 43 | } 44 | } 45 | 46 | [DllImport("libcomWrapper.so")] 47 | public static extern int com_open(string pathname); 48 | 49 | [DllImport("libcomWrapper.so")] 50 | public static extern int com_close(int fd); 51 | 52 | [DllImport("libcomWrapper.so")] 53 | public static extern int com_read(int fd, IntPtr buf, int count); 54 | 55 | [DllImport("libcomWrapper.so")] 56 | public static extern int com_write(int fd, IntPtr buf, int count); 57 | 58 | [DllImport("libcomWrapper.so")] 59 | public static extern int com_tciflush(int fd); 60 | 61 | [DllImport("libcomWrapper.so")] 62 | public static extern int com_tcoflush(int fd); 63 | 64 | [DllImport("libcomWrapper.so")] 65 | public static extern int com_set_interface_attribs(int fd, int speed, int data_bits, int parity_bit, int stop_bit); 66 | } 67 | } -------------------------------------------------------------------------------- /iotedgeModbus/modules/iotedgeModbus/Dockerfile.amd64: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:2.1-sdk AS build-env 2 | WORKDIR /app 3 | 4 | COPY *.csproj ./ 5 | RUN dotnet restore 6 | 7 | COPY . ./ 8 | RUN dotnet publish -c Release -o out 9 | 10 | FROM gcc:7 AS build-env-2 11 | WORKDIR /app 12 | 13 | # copy .c and .h file 14 | COPY *.c ./ 15 | COPY *.h ./ 16 | 17 | # build 18 | RUN gcc -shared -o libcomWrapper.so -fPIC comWrapper.c 19 | 20 | FROM microsoft/dotnet:2.1-runtime 21 | WORKDIR /app 22 | COPY --from=build-env /app/out ./ 23 | COPY --from=build-env-2 /app/libcomWrapper.so /usr/lib/ 24 | 25 | RUN useradd -ms /bin/bash moduleuser 26 | USER moduleuser 27 | 28 | ENTRYPOINT ["dotnet", "iotedgeModbus.dll"] -------------------------------------------------------------------------------- /iotedgeModbus/modules/iotedgeModbus/Dockerfile.amd64.debug: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:2.0-runtime-stretch AS base 2 | 3 | RUN apt-get update && \ 4 | apt-get install -y --no-install-recommends unzip procps && \ 5 | rm -rf /var/lib/apt/lists/* 6 | 7 | RUN useradd -ms /bin/bash moduleuser 8 | USER moduleuser 9 | RUN curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l ~/vsdbg 10 | 11 | FROM microsoft/dotnet:2.1-sdk AS build-env 12 | WORKDIR /app 13 | 14 | COPY *.csproj ./ 15 | RUN dotnet restore 16 | 17 | COPY . ./ 18 | RUN dotnet publish -c Debug -o out 19 | 20 | FROM gcc:7 AS build-env-2 21 | WORKDIR /app 22 | 23 | # copy .c and .h file 24 | COPY *.c ./ 25 | COPY *.h ./ 26 | 27 | # build 28 | RUN gcc -shared -o libcomWrapper.so -fPIC comWrapper.c 29 | 30 | FROM base 31 | WORKDIR /app 32 | COPY --from=build-env /app/out ./ 33 | COPY --from=build-env-2 /app/libcomWrapper.so /usr/lib/ 34 | 35 | ENTRYPOINT ["dotnet", "iotedgeModbus.dll"] -------------------------------------------------------------------------------- /iotedgeModbus/modules/iotedgeModbus/Dockerfile.arm32v7: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:2.1-sdk AS build-env 2 | WORKDIR /app 3 | 4 | COPY *.csproj ./ 5 | RUN dotnet restore 6 | 7 | COPY . ./ 8 | RUN dotnet publish -c Release -o out 9 | 10 | FROM arm32v7/gcc:7 AS build-env-2 11 | WORKDIR /app 12 | 13 | # copy .c and .h file 14 | COPY *.c ./ 15 | COPY *.h ./ 16 | 17 | # build 18 | RUN gcc -shared -o libcomWrapper.so -fPIC comWrapper.c 19 | 20 | FROM microsoft/dotnet:2.1-runtime-stretch-slim-arm32v7 21 | WORKDIR /app 22 | COPY --from=build-env /app/out ./ 23 | COPY --from=build-env-2 /app/libcomWrapper.so /usr/lib/ 24 | 25 | RUN useradd -ms /bin/bash moduleuser 26 | USER moduleuser 27 | 28 | CMD ["dotnet", "iotedgeModbus.dll"] -------------------------------------------------------------------------------- /iotedgeModbus/modules/iotedgeModbus/Dockerfile.windows-amd64: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:2.1-sdk AS build-env 2 | WORKDIR /app 3 | 4 | COPY *.csproj ./ 5 | RUN dotnet restore 6 | 7 | COPY . ./ 8 | RUN dotnet publish -c Release -r win10-x64 -o out 9 | 10 | FROM mcr.microsoft.com/windows/nanoserver:1809 11 | WORKDIR /app 12 | COPY --from=build-env /app/out ./ 13 | CMD ["iotedgeModbus.exe"] -------------------------------------------------------------------------------- /iotedgeModbus/modules/iotedgeModbus/Dockerfile.windows-arm32v7: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:2.1-sdk AS build-env 2 | WORKDIR /app 3 | 4 | COPY *.csproj ./ 5 | RUN dotnet restore -r win10-arm 6 | 7 | COPY . ./ 8 | RUN dotnet publish -c Release -r win10-arm -o out 9 | 10 | FROM mcr.microsoft.com/windows/nanoserver:1809_arm 11 | WORKDIR /app 12 | COPY --from=build-env /app/out ./ 13 | CMD ["iotedgeModbus.exe"] -------------------------------------------------------------------------------- /iotedgeModbus/modules/iotedgeModbus/ModbusSlave.cs: -------------------------------------------------------------------------------- 1 | namespace Modbus.Slaves 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Net; 8 | using System.Net.Sockets; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using tempSerialPort; 12 | using System.IO.Ports; 13 | using System.Runtime.InteropServices; 14 | 15 | /* Modbus Frame Details 16 | ----------------------- -------- 17 | |MBAP Header description|Length | 18 | ----------------------- -------- 19 | |Transaction Identifier |2 bytes | 20 | ----------------------- -------- 21 | |Protocol Identifier |2 bytes | 22 | ----------------------- -------- 23 | |Length 2bytes |2 bytes | 24 | ----------------------- -------- 25 | |Unit Identifier |1 byte | 26 | ----------------------- -------- 27 | |Body |variable| 28 | ----------------------- -------- 29 | */ 30 | 31 | /// 32 | /// This class contains the handle for this module. In this case, it is a list of active Modbus sessions. 33 | /// 34 | class ModuleHandle 35 | { 36 | public static async Task CreateHandleFromConfiguration(ModuleConfig config) 37 | { 38 | Modbus.Slaves.ModuleHandle moduleHandle = null; 39 | foreach (var config_pair in config.SlaveConfigs) 40 | { 41 | ModbusSlaveConfig slaveConfig = config_pair.Value; 42 | switch (slaveConfig.GetConnectionType()) 43 | { 44 | case ModbusConstants.ConnectionType.ModbusTCP: 45 | { 46 | if (moduleHandle == null) 47 | { 48 | moduleHandle = new Modbus.Slaves.ModuleHandle(); 49 | } 50 | 51 | ModbusSlaveSession slave = new ModbusTCPSlaveSession(slaveConfig); 52 | await slave.InitSession(); 53 | moduleHandle.ModbusSessionList.Add(slave); 54 | break; 55 | } 56 | case ModbusConstants.ConnectionType.ModbusRTU: 57 | { 58 | if (moduleHandle == null) 59 | { 60 | moduleHandle = new Modbus.Slaves.ModuleHandle(); 61 | } 62 | 63 | ModbusSlaveSession slave = new ModbusRTUSlaveSession(slaveConfig); 64 | await slave.InitSession(); 65 | moduleHandle.ModbusSessionList.Add(slave); 66 | break; 67 | } 68 | case ModbusConstants.ConnectionType.ModbusASCII: 69 | { 70 | break; 71 | } 72 | case ModbusConstants.ConnectionType.Unknown: 73 | { 74 | break; 75 | } 76 | } 77 | } 78 | return moduleHandle; 79 | } 80 | public List ModbusSessionList = new List(); 81 | public ModbusSlaveSession GetSlaveSession(string hwid) 82 | { 83 | return ModbusSessionList.Find(x => x.config.HwId.ToUpper() == hwid.ToUpper()); 84 | } 85 | public void Release() 86 | { 87 | foreach (var session in ModbusSessionList) 88 | { 89 | session.ReleaseSession(); 90 | } 91 | ModbusSessionList.Clear(); 92 | } 93 | public List CollectAndResetOutMessageFromSessions() 94 | { 95 | List obj_list = new List(); 96 | 97 | foreach (ModbusSlaveSession session in ModbusSessionList) 98 | { 99 | var obj = session.GetOutMessage(); 100 | if (obj != null) 101 | { 102 | obj_list.Add(obj); 103 | session.ClearOutMessage(); 104 | } 105 | } 106 | return obj_list; 107 | } 108 | 109 | public List CollectAndResetOutMessageFromSessionsV1() 110 | { 111 | List obj_list = new List(); 112 | 113 | foreach (ModbusSlaveSession session in ModbusSessionList) 114 | { 115 | var obj = session.GetOutMessage(); 116 | if (obj != null) 117 | { 118 | var content = (obj as ModbusOutContent); 119 | 120 | string hwId = content.HwId; 121 | 122 | foreach (var data in content.Data) 123 | { 124 | var sourceTimestamp = data.SourceTimestamp; 125 | 126 | foreach (var value in data.Values) 127 | { 128 | obj_list.Add(new ModbusOutMessageV1 129 | { 130 | HwId = hwId, 131 | SourceTimestamp = sourceTimestamp, 132 | Address = value.Address, 133 | DisplayName = value.DisplayName, 134 | Value = value.Value, 135 | }); 136 | } 137 | } 138 | 139 | session.ClearOutMessage(); 140 | } 141 | } 142 | 143 | return obj_list; 144 | } 145 | } 146 | 147 | /// 148 | /// Base class of Modbus session. 149 | /// 150 | abstract class ModbusSlaveSession 151 | { 152 | public ModbusSlaveConfig config; 153 | protected object OutMessage = null; 154 | protected const int m_bufSize = 512; 155 | protected SemaphoreSlim m_semaphore_collection = new SemaphoreSlim(1, 1); 156 | protected SemaphoreSlim m_semaphore_connection = new SemaphoreSlim(1, 1); 157 | protected bool m_run = false; 158 | protected List m_taskList = new List(); 159 | protected virtual int m_reqSize { get; } 160 | protected virtual int m_dataBodyOffset { get; } 161 | protected virtual int m_silent { get; } 162 | 163 | #region Constructors 164 | public ModbusSlaveSession(ModbusSlaveConfig conf) 165 | { 166 | config = conf; 167 | } 168 | #endregion 169 | 170 | #region Public Methods 171 | public abstract void ReleaseSession(); 172 | public async Task InitSession() 173 | { 174 | await ConnectSlave(); 175 | 176 | foreach (var op_pair in config.Operations) 177 | { 178 | ReadOperation x = op_pair.Value; 179 | 180 | x.RequestLen = m_reqSize; 181 | x.Request = new byte[m_bufSize]; 182 | 183 | EncodeRead(x); 184 | } 185 | } 186 | public async Task WriteCB(string uid, string address, string value) 187 | { 188 | byte[] writeRequest = new byte[m_bufSize]; 189 | byte[] writeResponse = null; 190 | int reqLen = m_reqSize; 191 | 192 | EncodeWrite(writeRequest, uid, address, value); 193 | writeResponse = await SendRequest(writeRequest, reqLen); 194 | } 195 | public void ProcessOperations() 196 | { 197 | m_run = true; 198 | foreach (var op_pair in config.Operations) 199 | { 200 | ReadOperation x = op_pair.Value; 201 | Task t = Task.Run(async () => await SingleOperation(x)); 202 | m_taskList.Add(t); 203 | } 204 | } 205 | public object GetOutMessage() 206 | { 207 | return OutMessage; 208 | } 209 | public void ClearOutMessage() 210 | { 211 | m_semaphore_collection.Wait(); 212 | 213 | OutMessage = null; 214 | 215 | m_semaphore_collection.Release(); 216 | } 217 | #endregion 218 | 219 | #region Protected Methods 220 | protected abstract void EncodeWrite(byte[] request, string uid, string address, string value); 221 | protected abstract Task SendRequest(byte[] request, int reqLen); 222 | protected abstract Task ConnectSlave(); 223 | protected abstract void EncodeRead(ReadOperation operation); 224 | protected async Task SingleOperation(ReadOperation x) 225 | { 226 | while (m_run) 227 | { 228 | x.Response = null; 229 | x.Response = await SendRequest(x.Request, x.RequestLen); 230 | 231 | if (x.Response != null) 232 | { 233 | if (x.Request[m_dataBodyOffset] == x.Response[m_dataBodyOffset]) 234 | { 235 | ProcessResponse(config, x); 236 | } 237 | else if (x.Request[m_dataBodyOffset] + ModbusConstants.ModbusExceptionCode == x.Response[m_dataBodyOffset]) 238 | { 239 | Console.WriteLine($"Modbus exception code: {x.Response[m_dataBodyOffset + 1]}"); 240 | } 241 | } 242 | await Task.Delay(x.PollingInterval - m_silent); 243 | } 244 | } 245 | protected void ProcessResponse(ModbusSlaveConfig config, ReadOperation x) 246 | { 247 | int count = 0; 248 | int step_size = 0; 249 | int start_digit = 0; 250 | List value_list = new List(); 251 | switch (x.Response[m_dataBodyOffset])//function code 252 | { 253 | case (byte)ModbusConstants.FunctionCodeType.ReadCoils: 254 | case (byte)ModbusConstants.FunctionCodeType.ReadInputs: 255 | { 256 | count = x.Response[m_dataBodyOffset + 1] * 8; 257 | count = (count > x.Count) ? x.Count : count; 258 | step_size = 1; 259 | start_digit = x.Response[m_dataBodyOffset] - 1; 260 | break; 261 | } 262 | case (byte)ModbusConstants.FunctionCodeType.ReadHoldingRegisters: 263 | case (byte)ModbusConstants.FunctionCodeType.ReadInputRegisters: 264 | { 265 | count = x.Response[m_dataBodyOffset + 1]; 266 | step_size = 2; 267 | start_digit = (x.Response[m_dataBodyOffset] == 3) ? 4 : 3; 268 | break; 269 | } 270 | } 271 | for (int i = 0; i < count; i += step_size) 272 | { 273 | string res = ""; 274 | string cell = ""; 275 | string val = ""; 276 | if (step_size == 1) 277 | { 278 | cell = string.Format(x.OutFormat, (char)x.EntityType, x.Address + i + 1); 279 | val = string.Format("{0}", (x.Response[m_dataBodyOffset + 2 + (i / 8)] >> (i % 8)) & 0b1); 280 | } 281 | else if (step_size == 2) 282 | { 283 | cell = string.Format(x.OutFormat, (char)x.EntityType, x.Address + (i / 2) + 1); 284 | val = string.Format("{0,00000}", ((x.Response[m_dataBodyOffset + 2 + i]) * 0x100 + x.Response[m_dataBodyOffset + 3 + i])); 285 | } 286 | res = cell + ": " + val + "\n"; 287 | Console.WriteLine(res); 288 | 289 | ModbusOutValue value = new ModbusOutValue() 290 | { DisplayName = x.DisplayName, Address = cell, Value = val }; 291 | value_list.Add(value); 292 | } 293 | 294 | if (value_list.Count > 0) 295 | PrepareOutMessage(config.HwId, x.CorrelationId, value_list); 296 | } 297 | protected void PrepareOutMessage(string HwId, string CorrelationId, List ValueList) 298 | { 299 | m_semaphore_collection.Wait(); 300 | ModbusOutContent content = null; 301 | if (OutMessage == null) 302 | { 303 | content = new ModbusOutContent 304 | { 305 | HwId = HwId, 306 | Data = new List() 307 | }; 308 | OutMessage = content; 309 | } 310 | else 311 | { 312 | content = (ModbusOutContent)OutMessage; 313 | } 314 | 315 | string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); 316 | ModbusOutData data = null; 317 | foreach(var d in content.Data) 318 | { 319 | if (d.CorrelationId == CorrelationId && d.SourceTimestamp == timestamp) 320 | { 321 | data = d; 322 | break; 323 | } 324 | } 325 | if(data == null) 326 | { 327 | data = new ModbusOutData 328 | { 329 | CorrelationId = CorrelationId, 330 | SourceTimestamp = timestamp, 331 | Values = new List() 332 | }; 333 | content.Data.Add(data); 334 | } 335 | 336 | data.Values.AddRange(ValueList); 337 | 338 | m_semaphore_collection.Release(); 339 | 340 | } 341 | protected void ReleaseOperations() 342 | { 343 | m_run = false; 344 | Task.WaitAll(m_taskList.ToArray()); 345 | m_taskList.Clear(); 346 | } 347 | #endregion 348 | } 349 | 350 | /// 351 | /// This class is Modbus TCP session. 352 | /// 353 | class ModbusTCPSlaveSession : ModbusSlaveSession 354 | { 355 | #region Constructors 356 | public ModbusTCPSlaveSession(ModbusSlaveConfig conf) 357 | : base(conf) 358 | { 359 | } 360 | #endregion 361 | 362 | #region Protected Properties 363 | protected override int m_reqSize { get { return 12; } } 364 | protected override int m_dataBodyOffset { get { return 7; } } 365 | protected override int m_silent { get { return 0; } } 366 | #endregion 367 | 368 | #region Private Fields 369 | private Socket m_socket = null; 370 | private IPAddress m_address = null; 371 | #endregion 372 | 373 | #region Public Methods 374 | public override void ReleaseSession() 375 | { 376 | ReleaseOperations(); 377 | if (m_socket != null) 378 | { 379 | m_socket.Disconnect(false); 380 | m_socket.Dispose(); 381 | m_socket = null; 382 | } 383 | } 384 | #endregion 385 | 386 | #region Private Methods 387 | protected override async Task ConnectSlave() 388 | { 389 | if (IPAddress.TryParse(config.SlaveConnection, out m_address)) 390 | { 391 | try 392 | { 393 | m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) 394 | { 395 | ReceiveTimeout = 100 396 | }; 397 | await m_socket.ConnectAsync(m_address, config.TcpPort.Value); 398 | } 399 | catch (Exception e) 400 | { 401 | Console.WriteLine("Connect Slave failed"); 402 | Console.WriteLine(e.Message); 403 | m_socket = null; 404 | } 405 | } 406 | } 407 | protected override void EncodeRead(ReadOperation operation) 408 | { 409 | //MBAP 410 | //transaction id 2 bytes 411 | operation.Request[0] = 0; 412 | operation.Request[1] = 0; 413 | //protocol id 2 bytes 414 | operation.Request[2] = 0; 415 | operation.Request[3] = 0; 416 | //length 417 | byte[] len_byte = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((Int16)6)); 418 | operation.Request[4] = len_byte[0]; 419 | operation.Request[5] = len_byte[1]; 420 | //uid 421 | operation.Request[6] = operation.UnitId; 422 | 423 | //Body 424 | //function code 425 | operation.Request[m_dataBodyOffset] = operation.FunctionCode; 426 | //address 427 | byte[] address_byte = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((Int16)(operation.Address))); 428 | operation.Request[m_dataBodyOffset + 1] = address_byte[0]; 429 | operation.Request[m_dataBodyOffset + 2] = address_byte[1]; 430 | //count 431 | byte[] count_byte = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((Int16)operation.Count)); 432 | operation.Request[m_dataBodyOffset + 3] = count_byte[0]; 433 | operation.Request[m_dataBodyOffset + 4] = count_byte[1]; 434 | } 435 | protected override void EncodeWrite(byte[] request, string uid, string address, string value) 436 | { 437 | //MBAP 438 | //transaction id 2 bytes 439 | request[0] = 0; 440 | request[1] = 0; 441 | //protocol id 2 bytes 442 | request[2] = 0; 443 | request[3] = 0; 444 | //length 445 | byte[] len_byte = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((Int16)6)); 446 | request[4] = len_byte[0]; 447 | request[5] = len_byte[1]; 448 | //uid 449 | request[6] = Convert.ToByte(uid); 450 | 451 | //Body 452 | //function code 453 | ModuleConfig.ParseEntity(address, false, out ushort address_int16, out request[m_dataBodyOffset], out byte entity_type); 454 | 455 | //address 456 | byte[] address_byte = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((Int16)(address_int16))); 457 | request[m_dataBodyOffset + 1] = address_byte[0]; 458 | request[m_dataBodyOffset + 2] = address_byte[1]; 459 | //value 460 | UInt16 value_int = (UInt16)Convert.ToInt32(value); 461 | if (entity_type == '0' && value_int == 1) 462 | { 463 | request[m_dataBodyOffset + 3] = 0xFF; 464 | request[m_dataBodyOffset + 4] = 0x00; 465 | } 466 | else 467 | { 468 | byte[] val_byte = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((Int16)value_int)); 469 | request[m_dataBodyOffset + 3] = val_byte[0]; 470 | request[m_dataBodyOffset + 4] = val_byte[1]; 471 | } 472 | } 473 | protected override async Task SendRequest(byte[] request, int reqLen) 474 | { 475 | byte[] response = null; 476 | byte[] garbage = new byte[m_bufSize]; 477 | int retryForSocketError = 0; 478 | bool sendSucceed = false; 479 | 480 | while (!sendSucceed && retryForSocketError < config.RetryCount) 481 | { 482 | retryForSocketError++; 483 | m_semaphore_connection.Wait(); 484 | 485 | if (m_socket != null && m_socket.Connected) 486 | { 487 | try 488 | { 489 | // clear receive buffer 490 | while (m_socket.Available > 0) 491 | { 492 | int rec = m_socket.Receive(garbage, m_bufSize, SocketFlags.None); 493 | Console.WriteLine("Dumping socket receive buffer..."); 494 | string data = ""; 495 | int cnt = 0; 496 | while (cnt < rec) 497 | { 498 | data += garbage[cnt].ToString(); 499 | cnt++; 500 | } 501 | Console.WriteLine(data); 502 | } 503 | 504 | // send request 505 | m_socket.Send(request, reqLen, SocketFlags.None); 506 | 507 | // read response 508 | response = ReadResponse(); 509 | sendSucceed = true; 510 | } 511 | catch (Exception e) 512 | { 513 | Console.WriteLine("Something wrong with the socket, disposing..."); 514 | Console.WriteLine(e.Message); 515 | m_socket.Disconnect(false); 516 | m_socket.Dispose(); 517 | m_socket = null; 518 | Console.WriteLine("Connection lost, reconnecting..."); 519 | await ConnectSlave(); 520 | } 521 | } 522 | else 523 | { 524 | Console.WriteLine("Connection lost, reconnecting..."); 525 | await ConnectSlave(); 526 | } 527 | 528 | m_semaphore_connection.Release(); 529 | } 530 | 531 | return response; 532 | } 533 | private byte[] ReadResponse() 534 | { 535 | byte[] response = new byte[m_bufSize]; 536 | int header_len = 0; 537 | int data_len = 0; 538 | int h_l = 0; 539 | int d_l = 0; 540 | int retry = 0; 541 | bool error = false; 542 | 543 | while(m_socket.Available <= 0 && retry < config.RetryCount) 544 | { 545 | retry++; 546 | Task.Delay(config.RetryInterval.Value).Wait(); 547 | } 548 | 549 | while (header_len < m_dataBodyOffset && retry < config.RetryCount) 550 | { 551 | if (m_socket.Available > 0) 552 | { 553 | h_l = m_socket.Receive(response, header_len, m_dataBodyOffset - header_len, SocketFlags.None); 554 | if (h_l > 0) 555 | { 556 | header_len += h_l; 557 | } 558 | } 559 | else 560 | { 561 | error = true; 562 | break; 563 | } 564 | } 565 | 566 | int byte_counts = IPAddress.NetworkToHostOrder((Int16)BitConverter.ToUInt16(response, 4)) - 1; 567 | 568 | while (data_len < byte_counts && retry < config.RetryCount) 569 | { 570 | if (m_socket.Available > 0) 571 | { 572 | d_l = m_socket.Receive(response, m_dataBodyOffset + data_len, byte_counts - data_len, SocketFlags.None); 573 | if (d_l > 0) 574 | { 575 | data_len += d_l; 576 | } 577 | } 578 | else 579 | { 580 | error = true; 581 | break; 582 | } 583 | } 584 | 585 | if (retry >= config.RetryCount || error) 586 | { 587 | response = null; 588 | } 589 | 590 | return response; 591 | } 592 | #endregion 593 | } 594 | 595 | /// 596 | /// This class is Modbus RTU session. 597 | /// 598 | class ModbusRTUSlaveSession : ModbusSlaveSession 599 | { 600 | #region Constructors 601 | public ModbusRTUSlaveSession(ModbusSlaveConfig conf) 602 | : base(conf) 603 | { 604 | } 605 | #endregion 606 | 607 | #region Protected Properties 608 | protected override int m_reqSize { get { return 8; } } 609 | protected override int m_dataBodyOffset { get { return 1; } } 610 | protected override int m_silent { get { return 100; } } 611 | #endregion 612 | 613 | #region Private Fields 614 | private const int m_numOfBits = 8; 615 | private ISerialDevice m_serialPort = null; 616 | #endregion 617 | 618 | #region Public Methods 619 | public override void ReleaseSession() 620 | { 621 | ReleaseOperations(); 622 | if (m_serialPort != null) 623 | { 624 | m_serialPort.Dispose(); 625 | m_serialPort = null; 626 | } 627 | } 628 | #endregion 629 | 630 | #region Private Methods 631 | protected override async Task ConnectSlave() 632 | { 633 | if (config.SlaveConnection.Substring(0, 3) == "COM" || config.SlaveConnection.Substring(0, 8) == "/dev/tty") 634 | { 635 | try 636 | { 637 | Console.WriteLine($"Opening...{config.SlaveConnection}"); 638 | 639 | m_serialPort = SerialDeviceFactory.CreateSerialDevice(config.SlaveConnection, (int)config.BaudRate.Value, config.Parity.Value, (int)config.DataBits.Value, config.StopBits.Value); 640 | 641 | m_serialPort.Open(); 642 | //m_serialPort.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived); 643 | await Task.Delay(2000); //Wait target to be ready to write the modbus package 644 | } 645 | catch (Exception e) 646 | { 647 | Console.WriteLine("Connect Slave failed"); 648 | Console.WriteLine(e.Message); 649 | m_serialPort = null; 650 | } 651 | } 652 | } 653 | protected override void EncodeRead(ReadOperation operation) 654 | { 655 | //uid 656 | operation.Request[0] = operation.UnitId; 657 | 658 | //Body 659 | //function code 660 | operation.Request[m_dataBodyOffset] = operation.FunctionCode; 661 | //address 662 | byte[] address_byte = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((Int16)(operation.Address))); 663 | operation.Request[m_dataBodyOffset + 1] = address_byte[0]; 664 | operation.Request[m_dataBodyOffset + 2] = address_byte[1]; 665 | //count 666 | byte[] count_byte = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((Int16)operation.Count)); 667 | operation.Request[m_dataBodyOffset + 3] = count_byte[0]; 668 | operation.Request[m_dataBodyOffset + 4] = count_byte[1]; 669 | 670 | if (GetCRC(operation.Request, 6, out UInt16 crc)) 671 | { 672 | byte[] crc_byte = BitConverter.GetBytes(crc); 673 | operation.Request[m_dataBodyOffset + 5] = crc_byte[0]; 674 | operation.Request[m_dataBodyOffset + 6] = crc_byte[1]; 675 | } 676 | } 677 | protected override void EncodeWrite(byte[] request, string uid, string address, string value) 678 | { 679 | //uid 680 | request[0] = Convert.ToByte(uid); 681 | 682 | //Body 683 | //function code 684 | ModuleConfig.ParseEntity(address, false, out ushort address_int16, out request[m_dataBodyOffset], out byte entity_type); 685 | 686 | //address 687 | byte[] address_byte = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((Int16)(address_int16))); 688 | request[m_dataBodyOffset + 1] = address_byte[0]; 689 | request[m_dataBodyOffset + 2] = address_byte[1]; 690 | //value 691 | UInt16 value_int = (UInt16)Convert.ToInt32(value); 692 | if (entity_type == '0' && value_int == 1) 693 | { 694 | request[m_dataBodyOffset + 3] = 0xFF; 695 | request[m_dataBodyOffset + 4] = 0x00; 696 | } 697 | else 698 | { 699 | byte[] val_byte = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((Int16)value_int)); 700 | request[m_dataBodyOffset + 3] = val_byte[0]; 701 | request[m_dataBodyOffset + 4] = val_byte[1]; 702 | } 703 | if (GetCRC(request, 6, out UInt16 crc)) 704 | { 705 | byte[] crc_byte = BitConverter.GetBytes(crc); 706 | request[m_dataBodyOffset + 5] = crc_byte[0]; 707 | request[m_dataBodyOffset + 6] = crc_byte[1]; 708 | } 709 | } 710 | protected override async Task SendRequest(byte[] request, int reqLen) 711 | { 712 | //double slient_interval = 1000 * 5 * ((double)1 / (double)config.BaudRate); 713 | byte[] response = null; 714 | 715 | m_semaphore_connection.Wait(); 716 | 717 | if (m_serialPort != null && m_serialPort.IsOpen()) 718 | { 719 | try 720 | { 721 | m_serialPort.DiscardInBuffer(); 722 | m_serialPort.DiscardOutBuffer(); 723 | Task.Delay(m_silent).Wait(); 724 | m_serialPort.Write(request, 0, reqLen); 725 | response = ReadResponse(); 726 | } 727 | catch (Exception e) 728 | { 729 | Console.WriteLine("Something wrong with the connection, disposing..."); 730 | Console.WriteLine(e.Message); 731 | m_serialPort.Dispose(); 732 | m_serialPort = null; 733 | Console.WriteLine("Connection lost, reconnecting..."); 734 | await ConnectSlave(); 735 | } 736 | } 737 | else 738 | { 739 | Console.WriteLine("Connection lost, reconnecting..."); 740 | await ConnectSlave(); 741 | } 742 | 743 | m_semaphore_connection.Release(); 744 | 745 | return response; 746 | } 747 | private byte[] ReadResponse() 748 | { 749 | byte[] response = new byte[m_bufSize]; 750 | int header_len = 0; 751 | int data_len = 0; 752 | int h_l = 0; 753 | int d_l = 0; 754 | int retry = 0; 755 | 756 | while (header_len < 3 && retry < config.RetryCount) 757 | { 758 | h_l = m_serialPort.Read(response, header_len, 3 - header_len); 759 | if (h_l > 0) 760 | { 761 | header_len += h_l; 762 | } 763 | else 764 | { 765 | retry++; 766 | Task.Delay(config.RetryInterval.Value).Wait(); 767 | } 768 | } 769 | 770 | int byte_counts = response[1] >= ModbusConstants.ModbusExceptionCode ? 2 : response[2] + 2; 771 | while (data_len < byte_counts && retry < config.RetryCount) 772 | { 773 | d_l = m_serialPort.Read(response, 3 + data_len, byte_counts - data_len); 774 | if (d_l > 0) 775 | { 776 | data_len += d_l; 777 | } 778 | else 779 | { 780 | retry++; 781 | Task.Delay(config.RetryInterval.Value).Wait(); 782 | } 783 | } 784 | 785 | if (retry >= config.RetryCount) 786 | { 787 | response = null; 788 | } 789 | 790 | return response; 791 | } 792 | //private void sp_DataReceived(object sender, SerialDataReceivedEventArgs e) 793 | //{ 794 | // string data = m_serialPort.ReadExisting(); 795 | //} 796 | private bool GetCRC(byte[] message, int length, out UInt16 res) 797 | { 798 | UInt16 crcFull = 0xFFFF; 799 | res = 0; 800 | 801 | if (message == null || length <= 0) 802 | return false; 803 | 804 | for (int _byte = 0; _byte < length; ++_byte) 805 | { 806 | crcFull = (UInt16)(crcFull ^ message[_byte]); 807 | 808 | for (int _bit = 0; _bit < m_numOfBits; ++_bit) 809 | { 810 | byte crcLsb = (byte)(crcFull & 0x0001); 811 | crcFull = (UInt16)((crcFull >> 1) & 0x7FFF); 812 | 813 | if (crcLsb == 1) 814 | { 815 | crcFull = (UInt16)(crcFull ^ 0xA001); 816 | } 817 | } 818 | } 819 | res = crcFull; 820 | return true; 821 | } 822 | #endregion 823 | } 824 | 825 | /// 826 | /// The base data for a Modbus session used so that reported properties can be echoed back to the IoT Hub. 827 | /// 828 | class BaseModbusSlaveConfig 829 | { 830 | public string SlaveConnection { get; set; } 831 | public int? RetryCount { get; set; } 832 | public int? RetryInterval { get; set; } 833 | public int? TcpPort { get; set; } 834 | public string HwId { get; set; } 835 | public uint? BaudRate { get; set; } 836 | public StopBits? StopBits { get; set; } 837 | public byte? DataBits { get; set; } 838 | public Parity? Parity { get; set; } 839 | //public byte FlowControl { get; set; } 840 | public Dictionary Operations = null; 841 | } 842 | 843 | /// 844 | /// This class contains the configuration for a Modbus session. 845 | /// 846 | class ModbusSlaveConfig : BaseModbusSlaveConfig 847 | { 848 | public new Dictionary Operations = null; 849 | 850 | public ModbusConstants.ConnectionType GetConnectionType() 851 | { 852 | if (IPAddress.TryParse(SlaveConnection, out IPAddress address)) 853 | return ModbusConstants.ConnectionType.ModbusTCP; 854 | else if (SlaveConnection.Contains("COM") || SlaveConnection.Contains("/tty")) 855 | return ModbusConstants.ConnectionType.ModbusRTU; 856 | //TODO: ModbusRTU ModbusASCII 857 | return ModbusConstants.ConnectionType.Unknown; 858 | } 859 | 860 | public BaseModbusSlaveConfig AsBase() 861 | { 862 | // Unfortunately we need to create new objects since simple polymorphism will still keep the same fields of the child classes. 863 | BaseModbusSlaveConfig baseConfig = new BaseModbusSlaveConfig 864 | { 865 | SlaveConnection = this.SlaveConnection, 866 | RetryCount = this.RetryCount, 867 | RetryInterval = this.RetryInterval, 868 | TcpPort = this.TcpPort, 869 | HwId = this.HwId, 870 | BaudRate = this.BaudRate, 871 | StopBits = this.StopBits, 872 | DataBits = this.DataBits, 873 | Parity = this.Parity 874 | }; 875 | 876 | baseConfig.Operations = this.Operations.ToDictionary( 877 | pair => pair.Key, 878 | pair => new BaseReadOperation 879 | { 880 | PollingInterval = pair.Value.PollingInterval, 881 | UnitId = pair.Value.UnitId, 882 | StartAddress = pair.Value.StartAddress, 883 | Count = pair.Value.Count, 884 | DisplayName = pair.Value.DisplayName, 885 | CorrelationId = pair.Value.CorrelationId, 886 | }); 887 | 888 | 889 | return baseConfig; 890 | } 891 | } 892 | 893 | /// 894 | /// The base data for a read operation used so that reported properties can be echoed back to the IoT Hub. 895 | /// 896 | class BaseReadOperation 897 | { 898 | public int PollingInterval { get; set; } 899 | public byte UnitId { get; set; } 900 | public string StartAddress { get; set; } 901 | public UInt16 Count { get; set; } 902 | public string DisplayName { get; set; } 903 | public string CorrelationId { get; set; } 904 | } 905 | 906 | /// 907 | /// This class contains the configuration for a single Modbus read request. 908 | /// 909 | class ReadOperation : BaseReadOperation 910 | { 911 | public byte[] Request; 912 | public byte[] Response; 913 | public int RequestLen; 914 | public byte EntityType { get; set; } 915 | public string OutFormat { get; set; } 916 | public byte FunctionCode { get; set; } 917 | public UInt16 Address { get; set; } 918 | } 919 | 920 | static class ModbusConstants 921 | { 922 | public enum EntityType 923 | { 924 | CoilStatus = '0', 925 | InputStatus = '1', 926 | InputRegister = '3', 927 | HoldingRegister = '4' 928 | } 929 | public enum ConnectionType 930 | { 931 | Unknown = 0, 932 | ModbusTCP = 1, 933 | ModbusRTU = 2, 934 | ModbusASCII = 3 935 | }; 936 | public enum FunctionCodeType 937 | { 938 | ReadCoils = 1, 939 | ReadInputs = 2, 940 | ReadHoldingRegisters = 3, 941 | ReadInputRegisters = 4, 942 | WriteCoil = 5, 943 | WriteHoldingRegister = 6 944 | }; 945 | public static int DefaultTcpPort = 502; 946 | public static int DefaultRetryCount = 10; 947 | public static int DefaultRetryInterval = 50; 948 | public static string DefaultCorrelationId = "DefaultCorrelationId"; 949 | public static int ModbusExceptionCode = 0x80; 950 | } 951 | 952 | class ModbusOutContent 953 | { 954 | public string HwId { get; set; } 955 | public List Data { get; set; } 956 | } 957 | 958 | class ModbusOutData 959 | { 960 | public string CorrelationId { get; set; } 961 | public string SourceTimestamp { get; set; } 962 | public List Values { get; set; } 963 | } 964 | class ModbusOutValue 965 | { 966 | public string DisplayName { get; set; } 967 | //public string OpName { get; set; } 968 | public string Address { get; set; } 969 | public string Value { get; set; } 970 | } 971 | 972 | class ModbusInMessage 973 | { 974 | public string HwId { get; set; } 975 | public string UId { get; set; } 976 | public string Address { get; set; } 977 | public string Value { get; set; } 978 | } 979 | 980 | class ModuleConfig 981 | { 982 | public Dictionary SlaveConfigs; 983 | public ModuleConfig(Dictionary slaves) 984 | { 985 | SlaveConfigs = slaves; 986 | } 987 | public void Validate() 988 | { 989 | List invalidConfigs = new List(); 990 | foreach (var config_pair in SlaveConfigs) 991 | { 992 | ModbusSlaveConfig slaveConfig = config_pair.Value; 993 | if(slaveConfig == null) 994 | { 995 | Console.WriteLine($"{config_pair.Key} is null, remove from dictionary..."); 996 | invalidConfigs.Add(config_pair.Key); 997 | continue; 998 | } 999 | if (slaveConfig.TcpPort == null || slaveConfig.TcpPort <= 0) 1000 | { 1001 | Console.WriteLine($"Invalid TcpPort: {slaveConfig.TcpPort}, set to DefaultTcpPort: {ModbusConstants.DefaultTcpPort}"); 1002 | slaveConfig.TcpPort = ModbusConstants.DefaultTcpPort; 1003 | } 1004 | if (slaveConfig.RetryCount == null || slaveConfig.RetryCount <= 0) 1005 | { 1006 | Console.WriteLine($"Invalid RetryCount: {slaveConfig.RetryCount}, set to DefaultRetryCount: {ModbusConstants.DefaultRetryCount}"); 1007 | slaveConfig.RetryCount = ModbusConstants.DefaultRetryCount; 1008 | } 1009 | if (slaveConfig.RetryInterval == null || slaveConfig.RetryInterval <= 0) 1010 | { 1011 | Console.WriteLine($"Invalid RetryInterval: {slaveConfig.RetryInterval}, set to DefaultRetryInterval: {ModbusConstants.DefaultRetryInterval}"); 1012 | slaveConfig.RetryInterval = ModbusConstants.DefaultRetryInterval; 1013 | } 1014 | List invalidOperations = new List(); 1015 | foreach (var operation_pair in slaveConfig.Operations) 1016 | { 1017 | ReadOperation operation = operation_pair.Value; 1018 | if(operation == null) 1019 | { 1020 | Console.WriteLine($"{operation_pair.Key} is null, remove from dictionary..."); 1021 | invalidOperations.Add(operation_pair.Key); 1022 | continue; 1023 | } 1024 | if(operation.StartAddress.Length < 5) 1025 | { 1026 | Console.WriteLine($"{operation_pair.Key} has invalid StartAddress {operation.StartAddress}, remove from dictionary..."); 1027 | invalidOperations.Add(operation_pair.Key); 1028 | continue; 1029 | } 1030 | ParseEntity(operation.StartAddress, true, out ushort address_int16, out byte function_code, out byte entity_type); 1031 | 1032 | if (operation.Count <= 0) 1033 | { 1034 | Console.WriteLine($"{operation_pair.Key} has invalid Count {operation.Count}, remove from dictionary..."); 1035 | invalidOperations.Add(operation_pair.Key); 1036 | continue; 1037 | } 1038 | if (operation.Count > 127 && ((char)entity_type == (char)ModbusConstants.EntityType.HoldingRegister || (char)entity_type == (char)ModbusConstants.EntityType.InputRegister)) 1039 | { 1040 | Console.WriteLine($"{operation_pair.Key} has invalid Count, must be 1~127, {operation.Count}, remove from dictionary..."); 1041 | invalidOperations.Add(operation_pair.Key); 1042 | continue; 1043 | } 1044 | if(operation.CorrelationId == "" || operation.CorrelationId == null) 1045 | { 1046 | Console.WriteLine($"Empty CorrelationId: {operation.CorrelationId}, set to DefaultCorrelationId: {ModbusConstants.DefaultCorrelationId}"); 1047 | operation.CorrelationId = ModbusConstants.DefaultCorrelationId; 1048 | } 1049 | operation.EntityType = entity_type; 1050 | operation.Address = address_int16; 1051 | operation.FunctionCode = function_code; 1052 | //output format 1053 | if (operation.StartAddress.Length == 5) 1054 | operation.OutFormat = "{0}{1:0000}"; 1055 | else if (operation.StartAddress.Length == 6) 1056 | operation.OutFormat = "{0}{1:00000}"; 1057 | } 1058 | foreach(var in_op in invalidOperations) 1059 | { 1060 | slaveConfig.Operations.Remove(in_op); 1061 | } 1062 | } 1063 | foreach(var in_slave in invalidConfigs) 1064 | { 1065 | SlaveConfigs.Remove(in_slave); 1066 | } 1067 | } 1068 | public static bool ParseEntity(string startAddress, bool isRead, out ushort outAddress, out byte functionCode, out byte entityType) 1069 | { 1070 | outAddress = 0; 1071 | functionCode = 0; 1072 | 1073 | byte[] entity_type = Encoding.ASCII.GetBytes(startAddress, 0, 1); 1074 | entityType = entity_type[0]; 1075 | string address_str = startAddress.Substring(1); 1076 | int address_int = Convert.ToInt32(address_str); 1077 | 1078 | //function code 1079 | switch ((char)entityType) 1080 | { 1081 | case (char)ModbusConstants.EntityType.CoilStatus: 1082 | { 1083 | functionCode = (byte)(isRead ? ModbusConstants.FunctionCodeType.ReadCoils : ModbusConstants.FunctionCodeType.WriteCoil); 1084 | break; 1085 | } 1086 | case (char)ModbusConstants.EntityType.InputStatus: 1087 | { 1088 | if (isRead) 1089 | functionCode = (byte)ModbusConstants.FunctionCodeType.ReadInputs; 1090 | else 1091 | return false; 1092 | break; 1093 | } 1094 | case (char)ModbusConstants.EntityType.InputRegister: 1095 | { 1096 | if (isRead) 1097 | functionCode = (byte)ModbusConstants.FunctionCodeType.ReadInputRegisters; 1098 | else 1099 | return false; 1100 | break; 1101 | } 1102 | case (char)ModbusConstants.EntityType.HoldingRegister: 1103 | { 1104 | functionCode = (byte)(isRead ? ModbusConstants.FunctionCodeType.ReadHoldingRegisters : ModbusConstants.FunctionCodeType.WriteHoldingRegister); 1105 | break; 1106 | } 1107 | default: 1108 | { 1109 | return false; 1110 | } 1111 | } 1112 | //address 1113 | outAddress = (UInt16)(address_int - 1); 1114 | return true; 1115 | } 1116 | } 1117 | 1118 | class ModbusPushInterval 1119 | { 1120 | public ModbusPushInterval(int interval) 1121 | { 1122 | PublishInterval = interval; 1123 | } 1124 | public int PublishInterval { get; set; } 1125 | } 1126 | 1127 | class ModbusVersion 1128 | { 1129 | public ModbusVersion(string version) 1130 | { 1131 | Version = version; 1132 | } 1133 | public string Version { get; set; } 1134 | } 1135 | 1136 | class ModbusOutMessageV1 1137 | { 1138 | public string DisplayName { get; set; } 1139 | public string HwId { get; set; } 1140 | public string Address { get; set; } 1141 | public string Value { get; set; } 1142 | public string SourceTimestamp { get; set; } 1143 | } 1144 | 1145 | class ModbusOutMessage 1146 | { 1147 | public string PublishTimestamp { get; set; } 1148 | public List Content { get; set; } 1149 | } 1150 | 1151 | /// 1152 | /// This class creates a container to easily serialize the configuration so that the reported 1153 | /// properties can be updated. 1154 | /// 1155 | class ModuleTwinProperties 1156 | { 1157 | public ModuleTwinProperties(int? publishInterval, ModuleConfig moduleConfig) 1158 | { 1159 | PublishInterval = publishInterval; 1160 | SlaveConfigs = moduleConfig?.SlaveConfigs.ToDictionary(pair => pair.Key, pair => pair.Value.AsBase()); 1161 | } 1162 | 1163 | public int? PublishInterval { get; set; } 1164 | public Dictionary SlaveConfigs { get; set; } 1165 | } 1166 | } 1167 | -------------------------------------------------------------------------------- /iotedgeModbus/modules/iotedgeModbus/Program.cs: -------------------------------------------------------------------------------- 1 | namespace Modbus.Containers 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Runtime.InteropServices; 7 | using System.Runtime.Loader; 8 | using System.Security.Cryptography.X509Certificates; 9 | using System.Text; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | using Microsoft.Azure.Devices.Client; 13 | using Microsoft.Azure.Devices.Client.Transport.Mqtt; 14 | using Microsoft.Azure.Devices.Shared; 15 | using Newtonsoft.Json; 16 | using Modbus.Slaves; 17 | 18 | class Program 19 | { 20 | const string ModbusSlaves = "SlaveConfigs"; 21 | const int DefaultPushInterval = 5000; 22 | const string DefaultVersion = "2"; 23 | static int m_counter = 0; 24 | static List m_task_list = new List(); 25 | static bool m_run = true; 26 | static ModbusPushInterval m_interval = null; 27 | static ModbusVersion m_version = null; 28 | static ModuleConfig m_existingConfig = null; 29 | static object message_lock = new object(); 30 | static List result = new List(); 31 | 32 | static void Main(string[] args) 33 | { 34 | // Initialize Edge Module 35 | InitEdgeModule().Wait(); 36 | 37 | // Wait until the app unloads or is cancelled 38 | var cts = new CancellationTokenSource(); 39 | AssemblyLoadContext.Default.Unloading += (ctx) => cts.Cancel(); 40 | Console.CancelKeyPress += (sender, cpe) => cts.Cancel(); 41 | WhenCancelled(cts.Token).Wait(); 42 | } 43 | 44 | /// 45 | /// Handles cleanup operations when app is cancelled or unloads 46 | /// 47 | public static Task WhenCancelled(CancellationToken cancellationToken) 48 | { 49 | var tcs = new TaskCompletionSource(); 50 | cancellationToken.Register(s => ((TaskCompletionSource)s).SetResult(true), tcs); 51 | return tcs.Task; 52 | } 53 | 54 | /// 55 | /// Initializes the Azure IoT Client for the Edge Module 56 | /// 57 | static async Task InitEdgeModule() 58 | { 59 | try 60 | { 61 | // Open a connection to the Edge runtime using MQTT transport and 62 | // the connection string provided as an environment variable 63 | string connectionString = Environment.GetEnvironmentVariable("EdgeHubConnectionString"); 64 | 65 | AmqpTransportSettings amqpSettings = new AmqpTransportSettings(TransportType.Amqp_Tcp_Only); 66 | // Suppress cert validation on Windows for now 67 | /* 68 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 69 | { 70 | amqpSettings.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true; 71 | } 72 | */ 73 | 74 | ITransportSettings[] settings = { amqpSettings }; 75 | 76 | ModuleClient ioTHubModuleClient = await ModuleClient.CreateFromEnvironmentAsync(settings); 77 | await ioTHubModuleClient.OpenAsync(); 78 | Console.WriteLine("IoT Hub module client initialized."); 79 | 80 | // Read config from Twin and Start 81 | Twin moduleTwin = await ioTHubModuleClient.GetTwinAsync(); 82 | await UpdateStartFromTwin(moduleTwin.Properties.Desired, ioTHubModuleClient); 83 | 84 | // Attach callback for Twin desired properties updates 85 | await ioTHubModuleClient.SetDesiredPropertyUpdateCallbackAsync(OnDesiredPropertiesUpdate, ioTHubModuleClient); 86 | 87 | } 88 | catch (AggregateException ex) 89 | { 90 | foreach (Exception exception in ex.InnerExceptions) 91 | { 92 | Console.WriteLine(); 93 | Console.WriteLine("Error when initializing module: {0}", exception); 94 | } 95 | } 96 | 97 | } 98 | 99 | /// 100 | /// This method is called whenever the module is sent a message from the EdgeHub. 101 | /// It just pipe the messages without any change. 102 | /// It prints all the incoming messages. 103 | /// 104 | static async Task PipeMessage(Message message, object userContext) 105 | { 106 | Console.WriteLine("Modbus Writer - Received command"); 107 | int counterValue = Interlocked.Increment(ref m_counter); 108 | 109 | var userContextValues = userContext as Tuple; 110 | if (userContextValues == null) 111 | { 112 | throw new InvalidOperationException("UserContext doesn't contain " + 113 | "expected values"); 114 | } 115 | ModuleClient ioTHubModuleClient = userContextValues.Item1; 116 | Slaves.ModuleHandle moduleHandle = userContextValues.Item2; 117 | 118 | byte[] messageBytes = message.GetBytes(); 119 | string messageString = Encoding.UTF8.GetString(messageBytes); 120 | Console.WriteLine($"Received message: {counterValue}, Body: [{messageString}]"); 121 | 122 | message.Properties.TryGetValue("command-type", out string cmdType); 123 | if (cmdType == "ModbusWrite") 124 | { 125 | // Get message body, containing the write target and value 126 | var messageBody = JsonConvert.DeserializeObject(messageString); 127 | 128 | if (messageBody != null) 129 | { 130 | Console.WriteLine($"Write device {messageBody.HwId}, " + 131 | $"address: {messageBody.Address}, value: {messageBody.Value}"); 132 | 133 | ModbusSlaveSession target = moduleHandle.GetSlaveSession(messageBody.HwId); 134 | if (target == null) 135 | { 136 | Console.WriteLine($"target \"{messageBody.HwId}\" not found!"); 137 | } 138 | else 139 | { 140 | await target.WriteCB(messageBody.UId, messageBody.Address, messageBody.Value); 141 | } 142 | } 143 | } 144 | return MessageResponse.Completed; 145 | } 146 | 147 | /// 148 | /// Callback to handle Twin desired properties updates� 149 | /// 150 | static async Task OnDesiredPropertiesUpdate(TwinCollection desiredProperties, object userContext) 151 | { 152 | ModuleClient ioTHubModuleClient = userContext as ModuleClient; 153 | 154 | try 155 | { 156 | // stop all activities while updating configuration 157 | await ioTHubModuleClient.SetInputMessageHandlerAsync( 158 | "input1", 159 | DummyCallBack, 160 | null); 161 | 162 | m_run = false; 163 | await Task.WhenAll(m_task_list); 164 | m_task_list.Clear(); 165 | m_run = true; 166 | 167 | await UpdateStartFromTwin(desiredProperties, ioTHubModuleClient); 168 | } 169 | catch (AggregateException ex) 170 | { 171 | foreach (Exception exception in ex.InnerExceptions) 172 | { 173 | Console.WriteLine(); 174 | Console.WriteLine("Error when receiving desired property: {0}", exception); 175 | } 176 | } 177 | catch (Exception ex) 178 | { 179 | Console.WriteLine(); 180 | Console.WriteLine("Error when receiving desired property: {0}", ex.Message); 181 | } 182 | } 183 | 184 | /// 185 | /// A dummy callback does nothing 186 | /// 187 | /// 188 | /// 189 | /// 190 | static async Task DummyCallBack(Message message, object userContext) 191 | { 192 | await Task.Delay(TimeSpan.FromSeconds(0)); 193 | return MessageResponse.Abandoned; 194 | } 195 | 196 | /// 197 | /// Update Start from module Twin. 198 | /// 199 | static async Task UpdateStartFromTwin(TwinCollection desiredProperties, ModuleClient ioTHubModuleClient) 200 | { 201 | ModuleConfig config; 202 | Slaves.ModuleHandle moduleHandle; 203 | string jsonStr = null; 204 | string serializedStr; 205 | 206 | serializedStr = JsonConvert.SerializeObject(desiredProperties); 207 | Console.WriteLine("Desired property change:"); 208 | Console.WriteLine(serializedStr); 209 | 210 | if (desiredProperties.Contains(ModbusSlaves)) 211 | { 212 | // get config from Twin 213 | jsonStr = serializedStr; 214 | } 215 | else 216 | { 217 | Console.WriteLine("No configuration found in desired properties, look in local..."); 218 | if (File.Exists(@"iot-edge-modbus.json")) 219 | { 220 | try 221 | { 222 | // get config from local file 223 | jsonStr = File.ReadAllText(@"iot-edge-modbus.json"); 224 | } 225 | catch (Exception ex) 226 | { 227 | Console.WriteLine("Load configuration error: " + ex.Message); 228 | } 229 | } 230 | else 231 | { 232 | Console.WriteLine("No configuration found in local file."); 233 | } 234 | } 235 | 236 | if (!string.IsNullOrEmpty(jsonStr)) 237 | { 238 | Console.WriteLine("Attempt to load configuration: " + jsonStr); 239 | config = JsonConvert.DeserializeObject(jsonStr); 240 | m_interval = JsonConvert.DeserializeObject(jsonStr); 241 | 242 | if (m_interval == null) 243 | { 244 | m_interval = new ModbusPushInterval(DefaultPushInterval); 245 | } 246 | 247 | m_version = JsonConvert.DeserializeObject(jsonStr); 248 | 249 | if (m_version == null) 250 | { 251 | m_version = new ModbusVersion(DefaultVersion); 252 | } 253 | 254 | config.Validate(); 255 | moduleHandle = await Slaves.ModuleHandle.CreateHandleFromConfiguration(config); 256 | 257 | if (moduleHandle != null) 258 | { 259 | var userContext = new Tuple(ioTHubModuleClient, moduleHandle); 260 | // Register callback to be called when a message is received by the module 261 | await ioTHubModuleClient.SetInputMessageHandlerAsync( 262 | "input1", 263 | PipeMessage, 264 | userContext); 265 | m_task_list.Add(Start(userContext)); 266 | 267 | // Save the new existing config for reporting. 268 | m_existingConfig = config; 269 | } 270 | } 271 | 272 | // Always report the change in properties. This keeps the reported properties in the module twin up to date even if none of the properties 273 | // actually change. It will also report the property changes if only the publish interval or slave configs change. 274 | ModuleTwinProperties moduleReportedProperties = new ModuleTwinProperties(m_interval?.PublishInterval, m_existingConfig); 275 | string reportedPropertiesJson = JsonConvert.SerializeObject(moduleReportedProperties); 276 | Console.WriteLine("Saving reported properties: " + reportedPropertiesJson); 277 | TwinCollection reportedProperties = new TwinCollection(reportedPropertiesJson); 278 | await ioTHubModuleClient.UpdateReportedPropertiesAsync(reportedProperties); 279 | } 280 | 281 | /// 282 | /// Iterate through each Modbus session to poll data 283 | /// 284 | /// 285 | /// 286 | static async Task Start(object userContext) 287 | { 288 | var userContextValues = userContext as Tuple; 289 | if (userContextValues == null) 290 | { 291 | throw new InvalidOperationException("UserContext doesn't contain " + 292 | "expected values"); 293 | } 294 | ModuleClient ioTHubModuleClient = userContextValues.Item1; 295 | Slaves.ModuleHandle moduleHandle = userContextValues.Item2; 296 | 297 | if(moduleHandle.ModbusSessionList.Count == 0) 298 | { 299 | Console.WriteLine("No valid modbus session available!!"); 300 | } 301 | foreach (ModbusSlaveSession s in moduleHandle.ModbusSessionList) 302 | { 303 | if(s.config.Operations.Count == 0) 304 | { 305 | Console.WriteLine("No valid operation in modbus session available!!"); 306 | } 307 | else 308 | { 309 | s.ProcessOperations(); 310 | } 311 | } 312 | 313 | while (m_run) 314 | { 315 | Message message = null; 316 | 317 | switch (m_version.Version) 318 | { 319 | case "1": 320 | List resultV1 = moduleHandle.CollectAndResetOutMessageFromSessionsV1(); 321 | 322 | if (resultV1.Count > 0) 323 | { 324 | message = new Message(Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(resultV1))); 325 | message.Properties.Add("content-type", "application/edge-modbus-json"); 326 | } 327 | 328 | break; 329 | 330 | default: 331 | List result = moduleHandle.CollectAndResetOutMessageFromSessions(); 332 | 333 | if (result.Count > 0) 334 | { 335 | ModbusOutMessage out_message = new ModbusOutMessage 336 | { 337 | PublishTimestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), 338 | Content = result 339 | }; 340 | 341 | message = new Message(Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(out_message))); 342 | message.Properties.Add("content-type", "application/edge-modbus-json"); 343 | } 344 | 345 | break; 346 | } 347 | 348 | if (message != null) 349 | { 350 | await ioTHubModuleClient.SendEventAsync("modbusOutput", message); 351 | } 352 | 353 | if (!m_run) 354 | { 355 | break; 356 | } 357 | await Task.Delay(m_interval.PublishInterval); 358 | } 359 | moduleHandle.Release(); 360 | } 361 | } 362 | 363 | class SQLiteCommandMessage 364 | { 365 | public int RequestId; 366 | public string RequestModule; 367 | public string DbName; 368 | public string Command; 369 | } 370 | } -------------------------------------------------------------------------------- /iotedgeModbus/modules/iotedgeModbus/SerialDevice.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.InteropServices; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace tempSerialPort 8 | { 9 | using System.IO.Ports; 10 | public interface ISerialDevice 11 | { 12 | void Open(); 13 | void Close(); 14 | void Write(byte[] buf, int offset, int len); 15 | int Read(byte[] buf, int offset, int len); 16 | bool IsOpen(); 17 | void DiscardInBuffer(); 18 | void DiscardOutBuffer(); 19 | void Dispose(); 20 | } 21 | public static class SerialDeviceFactory 22 | { 23 | public static ISerialDevice CreateSerialDevice(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits) 24 | { 25 | ISerialDevice device = null; 26 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 27 | { 28 | device = WinSerialDevice.CreateDevice(portName, baudRate, parity, dataBits, stopBits); 29 | } 30 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 31 | { 32 | device = UnixSerialDevice.CreateDevice(portName, baudRate, parity, dataBits, stopBits); 33 | } 34 | return device; 35 | } 36 | } 37 | public class WinSerialDevice : ISerialDevice 38 | { 39 | private SerialPort serialPort = null; 40 | public static WinSerialDevice CreateDevice(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits) 41 | { 42 | List serial_ports = new List(); 43 | 44 | // Are we on Windows? 45 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 46 | { 47 | return new WinSerialDevice(portName, baudRate, parity, dataBits, stopBits); 48 | } 49 | else 50 | { 51 | Console.WriteLine("WinSerialDevice only supports Windows system"); 52 | return null; 53 | } 54 | } 55 | 56 | public void Open() 57 | { 58 | this.serialPort.Open(); 59 | this.serialPort.Handshake = Handshake.None; 60 | this.serialPort.ReadTimeout = 5000; 61 | } 62 | 63 | private WinSerialDevice(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits) 64 | { 65 | this.serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits); 66 | } 67 | 68 | public void Close() 69 | { 70 | serialPort.Close(); 71 | } 72 | 73 | public void Write(byte[] buf, int offset, int len) 74 | { 75 | serialPort.Write(buf, offset, len); 76 | } 77 | 78 | public int Read(byte[] buf, int offset, int len) 79 | { 80 | return serialPort.Read(buf, offset, len); 81 | } 82 | 83 | public static string[] GetPortNames() 84 | { 85 | return SerialPort.GetPortNames(); 86 | } 87 | 88 | public void DiscardInBuffer() 89 | { 90 | serialPort.DiscardInBuffer(); 91 | } 92 | 93 | public void DiscardOutBuffer() 94 | { 95 | serialPort.DiscardOutBuffer(); 96 | } 97 | 98 | public bool IsOpen() 99 | { 100 | return serialPort.IsOpen; 101 | } 102 | 103 | public void Dispose() 104 | { 105 | if (IsOpen()) 106 | { 107 | Close(); 108 | } 109 | } 110 | 111 | } 112 | public class UnixSerialDevice : ISerialDevice 113 | { 114 | public const int READING_BUFFER_SIZE = 1024; 115 | 116 | private readonly CancellationTokenSource cts = new CancellationTokenSource(); 117 | private CancellationToken CancellationToken => cts.Token; 118 | private int? fd; 119 | private readonly IntPtr readingBuffer = Marshal.AllocHGlobal(READING_BUFFER_SIZE); 120 | protected readonly string portName; 121 | protected readonly int baudRate; 122 | protected readonly Parity parity; 123 | protected readonly int dataBits; 124 | protected readonly StopBits stopBits; 125 | public event Action DataReceived; 126 | 127 | public static UnixSerialDevice CreateDevice(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits) 128 | { 129 | List serial_ports = new List(); 130 | 131 | // Are we on Unix? 132 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 133 | { 134 | return new UnixSerialDevice(portName, baudRate, parity, dataBits, stopBits); 135 | } 136 | else 137 | { 138 | Console.WriteLine("UnixSerialDevice only supports Unix system"); 139 | return null; 140 | } 141 | } 142 | 143 | public void Open() 144 | { 145 | // open serial port 146 | int fd = ComWrapper.com_open(portName); 147 | 148 | if (fd == -1) 149 | { 150 | throw new Exception($"failed to open port ({portName})"); 151 | } 152 | 153 | ComWrapper.com_set_interface_attribs(fd, baudRate, dataBits, (int)parity, (int)stopBits); 154 | 155 | // start reading 156 | //Task.Run((Action)StartReading, CancellationToken); 157 | 158 | this.fd = fd; 159 | } 160 | 161 | private UnixSerialDevice(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits) 162 | { 163 | this.portName = portName; 164 | this.baudRate = baudRate; 165 | this.parity = parity; 166 | this.dataBits = dataBits; 167 | this.stopBits = stopBits; 168 | } 169 | 170 | private void StartReading() 171 | { 172 | if (!fd.HasValue) 173 | { 174 | throw new Exception(); 175 | } 176 | 177 | while (true) 178 | { 179 | CancellationToken.ThrowIfCancellationRequested(); 180 | 181 | int res = ComWrapper.com_read(fd.Value, readingBuffer, READING_BUFFER_SIZE); 182 | if (res != -1) 183 | { 184 | byte[] buf = new byte[res]; 185 | Marshal.Copy(readingBuffer, buf, 0, res); 186 | OnDataReceived(buf); 187 | } 188 | 189 | Thread.Sleep(50); 190 | } 191 | } 192 | 193 | protected virtual void OnDataReceived(byte[] data) 194 | { 195 | DataReceived?.Invoke(this, data); 196 | } 197 | 198 | public void Close() 199 | { 200 | if (!fd.HasValue) 201 | { 202 | throw new Exception(); 203 | } 204 | cts.Cancel(); 205 | ComWrapper.com_close(fd.Value); 206 | Marshal.FreeHGlobal(readingBuffer); 207 | } 208 | 209 | public void Write(byte[] buf, int offset, int len) 210 | { 211 | if (!fd.HasValue) 212 | { 213 | throw new Exception(); 214 | } 215 | IntPtr ptr = Marshal.AllocHGlobal(len); 216 | Marshal.Copy(buf, offset, ptr, len); 217 | ComWrapper.com_write(fd.Value, ptr, len); 218 | Marshal.FreeHGlobal(ptr); 219 | } 220 | 221 | public int Read(byte[] buf, int offset, int len) 222 | { 223 | if (!fd.HasValue || len > READING_BUFFER_SIZE) 224 | { 225 | throw new Exception(); 226 | } 227 | 228 | int res = ComWrapper.com_read(fd.Value, readingBuffer, len); 229 | if (res != -1) 230 | { 231 | Marshal.Copy(readingBuffer, buf, offset, res); 232 | } 233 | else 234 | { 235 | throw new Exception(); 236 | } 237 | 238 | return res; 239 | } 240 | 241 | public static string[] GetPortNames() 242 | { 243 | PlatformID p = Environment.OSVersion.Platform; 244 | List serial_ports = new List(); 245 | 246 | // Are we on Unix? 247 | if (p == PlatformID.Unix || p == PlatformID.MacOSX) 248 | { 249 | string[] ttys = System.IO.Directory.GetFiles("/dev/", "tty*"); 250 | foreach (string dev in ttys) 251 | { 252 | //Arduino MEGAs show up as ttyACM due to their different USB<->RS232 chips 253 | if (dev.StartsWith("/dev/ttyS") || dev.StartsWith("/dev/ttyUSB") || dev.StartsWith("/dev/ttyACM") || dev.StartsWith("/dev/ttyAMA")) 254 | { 255 | serial_ports.Add(dev); 256 | //Console.WriteLine("Serial list: {0}", dev); 257 | } 258 | } 259 | } 260 | return serial_ports.ToArray(); 261 | } 262 | 263 | public void DiscardInBuffer() 264 | { 265 | if (!fd.HasValue) 266 | { 267 | throw new Exception(); 268 | } 269 | ComWrapper.com_tciflush(fd.Value); 270 | } 271 | 272 | public void DiscardOutBuffer() 273 | { 274 | if (!fd.HasValue) 275 | { 276 | throw new Exception(); 277 | } 278 | ComWrapper.com_tcoflush(fd.Value); 279 | } 280 | 281 | public bool IsOpen() 282 | { 283 | if (!fd.HasValue) 284 | { 285 | return false; 286 | } 287 | else 288 | { 289 | return true; 290 | } 291 | } 292 | 293 | public void Dispose() 294 | { 295 | if (IsOpen()) 296 | { 297 | Close(); 298 | } 299 | } 300 | } 301 | } -------------------------------------------------------------------------------- /iotedgeModbus/modules/iotedgeModbus/comWrapper.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // speed: 4800, 9600, 19200 10 | // char_bits: 5, 6, 7 11 | // parity_bit: 0(none), 1(odd), 2(even) 12 | // stop_bit: 0(none), 1(one), 2(two) 13 | 14 | int com_set_interface_attribs(int fd, int speed, int data_bits, int parity_bit, int stop_bit) 15 | { 16 | struct termios tty; 17 | 18 | if (tcgetattr(fd, &tty) < 0) { 19 | printf("Error from tcgetattr: %s\n", strerror(errno)); 20 | return -1; 21 | } 22 | 23 | switch (speed) 24 | { 25 | case 110: { 26 | cfsetspeed(&tty, (speed_t)B110); 27 | break; 28 | } 29 | case 300: { 30 | cfsetspeed(&tty, (speed_t)B300); 31 | break; 32 | } 33 | case 600: { 34 | cfsetspeed(&tty, (speed_t)B600); 35 | break; 36 | } 37 | case 1200: { 38 | cfsetspeed(&tty, (speed_t)B1200); 39 | break; 40 | } 41 | case 2400: { 42 | cfsetspeed(&tty, (speed_t)B2400); 43 | break; 44 | } 45 | case 4800: { 46 | cfsetspeed(&tty, (speed_t)B4800); 47 | break; 48 | } 49 | case 9600: { 50 | cfsetspeed(&tty, (speed_t)B9600); 51 | break; 52 | } 53 | case 19200: { 54 | cfsetspeed(&tty, (speed_t)B19200); 55 | break; 56 | } 57 | case 38400: { 58 | cfsetspeed(&tty, (speed_t)B38400); 59 | break; 60 | } 61 | default: 62 | break; 63 | } 64 | 65 | tty.c_cflag |= (CLOCAL | CREAD); /* ignore modem controls */ 66 | 67 | tty.c_cflag &= ~CSIZE; 68 | switch (data_bits) 69 | { 70 | case 5: { 71 | tty.c_cflag |= CS5; /* 5-bit characters */ 72 | break; 73 | } 74 | case 6: { 75 | tty.c_cflag |= CS6; /* 6-bit characters */ 76 | break; 77 | } 78 | case 7: { 79 | tty.c_cflag |= CS7; /* 7-bit characters */ 80 | break; 81 | } 82 | case 8: { 83 | tty.c_cflag |= CS8; /* 8-bit characters */ 84 | break; 85 | } 86 | default: 87 | break; 88 | } 89 | 90 | switch (parity_bit) 91 | { 92 | case 0: 93 | { 94 | tty.c_cflag &= ~PARENB; /* no parity */ 95 | break; 96 | } 97 | case 1: 98 | { 99 | tty.c_cflag |= PARENB; 100 | tty.c_cflag |= PARODD; 101 | break; 102 | } 103 | case 2: 104 | { 105 | tty.c_cflag |= PARENB; 106 | tty.c_cflag &= ~PARODD; 107 | break; 108 | } 109 | default: 110 | break; 111 | } 112 | 113 | switch (stop_bit) 114 | { 115 | case 1: 116 | { 117 | tty.c_cflag &= ~CSTOPB; /* 1 stop bit */ 118 | break; 119 | } 120 | case 2: 121 | { 122 | tty.c_cflag |= CSTOPB; /* 2 stop bit */ 123 | break; 124 | } 125 | default: 126 | break; 127 | } 128 | 129 | tty.c_cflag &= ~CRTSCTS; /* no hardware flowcontrol */ 130 | tty.c_iflag &= ~(IXON | IXOFF | IXANY); /* disable software flow control */ 131 | 132 | //Set an overall timeout of 2 sec/byte 133 | tty.c_cc[VTIME] = 20; 134 | tty.c_cc[VMIN] = 0; 135 | 136 | /* setup for non-canonical mode, based on function cfmakeraw() */ 137 | tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); 138 | tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); 139 | tty.c_oflag &= ~OPOST; 140 | 141 | if (tcsetattr(fd, TCSANOW, &tty) != 0) { 142 | printf("Error from tcsetattr: %s\n", strerror(errno)); 143 | return -1; 144 | } 145 | return 0; 146 | } 147 | 148 | int com_open(const char* pathname) 149 | { 150 | return open(pathname, O_RDWR | O_NOCTTY); 151 | } 152 | 153 | int com_close(int fd) 154 | { 155 | return close(fd); 156 | } 157 | 158 | ssize_t com_read(int fd, void *buf, size_t count) 159 | { 160 | return read(fd, buf, count); 161 | } 162 | 163 | ssize_t com_write(int fd, const void *buf, size_t count) 164 | { 165 | return write(fd, buf, count); 166 | } 167 | 168 | int com_tciflush(int fd) 169 | { 170 | return tcflush(fd, TCIFLUSH); 171 | } 172 | 173 | int com_tcoflush(int fd) 174 | { 175 | return tcflush(fd, TCOFLUSH); 176 | } -------------------------------------------------------------------------------- /iotedgeModbus/modules/iotedgeModbus/comWrapper.h: -------------------------------------------------------------------------------- 1 | #ifdef __cplusplus 2 | extern "C" { 3 | #endif 4 | 5 | int com_set_interface_attribs(int fd, int speed, int data_bits, int parity_bit, int stop_bit); 6 | int com_open(const char* pathname); 7 | int com_close(int fd); 8 | ssize_t com_read(int fd, void *buf, size_t count); 9 | ssize_t com_write(int fd, const void *buf, size_t count); 10 | int com_tciflush(int fd); 11 | int com_tcoflush(int fd); 12 | 13 | #ifdef __cplusplus 14 | } 15 | #endif 16 | -------------------------------------------------------------------------------- /iotedgeModbus/modules/iotedgeModbus/iotedgeModbus.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | netcoreapp2.1 5 | 6 | 7 | 8 | True 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /iotedgeModbus/modules/iotedgeModbus/module.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema-version": "0.0.1", 3 | "description": "", 4 | "image": { 5 | "repository": "azureiotedge/modbus", 6 | "tag": { 7 | "version": "1.0.5", 8 | "platforms": { 9 | "amd64": "./Dockerfile.amd64", 10 | "amd64.debug": "./Dockerfile.amd64.debug", 11 | "arm32v7": "./Dockerfile.arm32v7", 12 | "windows-amd64": "./Dockerfile.windows-amd64", 13 | "windows-arm32v7": "./Dockerfile.windows-arm32v7" 14 | } 15 | }, 16 | "buildOptions": [] 17 | }, 18 | "language": "csharp" 19 | } -------------------------------------------------------------------------------- /v1/License.txt: -------------------------------------------------------------------------------- 1 | Microsoft Azure IoT Gateway Modbus 2 | Copyright (c) Microsoft Corporation 3 | 4 | MIT license 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 10 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 11 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 12 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 13 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 14 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /v1/README.md: -------------------------------------------------------------------------------- 1 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments 2 | 3 | # Beta Azure IoT Gateway Modbus Module 4 | Using this module, developers can build Azure IoT Gateway solutions with Modbus connectivity. The Modbus module is a [Azure IoT Gateway SDK](https://github.com/Azure/azure-iot-gateway-sdk) module, capable of reading data from Modbus devices and publish the data to the Azure IoT Hub via a message broker. Developers can modify the module tailoring to any scenario. 5 | 6 | Visit http://azure.com/iotdev to learn more about developing applications for Azure IoT. 7 | 8 | ## Azure IoT Gateway SDK compatibility 9 | Current version of the module is targeted for the Azure IoT Gateway SDK 2017-01-13 version. 10 | Use the following script to download the compatible version Azure IoT Gateway SDK. 11 | ``` 12 | git clone -b "2017-01-13" --recursive https://github.com/Azure/azure-iot-gateway-sdk.git 13 | ``` 14 | 15 | ## Operating system compatibility 16 | Refer to [Azure IoT Gateway SDK](https://github.com/Azure/azure-iot-gateway-sdk#operating-system-compatibility) 17 | 18 | ## Hardware compatibility 19 | Refer to [Azure IoT Gateway SDK](https://github.com/Azure/azure-iot-gateway-sdk#hardware-compatibility) 20 | 21 | ## Directory structure 22 | 23 | ### /doc 24 | This folder contains step by step instructions for building and running the sample: 25 | 26 | General documentation 27 | 28 | - [Dev box setup](./doc/devbox_setup.md) contains instructions for configure your machine to build the Azure IoT Gateway Modbus module. 29 | 30 | Samples 31 | 32 | - [modbus sample](./doc/sample_modbus.md) contains step by step instructions for building and running the modbus sample. 33 | 34 | 35 | ### /samples 36 | This folder contains a sample for the Azure IoT Gateway Modbus module. Step by step instructions for building and running the sample can be found at [sample_modbus.md](./doc/sample_modbus.md). 37 | 38 | ### /modules 39 | This folder contains the Modbus module that could be included with the Azure IoT Gateway SDK. Details on the implementation of the module can be found in the [devdoc](./modules/modbus_read/devdoc) folder. 40 | -------------------------------------------------------------------------------- /v1/doc/devbox_setup.md: -------------------------------------------------------------------------------- 1 | # Prepare your development environment 2 | 3 | This document describes how to prepare your development environment to use the *Microsoft Azure IoT Gateway Modbus Module*. It requires a pre-setup environment for the *Microsoft Azure IoT Gateway SDK*. 4 | 5 | - [Setting up an environment for Microsoft Azure IoT Gateway SDK](https://github.com/Azure/azure-iot-gateway-sdk/blob/master/doc/devbox_setup.md) 6 | 7 | # Compile the Modbus module 8 | 9 | This section shows you how to integrate the Modbus module with the Azure IoT Gateway SDK and compile it. 10 | 11 | 1. Copy the modules\\**modbus_read** folder from the Azure IoT Gateway Modbus and paste it into the **modules** directory of the Azure IoT Gateway SDK. 12 | 2. Copy the samples\\**modbus_sample** folder from the Azure IoT Gateway Modbus and paste it into the **samples** directory of the Azure IoT Gateway SDK. 13 | 3. Edit the **CMakeLists.txt** in the **modules** directory. Add this line `add_subdirectory(modbus_read)` to the end of the file. 14 | 4. Edit the **CMakeLists.txt** in the **samples** directory. Add this line `add_subdirectory(modbus_sample)` to the end of the file. 15 | 5. [Compile the Azure IoT Gateway SDK](https://github.com/Azure/iot-edge/blob/master/v1/samples/hello_world/iot-hub-windows-iot-edge-get-started.md) for your machine. 16 | 17 | 18 | -------------------------------------------------------------------------------- /v1/doc/media/gateway_modbus_command_data_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/iot-edge-modbus/ed85c4077be30fdcfd98e9c51db0ebe202586df6/v1/doc/media/gateway_modbus_command_data_flow.png -------------------------------------------------------------------------------- /v1/doc/media/gateway_modbus_upload_data_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/iot-edge-modbus/ed85c4077be30fdcfd98e9c51db0ebe202586df6/v1/doc/media/gateway_modbus_upload_data_flow.png -------------------------------------------------------------------------------- /v1/doc/sample_modbus.md: -------------------------------------------------------------------------------- 1 | # Modbus Sample for Azure IoT Gateway SDK # 2 | 3 | ## Overview ## 4 | 5 | This sample showcases how one can build an IoT gateway that interacts with 6 | Modbus devices using the Azure IoT Gateway SDK. The sample contains the following modules: 7 | 8 | 1. A Modbus module that interfaces with a Modbus TCP/RTU device to 9 | read data. 10 | 2. A logger module for diagnostics. 11 | 3. An identity mapping module for translating between Modbus device MAC addresses 12 | and Azure IoT Hub device identities. 13 | 4. An IoT Hub module for uploading Modbus telemetry data and for receiving 14 | device commands from the Azure IoT Hub. 15 | 16 | ## How the data flows through the Gateway ## 17 | 18 | The telemetry upload data flow pipeline is best described via a block diagram: 19 | 20 | ![](./media/gateway_modbus_upload_data_flow.png) 21 | 22 | Here's the journey that a piece of telemetry data takes originating from a Modbus 23 | device before finding its way to an Azure IoT Hub. 24 | 25 | 1. The Modbus device generates a data set and transfers it over 26 | Modbus TCP/RTU to the Modbus module. 27 | 2. The Modbus module receives the data and publishes it on to the message broker 28 | along with the device's MAC address. 29 | 3. The identity mapping module picks up this message from the message broker and 30 | looks up its internal table in order to translate the device MAC address 31 | into an Azure IoT Hub identity (comprised of a device ID and device key). 32 | It then proceeds to publish a new message on to the message broker containing 33 | the sample data, the MAC address, the IoT Hub device ID and 34 | key. 35 | 4. The IoT Hub module then receives this message from the identity 36 | mapping module and publishes it to the Azure IoT Hub itself. 37 | 5. The logger module logs all messages from the Modbus module into a file on 38 | the disk. 39 | 40 | The cloud to device command data flow pipeline is described via a block diagram 41 | below: 42 | 43 | ![](./media/gateway_modbus_command_data_flow.png) 44 | 45 | 1. The IoT Hub module receives a new command message from IoT Hub and it 46 | publishes it to the message broker. 47 | 2. The Identity Mapping module picks up the message and translates the Azure 48 | IoT Hub device ID to a device MAC address and publishes a new message to 49 | the message broker including the MAC address in the message's properties map. 50 | 3. The Modbus module then picks up this message and executes the Modbus write 51 | operation by communicating with the Modbus device. 52 | 53 | ## Building the sample ## 54 | 55 | The [devbox setup](./devbox_setup.md) guide has information on how you can build the 56 | module. 57 | 58 | ## Preparing your Modbus device ## 59 | 60 | The Modbus devices can be connected to the Modbus module via either Modbus TCP 61 | or Modbus RTU. Simply filled in the ipv4 address (XXX.XXX.XXX.XXX) or COM# to the 62 | Modbus module configuration. For Modbus RTU, stopBits = 1, 1.5, 2; parity = ODD, EVEN, NONE; flowControl = RTS, DSR, NONE. 63 | 64 | ## Running the sample ## 65 | 66 | In order to bootstrap and run the sample, you'll need to configure each module 67 | that participates in the gateway. This configuration is provided as JSON. All 68 | 4 participating modules will need to be configured. There are sample JSON files 69 | provided in the repo called `modbus_win.json` and `modbus_win.json` which you can 70 | use as a starting point for building your own configuration file. You should find 71 | the file at the path `samples/modbus/src` relative to the root of the repo. 72 | 73 | In order to run the sample you'll run the `modbus_sample` binary passing the 74 | path to the configuration JSON file. 75 | 76 | ``` 77 | modbus_sample <> 78 | ``` 79 | 80 | Template configuration JSONs are given below for all the modules that are a part 81 | of this sample. 82 | 83 | ### Linux 84 | 85 | #### Logger configuration 86 | 87 | ```json 88 | { 89 | "name": "logger", 90 | "loader": { 91 | "name": "native", 92 | "entrypoint": { 93 | "module.path": "../../modules/logger/liblogger.so" 94 | } 95 | }, 96 | "args": { 97 | "filename": "log.txt" 98 | } 99 | } 100 | ``` 101 | 102 | #### Modbus module configuration 103 | 104 | ```json 105 | { 106 | "name": "modbus_read", 107 | "loader": { 108 | "name": "native", 109 | "entrypoint": { 110 | "module.path": "../../modules/modbus_read/libmodbus_read.so" 111 | } 112 | }, 113 | "args": [ 114 | { 115 | "serverConnectionString": "COM1", 116 | "interval": "2000", 117 | "macAddress": "01:01:01:01:01:01", 118 | "baudRate": "9600", 119 | "stopBits": "1", 120 | "dataBits": "8", 121 | "parity": "EVEN", 122 | "flowControl": "NONE", 123 | "deviceType": "powerMeter", 124 | "sqliteEnabled": "0", 125 | "operations": [ 126 | { 127 | "unitId": "1", 128 | "functionCode": "3", 129 | "startingAddress": "1", 130 | "length": "5" 131 | } 132 | ] 133 | } 134 | ] 135 | } 136 | ``` 137 | 138 | #### IoT Hub module 139 | 140 | ```json 141 | { 142 | "name": "IoTHub", 143 | "loader": { 144 | "name": "native", 145 | "entrypoint": { 146 | "module.path": "../../modules/iothub/libiothub.so" 147 | } 148 | }, 149 | "args": { 150 | "IoTHubName": "YOUR IOT HUB NAME", 151 | "IoTHubSuffix": "YOUR IOT HUB SUFFIX", 152 | "Transport": "TRANSPORT PROTOCOL" 153 | } 154 | } 155 | ``` 156 | 157 | ### Identity mapping module configuration 158 | 159 | ```json 160 | { 161 | "name": "mapping", 162 | "loader": { 163 | "name": "native", 164 | "entrypoint": { 165 | "module.path": "../../modules/identitymap/libidentity_map.so" 166 | } 167 | }, 168 | "args": [ 169 | { 170 | "macAddress": "01:01:01:01:01:01", 171 | "deviceId": "YOUR DEVICE ID", 172 | "deviceKey": "YOUR DEVICE KEY" 173 | } 174 | ] 175 | } 176 | ``` 177 | 178 | ### Windows ## 179 | 180 | #### Logger configuration 181 | 182 | ```json 183 | { 184 | "name": "logger", 185 | "loader": { 186 | "name": "native", 187 | "entrypoint": { 188 | "module.path": "..\\..\\..\\modules\\logger\\Debug\\logger.dll" 189 | } 190 | }, 191 | "args": { 192 | "filename": "log.txt" 193 | } 194 | } 195 | ``` 196 | 197 | #### Modbus module configuration 198 | 199 | ```json 200 | { 201 | "name": "modbus_read", 202 | "loader": { 203 | "name": "native", 204 | "entrypoint": { 205 | "module.path": "..\\..\\..\\modules\\modbus_read\\Debug\\modbus_read.dll" 206 | } 207 | }, 208 | "args": [ 209 | { 210 | "serverConnectionString": "127.0.0.1", 211 | "interval": "2000", 212 | "macAddress": "01:01:01:01:01:01", 213 | "baudRate": "9600", 214 | "stopBits": "1", 215 | "dataBits": "8", 216 | "parity": "EVEN", 217 | "flowControl": "NONE", 218 | "deviceType": "powerMeter", 219 | "sqliteEnabled": "0", 220 | "operations": [ 221 | { 222 | "unitId": "1", 223 | "functionCode": "3", 224 | "startingAddress": "1", 225 | "length": "1" 226 | } 227 | ] 228 | } 229 | ] 230 | } 231 | ``` 232 | 233 | #### IoT Hub module 234 | 235 | ```json 236 | { 237 | "name": "IoTHub", 238 | "loader": { 239 | "name": "native", 240 | "entrypoint": { 241 | "module.path": "..\\..\\..\\modules\\iothub\\Debug\\iothub.dll" 242 | } 243 | }, 244 | "args": { 245 | "IoTHubName": "YOUR IOT HUB NAME", 246 | "IoTHubSuffix": "YOUR IOT HUB SUFFIX", 247 | "Transport": "TRANSPORT PROTOCOL" 248 | } 249 | } 250 | ``` 251 | 252 | #### Identity mapping module configuration 253 | 254 | ```json 255 | { 256 | "name": "mapping", 257 | "loader": { 258 | "name": "native", 259 | "entrypoint": { 260 | "module.path": "..\\..\\..\\modules\\identitymap\\Debug\\identity_map.dll" 261 | } 262 | }, 263 | "args": [ 264 | { 265 | "macAddress": "01:01:01:01:01:01", 266 | "deviceId": "YOUR DEVICE ID", 267 | "deviceKey": "YOUR DEVICE KEY" 268 | } 269 | ] 270 | } 271 | ``` 272 | 273 | Links 274 | ----- 275 | Links are to specify the message flow controlled by the message broker. 276 | ```json 277 | "links": [ 278 | { 279 | "source": "mapping", 280 | "sink": "IoTHub" 281 | }, 282 | { 283 | "source": "IoTHub", 284 | "sink": "mapping" 285 | }, 286 | { 287 | "source": "mapping", 288 | "sink": "modbus_read" 289 | }, 290 | { 291 | "source": "modbus_read", 292 | "sink": "mapping" 293 | }, 294 | { 295 | "source": "modbus_read", 296 | "sink": "logger" 297 | } 298 | ] 299 | ``` 300 | 301 | ## Sending cloud-to-device messages ## 302 | 303 | The Modbus module also supports sending of instructions from the Azure IoT Hub to 304 | the device. You should be able to use the 305 | [Azure IoT Hub Device Explorer](https://github.com/Azure/azure-iot-sdks/blob/master/tools/DeviceExplorer/doc/how_to_use_device_explorer.md) or the [IoT Hub Explorer](https://github.com/Azure/azure-iot-sdks/tree/master/tools/iothub-explorer) 306 | to craft and send JSON messages that are handled and passed on to the Modbus device 307 | by the Modbus module. For example, sending the following JSON message to the device 308 | via IoT Hub will write value 9999 to the Holding Register 1: 309 | 310 | * Set the value of the Holding Register #1 to 9999. 311 | * Function code 6 is "Write Single Holding Register". [Check more function codes](https://en.wikipedia.org/wiki/Modbus#Format_of_data_of_requests_and_responses_for_main_function_codes) 312 | * startingAddress is the offset of Holding Register. [Check holding register numbers start with](https://en.wikipedia.org/wiki/Modbus#Coil.2C_discrete_input.2C_input_register.2C_holding_register_numbers_and_addresses) 313 | 314 | ```json 315 | { 316 | "functionCode": "6", 317 | "startingAddress": "2", 318 | "value": "9999", 319 | "uid": "1" 320 | } 321 | ``` 322 | 323 | -------------------------------------------------------------------------------- /v1/modules/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #Copyright (c) Microsoft. All rights reserved. 2 | #Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | cmake_minimum_required(VERSION 2.8.12) 5 | 6 | include("dependencies.cmake") 7 | 8 | set(MODULES_DIR ${CMAKE_CURRENT_LIST_DIR} CACHE INTERNAL "Modules include directory" FORCE) 9 | 10 | include_directories(./common) 11 | 12 | if(${enable_ble_module}) 13 | add_subdirectory(ble) 14 | endif() 15 | 16 | add_subdirectory(simulated_device) 17 | add_subdirectory(identitymap) 18 | add_subdirectory(iothub) 19 | add_subdirectory(logger) 20 | add_subdirectory(hello_world) 21 | add_subdirectory(azure_functions) 22 | 23 | add_subdirectory(modbus_read) -------------------------------------------------------------------------------- /v1/modules/modbus_read/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #Copyright (c) Microsoft. All rights reserved. 2 | #Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | cmake_minimum_required(VERSION 2.8.12) 5 | 6 | set(modbus_read_sources 7 | ./src/modbus_read.c 8 | ) 9 | 10 | set(modbus_read_headers 11 | ./inc/modbus_read.h 12 | ) 13 | 14 | include_directories(./inc) 15 | include_directories(${GW_INC}) 16 | 17 | #this builds the modbus_read dynamic library 18 | add_library(modbus_read MODULE ${modbus_read_sources} ${modbus_read_headers}) 19 | target_link_libraries(modbus_read gateway) 20 | 21 | #this builds the modbus_read static library 22 | add_library(modbus_read_static ${modbus_read_sources} ${modbus_read_headers}) 23 | target_compile_definitions(modbus_read_static PRIVATE BUILD_MODULE_TYPE_STATIC) 24 | target_link_libraries(modbus_read_static gateway) 25 | 26 | linkSharedUtil(modbus_read) 27 | linkSharedUtil(modbus_read_static) 28 | 29 | add_module_to_solution(modbus_read) 30 | if(${run_unittests}) 31 | add_subdirectory(tests) 32 | endif() 33 | 34 | if(install_modules) 35 | install(TARGETS modbus_read LIBRARY DESTINATION "${LIB_INSTALL_DIR}/modules") 36 | endif() 37 | 38 | -------------------------------------------------------------------------------- /v1/modules/modbus_read/README.md: -------------------------------------------------------------------------------- 1 | # ModbusRead Module -------------------------------------------------------------------------------- /v1/modules/modbus_read/devdoc/modbus_read.md: -------------------------------------------------------------------------------- 1 | # MODBUS_READ MODULE 2 | 3 | ## Overview 4 | 5 | This module is mainly a Modbus reader which also performs Modbus writeback action on demand. The module periodically read from a Modbus server and publish data to IoT Hub. 6 | 7 | ### Additional data types 8 | ```c 9 | typedef struct MODBUS_READ_OPERATION_TAG 10 | { 11 | struct MODBUS_READ_OPERATION_TAG * p_next; 12 | unsigned char unit_id; 13 | unsigned char function_code; 14 | unsigned short address; 15 | unsigned short length; 16 | }MODBUS_READ_OPERATION; 17 | 18 | typedef struct MODBUS_READ_CONFIG_TAG 19 | { 20 | struct MOD_BUS_READ_CONFIG_TAG * p_next; 21 | struct MODBUS_READ_OPERATION_TAG * p_operation; 22 | unsigned int interval; 23 | char server_ip[16]; 24 | char mac_address[18]; 25 | SOCKET_TYPE s; 26 | int time_check; 27 | }MODBUS_READ_CONFIG; 28 | ``` 29 | ## ModbusRead_ParseConfigurationFromJson 30 | ```c 31 | void* ModbusRead_ParseConfigurationFromJson(const void* configuration); 32 | ``` 33 | Creates a new configuration for MODBUS_READ module instance from a JSON string.`configuration` is a pointer to a `const char*` that contains a json object as supplied by `Gateway_CreateFromJson`. 34 | By convention the json object should contain the target modbus server and related read operation settings. 35 | 36 | ### Expected Arguments 37 | 38 | The arguments to this module is a JSON object with the following information: 39 | ```json 40 | { 41 | "serverConnectionString": "", 42 | "interval": "", 43 | "deviceType": "", 44 | "macAddress": "", 45 | "sqliteEnabled": "<0/1 to specify whether to enable SQLite module command>", 46 | "operations": [ 47 | { 48 | "unitId": "", 49 | "functionCode": "", 50 | "startingAddress": "", 51 | "length": "" 52 | } 53 | ] 54 | } 55 | ``` 56 | Example: 57 | The following Gateway config file describes an instance of the "modbus_read" module, available .\modbus_read.dll: 58 | ```json 59 | { 60 | "modules" : 61 | [ 62 | { 63 | "name" : "modbus_read", 64 | "loader": { 65 | "type": "native", 66 | "entrypoint": { 67 | "module path" : "modbus_read.dll" 68 | } 69 | }, 70 | "args" : 71 | { 72 | "serverConnectionString": "127.0.0.1", 73 | "interval": "10000", 74 | "deviceType": "powerMeter", 75 | "macAddress": "01:01:01:01:01:01", 76 | "sqliteEnabled": "0", 77 | "operations": [ 78 | { 79 | "unitId": "1", 80 | "functionCode": "3", 81 | "startingAddress": "0", 82 | "length": "5" 83 | } 84 | ] 85 | } 86 | } 87 | ] 88 | } 89 | ``` 90 | 91 | 92 | **SRS_MODBUS_READ_JSON_99_021: [** If `broker` is NULL then `ModbusRead_CreateFromJson` shall fail and return NULL. **]** 93 | 94 | **SRS_MODBUS_READ_JSON_99_023: [** If `configuration` is NULL then `ModbusRead_CreateFromJson` shall fail and return NULL. **]** 95 | 96 | **SRS_MODBUS_READ_JSON_99_031: [** If `configuration` is not a JSON object, then `ModbusRead_CreateFromJson` shall fail and return NULL. **]** 97 | 98 | **SRS_MODBUS_READ_JSON_99_032: [** If the JSON value does not contain "args" array then `ModbusRead_CreateFromJson` shall fail and return NULL. **]** 99 | 100 | **SRS_MODBUS_READ_JSON_99_033: [** If the JSON object of `args` array does not contain "operations" array then `ModbusRead_CreateFromJson` shall fail and return NULL. **]** 101 | 102 | **SRS_MODBUS_READ_JSON_99_025: [** `ModbusRead_CreateFromJson` shall pass `broker` and the entire config to `ModbusRead_Create`. **]** 103 | 104 | **SRS_MODBUS_READ_JSON_99_026: [** If `ModbusRead_Create` succeeds then `ModbusRead_CreateFromJson` shall succeed and return a non-NULL value. **]** 105 | 106 | **SRS_MODBUS_READ_JSON_99_027: [** If `ModbusRead_Create` fails then `ModbusRead_CreateFromJson` shall fail and return NULL. **]** 107 | 108 | **SRS_MODBUS_READ_JSON_99_034: [** If the `args` object does not contain a value named "serverConnectionString" then `ModbusRead_CreateFromJson` shall fail and return NULL. **]** 109 | 110 | **SRS_MODBUS_READ_JSON_99_035: [** If the `args` object does not contain a value named "macAddress" then `ModbusRead_CreateFromJson` shall fail and return NULL. **]** 111 | 112 | **SRS_MODBUS_READ_JSON_99_036: [** If the `args` object does not contain a value named "interval" then `ModbusRead_CreateFromJson` shall fail and return NULL. **]** 113 | 114 | **SRS_MODBUS_READ_JSON_99_046: [** If the `args` object does not contain a value named "deviceType" then `ModbusRead_CreateFromJson` shall fail and return NULL. **]** 115 | 116 | **SRS_MODBUS_READ_JSON_99_041: [** `ModbusRead_CreateFromJson` shall use "serverConnectionString", "macAddress", and "interval" values as the fields for an `MODBUS_READ_CONFIG` structure and add this element to the link list. **]** 117 | 118 | **SRS_MODBUS_READ_JSON_99_037: [** If the `operations` object does not contain a value named "unitId" then `ModbusRead_CreateFromJson` shall fail and return NULL. **]** 119 | 120 | **SRS_MODBUS_READ_JSON_99_038: [** If the `operations` object does not contain a value named "functionCode" then `ModbusRead_CreateFromJson` shall fail and return NULL. **]** 121 | 122 | **SRS_MODBUS_READ_JSON_99_039: [** If the `operations` object does not contain a value named "startingAddress" then `ModbusRead_CreateFromJson` shall fail and return NULL. **]** 123 | 124 | **SRS_MODBUS_READ_JSON_99_040: [** If the `operations` object does not contain a value named "length" then `ModbusRead_CreateFromJson` shall fail and return NULL. **]** 125 | 126 | **SRS_MODBUS_READ_JSON_99_042: [** `ModbusRead_CreateFromJson` shall use "unitId", "functionCode", "startingAddress" and "length" values as the fields for an `MODBUS_READ_OPERATION` structure and add this element to the link list. **]** 127 | 128 | **SRS_MODBUS_READ_JSON_99_043: [** If the `malloc` for `config` fail, `ModbusRead_CreateFromJson` shall fail and return NULL. **]** 129 | 130 | **SRS_MODBUS_READ_JSON_99_044: [** If the `malloc` for `operation` fail, `ModbusRead_CreateFromJson` shall fail and return NULL. **]** 131 | 132 | **SRS_MODBUS_READ_JSON_99_045: [** `ModbusRead_CreateFromJson` shall walk through each object of the array. **]** 133 | 134 | 135 | ## ModbusRead_Create 136 | ```c 137 | MODULE_HANDLE ModbusRead_Create(BROKER_HANDLE broker, const void* configuration); 138 | ``` 139 | Creates a new `MODBUS_READ` instance. `configuration` is a pointer to a `MODBUS_READ_CONFIG`. 140 | 141 | **SRS_MODBUS_READ_99_001: [**If `broker` is NULL then `ModbusRead_Create` shall fail and return NULL.**]** 142 | 143 | **SRS_MODBUS_READ_99_002: [**If `configuration` is NULL then `ModbusRead_Create` shall fail and return NULL.**]** 144 | 145 | **SRS_MODBUS_READ_99_007: [**If `ModbusRead_Create` encounters any errors while creating the `MODBUSREAD_HANDLE_DATA` then it shall fail and return NULL.**]** 146 | 147 | **SRS_MODBUS_READ_99_008: [**Otherwise `ModbusRead_Create` shall return a non-NULL pointer.**]** 148 | 149 | 150 | ## ModbusRead_Receive 151 | ```c 152 | void ModbusRead_Receive(MODULE_HANDLE moduleHandle, MESSAGE_HANDLE messageHandle); 153 | ``` 154 | 155 | 156 | **SRS_MODBUS_READ_99_009: [**If `moduleHandle` is NULL then `ModbusRead_Receive` shall fail and return.**]** 157 | 158 | **SRS_MODBUS_READ_99_010: [**If `messageHandle` is NULL then `ModbusRead_Receive` shall fail and return.**]** 159 | 160 | **SRS_MODBUS_READ_99_011: [**If `messageHandle` properties does not contain a "source" property, then `ModbusRead_Receive` shall fail and return.**]** 161 | 162 | **SRS_MODBUS_READ_99_012: [**If `messageHandle` properties contains a "deviceKey" property, then `ModbusRead_Receive` shall fail and return.**]** 163 | 164 | **SRS_MODBUS_READ_99_013: [**If `messageHandle` properties contains a "source" property that is set to "mapping", then `ModbusRead_Receive` shall fail and return.**]** 165 | 166 | **SRS_MODBUS_READ_99_017: [**`ModbusRead_Receive` shall return.**]** 167 | 168 | **SRS_MODBUS_READ_99_018: [**If content of `messageHandle` is not a JSON value, then `ModbusRead_Receive` shall fail and return NULL.**]** 169 | 170 | 171 | ## ModbusRead_FreeConfiguration 172 | ```c 173 | void ModbusRead_FreeConfiguration(void* configuration); 174 | ``` 175 | **SRS_MODBUS_READ_99_006: [**`ModbusRead_FreeConfiguration` shall do nothing, cleanup is done in `ModbusRead_Destroy`.**]** 176 | 177 | 178 | ## ModbusRead_Destroy 179 | ```c 180 | void ModbusRead_Destroy(MODULE_HANDLE moduleHandle); 181 | ``` 182 | **SRS_MODBUS_READ_99_014: [**If `moduleHandle` is NULL then `ModbusRead_Destroy` shall return.**]** 183 | 184 | **SRS_MODBUS_READ_99_015: [**Otherwise `ModbusRead_Destroy` shall unuse all used resources.**]** 185 | 186 | 187 | ## Module_GetAPIs 188 | ```c 189 | extern const MODULE_API* MODULE_STATIC_GETAPI(MODBUSREAD_MODULE)(const MODULE_API_VERSION gateway_api_version); 190 | extern const MODULE_API* Module_GetApi(const MODULE_API_VERSION gateway_api_version) 191 | ``` 192 | **SRS_MODBUS_READ_99_016: [**`Module_GetApi` shall return a pointer to a `MODULE_API` structure with the required function pointers.**]** 193 | -------------------------------------------------------------------------------- /v1/modules/modbus_read/inc/modbus_read.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #ifndef MODBUS_READ_H 5 | #define MODBUS_READ_H 6 | 7 | #include "module.h" 8 | #include "modbus_read_common.h" 9 | 10 | #ifdef __cplusplus 11 | extern "C" 12 | { 13 | #endif 14 | 15 | MODULE_EXPORT const MODULE_API* MODULE_STATIC_GETAPI(MODBUSREAD_MODULE)(const MODULE_API_VERSION gateway_api_version); 16 | 17 | #ifdef __cplusplus 18 | } 19 | #endif 20 | 21 | 22 | #endif /*MODBUS_READ_H*/ 23 | -------------------------------------------------------------------------------- /v1/modules/modbus_read/inc/modbus_read_common.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #ifndef MODBUS_READ_COMMON_H 5 | #define MODBUS_READ_COMMON_H 6 | 7 | #include "parson.h" 8 | #define SOCKET_CLOSED (0) 9 | 10 | #ifdef WIN32 11 | 12 | #define _WINSOCK_DEPRECATED_NO_WARNINGS 13 | #include 14 | #pragma comment(lib,"ws2_32.lib") //Winsock Library 15 | #define SOCKET_TYPE SOCKET 16 | #define FILE_TYPE HANDLE 17 | #define INVALID_FILE INVALID_HANDLE_VALUE 18 | #define SNPRINTF_S sprintf_s 19 | 20 | #else 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #define SOCKET_TYPE int 30 | #define FILE_TYPE int 31 | #define SOCKET_ERROR (-1) 32 | #define INVALID_SOCKET (~0) 33 | #define INVALID_FILE -1 34 | #define SNPRINTF_S snprintf 35 | 36 | #endif 37 | 38 | typedef struct MODBUS_READ_CONFIG_TAG MODBUS_READ_CONFIG; 39 | typedef struct MODBUS_READ_OPERATION_TAG MODBUS_READ_OPERATION; 40 | 41 | typedef int(*encode_read_cb_type)(void*, void*, void*); 42 | typedef int(*encode_write_cb_type)(void*, void*, unsigned char, unsigned char, unsigned short, unsigned short); 43 | typedef int(*decode_response_cb_type)(void*, void*); 44 | typedef int(*send_request_cb_type)(MODBUS_READ_CONFIG *, unsigned char*, int, unsigned char*); 45 | typedef void(*close_server_cb_type)(MODBUS_READ_CONFIG *); 46 | 47 | //baud 48 | #define CONFIG_BAUD_9600 9600 49 | //stop bits 50 | #define CONFIG_STOP_ONE 0 51 | #define CONFIG_STOP_ONE5 1 52 | #define CONFIG_STOP_TWO 2 53 | //data bits 54 | #define CONFIG_DATA_8 8 55 | //parity 56 | #define CONFIG_PARITY_NO 0 57 | #define CONFIG_PARITY_ODD 1 58 | #define CONFIG_PARITY_EVEN 2 59 | #define CONFIG_PARITY_MARK 3 60 | #define CONFIG_PARITY_SPACE 4 61 | //flow control 62 | #define CONFIG_FLOW_CONTROL_NONE 0 63 | #define CONFIG_FLOW_CONTROL_XONOFF 1 64 | #define CONFIG_FLOW_CONTROL_RTSCTS 2 65 | #define CONFIG_FLOW_CONTROL_DSRDTR 3 66 | 67 | struct MODBUS_READ_OPERATION_TAG 68 | { 69 | MODBUS_READ_OPERATION * p_next; 70 | unsigned char unit_id; 71 | unsigned char function_code; 72 | unsigned short address; 73 | unsigned short length; 74 | unsigned char read_request[256]; 75 | int read_request_len; 76 | }; 77 | 78 | struct MODBUS_READ_CONFIG_TAG 79 | { 80 | MODBUS_READ_CONFIG * p_next; 81 | MODBUS_READ_OPERATION * p_operation; 82 | size_t read_interval; 83 | char server_str[16]; 84 | char mac_address[18]; 85 | char device_type[64]; 86 | int sqlite_enabled; 87 | SOCKET_TYPE socks; 88 | FILE_TYPE files; 89 | size_t time_check; 90 | unsigned int baud_rate; 91 | unsigned char stop_bits; 92 | unsigned char data_bits; 93 | unsigned char parity; 94 | unsigned char flow_control; 95 | encode_read_cb_type encode_read_cb; 96 | encode_write_cb_type encode_write_cb; 97 | decode_response_cb_type decode_response_cb; 98 | send_request_cb_type send_request_cb; 99 | close_server_cb_type close_server_cb; 100 | }; /*this needs to be passed to the Module_Create function*/ 101 | 102 | #endif /*MODBUS_READ_COMMON_H*/ 103 | -------------------------------------------------------------------------------- /v1/modules/modbus_read/src/modbus_read.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #include 5 | #ifdef _CRTDBG_MAP_ALLOC 6 | #include 7 | #endif 8 | #include "azure_c_shared_utility/gballoc.h" 9 | 10 | #include "module.h" 11 | #include 12 | 13 | #include "azure_c_shared_utility/threadapi.h" 14 | #include "modbus_read.h" 15 | #include "message.h" 16 | #include "azure_c_shared_utility/xlogging.h" 17 | #include "azure_c_shared_utility/lock.h" 18 | 19 | typedef struct MODBUSREAD_HANDLE_DATA_TAG 20 | { 21 | THREAD_HANDLE threadHandle; 22 | LOCK_HANDLE lockHandle; 23 | int stopThread; 24 | BROKER_HANDLE broker; 25 | MODBUS_READ_CONFIG * config; 26 | 27 | }MODBUSREAD_HANDLE_DATA; 28 | 29 | #define CONNECTION_TCP 0 30 | #define CONNECTION_COM 1 31 | #define CONNECTION_UNKNOWN 2 32 | #define MODBUS_MESSAGE "modbus read" 33 | #define MODBUS_TCP_OFFSET 7 34 | #define MODBUS_COM_OFFSET 1 35 | #define TIMESTRLEN 19 36 | #define NUMOFBITS 8 37 | #define MACSTRLEN 17 38 | #define BUFSIZE 1024 39 | 40 | /* 41 | ----------------------- -------- 42 | |MBAP Header description|Length | 43 | ----------------------- -------- 44 | |Transaction Identifier |2 bytes | 45 | ----------------------- -------- 46 | |Protocol Identifier |2 bytes | 47 | ----------------------- -------- 48 | |Length 2bytes |2 bytes | 49 | ----------------------- -------- 50 | |Unit Identifier |1 byte | 51 | ----------------------- -------- 52 | |Body |variable| 53 | ----------------------- -------- 54 | */ 55 | 56 | 57 | JSON_Value *root_value; 58 | JSON_Object *root_object; 59 | char *serialized_string; 60 | 61 | JSON_Value *sqlite_root_value; 62 | JSON_Object *sqlite_root_object; 63 | char *sqlite_serialized_string; 64 | 65 | char sqlite_upsert[BUFSIZE]; 66 | char glob_currentTime[TIMESTRLEN + 1]; 67 | char glob_currentMac[MACSTRLEN + 1]; 68 | 69 | static void modbus_config_cleanup(MODBUS_READ_CONFIG * config) 70 | { 71 | MODBUS_READ_CONFIG * modbus_config = config; 72 | while (modbus_config) 73 | { 74 | MODBUS_READ_OPERATION * modbus_operation = modbus_config->p_operation; 75 | while (modbus_operation) 76 | { 77 | MODBUS_READ_OPERATION * temp_operation = modbus_operation; 78 | modbus_operation = modbus_operation->p_next; 79 | free(temp_operation); 80 | } 81 | 82 | MODBUS_READ_CONFIG * temp_config = modbus_config; 83 | modbus_config = modbus_config->p_next; 84 | free(temp_config); 85 | } 86 | } 87 | static bool isValidMac(char* mac) 88 | { 89 | //format XX:XX:XX:XX:XX:XX 90 | bool ret = true; 91 | int len = strlen(mac); 92 | 93 | if (len != MACSTRLEN) 94 | { 95 | LogError("invalid mac length: %d", len); 96 | ret = false; 97 | } 98 | else 99 | { 100 | for (int mac_char = 0; mac_char < MACSTRLEN; mac_char++) 101 | { 102 | if (((mac_char + 1) % 3 == 0)) 103 | { 104 | if (mac[mac_char] != ':') 105 | { 106 | ret = false; 107 | break; 108 | } 109 | } 110 | else 111 | { 112 | if (!((mac[mac_char] >= '0' && mac[mac_char] <= '9') || (mac[mac_char] >= 'a' && mac[mac_char] <= 'f') || (mac[mac_char] >= 'A' && mac[mac_char] <= 'F'))) 113 | { 114 | ret = false; 115 | break; 116 | } 117 | } 118 | } 119 | } 120 | return ret; 121 | } 122 | 123 | static int getServerType(char* server) 124 | { 125 | //ipv4 format XXX.XXX.XXX.XXX 126 | //serial port format COMX 127 | int ret = CONNECTION_UNKNOWN; 128 | 129 | if (memcmp(server, "COM", 3) == 0) 130 | { 131 | ret = CONNECTION_COM; 132 | if (0 > atoi(server + 3)) 133 | { 134 | LogError("invalid COM port: %s", server); 135 | ret = CONNECTION_UNKNOWN; 136 | } 137 | } 138 | else 139 | { 140 | ret = CONNECTION_TCP; 141 | if (inet_addr(server) == INADDR_NONE) 142 | { 143 | LogError("invalid ipv4: %s", server); 144 | ret = CONNECTION_UNKNOWN; 145 | } 146 | } 147 | 148 | return ret; 149 | } 150 | 151 | 152 | static bool addOneOperation(MODBUS_READ_OPERATION * operation, JSON_Object * operation_obj) 153 | { 154 | bool result = true; 155 | const char* unit_id = json_object_get_string(operation_obj, "unitId"); 156 | const char* function = json_object_get_string(operation_obj, "functionCode"); 157 | const char* address = json_object_get_string(operation_obj, "startingAddress"); 158 | const char* length = json_object_get_string(operation_obj, "length"); 159 | 160 | if (unit_id == NULL) 161 | { 162 | /*Codes_SRS_MODBUS_READ_JSON_99_037: [ If the `operations` object does not contain a value named "unitId" then ModbusRead_CreateFromJson shall fail and return NULL. ]*/ 163 | LogError("Did not find expected %s configuration", "unitId"); 164 | result = false; 165 | } 166 | else if (function == NULL) 167 | { 168 | /*Codes_SRS_MODBUS_READ_JSON_99_038: [ If the `operations` object does not contain a value named "functionCode" then ModbusRead_CreateFromJson shall fail and return NULL. ]*/ 169 | LogError("Did not find expected %s configuration", "functionCode"); 170 | result = false; 171 | } 172 | else if (address == NULL) 173 | { 174 | /*Codes_SRS_MODBUS_READ_JSON_99_039: [ If the `operations` object does not contain a value named "startingAddress" then ModbusRead_CreateFromJson shall fail and return NULL. ]*/ 175 | LogError("Did not find expected %s configuration", "startingAddress"); 176 | result = false; 177 | } 178 | else if (length == NULL) 179 | { 180 | /*Codes_SRS_MODBUS_READ_JSON_99_040: [ If the `operations` object does not contain a value named "length" then ModbusRead_CreateFromJson shall fail and return NULL. ]*/ 181 | LogError("Did not find expected %s configuration", "length"); 182 | result = false; 183 | } 184 | 185 | if (!result) 186 | { 187 | return result; 188 | } 189 | 190 | /*Codes_SRS_MODBUS_READ_JSON_99_042: [ `ModbusRead_CreateFromJson` shall use "unitId", "functionCode", "startingAddress" and "length" values as the fields for an MODBUS_READ_OPERATION structure and add this element to the link list. ]*/ 191 | operation->unit_id = atoi(unit_id); 192 | operation->function_code = atoi(function); 193 | operation->address = atoi(address); 194 | operation->length = atoi(length); 195 | 196 | return result; 197 | } 198 | static bool addAllOperations(MODBUS_READ_CONFIG * config, JSON_Array * operation_array) 199 | { 200 | bool ret = true; 201 | int operation_i; 202 | int operation_count = json_array_get_count(operation_array); 203 | /*Codes_SRS_MODBUS_READ_JSON_99_045: [** ModbusRead_CreateFromJson shall walk through each object of the array. **]*/ 204 | for (operation_i = 0; operation_i < operation_count; operation_i++) 205 | { 206 | MODBUS_READ_OPERATION * operation = malloc(sizeof(MODBUS_READ_OPERATION)); 207 | /*Codes_SRS_MODBUS_READ_JSON_99_044: [ If the 'malloc' for `operation` fail, ModbusRead_CreateFromJson shall fail and return NULL. ]*/ 208 | if (operation == NULL) 209 | { 210 | ret = false; 211 | break; 212 | } 213 | else 214 | { 215 | operation->p_next = config->p_operation; 216 | config->p_operation = operation; 217 | JSON_Object* operation_obj = json_array_get_object(operation_array, operation_i); 218 | 219 | //add one operation 220 | if (!addOneOperation(operation, operation_obj)) 221 | { 222 | ret = false; 223 | break; 224 | } 225 | } 226 | } 227 | return ret; 228 | } 229 | static bool addOneServer(MODBUS_READ_CONFIG * config, JSON_Object * arg_obj) 230 | { 231 | bool result = true; 232 | const char* server_str = json_object_get_string(arg_obj, "serverConnectionString"); 233 | const char* mac_address = json_object_get_string(arg_obj, "macAddress"); 234 | const char* baud_rate = json_object_get_string(arg_obj, "baudRate"); 235 | const char* stop_bits = json_object_get_string(arg_obj, "stopBits"); 236 | const char* data_bits = json_object_get_string(arg_obj, "dataBits"); 237 | const char* parity = json_object_get_string(arg_obj, "parity"); 238 | const char* flow_control = json_object_get_string(arg_obj, "flowControl"); 239 | const char* interval = json_object_get_string(arg_obj, "interval"); 240 | const char* device_type = json_object_get_string(arg_obj, "deviceType"); 241 | const char* sqlite_enabled = json_object_get_string(arg_obj, "sqliteEnabled"); 242 | if (server_str == NULL || getServerType((char *)server_str) == CONNECTION_UNKNOWN) 243 | { 244 | /*Codes_SRS_MODBUS_READ_JSON_99_034: [ If the `args` object does not contain a value named "serverConnectionString" then ModbusRead_CreateFromJson shall fail and return NULL. ]*/ 245 | LogError("Did not find expected %s configuration", "serverConnectionString"); 246 | result = false; 247 | } 248 | else if (mac_address == NULL || !isValidMac((char *)mac_address)) 249 | { 250 | /*Codes_SRS_MODBUS_READ_JSON_99_035: [ If the `args` object does not contain a value named "macAddress" then ModbusRead_CreateFromJson shall fail and return NULL. ]*/ 251 | LogError("Did not find expected %s configuration", "macAddress"); 252 | result = false; 253 | } 254 | else if (interval == NULL) 255 | { 256 | /*Codes_SRS_MODBUS_READ_JSON_99_036: [ If the `args` object does not contain a value named "interval" then ModbusRead_CreateFromJson shall fail and return NULL. ]*/ 257 | LogError("Did not find expected %s configuration", "interval"); 258 | result = false; 259 | } 260 | else if (device_type == NULL) 261 | { 262 | /*Codes_SRS_MODBUS_READ_JSON_99_046: [ If the `args` object does not contain a value named "deviceType" then ModbusRead_CreateFromJson shall fail and return NULL. ]*/ 263 | LogError("Did not find expected %s configuration", "deviceType"); 264 | result = false; 265 | } 266 | else if (sqlite_enabled == NULL) 267 | { 268 | /*Codes_SRS_MODBUS_READ_JSON_99_046: [ If the `args` object does not contain a value named "deviceType" then ModbusRead_CreateFromJson shall fail and return NULL. ]*/ 269 | LogError("Did not find expected %s configuration", "sqliteEnabled"); 270 | result = false; 271 | } 272 | 273 | if (!result) 274 | { 275 | return result; 276 | } 277 | 278 | /*Codes_SRS_MODBUS_READ_JSON_99_041: [ `ModbusRead_CreateFromJson` shall use "serverConnectionString", "macAddress", and "interval" values as the fields for an MODBUS_READ_CONFIG structure and add this element to the link list. ]*/ 279 | memcpy(config->server_str, server_str, strlen(server_str)); 280 | config->server_str[strlen(server_str)] = '\0'; 281 | 282 | int i = 0; 283 | char * temp = mac_address; 284 | while (*temp) 285 | { 286 | config->mac_address[i] = toupper(*temp); 287 | i++; 288 | temp++; 289 | } 290 | config->mac_address[strlen(mac_address)] = '\0'; 291 | 292 | memcpy(config->device_type, device_type, strlen(device_type)); 293 | config->device_type[strlen(device_type)] = '\0'; 294 | 295 | config->read_interval = atoi(interval); 296 | config->sqlite_enabled = atoi(sqlite_enabled); 297 | 298 | config->baud_rate = CONFIG_BAUD_9600; 299 | if (baud_rate != NULL) 300 | { 301 | config->baud_rate = atoi(baud_rate); 302 | } 303 | 304 | config->data_bits = CONFIG_DATA_8; 305 | if (data_bits != NULL) 306 | { 307 | config->data_bits = atoi(data_bits); 308 | } 309 | 310 | config->stop_bits = CONFIG_STOP_ONE; 311 | if (stop_bits != NULL) 312 | { 313 | if (strcmp(stop_bits, "1") == 0) 314 | config->stop_bits = CONFIG_STOP_ONE; 315 | else if (strcmp(stop_bits, "1.5") == 0) 316 | config->stop_bits = CONFIG_STOP_ONE5; 317 | else if (strcmp(stop_bits, "2") == 0) 318 | config->stop_bits = CONFIG_STOP_TWO; 319 | } 320 | 321 | config->parity = CONFIG_PARITY_NO; 322 | if (parity != NULL) 323 | { 324 | if (strcmp(parity, "NONE") == 0) 325 | config->parity = CONFIG_PARITY_NO; 326 | else if (strcmp(parity, "ODD") == 0) 327 | config->parity = CONFIG_PARITY_ODD; 328 | else if (strcmp(parity, "EVEN") == 0) 329 | config->parity = CONFIG_PARITY_EVEN; 330 | else if (strcmp(parity, "MARK") == 0) 331 | config->parity = CONFIG_PARITY_MARK; 332 | else if (strcmp(parity, "SPACE") == 0) 333 | config->parity = CONFIG_PARITY_SPACE; 334 | } 335 | 336 | config->flow_control = CONFIG_FLOW_CONTROL_NONE; 337 | if (flow_control != NULL) 338 | { 339 | if (strcmp(flow_control, "NONE") == 0) 340 | config->flow_control = CONFIG_FLOW_CONTROL_NONE; 341 | else if (strcmp(flow_control, "XON") == 0) 342 | config->flow_control = CONFIG_FLOW_CONTROL_XONOFF; 343 | else if (strcmp(flow_control, "RTS") == 0) 344 | config->flow_control = CONFIG_FLOW_CONTROL_RTSCTS; 345 | else if (strcmp(flow_control, "DSR") == 0) 346 | config->flow_control = CONFIG_FLOW_CONTROL_DSRDTR; 347 | } 348 | 349 | return result; 350 | } 351 | static MODBUS_READ_CONFIG * addAllServers(JSON_Array * arg_array) 352 | { 353 | int arg_i; 354 | int arg_count = json_array_get_count(arg_array); 355 | MODBUS_READ_CONFIG * ret_config = NULL; 356 | bool fail = false; 357 | 358 | /*Codes_SRS_MODBUS_READ_JSON_99_045: [** ModbusRead_CreateFromJson shall walk through each object of the array. **]*/ 359 | for (arg_i = 0; arg_i < arg_count; arg_i++) 360 | { 361 | MODBUS_READ_CONFIG * config = malloc(sizeof(MODBUS_READ_CONFIG)); 362 | if (config == NULL) 363 | { 364 | /*Codes_SRS_MODBUS_READ_JSON_99_043: [ If the 'malloc' for `config` fail, ModbusRead_CreateFromJson shall fail and return NULL. ]*/ 365 | fail = true; 366 | break; 367 | } 368 | else 369 | { 370 | memset(config, 0, sizeof(MODBUS_READ_CONFIG));// to set socket s to 0 371 | config->p_next = ret_config; 372 | ret_config = config; 373 | 374 | JSON_Object* arg_obj = json_array_get_object(arg_array, arg_i); 375 | 376 | if (!addOneServer(config, arg_obj)) 377 | { 378 | fail = true; 379 | break; 380 | } 381 | JSON_Array * operation_array = json_object_get_array(arg_obj, "operations"); 382 | if (operation_array != NULL) 383 | { 384 | if (!addAllOperations(config, operation_array)) 385 | { 386 | fail = true; 387 | break; 388 | } 389 | } 390 | else 391 | { 392 | fail = true; 393 | break; 394 | } 395 | } 396 | } 397 | if (fail) 398 | { 399 | modbus_config_cleanup(ret_config); 400 | ret_config = NULL; 401 | } 402 | return ret_config; 403 | } 404 | static int get_crc(unsigned char * message, int length, unsigned short * out)//refer to J.J. Lee's code 405 | { 406 | unsigned short crcFull = 0xFFFF; 407 | 408 | if (message == NULL || length <= 0) 409 | return -1; 410 | 411 | for (int _byte = 0; _byte < length; ++_byte) 412 | { 413 | crcFull = (unsigned short)(crcFull ^ message[_byte]); 414 | 415 | for (int _bit = 0; _bit < NUMOFBITS; ++_bit) 416 | { 417 | char crcLsb = (char)(crcFull & 0x0001); 418 | crcFull = (unsigned short)((crcFull >> 1) & 0x7FFF); 419 | if (crcLsb == 1) 420 | { 421 | crcFull = (unsigned short)(crcFull ^ 0xA001); 422 | } 423 | } 424 | } 425 | *out = crcFull; 426 | return 0; 427 | } 428 | 429 | static int send_request_com(MODBUS_READ_CONFIG * config, unsigned char * request, int request_len, unsigned char * response) 430 | { 431 | int write_size; 432 | int read_size; 433 | #ifdef WIN32 434 | (void)ThreadAPI_Sleep(500); 435 | if (!WriteFile(config->files, request, request_len, &write_size, NULL))//Additional Address+PDU+Error 436 | { 437 | LogError("write failed"); 438 | return -1; 439 | } 440 | if (!ReadFile(config->files, response, 3, &read_size, NULL)) 441 | { 442 | LogError("read failed"); 443 | return -1; 444 | } 445 | else 446 | { 447 | if (!ReadFile(config->files, response + 3, response[2] + 2, &read_size, NULL)) 448 | { 449 | LogError("read failed"); 450 | return -1; 451 | } 452 | } 453 | #else 454 | write_size = write(config->files, request, request_len); 455 | if (write_size != request_len) 456 | { 457 | LogError("write failed"); 458 | return -1; 459 | } 460 | 461 | read_size = read(config->files, response, 255); 462 | if (read_size <= 0) 463 | { 464 | LogError("read failed"); 465 | return -1; 466 | } 467 | #endif 468 | if (response[MODBUS_COM_OFFSET] == (request[MODBUS_COM_OFFSET] + 128)) 469 | return response[MODBUS_COM_OFFSET+1]; 470 | return 0; 471 | } 472 | 473 | static int send_with_len_check(SOCKET_TYPE sock, unsigned char * request, int request_len) 474 | { 475 | int total_send = 0; 476 | int send_size = 0; 477 | while (request_len > 0) 478 | { 479 | send_size = send(sock, request + total_send, request_len, 0); 480 | if (send_size < 0) 481 | { 482 | LogError("send failed"); 483 | return send_size; 484 | } 485 | total_send += send_size; 486 | request_len -= send_size; 487 | } 488 | return 0; 489 | } 490 | 491 | static int recv_with_len_check(SOCKET_TYPE sock, unsigned char * response) 492 | { 493 | int total_recv = 0; 494 | int recv_size = 0; 495 | unsigned short expected_len = 0; 496 | while (total_recv < MODBUS_TCP_OFFSET || expected_len > (total_recv - 6)) 497 | { 498 | recv_size = recv(sock, response + total_recv, (expected_len == 0) ? MODBUS_TCP_OFFSET : expected_len + 6 - total_recv, 0); 499 | if (recv_size == SOCKET_ERROR || recv_size == SOCKET_CLOSED) 500 | { 501 | LogError("recv failed"); 502 | return recv_size; 503 | } 504 | total_recv += recv_size; 505 | if (total_recv >= MODBUS_TCP_OFFSET && expected_len == 0) 506 | expected_len = ntohs(*(unsigned short *)(response + 4)); 507 | } 508 | return total_recv; 509 | } 510 | 511 | static int send_request_tcp(MODBUS_READ_CONFIG * config, unsigned char * request, int request_len, unsigned char * response) 512 | { 513 | int recv_size; 514 | if (send_with_len_check(config->socks, request, request_len) < 0)//MBAP+PDU 515 | { 516 | LogError("send failed"); 517 | return -1; 518 | } 519 | recv_size = recv_with_len_check(config->socks, response); 520 | if (recv_size == SOCKET_ERROR || recv_size == SOCKET_CLOSED) 521 | { 522 | LogError("recv failed"); 523 | return -1; 524 | } 525 | if (response[MODBUS_TCP_OFFSET] == (request[MODBUS_TCP_OFFSET] + 128)) 526 | return response[MODBUS_TCP_OFFSET + 1]; 527 | return 0; 528 | } 529 | static void encode_write_PDU(unsigned char * buf, unsigned char functionCode, unsigned short startingAddress, unsigned short value) 530 | { 531 | unsigned short * _pU16; 532 | //encoding PDU 533 | buf[0] = functionCode; //function code 534 | _pU16 = (unsigned short *)(buf + 1); 535 | *_pU16 = htons(startingAddress - 1); //addr (2 bytes) 536 | _pU16 = (unsigned short *)(buf + 3); 537 | if (buf[0] == 5)//single coil 538 | { 539 | *_pU16 = htons((value == 1) ? 0XFF00 : 0); 540 | } 541 | else if (buf[0] == 6)//single register 542 | { 543 | *_pU16 = htons(value); 544 | } 545 | } 546 | static void encode_read_PDU(unsigned char * buf, MODBUS_READ_OPERATION * operation) 547 | { 548 | unsigned short * _pU16; 549 | //encoding PDU 550 | buf[0] = operation->function_code; //function code 551 | _pU16 = (unsigned short *)(buf + 1); 552 | *_pU16 = htons(operation->address - 1); //addr (2 bytes) 553 | _pU16 = (unsigned short *)(buf + 3); 554 | *_pU16 = htons(operation->length); //length (2 bytes) 555 | } 556 | static void encode_MBAP(unsigned char * buf, int uid) 557 | { 558 | unsigned short * _pU16; 559 | //encoding MBAP 560 | _pU16 = (unsigned short *)buf; 561 | *_pU16 = 0; //Transaction ID (2 bytes) 562 | buf[2] = 0; //Protocol ID (2 bytes): 0 = MODBUS 563 | buf[3] = 0; 564 | _pU16 = (unsigned short *)(buf + 4); 565 | *_pU16 = htons(6); //Length (2 bytes) 566 | buf[6] = uid; //Unit ID (1 byte) 567 | } 568 | static int encode_write_request_tcp(unsigned char * buf, int * len, unsigned char uid, unsigned char functionCode, unsigned short startingAddres, unsigned short value) 569 | { 570 | encode_MBAP(buf, uid); 571 | 572 | encode_write_PDU(buf + MODBUS_TCP_OFFSET, functionCode, startingAddres, value); 573 | 574 | *len = 12; 575 | 576 | return 0; 577 | } 578 | static int encode_read_request_tcp(unsigned char * buf, int * len, MODBUS_READ_OPERATION * operation) 579 | { 580 | encode_MBAP(buf, operation->unit_id); 581 | 582 | encode_read_PDU(buf + MODBUS_TCP_OFFSET, operation); 583 | 584 | *len = 12; 585 | 586 | return 0; 587 | } 588 | static int encode_write_request_com(unsigned char * buf, int * len, unsigned char uid, unsigned char functionCode, unsigned short startingAddress, unsigned short value) 589 | { 590 | unsigned short * _pU16; 591 | unsigned short crc; 592 | int ret = 0; 593 | 594 | buf[0] = uid; //Unit ID (1 byte) 595 | 596 | encode_write_PDU(buf + MODBUS_COM_OFFSET, functionCode, startingAddress, value); 597 | 598 | if (get_crc(buf, 6, &crc) == -1) 599 | ret = -1; 600 | else 601 | { 602 | _pU16 = (unsigned short *)(buf + 6); 603 | *_pU16 = crc; 604 | *len = 8; 605 | } 606 | 607 | return ret; 608 | } 609 | static int encode_read_request_com(unsigned char * buf, int * len, MODBUS_READ_OPERATION * operation) 610 | { 611 | unsigned short * _pU16; 612 | unsigned short crc; 613 | int ret = 0; 614 | 615 | //encoding MBAP 616 | buf[0] = operation->unit_id; //Unit ID (1 byte) 617 | 618 | encode_read_PDU(buf + MODBUS_COM_OFFSET, operation); 619 | 620 | if (get_crc(buf, 6, &crc) == -1) 621 | ret = -1; 622 | else 623 | { 624 | _pU16 = (unsigned short *)(buf + 6); 625 | *_pU16 = crc; 626 | *len = 8; 627 | } 628 | 629 | return ret; 630 | } 631 | static int decode_response_PDU(unsigned char * buf, MODBUS_READ_OPERATION* operation) 632 | { 633 | unsigned char byte_count = buf[1]; 634 | unsigned char index = 0; 635 | unsigned short count; 636 | int step_size = 0; 637 | unsigned char start_digit; 638 | char tempKey[64]; 639 | char tempValue[64]; 640 | 641 | if (buf[0] == 1 || buf[0] == 2)//discrete input or coil status 1 bit 642 | { 643 | count = (byte_count * 8); 644 | count = (count > operation->length) ? operation->length : count; 645 | step_size = 1; 646 | start_digit = buf[0] - 1; 647 | } 648 | else if (buf[0] == 3 || buf[0] == 4)//register 16 bits 649 | { 650 | count = byte_count; 651 | step_size = 2; 652 | start_digit = (buf[0] == 3) ? 4 : 3; 653 | } 654 | else 655 | return -1; 656 | 657 | while (count > index) 658 | { 659 | memset(tempKey, 0, sizeof(tempKey)); 660 | memset(tempValue, 0, sizeof(tempValue)); 661 | if (step_size == 1) 662 | { 663 | LogInfo("status %01X%04u: <%01X>\n", start_digit, operation->address + index, (buf[2 + (index / 8)] >> (index % 8)) & 1); 664 | 665 | if (SNPRINTF_S(tempKey, sizeof(tempKey), "address_%01X%04u", start_digit, operation->address + index)<0 || 666 | SNPRINTF_S(tempValue, sizeof(tempValue), "%01X", (buf[2 + (index / 8)] >> (index % 8)) & 1)< 0) 667 | { 668 | LogError("Failed to set message text"); 669 | } 670 | else 671 | { 672 | json_object_set_string(root_object, tempKey, tempValue); 673 | } 674 | } 675 | else 676 | { 677 | LogInfo("register %01X%04u: <%02X%02X>\n", start_digit, operation->address + (index / 2), buf[2 + index], buf[3 + index]); 678 | 679 | if (SNPRINTF_S(tempKey, sizeof(tempKey), "address_%01X%04u", start_digit, operation->address + (index / 2))<0 || 680 | SNPRINTF_S(tempValue, sizeof(tempValue), "%05u", buf[2 + index] * (0x100) + buf[3 + index])< 0) 681 | { 682 | LogError("Failed to set message text"); 683 | } 684 | else 685 | { 686 | json_object_set_string(root_object, tempKey, tempValue); 687 | } 688 | } 689 | if (strlen(tempKey) > 0 && strlen(tempValue) > 0) 690 | { 691 | int offset = strlen(sqlite_upsert); 692 | SNPRINTF_S(sqlite_upsert + offset, BUFSIZE - 1 - offset, "INSERT INTO MODBUS(VALUE,ADDRESS,MAC,DATETIME) VALUES(%s,%s,'%s','%s');", tempValue, tempKey + 8, glob_currentMac, glob_currentTime); 693 | /* upsert 694 | int offset = strlen(sqlite_upsert); 695 | SNPRINTF_S(sqlite_upsert + offset, BUFSIZE - 1 - offset, "UPDATE MODBUS SET VALUE=%s WHERE ADDRESS=%s;", tempValue, tempKey + 8); 696 | int offset = strlen(sqlite_upsert); 697 | SNPRINTF_S(sqlite_upsert + offset, BUFSIZE - 1 - offset, "INSERT INTO MODBUS(VALUE,ADDRESS) SELECT %s, %s WHERE NOT EXISTS(SELECT changes() AS change FROM MODBUS WHERE change <> 0);", tempValue, tempKey + 8); 698 | */ 699 | } 700 | index += step_size; 701 | } 702 | return 0; 703 | } 704 | static void decode_response_tcp(unsigned char * buf, MODBUS_READ_OPERATION* operation) 705 | { 706 | decode_response_PDU(buf + MODBUS_TCP_OFFSET, operation); 707 | } 708 | static void decode_response_com(unsigned char * buf, MODBUS_READ_OPERATION* operation) 709 | { 710 | decode_response_PDU(buf + MODBUS_COM_OFFSET, operation); 711 | } 712 | static void modbus_publish(BROKER_HANDLE broker, MODULE_HANDLE * handle, MESSAGE_CONFIG * msgConfig, int sqlite_enabled) 713 | { 714 | char * source; 715 | JSON_Value * root; 716 | MESSAGE_HANDLE modbusMessage; 717 | if (sqlite_enabled == 1) 718 | { 719 | //to sqlite Command 720 | source = sqlite_serialized_string; 721 | root = sqlite_root_value; 722 | } 723 | else 724 | { 725 | //to IoTHub message 726 | source = serialized_string; 727 | root = root_value; 728 | } 729 | 730 | msgConfig->source = (const unsigned char *)source; 731 | msgConfig->size = strlen(source); 732 | modbusMessage = Message_Create(msgConfig); 733 | if (modbusMessage == NULL) 734 | { 735 | LogError("unable to create \"modbus read\" message"); 736 | } 737 | else 738 | { 739 | (void)Broker_Publish(broker, handle, modbusMessage); 740 | Message_Destroy(modbusMessage); 741 | } 742 | json_free_serialized_string(source); 743 | json_value_free(root); 744 | } 745 | void close_server_tcp(MODBUS_READ_CONFIG * config) 746 | { 747 | if (config->socks != INVALID_SOCKET) 748 | { 749 | #ifdef WIN32 750 | closesocket(config->socks); 751 | #else 752 | close(config->socks); 753 | #endif 754 | } 755 | } 756 | void close_server_com(MODBUS_READ_CONFIG * config) 757 | { 758 | if (config->files != INVALID_FILE) 759 | { 760 | #ifdef WIN32 761 | CloseHandle(config->files); 762 | #else 763 | close(config->files); 764 | #endif 765 | } 766 | } 767 | void modbus_cleanup(MODBUS_READ_CONFIG * config) 768 | { 769 | MODBUS_READ_CONFIG * modbus_config = config; 770 | while (modbus_config) 771 | { 772 | MODBUS_READ_OPERATION * modbus_operation = modbus_config->p_operation; 773 | while (modbus_operation) 774 | { 775 | MODBUS_READ_OPERATION * temp_operation = modbus_operation; 776 | modbus_operation = modbus_operation->p_next; 777 | free(temp_operation); 778 | } 779 | if (modbus_config->close_server_cb) 780 | modbus_config->close_server_cb(modbus_config); 781 | 782 | MODBUS_READ_CONFIG * temp_config = modbus_config; 783 | modbus_config = modbus_config->p_next; 784 | free(temp_config); 785 | } 786 | #ifdef WIN32 787 | WSACleanup(); 788 | #endif 789 | } 790 | static int get_timestamp(char* timetemp) 791 | { 792 | /*getting the time*/ 793 | time_t temp = time(NULL); 794 | if (temp == (time_t)-1) 795 | { 796 | LogError("time function failed"); 797 | return -1; 798 | } 799 | else 800 | { 801 | struct tm* t = localtime(&temp); 802 | if (t == NULL) 803 | { 804 | LogError("localtime failed"); 805 | return -1; 806 | } 807 | else 808 | { 809 | if (strftime(timetemp, TIMESTRLEN + 1, "%F %T", t) == 0) 810 | { 811 | LogError("unable to strftime"); 812 | return -1; 813 | } 814 | } 815 | } 816 | return 0; 817 | } 818 | static int process_operation(MODBUS_READ_CONFIG * config, MODBUS_READ_OPERATION * operation) 819 | { 820 | unsigned char response[256]; 821 | int request_len = 0; 822 | int ret = 0; 823 | 824 | root_value = json_value_init_object(); 825 | root_object = json_value_get_object(root_value); 826 | 827 | if (config->sqlite_enabled == 1) 828 | { 829 | sqlite_root_value = json_value_init_object(); 830 | sqlite_root_object = json_value_get_object(sqlite_root_value); 831 | } 832 | char timetemp[TIMESTRLEN + 1] = { 0 }; 833 | 834 | if (get_timestamp(timetemp) != 0) 835 | { 836 | ret = -1; 837 | return ret; 838 | } 839 | memset(sqlite_upsert, 0, sizeof(sqlite_upsert)); 840 | memcpy(glob_currentTime, timetemp, strlen(timetemp)); 841 | memcpy(glob_currentMac, config->mac_address, strlen(config->mac_address)); 842 | 843 | glob_currentTime[strlen(timetemp)] = '\0'; 844 | glob_currentMac[strlen(config->mac_address)] = '\0'; 845 | 846 | json_object_set_string(root_object, "DataTimestamp", timetemp); 847 | json_object_set_string(root_object, "mac_address", config->mac_address); 848 | json_object_set_string(root_object, "device_type", config->device_type); 849 | 850 | MODBUS_READ_OPERATION * request_operation = operation; 851 | while (request_operation) 852 | { 853 | int send_ret = -1; 854 | if (config->send_request_cb) 855 | send_ret = config->send_request_cb(config, request_operation->read_request, operation->read_request_len, response); 856 | if (send_ret == -1) 857 | { 858 | LogError("send request failed"); 859 | ret = 1; 860 | } 861 | else if (send_ret > 0) 862 | { 863 | LogError("Exception occured, error code : %X\n", send_ret); 864 | ret = 1; 865 | } 866 | else 867 | { 868 | if (config->decode_response_cb) 869 | config->decode_response_cb(response, request_operation); 870 | } 871 | request_operation = request_operation->p_next; 872 | } 873 | 874 | serialized_string = json_serialize_to_string_pretty(root_value); 875 | 876 | if (config->sqlite_enabled == 1) 877 | { 878 | json_object_set_string(sqlite_root_object, "sqlCommand", sqlite_upsert);//testing 879 | sqlite_serialized_string = json_serialize_to_string_pretty(sqlite_root_value); 880 | } 881 | 882 | return ret; 883 | } 884 | static MODBUS_READ_CONFIG * get_config_by_mac(const char * mac_address, MODBUS_READ_CONFIG * config) 885 | { 886 | char * result = NULL; 887 | int status; 888 | MODBUS_READ_CONFIG * modbus_config = config; 889 | if ((mac_address == NULL) || (modbus_config == NULL)) 890 | return NULL; 891 | 892 | status = mallocAndStrcpy_s(&result, mac_address); 893 | if (status != 0) // failure 894 | { 895 | return NULL; 896 | } 897 | else 898 | { 899 | char * temp = result; 900 | while (*temp) 901 | { 902 | *temp = toupper(*temp); 903 | temp++; 904 | } 905 | } 906 | 907 | while (modbus_config) 908 | { 909 | if (strcmp(result, modbus_config->mac_address) == 0) 910 | { 911 | free(result); 912 | return modbus_config; 913 | } 914 | modbus_config = modbus_config->p_next; 915 | } 916 | free(result); 917 | return NULL; 918 | } 919 | static void set_com_state(MODBUS_READ_CONFIG * config) 920 | { 921 | #ifdef WIN32 922 | DCB dcb; 923 | bool set_res; 924 | 925 | FillMemory(&dcb, sizeof(dcb), 0); 926 | dcb.DCBlength = sizeof(dcb); 927 | 928 | dcb.BaudRate = config->baud_rate; 929 | dcb.Parity = config->parity; 930 | dcb.StopBits = config->stop_bits; 931 | dcb.ByteSize = config->data_bits; 932 | 933 | dcb.fRtsControl = RTS_CONTROL_DISABLE; 934 | dcb.fDtrControl = DTR_CONTROL_DISABLE; 935 | 936 | if (config->flow_control == CONFIG_FLOW_CONTROL_RTSCTS) 937 | { 938 | dcb.fOutX = false; 939 | dcb.fInX = false; 940 | dcb.fOutxCtsFlow = true; 941 | dcb.fOutxDsrFlow = false; 942 | dcb.fDtrControl = DTR_CONTROL_DISABLE; 943 | dcb.fRtsControl = RTS_CONTROL_HANDSHAKE; 944 | } 945 | else if (config->flow_control == CONFIG_FLOW_CONTROL_DSRDTR) 946 | { 947 | dcb.fOutX = false; 948 | dcb.fInX = false; 949 | dcb.fOutxCtsFlow = false; 950 | dcb.fOutxDsrFlow = true; 951 | dcb.fDsrSensitivity = true; 952 | dcb.fDtrControl = DTR_CONTROL_HANDSHAKE; 953 | dcb.fRtsControl = RTS_CONTROL_DISABLE; 954 | } 955 | //else if (config->flow_control == CONFIG_FLOW_CONTROL_XONOFF) 956 | 957 | set_res = SetCommState(config->files, &dcb); 958 | 959 | 960 | #else 961 | 962 | struct termios settings; 963 | tcgetattr(config->files, &settings); 964 | cfsetospeed(&settings, config->baud_rate); /* baud rate */ 965 | 966 | if (config->parity == CONFIG_PARITY_NO) 967 | { 968 | settings.c_cflag &= ~PARENB; /* no parity */ 969 | } 970 | else if (config->parity == CONFIG_PARITY_ODD) 971 | { 972 | settings.c_cflag |= PARENB; 973 | settings.c_cflag |= PARODD; 974 | } 975 | else if (config->parity == CONFIG_PARITY_EVEN) 976 | { 977 | settings.c_cflag |= PARENB; 978 | settings.c_cflag &= ~PARODD; 979 | } 980 | 981 | if (config->stop_bits == CONFIG_STOP_ONE) 982 | { 983 | settings.c_cflag &= ~CSTOPB; /* 1 stop bit */ 984 | } 985 | else if (config->stop_bits == CONFIG_STOP_TWO) 986 | { 987 | settings.c_cflag |= CSTOPB; /* 2 stop bit */ 988 | } 989 | /* not in POSIX 990 | if (config->flow_control == CONFIG_FLOW_CONTROL_RTSCTS) 991 | { 992 | settings.c_cflag |= CRTSCTS; 993 | } 994 | */ 995 | 996 | tcsetattr(config->files, TCSANOW, &settings); /* apply the settings */ 997 | tcflush(config->files, TCOFLUSH); 998 | #endif 999 | 1000 | } 1001 | static SOCKET_TYPE connect_modbus_server_tcp(const char * server_ip) 1002 | { 1003 | SOCKET_TYPE s; 1004 | struct sockaddr_in server; 1005 | 1006 | if ((s = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) 1007 | { 1008 | LogError("Could not create socket"); 1009 | } 1010 | else 1011 | { 1012 | 1013 | server.sin_addr.s_addr = inet_addr(server_ip); 1014 | server.sin_family = AF_INET; 1015 | server.sin_port = htons(502); 1016 | if (connect(s, (struct sockaddr *)&server, sizeof(server)) < 0) 1017 | { 1018 | LogError("connect error"); 1019 | s = INVALID_SOCKET; 1020 | } 1021 | else 1022 | { 1023 | struct timeval timeout; 1024 | timeout.tv_sec = 10; 1025 | timeout.tv_usec = 0; 1026 | 1027 | if (setsockopt (s, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, 1028 | sizeof(timeout)) < 0) 1029 | LogError("setsockopt failed\n"); 1030 | 1031 | if (setsockopt (s, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, 1032 | sizeof(timeout)) < 0) 1033 | LogError("setsockopt failed\n"); 1034 | } 1035 | } 1036 | 1037 | return s; 1038 | } 1039 | static FILE_TYPE connect_modbus_server_com(int port) 1040 | { 1041 | FILE_TYPE f; 1042 | char file[32]; 1043 | 1044 | #ifdef WIN32 1045 | if (port >= 10) 1046 | { 1047 | SNPRINTF_S(file, 32, "\\\\.\\COM%d", port); 1048 | } 1049 | else 1050 | { 1051 | SNPRINTF_S(file, 32, "COM%d", port); 1052 | } 1053 | f = CreateFile(file, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 1054 | #else 1055 | SNPRINTF_S(file, 32, "/dev/ttyS%d", port - 1); 1056 | f = open(file, O_RDWR | O_NOCTTY); 1057 | #endif 1058 | 1059 | return f; 1060 | } 1061 | static int connect_modbus_server(MODBUS_READ_CONFIG * server_config) 1062 | { 1063 | if (memcmp(server_config->server_str, "COM", 3) == 0) 1064 | { 1065 | server_config->files = connect_modbus_server_com(atoi(server_config->server_str + 3)); 1066 | if (server_config->files == INVALID_FILE) 1067 | { 1068 | return 1; 1069 | } 1070 | } 1071 | else 1072 | { 1073 | server_config->socks = connect_modbus_server_tcp(server_config->server_str); 1074 | 1075 | if (server_config->socks == INVALID_SOCKET) 1076 | { 1077 | return 1; 1078 | } 1079 | } 1080 | return 0; 1081 | } 1082 | static int modbusReadThread(void *param) 1083 | { 1084 | MODBUSREAD_HANDLE_DATA* handleData = param; 1085 | MESSAGE_CONFIG msgConfig; 1086 | MESSAGE_CONFIG sqlite_msgConfig; 1087 | 1088 | MODBUS_READ_CONFIG * server_config = handleData->config; 1089 | 1090 | #ifdef WIN32 1091 | WSADATA wsa; 1092 | if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) 1093 | { 1094 | LogError("Failed. Error Code : %d", WSAGetLastError()); 1095 | return INVALID_SOCKET; 1096 | } 1097 | #endif 1098 | 1099 | while (server_config) 1100 | { 1101 | server_config->files = INVALID_FILE; 1102 | server_config->socks = INVALID_SOCKET; 1103 | //connect to server 1104 | 1105 | int connection_type = getServerType(server_config->server_str); 1106 | 1107 | if (connection_type == CONNECTION_COM) 1108 | { 1109 | server_config->encode_read_cb = (encode_read_cb_type)encode_read_request_com; 1110 | server_config->encode_write_cb = (encode_write_cb_type)encode_write_request_com; 1111 | server_config->decode_response_cb = (decode_response_cb_type)decode_response_com; 1112 | server_config->send_request_cb = (send_request_cb_type)send_request_com; 1113 | server_config->close_server_cb = (close_server_cb_type)close_server_com; 1114 | set_com_state(server_config); 1115 | } 1116 | else if(connection_type == CONNECTION_TCP) 1117 | { 1118 | server_config->encode_read_cb = (encode_read_cb_type)encode_read_request_tcp; 1119 | server_config->encode_write_cb = (encode_write_cb_type)encode_write_request_tcp; 1120 | server_config->decode_response_cb = (decode_response_cb_type)decode_response_tcp; 1121 | server_config->send_request_cb = (send_request_cb_type)send_request_tcp; 1122 | server_config->close_server_cb = (close_server_cb_type)close_server_tcp; 1123 | } 1124 | 1125 | connect_modbus_server(server_config); 1126 | 1127 | MODBUS_READ_OPERATION * request_operation = server_config->p_operation; 1128 | while (request_operation) 1129 | { 1130 | if (server_config->encode_read_cb) 1131 | server_config->encode_read_cb(request_operation->read_request, &(request_operation->read_request_len), request_operation); 1132 | request_operation = request_operation->p_next; 1133 | } 1134 | //check mac 1135 | server_config = server_config->p_next; 1136 | } 1137 | 1138 | MAP_HANDLE propertiesMap = Map_Create(NULL); 1139 | MAP_HANDLE sqlite_propertiesMap = Map_Create(NULL); 1140 | if(sqlite_propertiesMap == NULL || propertiesMap == NULL) 1141 | { 1142 | LogError("unable to create a Map"); 1143 | } 1144 | else 1145 | { 1146 | if (Map_AddOrUpdate(propertiesMap, "modbusRead", "from Azure IoT Gateway SDK simple sample!") != MAP_OK ) 1147 | { 1148 | LogError("Could not attach modbusRead property to message"); 1149 | } 1150 | else if (Map_AddOrUpdate(propertiesMap, "source", "modbus") != MAP_OK) 1151 | { 1152 | LogError("Could not attach source property to message"); 1153 | } 1154 | else if (Map_AddOrUpdate(sqlite_propertiesMap, "sqlite", "modbus") != MAP_OK) 1155 | { 1156 | LogError("Could not attach sqlite property to message"); 1157 | } 1158 | else 1159 | { 1160 | msgConfig.sourceProperties = propertiesMap; 1161 | sqlite_msgConfig.sourceProperties = sqlite_propertiesMap; 1162 | while (1) 1163 | { 1164 | if (Lock(handleData->lockHandle) == LOCK_OK) 1165 | { 1166 | if (handleData->stopThread) 1167 | { 1168 | Map_Destroy(propertiesMap); 1169 | Map_Destroy(sqlite_propertiesMap); 1170 | (void)Unlock(handleData->lockHandle); 1171 | break; /*gets out of the thread*/ 1172 | } 1173 | else 1174 | { 1175 | server_config = handleData->config; 1176 | while (server_config) 1177 | { 1178 | if ((server_config->time_check) * 1000 >= server_config->read_interval) 1179 | { 1180 | server_config->time_check = 0; 1181 | if (process_operation(server_config, server_config->p_operation) != 0) 1182 | { 1183 | LogError("unable to send request to modbus server %s", server_config->server_str); 1184 | connect_modbus_server(server_config); 1185 | } 1186 | else 1187 | { 1188 | if (Map_AddOrUpdate(propertiesMap, "macAddress", (const char *)server_config->mac_address) != MAP_OK) 1189 | { 1190 | LogError("Could not attach macAddress property to message"); 1191 | } 1192 | else 1193 | { 1194 | if (server_config->sqlite_enabled) 1195 | { 1196 | modbus_publish(handleData->broker, (MODULE_HANDLE *)param, &sqlite_msgConfig, server_config->sqlite_enabled); 1197 | } 1198 | modbus_publish(handleData->broker, (MODULE_HANDLE *)param, &msgConfig, 0); 1199 | } 1200 | } 1201 | } 1202 | else 1203 | { 1204 | server_config->time_check++; 1205 | } 1206 | 1207 | server_config = server_config->p_next; 1208 | } 1209 | (void)Unlock(handleData->lockHandle); 1210 | } 1211 | } 1212 | else 1213 | { 1214 | /*shall retry*/ 1215 | } 1216 | (void)ThreadAPI_Sleep(1000); 1217 | } 1218 | } 1219 | } 1220 | return 0; 1221 | } 1222 | static void ModbusRead_Start(MODULE_HANDLE module) 1223 | { 1224 | MODBUSREAD_HANDLE_DATA* handleData = module; 1225 | if (handleData != NULL) 1226 | { 1227 | if (Lock(handleData->lockHandle) != LOCK_OK) 1228 | { 1229 | LogError("not able to Lock, still setting the thread to finish"); 1230 | handleData->stopThread = 1; 1231 | } 1232 | else 1233 | { 1234 | if (ThreadAPI_Create(&handleData->threadHandle, modbusReadThread, handleData) != THREADAPI_OK) 1235 | { 1236 | LogError("failed to spawn a thread"); 1237 | handleData->threadHandle = NULL; 1238 | } 1239 | (void)Unlock(handleData->lockHandle); 1240 | } 1241 | } 1242 | } 1243 | 1244 | static MODULE_HANDLE ModbusRead_Create(BROKER_HANDLE broker, const void* configuration) 1245 | { 1246 | bool isValidConfig = true; 1247 | MODBUSREAD_HANDLE_DATA* result = NULL; 1248 | if ( 1249 | (broker == NULL || configuration == NULL) 1250 | ) 1251 | { 1252 | 1253 | /*Codes_SRS_MODBUS_READ_99_001: [If broker is NULL then ModbusRead_Create shall fail and return NULL.]*/ 1254 | /*Codes_SRS_MODBUS_READ_99_002 : [If configuration is NULL then ModbusRead_Create shall fail and return NULL.]*/ 1255 | LogError("invalid arg broker=%p configuration=%p", broker, configuration); 1256 | } 1257 | else 1258 | { 1259 | /* validate mac_address, server_str*/ 1260 | 1261 | MODBUS_READ_CONFIG *cur_config = (MODBUS_READ_CONFIG *)configuration; 1262 | 1263 | result = malloc(sizeof(MODBUSREAD_HANDLE_DATA)); 1264 | if (result == NULL) 1265 | { 1266 | /*Codes_SRS_MODBUS_READ_99_007: [If ModbusRead_Create encounters any errors while creating the MODBUSREAD_HANDLE_DATA then it shall fail and return NULL.]*/ 1267 | LogError("unable to malloc"); 1268 | } 1269 | else 1270 | { 1271 | result->lockHandle = Lock_Init(); 1272 | if (result->lockHandle == NULL) 1273 | { 1274 | LogError("unable to Lock_Init"); 1275 | free(result); 1276 | result = NULL; 1277 | } 1278 | else 1279 | { 1280 | result->stopThread = 0; 1281 | result->broker = broker; 1282 | result->config = (MODBUS_READ_CONFIG *)configuration; 1283 | result->threadHandle = NULL; 1284 | } 1285 | } 1286 | } 1287 | return result; 1288 | } 1289 | 1290 | static void ModbusRead_Destroy(MODULE_HANDLE module) 1291 | { 1292 | /*Codes_SRS_MODBUS_READ_99_014: [If moduleHandle is NULL then ModbusRead_Destroy shall return.]*/ 1293 | if (module != NULL) 1294 | { 1295 | /*first stop the thread*/ 1296 | MODBUSREAD_HANDLE_DATA* handleData = module; 1297 | int notUsed; 1298 | if (Lock(handleData->lockHandle) != LOCK_OK) 1299 | { 1300 | LogError("not able to Lock, still setting the thread to finish"); 1301 | handleData->stopThread = 1; 1302 | } 1303 | else 1304 | { 1305 | handleData->stopThread = 1; 1306 | Unlock(handleData->lockHandle); 1307 | } 1308 | 1309 | /*Codes_SRS_MODBUS_READ_99_015 : [Otherwise ModbusRead_Destroy shall unuse all used resources.]*/ 1310 | if (ThreadAPI_Join(handleData->threadHandle, ¬Used) != THREADAPI_OK) 1311 | { 1312 | LogError("unable to ThreadAPI_Join, still proceeding in _Destroy"); 1313 | } 1314 | 1315 | (void)Lock_Deinit(handleData->lockHandle); 1316 | modbus_cleanup(handleData->config); 1317 | free(handleData); 1318 | } 1319 | } 1320 | //remote command format {"functionCode":"6","startingAddress":"1","value":"100","uid":"1"} 1321 | static void ModbusRead_Receive(MODULE_HANDLE moduleHandle, MESSAGE_HANDLE messageHandle) 1322 | { 1323 | if (moduleHandle == NULL || messageHandle == NULL) 1324 | { 1325 | /*Codes_SRS_MODBUS_READ_99_009: [If moduleHandle is NULL then ModbusRead_Receive shall fail and return.]*/ 1326 | /*Codes_SRS_MODBUS_READ_99_010 : [If messageHandle is NULL then ModbusRead_Receive shall fail and return.]*/ 1327 | LogError("Received NULL arguments: module = %p, massage = %p", moduleHandle, messageHandle); 1328 | } 1329 | else 1330 | { 1331 | MODBUSREAD_HANDLE_DATA* handleData = moduleHandle; 1332 | CONSTMAP_HANDLE properties = Message_GetProperties(messageHandle); 1333 | 1334 | /*Codes_SRS_MODBUS_READ_99_011: [If `messageHandle` properties does not contain a "source" property, then ModbusRead_Receive shall fail and return.]*/ 1335 | /*Codes_SRS_MODBUS_READ_99_012 : [If `messageHandle` properties contains a "deviceKey" property, then ModbusRead_Receive shall fail and return.]*/ 1336 | /*Codes_SRS_MODBUS_READ_99_013 : [If `messageHandle` properties contains a "source" property that is set to "mapping", then ModbusRead_Receive shall fail and return.]*/ 1337 | const char * source = ConstMap_GetValue(properties, "source"); 1338 | if (source != NULL) 1339 | { 1340 | if (strcmp(source, "mapping") == 0 && !ConstMap_ContainsKey(properties, "deviceKey")) 1341 | { 1342 | const char * mac_address = ConstMap_GetValue(properties, "macAddress"); 1343 | MODBUS_READ_CONFIG * modbus_config = get_config_by_mac(mac_address, handleData->config); 1344 | if (modbus_config != NULL) 1345 | { 1346 | const char *functionCode_str; 1347 | const char *startingAddress_str; 1348 | const char *value_str; 1349 | const char *uid_str; 1350 | const CONSTBUFFER * content = Message_GetContent(messageHandle); /*by contract, this is never NULL*/ 1351 | JSON_Value* json = json_parse_string((const char*)content->buffer); 1352 | if (json == NULL) 1353 | { 1354 | /*Codes_SRS_MODBUS_READ_99_018 : [If the content of messageHandle is not a JSON value, then `ModbusRead_Receive` shall fail and return NULL.]*/ 1355 | LogError("unable to json_parse_string"); 1356 | } 1357 | else 1358 | { 1359 | JSON_Object * obj = json_value_get_object(json); 1360 | if (obj == NULL) 1361 | { 1362 | LogError("json_value_get_obj failed"); 1363 | } 1364 | else 1365 | { 1366 | //expect a JSON format writeback request 1367 | functionCode_str = json_object_get_string(obj, "functionCode"); 1368 | startingAddress_str = json_object_get_string(obj, "startingAddress"); 1369 | value_str = json_object_get_string(obj, "value"); 1370 | uid_str = json_object_get_string(obj, "uid"); 1371 | 1372 | if (functionCode_str == NULL || startingAddress_str == NULL || value_str == NULL || uid_str == NULL) 1373 | LogError("Invalid JSON command, please input {\"functionCode\",\"startingAddress\",\"value\",\"uid\"}"); 1374 | else 1375 | { 1376 | LogInfo("WriteBack to functionCode: %s, startingAddress: %s, value: %s, uid: %s recived\n", functionCode_str, startingAddress_str, value_str, uid_str); 1377 | 1378 | unsigned char request[256]; 1379 | unsigned char response[256]; 1380 | int request_len = 0; 1381 | 1382 | if (modbus_config->encode_write_cb) 1383 | modbus_config->encode_write_cb(request, &request_len, atoi(uid_str), atoi(functionCode_str), atoi(startingAddress_str), atoi(value_str)); 1384 | 1385 | while (Lock(handleData->lockHandle) != LOCK_OK) 1386 | { 1387 | (void)ThreadAPI_Sleep(100); 1388 | } 1389 | int send_ret = -1; 1390 | 1391 | if (modbus_config->send_request_cb) 1392 | send_ret = modbus_config->send_request_cb(modbus_config, request, request_len, response); 1393 | 1394 | (void)Unlock(handleData->lockHandle); 1395 | if (send_ret == -1) 1396 | { 1397 | LogError("unable to send request to modbus server"); 1398 | connect_modbus_server(modbus_config); 1399 | } 1400 | else if (send_ret > 0) 1401 | { 1402 | LogError("Exception occured, error code : %X\n", send_ret); 1403 | } 1404 | } 1405 | } 1406 | } 1407 | json_value_free(json); 1408 | } 1409 | else 1410 | { 1411 | LogError("Did not find device mac_address [%s] of current message", mac_address); 1412 | } 1413 | } 1414 | } 1415 | ConstMap_Destroy(properties); 1416 | } 1417 | /*Codes_SRS_MODBUS_READ_99_017 : [ModbusRead_Receive shall return.]*/ 1418 | } 1419 | 1420 | static void* ModbusRead_ParseConfigurationFromJson(const char* configuration) 1421 | { 1422 | 1423 | MODBUS_READ_CONFIG * result = NULL; 1424 | /*Codes_SRS_MODBUS_READ_JSON_99_023: [ If configuration is NULL then ModbusRead_CreateFromJson shall fail and return NULL. ]*/ 1425 | if ( 1426 | (configuration == NULL) 1427 | ) 1428 | { 1429 | LogError("NULL parameter detected configuration=%p", configuration); 1430 | } 1431 | else 1432 | { 1433 | /*Codes_SRS_MODBUS_READ_JSON_99_031: [ If configuration is not a JSON object, then ModbusRead_CreateFromJson shall fail and return NULL. ]*/ 1434 | JSON_Value* json = json_parse_string((const char*)configuration); 1435 | if (json == NULL) 1436 | { 1437 | LogError("unable to json_parse_string"); 1438 | } 1439 | else 1440 | { 1441 | /*Codes_SRS_MODBUS_READ_JSON_99_032: [ If the JSON value does not contain `args` array then ModbusRead_CreateFromJson shall fail and return NULL. ]*/ 1442 | JSON_Array * arg_array = json_value_get_array(json); 1443 | if (arg_array == NULL) 1444 | { 1445 | LogError("json_value_get_array failed arg"); 1446 | } 1447 | else 1448 | { 1449 | result = addAllServers(arg_array); 1450 | } 1451 | json_value_free(json); 1452 | } 1453 | } 1454 | return result; 1455 | } 1456 | 1457 | static void ModbusRead_FreeConfiguration(void* configuration) 1458 | { 1459 | /*Codes_SRS_MODBUS_READ_99_006: [ ModbusRead_FreeConfiguration shall do nothing, cleanup is done in ModbusRead_Destroy. ]*/ 1460 | } 1461 | static const MODULE_API_1 moduleInterface = 1462 | { 1463 | {MODULE_API_VERSION_1}, 1464 | 1465 | ModbusRead_ParseConfigurationFromJson, 1466 | ModbusRead_FreeConfiguration, 1467 | ModbusRead_Create, 1468 | ModbusRead_Destroy, 1469 | ModbusRead_Receive, 1470 | ModbusRead_Start 1471 | }; 1472 | 1473 | #ifdef BUILD_MODULE_TYPE_STATIC 1474 | MODULE_EXPORT const MODULE_API* MODULE_STATIC_GETAPI(MODBUSREAD_MODULE)(const MODULE_API_VERSION gateway_api_version) 1475 | #else 1476 | MODULE_EXPORT const MODULE_API* Module_GetApi(const MODULE_API_VERSION gateway_api_version) 1477 | #endif 1478 | { 1479 | (void)gateway_api_version; 1480 | /* Codes_SRS_MODBUS_READ_99_016: [`Module_GetApi` shall return a pointer to a `MODULE_API` structure with the required function pointers.]*/ 1481 | return (const MODULE_API *)&moduleInterface; 1482 | } 1483 | -------------------------------------------------------------------------------- /v1/modules/modbus_read/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #Copyright (c) Microsoft. All rights reserved. 2 | #Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | cmake_minimum_required(VERSION 2.8.12) 5 | 6 | add_subdirectory(modbus_read_ut) 7 | -------------------------------------------------------------------------------- /v1/modules/modbus_read/tests/modbus_read_ut/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #Copyright (c) Microsoft. All rights reserved. 2 | #Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | cmake_minimum_required(VERSION 2.8.12) 5 | 6 | compileAsC99() 7 | 8 | set(theseTestsName modbus_read_ut) 9 | 10 | set(${theseTestsName}_cpp_files 11 | ${theseTestsName}.cpp 12 | ) 13 | 14 | set(${theseTestsName}_c_files 15 | ../../src/modbus_read.c 16 | ) 17 | 18 | set(${theseTestsName}_h_files 19 | ) 20 | 21 | include_directories(${GW_INC} ../../inc) 22 | 23 | build_test_artifacts(${theseTestsName} ON) -------------------------------------------------------------------------------- /v1/modules/modbus_read/tests/modbus_read_ut/main.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #include "testrunnerswitcher.h" 5 | 6 | int main(void) 7 | { 8 | size_t failedTestCount = 0; 9 | RUN_TEST_SUITE(modbus_read_ut, failedTestCount); 10 | return failedTestCount; 11 | } 12 | -------------------------------------------------------------------------------- /v1/samples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #Copyright (c) Microsoft. All rights reserved. 2 | #Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | cmake_minimum_required(VERSION 2.8.12) 5 | 6 | add_subdirectory(hello_world) 7 | add_subdirectory(simulated_device_cloud_upload) 8 | add_subdirectory(experimental/events_sample) 9 | add_subdirectory(azure_functions_sample) 10 | 11 | if(${enable_dotnet_binding}) 12 | add_subdirectory(dotnet_binding_sample) 13 | endif() 14 | 15 | if(${enable_java_binding}) 16 | add_subdirectory(java_sample) 17 | endif() 18 | 19 | if(${enable_nodejs_binding}) 20 | add_subdirectory(nodejs_simple_sample) 21 | endif() 22 | 23 | 24 | if(${enable_ble_module} AND LINUX) 25 | add_subdirectory(ble_gateway) 26 | endif() 27 | 28 | add_subdirectory(modbus_sample) -------------------------------------------------------------------------------- /v1/samples/modbus_sample/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #Copyright (c) Microsoft. All rights reserved. 2 | #Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | cmake_minimum_required(VERSION 2.8.12) 5 | #this is CMakeLists for modbus sample 6 | 7 | set(modbus_sources 8 | ./src/main.c 9 | ) 10 | if(WIN32) 11 | set(modbus_sources 12 | ${modbus_sources} 13 | ./src/modbus_win.json 14 | ) 15 | set_source_files_properties(./src/modbus_win.json PROPERTIES HEADER_FILE_ONLY ON) 16 | else() 17 | set(modbus_sources 18 | ${modbus_sources} 19 | ./src/modbus_lin.json 20 | ) 21 | set_source_files_properties(./src/modbus_lin.json PROPERTIES HEADER_FILE_ONLY ON) 22 | endif() 23 | 24 | set(modbus_headers 25 | ) 26 | 27 | include_directories(./inc ${IOTHUB_CLIENT_INC_FOLDER}) 28 | include_directories(${GW_INC}) 29 | include_directories(../../modules/common) 30 | 31 | add_executable(modbus_sample ${modbus_headers} ${modules_path_file} ${modbus_sources}) 32 | 33 | add_dependencies(modbus_sample logger identity_map iothub modbus_read) 34 | 35 | target_link_libraries(modbus_sample gateway) 36 | linkSharedUtil(modbus_sample) 37 | install_broker(modbus_sample ${CMAKE_CURRENT_BINARY_DIR}/$(Configuration) ) 38 | copy_gateway_dll(modbus_sample ${CMAKE_CURRENT_BINARY_DIR}/$(Configuration) ) 39 | copy_iothub_client_dll(modbus_sample ${CMAKE_CURRENT_BINARY_DIR}/$(Configuration) ) 40 | 41 | add_sample_to_solution(modbus_sample) 42 | -------------------------------------------------------------------------------- /v1/samples/modbus_sample/README.md: -------------------------------------------------------------------------------- 1 | The directions to run this sample can be found at [doc/sample_modbus.md](../../doc/sample_modbus.md). -------------------------------------------------------------------------------- /v1/samples/modbus_sample/src/main.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | #include 5 | 6 | #include "gateway.h" 7 | #include "azure_c_shared_utility/xlogging.h" 8 | #include "azure_c_shared_utility/vector.h" 9 | #include "azure_c_shared_utility/platform.h" 10 | 11 | 12 | int main(int argc, char** argv) 13 | { 14 | GATEWAY_HANDLE gateway; 15 | if (argc != 2) 16 | { 17 | printf("usage: modbus_read_sample configFile\n"); 18 | printf("where configFile is the name of the file that contains the Gateway configuration\n"); 19 | } 20 | else 21 | { 22 | if (platform_init() == 0) 23 | { 24 | if ((gateway = Gateway_CreateFromJson(argv[1])) == NULL) 25 | { 26 | printf("failed to create the gateway from JSON\n"); 27 | } 28 | else 29 | { 30 | printf("gateway successfully created from JSON\n"); 31 | printf("gateway shall run until ENTER is pressed\n"); 32 | (void)getchar(); 33 | Gateway_Destroy(gateway); 34 | } 35 | platform_deinit(); 36 | } 37 | else 38 | { 39 | LogError("Failed to initialize the platform."); 40 | } 41 | } 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /v1/samples/modbus_sample/src/modbus_lin.json: -------------------------------------------------------------------------------- 1 | { 2 | "modules": [ 3 | { 4 | "name": "IoTHub", 5 | "loader": { 6 | "name": "native", 7 | "entrypoint": { 8 | "module.path": "../../modules/iothub/libiothub.so" 9 | } 10 | }, 11 | "args": { 12 | "IoTHubName": "YOUR IOT HUB NAME", 13 | "IoTHubSuffix": "YOUR IOT HUB SUFFIX", 14 | "Transport": "TRANSPORT PROTOCOL" 15 | } 16 | }, 17 | { 18 | "name": "mapping", 19 | "loader": { 20 | "name": "native", 21 | "entrypoint": { 22 | "module.path": "../../modules/identitymap/libidentity_map.so" 23 | } 24 | }, 25 | "args": [ 26 | { 27 | "macAddress": "01:01:01:01:01:01", 28 | "deviceId": "YOUR DEVICE ID", 29 | "deviceKey": "YOUR DEVICE KEY" 30 | } 31 | ] 32 | }, 33 | { 34 | "name": "logger", 35 | "loader": { 36 | "name": "native", 37 | "entrypoint": { 38 | "module.path": "../../modules/logger/liblogger.so" 39 | } 40 | }, 41 | "args": { 42 | "filename": "log.txt" 43 | } 44 | }, 45 | { 46 | "name": "modbus_read", 47 | "loader": { 48 | "name": "native", 49 | "entrypoint": { 50 | "module.path": "../../modules/modbus_read/libmodbus_read.so" 51 | } 52 | }, 53 | "args": [ 54 | { 55 | "serverConnectionString": "COM1", 56 | "interval": "2000", 57 | "macAddress": "01:01:01:01:01:01", 58 | "baudRate": "9600", 59 | "stopBits": "1", 60 | "dataBits": "8", 61 | "parity": "EVEN", 62 | "flowControl": "NONE", 63 | "deviceType": "powerMeter", 64 | "sqliteEnabled": "0", 65 | "operations": [ 66 | { 67 | "unitId": "1", 68 | "functionCode": "3", 69 | "startingAddress": "1", 70 | "length": "5" 71 | } 72 | ] 73 | } 74 | ] 75 | } 76 | ], 77 | "links": [ 78 | { 79 | "source": "mapping", 80 | "sink": "IoTHub" 81 | }, 82 | { 83 | "source": "IoTHub", 84 | "sink": "mapping" 85 | }, 86 | { 87 | "source": "mapping", 88 | "sink": "modbus_read" 89 | }, 90 | { 91 | "source": "modbus_read", 92 | "sink": "mapping" 93 | }, 94 | { 95 | "source": "modbus_read", 96 | "sink": "logger" 97 | } 98 | ] 99 | } 100 | -------------------------------------------------------------------------------- /v1/samples/modbus_sample/src/modbus_win.json: -------------------------------------------------------------------------------- 1 | { 2 | "modules": [ 3 | { 4 | "name": "IoTHub", 5 | "loader": { 6 | "name": "native", 7 | "entrypoint": { 8 | "module.path": "..\\..\\..\\modules\\iothub\\Debug\\iothub.dll" 9 | } 10 | }, 11 | "args": { 12 | "IoTHubName": "YOUR IOT HUB NAME", 13 | "IoTHubSuffix": "YOUR IOT HUB SUFFIX", 14 | "Transport": "TRANSPORT PROTOCOL" 15 | } 16 | }, 17 | { 18 | "name": "mapping", 19 | "loader": { 20 | "name": "native", 21 | "entrypoint": { 22 | "module.path": "..\\..\\..\\modules\\identitymap\\Debug\\identity_map.dll" 23 | } 24 | }, 25 | "args": [ 26 | { 27 | "macAddress": "01:01:01:01:01:01", 28 | "deviceId": "YOUR DEVICE ID", 29 | "deviceKey": "YOUR DEVICE KEY" 30 | } 31 | ] 32 | }, 33 | { 34 | "name": "logger", 35 | "loader": { 36 | "name": "native", 37 | "entrypoint": { 38 | "module.path": "..\\..\\..\\modules\\logger\\Debug\\logger.dll" 39 | } 40 | }, 41 | "args": { 42 | "filename": "log.txt" 43 | } 44 | }, 45 | { 46 | "name": "modbus_read", 47 | "loader": { 48 | "name": "native", 49 | "entrypoint": { 50 | "module.path": "..\\..\\..\\modules\\modbus_read\\Debug\\modbus_read.dll" 51 | } 52 | }, 53 | "args": [ 54 | { 55 | "serverConnectionString": "127.0.0.1", 56 | "interval": "2000", 57 | "macAddress": "01:01:01:01:01:01", 58 | "baudRate": "9600", 59 | "stopBits": "1", 60 | "dataBits": "8", 61 | "parity": "EVEN", 62 | "flowControl": "NONE", 63 | "deviceType": "powerMeter", 64 | "sqliteEnabled": "0", 65 | "operations": [ 66 | { 67 | "unitId": "1", 68 | "functionCode": "3", 69 | "startingAddress": "1", 70 | "length": "1" 71 | } 72 | ] 73 | } 74 | ] 75 | } 76 | ], 77 | "links": [ 78 | { 79 | "source": "mapping", 80 | "sink": "IoTHub" 81 | }, 82 | { 83 | "source": "IoTHub", 84 | "sink": "mapping" 85 | }, 86 | { 87 | "source": "mapping", 88 | "sink": "modbus_read" 89 | }, 90 | { 91 | "source": "modbus_read", 92 | "sink": "mapping" 93 | }, 94 | { 95 | "source": "modbus_read", 96 | "sink": "logger" 97 | } 98 | ] 99 | } 100 | --------------------------------------------------------------------------------