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