I am sending (writing) bytes to a device via my serial port. I am using the QSerialPort (http://qt-project.org/wiki/QtSerialPort) module to instantiate device IO support. When I send messages to my INSTEON modem (serial), upon reading my message the device sends back a copy of my message + 0x06 (ACK Byte) followed by a status message.
I have tested my message using DockLight (http://www.docklight.de/). I send the following message to query the state of the device:
02 62 1D E9 4B 05 19 00
Using Docklight, I receive the response:
02 62 1D E9 4B 05 19 00 06 02 50 20 CB CF 1E DA F7 21 00 FF
The returned message indicates exactly what I would expect, that the device is on. If off, the modem would send back 0x00 in the last byte position if the device was off. Now, my problem - I must not have my function setup properly to send and then receive the response bytes. I have tried many different examples and configurations, currently I am using the following:
Setup signal-slot connections:
QObject::connect(&thread, SIGNAL(sendResponse(QByteArray)),
this, SLOT(handleResponse(QByteArray)));
QObject::connect(&thread, SIGNAL(error(QString)),
this, SLOT(processError(QString)));
QObject::connect(&thread, SIGNAL(timeout(QString)),
this, SLOT(processTimeout(QString)));
Function used to iterate through QList of devices. If device is desired type ("Light"), then we format the device ID to the intended QByteArray message structure. Pass message to thread for sending. (Thread modified from QSerialPort BlockingMaster example.
void Device::currentStatus(QList<Device *> * deviceList){
QString devID, updateQry;
int devStatus, updateStatus;
updateStatus=0;
QSqlQuery query;
for(int i=0; i<deviceList->size(); i++){
if(deviceList->at(i)->type == "Light"){
devStatus = deviceList->at(i)->status;
devID = deviceList->at(i)->deviceID;
QByteArray msg;
bool msgStatus;
msg.resize(8);
msg[0] = 0x02;
msg[1] = 0x62;
msg[2] = 0x00;
msg[3] = 0x00;
msg[4] = 0x00;
msg[5] = 0x05;
msg[6] = 0x19;
msg[7] = 0x00;
msg.replace(2, 3, QByteArray::fromHex( devID.toLocal8Bit() ) );
qDebug() << "Has device " << deviceList->at(i)->name << "Changed?";
//send(msg,&msgStatus, &updateStatus);
//msg.clear();
thread.setupPort("COM3",500,msg);
if(devStatus!=updateStatus){
qDebug() << deviceList->at(i)->name << " is now: " << updateStatus;
updateStatus = !updateStatus;
}
}
}
}
SetupThread function used to set local thread variables and executes (runs) thread.
void serialThread::setupPort(const QString &portName, int waitTimeout, const QByteArray &msg){
qDebug() << "Send Message " << msg.toHex();
QMutexLocker locker(&mutex);
this->portName = portName;
this->waitTimeout = waitTimeout;
this->msg = msg;
if(!isRunning())
start();
else
cond.wakeOne();
}
Run Function - Handled sending and receiving
void serialThread::run(){
bool currentPortNameChanged = false;
qDebug() << "Thread executed";
mutex.lock();
QString currentPortName;
if(currentPortName != portName){
currentPortName = portName;
currentPortNameChanged = true;
}
int currentWaitTimeout = waitTimeout;
QByteArray sendMsg = msg;
mutex.unlock();
QSerialPort serial;
while(!quit){
if(currentPortNameChanged){
serial.close();
serial.setPortName("COM3");
if (!serial.open(QIODevice::ReadWrite)) {
emit error(tr("Can't open %1, error code %2")
.arg(portName).arg(serial.error()));
return;
}
if (!serial.setBaudRate(QSerialPort::Baud19200)) {
emit error(tr("Can't set baud rate 9600 baud to port %1, error code %2")
.arg(portName).arg(serial.error()));
return;
}
if (!serial.setDataBits(QSerialPort::Data8)) {
emit error(tr("Can't set 8 data bits to port %1, error code %2")
.arg(portName).arg(serial.error()));
return;
}
if (!serial.setParity(QSerialPort::NoParity)) {
emit error(tr("Can't set no patity to port %1, error code %2")
.arg(portName).arg(serial.error()));
return;
}
if (!serial.setStopBits(QSerialPort::OneStop)) {
emit error(tr("Can't set 1 stop bit to port %1, error code %2")
.arg(portName).arg(serial.error()));
return;
}
if (!serial.setFlowControl(QSerialPort::NoFlowControl)) {
emit error(tr("Can't set no flow control to port %1, error code %2")
.arg(portName).arg(serial.error()));
return;
}
}
//write request
serial.write(msg);
if (serial.waitForBytesWritten(waitTimeout)) {
//! [8] //! [10]
// read response
if (serial.waitForReadyRead(currentWaitTimeout)) {
QByteArray responseData = serial.readAll();
while (serial.waitForReadyRead(10)){
responseData += serial.readAll();
}
QByteArray response = responseData;
//! [12]
emit this->sendResponse(response);
//! [10] //! [11] //! [12]
} else {
emit this->timeout(tr("Wait read response timeout %1")
.arg(QTime::currentTime().toString()));
}
//! [9] //! [11]
} else {
emit timeout(tr("Wait write request timeout %1")
.arg(QTime::currentTime().toString()));
}
mutex.lock();
cond.wait(&mutex);
if (currentPortName != portName) {
currentPortName = portName;
currentPortNameChanged = true;
} else {
currentPortNameChanged = false;
}
currentWaitTimeout = waitTimeout;
sendMsg = msg;
mutex.unlock();
}
serial.close();
}
handleResponse function, SLOT which receives response signal
void Device::handleResponse(const QByteArray &msg){
qDebug() << "Read: " << msg.toHex();
}
I receive the following output:
Has device "Living Room Light" Changed?
Send Message "02621de94b051900"
Has device "Bedroom Light" Changed?
Send Message "026220cbcf051900"
Thread executed
Read: "026220cbcf05190006"
Polling for changes...
Has device "Living Room Light" Changed?
Send Message "02621de94b051900"
Has device "Bedroom Light" Changed?
Send Message "026220cbcf051900"
Read: "025020cbcf1edaf721000002621de94b05190006"
Polling for changes...
Has device "Living Room Light" Changed?
Send Message "02621de94b051900"
Has device "Bedroom Light" Changed?
Send Message "026220cbcf051900"
Read: "02501de94b1edaf72100ff02621de94b05190006"
Two issues here.
I never receive any response regarding the second device (Bedroom Light), this is the message that is sent second. It seems that the send is being blocked, how would you recommend I format my sending so that I send after the response is received for the first send? There is only 1 COM port that can be used to send/receive. I believe I should Send message to Device 1, receive Device 1 response, Send to Device 2, receive Device 2. Could I end up seeing a huge traffic jam with a lot of devices and using wait conditions, ie. wait for device 1 communication process to finish before executing comm process for device 2?
The very first read contains the appropriate 1st half of the receive. Read: "026220cbcf05190006" The second receive contains the 2nd half of the 1st response followed by the 1st half of the second response: Read 2 - Read: "025020cbcf1edaf721000002621de94b05190006" The appropriate full response is 02621DE94B05190006 025020CBCF1EDAF72100FF
(note 20CBCF is Device 2's ID in the full response example)
What corrections should be made to the way I am receiving data from the serial port?
Thank you!
I believe my problems have shifted from the scope of this question. With help from Kuzulis I have implemented Write/Read functions to successfully send and read serial messages consistently. Kuzulis recommended using the Synchronous blocking communication pattern, however it was later decided that the Asynchronous Non-Blocking method would be best fit for my application.
My implementation closely follows the "Master" example provided with the QSerialPort source files.
I use CurrentStatus to iterate through a QList of Device objects. For each Light in the Device list, I format an 8 Byte message to query the current status of the device (ON/OFF).
void Device::currentStatus(QList<Device *> * deviceList){
QString devID, updateQry;
int devStatus, updateStatus;
updateStatus=0;
QSqlQuery query;
for(int i=0; i<deviceList->size(); i++){
if(deviceList->at(i)->type == "Light"){
devStatus = deviceList->at(i)->status;
devID = deviceList->at(i)->deviceID;
QByteArray msg;
msg.resize(8);
msg[0] = 0x02;
msg[1] = 0x62;
msg[2] = 0x00;
msg[3] = 0x00;
msg[4] = 0x00;
msg[5] = 0x05;
msg[6] = 0x19;
msg[7] = 0x00;
msg.replace(2, 3, QByteArray::fromHex( devID.toLocal8Bit() ) );
qDebug() << "Has device " << deviceList->at(i)->name << "Changed?";
emit writeRequest(msg);
if(devStatus!=updateStatus){
qDebug() << deviceList->at(i)->name << " is now: " << updateStatus;
updateStatus = !updateStatus;
}
}
}
}
In the Device class constructor, I connect the signals and slots:
Device::Device(){
serialTimer.setSingleShot(true);
QObject::connect(&serial, SIGNAL(readyRead()),
this, SLOT(handleResponse()));
QObject::connect(&serialTimer, SIGNAL(timeout()),
this, SLOT(processTimeout()));
QObject::connect(this, SIGNAL(writeRequest(QByteArray)),
this, SLOT(writeSerial(QByteArray)));
}
After the message to send in currentStatus has been prepared, emit writeRequest(msg); is called. This dispatches a signal that is connected to the slot writeRequest. writeRequest is used to setup and actually write the message to the serial port.
void Device::writeSerial(const QByteArray &msg){
if (serial.portName() != "COM3") {
serial.close();
serial.setPortName("COM3");
if (!serial.open(QIODevice::ReadWrite)) {
processError(tr("Can't open %1, error code %2")
.arg(serial.portName()).arg(serial.error()));
return;
}
if (!serial.setBaudRate(QSerialPort::Baud19200)) {
processError(tr("Can't set rate 19200 baud to port %1, error code %2")
.arg(serial.portName()).arg(serial.error()));
return;
}
if (!serial.setDataBits(QSerialPort::Data8)) {
processError(tr("Can't set 8 data bits to port %1, error code %2")
.arg(serial.portName()).arg(serial.error()));
return;
}
if (!serial.setParity(QSerialPort::NoParity)) {
processError(tr("Can't set no patity to port %1, error code %2")
.arg(serial.portName()).arg(serial.error()));
return;
}
if (!serial.setStopBits(QSerialPort::OneStop)) {
processError(tr("Can't set 1 stop bit to port %1, error code %2")
.arg(serial.portName()).arg(serial.error()));
return;
}
if (!serial.setFlowControl(QSerialPort::NoFlowControl)) {
processError(tr("Can't set no flow control to port %1, error code %2")
.arg(serial.portName()).arg(serial.error()));
return;
}
}
qDebug() << "Message written";
this->msgRequest = msg;
serial.write(msgRequest);
serialTimer.start(400);
}
After setting up the serial port, I save the current message to msgRequest. This may have to be used to resend the message if there is an error. After serial.write() is called, I setup a timer for 400ms. Once this timer expires, I check what was read from the serial port.
handleResponse() is a slot that is called everytime QSerialPort emits the readyRead() signal. readyRead() appends any available data to the QByteArray response.
void Device::handleResponse(){
response.append(serial.readAll());
}
After 400ms, serialTimer (one shot Timer) will emit a timeout() signal. serialTimer was started right after writing our requested message to the serial port. processTimeout() is where we finally check the response received from the PowerLinc Modem after sending our message. When messages are sent to the INSTEON PowerLinc Modem (PLM), the PLM echoes back the message and appends either 0x06 (Positive ACK) or 0x15 (NACK). In processTimeout() I check to make sure the last byte received is the ACK byte, if not - resend our originally requested message.
void Device::processTimeout(){
qDebug() << "Read: " << response.toHex();
int msgLength = this->msgRequest.length();
if(response.at(msgLength)!=0x06){
qDebug() << "Error, resend.";
emit writeRequest(msgRequest);
}
response.clear();
}
I used the Serial Port Monitor 4.0 (Eltima Software) to verify the write and read transactions on the serial port. Below, you can see the log printout for 1 sample transaction.
20:44:30:666 STATUS_SUCCESS 02 62 1d e9 4b 05 19 00 <--- Send
20:44:30:669 STATUS_SUCCESS 02 62 1d e9 4b 05 19 00 06 <--- Receive
20:44:30:875 STATUS_SUCCESS 02 <--- Receive
20:44:30:881 STATUS_SUCCESS 50 1d e9 4b 1e da f7 21 00 ff <--- Receive
For 20 sends, I received the same response. Thus, I can safely say my issues with inconsistent data arrival have been resolved. Now I am struggling with multiple write requests, but I believe that is a separate question to be investigated. I appreciate everyone's support.
See the BlockingMaster example in the repository and read the documentation about the blocking I/O. Also, do not use blocking I/O unnecessarily.
Use bytesAvailable() to get the number of available data for reading, because not the fact that you immediately receive a complete response package.
Related
I'm using this example from the Sparkfun Arduino Library
/*
Use ESP32 WiFi to get RTCM data from RTK2Go (caster) as a Client
By: SparkFun Electronics / Nathan Seidle
Date: November 18th, 2021
License: MIT. See license file for more information but you can
basically do whatever you want with this code.
This example shows how to obtain RTCM data from a NTRIP Caster over WiFi
and push it over I2C to a ZED-F9x.
It's confusing, but the Arduino is acting as a 'client' to a 'caster'. In this case we will
use RTK2Go.com as our caster because it is free. See the NTRIPServer example to see how
to push RTCM data to the caster.
You will need to have a valid mountpoint available. To see available mountpoints go here: http://rtk2go.com:2101/
This is a proof of concept to show how to connect to a caster via HTTP. Using WiFi for a rover
is generally a bad idea because of limited WiFi range in the field.
For more information about NTRIP Clients and the differences between Rev1 and Rev2 of the protocol
please see: https://www.use-snip.com/kb/knowledge-base/ntrip-rev1-versus-rev2-formats/
Feel like supporting open source hardware?
Buy a board from SparkFun!
ZED-F9P RTK2: https://www.sparkfun.com/products/16481
RTK Surveyor: https://www.sparkfun.com/products/18443
RTK Express: https://www.sparkfun.com/products/18442
Hardware Connections:
Plug a Qwiic cable into the GNSS and a ESP32 Thing Plus
If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425)
Open the serial monitor at 115200 baud to see the output
*/
#include <WiFi.h>
#include "secrets.h"
#include <SparkFun_u-blox_GNSS_Arduino_Library.h> //http://librarymanager/All#SparkFun_u-blox_GNSS
SFE_UBLOX_GNSS myGNSS;
//The ESP32 core has a built in base64 library but not every platform does
//We'll use an external lib if necessary.
#if defined(ARDUINO_ARCH_ESP32)
#include "base64.h" //Built-in ESP32 library
#else
#include <Base64.h> //nfriendly library from https://github.com/adamvr/arduino-base64, will work with any platform
#endif
//Global variables
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
long lastReceivedRTCM_ms = 0; //5 RTCM messages take approximately ~300ms to arrive at 115200bps
int maxTimeBeforeHangup_ms = 10000; //If we fail to get a complete RTCM frame after 10s, then disconnect from caster
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void setup()
{
Serial.begin(115200);
Serial.println(F("NTRIP testing"));
Wire.begin(); //Start I2C
if (myGNSS.begin() == false) //Connect to the Ublox module using Wire port
{
Serial.println(F("u-blox GPS not detected at default I2C address. Please check wiring. Freezing."));
while (1);
}
Serial.println(F("u-blox module connected"));
myGNSS.setI2COutput(COM_TYPE_UBX); //Turn off NMEA noise
myGNSS.setPortInput(COM_PORT_I2C, COM_TYPE_UBX | COM_TYPE_NMEA | COM_TYPE_RTCM3); //Be sure RTCM3 input is enabled. UBX + RTCM3 is not a valid state.
myGNSS.setNavigationFrequency(1); //Set output in Hz.
Serial.print(F("Connecting to local WiFi"));
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(F("."));
}
Serial.println();
Serial.print(F("WiFi connected with IP: "));
Serial.println(WiFi.localIP());
while (Serial.available()) Serial.read();
}
void loop()
{
if (Serial.available())
{
beginClient();
while (Serial.available()) Serial.read(); //Empty buffer of any newline chars
}
Serial.println(F("Press any key to start NTRIP Client."));
delay(1000);
}
//Connect to NTRIP Caster, receive RTCM, and push to ZED module over I2C
void beginClient()
{
WiFiClient ntripClient;
long rtcmCount = 0;
Serial.println(F("Subscribing to Caster. Press key to stop"));
delay(10); //Wait for any serial to arrive
while (Serial.available()) Serial.read(); //Flush
while (Serial.available() == 0)
{
//Connect if we are not already. Limit to 5s between attempts.
if (ntripClient.connected() == false)
{
Serial.print(F("Opening socket to "));
Serial.println(casterHost);
if (ntripClient.connect(casterHost, casterPort) == false) //Attempt connection
{
Serial.println(F("Connection to caster failed"));
return;
}
else
{
Serial.print(F("Connected to "));
Serial.print(casterHost);
Serial.print(F(": "));
Serial.println(casterPort);
Serial.print(F("Requesting NTRIP Data from mount point "));
Serial.println(mountPoint);
const int SERVER_BUFFER_SIZE = 512;
char serverRequest[SERVER_BUFFER_SIZE];
snprintf(serverRequest, SERVER_BUFFER_SIZE, "GET /%s HTTP/1.0\r\nUser-Agent: NTRIP SparkFun u-blox Client v1.0\r\n",
mountPoint);
char credentials[512];
if (strlen(casterUser) == 0)
{
strncpy(credentials, "Accept: */*\r\nConnection: close\r\n", sizeof(credentials));
}
else
{
//Pass base64 encoded user:pw
char userCredentials[sizeof(casterUser) + sizeof(casterUserPW) + 1]; //The ':' takes up a spot
snprintf(userCredentials, sizeof(userCredentials), "%s:%s", casterUser, casterUserPW);
Serial.print(F("Sending credentials: "));
Serial.println(userCredentials);
#if defined(ARDUINO_ARCH_ESP32)
//Encode with ESP32 built-in library
base64 b;
String strEncodedCredentials = b.encode(userCredentials);
char encodedCredentials[strEncodedCredentials.length() + 1];
strEncodedCredentials.toCharArray(encodedCredentials, sizeof(encodedCredentials)); //Convert String to char array
snprintf(credentials, sizeof(credentials), "Authorization: Basic %s\r\n", encodedCredentials);
#else
//Encode with nfriendly library
int encodedLen = base64_enc_len(strlen(userCredentials));
char encodedCredentials[encodedLen]; //Create array large enough to house encoded data
base64_encode(encodedCredentials, userCredentials, strlen(userCredentials)); //Note: Input array is consumed
#endif
}
strncat(serverRequest, credentials, SERVER_BUFFER_SIZE);
strncat(serverRequest, "\r\n", SERVER_BUFFER_SIZE);
Serial.print(F("serverRequest size: "));
Serial.print(strlen(serverRequest));
Serial.print(F(" of "));
Serial.print(sizeof(serverRequest));
Serial.println(F(" bytes available"));
Serial.println(F("Sending server request:"));
Serial.println(serverRequest);
ntripClient.write(serverRequest, strlen(serverRequest));
//Wait for response
unsigned long timeout = millis();
while (ntripClient.available() == 0)
{
if (millis() - timeout > 5000)
{
Serial.println(F("Caster timed out!"));
ntripClient.stop();
return;
}
delay(10);
}
//Check reply
bool connectionSuccess = false;
char response[512];
int responseSpot = 0;
while (ntripClient.available())
{
if (responseSpot == sizeof(response) - 1) break;
response[responseSpot++] = ntripClient.read();
if (strstr(response, "200") > 0) //Look for 'ICY 200 OK'
connectionSuccess = true;
if (strstr(response, "401") > 0) //Look for '401 Unauthorized'
{
Serial.println(F("Hey - your credentials look bad! Check you caster username and password."));
connectionSuccess = false;
}
}
response[responseSpot] = '\0';
Serial.print(F("Caster responded with: "));
Serial.println(response);
if (connectionSuccess == false)
{
Serial.print(F("Failed to connect to "));
Serial.print(casterHost);
Serial.print(F(": "));
Serial.println(response);
return;
}
else
{
Serial.print(F("Connected to "));
Serial.println(casterHost);
lastReceivedRTCM_ms = millis(); //Reset timeout
}
} //End attempt to connect
} //End connected == false
if (ntripClient.connected() == true)
{
uint8_t rtcmData[512 * 4]; //Most incoming data is around 500 bytes but may be larger
rtcmCount = 0;
//Print any available RTCM data
while (ntripClient.available())
{
//Serial.write(ntripClient.read()); //Pipe to serial port is fine but beware, it's a lot of binary data
rtcmData[rtcmCount++] = ntripClient.read();
if (rtcmCount == sizeof(rtcmData)) break;
}
if (rtcmCount > 0)
{
lastReceivedRTCM_ms = millis();
//Push RTCM to GNSS module over I2C
myGNSS.pushRawData(rtcmData, rtcmCount, false);
Serial.print(F("RTCM pushed to ZED: "));
Serial.println(rtcmCount);
}
}
//Close socket if we don't have new data for 10s
if (millis() - lastReceivedRTCM_ms > maxTimeBeforeHangup_ms)
{
Serial.println(F("RTCM timeout. Disconnecting..."));
if (ntripClient.connected() == true)
ntripClient.stop();
return;
}
delay(10);
}
Serial.println(F("User pressed a key"));
Serial.println(F("Disconnecting..."));
ntripClient.stop();
}
on an ESP32 Thing Plus C with a ZED-F9P and it's working fine, but it only outputs RTCM data. How do I apply the RTCM data to the GPS data and achieve RTK? My goal is to have the ESP32 Thing Plus C output RTK Latitude and Longitude to the serial monitor.
Example output:
RTCM pushed to ZED: 163
RTCM pushed to ZED: 311
RTCM pushed to ZED: 1694
RTCM pushed to ZED: 1332
Any ideas would be appeciated! Thanks
Answered by PaulZC on Sparkfun Forum
Hi Jacob,
It sounds like everything is working OK. But, correct, there is
nothing in that example to actually print out the position.
Please try Example17. It is better-structured and uses callbacks to:
display your position; and push NMEA GGA data to the server. Some
NTRIP servers require the GGA data, others don't. If the GGA data
causes problems, you can comment this line to disable the push:
https://github.com/sparkfun/SparkFun_u- ... ck.ino#L81
Have fun! Paul
When I upload the code to try 2-way communication of LoRa Sx1278 with Arduino UNO it fails to work. I am using 2 modules with the same code. This is the output I receive:
23:09:27.186 -> Received packet: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^' with RSSI -70
23:09:28.207 -> Sending message
I understand the module receives a message but fails to read it, and the second LoRa module with the receiver code fails.
Here is my code:
#include <Wire.h>
#include <SPI.h>
#include <LoRa.h>
String outgoing;
byte msgCount = 0; // count of outgoing messages
byte localAddress = 0xBB; // address of this device
byte destination = 0xFF; // destination to send to
long lastSendTime = 0; // last send time
int interval = 300; // interval between sends
void setup() {
Serial.begin(115200);
while (!Serial);
Serial.println("LoRa Two-Way Communication");
if (!LoRa.begin(433E6)) {
Serial.println("Starting LoRa failed!");
delay(100);
while (1);
}
}
void loop() {
if (millis() - lastSendTime > interval) {
String message = "data from sensors";
Serial.println("Sending message");
sendMessage(message);
// Serial.println("Sending " + message);
lastSendTime = millis(); // timestamp the message
interval = random(50) + 300; // 2-3 seconds
}
// parse for a packet, and call onReceive with the result:
onReceive(LoRa.parsePacket());
}
void sendMessage(String outgoing) {
LoRa.beginPacket(); // start packet
LoRa.write(destination); // add destination address
LoRa.write(localAddress); // add sender address
LoRa.write(msgCount); // add message ID
LoRa.write(outgoing.length()); // add payload length
LoRa.print(outgoing); // add payload
LoRa.endPacket(); // finish packet and send it
msgCount++; // increment message ID
}
void onReceive(int packetSize) {
if (packetSize == 0) return;
// read packet header bytes:
int recipient = LoRa.read(); // recipient address
byte sender = LoRa.read(); // sender address
byte incomingMsgId = LoRa.read(); // incoming msg ID
byte incomingLength = LoRa.read(); // incoming msg length
// received a packet
Serial.print("Received packet: ");
String LoRaData = LoRa.readString();
Serial.print(LoRaData);
// read packet
while (LoRa.available()) {
Serial.print((char)LoRa.read());
}
// print RSSI of packet
Serial.print("' with RSSI ");
Serial.println(LoRa.packetRssi());
delay(1000);
}
I got the code from a tutorial and changed it so it is used to test the module for 2-way communication. Earlier, I tried an example from the library "LoRa Sender" and "LoRa Receiver" and it works flawlessly, so this isn't a hardware issue as far as I am concerned.
There are a few things that are not quite right in the code, but the main one is this: interval = random(50) + 300; // 2-3 seconds That's nowhere near 2 seconds. interval is in milliseconds, so a maximum of 50+300 will give you 0.35 seconds. Which means that both devices are transmitting NON-STOP, and can't hear each other. Try something like 2000 + random(1000)... Although there are better ways to get a random than using random. But anyway.
Also, stay off 433e6, it's a busy frequency, and if there are people nearby with a car remote control, you'll receive a lot of stuff not from you.
I am writing a simple client/server communication with fifo but i am stuck at using a signal handler to process client request.
The server open a fifo in readonly and non blocking mode, read datas received and writes back some datas to the client fifo.
And this actually works fine when there is no signal handler on the server side. Here is the main code for both sides.
Server :
int main(int argc, char *argv[])
{
// install handler
struct sigaction action;
action.sa_handler = requestHandler;
sigemptyset(&(action.sa_mask));
action.sa_flags = SA_RESETHAND | SA_RESTART;
sigaction(SIGIO, &action, NULL);
if(!makeFifo(FIFO_READ, 0644))
exit(1);
int rd_fifo = openFifo(FIFO_READ, O_RDONLY | O_NONBLOCK); // non blocking
if(rd_fifo == -1)
exit(1);
// wait for request and answer
while (1) {
qWarning() << "waiting client...";
sleep(1);
QString msg = readFifo(rd_fifo);
qWarning() << "msg = " << msg;
if(msg == "ReqMode") {
int wr_fifo = openFifo(FIFO_WRITE, O_WRONLY); // blocking
writeFifo(wr_fifo, QString("mode"));
break;
} else
qWarning() << "unknow request ..";
}
close(rd_fifo);
unlink(FIFO_READ);
return 0;
}
Client :
int main(int argc, char *argv[])
{
int wr_fifo = openFifo(FIFO_WRITE, O_WRONLY);
if(wr_fifo == -1)
exit(1);
// create a fifo to read server answer
if(!makeFifo(FIFO_READ, 0644))
exit(1);
// ask the server his mode
writeFifo(wr_fifo, QString("ReqMode"));
// read his answer and print it
int rd_fifo = openFifo(FIFO_READ, O_RDONLY); // blocking
qWarning() << "server is in mode : " << readFifo(rd_fifo);
close(rd_fifo);
unlink(FIFO_READ);
return 0;
}
Everything works as expected ( even if all errors are not properly handled, this is just a sample code to demonstrate that this is possible).
The problem is that the handler ( not shown here, but it only print a message on the terminal with the signal received ) is never called when the client write datas to the fifo. Beside, i have check that if i send a kill -SIGIO to the server from a bash ( or from elsewhere ) the signal handler is executed.
Thanks for your help.
Actually, i missed the 3 following lines on the server side :
fcntl(rd_fifo, F_SETOWN, getpid()); // set PID of the receiving process
fcntl(rd_fifo, F_SETFL, fcntl(rd_fifo, F_GETFL) | O_ASYNC); // enable asynchronous beahviour
fcntl(rd_fifo, F_SETSIG, SIGIO); // set the signal that is sent when the kernel tell us that there is a read/write on the fifo.
The last point was important because the default signal sent was 0 in my case, so i had to set it explicity to SIGIO to make things works. Here is the output of the server side :
waiting client...
nb_read = 0
msg = ""
unknow request ..
waiting client...
signal 29
SIGPOLL
nb_read = 7
msg = "ReqMode"
Now, i guess it's possible to handle the request inside the handler by moving what is inside the while loop into the requestHandler function.
I am very new to programming and I am teaching myself. I wrote a application to poll a number of requests from a control unit . I basically continuously send various read commands to the Control unit and read back the response .My program works and i successfully send commands and receive answers . But the reading is very slow ( i have a 100 ms timeout in my code to ensure i get the complete reply )
I have a program for the same control unit that was written by a professional coder in C++ , in his program he polls every 30 ms and i always receive the complete answer in that time frame . I have the same settings 57K baud 8 bits 1 stop bit and no parity . However my QT code takes almost 100 ms to receive the answer.
In my code i read the first 2 bytes ( first byte is the message identifier and second byte is the remainder of the message length) then i read in a loop until the total message length is equal to the message length byte +1 (the +1 is there to include the first byte ) . I am a wits end as to why my code is so slow in QT when i know its know the Hardware that's the limiting factor . The requests are always 3 bytes and the reply varies from 3 to 61 bytes . Please help me to point me to my error. If i remove the timeout i always have short reads . So far i also tried read(all) but with the same result .
Below is the extract from my code where i read the response . The full code is at https://github.com/MarkusIppy/PowerTune
//Error handling
QTime startTime = QTime::currentTime();
int timeOut = 100; // timeout in milisec.
QByteArray recvData = m_serialport->read(2); // reading first two bytes of received message to determine lenght of ecpected message
int msgLen = recvData[1]; //Total message Lenght excluding the first byte
while ( recvData.size() <= (msgLen+1) )
{
if ( startTime.msecsTo(QTime::currentTime()) > timeOut ) break;
recvData += m_serialport->read(msgLen+1-recvData.size());
}
if (msgLen +1 == recvData.length()) //if the received data lenght equals the message lenght from lenght byte + identifier byte (correct message lenght received )
{
qDebug() << "Received data OK"<<msgLen +1;
if(requestIndex <= 61){requestIndex++;}
else{requestIndex = 58;}
readData(recvData);
}
else //if the lenght of the received message does not correspond with the expected lenght repeat the request
{
qDebug() << "Received data lenght NIO";
readData(recvData);
qDebug() << "Request Message again"<< requestIndex;
}
I am sorry, I don't have enough time to go through your project and from the code you've provided I cannot be 100% sure what the cause is. My best guess though is that the problem in this case is that you wait explicitly for the data to be received and the events processing is somehow delayed or does not take place at all.
Anyway, here you have a couple of suggestions:
Use QTimer for timeouts instead of QTime.
Learn about the Qt5's signals and slots and use them to read from the serial port asynchronously.
I use the QSerialPort by connecting its bytesWritten(qint64 bytes) and readyRead() signals to slots of my program, let's say on_bytesWritten(qint64 bytes) and on_readyRead(). Then I send request to the target device and in the on_readyRead() slot I process the result. With each send command I start a QTimer with its timeout() signal connected to a on_timeout() slot of my application. This way I could monitor whether the device responds in time or not, as well as to have the data as soon as it comes. You may also use the errorOccurred(QSerialPort::SerialPortError error) signal of the QSerialPort to check if there is a problem with the transmission.
Changed my code slightly again and now it works perfectly on the actual Hardware
Below is my ready to read Slot :
void Serial::readyToRead()
{
qDebug() << "ready read";
if(ecu == 0)
{
m_readData.append(m_serialport->readAll());
Bytesexpected = m_readData[1]+1;
qDebug() << "readdata current" <<m_readData.toHex();
if (Bytesexpected == m_readData.size())
{
m_timer.stop();
if(requestIndex <= 62){requestIndex++;}
else{requestIndex = 59;}
readData(m_readData);
Serial::clear();
m_readData.clear();
Serial::sendRequest(requestIndex);
}
if (Bytesexpected != m_readData.size())
{
qDebug() << "starting timer";
m_timer.start(5000);
}
}
This is what i have so far ( just posting the important parts of my cpp file )
This code works now almost perfectly with my message emulator. It polls now as expected but the timeout gets always triggered after 5 seconds ( i need to change it to only trigger if there is no message comming ) . I will only be able to test it on the actual hardware end of next week .
This is what i have so far :
void Serial::initSerialPort()
{
if (m_serialport)
delete m_serialport;
m_serialport = new SerialPort(this);
connect(this->m_serialport,SIGNAL(readyRead()),this,SLOT(readyToRead()));
connect(m_serialport, static_cast<void (QSerialPort::*) (QSerialPort::SerialPortError)>(&QSerialPort::error),
this, &Serial::handleError);
connect(&m_timer, &QTimer::timeout, this, &Serial::handleTimeout);
m_timer.start(5000);
}
void Serial::readyToRead()
{
if(ecu == 0)
{
m_readData.append(m_serialport->readAll());
Bytesexpected = m_readData[1]+1;
if (Bytesexpected == m_readData.size())
{
if(requestIndex <= 62){requestIndex++;}
else{requestIndex = 59;}
readData(m_readData); // message for processing
Serial::clear();
m_readData.clear();
}
//Timeout
if (!m_timer.isActive())
m_timer.start(5000);
}
}
void Serial::handleTimeout()
{
qDebug() << "Timeout";
//request Data Again
QString fileName = "Errors.txt";
QFile mFile(fileName);
if(!mFile.open(QFile::Append | QFile::Text)){
qDebug() << "Could not open file for writing";
}
QTextStream out(&mFile);
out << "Timeout Request Index " << int(requestIndex)<< " lenght received "<< int(m_readData.length())<< " Bytes "<< " Expected Bytes "<< int(Bytesexpected)<< " bytes " <<" Message "<< QByteArray(m_readData.toHex()) <<endl;
mFile.close();
Serial::clear();
m_readData.clear();
Serial::sendRequest(requestIndex);
}
void Serial::handleError(QSerialPort::SerialPortError serialPortError)
{
if (serialPortError == QSerialPort::ReadError) {
QString fileName = "Errors.txt";
QFile mFile(fileName);
if(!mFile.open(QFile::Append | QFile::Text)){
qDebug() << "Could not open file for writing";
}
QTextStream out(&mFile);
out << "Serial Error " << (m_serialport->errorString()) <<endl;
mFile.close();
qDebug() <<"Serialport Error" <<(m_serialport->errorString());
}
}
I am creating a script in QT for reading the format packages (AA), (BB), etc from serial port. I open the serial port, but when I go to check inside the QByteArray values, comes back that I could not read any value.
This is my code
...
QSerialPort *serialPort = new QSerialPort();
serialPort->setPortName("ttyUSB0");
serialPort->setParity(QSerialPort::NoParity);
serialPort->setBaudRate(QSerialPort::Baud9600, QSerialPort::AllDirections);
serialPort->setStopBits(QSerialPort::OneStop);
serialPort->setFlowControl(QSerialPort::NoFlowControl);
serialPort->open(QIODevice::ReadOnly);
if (serialPort->isOpen()) {
qDebug() << "Serial port is open...";
QByteArray datas = serialPort->readAll();
if (datas.size() == 0) {
qDebug() << "Arrived data: 0";
} else {
for (int i = 0; i < datas.size(); i++){
if (datas.at(i)) {
qDebug() << datas[i];
}
}
}
} else {
qDebug() << "OPEN ERROR: " << serialPort->errorString();
}
serialPort->close();
qDebug() << "...serial port is closed!";
return 0;
...
You called readAll() immediately after open(). It probably took the computer a few nanoseconds to get from one to the other.
At 9600 baud, each byte of data takes slightly more than one millisecond to transfer. It would be absolutely impossible for any data to have arrived in that short an interval, so that's why you got no data.
Serial ports don't begin buffering incoming data until you open them (how could they, what baud rate and other settings would be used for receiving and buffering when no program has the port open?)
Use either a blocking read function of some sort (such as readLine()) or an event loop that reacts to data when it arrives.