forked from tbrandon/mbserver
-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.go
131 lines (108 loc) · 3.29 KB
/
server.go
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
// Package mbserver implments a Modbus server (slave).
package mbserver
import (
"context"
"io"
"net"
"github.com/goburrow/serial"
)
// FunctionHandler defines a function type for defining custom Modbus
// function code handlers.
type FunctionHandler func(*Server, Framer) ([]byte, *Exception)
// ContextFunctionHandler defines a function type for defining external Modbus
// function code handlers with a Context.
type ContextFunctionHandler func(context.Context, Framer) ([]byte, *Exception)
// Server is a Modbus slave with allocated memory for discrete inputs, coils, etc.
type Server struct {
// Debug enables more verbose messaging.
Debug bool
listeners []net.Listener
ports []serial.Port
requestChan chan *Request
function [256]FunctionHandler
DiscreteInputs []byte
Coils []byte
HoldingRegisters []uint16
InputRegisters []uint16
handlers [256]ContextFunctionHandler
}
// Request contains the connection and Modbus frame.
type Request struct {
ctx context.Context
conn io.ReadWriteCloser
frame Framer
}
// NewServer creates a new Modbus server (slave).
func NewServer() *Server {
s := &Server{
requestChan: make(chan *Request),
}
go s.handler()
return s
}
// NewServer creates a new Modbus server (slave) with default function handlers
// and registers.
func NewServerWithDefaults() *Server {
s := &Server{}
// Allocate Modbus memory maps.
s.DiscreteInputs = make([]byte, 65536)
s.Coils = make([]byte, 65536)
s.HoldingRegisters = make([]uint16, 65536)
s.InputRegisters = make([]uint16, 65536)
// Add default functions.
s.function[1] = ReadCoils
s.function[2] = ReadDiscreteInputs
s.function[3] = ReadHoldingRegisters
s.function[4] = ReadInputRegisters
s.function[5] = WriteSingleCoil
s.function[6] = WriteHoldingRegister
s.function[15] = WriteMultipleCoils
s.function[16] = WriteHoldingRegisters
s.requestChan = make(chan *Request)
go s.handler()
return s
}
// RegisterFunctionHandler override the default behavior for a given Modbus function.
func (s *Server) RegisterFunctionHandler(code uint8, handler FunctionHandler) {
s.function[code] = handler
}
// RegisterContextFunctionHandler registers a new external ContextFunctionHandler.
func (s *Server) RegisterContextFunctionHandler(code uint8, handler ContextFunctionHandler) {
s.handlers[code] = handler
}
func (s *Server) handle(request *Request) Framer {
var exception *Exception
var data []byte
response := request.frame.Copy()
function := request.frame.GetFunction()
if s.function[function] != nil {
data, exception = s.function[function](s, request.frame)
response.SetData(data)
} else if s.handlers[function] != nil {
data, exception = s.handlers[function](request.ctx, request.frame)
response.SetData(data)
} else {
exception = &IllegalFunction
}
if exception != &Success {
response.SetException(exception)
}
return response
}
// All requests are handled synchronously to prevent modbus memory corruption.
func (s *Server) handler() {
for {
request := <-s.requestChan
response := s.handle(request)
request.conn.Write(response.Bytes())
}
}
// Close stops listening to TCP/IP ports and closes serial ports.
func (s *Server) Close() {
for _, listen := range s.listeners {
listen.Close()
}
for _, port := range s.ports {
port.Close()
}
}