github.com/simpleiot/simpleiot@v0.18.3/modbus/server.go (about) 1 package modbus 2 3 import ( 4 "fmt" 5 "io" 6 "log" 7 "time" 8 9 "github.com/simpleiot/simpleiot/test" 10 ) 11 12 // Server defines a server (slave) 13 // Current Server only supports Modbus RTU, 14 // but could be expanded to do ASCII and TCP. 15 type Server struct { 16 id byte 17 transport Transport 18 regs *Regs 19 chDone chan bool 20 debug int 21 } 22 23 // NewServer creates a new server instance 24 // port must return an entire packet for each Read(). 25 // github.com/simpleiot/simpleiot/respreader is a good 26 // way to do this. 27 func NewServer(id byte, transport Transport, regs *Regs, debug int) *Server { 28 return &Server{ 29 id: id, 30 transport: transport, 31 regs: regs, 32 chDone: make(chan bool), 33 debug: debug, 34 } 35 } 36 37 // Close stops the listening channel 38 func (s *Server) Close() error { 39 s.transport.Close() 40 s.chDone <- true 41 return nil 42 } 43 44 // Listen starts the server and listens for modbus requests 45 // this function does not return unless an error occurs 46 // The listen function supports various debug levels: 47 // 1 - dump packets 48 // 9 - dump raw data 49 func (s *Server) Listen(errorCallback func(error), 50 changesCallback func(), done func()) { 51 for { 52 select { 53 case <-s.chDone: 54 // FIXME is there a way to detect closed port with serial so 55 // we don't need this channel any more? 56 log.Println("Exiting modbus server listen") 57 done() 58 return 59 default: 60 } 61 buf := make([]byte, 200) 62 cnt, err := s.transport.Read(buf) 63 if err != nil { 64 if err != io.EOF && s.transport.Type() == TransportTypeRTU { 65 // only print errors for RTU for now as we get timeout 66 // errors with TCP 67 log.Println("Error reading modbus port:", err) 68 } 69 70 if err == io.EOF && s.transport.Type() == TransportTypeTCP { 71 // with TCP, EOF means we are done with this connection 72 if s.debug > 0 { 73 log.Println("Modbus TCP client disconnected") 74 } 75 done() 76 return 77 } 78 79 // FIXME -- do we want to keep this long term? 80 // to keep the system from spinning if a connection is destroyed 81 time.Sleep(100 * time.Millisecond) 82 continue 83 } 84 85 if cnt <= 0 { 86 continue 87 } 88 89 // parse packet from server 90 packet := buf[:cnt] 91 92 if s.debug >= 9 { 93 fmt.Println("Modbus server rx: ", test.HexDump(packet)) 94 } 95 96 id, req, err := s.transport.Decode(packet) 97 98 if err != nil { 99 errorCallback(err) 100 continue 101 } 102 103 if id != s.id { 104 // packet is not for this device 105 // for RTU this is normal as the devices are all listening 106 // on one bus. 107 continue 108 109 // For TCP, this should not happen, FIXME should we return 110 // an error here? 111 } 112 113 if s.debug >= 2 { 114 fmt.Println("Modbus server req: ", req) 115 } 116 117 regsChanged, resp, err := req.ProcessRequest(s.regs) 118 if regsChanged { 119 changesCallback() 120 } 121 122 if err != nil { 123 errorCallback(err) 124 continue 125 } 126 127 if s.debug >= 2 { 128 fmt.Println("Modbus server resp: ", resp) 129 } 130 131 respRtu, err := s.transport.Encode(s.id, resp) 132 if err != nil { 133 errorCallback(err) 134 continue 135 } 136 137 if s.debug >= 9 { 138 fmt.Println("Modbus server tx: ", test.HexDump(respRtu)) 139 } 140 141 _, err = s.transport.Write(respRtu) 142 if err != nil { 143 errorCallback(err) 144 continue 145 } 146 } 147 }