bugst / go-serial

A cross-platform serial library for go-lang.
BSD 3-Clause "New" or "Revised" License
682 stars 205 forks source link

Transaction causes device to spew newlines #160

Open biohazduck opened 1 year ago

biohazduck commented 1 year ago

For example, I'm working with a electronic scale. When I open it with cutecom and select 1200 baud, 8 bits data, no parity, 1 stop bit I can send the "Get Weight" command which is D05\r\n, then I see in console 123.032g\r\n.

When I try to use go-serial and writing the command after opening it with same mode parameters, setting the read timeout to 1 second I get my buffer filled with newlines as soon as I send the command. Switching to Cutecom and opening the port in this state causes cutecom to crash due to the newlines filling the log. The only way to reset the devices gone-amok state is by disconnecting and connecting the USB cable.

Why would go-serial give different behavior to pySerial and cutecom? What could be making the device to enter this tilted/"stuck" state?

Darkskald commented 1 year ago

Big upvote for this here. I am implementing a driver for a laboratory balance, facing EXACTLY the same issue as above.

cmaglie commented 9 months ago

Please provide the source code to reproduce the issue.

biohazduck commented 9 months ago

@Darkskald The way I ended up solving this issue is by adding a mutex to the balance struct and making sure I write once every 1 second minimum. Writing faster will trigger the amok state (though have not tested at what frequency the bug pops up).

I am not using a read timeout, but am wrapping the serial device with a cereal.NonBlocking to not block on a failed message, which happens often.

The code to reproduce would look somewhat like

mode := &serial.Mode{
    BaudRate: 1200,
    Parity: serial.NoParity,
    DataBits: 8,
    StopBits: serial.OneStopBit,
}

port, _ := serial.Open(ports[0], mode) // Got port from listports
var buffer [1024]byte
cmd := []byte("D05\r\n") // Read scale command.

port.Write(cmd)
n, _ :=port.Read(buffer[:])
port.Write(cmd) // Two fast writes should be enough to trigger error
n, _ =port.Read(buffer[:])
port.Write(cmd)
n, _ =port.Read(buffer[:])
port.Write(cmd)
n, _ =port.Read(buffer[:])
fmt.Println(buffer[:n]) // Get newlines?
cmaglie commented 9 months ago

It could be that you get partial Reads. May you try to print the results of all Read operations?

    if err := port.Write(cmd); err != nil {
        fmt.Println("Error writing to port: ", err)
    }
    n, err := port.Read(buffer[:])
    fmt.Printf("READ: n=%d err=%v data:>%s<", n, err, buffer[:n])
    // The content of the buffer[:n] here may be a partial response
    // You may need to read again to get the remainder of the full response

    if err := port.Write(cmd); err != nil {
        fmt.Println("Error writing to port: ", err)
    }
    n, err = port.Read(buffer[:])
    fmt.Printf("READ: n=%d err=%v data:>%s<", n, err, buffer[:n])

    if err := port.Write(cmd); err != nil {
        fmt.Println("Error writing to port: ", err)
    }
    n, err = port.Read(buffer[:])
    fmt.Printf("READ: n=%d err=%v data:>%s<", n, err, buffer[:n])

    if err := port.Write(cmd); err != nil {
        fmt.Println("Error writing to port: ", err)
    }
    n, err := port.Read(buffer[:])
    fmt.Printf("READ: n=%d err=%v data:>%s<", n, err, buffer[:n])
    fmt.Println(buffer[:n])
biohazduck commented 9 months ago

The partial reads are not material to the issue of newline spewing. The issue I'm having is that I must be extremely careful when using bugst compared to cutecom and pyserial since writing too frequently with bugst will cause the scale to irrecoverably go into a state where all it does is write newlines until I unplug and plug again.

Normally I'd attribute this to bad firmware or hardware... but I then observed cutecom and pyserial never run into the issue so I began wondering if it was a problem in how linux port is handled by bugst.

On Sun, Feb 18, 2024, 13:16 Cristian Maglie @.***> wrote:

It could be that you get partial Reads. May you try to print the results of all Read operations?

if err := port.Write(cmd); err != nil { fmt.Println("Error writing to port: ", err) } n, err := port.Read(buffer[:]) fmt.Printf("READ: n=%d err=%v data:>%s<", n, err, buffer[:n]) // The content of the buffer[:n] here may be a partial response // You may need to read again to get the remainder of the full response

if err := port.Write(cmd); err != nil { fmt.Println("Error writing to port: ", err) } n, err = port.Read(buffer[:]) fmt.Printf("READ: n=%d err=%v data:>%s<", n, err, buffer[:n])

if err := port.Write(cmd); err != nil { fmt.Println("Error writing to port: ", err) } n, err = port.Read(buffer[:]) fmt.Printf("READ: n=%d err=%v data:>%s<", n, err, buffer[:n])

if err := port.Write(cmd); err != nil { fmt.Println("Error writing to port: ", err) } n, err := port.Read(buffer[:]) fmt.Printf("READ: n=%d err=%v data:>%s<", n, err, buffer[:n]) fmt.Println(buffer[:n])

— Reply to this email directly, view it on GitHub https://github.com/bugst/go-serial/issues/160#issuecomment-1951374411, or unsubscribe https://github.com/notifications/unsubscribe-auth/A5BK55VAXBZN63YQG4WBWXDYUISOTAVCNFSM6AAAAAAZDQNIX2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTSNJRGM3TINBRGE . You are receiving this because you authored the thread.Message ID: @.***>

soypat commented 9 months ago

I'll also note that I started work on a pyserial syscall-identical serial implementation in Go to test this out here, still not finished since reading syscall python code is quite hard to follow.

cmaglie commented 9 months ago

Normally I'd attribute this to bad firmware or hardware... but I then observed cutecom and pyserial never run into the issue so I began wondering if it was a problem in how linux port is handled by bugst.

In my opinion, is the firmware that is buggy and goes into this weird loop state if you try to send a command while the scale is still writing the response of the previous command. In this sense getting a partial reply means that you go into sending the next command while the previous response is still being written.

cmaglie commented 9 months ago

To support my hypothesis you may try to use this function instead of the simple Read:

func ReadLine(port serial.Port) (string, error) {
    buf := make([]byte, 100)
    line := ""
    for {
        n, err := port.Read(buf[:])
        if err != nil {
            return "", err
        }
        if n == 0 {
            return line, errors.New("timeout")
        }
        line += string(buf[:n])
        if i := strings.Index(line, "\n\r"); i != -1 {
            return line[:i], nil
        }
    }
}