Browse Source

Untested: Communications code

Neal Wilson 7 years ago
parent
commit
6a352ad1ca
2 changed files with 269 additions and 1 deletions
  1. 269
    0
      src/modbus.w
  2. 0
    1
      src/typica.w

+ 269
- 0
src/modbus.w View File

@@ -325,6 +325,275 @@ app.registerDeviceConfigurationWidget("modbusnginput",
325 325
 inserter = new NodeInserter(tr("ModbusNG Port"), tr("Modbus RTU Port"), "modbusngport", NULL);
326 326
 topLevelNodeInserters.append(inserter);
327 327
 
328
+@ While the old design only needed to deal with a small number of potential
329
+messages and responses, it makes sense for the new design to use a scan list
330
+of arbitrary length. An initial implementation can simply store the data needed
331
+to make requests, properly interpret the results, and output data to the
332
+correct channels. There is room to improve operational efficiency later by
333
+batching operations on adjacent addresses on the same function into a single
334
+request, but there are known devices in use in coffee roasters which do not
335
+support reading from multiple registers simultaneously, so there must also be a
336
+way to turn such optimizations off.
337
+
338
+@<Class declarations@>=
339
+enum ModbusDataFormat
340
+{
341
+    Int16,
342
+    FloatHL,
343
+    FloatLH
344
+};
345
+
346
+struct ModbusScanItem
347
+{
348
+    QByteArray request;
349
+    ModbusDataFormat format;
350
+    int decimalPosition;
351
+    Units::Unit unit;
352
+    mutable double lastValue;
353
+};
354
+
328 355
 @ Another class is used to handle the communication with the bus and serve as
329 356
 an integration point for \pn{}.
330 357
 
358
+@<Class declarations@>=
359
+class ModbusNG : public QObject
360
+{
361
+    Q_OBJECT
362
+    public:
363
+        ModbusNG(DeviceTreeModel *model, const QModelIndex &index);
364
+        ~ModbusNG();
365
+        QList<Channel*> channels;
366
+        QList<ModbusScanItem> scanList;
367
+        QList<QString> channelNames;
368
+        QList<bool> hiddenStates;
369
+    private slots:
370
+        void sendNextMessage();
371
+        void timeout();
372
+        void dataAvailable();
373
+    private:
374
+        quint16 calculateCRC(QByteArray data);
375
+        QextSerialPort *port;
376
+        int delayTime;
377
+        QTimer *messageDelayTimer;
378
+        QTimer *commTimeout;
379
+        int scanPosition;
380
+        QByteArray responseBuffer;
381
+};
382
+
383
+@ One of the things that the old Modbus code got right was in allowing the
384
+constructor to handle device configuration by accepting its configuration
385
+sub-tree. In this design, child nodes establish a scan list.
386
+
387
+@<ModbusNG implementation@>=
388
+ModbusNG::ModbusNG(DeviceTreeModel *model, const QModelIndex &index) :
389
+    QObject(NULL), messageDelayTimer(new QTimer), commTimeout(new QTimer),
390
+    scanPosition(0)
391
+{
392
+    QDomElement portReferenceElement =
393
+        model->referenceElement(model->data(index, Qt::UserRole).toString());
394
+    QDomNodeList portConfigData = portReferenceElement.elementsByTagName("attribute");
395
+    QDomElement node;
396
+    QVariantMap attributes;
397
+    for(int i = 0; i < portConfigData.size(); i++)
398
+    {
399
+        node = portConfigData.at(i).toElement();
400
+        attributes.insert(node.attribute("name"), node.attribute("value"));
401
+    }
402
+    port = new QextSerialPort(attributes.value("port").toString(),
403
+                              QextSerialPort::EventDriven);
404
+    port->setBaudRate((BaudRateType)(attributes.value("baud").toInt()));
405
+    port->setDataBits(DATA_8);
406
+    port->setParity((ParityType)attributes.value("parity").toInt());
407
+    port->setStopBits((StopBitsType)attributes.value("stop").toInt());
408
+    port->setFlowControl((FlowType)attributes.value("flow").toInt());
409
+    delayTime = (int)(((double)(1)/(double)(attributes.value("baud").toInt())) * 144000.0);
410
+    messageDelayTimer->setSingleShot(true);
411
+    commTimeout->setSingleShot(true);
412
+    connect(messageDelayTimer, SIGNAL(timeout()), this, SLOT(sendNextMessage()));
413
+    connect(commTimeout, SIGNAL(timeout()), this, SLOT(timeout()));
414
+    connect(port, SIGNAL(readyRead()), this, SLOT(dataAvailable()));
415
+    port->open(QIODevice::ReadWrite);
416
+    for(int i = 0; i < model->rowCount(index); i++)
417
+    {
418
+        QModelIndex channelIndex = model->index(i, 0, index);
419
+        QDomElement channelReferenceElement =
420
+            model->referenceElement(model->data(channelIndex, Qt::UserRole).toString());
421
+        QDomNodeList channelConfigData =
422
+            channelReferenceElement.elementsByTagName("attribute");
423
+        QDomElement channelNode;
424
+        QVariantMap channelAttributes;
425
+        for(int j = 0; j < channelConfigData.size(); j++)
426
+        {
427
+            channelNode = channelConfigData.at(j).toElement();
428
+            channelAttributes.insert(channelNode.attribute("name"),
429
+                                     channelNode.attribute("value"));
430
+        }
431
+        ModbusScanItem scanItem;
432
+        QString format = channelAttributes.value("format").toString();
433
+        if(format == "16fixedint")
434
+        {
435
+            scanItem.format = Int16;
436
+        }
437
+        else if(format == "32floathl")
438
+        {
439
+            scanItem.format = FloatHL;
440
+        }
441
+        else if(format == "32floatlh")
442
+        {
443
+            scanItem.format = FloatLH;
444
+        }
445
+        scanItem.request.append((char)channelAttributes.value("station").toInt());
446
+        scanItem.request.append((char)channelAttributes.value("function").toInt());
447
+        quint16 startAddress = (quint16)channelAttributes.value("address").toInt();
448
+        char *startAddressBytes = (char*)&startAddress;
449
+        scanItem.request.append(startAddressBytes[1]);
450
+        scanItem.request.append(startAddressBytes[0]);
451
+        scanItem.request.append((char)0x00);
452
+        if(scanItem.format == Int16)
453
+        {
454
+            scanItem.request.append((char)0x01);
455
+        }
456
+        else
457
+        {
458
+            scanItem.request.append((char)0x02);
459
+        }
460
+        quint16 crc = calculateCRC(scanItem.request);
461
+        char *crcBytes = (char*)&crc;
462
+        scanItem.request.append(crcBytes[0]);
463
+        scanItem.request.append(crcBytes[1]);
464
+        scanItem.decimalPosition = channelAttributes.value("decimals").toInt();
465
+        if(channelAttributes.value("unit").toString() == "C")
466
+        {
467
+            scanItem.unit = Units::Celsius;
468
+        }
469
+        else
470
+        {
471
+            scanItem.unit = Units::Fahrenheit;
472
+        }
473
+        scanList.append(scanItem);
474
+        channels.append(new Channel);
475
+        channelNames.append(channelAttributes.value("column").toString());
476
+        hiddenStates.append(
477
+            channelAttributes.value("hidden").toString() == "true" ? true : false);
478
+    }
479
+}
480
+
481
+ModbusNG::~ModbusNG()
482
+{
483
+    commTimeout->stop();
484
+    messageDelayTimer->stop();
485
+    port->close();
486
+}
487
+
488
+void ModbusNG::sendNextMessage()
489
+{
490
+    if(scanList.length() > 0)
491
+    {
492
+        port->write(scanList.at(scanPosition).request);
493
+        commTimeout->start(2000);
494
+        messageDelayTimer->start(delayTime);
495
+    }
496
+}
497
+
498
+void ModbusNG::timeout()
499
+{
500
+    qDebug() << "Communications timeout.";
501
+    messageDelayTimer->start();
502
+}
503
+
504
+void ModbusNG::dataAvailable()
505
+{
506
+    if(messageDelayTimer->isActive())
507
+    {
508
+        messageDelayTimer->stop();
509
+    }
510
+    responseBuffer.append(port->readAll());
511
+    if(responseBuffer.size() < 5)
512
+    {
513
+        return;
514
+    }
515
+    if(responseBuffer.size() < 5 + responseBuffer.at(2))
516
+    {
517
+        return;
518
+    }
519
+    responseBuffer = responseBuffer.left(5 + responseBuffer.at(2));
520
+    commTimeout->stop();
521
+    if(calculateCRC(responseBuffer) == 0)
522
+    {
523
+        qDebug() << responseBuffer;
524
+        quint16 intresponse;
525
+        float floatresponse;
526
+        char *ibytes = (char*)&intresponse;
527
+        char *fbytes = (char*)&floatresponse;
528
+        double output;
529
+        switch(scanList.at(scanPosition).format)
530
+        {
531
+            case Int16:
532
+                ibytes[0] = responseBuffer.at(4);
533
+                ibytes[1] = responseBuffer.at(3);
534
+                output = intresponse;
535
+                for(int i = 0; i < scanList.at(scanPosition).decimalPosition; i++)
536
+                {
537
+                    output /= 10;
538
+                }
539
+                break;
540
+            case FloatHL:
541
+                fbytes[0] = responseBuffer.at(4);
542
+                fbytes[1] = responseBuffer.at(3);
543
+                fbytes[2] = responseBuffer.at(6);
544
+                fbytes[3] = responseBuffer.at(5);
545
+                output = floatresponse;
546
+                break;
547
+            case FloatLH:
548
+                fbytes[0] = responseBuffer.at(6);
549
+                fbytes[1] = responseBuffer.at(5);
550
+                fbytes[2] = responseBuffer.at(4);
551
+                fbytes[3] = responseBuffer.at(3);
552
+                output = floatresponse;
553
+                break;
554
+        }
555
+        if(scanList.at(scanPosition).unit == Units::Celsius)
556
+        {
557
+            output = output * 9.0 / 5.0 + 32.0;
558
+        }
559
+        scanList.at(scanPosition).lastValue = output;
560
+    }
561
+    else
562
+    {
563
+        qDebug() << "CRC failed";
564
+    }
565
+    scanPosition = (scanPosition + 1) % scanList.size();
566
+    if(scanPosition == 0)
567
+    {
568
+        QTime time = QTime::currentTime();
569
+        for(int i = 0; i < scanList.size(); i++)
570
+        {
571
+            channels.at(i)->input(Measurement(scanList.at(i).lastValue, time, Units::Fahrenheit));
572
+        }
573
+    }
574
+    responseBuffer.clear();
575
+    messageDelayTimer->start(delayTime);
576
+}
577
+
578
+quint16 ModbusNG::calculateCRC(QByteArray data)
579
+{
580
+    quint16 retval = 0xFFFF;
581
+    int i = 0;
582
+    while(i < data.size())
583
+    {
584
+        retval ^= 0x00FF & (quint16)data.at(i);
585
+        for(int j = 0; j < 8; j++)
586
+        {
587
+            if(retval & 1)
588
+            {
589
+                retval = (retval >> 1) ^ 0xA001;
590
+            }
591
+            else
592
+            {
593
+                retval >>= 1;
594
+            }
595
+        }
596
+        i++;
597
+    }
598
+    return retval;
599
+}

+ 0
- 1
src/typica.w View File

@@ -18176,7 +18176,6 @@ ModbusRTUDevice::ModbusRTUDevice(DeviceTreeModel *model,@| const QModelIndex &in
18176 18176
 : QObject(NULL), messageDelayTimer(new QTimer), commTimeout(new QTimer), unitIsF(@[true@]), readingsv(@[false@]),
18177 18177
     waiting(@[false@])@/
18178 18178
 {@/
18179
-qDebug() << "Initializing Modbus RTU Device";
18180 18179
     QDomElement portReferenceElement = model->referenceElement(model->data(index,
18181 18180
         Qt::UserRole).toString());
18182 18181
     QDomNodeList portConfigData = portReferenceElement.elementsByTagName("attribute");

Loading…
Cancel
Save