-
Notifications
You must be signed in to change notification settings - Fork 39
Expand file tree
/
Copy pathserial.go
More file actions
171 lines (146 loc) · 4.21 KB
/
Copy pathserial.go
File metadata and controls
171 lines (146 loc) · 4.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
// This software may be modified and distributed under the terms
// of the BSD license. See the LICENSE file for details.
package modbus
import (
"context"
"errors"
"fmt"
"io"
"sync"
"time"
"github.com/grid-x/serial"
)
const (
// Default timeout
serialTimeout = 5 * time.Second
serialIdleTimeout = 60 * time.Second
// Retry interval while spending the link recovery budget on reconnects.
serialReconnectRetryInterval = 10 * time.Millisecond
)
// serialPort has configuration and I/O controller.
type serialPort struct {
// Serial port configuration.
serial.Config
Logger Logger
// IdleTimeout is the duration to close the connection when no activity.
IdleTimeout time.Duration
// Silent period after successful connection
ConnectDelay time.Duration
// Recovery timeout if the connection is lost
LinkRecoveryTimeout time.Duration
// Interval between reconnect attempts while spending the link recovery budget.
// Zero or negative values fall back to the default retry interval.
ReconnectRetryInterval time.Duration
mu sync.Mutex
// port is platform-dependent data structure for serial port.
port io.ReadWriteCloser
lastActivity time.Time
closeTimer *time.Timer
}
func (mb *serialPort) Connect(ctx context.Context) (err error) {
mb.mu.Lock()
defer mb.mu.Unlock()
return mb.connect(ctx)
}
// connect connects to the serial port if it is not connected. Caller must hold the mutex.
// Note: caller must handle the connection close and recovery if the connection is lost.
func (mb *serialPort) connect(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
if mb.port == nil {
port, err := serial.Open(&mb.Config)
if err != nil {
return fmt.Errorf("could not open %s: %w", mb.Address, err)
}
mb.port = port
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(mb.ConnectDelay): //silent period
}
}
return nil
}
func (mb *serialPort) Close() (err error) {
mb.mu.Lock()
defer mb.mu.Unlock()
return mb.close()
}
// close closes the serial port if it is connected. Caller must hold the mutex.
func (mb *serialPort) close() (err error) {
if mb.port != nil {
err = mb.port.Close()
mb.port = nil
}
return
}
func (mb *serialPort) logf(format string, v ...interface{}) {
if mb.Logger != nil {
mb.Logger.Printf(format, v...)
}
}
func (mb *serialPort) shouldRecover(err error) bool {
return errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF)
}
func (mb *serialPort) reconnect(ctx context.Context, err error, linkRecoveryDeadline time.Time) error {
if mb.LinkRecoveryTimeout == 0 || time.Until(linkRecoveryDeadline) < 0 {
return fmt.Errorf("modbus: link recovery timeout reached: %w", err)
}
mb.logf("modbus: connection reset, reconnecting")
recoveryErr := err
if cerr := mb.close(); cerr != nil {
recoveryErr = errors.Join(recoveryErr, cerr)
mb.logf("modbus: error closing connection: %v", cerr)
}
deadlineTimer := time.NewTimer(time.Until(linkRecoveryDeadline))
defer deadlineTimer.Stop()
retryTicker := time.NewTicker(mb.reconnectRetryInterval())
defer retryTicker.Stop()
for {
if cerr := mb.connect(ctx); cerr == nil {
return nil
} else {
recoveryErr = errors.Join(recoveryErr, cerr)
mb.logf("modbus: error reconnecting: %v", cerr)
}
select {
case <-ctx.Done():
return ctx.Err()
case <-deadlineTimer.C:
return fmt.Errorf("modbus: link recovery timeout reached: %w", recoveryErr)
case <-retryTicker.C:
}
}
}
func (mb *serialPort) reconnectRetryInterval() time.Duration {
if mb.ReconnectRetryInterval > 0 {
return mb.ReconnectRetryInterval
}
return serialReconnectRetryInterval
}
func (mb *serialPort) startCloseTimer() {
if mb.IdleTimeout <= 0 {
return
}
if mb.closeTimer == nil {
mb.closeTimer = time.AfterFunc(mb.IdleTimeout, mb.closeIdle)
} else {
mb.closeTimer.Reset(mb.IdleTimeout)
}
}
// closeIdle closes the connection if last activity is passed behind IdleTimeout.
func (mb *serialPort) closeIdle() {
mb.mu.Lock()
defer mb.mu.Unlock()
if mb.IdleTimeout <= 0 {
return
}
if idle := time.Since(mb.lastActivity); idle >= mb.IdleTimeout {
mb.logf("modbus: closing connection due to idle timeout: %v", idle)
_ = mb.close()
}
}