Conversation
There was a problem hiding this comment.
Pull request overview
This pull request adds support for the GTU7 GPS sensor module and implements Linux serial port drivers to enable communication with serial devices.
Changes:
- Adds GTU7 GPS sensor with NMEA sentence parsing (GGA, RMC, VTG) and precedence logic for speed/course data
- Implements platform-specific serial drivers for Linux with termios configuration and stub implementation for non-Linux platforms
- Includes basic test coverage for RMC precedence behavior over VTG
Reviewed changes
Copilot reviewed 38 out of 39 changed files in this pull request and generated 18 comments.
Show a summary per file
| File | Description |
|---|---|
| sensors/gtu7.go | Core GTU7 GPS sensor implementation with NMEA parsing logic |
| sensors/gtu7_test.go | Test verifying RMC sentence precedence over VTG for speed/course data |
| drivers/serial.go | Cross-platform serial port interface definitions and configuration validation |
| drivers/serial_linux.go | Linux-specific serial port implementation using termios |
| drivers/serial_stub.go | Stub implementation for unsupported platforms |
| _archive/gtu7/* | Removal of old GTU7 implementation |
| _archive/examples/* | Removal of archived example code |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| func TestGTU7_PrefersRMCOverVTG(t *testing.T) { | ||
| input := ` | ||
| $GPGGA,160446.00,3340.34121,N,11800.11332,W,2,08,1.20,11.8,M,-33.1,M,,0000*58 | ||
| $GPVTG,54.70,T,,M,5.50,N,10.19,K,A*00 | ||
| $GPRMC,160446.00,A,3340.34121,N,11800.11332,W,7.25,123.40,160126,,,A*00 | ||
| ` | ||
|
|
||
| gps := NewGTU7(GTU7Config{ | ||
| Reader: strings.NewReader(input), | ||
| }) | ||
|
|
||
| ctx, cancel := context.WithCancel(context.Background()) | ||
| defer cancel() | ||
|
|
||
| done := make(chan error, 1) | ||
| go func() { done <- gps.Run(ctx) }() | ||
|
|
||
| // Drain GGA + VTG | ||
| for i := 0; i < 2; i++ { | ||
| select { | ||
| case <-gps.Out(): | ||
| case <-time.After(time.Second): | ||
| require.FailNow(t, "timeout") | ||
| } | ||
| } | ||
|
|
||
| // RMC must override VTG | ||
| select { | ||
| case fix := <-gps.Out(): | ||
| cancel() | ||
| require.InDelta(t, 7.25, fix.SpeedKnots, 1e-6) | ||
| require.InDelta(t, 7.25*0.514444, fix.SpeedMPS, 1e-6) | ||
| require.InDelta(t, 123.40, fix.CourseDeg, 1e-6) | ||
| require.Equal(t, "A", fix.Status) | ||
| require.Equal(t, "160126", fix.Date) | ||
| case <-time.After(time.Second): | ||
| require.FailNow(t, "timeout waiting for RMC fix") | ||
| } | ||
|
|
||
| select { | ||
| case err := <-done: | ||
| require.NoError(t, err) | ||
| case <-time.After(time.Second): | ||
| require.FailNow(t, "run did not exit") | ||
| } | ||
| } |
There was a problem hiding this comment.
The test covers RMC precedence over VTG but doesn't test other important scenarios such as: parsing errors, handling of empty/malformed NMEA sentences, GGA-only data, behavior when context is cancelled mid-stream, or handling of GNGGA/GNRMC/GNVTG variants. Consider adding more comprehensive test cases to cover these scenarios.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| func (g *GTU7) Run(ctx context.Context) error { | ||
| defer close(g.out) | ||
|
|
||
| var last GPSFix | ||
| haveFix := false | ||
|
|
||
| // RMC precedence flags | ||
| haveRMCSpeed := false | ||
| haveRMCCourse := false | ||
|
|
||
| sc := bufio.NewScanner(g.r) | ||
| for sc.Scan() { | ||
| select { | ||
| case <-ctx.Done(): | ||
| return nil | ||
| default: | ||
| } | ||
|
|
||
| line := strings.TrimSpace(sc.Text()) | ||
| if line == "" { | ||
| continue | ||
| } | ||
|
|
||
| if fix, ok := parseGPGGA(line); ok { | ||
| last.Lat = fix.Lat | ||
| last.Lon = fix.Lon | ||
| last.AltMeters = fix.AltMeters | ||
| last.HDOP = fix.HDOP | ||
| last.Satellites = fix.Satellites | ||
| last.Quality = fix.Quality | ||
|
|
||
| haveFix = true | ||
| g.emit(last) | ||
| continue | ||
| } | ||
|
|
||
| if fix, ok := parseGPRMC(line); ok { | ||
| if !math.IsNaN(fix.Lat) { | ||
| last.Lat = fix.Lat | ||
| last.Lon = fix.Lon | ||
| haveFix = true | ||
| } | ||
| if !math.IsNaN(fix.SpeedKnots) { | ||
| last.SpeedKnots = fix.SpeedKnots | ||
| last.SpeedMPS = fix.SpeedMPS | ||
| haveRMCSpeed = true | ||
| } | ||
| if !math.IsNaN(fix.CourseDeg) { | ||
| last.CourseDeg = fix.CourseDeg | ||
| haveRMCCourse = true | ||
| } | ||
| if fix.Status != "" { | ||
| last.Status = fix.Status | ||
| } | ||
| if fix.Date != "" { | ||
| last.Date = fix.Date | ||
| } | ||
|
|
||
| if haveFix { | ||
| g.emit(last) | ||
| } | ||
| continue | ||
| } | ||
|
|
||
| if fix, ok := parseGPVTG(line); ok { | ||
| if !math.IsNaN(fix.SpeedKnots) && (!haveRMCSpeed || math.IsNaN(last.SpeedKnots)) { | ||
| last.SpeedKnots = fix.SpeedKnots | ||
| last.SpeedMPS = fix.SpeedMPS | ||
| } | ||
| if !math.IsNaN(fix.CourseDeg) && (!haveRMCCourse || math.IsNaN(last.CourseDeg)) { | ||
| last.CourseDeg = fix.CourseDeg | ||
| } | ||
|
|
||
| if haveFix { | ||
| g.emit(last) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return sc.Err() | ||
| } |
There was a problem hiding this comment.
The Run method doesn't clean up the serial port resource. When the serial port is opened (lines 56-60), it implements io.ReadWriteCloser but is stored as io.Reader, losing access to the Close() method. The Run method should close the serial port before returning, similar to how VH400 closes its ADC with a defer (see vh400.go lines 136-140). Consider storing the SerialPort interface instead of io.Reader, or adding a defer that checks if r implements io.Closer and closes it.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
|
|
||
| return >U7{ | ||
| name: cfg.Name, | ||
| out: make(chan GPSFix, 4), |
There was a problem hiding this comment.
The output channel buffer size is hard-coded to 4 which seems arbitrary and may be insufficient for high-rate GPS data. Most GPS modules output around 1-10 Hz, so a buffer of 4 could fill quickly if the consumer is slow. Consider making this configurable via GTU7Config similar to how VH400Config has a Buf field, or documenting why 4 is appropriate.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| func NewGTU7(cfg GTU7Config) *GTU7 { | ||
| if cfg.Factory == nil { | ||
| cfg.Factory = drivers.LinuxSerialFactory{} | ||
| } | ||
|
|
||
| var r io.Reader | ||
| if cfg.Reader != nil { | ||
| r = cfg.Reader | ||
| } else { | ||
| port, err := cfg.Factory.OpenSerial(cfg.Serial) | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
| r = port | ||
| } | ||
|
|
||
| return >U7{ | ||
| name: cfg.Name, | ||
| out: make(chan GPSFix, 4), | ||
| r: r, | ||
| } | ||
| } |
There was a problem hiding this comment.
The NewGTU7 constructor panics when OpenSerial fails. This violates error handling best practices and makes the API difficult to use safely. Consider returning an error instead of panicking, similar to how other constructors in the codebase handle errors. This would allow callers to decide how to handle initialization failures.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| // RMC precedence flags | ||
| haveRMCSpeed := false | ||
| haveRMCCourse := false | ||
|
|
||
| sc := bufio.NewScanner(g.r) | ||
| for sc.Scan() { | ||
| select { | ||
| case <-ctx.Done(): | ||
| return nil | ||
| default: | ||
| } | ||
|
|
||
| line := strings.TrimSpace(sc.Text()) | ||
| if line == "" { | ||
| continue | ||
| } | ||
|
|
||
| if fix, ok := parseGPGGA(line); ok { | ||
| last.Lat = fix.Lat | ||
| last.Lon = fix.Lon | ||
| last.AltMeters = fix.AltMeters | ||
| last.HDOP = fix.HDOP | ||
| last.Satellites = fix.Satellites | ||
| last.Quality = fix.Quality | ||
|
|
||
| haveFix = true | ||
| g.emit(last) | ||
| continue | ||
| } | ||
|
|
||
| if fix, ok := parseGPRMC(line); ok { | ||
| if !math.IsNaN(fix.Lat) { | ||
| last.Lat = fix.Lat | ||
| last.Lon = fix.Lon | ||
| haveFix = true | ||
| } | ||
| if !math.IsNaN(fix.SpeedKnots) { | ||
| last.SpeedKnots = fix.SpeedKnots | ||
| last.SpeedMPS = fix.SpeedMPS | ||
| haveRMCSpeed = true | ||
| } | ||
| if !math.IsNaN(fix.CourseDeg) { | ||
| last.CourseDeg = fix.CourseDeg | ||
| haveRMCCourse = true | ||
| } | ||
| if fix.Status != "" { | ||
| last.Status = fix.Status | ||
| } | ||
| if fix.Date != "" { | ||
| last.Date = fix.Date | ||
| } | ||
|
|
||
| if haveFix { | ||
| g.emit(last) | ||
| } | ||
| continue | ||
| } | ||
|
|
||
| if fix, ok := parseGPVTG(line); ok { | ||
| if !math.IsNaN(fix.SpeedKnots) && (!haveRMCSpeed || math.IsNaN(last.SpeedKnots)) { | ||
| last.SpeedKnots = fix.SpeedKnots | ||
| last.SpeedMPS = fix.SpeedMPS | ||
| } | ||
| if !math.IsNaN(fix.CourseDeg) && (!haveRMCCourse || math.IsNaN(last.CourseDeg)) { | ||
| last.CourseDeg = fix.CourseDeg | ||
| } | ||
|
|
||
| if haveFix { | ||
| g.emit(last) | ||
| } | ||
| } |
There was a problem hiding this comment.
The precedence flags haveRMCSpeed and haveRMCCourse are never reset between NMEA sentences. This means once RMC data is received, VTG data will never be used again even if RMC stops providing speed/course data in subsequent sentences. Consider resetting these flags at appropriate points or restructuring the logic to handle per-sentence precedence rather than global state.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| func (g *GTU7) emit(f GPSFix) { | ||
| select { | ||
| case g.out <- f: | ||
| default: | ||
| } | ||
| } |
There was a problem hiding this comment.
The emit method silently drops GPS fixes when the output channel is full (line 165). This is a data loss scenario that could be problematic for applications that need every GPS update. Consider logging a warning or emitting an event when fixes are dropped so applications can detect this condition. Compare with VH400 which uses a configurable buffer and follows a similar pattern.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| fix.SpeedMPS = v * 0.514444 | ||
| } | ||
| } | ||
|
|
||
| if parts[8] != "" { | ||
| if v, err := strconv.ParseFloat(parts[8], 64); err == nil { | ||
| fix.CourseDeg = v | ||
| } | ||
| } | ||
|
|
||
| return fix, true | ||
| } | ||
|
|
||
| func parseGPVTG(line string) (GPSFix, bool) { | ||
| line = stripChecksum(line) | ||
| parts := strings.Split(line, ",") | ||
| if len(parts) < 9 || (parts[0] != "$GPVTG" && parts[0] != "$GNVTG") { | ||
| return GPSFix{}, false | ||
| } | ||
|
|
||
| fix := GPSFix{ | ||
| SpeedKnots: math.NaN(), | ||
| SpeedMPS: math.NaN(), | ||
| CourseDeg: math.NaN(), | ||
| } | ||
|
|
||
| if parts[1] != "" { | ||
| if v, err := strconv.ParseFloat(parts[1], 64); err == nil { | ||
| fix.CourseDeg = v | ||
| } | ||
| } | ||
|
|
||
| if parts[5] != "" { | ||
| if v, err := strconv.ParseFloat(parts[5], 64); err == nil { | ||
| fix.SpeedKnots = v | ||
| fix.SpeedMPS = v * 0.514444 |
There was a problem hiding this comment.
The conversion factor 0.514444 from knots to meters per second is duplicated on lines 225 and 260. Consider defining this as a named constant at the package level (e.g., const knotsToMPS = 0.514444) to improve maintainability and make the conversion explicit.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| //go:build linux | ||
|
|
||
| package drivers | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "os" | ||
| "time" | ||
|
|
||
| "golang.org/x/sys/unix" | ||
| ) | ||
|
|
||
| // LinuxSerialFactory opens a configured serial port on Linux. | ||
| // | ||
| // It uses termios to set 8N1 and the requested baud rate. | ||
| type LinuxSerialFactory struct{} | ||
|
|
||
| func (LinuxSerialFactory) OpenSerial(cfg SerialConfig) (SerialPort, error) { | ||
| if err := validateSerialConfig(cfg); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| // Open non-blocking first, configure termios, then clear O_NONBLOCK. | ||
| fd, err := unix.Open(cfg.Port, unix.O_RDWR|unix.O_NOCTTY|unix.O_NONBLOCK, 0) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("serial: open %s: %w", cfg.Port, err) | ||
| } | ||
|
|
||
| // Ensure we don't leak the fd if configuration fails. | ||
| ok := false | ||
| defer func() { | ||
| if !ok { | ||
| _ = unix.Close(fd) | ||
| } | ||
| }() | ||
|
|
||
| if err := configureTermios(fd, cfg.Baud); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| // Clear O_NONBLOCK so reads behave as expected. | ||
| if err := unix.SetNonblock(fd, false); err != nil { | ||
| return nil, fmt.Errorf("serial: set blocking %s: %w", cfg.Port, err) | ||
| } | ||
|
|
||
| f := os.NewFile(uintptr(fd), cfg.Port) | ||
| if f == nil { | ||
| return nil, fmt.Errorf("serial: os.NewFile returned nil for %s", cfg.Port) | ||
| } | ||
|
|
||
| ok = true | ||
| return &linuxSerialPort{file: f, port: cfg.Port, baud: cfg.Baud}, nil | ||
| } | ||
|
|
||
| type linuxSerialPort struct { | ||
| file *os.File | ||
| port string | ||
| baud int | ||
| } | ||
|
|
||
| func (p *linuxSerialPort) Read(b []byte) (int, error) { return p.file.Read(b) } | ||
| func (p *linuxSerialPort) Write(b []byte) (int, error) { return p.file.Write(b) } | ||
| func (p *linuxSerialPort) Close() error { return p.file.Close() } | ||
| func (p *linuxSerialPort) String() string { return fmt.Sprintf("%s@%d", p.port, p.baud) } | ||
|
|
||
| func configureTermios(fd int, baud int) error { | ||
| t, err := unix.IoctlGetTermios(fd, unix.TCGETS) | ||
| if err != nil { | ||
| return fmt.Errorf("serial: ioctl TCGETS: %w", err) | ||
| } | ||
|
|
||
| speed, err := baudToUnix(baud) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| // Raw-ish 8N1. | ||
| t.Iflag = 0 | ||
| t.Oflag = 0 | ||
| t.Lflag = 0 | ||
| t.Cflag = unix.CS8 | unix.CREAD | unix.CLOCAL | ||
|
|
||
| // Disable flow control. | ||
| t.Cflag &^= unix.CRTSCTS | ||
|
|
||
| // VMIN/VTIME: block until at least 1 byte. | ||
| t.Cc[unix.VMIN] = 1 | ||
| t.Cc[unix.VTIME] = 0 | ||
|
|
||
| // Set baud. | ||
| t.Ispeed = uint32(speed) | ||
| t.Ospeed = uint32(speed) | ||
|
|
||
| if err := unix.IoctlSetTermios(fd, unix.TCSETS, t); err != nil { | ||
| return fmt.Errorf("serial: ioctl TCSETS: %w", err) | ||
| } | ||
|
|
||
| // Give the line a moment to settle. | ||
| time.Sleep(10 * time.Millisecond) | ||
| return nil | ||
| } | ||
|
|
||
| func baudToUnix(baud int) (uint32, error) { | ||
| switch baud { | ||
| case 4800: | ||
| return unix.B4800, nil | ||
| case 9600: | ||
| return unix.B9600, nil | ||
| case 19200: | ||
| return unix.B19200, nil | ||
| case 38400: | ||
| return unix.B38400, nil | ||
| case 57600: | ||
| return unix.B57600, nil | ||
| case 115200: | ||
| return unix.B115200, nil | ||
| default: | ||
| return 0, fmt.Errorf("serial: unsupported baud %d (supported: 4800,9600,19200,38400,57600,115200)", baud) | ||
| } | ||
| } |
There was a problem hiding this comment.
The serial driver implementation lacks test coverage. The serial_linux.go file contains critical low-level serial port configuration logic including termios settings, baud rate mapping, and file descriptor management, but there are no corresponding test files. Consider adding tests for configuration validation, baud rate conversion, and error handling paths.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| func (g *GTU7) Descriptor() devices.Descriptor { | ||
| return devices.Descriptor{ | ||
| Name: g.name, | ||
| Kind: "gps", | ||
| ValueType: "GPSFix", | ||
| } | ||
| } |
There was a problem hiding this comment.
The Descriptor implementation doesn't include useful metadata fields that are present in other sensors like VH400. Consider adding Tags (e.g., "gps", "navigation", "location") and Attributes (e.g., serial port information) to provide more complete device metadata. This would make the device more discoverable and better documented.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| type GTU7Config struct { | ||
| Name string | ||
| Serial drivers.SerialConfig | ||
| Factory drivers.SerialFactory | ||
|
|
||
| // Test injection | ||
| Reader io.Reader | ||
| } |
There was a problem hiding this comment.
The GTU7Config struct lacks documentation. As an exported configuration type, it should have a doc comment explaining its purpose and providing usage examples. The Reader field is particularly important to document as it's specifically marked for test injection.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
|
@rustyeddy I've opened a new pull request, #17, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@rustyeddy I've opened a new pull request, #18, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@rustyeddy I've opened a new pull request, #19, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@rustyeddy I've opened a new pull request, #20, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@rustyeddy I've opened a new pull request, #21, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@rustyeddy I've opened a new pull request, #22, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@rustyeddy I've opened a new pull request, #23, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@rustyeddy I've opened a new pull request, #24, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@rustyeddy I've opened a new pull request, #25, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@rustyeddy I've opened a new pull request, #26, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@rustyeddy I've opened a new pull request, #27, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@rustyeddy I've opened a new pull request, #28, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@rustyeddy I've opened a new pull request, #29, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@rustyeddy I've opened a new pull request, #30, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@rustyeddy I've opened a new pull request, #31, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@rustyeddy I've opened a new pull request, #32, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@rustyeddy I've opened a new pull request, #33, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@rustyeddy I've opened a new pull request, #34, to work on those changes. Once the pull request is ready, I'll request review from you. |
* Initial plan * Make GTU7 buffer size configurable via GTU7Config.Buf Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> * Make GTU7 output channel buffer size configurable Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com>
* Initial plan * test: add comprehensive GTU7 test coverage Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> * Final review completed Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> * Update sensors/gtu7_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> Co-authored-by: Rusty Eddy <rusty@rustyeddy.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Initial plan * Add proper serial port cleanup in GTU7.Run method Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> * Improve comment clarity and error message in nopCloser Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> * Update nopCloser comment for clarity Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> * Final update to PR description Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> * Replace custom nopCloser with stdlib io.NopCloser and use io.ReadCloser Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> * Remove unnecessary nil check in Run defer Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> Co-authored-by: Rusty Eddy <rusty@rustyeddy.com>
* Initial plan * Fix RMC precedence flags to reset when data is unavailable Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> * Complete: Fixed RMC precedence flag reset issue Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> Co-authored-by: Rusty Eddy <rusty@rustyeddy.com>
* Initial plan * Add ReadOnly access to GTU7 Descriptor method Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> * Add ReadOnly access mode to GTU7 GPS sensor descriptor Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com>
* Initial plan * docs: add comprehensive documentation for GPSFix struct Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com>
* Initial plan * Add Tags and Attributes to GTU7 Descriptor - Added Tags: "gps", "navigation", "location" - Added Attributes: serial port and baud rate information - Added Access: ReadOnly to match other sensor patterns - Store GTU7Config in struct to access serial info in Descriptor - Added comprehensive tests for Descriptor functionality Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> * Completed: Enhanced GTU7 Descriptor with Tags and Attributes Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> Co-authored-by: Rusty Eddy <rusty@rustyeddy.com>
* Initial plan * Fix parseLatLon to check strconv.ParseFloat errors Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> * Improve error message for empty coordinates Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> * Complete parseLatLon error handling improvements Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> * Update sensors/gtu7_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> Co-authored-by: Rusty Eddy <rusty@rustyeddy.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Initial plan * Make serial port settle delay configurable with documentation Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> * Simplify delay handling logic Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> * Make serial port settle delay configurable Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com>
* Initial plan * Change NewGTU7 to return error instead of panicking Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> * Return error from NewGTU7 instead of panicking Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> * Add test coverage for NewGTU7 error handling Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rustyeddy <2903425+rustyeddy@users.noreply.github.com> Co-authored-by: Rusty Eddy <rusty@rustyeddy.com>
No description provided.