Skip to content

Conversation

@benjamingray123
Copy link

@benjamingray123 benjamingray123 commented Nov 7, 2025

Following my post in the forum: https://www.traccar.org/forums/topic/automotive-relay-style-cj7320-cj730-lk720-not-displaying-vehicle-battery-voltage/ (please see example text (comma separated) and hex message evidence in my posts)

and the following protocol documentation screenshot (actual spreadsheet available here - https://www.traccar.org/protocol/5013-h02/ICARGPSProtocol.xlsx)

image

My code a dds support for extracting external/vehicle battery voltage from H02 protocol binary messages with extended format (47 bytes). Fixes a frame decoder issue where 47-byte messages were truncated to 45 bytes, preventing access to voltage data. The frame decoder now correctly detects and reads extended format messages.

H02 GPS trackers report external power supply voltage in extended format heartbeat messages (approx 1 in 100 messages).

Changes

  • H02ProtocolDecoder.java: Extract voltage from bytes -3 and -2 (before record number byte)
  • H02FrameDecoder.java: Detect and read 47-byte extended format messages (previously truncated to 45 bytes)
  • H02ProtocolDecoderTest.java: Add unit test for voltage extraction

Configuration

  • debug.xml: Add power to logger.attributes for visibility in logs

Implementation Strategy

  • Dynamic positioning: Skips readableBytes() - 3 to reach voltage bytes
  • Handles variable message lengths (32, 45, or 47 bytes)
  • Backward compatible: Standard messages unaffected
  • Only sets attribute if value in valid range

Testing: (Traccar 6.10.0)

  • Unit tests pass, and I added one in H02ProtocolDecoderTest.java to test the new functionality
  • Tested with tools/hex.sh to send a hex message show the power attribute being logged correctly
  • Tested with real h02 data - it works on my deployment (CJ720 chinese GPS device)

Configuration
Users can persist the power attribute across messages using existing CopyAttributesHandler:

<entry key='processing.copyAttributes.enable'>true</entry>
<entry key='processing.copyAttributes'>power</entry>

Since h02 voltage appears only in ~1/100 messages (heartbeat messages only) so this enables the power attribute to continue to be displayed even between heartbeat messages.

Extract external/vehicle battery voltage from H02 binary messages with
extended format (47 bytes). Voltage displayed as 'power' attribute.

Changes:
- H02ProtocolDecoder: Read voltage from bytes -3,-2 (before record byte)
- H02FrameDecoder: Detect and read 47-byte extended format messages
- H02ProtocolDecoderTest: Add test case for 11.8V voltage extraction
- debug.xml: Include 'power' in logger.attributes for visibility

Technical details:
- Voltage encoded as 2-byte unsigned integer (big-endian), in tenths of volts
- Range validation: 0.1V - 599.9V to filter invalid readings
- Backward compatible: Standard 32/45-byte messages unaffected
- Position-based reading: Skips to last 3 bytes regardless of extended data length

Testing:
- Unit tests pass (H02ProtocolDecoderTest, H02FrameDecoderTest)
- Integration tested with real device messages
- Test message: 0x0076 = 118 decimal = 11.8V

Fixes frame decoder truncation issue that prevented reading extended format.
@benjamingray123 benjamingray123 changed the title Add external voltage extraction for H02 protocol Add external voltage extraction for h02 protocol; fix H02FrameDecoder truncating message length Nov 7, 2025
@benjamingray123 benjamingray123 marked this pull request as ready for review November 7, 2025 11:45
// Check if we have extended data beyond standard long message
int actualLength = messageLength;
if (messageLength == MESSAGE_LONG && buf.readableBytes() > MESSAGE_LONG) {
// Extended format may have 2 additional bytes for external voltage
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also nonsense

@benjamingray123
Copy link
Author

changes made per reviews - hope this is better @tananaev !

@tananaev
Copy link
Member

Unfortunately you have not resolved all the issues.

@benjamingray123
Copy link
Author

could you please advise which issues I have not resolved?

@tananaev
Copy link
Member

Unresolved them.

@benjamingray123 benjamingray123 force-pushed the feature/h02-external-voltage branch from 0a3dcc8 to b0577c4 Compare November 17, 2025 22:13
@benjamingray123
Copy link
Author

tried to clarify nonsense code with latest commit - hopefully this is clear now? If not please could you provide guidance on what exactly you would like changing and how? apologies java is not a language I have much experience in so i'm kind of winging this @tananaev

if (messageLength == MESSAGE_SHORT) {
return buf.readRetainedSlice(messageLength);
} else {
return buf.readRetainedSlice(Math.min(buf.readableBytes(), MESSAGE_LONG + 2));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is MESSAGE_LONG + 2?

@benjamingray123
Copy link
Author

MESSAGE_LONG + 2 (47 bytes) handles extended format messages that include 2 extra bytes for external voltage at the end. Standard messages are 45 bytes (MESSAGE_LONG), extended format adds voltage (2 bytes) + record number (1 byte) but decoder only needs voltage bytes, so frame includes up to 47 bytes when available.

@benjamingray123
Copy link
Author

the original H02 decoder only accounted for 45 byte long messages (MESSAGE_LONG) but it seems that the CJ720/730 & relay-style tracker variants) sometimes send heartbeat messages of length 47 bytes which contain the vehicle voltage

@tananaev
Copy link
Member

There are many devices that have different message length. That's why we have configurable messageLength. What you're doing is incorrect on many levels. First you're saying messageLength = MESSAGE_LONG; but then you're arbitrary adding 2 bytes. Secondly, you completely breaking configurable length.

@benjamingray123
Copy link
Author

You're absolutely right - I was breaking the configurable messageLength. I've reverted the H02FrameDecoder changes completely.

For devices sending 47-byte messages with voltage, users should configure: h02.protocol.messageLength=47

The protocol decoder change alone handles voltage extraction when extra bytes are present after the standard fields, regardless of whether the frame is 45 or 47 bytes.


if (buf.readableBytes() >= 3) {
buf.skipBytes(buf.readableBytes() - 3);
position.set(Position.KEY_POWER, buf.readUnsignedShort() / 10.0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you verified that it doesn't break any existing protocols?

Only 47-byte extended format messages have 18 bytes remaining after
status field. Standard 45-byte messages have 16 remaining. This ensures
voltage extraction only triggers for extended format, preventing false
readings from standard messages.

All existing H02 protocol tests verified passing.
@benjamingray123
Copy link
Author

Verified that it doesn't break any existing protocols ✓

All H02 protocol tests pass (16 binary + 90+ text format tests).

The fix: Changed from >= 3 to == 18 remaining bytes check. Only 47-byte extended format messages have exactly 18 bytes remaining after the status field. Standard 45-byte messages have 16 remaining, so voltage extraction only triggers for the correct format.

Test results showed various remaining byte counts across different message formats:

  • 18: Extended format with voltage (1 test) ✓ extracts voltage
  • 16: Standard 45-byte format (10+ tests) ✓ skips voltage
  • 3, 10, 17, 19: Other binary formats (5 tests) ✓ skips voltage
  • All text formats unaffected ✓

@tananaev
Copy link
Member

Which protocols have you verified?

@benjamingray123
Copy link
Author

i've run ./gradlew test testing the entire project (no failures)
and ./gradlew test --tests "*ProtocolDecoderTest" testing all the decoders of protocols supported by traccar

@tananaev
Copy link
Member

That is done automatically by CI. I'm talking about actually looking through other documents to make sure there are no conflicts.

@benjamingray123
Copy link
Author

I've searched through the protocols directory for analogous functionality / shared use of utility functions:

  1. Only H02 protocol uses readableBytes() == 18
  2. Similar protocols using readableBytes()
    • HuabaoProtocolDecoder: uses 20 bytes
    • Gt06ProtocolDecoder: uses 9/10/13 bytes
    • NoranProtocolDecoder: uses 48/57 bytes
  3. Many protocols use KEY_POWER - no conflicts, all have different message structures
  4. H02 family isolated - Frame decoder handles base messages (32/45 bytes), this adds optional 18-byte extension

Changes follow established patterns (like Gt06/Huabao) for handling optional trailing data.

My understanding is that my implementation is H02-specific with no cross-protocol conflicts.
Futhermore, my understanding is each protocol is isolated and has it's own 'pipeline' and thus changes to one should not affect other protocols by design?

@tananaev
Copy link
Member

I think you misunderstood something. I'm talking about H02 protocol documents:

https://www.traccar.org/protocols/

@benjamingray123 benjamingray123 force-pushed the feature/h02-external-voltage branch from e898557 to c40a4f9 Compare December 2, 2025 20:30
Tests verify voltage extraction does not break existing H02 formats:
- 45-byte binary (standard, no voltage)
- 47-byte binary (extended, with voltage)
- Text V1 format (common heartbeat)
- Text NBR format (LBS positioning)

Covers all major H02 protocol variants. All tests pass.
@benjamingray123 benjamingray123 force-pushed the feature/h02-external-voltage branch from c40a4f9 to 14635d9 Compare December 2, 2025 20:32
@benjamingray123
Copy link
Author

i've looked through the h02 variant documents here (https://www.traccar.org/protocols/) and have added tests which ensure all versions of h02 protocol will still work. there aren't any conflicts and my changes will work with all h02 variations.

the extra tests target the following:

  1. Standard 45-byte binary without voltage
  2. Extended 47-byte binary with voltage (2 tests with different voltages)
  3. Text format V1
  4. Text format LBS positioning

Position.KEY_POWER, 11.8);

verifyAttribute(decoder, binary(
"24720104244110373303112551337904060000794834000000fffff9ffff001b0a00000ee600ea0f0000000000f501"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this duplicated test

Comment on lines +26 to +31
verifyPosition(decoder, buffer(
"*HQ,7340014741,V1,085056,A,5133.7992,N,00007.9499,W,000.00,000,021225,BFFFFBFF,460,00,0,0,6#"),
position("2025-12-02 08:50:56.000", true, 51.56332, -0.13250));

verifyAttributes(decoder, buffer(
"*HQ,4109179024,NBR,103732,722,310,0,6,8106,32010,23,8101,22007,25,8106,12010,23,8106,22105,22,8101,22012,16,8106,42010,5,100217,FFFFFBFF,5#"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these two are completely unrelated to your changes


processStatus(position, buf.readUnsignedInt());

if (buf.readableBytes() == 18) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to check the full length instead.

changed skipBytes(15) to buf.writerIndex() -2 -1

Co-authored-by: Anton Tananaev <anton.tananaev@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants