bugst / go-serial

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

SetDeadline implementation #117

Open maitredede opened 2 years ago

maitredede commented 2 years ago

Hello,

Thanks for PR #109 (timeouts).

I am using your lib (go 1.16.7 on linux/amd64) to drive Ingenico payement hardware that has timeouts defined in its communication protocol. But the timeouts are not the same between protocol states. For example : when I write a ENQ byte, there is a response timeout of 2s; when I ended sending something, I have to listen 2s before I can start sending a new item; when there is a communication conflict, the timeout is 5s before retry...

My first driving implementation was based on your lib, and did not fully respected all the timeouts. My second implementation use directly the /dev/ttyACM0 file (using os.File), and respects the timeouts, using the `os.File.SetDeadline` func.

So I would like if it is possible to add a SetDeadline implementation that can be called when needed, and that may have the same behavior that the *os.File. :slightly_smiling_face:

maitredede commented 2 years ago

A quick and dirty implementation may be :

func (port *unixPort) SetDeadline(t time.Time) error {
    return port.SetReadTimeout(time.Until(t))
}

but it would be also interesting to have timeouts/deadlines on .Write() :slightly_smiling_face:

maitredede commented 1 year ago

Hello, More precisions about my use case, I need to communicate with devices that respects some timouts, when both reading and writing.

Either the native timeout or the context approach can be used in my case, but I need both read and write timeouts :smiley:

quite commented 1 year ago

I need this as well. I would like to use io.ReadFull on the go-serial reader/Port interface. But since go-serial's SetReadTimeout() only causes go-serial's Read() implementation to do return 0, nil, the ReadFull gets stuck. My workaround might end up using SetReadTimeout(), and the doing my own Read-loop, reading 1 byte at a time into my buffer and interpreting n, nil as the timeout. related #148

kmpm commented 1 year ago

How about the same interface as net.conn?

SetDeadline(t time.Time) error
SetReadDeadline(t time.Time) error
SetWriteDeadline(t time.Time) error

which could be set using for example port.SetReadDeadline(time.Now().Add(time.Second)).

If implementing the same interface as net.Conn one could keep much code between networked and serial connections without much change. As long as one of the existing interfaces from either from this or os.File is used then I'm happy.

dschmidt commented 9 months ago

How about the same interface as net.conn?

SetDeadline(t time.Time) error
SetReadDeadline(t time.Time) error
SetWriteDeadline(t time.Time) error

which could be set using for example port.SetReadDeadline(time.Now().Add(time.Second)).

If implementing the same interface as net.Conn one could keep much code between networked and serial connections without much change. As long as one of the existing interfaces from either from this or os.File is used then I'm happy.

I second this! I had to implement serial port handling in software that was already using net.Conn, so having the deadline functions would have made Port basically a drop in replacement. It would be nice to have it in the long run, but for the time being this is what I've come up with:

type portWithDeadline struct {
    serial.Port

    writeDeadline *time.Time
}

func (p portWithDeadline) SetDeadline(t time.Time) error {
    p.SetWriteDeadline(t)
    return p.SetReadDeadline(t)
}

func (p portWithDeadline) SetReadDeadline(t time.Time) error {
    return p.SetReadTimeout(time.Until(t))
}

func (p portWithDeadline) SetWriteDeadline(t time.Time) error {
    p.writeDeadline = &t
    return nil
}

func (p portWithDeadline) Write(b []byte) (n int, err error) {
    if p.writeDeadline == nil || p.writeDeadline.IsZero() {
        return p.Port.Write(b)
    }

    finishedCh := make(chan struct{})
    timeoutCh := time.After(time.Until(*p.writeDeadline))
    go func() {
        defer close(finishedCh)
        n, err = p.Port.Write(b)
    }()

    select {
    case <-timeoutCh:
        return 0, errors.New("write operation timed out")
    case <-finishedCh:
        return n, err
    }
}