{"id":47116954,"url":"https://github.com/xiegeo/modbusone","last_synced_at":"2026-03-12T19:04:01.576Z","repository":{"id":49730843,"uuid":"70261609","full_name":"xiegeo/modbusone","owner":"xiegeo","description":"A modbus library for Go, with unified client and server APIs. One implementation to rule them all.","archived":false,"fork":false,"pushed_at":"2025-06-16T08:50:30.000Z","size":316,"stargazers_count":65,"open_issues_count":0,"forks_count":10,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-06-16T09:57:24.001Z","etag":null,"topics":["client","golang","master","modbus","rtu","server","slave"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xiegeo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-10-07T15:58:50.000Z","updated_at":"2024-12-21T06:30:46.000Z","dependencies_parsed_at":"2022-09-26T20:31:11.349Z","dependency_job_id":null,"html_url":"https://github.com/xiegeo/modbusone","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/xiegeo/modbusone","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xiegeo%2Fmodbusone","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xiegeo%2Fmodbusone/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xiegeo%2Fmodbusone/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xiegeo%2Fmodbusone/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xiegeo","download_url":"https://codeload.github.com/xiegeo/modbusone/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xiegeo%2Fmodbusone/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30439361,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-12T14:34:45.044Z","status":"ssl_error","status_checked_at":"2026-03-12T14:09:33.793Z","response_time":114,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["client","golang","master","modbus","rtu","server","slave"],"created_at":"2026-03-12T19:03:57.708Z","updated_at":"2026-03-12T19:04:01.569Z","avatar_url":"https://github.com/xiegeo.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ModbusOne [![Go Reference](https://pkg.go.dev/badge/github.com/xiegeo/modbusone?utm_source=godoc#section-documentation.svg)](https://pkg.go.dev/github.com/xiegeo/modbusone?utm_source=godoc#section-documentation)\nA Modbus library for Go, with unified client and server APIs.\nOne implementation to rule them all.\n\u003cdetails\u003e\n  \u003csummary\u003eExample\u003c/summary\u003e\n\n[embedmd]:# (examples_test.go /\\/\\/ handlerGenerator/ /end readme example/)\n```go\n// handlerGenerator returns ProtocolHandlers that interact with our application.\n// In this example, we are only using Holding Registers.\nfunc handlerGenerator(name string) modbusone.ProtocolHandler {\n    return \u0026modbusone.SimpleHandler{\n        ReadHoldingRegisters: func(address, quantity uint16) ([]uint16, error) {\n            fmt.Printf(\"%v ReadHoldingRegisters from %v, quantity %v\\n\",\n                name, address, quantity)\n            r := make([]uint16, quantity)\n            // Application code that fills in r here.\n            return r, nil\n        },\n        WriteHoldingRegisters: func(address uint16, values []uint16) error {\n            fmt.Printf(\"%v WriteHoldingRegisters from %v, quantity %v\\n\",\n                name, address, len(values))\n            // Application code here.\n            return nil\n        },\n        OnErrorImp: func(req modbusone.PDU, errRep modbusone.PDU) {\n            fmt.Printf(\"%v received error:%x in request:%x\", name, errRep, req)\n        },\n    }\n}\n\n// serial is a fake serial port.\ntype serial struct {\n    io.ReadCloser\n    io.WriteCloser\n}\n\nfunc newInternalSerial() (io.ReadWriteCloser, io.ReadWriteCloser) {\n    r1, w1 := io.Pipe()\n    r2, w2 := io.Pipe()\n    return \u0026serial{ReadCloser: r1, WriteCloser: w2}, \u0026serial{ReadCloser: r2, WriteCloser: w1}\n}\n\nfunc (s *serial) Close() error {\n    s.ReadCloser.Close()\n    return s.WriteCloser.Close()\n}\n\nfunc Example_serialPort() {\n    // Server id and baudRate, for Modbus over serial port.\n    id := byte(1)\n    baudRate := int64(19200)\n\n    // Open serial connections:\n    clientSerial, serverSerial := newInternalSerial()\n    // Normally we want to open a serial connection from serial.OpenPort\n    // such as github.com/tarm/serial. modbusone can take any io.ReadWriteCloser,\n    // so we created two that talks to each other for demonstration here.\n\n    // SerialContext adds baudRate information to calculate\n    // the duration that data transfers should takes.\n    // It also records Stats of read and dropped packets.\n    clientSerialContext := modbusone.NewSerialContext(clientSerial, baudRate)\n    serverSerialContext := modbusone.NewSerialContext(serverSerial, baudRate)\n\n    // You can create either a client or a server from a SerialContext and an id.\n    client := modbusone.NewRTUClient(clientSerialContext, id)\n    server := modbusone.NewRTUServer(serverSerialContext, id)\n\n    useClientAndServer(client, server, id) // follow the next function\n\n    // Output:\n    // reqs count: 2\n    // reqs count: 3\n    // server ReadHoldingRegisters from 0, quantity 125\n    // client WriteHoldingRegisters from 0, quantity 125\n    // server ReadHoldingRegisters from 125, quantity 75\n    // client WriteHoldingRegisters from 125, quantity 75\n    // client ReadHoldingRegisters from 1000, quantity 100\n    // server WriteHoldingRegisters from 1000, quantity 100\n    // server ReadHoldingRegisters from 0, quantity 125\n    // client WriteHoldingRegisters from 0, quantity 125\n    // server ReadHoldingRegisters from 125, quantity 75\n    // client WriteHoldingRegisters from 125, quantity 75\n    // client ReadHoldingRegisters from 1000, quantity 100\n    // server WriteHoldingRegisters from 1000, quantity 100\n    // serve terminated: io: read/write on closed pipe\n}\n\nfunc useClientAndServer(client modbusone.Client, server modbusone.ServerCloser, id byte) {\n    termChan := make(chan error)\n\n    // Serve is blocking until the serial connection has io errors or is closed.\n    // So we use a goroutine to start it and continue setting up our demo.\n    go client.Serve(handlerGenerator(\"client\"))\n    go func() {\n        // A server is Started to same way as a client\n        err := server.Serve(handlerGenerator(\"server\"))\n        // Do something with the err here.\n        // For a command line app, you probably want to terminate.\n        // For a service, you probably want to wait until you can open the serial port again.\n        termChan \u003c- err\n    }()\n    defer client.Close()\n    defer server.Close()\n\n    // If you only need to support server side, then you are done.\n    // If you need to support client side, then you need to make requests.\n    clientDoTransactions(client, id) // see following function\n\n    // Clean up\n    server.Close()\n    fmt.Println(\"serve terminated:\", \u003c-termChan)\n}\n\nfunc clientDoTransactions(client modbusone.Client, id byte) {\n    // start by building some requests\n    startAddress := uint16(0)\n    quantity := uint16(200)\n    reqs, err := modbusone.MakePDURequestHeaders(modbusone.FcReadHoldingRegisters,\n        startAddress, quantity, nil)\n    if err != nil {\n        fmt.Println(err) // if what you asked for is not possible.\n    }\n    // Larger than allowed requests are split to many packets.\n    fmt.Println(\"reqs count:\", len(reqs))\n\n    // We can add more requests, even of different types.\n    // The last nil is replaced by the reqs to append to.\n    startAddress = uint16(1000)\n    quantity = uint16(100)\n    reqs, err = modbusone.MakePDURequestHeaders(modbusone.FcWriteMultipleRegisters,\n        startAddress, quantity, reqs)\n    if err != nil {\n        fmt.Println(err)\n    }\n    fmt.Println(\"reqs count:\", len(reqs))\n\n    // Range over the requests to handle each individually,\n    for _, r := range reqs {\n        err = client.DoTransaction(r)\n        if err != nil {\n            fmt.Println(err, \"on\", r) // The server timed out, or the connection was closed.\n        }\n    }\n    // or just do them all at once. Notice that reqs can be reused.\n    n, err := modbusone.DoTransactions(client, id, reqs)\n    if err != nil {\n        fmt.Println(err, \"on\", reqs[n])\n    }\n}\n\nfunc Example_tcp() {\n    // TCP address of the host\n    host := \"127.2.9.1:12345\"\n\n    // Default server id\n    id := byte(1)\n\n    // Open server tcp listener:\n    listener, err := net.Listen(\"tcp\", host)\n    if err != nil {\n        fmt.Println(err)\n        return\n    }\n\n    // Connect to server:\n    conn, err := net.Dial(\"tcp\", host)\n    if err != nil {\n        fmt.Println(err)\n        return\n    }\n\n    // You can create either a client or a server\n    client := modbusone.NewTCPClient(conn, 0)\n    server := modbusone.NewTCPServer(listener)\n\n    // shared example code with serial port\n    useClientAndServer(client, server, id)\n\n    // Output:\n    // reqs count: 2\n    // reqs count: 3\n    // server ReadHoldingRegisters from 0, quantity 125\n    // client WriteHoldingRegisters from 0, quantity 125\n    // server ReadHoldingRegisters from 125, quantity 75\n    // client WriteHoldingRegisters from 125, quantity 75\n    // client ReadHoldingRegisters from 1000, quantity 100\n    // server WriteHoldingRegisters from 1000, quantity 100\n    // server ReadHoldingRegisters from 0, quantity 125\n    // client WriteHoldingRegisters from 0, quantity 125\n    // server ReadHoldingRegisters from 125, quantity 75\n    // client WriteHoldingRegisters from 125, quantity 75\n    // client ReadHoldingRegisters from 1000, quantity 100\n    // server WriteHoldingRegisters from 1000, quantity 100\n    // serve terminated: accept tcp 127.2.9.1:12345: use of closed network connection\n}\n\n// end readme example\n```\n\n\u003c/details\u003e\nFor more usage examples, see examples/memory, which is a command line application that can be used as either a server or a client.\n\n## Architecture\n\n![modbusone architecture](./modbusone_architecture.svg)\n\n## Why\n\nThere exist Modbus libraries for Go, such as goburrow/modbus and flosse/go-modbus.\nHowever they do not include any server APIs. Even if server function is implemented, user code will have to be written separately to support running both as client and server.\n\nIn my use case, client/server should be interchangeable. User code should worry about how to handle the translation of MODBUS data model to application logic. The only difference is the client also initiate requests.\n\nThis means that a remote function call like API, which is effective as a client-side API, is insufficient.\n\nInstead, a callback based API (like http server handler) is used for both server and client.\n\n## Implemented\n\n- Serial RTU\n- Modbus over TCP\n- Function Codes 1-6,15,16\n- Server and Client API\n- Server and Client Tester (examples/memory)\n\n## Development\n\nThis project and API is stable, and I am using it in production.\n\nMy primary usage is RTU (over RS-485). TCP is also supported. Others may or may not be implemented in the future.\n\nContribution to new or existing functionally, or just changing a private identifier public are welcome, as well as documentation, test, example code or any other improvements.\n\nDevelopment tools:\n\n- `go generate` runs `embedmd` to copy parts of the examples_test.go to this readme file\n- `golangci-lint run` for improvement hints. Ideally there are no warnings.\n- Use `go test -race -count=5 ./...` pre release.\n\n## Breaking Changes\n\n2022-09-09 v1.0.0\n\nV1 release has the following depreciated identifiers removed:\n\n- The `Server` interface is removed. Use `ServerCloser` instead.\n- Public global variable `DebugOut` is changed to private. Use `SetDebugOut(w io.Writer)` instead for thread safety.\n- Type alias `type ProtocalHandler = ProtocolHandler` removed.\n- Function redirect from `GetRTUBidirectionSizeFromHeader` to `GetRTUBidirectionalSizeFromHeader` removed.\n- Function redirect from `NewRTUCLient` to `NewRTUClient` removed.\n\n\n2018-09-27 v0.2.0\n\n- NewRTUPacketReader returns PacketReader interface instead of io.Reader. When a new RTU server or client receives a SerialContext, it will test if it is also a PacketReader, and only create a new NewRTUPacketReader if not.\n- (client/server).Serve() now also closes themselves when returned. This avoids some potentially bad usages. Before, the behavior was undefined.\n\n2017-06-13 pre-v0.1.0\n\n- Removed dependency on goburrow/serial. All serial connections should be created with NewSerialContext, which can accept any ReadWriteCloser\n\n## Challenges\n\nCompatibility with a wide range of serial hardware/drivers. (good)\n\nCompatibility with existing Modbus environments, including non-compliance and extensions. (good)\n\nRecover from transmission errors and timeouts, to work continuously unattended. (good)\n\nBetter test coverage that also tests error conditions. (todo)\n\nFuzz testing. (todo)\n\n## Failover mode\n\nTLDR: do not use.\n\nFailover has landed in v0.2.0, but it should be considered less stable than the other parts.\n\nIn mission-critical applications, or anywhere hardware redundancy is cheaper than downtime, having a standby system taking over in case of the failure of the primary system is desirable.\n\nIdeally, failover is implemented in the application level, which speaks over two serial ports simultaneously, only acting on the values from one of the ports at a time. However, this may not always be possible. A \"foreign\" application, which you have no control over, might not have this feature. As such, failover mode attempts to addresses this by allowing two separate hardware devices sharing a single serial bus to appear as a single device. This failover mode is outside the design of the original Modbus protocol.\n\nThe basic operation of failover mode is to stay quiet on the port until the primary fails. While staying quiet, it relays all reads and writes to the application side as if it is the primary. This allows the application to stay in sync for a hot switch over when the primary fails. While on standby and in Client (Master) mode, writes may be received by the handler that is not initiated by that Client.\n\n## Definitions\n\n\u003cdl\u003e\n\u003cdt\u003eClient/Server\n  \u003cdd\u003eAlso called Master/Slave in the context of serial communication.\n\u003cdt\u003ePDU\n  \u003cdd\u003eProtocol data unit, MODBUS application protocol, include function code and data. The same format no matter what the lower level protocol is.\n\u003cdt\u003eADU\n  \u003cdd\u003eApplication data unit, PDU prepended with Server addresses and postpended with error check, as needed.\n\u003cdt\u003eRTU\n  \u003cdd\u003eRemote terminal unit, in the context of Modbus, it is a raw wire protocol delimited by a delay. RTU is an example of ADU.\n\u003c/dl\u003e\n\n## License\n\nThis library is distributed under the BSD-style license found in the LICENSE file.\n\nSee also licenses folder for origins of large blocks of source code.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxiegeo%2Fmodbusone","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxiegeo%2Fmodbusone","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxiegeo%2Fmodbusone/lists"}