Yes I have set the baudrate to 9600, data bit 8, stop bit 1, parity none.
Then on the scale I have set the baud rate also to 9600, com usage to connect to computer or auxilary display mode, com output format to 22 bytes fo CA and output mode to stream mode.
are you sure it is outputting ASCII text not some binary format?
Thanks everyone, so I dismantled the scales that I used to look at.
it turns out that there is an error in the pin installation on the DB9 connector, so the GND pin on the scale is soldered to pin 3 on the DB9 connector, so you have to change the pin place. I use this code
void setup() {
Serial.begin(9600);
Serial2.begin(9600);
}
void loop() {
while (Serial2.available()) {
String receivedData = Serial2.readStringUntil('\n'); // Read until newline character
if (receivedData.length() > 0) {
Serial.print("Raw Data: ");
Serial.println(receivedData);
}
}
}
then receives raw data from the digital scale displayed on the serial monitor like this
I'm still trying to do data parsing so that I can retrieve only the weight of the scales.
All data is in a fixed position, so parsing would be easy. No need to search for the commas, but you could do a check to verify they are all in the correct location as a method of detecting errors.
yes I know this I'm trying to make data parsing but always error hahaha
I tried parsing the data using this code
void setup() {
Serial.begin(9600);
Serial2.begin(9600);
}
void loop() {
if (Serial2.available()) {
String receivedData = Serial2.readStringUntil('\n'); // Read until newline character
if (receivedData.length() > 0) {
Serial.print("Raw Data :");
Serial.println(receivedData);
//Parse raw data
String dataParse = receivedData.substring(9, receivedData.length() - 3);
Serial.println("Parsing :" + dataParse);
//removes spaces in the parsed data
String finalData = removeSpaces(dataParse);
Serial.println("Final Data :" + finalData);
}
}
}
String removeSpaces(String input) {
String output = ""; // String kosong untuk hasil akhir
for (int i = 0; i < input.length(); i++) {
if (input[i] != ' ') { // Cek jika karakter bukan spasi
output += input[i]; // Tambahkan ke string output
}
}
return output;
}
and this is the output
The results that are issued are what I want, but is my coding efficient? I tried to make a coding to take the weight number of the scale and it always errors and I tried to make a coding like the one above.
The use of Sting (capital S) is never efficient
You will have to look at the data as posted in post #24. You seem to have non-printable characters in the 3rd field (device ID and lamp status byte); it will be difficult to parse that and there is the risk that their value is a '\n' and it will break the reading.
yes, I have trouble parsing because there is data that cannot be printed on the serial monitor and sometimes makes my parsing error. then if not using String (capital S), what else to use?
Your first improvent will be to change if (receivedData.length() > 0)
to if (receivedData.length() == 22)
. The 22 assumes that readStringUntil()
under normal circumstances gives you 22 bytes; you might have to adjust that a little if the '\n' is stripped off by readStringUntil()
.
The more efficient way is to use readBytesUntil()
and a 22 byte buffer. But it will not solve the issue of corrupt packets.
How often do you get a bunch of 22 bytes? Continuously? Or on change of the weight / something else in the scale? Or is there a pause in between the 22 bytes (I can see some variable pauses in your screenshot)? Or can you get it on demand by sending a certain command to the scale?
According to the manual, the device ID can be set to any number from 0 - 99, so that could be problematic with parsing, especially it if were 0. The status byte has the MSB set to 1, so should not conflict with any ASCII characters.
The weight field starts at the 10th character, and is always eight characters long. The following character (18th character) appears to always be a space, followed by the two letters KG or LB. Easy enough to copy those eight characters out to another string, or convert to a float.
The device ID is particularly problematic, unless you specifically set it to something other than 0 or 10 (10 being the ASCII code for the newline character). If you want to allow for any possible device ID, then you cannot simply watch for newline to indicate the end of the received data, instead watch for the carriage return followed by a newline.
I set the digital scale to send data when it is in a stable state while weighing so it is not the stream mode that continuously sends the weighing data.
You can try the following code for the receiving of the data. It makes use of a byte array of 22 bytes (for 22 byte CAS).
Place the below at the top of your code.
// store data from scale
uint8_t data[22];
// end marker in message from scale
const uint8_t endMarker = '\n';
// scale timeout duration
const uint32_t scaleTimeout = 100;
// separator for the parser
const uint8_t separator = ',';
Notes:
- The separator is not used for the receiving.
- I use
uint8_t
instead ofbyte
anduint32_t
instead ofunsigned long
.
The receive function implements reading of received data; it basically reads 22 bytes (can be anything) and expects the last byte to be a '\n' (<LF>
). It does not check for the '\r' (<CR>
). A timeout mechanism is implemented so the code will not wait forever if something goes wrong.
If there are no problems, the function returns true; it will return false if there are problems or if no complete message from the scale is available.
/*
Receive data from scale
Returns:
true if a valid 22 byte message is received, else false
Comment:
valid message will be in data
*/
bool receiveFromScale()
{
// return value
bool rb = false;
// index in 22 byte array
static uint8_t idx;
// timeout timer
static uint32_t lastReceivedTime;
// receive in progress indicator
static bool receiveInProgress = false;
...
...
}
Note:
Static variables are variables that are only known inside the function but don't loose their value when the function ends.
After the declarion of the variables, the code checks for a timeout; before setup() it is set to 100 and you might have to play with it.
// if a receive is in progress
if (receiveInProgress == true)
{
// and there is a timeout
if (millis() - lastReceivedTime >= scaleTimeout)
{
idx = 0;
receiveInProgress = false;
Serial.println();
Serial.println(F("Scale timeout"));
}
}
...
...
If a timeout occurs, the index is reset, the receive-in-progress indicator is cleared and a message is printed.
Next a while-loop is used to read available data; note that serial communication is slow and you might not get all data in one go. So you will have to call the receiveFromScale repeatedly from loop().
while (Serial1.available())
{
// reset timeout
lastReceivedTime = millis();
receiveInProgress = true;
// get the byte and store in the array
data[idx] = Serial1.read();
// print for debugging
if (data[idx] < 0x10)
{
Serial.print(F("0"));
}
Serial.print(data[idx], HEX);
Serial.print(F(" "));
...
...
}
If a byte is received, the first thing is to remember when it was received so the timeout mechanism can work. Next an indicator is set to remember that a receive of data from the scale is in progress.
Next the byte is read from the serial buffer and stored in the data array at the location indicated by idx
. In case you don't know, valid array indices go from 0 to one lower than the size (so data[0]
to data[21]
). And the received byte is printed in hexadecimal.
Next there is the check for the terminating '\n' at position 21 in the array
// check for end marker at alst position in array
if (data[idx] == endMarker && idx == sizeof(data) - 1)
{
// next time start filling data array from the beginning
idx = 0;
receiveInProgress = false;
Serial.println();
// indicate to caller that receive is complete
rb = true;
}
...
...
sizeof()
gives the size of the array in bytes; so sizeof(data) - 1
equals 21 which is the last index.
If we got the end marker at position 21, we reset idx so the next time the array will be filled from the first element again, we indicate that we're no longer receiving, print an empty line and set rb
to true which will be returned at the end of the function.
If the received character was not the end marker or we are not yet at the end of the array
else
{
// if there is still space in the array
if (idx < sizeof(data) -1)
{
// update index
idx++;
}
else
{
idx = 0;
receiveInProgress = false;
Serial.println();
Serial.println(F("Buffer overflow while receiving from scale"));
}
}
We check if we are at the end of the array; if we are still safe to add received bytes to the array, we will update the index. Else we reset the index and the receiveInProgress indicator and print an error.
And lastly we return the status of the function
...
...
}
// indicate result to caller
return rb;
}
As said, you have to call this repeatedly from loop(). Your complete sketch for the receiving will now be
// store data from scale
uint8_t data[22];
// end marker in message from scale
const uint8_t endMarker = '\n';
// scale timeout duration
const uint32_t scaleTimeout = 100;
// separator for the parser
const uint8_t separator = ',';
void setup()
{
Serial.begin(115200);
Serial1.begin(9600);
Serial.println(F("Mega receiver"));
}
void loop()
{
// if a complete message was received from the scale
if (receiveFromScale() == true)
{
Serial.print(F("Message from scale received >> "));
for (uint8_t cnt = 0; cnt < sizeof(data); cnt++)
{
if (data[cnt] < 0x10)
{
Serial.print(F("0"));
}
Serial.print(data[cnt], HEX);
Serial.print(F(" "));
}
Serial.println();
}
}
Note that all data is printed in hex; you will have to lookup the ASCII values at e.g. https://www.asciitable.com/.
Next you can write a parser; the below is a crude way
/*
parse data
Returns:
false if there is an error, else true
*/
bool parseData()
{
// space to store largest array (8 bytes plus terminating '\0')
char parsedValue[9];
// message should end with '\r' followed by '\n'; '\n' is handled by the receive function
if (data[sizeof(data) - 2] != '\r')
{
Serial.println(F("No <cr>"));
return false;
}
// first two bytes
memcpy(parsedValue, data, 2);
// add string terminator
parsedValue[2] = '\0';
Serial.print(F("Field 1: "));
Serial.println(parsedValue);
// next byte should be a separator (comma)
if (data[2] != separator)
{
Serial.println(F("Missing separator"));
return false;
}
// second two bytes
memcpy(parsedValue, &data[3], 2);
// add string terminator
parsedValue[2] = '\0';
Serial.print(F("Field 2: "));
Serial.println(parsedValue);
// next byte should be a separator (comma)
if (data[5] != separator)
{
Serial.println(F("Missing separator"));
return false;
}
// third field is binary
Serial.print(F("Device ID = "));
if (data[6] < 0x10)
{
Serial.print(F("0"));
}
Serial.println(data[6], HEX);
Serial.print(F("Status byte = "));
if (data[7] < 0x10)
{
Serial.print(F("0"));
}
Serial.println(data[7], HEX);
// next byte should be a separator (comma)
if (data[8] != separator)
{
Serial.println(F("Missing separator"));
return false;
}
// fourth field is data of interest (8 bytes)
memcpy(parsedValue, &data[9], 8);
// add string terminator
parsedValue[9] = '\0';
Serial.print(F("Field 4: "));
Serial.println(parsedValue);
// next byte should be empty (whatever that means)
Serial.print(F("data[17] = "));
if (data[17] < 0x10)
{
Serial.print(F("0"));
}
Serial.println(data[17], HEX);
// last two bytes before <cr><lf> are units
memcpy(parsedValue, &data[18], 2);
// add string terminator
parsedValue[2] = '\0';
Serial.print(F("Field 5 (units): "));
Serial.println(parsedValue);
return true;
}
It basically takes the N bytes (2 or 8) from the relevant position in the data array and copies them to a character array called parsedValue which is 9 bytes in size; this allows for the largest text (the 8 data bytes from the spec) plus the terminating '\0'.
The function returns false if the data is in some form corrupt, else true.
The first test is a check if there was a '\r' (<CR>
) just before the end. The function prints an error and returns false if that is not the case, else it will continue.
Next it will copy the first two bytes to the parsedValue array and makes it a valid c-string by adding a '\0'. c-strings represent text and are character arrays where the end of the text is indicated by a '\0'. Next the parsedValue is printed.
After that the code checks if the next character is a separator (specified in the beginning of the code as a comma); if not, the function will print an error and next return false.
This basically repeats; the device ID and the status byte are printed in hex.
The &data[N]
that you see in the above is a pointer to the Nth element of the data array; that way you can simply copy a few elements from the data array to the parsedValue array.
loop() can now incorporate the parsing as shown below
void loop()
{
// if a complete message was received from the scale
if (receiveFromScale() == true)
{
Serial.print(F("Message from scale received >> "));
for (uint8_t cnt = 0; cnt < sizeof(data); cnt++)
{
if (data[cnt] < 0x10)
{
Serial.print(F("0"));
}
Serial.print(data[cnt], HEX);
Serial.print(F(" "));
}
Serial.println();
if(parseData() == false)
{
Serial.println(F("Error"));
}
}
}
I've done some basic (but not compehensive) tests using a Leonardo to simulate your scale and forcing timeout errors and inserting '\n' in random places in the data.
Notes:
You can create global character arrays for the fields that you are interested in; make sure that the are big enough to hold the characters like "ST" plus the terminating '\0'. You e.g. create two arrays
char value[9]; // 8 characters for value plus '\0'
char units[3]; // 2 characters for units plus '\0'
And use strcpy to copy parsedValue to those arrays.
This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.