1 /*
2 * modbus.cpp
3 *
4 * Driver for APC MODBUS protocol
5 */
6
7 /*
8 * Copyright (C) 2013 Adam Kropelin
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of version 2 of the GNU General
12 * Public License as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public
20 * License along with this program; if not, write to the Free
21 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22 * MA 02110-1335, USA.
23 */
24
25 /*
26 * Thanks go to APC/Schneider for providing the apcctrl team with early access
27 * to MODBUS protocol information to facilitate an apcctrl driver.
28 *
29 * APC/Schneider has published the following relevant application notes:
30 *
31 * AN176: Modbus Implementation in APC Smart-UPS
32 * <http://www.apc.com/whitepaper/?an=176>
33 * AN177: Software interface for Switched Outlet and UPS Management in Smart-UPS
34 * <http://www.apc.com/whitepaper/?an=177>
35 * AN178: USB HID Implementation in Smart-UPS
36 * <http://www.apc.com/whitepaper/?an=178>
37 */
38
39 #include "apc.h"
40 #include "ModbusComm.h"
41
ReadRegister(uint16_t reg,unsigned int nregs)42 uint8_t *ModbusComm::ReadRegister(uint16_t reg, unsigned int nregs)
43 {
44 ModbusPdu txpdu;
45 ModbusPdu rxpdu;
46 const unsigned int nbytes = nregs * sizeof(uint16_t);
47
48 Dmsg(50, "%s: reg=%u, nregs=%u\n", __func__, reg, nregs);
49
50 txpdu[0] = reg >> 8;
51 txpdu[1] = reg;
52 txpdu[2] = nregs >> 8;
53 txpdu[3] = nregs;
54
55 if (!SendAndWait(MODBUS_FC_READ_HOLDING_REGS, &txpdu, 4,
56 &rxpdu, nbytes+1))
57 {
58 return NULL;
59 }
60
61 if (rxpdu[0] != nbytes)
62 {
63 // Invalid size
64 Dmsg(0, "%s: Wrong number of data bytes received (exp=%u, rx=%u)\n",
65 __func__, nbytes, rxpdu[0]);
66 }
67
68 uint8_t *ret = new uint8_t[nbytes];
69 memcpy(ret, rxpdu+1, nbytes);
70 return ret;
71 }
72
WriteRegister(uint16_t reg,unsigned int nregs,const uint8_t * data)73 bool ModbusComm::WriteRegister(uint16_t reg, unsigned int nregs, const uint8_t *data)
74 {
75 ModbusPdu txpdu;
76 ModbusPdu rxpdu;
77 const unsigned int nbytes = nregs * sizeof(uint16_t);
78
79 Dmsg(50, "%s: reg=%u, nregs=%u\n", __func__, reg, nregs);
80
81 txpdu[0] = reg >> 8;
82 txpdu[1] = reg;
83 txpdu[2] = nregs >> 8;
84 txpdu[3] = nregs;
85 txpdu[4] = nbytes;
86 memcpy(txpdu+5, data, nbytes);
87
88 if (!SendAndWait(MODBUS_FC_WRITE_MULTIPLE_REGS, &txpdu, nbytes+5, &rxpdu, 4))
89 {
90 return false;
91 }
92
93 // Response should match first 4 bytes of command (reg and nregs)
94 if (memcmp(rxpdu, txpdu, 4))
95 {
96 // Did not write expected number of registers
97 Dmsg(0, "%s: Write failed reg=%u, nregs=%u, writereg=%u, writenregs=%u\n",
98 __func__, reg, nregs, (rxpdu[0] << 8) | rxpdu[1],
99 (rxpdu[2] << 8) | rxpdu[3]);
100 return false;
101 }
102
103 return true;
104 }
105
SendAndWait(uint8_t fc,const ModbusPdu * txpdu,unsigned int txsz,ModbusPdu * rxpdu,unsigned int rxsz)106 bool ModbusComm::SendAndWait(
107 uint8_t fc,
108 const ModbusPdu *txpdu, unsigned int txsz,
109 ModbusPdu *rxpdu, unsigned int rxsz)
110 {
111 ModbusFrame txfrm;
112 ModbusFrame rxfrm;
113 unsigned int sz;
114
115 // Ensure caller isn't trying to send an oversized PDU
116 if (txsz > MODBUS_MAX_PDU_SZ || rxsz > MODBUS_MAX_PDU_SZ)
117 return false;
118
119 // Prepend slave address and function code
120 txfrm[0] = _slaveaddr;
121 txfrm[1] = fc;
122
123 // Add PDU
124 memcpy(txfrm+2, txpdu, txsz);
125
126 // Calculate crc
127 uint16_t crc = ModbusCrc(txfrm, txsz+2);
128
129 // CRC goes out LSB first, unlike other MODBUS fields
130 txfrm[txsz+2] = crc;
131 txfrm[txsz+3] = crc >> 8;
132
133 int retries = 2;
134 do
135 {
136 if (!ModbusTx(&txfrm, txsz+4))
137 {
138 // Failure to send is immediately fatal
139 return false;
140 }
141
142 if (!ModbusRx(&rxfrm, &sz))
143 {
144 // Rx timeout: Retry
145 continue;
146 }
147
148 if (sz < 4)
149 {
150 // Runt frame: Retry
151 Dmsg(0, "%s: runt frame (%u)\n", __func__, sz);
152 continue;
153 }
154
155 crc = ModbusCrc(rxfrm, sz-2);
156 if (rxfrm[sz-2] != (crc & 0xff) ||
157 rxfrm[sz-1] != (crc >> 8))
158 {
159 // CRC error: Retry
160 Dmsg(0, "%s: CRC error\n", __func__);
161 continue;
162 }
163
164 if (rxfrm[0] != _slaveaddr)
165 {
166 // Not from expected slave: Retry
167 Dmsg(0, "%s: Bad address (exp=%u, rx=%u)\n",
168 __func__, _slaveaddr, rxfrm[0]);
169 continue;
170 }
171
172 if (rxfrm[1] == (fc | MODBUS_FC_ERROR))
173 {
174 // Exception response: Immediately fatal
175 Dmsg(0, "%s: Exception (code=%u)\n", __func__, rxfrm[2]);
176 return false;
177 }
178
179 if (rxfrm[1] != fc)
180 {
181 // Unknown response: Retry
182 Dmsg(0, "%s: Unexpected response 0x%02x\n", __func__, rxfrm[1]);
183 continue;
184 }
185
186 if (sz != rxsz+4)
187 {
188 // Wrong size: Retry
189 Dmsg(0, "%s: Wrong size (exp=%u, rx=%u)\n", __func__, rxsz+4, sz);
190 continue;
191 }
192
193 // Everything is ok
194 memcpy(rxpdu, rxfrm+2, rxsz);
195 return true;
196 }
197 while (retries--);
198
199 // Retries exhausted
200 Dmsg(0, "%s: Retries exhausted\n", __func__);
201 return false;
202 }
203
ModbusCrc(const uint8_t * data,unsigned int sz)204 uint16_t ModbusComm::ModbusCrc(const uint8_t *data, unsigned int sz)
205 {
206 // 1 + x^2 + x^15 + x^16
207 static const uint16_t MODBUS_CRC_POLY = 0xA001;
208 uint16_t crc = 0xffff;
209
210 while (sz--)
211 {
212 crc ^= *data++;
213 for (unsigned int i = 0; i < 8; ++i)
214 {
215 if (crc & 0x1)
216 crc = (crc >> 1) ^ MODBUS_CRC_POLY;
217 else
218 crc >>= 1;
219 }
220 }
221
222 return crc;
223 }
224