Fotobot
Get data from your photovoltaic plant
modbustcp.cpp
Go to the documentation of this file.
1 
7 #include "modbustcp.h"
8 #include "socketlist.h"
9 #include <QCoreApplication>
10 #include <QTcpSocket>
11 #include <QDebug>
12 #include <QStringList>
13 #include <QEventLoop>
14 #include <QTimer>
15 #include "string.h"
16 
17 #define HEXDEBUG(x) \
18  { QStringList str; \
19  for (int i=0; i<x.size(); i++) { \
20  str << QString("%1").arg( (quint8) x[i], 2, 16, QChar('0')); \
21  } \
22  qDebug() << str.join(" "); \
23  }
24 
25 
26 MODBUSTCP::~MODBUSTCP() {
27 }
28 
36  // qDebug() << "MODBUSTCP::MODBUSTCP()";
37  m_frameid = 0;
38  m_number_of_err=0;
39  m_number_of_ok=0;
41 }
42 
43 
52  qDebug() << "MODBUSTCP::slotInit()" << this;
53  m_timer = new QTimer(this);
54  m_timer->setInterval(10);
55  m_timer->setSingleShot(true);
56  connect(m_timer, SIGNAL(timeout()),
57  this, SLOT(loop()));
58  m_timer->start();
59 
60  try {
61  SOCKETLIST->addLine(m_line.line, m_line.hostname, m_line.portnumber);
62  SOCKETLIST->open(m_line.line);
63  } catch (QString e) {};
64 }
65 
66 
67 void MODBUSTCP::slotQuit() {
68 }
69 
70 
80 
81 }
82 
83 
97  if (m_invertors.size() <= 0) return;
98 
99  readInvertor();
100 
102  if (m_invertors.size() <= m_current_invertor_index) {
103  int secs = 3;
105  setStatus(
107  0,
108  QString(),
109  tr("Sleeping %1 secs to begin new reading cycle").arg(secs)
110  );
111  m_timer->setInterval(secs*1000); // wait a few seconds after whole cycle
112  emit loopFinished(m_number_of_ok, m_number_of_err);
113  m_number_of_err=0;
114  m_number_of_ok=0;
115  } else {
116  setStatus(
118  0,
119  QString(),
120  tr("Going to read next invertor")
121  );
122  m_timer->setInterval(10);
123  }
124 
125  m_timer->start();
126 }
127 
128 
129 void MODBUSTCP::readInvertor() {
130  try {
131  readInvertorInfo(m_invertors[m_current_invertor_index]);
132  }
133  catch (QString e) {
134  DBT_DATA x;
135  setStatus(m_invertors[m_current_invertor_index].address, 0, QString(), e);
136  x.invertor = m_invertors[m_current_invertor_index].invertor;
137  x.error = e;
138  emit data(x);
139  m_number_of_err++;
140  qDebug() << "MODBUSTCP::readInvertor()" << e;
141  try {
142  SOCKETLIST->open(m_invertors[m_current_invertor_index].line);
143  } catch (QString e) {};
144  }
145 
146 }
147 
148 
149 #define CLEANSOCKET() { \
150  socket->flush(); \
151  socket->waitForReadyRead(m_line.timeout/10); \
152  socket->readAll(); \
153  }
154 
155 
156 QByteArray MODBUSTCP::readModbus(const DBT_INVERTORS& invertor, quint16 regaddress, quint16 regcnt, quint16 expectedLength) {
157  incrementFrameId();
158  QTcpSocket *socket = SOCKETLIST->socket(invertor.line);
159  quint8 unitid = invertor.address;
160  quint16 length = 4;
161  quint8 *f = (quint8 *)(&m_frameid);
162  quint8 *l = (quint8 *)(&length);
163  quint16 reg = regaddress - 1;
164 
165  QByteArray cmd;
166  cmd[0] = *(f+1);
167  cmd[1] = *(f+0);
168  cmd[2] = 0;
169  cmd[3] = 0;
170  cmd[4] = *(l+1);
171  cmd[5] = *(l+0);
172  cmd[6] = unitid;
173  cmd[7] = 0x03;
174  cmd[8] = (reg >> 8) & 0xff;
175  cmd[9] = reg & 0xff;
176  cmd[10] = (regcnt >> 8) & 0xff;
177  cmd[11] = regcnt & 0xff;
178  socket->write(cmd);
179 
180 // HEXDEBUG(cmd);
181  qDebug() << "zapis povelu do TCP address" << invertor.address << "frameid" << m_frameid;
182 
183  setStatus(invertor.address, 0, QString(), tr("Writing data to TCP socket"));
184  if (!socket->waitForBytesWritten(1000)) {
185  throw tr("Could not write data to %1").arg(m_line.hostname);
186  }
187  qDebug() << "zapsano";
188 
189  QByteArray rec;
190  for (int trynumber=0; trynumber <= 5; trynumber++) {
191  qDebug() << "cteni z TCP";
192  setStatus(invertor.address, 0, QString(), tr("Reading data from TCP socket"));
193 
194  if (!socket->waitForReadyRead(m_line.timeout)) {
195  CLEANSOCKET();
196  throw tr("Could not read data from %1").arg(m_line.hostname);
197  }
198 
199  QEventLoop loop;
200  QTimer::singleShot(100, &loop, SLOT(quit()));
201  loop.exec();
202 
203  rec = socket->readAll();
204  qDebug() << "precteno" << rec.size();
205 
206  uint rtcode = static_cast<uint>(rec[7]); // nedávat do podmínky, nechat v proměnné!
207  if (rec.size() == 9 && rtcode == 0x83) {
208  CLEANSOCKET();
209  int exceptionCode = (int)rec[8];
210  QString exceptionText = tr("Modbus protocol exception: 0x83 0x%1 %2")
211  .arg(exceptionCode, 2, 16, QChar('0'))
212  .arg(exceptionCodeToString(exceptionCode))
213  ;
214  qDebug() << exceptionText;
215  throw exceptionText;
216  }
217 
218  if (rec.size() < expectedLength) {
219  CLEANSOCKET();
220  HEXDEBUG(rec);
221  throw tr("Bad response: read %1 bytes, expected: %2 bytes").arg(rec.size()).arg(expectedLength);
222  }
223 
224  int fid = (int)((unsigned char)rec[0]) * 256
225  + (int)((unsigned char)rec[1]);
226  if (fid != m_frameid && trynumber++ < 5 ) {
227  qDebug() << "pozadovana adresa" << m_frameid << "vracena adresa" << fid;
228  CLEANSOCKET();
229  continue;
230  }
231 
232  if (trynumber >= 5) {
233  CLEANSOCKET();
234  throw tr("Bad response: frame id received differs from frame id sent");
235  }
236 
237  break;
238  }
239 
240  setStatus(invertor.address, 0, QString(), tr("Decoding data read from TCP socket"));
241  int uid = rec[6];
242  if (uid != unitid) {
243  throw tr("Bad response: unexpected unit id: %1, expected: %2").arg(uid).arg(unitid);
244  }
245 
246 // HEXDEBUG(rec);
247 
248  return rec;
249 }
250 
251 
252 void MODBUSTCP::readInvertorInfo(const DBT_INVERTORS& invertor) {
253  qDebug() << "MODBUSTCP::readInvertorInfo() invertor line address: " << invertor.line << invertor.invertor << invertor.address;
254 
255  // Zjisti status
256  DBT_DATA x;
257  QByteArray rec = readModbus(invertor,
258  214, /* Address of register */
259  1, /* Number of registers to read */
260  11 /* Expected length */ );
261  int errcode = integer16(rec, 9);
262  qDebug() << "Invertor status:" << errcode << stateToString(errcode);
263 
264  // Precti data
265  int istatus;
266  for (int i=0; i<3; i++) {
267  rec = readModbus(invertor,
268  40071, /* Address of first read register */
269  60, /* Number of read register*/
270  129 /* Expected length */ );
271  istatus = integer16(rec, 103);
272  if (istatus != 7) { break; } // FAULT
273  if (errcode != 0) { break; } // Unknown error
274  }
275 
276  // Vyhodnotit status invertoru
277  QString status;
278  bool working = false;
279  switch (istatus) {
280  case 1: working = false; status = "OFF"; break;
281  case 2: working = false; status = "SLEEPING"; break;
282  case 3: working = false; status = "STARTING"; break;
283  case 4: working = true; status = "MPPT"; break;
284  case 5: working = true; status = "THROTTLED"; break;
285  case 6: working = false; status = "SHUTTING_DOWN"; break;
286  case 7: working = false; status = "FAULT"; break;
287  case 8: working = false; status = "STANDBY"; break;
288  case 9: working = false; status = "NO_BUSINIT"; break;
289  case 10: working = false; status = "NO_COMM_INV"; break;
290  case 11: working = false; status = "SN_OVERCURRENT"; break;
291  case 12: working = false; status = "BOOTLOAD"; break;
292  case 13: working = false; status = "AFCI"; break;
293  default: working = false; status = "UNKNOWN"; break;
294  }
295  if (istatus == 7) {
296  x.error = tr("Status Fault, error code: %1: %2").arg(errcode).arg(stateToString(errcode));
297  m_number_of_ok--;
298  }
299 
300  x.invertor = invertor.invertor;
301  x.now_power = (working) ? float32(rec, 51) : 0;
302  x.now_ac_current = (working) ? float32(rec, 11) : 0;
303  x.now_ac_voltage = 0; //??
304  x.now_ac_frequency = (working) ? float32(rec, 55) : 0;
305  x.now_dc_current = (working) ? float32(rec, 75) : 0;
306  x.now_dc_voltage = (working) ? float32(rec, 79) : 0;
307  x.day_energy = 0;
308  x.day_power_maximum = 0;
309  x.day_ac_voltage_maximum = 0;
310  x.day_ac_voltage_minimum = 0;
311  x.day_dc_voltage_maximum = 0;
312  x.day_operating_hours = 0;
313  x.total_energy = (working) ? float32(rec, 71) : 0;
314  x.total_power_maximum = 0;
315  x.total_ac_voltage_maximum = 0;
316  x.total_ac_voltage_minimum = 0;
317  x.total_dc_voltage_maximum = 0;
318  x.total_operating_hours = 0;
319  x.status = status;
320 
321  emit data(x);
322  m_number_of_ok++;
323 
324 /*
325  qDebug() << "ac total current " << float32(rec, 11);
326  qDebug() << "ac phase-a current " << float32(rec, 15);
327  qDebug() << "ac phase-b current " << float32(rec, 19);
328  qDebug() << "ac phase-c current " << float32(rec, 23);
329  qDebug() << "ac voltage phase AB " << float32(rec, 27);
330  qDebug() << "ac voltage phase BC " << float32(rec, 31);
331  qDebug() << "ac voltage phase CA " << float32(rec, 35);
332  qDebug() << "ac voltage phase A0 " << float32(rec, 39);
333  qDebug() << "ac voltage phase B0 " << float32(rec, 43);
334  qDebug() << "ac voltage phase C0 " << float32(rec, 47);
335  qDebug() << "ac power " << float32(rec, 51);
336  qDebug() << "ac frequency " << float32(rec, 55);
337  qDebug() << "aparent power " << float32(rec, 59);
338  qDebug() << "reactive power " << float32(rec, 63);
339  qDebug() << "power factor " << float32(rec, 67);
340  qDebug() << "production wH " << float32(rec, 71);
341  qDebug() << "DC current value " << float32(rec, 75);
342  qDebug() << "DC voltage value " << float32(rec, 79);
343  qDebug() << "DC power value " << float32(rec, 83);
344  qDebug() << "cabinet temperature " << float32(rec, 87);
345  qDebug() << "coolant temperature " << float32(rec, 91);
346  qDebug() << "trafo temperature " << float32(rec, 95);
347  qDebug() << "other temperature " << float32(rec, 99);
348  qDebug() << "operating state " << integer16(rec, 103);
349  qDebug() << "vendor operating st." << integer16(rec, 105);
350  qDebug() << "event flags 0-31 " << integer32(rec, 107);
351  qDebug() << "event flags 32-63 " << integer32(rec, 109);
352  qDebug() << "event flags 32-63 " << integer32(rec, 113);
353  qDebug() << "event flags 32-63 " << integer32(rec, 117);
354  qDebug() << "event flags 32-63 " << integer32(rec, 121);
355  qDebug() << "event flags 32-63 " << integer32(rec, 125);
356 */
357 
358 }
359 
360 
361 float MODBUSTCP::float32(const QByteArray& data, int offset) const {
362  int ival0 = data[offset++];
363  int ival1 = data[offset++];
364  int ival2 = data[offset++];
365  int ival3 = data[offset++];
366  int ival = (ival0 << 24) + (ival1 << 16) + (ival2 << 8) + ival3;
367  float value = *(float*)&ival;
368  return value;
369 }
370 
371 
372 qint16 MODBUSTCP::integer16(const QByteArray& data, int offset) const {
373  int ival0 = data[offset++];
374  int ival1 = data[offset++];
375  int ival = (ival0 << 8) + ival1;
376  return ival;
377 }
378 
379 
380 qint32 MODBUSTCP::integer32(const QByteArray& data, int offset) const {
381  int ival0 = data[offset++];
382  int ival1 = data[offset++];
383  int ival2 = data[offset++];
384  int ival3 = data[offset++];
385  int ival = (ival0 << 24) + (ival1 << 16) + (ival2 << 8) + ival3;
386  return ival;
387 }
388 
389 
390 QString MODBUSTCP::stateToString(int state) {
391  switch (state) {
392  case 102: return "AC voltage too high";
393  case 103: return "AC voltage too low";
394  case 105: return "AC frequenci too high";
395  case 106: return "AC frequenci too low";
396  case 107: return "AC grid outside the permissible limits";
397  case 108: return "Stand alone operation detected";
398  case 112: return "RCMU error";
399 
400  case 301: return "Overcurrent (AC)";
401  case 302: return "Overcurrent (DC)";
402  case 303: return "Power stage set over temperature";
403  case 304: return "Internal temperature too high";
404  case 306: return "Intermediate circuit voltage too low for feeding energy into the grid";
405  case 307: return "DC input voltage too low for feeding energy into the grid";
406  case 308: return "Intermediate circuit overvoltage";
407  case 309: return "DC input voltage MPPT 1 too high";
408  case 313: return "DC input voltage MPPT 2 too high";
409 
410  case 401: return "No communication with power stage set possible ";
411  case 406: return "Power stage set temperature sensor faulty";
412  case 407: return "Internal temperature sensor faulty";
413  case 408: return "DC feeding into the grid detected";
414  case 412: return "Fixed voltage mode has been selected instead of MPP voltage mode and the fixed voltage has been set to too low or too high a value";
415  case 415: return "Safety cut out via option card or RECERBO has triggered ";
416  case 416: return "No communication possible between power stage set and control system";
417  case 417: return "Hardware ID problem";
418  case 419: return "Unique ID conflict";
419  case 421: return "HID range error";
420  case 425: return "No communication possible with the power stage set";
421  case 426: return "Possible hardware fault";
422  case 427: return "Possible hardware fault";
423  case 428: return "Possible hardware fault";
424  case 431: return "Software problem";
425  case 436: return "Functional incompatibility (one or more PC boards in the inverter are not compatible with each other, e.g. after a PC board has been replaced.";
426  case 437: return "Power stage set problem";
427  case 438: return "Functional incompatibility (one or more PC boards in the inverter in the inverter are not compatible with each other, e.g. after a PC board has been replaced)";
428  case 443: return "Intermediate circuit voltage too low or asymmetric";
429  case 445: return "Invalid limit value settings";
430  case 447: return "Insulation fault";
431  case 448: return "Neutral conductor not connected";
432  case 450: return "Guard cannot be found";
433  case 452: return "Memory error detected";
434  case 453: return "Short term grid voltage error";
435  case 454: return "Short term grid frequency error";
436  case 456: return "Anti-islanding function is no longer implemented correctly";
437  case 457: return "Grid relay sticking";
438  case 459: return "Error when recording the measuring signal for the insulation test";
439  case 460: return "Reference voltage source for the digital signal processor (DSP) is working out of tolerance";
440  case 461: return "Fault in the DSP data memory";
441  case 462: return "Error with DC feed monitoring routine";
442  case 463: return "Reversed AC polarity, AC connector inserted incorrectly";
443  case 474: return "RCMU sensor faulty";
444  case 475: return "Solar panel ground fault, insulation fault (connection between solar panel and ground)";
445  case 476: return "Driver supply voltage too low";
446  case 480: return "Functional incompatibility (one or more PC boards in the inverter are not compatible with each other, e.g. after a PC board has been replaced)";
447  case 481: return "Functional incompatibility (one or more PC boards in the inverter are not compatible with each other, e.g. after a PC board has been replaced)";
448  case 482: return "Startup incomplete";
449  case 483: return "Voltage UDC fixed on MPP2 string out of limits";
450  case 485: return "CAN transmit buffer is full";
451 
452  case 501: return "Insulation error on the solar panels";
453  case 509: return "No energy fed into the grid in the past 24 hours";
454  case 515: return "No communication with filter possible";
455  case 516: return "No communication possible with the storage unit";
456  case 517: return "Derating caused by too high a temperature";
457  case 558: return "Functional incompatibility (one or more PC boards in the inverter are not compatible with each other, e.g. after a PC board has been replaced)";
458  case 560: return "Derating caused by over-frequency";
459  case 566: return "Arc detector switched off (e.g. during external arc monitoring)";
460 
461  case 705: return "Conflict when setting the inverter number (e.g. number already assigned)";
462  case 721: return "EEPROM has been reinitialised or EEPROM is faulty";
463  case 731: return "Initialisation error – USB stick is not supported";
464  case 732: return "Over current on USB stick";
465  case 733: return "No USB stick connected";
466  case 734: return "Update file not recognised or not present";
467  case 735: return "Update file does not match the device, update file too old";
468  case 736: return "Write or read error occurred";
469  case 738: return "Log file cannot be saved (e.g. USB stick is write protected or full)";
470  case 743: return "Error occurred during update process";
471  case 745: return "Update file corrupt";
472  case 751: return "Time lost";
473  case 752: return "Real Time Clock module communication error";
474  case 757: return "Hardware error in the Real Time Clock module";
475  case 758: return "Internal error: Real Time Clock module is in emergency mode";
476  case 766: return "Emergency power de-rating has been activated";
477  };
478 
479  return "Uknown status code";
480 }
481 
482 
483 QString MODBUSTCP::exceptionCodeToString(int exceptionCode) {
484  switch(exceptionCode) {
485  case 0x01: return "Illegal function";
486  case 0x02: return "Illegal data address";
487  case 0x03: return "Illegal data value";
488  case 0x04: return "Slave device failure";
489  case 0x05: return "Acknowledge";
490  case 0x06: return "Slave device busy";
491  case 0x07: return "Negative acknowledge";
492  case 0x08: return "Memory parity error";
493  case 0x0a: return "Gateway path unavailable";
494  case 0x0b: return "Gateway target device failed to respond";
495  }
496  return "";
497 }
498 
499 
void quit()
Quits the running thread.
Definition: invertor.cpp:69
INVERTOR_status status()
Returns current status of the line.
Definition: invertor.cpp:63
MODBUSTCP()
Constructor. The very basic initializations.
Definition: modbustcp.cpp:35
void data(DBT_DATA)
Signal to send retrieved data to other objects.
void open()
Opens serial port.
Definition: modbustcp.cpp:79
Class describing database table DATA.
DBT_LINES m_line
Stores information about line.
Definition: invertor.h:122
QList< DBT_INVERTORS > m_invertors
Stores information about all invertors connected to the line.
Definition: invertor.h:127
void slotInit()
Function called within running thread to initialize all needed child objects.
Definition: modbustcp.cpp:51
Virtual class for invertor communication.
Definition: invertor.h:28
void loopFinished(int number_of_ok, int number_of_err)
Signal is sent when reading cycle was finished and all invertors were read.
int m_current_invertor_index
Current index in INVERTOR::m_invertors list.
Definition: modbustcp.h:58
void setStatus(int address, int retries, const QString &command, const QString &status)
Set status of line.
Definition: invertor.cpp:54
void loop()
Loops the invertors's list.
Definition: modbustcp.cpp:96
Class describing database table INVERTORS.