-
Notifications
You must be signed in to change notification settings - Fork 7.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix for negative temp in Eddystone TLM; solving #7618 #7791
Conversation
libraries/BLE/src/BLEEddystoneTLM.h
Outdated
@@ -41,7 +41,7 @@ class BLEEddystoneTLM { | |||
uint8_t frameType; | |||
uint8_t version; | |||
uint16_t volt; | |||
uint16_t temp; | |||
int16_t temp; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a packed C struct with raw data from BLE advertising package.
Temperature is a bigendian data in fixed point notation 8.8.
Better leave it like in the original code as uint16_t
Please revert it.
@@ -132,7 +132,7 @@ void BLEEddystoneTLM::setVolt(uint16_t volt) { | |||
} // setVolt | |||
|
|||
void BLEEddystoneTLM::setTemp(float temp) { | |||
m_eddystoneData.temp = (uint16_t)temp; | |||
m_eddystoneData.temp = (int16_t)temp; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the C struct with raw data.
m_eddystoneData.temp
shall be a uint16_t
BUT.... this data shall be 2 bigendian bytes in Fixed Point Notation 8.8 that represents the value of the float...
Therefore, it is necessary to calculate it instead of just casting a single precision float (32 bits) to a uint16_t
.
The original code is completely wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we will leave the data model in its original type casting, it is now necessary to cast m_eddystoneData.temp
to int16_t
in the float BLEEddystoneTLM::getTemp()
function.
It is always better to leave the data processing logic in the API code that returns/gets and sets the information.
float BLEEddystoneTLM::getTemp() {
return ((int16_t)ENDIAN_CHANGE_U16(m_eddystoneData.temp)) / 256.0f;
} // getTemp
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is completely wrong as well.
It shall be the 2 bytes BigEndian representation of a 8.8 fixed point notation of the 23.00 float number...
Running that original code line, m_eddystoneData.temp
will value zero.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also needs to be casted to int16_t
after having its endianness solved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Final suggestion:
Create a static BLEEddystoneTLM::float2temp(float fTemp)
or just a macro to convert a float
to BigEndian 2 bytes in 8.8 fixed point notation. It will be used to fix the code.
@@ -50,7 +50,7 @@ void setBeacon() | |||
uint16_t volt = random(2800, 3700); // 3300mV = 3.3V | |||
float tempFloat = random(2000, 3100) / 100.0f; | |||
Serial.printf("Random temperature is %.2fC\n", tempFloat); | |||
int temp = (int)(tempFloat * 256); //(uint16_t)((float)23.00); | |||
int16_t temp = (int16_t)(tempFloat * 256); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
float
can't be converted to uint16_t
in this way...
float
has 4 bytes and it uses IEEE-754 single precision representation.
https://www.c-programming-simple-steps.com/c-float.html
The original code is correct. Please revert the change.
https://en.wikipedia.org/wiki/Fixed-point_arithmetic
The float
temperature is converted into 8.8 fixed point notation and then inverted to become a bigendian data in order to be placed in the Eddystone Beacon advertising information, as done in the sketch.
@@ -115,7 +115,7 @@ class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks | |||
foundEddyURL.setData(eddyContent); | |||
Serial.printf("Reported battery voltage: %dmV\n", foundEddyURL.getVolt()); | |||
Serial.printf("Reported temperature from TLM class: %.2fC\n", (double)foundEddyURL.getTemp()); | |||
int temp = (int)payLoad[16] + (int)(payLoad[15] << 8); | |||
int16_t temp = (int16_t)payLoad[16] + (int16_t)(payLoad[15] << 8); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is OK. Another way to do the same:
int16_t temp = (payLoad[16] + (payLoad[15] << 8);
We could improve it by testing if the beacon really supports temperature measuring :
// BIG ENDIAN payload in signed 8.8 fixed-point notation. Unit is Celsius.
int16_t temp_payload = payLoad[16] + (payLoad[15] << 8);
if (payLoad[15] == 0x80 && payLoad[16] == 0) {
Serial.printf("This device can't measure temperatures.\n");
} else {
float calcTemp = temp_payload / 256.0f;
Serial.printf("Reported temperature from data: %.2fC\n", calcTemp);
}
This reverts commit 1f3a941.
2 Macros EDDYSTONE_TEMP_U16_TO_FLOAT(tempU16) - takes the TLM BigEndian 8.8 fixed point representation and returns its float value EDDYSTONE_TEMP_FLOAT_TO_U16(tempFloat) - takes a float (temperature) and returns its BigEndian 8.8 fixed point representation
@SuGlider PTAL |
I have tested the original version of |
For the screenshot above this is the Serial output:
When I click on "RAW", I can see the Advertsing block of data which has such information, including the Temperature as |
I'll do a full review of your PR by Wednesday. |
libraries/BLE/src/BLEEddystoneTLM.h
Outdated
uint8_t getVersion(); | ||
uint16_t getVolt(); | ||
uint16_t getTemp(); | ||
float getFloatTemp(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the Original code there was just
float getTemp();
Now this has changed to float getFloatTemp();
and uint16_t getTemp();
This is a breaking change...
I don't think that it is a good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The suggestion here is to keep the original API float getTemp();
and use the MACROs for converting temperature types whenever necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be the final code:
BLEEddystoneTLM();
std::string getData();
BLEUUID getUUID();
uint8_t getVersion();
uint16_t getVolt();
float getTemp();
float BLEEddystoneTLM::getTemp() { | ||
return ENDIAN_CHANGE_U16(m_eddystoneData.temp) / 256.0f; | ||
uint16_t BLEEddystoneTLM::getTemp() { | ||
return ENDIAN_CHANGE_U16(m_eddystoneData.temp); | ||
} // getTemp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As said, this is a breaking change. I think it should stay the same:
float BLEEddystoneTLM::getTemp()
It should just use the new MACRO to convert u16
to float
, considering the signal.
EDDYSTONE_TEMP_U16_TO_FLOAT(tempU16)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion is to eliminate uint16_t BLEEddystoneTLM::getTemp()
and just keep original float BLEEddystoneTLM::getTemp()
that converts U16 BigEndian to Float (return value) using the MACRO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be the final code:
float BLEEddystoneTLM::getTemp() {
return EDDYSTONE_TEMP_U16_TO_FLOAT(m_eddystoneData.temp);
} // getTemp
float BLEEddystoneTLM::getFloatTemp() { | ||
return EDDYSTONE_TEMP_U16_TO_FLOAT(ENDIAN_CHANGE_U16(m_eddystoneData.temp)); | ||
} // getFloatTemp | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As said, this is a breaking change. I think it should stay the same:
float BLEEddystoneTLM::getTemp()
It should just use the new MACRO to convert u16 to float, considering the signal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m_eddystoneData.temp
is in Bigendian 8.8 fixed point (16 bits) and EDDYSTONE_TEMP_U16_TO_FLOAT()
already does all the job.
No need to use ENDIAN_CHANGE_U16()
here.
Please eliminate this method because it causes a breaking change to the API.
@@ -99,30 +99,17 @@ class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks | |||
Serial.printf("TX power %d\n", foundEddyURL.getPower()); | |||
Serial.println("\n"); | |||
} | |||
else if (payLoad[11] == 0x20) | |||
if (advertisedDevice.getPayloadLength() >= 22 && payLoad[22] == 0x20) | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure that this is right? [11] --> [22]? All fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. I checked it with nRF APP and it is correct. The position is 22.
//eddystoneTLM.setData(TLMFrame); | ||
eddystoneTLM.setData(std::string((char*)payLoad+22, advertisedDevice.getPayloadLength() - 22)); | ||
Serial.printf("Reported battery voltage: %dmV\n", eddystoneTLM.getVolt()); | ||
Serial.printf("Reported temperature: %.2f°C (raw data=0x%04X)\n", eddystoneTLM.getFloatTemp(), eddystoneTLM.getTemp()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here it may need a change back using the MACROs...
Please see the comentaries I made in BLEEddystoneTLM.h/.cpp
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As suggested in BLEEddystoneTLM.h/.cpp
, eddystoneTLM.getTemp()
should be kept as in the original API, returning a float
.
IF necessary, you can use EDDYSTONE_TEMP_FLOAT_TO_U16()
to get the raw data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, I'd say that the less we change the API and the original code, the better.
Changing the API will cause a breaking change, which is not desirable.
libraries/BLE/src/BLEAdvertising.cpp
Outdated
* | Field || Len | Type | UUID | EddyStone Frame | | ||
* | Offset || 0 | 1 | 2 | 4 | | ||
* | Len || 1 | 1 | 2 | up to 20 | | ||
* | Data || ?? | ?? | 0xAA | 0xFE | ??? | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a generic BLE advertising API.
Does this commentary relate to this?
float tempFloat = random(-3000, 3000) / 100.0f; | ||
Serial.printf("Random temperature is %.2f°C\n", tempFloat); | ||
int temp = EDDYSTONE_TEMP_FLOAT_TO_U16(tempFloat); | ||
Serial.printf("Converted to 8.8 format 0x%04X\n", temp); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested the proposed new code and it doesn't work.
The problem with the new code is that EDDYSTONE_TEMP_FLOAT_TO_U16()
returns a big endian uint16_t
and the original code was using temp
as little endian
which was turned into big endian
with the lines:
beacon_data[4] = (temp >> 8); // Beacon temperature
beacon_data[5] = (temp & 0xFF); //
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please change it to:
int temp = (int)(tempFloat * 256);
Serial.printf("Converted to 8.8 format %0X%0X\n", (temp >> 8) & 0xFF, (temp & 0xFF));
{ | ||
Serial.println("Found an EddystoneTLM beacon!"); | ||
BLEEddystoneTLM foundEddyURL = BLEEddystoneTLM(); | ||
std::string eddyContent((char *)&payLoad[11]); // incomplete EddystoneURL struct! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just changed Line 106 to:
std::string eddyContent((char *)&payLoad[22]); // incomplete EddystoneURL struct!
|
||
for (int idx = 0; idx < 14; idx++) | ||
{ | ||
eddyContent[idx] = payLoad[idx + 11]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just changed Line 112 to:
eddyContent[idx] = payLoad[idx + 22];
foundEddyURL.setData(eddyContent); | ||
Serial.printf("Reported battery voltage: %dmV\n", foundEddyURL.getVolt()); | ||
Serial.printf("Reported temperature from TLM class: %.2fC\n", (double)foundEddyURL.getTemp()); | ||
int temp = (int)payLoad[16] + (int)(payLoad[15] << 8); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just changed Line 118 to:
int16_t temp = payLoad[22+5] + (payLoad[22+4] << 8);
@@ -99,30 +99,17 @@ class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks | |||
Serial.printf("TX power %d\n", foundEddyURL.getPower()); | |||
Serial.println("\n"); | |||
} | |||
else if (payLoad[11] == 0x20) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just changed Line 102 to:
else if (payLoad[22] == 0x20)
in order to make it work.
Serial.println("\n"); | ||
Serial.print(foundEddyURL.toString().c_str()); | ||
Serial.println("\n"); | ||
Serial.printf("Found an EddystoneTLM beacon! payload length = %d B; minus 22 = %d\n", advertisedDevice.getPayloadLength(), advertisedDevice.getPayloadLength() - 22); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess that printing the payload length is a left over of debugging, right?
Serial.printf("Reported time since last reboot: %ds\n", foundEddyURL.getTime()); | ||
Serial.println("\n"); | ||
Serial.print(foundEddyURL.toString().c_str()); | ||
Serial.println("\n"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason for removing Lines 123 to 125?
A last VERY important thing... why the original code looks at TLM data in the position [11] and not in the position [22]? So the position 22 works for the Therefore, this postion shall be calculated based on the interpretation the this frame, looking for the field TYPE 0x16 with LEN = 17 and 0x20 in the 3rd byte of the VALUE. More information can be found in these links: |
In that case i can fix this in the other PR. But what to do now when we want it ASAP? Just roll back to original? |
I've added the necessary commit to make it find the Eddystone data dynamicaly. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@PilnyTomas @me-no-dev @VojtechBartoska
I have tested Eddystone TLM and URL.
Everything is working fine.
Ready to merge.
iBeacons use big endian BLE fields.
This fix makes the BLE_iBeacon.ino to work correctly with the BLE_Beacon_Scanner.ino example
This PR changes the conversion of 8.8 to float and vise versa to accommodate negative values.
Fix #7618
Fix #7858