Jimi VL103M Protocol Decoder

Professional Go library for decoding and encoding the Jimi VL103M GPS Tracker protocol (JM-VL03) manufactured by Shenzhen Concox Information Technology Co., Ltd.
Overview
This library provides complete support for the JM-VL03 protocol version 1.1.2, implementing all 17 packet types with proper handling of both 2G and 4G base station formats.
Key Features:
- Complete Protocol Support - All 17 packet types (Login, Location, Alarm, Heartbeat, etc.)
- 2G & 4G Variants - Automatic handling of legacy 2G and modern 4G formats
- ACC Status Correction - Properly handles ACC (ignition) status for all packet types
- Automatic CRC Validation - Built-in CRC-ITU checksum verification with optional skip
- TCP Stream Handling - Robust handling of concatenated and fragmented packets
- Bidirectional Communication - Both decoding (device to server) and encoding (server to device)
- Type-Safe API - Strongly typed value objects with comprehensive validation
- Zero External Dependencies - Pure Go standard library for production builds
- Production Ready - Comprehensive test coverage and optimized performance
Table of Contents
Installation
go get github.com/fcode09/jimi-vl103m
Requirements:
- Go 1.21 or higher
- No external dependencies for production use
Quick Start
Basic Packet Decoding
package main
import (
"encoding/hex"
"fmt"
"log"
"github.com/fcode09/jimi-vl103m/pkg/jimi"
"github.com/fcode09/jimi-vl103m/pkg/jimi/packet"
)
func main() {
// Create decoder with default options
decoder := jimi.NewDecoder()
// Example GPS location packet (from device)
hexData := "787822220F0C1D023305C9027AC8180C46586000140001CC00287D001F71000001000820860D0A"
data, _ := hex.DecodeString(hexData)
// Decode packet
pkt, err := decoder.Decode(data)
if err != nil {
log.Fatal(err)
}
// Type assertion to access specific packet fields
switch p := pkt.(type) {
case *packet.LocationPacket:
fmt.Printf("GPS Location:\n")
fmt.Printf(" Position: %.6f, %.6f\n", p.Latitude(), p.Longitude())
fmt.Printf(" Speed: %d km/h\n", p.Speed)
fmt.Printf(" ACC Status: %s\n", map[bool]string{true: "ON", false: "OFF"}[p.ACC])
fmt.Printf(" Satellites: %d\n", p.Satellites)
fmt.Printf(" Timestamp: %s\n", p.DateTime)
case *packet.HeartbeatPacket:
fmt.Printf("Heartbeat:\n")
fmt.Printf(" ACC: %v, GPS Tracking: %v, Armed: %v\n",
p.TerminalInfo.ACCOn(),
p.TerminalInfo.GPSTrackingEnabled(),
p.TerminalInfo.IsArmed())
fmt.Printf(" Battery: %s (%d%%)\n", p.VoltageLevel.String(), p.VoltageLevel.Percentage())
fmt.Printf(" GSM Signal: %s (%d bars)\n", p.GSMSignal.String(), p.GSMSignal.Bars())
}
}
TCP Server with Complete Packet Handling
package main
import (
"log"
"net"
"github.com/fcode09/jimi-vl103m/pkg/jimi"
"github.com/fcode09/jimi-vl103m/pkg/jimi/encoder"
"github.com/fcode09/jimi-vl103m/pkg/jimi/packet"
)
func main() {
decoder := jimi.NewDecoder()
enc := encoder.NewEncoder()
listener, _ := net.Listen("tcp", ":5023")
defer listener.Close()
log.Println("GPS Server listening on :5023")
for {
conn, _ := listener.Accept()
go handleConnection(conn, decoder, enc)
}
}
func handleConnection(conn net.Conn, decoder *jimi.Decoder, enc *encoder.Encoder) {
defer conn.Close()
buffer := make([]byte, 0, 4096)
readBuf := make([]byte, 1024)
for {
n, err := conn.Read(readBuf)
if err != nil {
return
}
buffer = append(buffer, readBuf[:n]...)
// Decode TCP stream (handles packet fragmentation)
packets, residue, err := decoder.DecodeStream(buffer)
if err != nil {
continue
}
buffer = residue
// Process each complete packet
for _, pkt := range packets {
processPacket(pkt, conn, enc)
}
}
}
func processPacket(pkt packet.Packet, conn net.Conn, enc *encoder.Encoder) {
switch p := pkt.(type) {
case *packet.LoginPacket:
log.Printf("Login from IMEI: %s", p.GetIMEI())
response := enc.EncodeLoginResponse(p.SerialNumber())
conn.Write(response)
case *packet.LocationPacket:
log.Printf("Location: %.6f, %.6f | ACC: %s | Speed: %d km/h",
p.Latitude(), p.Longitude(),
map[bool]string{true: "ON", false: "OFF"}[p.ACC],
p.Speed)
case *packet.Location4GPacket:
log.Printf("Location 4G: %.6f, %.6f | MCC/MNC: %d",
p.Latitude(), p.Longitude(), p.MCCMNC)
case *packet.AlarmPacket:
log.Printf("ALARM: %s (Critical: %v)", p.AlarmType, p.IsCritical())
response := enc.EncodeAlarmResponse(p.SerialNumber())
conn.Write(response)
case *packet.HeartbeatPacket:
log.Printf("Heartbeat - ACC: %v, Battery: %d%%",
p.TerminalInfo.ACCOn(), p.VoltageLevel.Percentage())
response := enc.EncodeHeartbeatResponse(p.SerialNumber())
conn.Write(response)
}
}
Supported Packet Types
| Protocol |
Code |
Description |
Direction |
Status |
| Login |
0x01 |
Device authentication with IMEI |
Device to Server |
Complete |
| Heartbeat |
0x13 |
Keep-alive with status and battery |
Device to Server |
Complete |
| GPS Location |
0x22 |
GPS data with 2G cell towers |
Device to Server |
Complete |
| GPS Location 4G |
0xA0 |
GPS data with 4G cell towers |
Device to Server |
Complete |
| LBS Multi-Base |
0x28 |
LBS-only location (2G) |
Device to Server |
Complete |
| LBS Multi-Base 4G |
0xA1 |
LBS-only location (4G) |
Device to Server |
Complete |
| Alarm |
0x26 |
Single geofence alarm |
Device to Server |
Complete |
| Alarm Multi-Fence |
0x27 |
Multiple geofence alarm (2G) |
Device to Server |
Complete |
| Alarm 4G |
0xA4 |
Multiple geofence alarm (4G) |
Device to Server |
Complete |
| GPS Address Request |
0x2A |
Request address from coordinates |
Device to Server |
Complete |
| Online Command |
0x80 |
Send command to device |
Server to Device |
Complete |
| Time Calibration |
0x8A |
Time synchronization |
Bidirectional |
Complete |
| Information Transfer |
0x94 |
Device status and parameters |
Device to Server |
Complete |
| Command Response |
0x21/0x15 |
Response to commands |
Device to Server |
Complete |
| Chinese Address |
0x17 |
Parsed address response (Chinese) |
Server to Device |
Complete |
| English Address |
0x97 |
Parsed address response (English) |
Server to Device |
Complete |
2G vs 4G Packet Differences
The library automatically handles differences between 2G and 4G protocols:
| Field |
2G Format |
4G Format |
Notes |
| MNC |
1 byte |
1 or 2 bytes |
Determined by MCC Bit15 |
| LAC |
2 bytes |
4 bytes |
Extended range in 4G |
| Cell ID |
3 bytes |
8 bytes |
Extended range in 4G |
API Documentation
Decoding Packets
// Create decoder with options
decoder := jimi.NewDecoder(
jimi.WithSkipCRC(), // Skip CRC validation (faster)
jimi.WithStrictMode(false), // Allow unknown protocols
jimi.WithAllowUnknownProtocols(),
)
// Decode single packet
packet, err := decoder.Decode(data)
// Decode TCP stream (handles fragmentation)
packets, residue, err := decoder.DecodeStream(buffer)
Common Packet Fields
LocationPacket (0x22) and Location4GPacket (0xA0)
// Access GPS data
lat := packet.Latitude() // float64
lon := packet.Longitude() // float64
speed := packet.Speed // uint8 (km/h)
heading := packet.Heading() // uint16 (degrees)
// ACC Status
accOn := packet.ACC // bool - direct field
// OR
accOn := packet.ACCOn() // bool - method
// LBS Information (cell tower)
if packet.LBSInfo.IsValid() {
mcc := packet.LBSInfo.MCC // Mobile Country Code
mnc := packet.LBSInfo.MNC // Mobile Network Code
lac := packet.LBSInfo.LAC // Location Area Code
cellID := packet.LBSInfo.CellID
}
HeartbeatPacket (0x13)
// Terminal status bits
accOn := packet.TerminalInfo.ACCOn() // Bit 1
isCharging := packet.TerminalInfo.IsCharging() // Bit 2
gpsTracking := packet.TerminalInfo.GPSTrackingEnabled() // Bit 6
isArmed := packet.TerminalInfo.IsArmed() // Bit 0
// Battery and signal
batteryPercent := packet.VoltageLevel.Percentage() // 0-100%
gsmBars := packet.GSMSignal.Bars() // 0-4 bars
AlarmPacket (0x26/0x27/0xA4)
// Alarm information
alarmType := packet.AlarmType // protocol.AlarmType
isCritical := packet.IsCritical() // bool
// GPS data (same as LocationPacket)
lat := packet.Latitude()
lon := packet.Longitude()
// Terminal info (from bit fields)
accOn := packet.TerminalInfo.ACCOn()
alarmBits := packet.TerminalInfo.AlarmTypeBits() // Bits 3-5
Encoding Responses
enc := encoder.NewEncoder()
// Send responses back to device
loginResp := enc.EncodeLoginResponse(serialNumber)
heartbeatResp := enc.EncodeHeartbeatResponse(serialNumber)
alarmResp := enc.EncodeAlarmResponse(serialNumber)
timeResp := enc.EncodeTimeCalibrationResponse(serialNumber, time.Now())
conn.Write(loginResp)
Examples
See the /examples directory for complete working examples:
Troubleshooting
ACC Status Shows Incorrect Value
Issue: GPS Location packets show ACC=OFF when they should show ACC=ON.
Solution: This has been fixed in the current version. GPS Location packets use a dedicated ACC byte (0x00=OFF, 0x01=ON), while Heartbeat and Alarm packets use a bit field (Bit 1). The library now correctly handles both formats.
// GPS Location packets (0x22, 0xA0)
accOn := packet.ACC // Reads dedicated byte
// Heartbeat/Alarm packets (0x13, 0x26, 0x27, 0xA4)
accOn := packet.TerminalInfo.ACCOn() // Reads Bit 1
Packet Too Short Errors
Cause: Packet content does not match expected length for the protocol.
Solution:
// Use lenient mode for testing
decoder := jimi.NewDecoder(
jimi.WithStrictMode(false),
)
CRC Validation Failures
Cause: Corrupted data or incorrect CRC calculation.
Solution:
// Skip CRC validation during development
decoder := jimi.NewDecoder(
jimi.WithSkipCRC(),
)
For more troubleshooting information, see docs/TROUBLESHOOTING.md.
Documentation
Development
Building
# Build all binaries
make build
# Run tests
make test
# Run tests with coverage
make test-coverage
# Run linter
make lint
Testing
# Run all tests
go test ./...
# Run tests with race detector
go test -race ./...
# Generate coverage report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
- Throughput: 10,000+ packets/second on modern hardware
- Memory: Approximately 1KB per concurrent connection
- Latency: Less than 1ms per packet decode (average)
- Zero allocations in hot paths where possible
Contributing
Contributions are welcome. Please read our Contributing Guide first.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature)
- Commit your changes (
git commit -m 'Add amazing feature')
- Push to the branch (
git push origin feature/amazing-feature)
- Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
Protocol specification based on official Concox JM-VL03 documentation (version 1.1.2).
Support
- Issues: GitHub Issues
- Documentation: This README and
/docs folder
Copyright 2026 Intelcon Group. All rights reserved.