Sending a valid NMEA0183 string

I am a real beginner, I have worked my way through several basic Arduino projects including one that uses the DHT11 temp/humidity sensor. It occurred to me that I could actually use this to send temp and humidity data to my astro PC. The only data strings that are acceptable are NMEA0183. This crude piece of code almost works:

void loop() {
humidity = HT.readHumidity();
tempC = HT.readTemperature();
tempF = HT.readTemperature(true);
dewPointC = (tempC)-((100-humidity)/5);
dewPointF = (dewPointC*9/5)+32;

Serial.print("$PXDR,P,96276.0,P,0,C,");
Serial.print(tempC);
Serial.print(",C,1,H,");
Serial.print(humidity);
Serial.print(",P,2,C,");
Serial.print(dewPointC);
Serial.println(",C,3,1.1*31");

But fails because the check sum (two hex digits after the *) is incorrect. If I compute the correct checksum all is well. I found some code that correctly computes the checksum, but I have no idea how to:

  1. Assemble the complete string,
  2. Pass it to the checksum code,
  3. Add the hex checksum digits and send it via the serial monitor.

I have a feeling I am slightly out of my depth!
Geoff

It would have helped if you included all the sketch but the below may point you in the right direction. You will need to install the PString library to try it.

#include <PString.h>
#include <DHT.h>

#define DHTPIN 2
#define DHTTYPE DHT22
DHT HT(DHTPIN, DHTTYPE);

char buffer[140];


void setup()
{
  Serial.begin(115200);
  HT.begin();
  
}

void loop() 
{
  float humidity = HT.readHumidity();
  float tempC = HT.readTemperature();
  float tempF = HT.readTemperature(true);
  float dewPointC = (tempC)-((100-humidity)/5);
  float dewPointF = (dewPointC*9/5)+32;
  
  PString str(buffer, sizeof(buffer));
  
  str.print("$PXDR,P,96276.0,P,0,C,");
  str.print(tempC);
  str.print(",C,1,H,");
  str.print(humidity);
  str.print(",P,2,C,");
  str.print(dewPointC);
  str.print(",C,3,1.1*");
  
  int check = checksum(buffer);
  
  str.println(check);
  
  Serial.println(buffer);
}

int checksum(const char *s) 
{
  int c = 0;
  
  while (*s)
  c ^= *s++;
  
  return c;
}

Something made me look at this closerly.

You'll also see that the checksum is an XOR of all bytes between the $ and the * , and the checksum, in hexadecimal, follows the * in ASCII format.

That I found googling just now.

And

The checksum is the bitwise exclusive OR of ASCII codes of all characters between the ‘$’ and ‘*’ , not inclusive.

Is your example code from a working system?

It seems to include the excluded chars, and does not appear to attach the checksum in hexadecimal.

a7

Maybe this would work:

  String buffer = "$PXDR,P,96276.0,P,0,C,";
  buffer += String(tempC);
  buffer += ",C,1,H,";
  buffer += String(humidity);
  buffer += ",P,2,C,";
  buffer += String(dewPointC);
  buffer += ",C,3,1.1*";

  byte checksum = 0;
  // Skip the '$' and '*'
  for (int i=1; i < buffer.length()-1; i++)
    checksum ^= buffer[i];

  Serial.print(buffer);
  if (checksum < 0x10)
    Serial.print('0'); // Leading zero
  Serial.println(checksum, HEX);

Whatcha got against PString?

void loop() 
{
  float humidity = HT.readHumidity();
  float tempC = HT.readTemperature();
  float tempF = HT.readTemperature(true);
  float dewPointC = (tempC)-((100-humidity)/5);
  float dewPointF = (dewPointC*9/5)+32;
  
  PString str(buffer, sizeof(buffer));
  
  str.print("PXDR,P,96276.0,P,0,C,");
  str.print(tempC);
  str.print(",C,1,H,");
  str.print(humidity);
  str.print(",P,2,C,");
  str.print(dewPointC);
  str.print(",C,3,1.1");
  
  int check = checksum(buffer);
   
  Serial.print("$");
  Serial.print(buffer);
  Serial.print("*");

  if (checksum < 0x10)
    Serial.print("0"); // Leading zero
  Serial.println(checksum, HEX);
}

a7

Thank you, the concept of a buffer is new for me - I will investigate.

Yes all I did was look at the manual of the device I am trying to emulate and copied a sample string, when I sent that it worked, but of course when I included my "real" values the checksum was wrong.

Thank you, as I said the concept of a buffer is new for me - I am beginning to see the light!

Understood. I should have made clear that I was asking @Riva if he had looked before leaping - my use of NMEA was from some time ago, never mind precisely how long, and I didna recall the exclusion characters but did manage to be kinda confident that the checksum, such as it is, is two hex chars, a readable representation of the value.

Have you made any of the programs we threw out function?

I like the idea of hijacking NMEA even if it is a bit of bother making up the sentences.

Another approach amounting to the same thing would use sprintf or one of its modern variants; Both String and PString methods are fine and somewhat easier to see that you are doing what you want and need to.

I don't know PStrings, but at a glance it looks like they do not carry the baggage of Strings.

If you learn more at some point sprintf may be useful, as is the entire old school set of functions for dealing with character arrays, known as buffers in some use cases.

a7

I am working my way through all the (new to me) stuff. I don't like just copying chunks of code, I want to understand a bit better. Given my advanced years this might take a while. I understand the overall solution, the devil is in the details...

Haha, welcome to programming. Good on you for not wanting to be given a fish, so to speak.

Just read through all the solutions like you know what you doing.

And TBC, we all named the array buffer, but it's just a character array and could have been called anything, buffer is commonly used where

char arrayForABunchOfCharsSoWeCanWorkWithThemAndPassThemAroundTogether[256];

might have made its purpose more obvious.

For this kind of use, it has to have enough room for the largest sentence you wanna write plus one, the extra for a '\0' character at the end which signifies the end of meaningful data in the array at any time.

The memory for the array comes out of your total budget variously and can be a limit on small machines.

a7

OK Well its working! I have a friend who I will consult over coffee to explain the details but as I said I kinda get the idea. Here's the full sketch, I also have an LCD display. Further development includes another temperature sensor on the camera case, and a relay to disable the camera cooling function if the case temperature gets too close to the dew point (ask me how I know this is bad news)
Thanks again to everyone.
Geoff

#include "DHT.h"
#define Type DHT11
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4); // set the LCD address to 0x27 for a 16 chars and 2 line display
int sensePin = 2;
DHT HT(sensePin, Type);
int humidity;
int tempC;
float tempF;
int setTime = 500;
int dt = 10000;
float dewPointC;
float dewPointF;

