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  }