void setup() {
// put your setup code here, to run once:
Serial.begin(38400);
HT.begin();
delay(setTime);
lcd.init(); //initialize the lcd
lcd.backlight(); //open the backlight
}

void loop() {
humidity = HT.readHumidity();
tempC = HT.readTemperature();
tempF = HT.readTemperature(true);
dewPointC = (tempC) - ((100 - humidity) / 5);
dewPointF = (dewPointC * 9 / 5) + 32;

String buffer = "$PXDR,P,96276.0,P,0,C,";
buffer += String(tempC);
buffer += ",C,1,H,";
buffer += String(humidity);
buffer += ",P,2,C,";
buffer += String(dewPointC);
buffer += ",C,3,1.1*";

byte checksum = 0;
// Skip the '$' and '*'
for (int i = 1; i < buffer.length() - 1; i++)
checksum ^= buffer[i];

Serial.print(buffer);
if (checksum < 0x10)
Serial.print('0'); // Leading zero
Serial.println(checksum, HEX);

//Serial.print("$PXDR,P,96276.0,P,0,C,");
//Serial.print(tempC);
//Serial.print(",C,1,H,");
//Serial.print(humidity);
//Serial.print(",P,2,C,");
//Serial.print(dewPointC);
//Serial.println(",C,3,1.1*31");

lcd.setCursor(1, 0);
lcd.print("Humidity: ");
lcd.print(humidity);
lcd.print("%");
lcd.setCursor(0, 1);
lcd.print("Dp:");
lcd.print (dewPointF);
lcd.print("f");
lcd.print(" T:");
lcd.print(tempF);
lcd.print("f");

delay(dt);
}

I just copied the checksum code from the Wiki entry on NMEA0183, did not bother to read the article though.