{"id":20655506,"url":"https://github.com/postmannen/modbusgenerator","last_synced_at":"2026-04-18T22:32:30.958Z","repository":{"id":221127360,"uuid":"753517764","full_name":"postmannen/modbusgenerator","owner":"postmannen","description":null,"archived":false,"fork":false,"pushed_at":"2024-02-06T09:39:48.000Z","size":24,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-09T22:39:36.650Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/postmannen.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,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2024-02-06T09:31:15.000Z","updated_at":"2024-02-06T09:39:52.000Z","dependencies_parsed_at":"2024-02-06T10:43:46.647Z","dependency_job_id":"e70fc6a1-2ce3-4430-8f1f-ec78ed94e89e","html_url":"https://github.com/postmannen/modbusgenerator","commit_stats":null,"previous_names":["postmannen/modbusgenerator"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/postmannen/modbusgenerator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/postmannen%2Fmodbusgenerator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/postmannen%2Fmodbusgenerator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/postmannen%2Fmodbusgenerator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/postmannen%2Fmodbusgenerator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/postmannen","download_url":"https://codeload.github.com/postmannen/modbusgenerator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/postmannen%2Fmodbusgenerator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31987813,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T20:23:30.271Z","status":"ssl_error","status_checked_at":"2026-04-18T20:23:29.375Z","response_time":103,"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":[],"created_at":"2024-11-16T18:11:29.299Z","updated_at":"2026-04-18T22:32:30.911Z","avatar_url":"https://github.com/postmannen.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/tbrandon/mbserver.svg?branch=master)](https://travis-ci.org/tbrandon/mbserver)\n[![Coverage Status](http://codecov.io/github/tbrandon/mbserver/coverage.svg?branch=master)](http://codecov.io/github/tbrandon/mbserver?branch=master)\n[![GoDoc](https://godoc.org/github.com/tbrandon/mbserver?status.svg)](https://godoc.org/github.com/tbrandon/mbserver)\n[![Software License](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/tbrandon/mbserver/blob/master/LICENSE)\n\n# Golang Modbus Server (Slave)\n\nThe Golang Modbus Server (Slave) responds to the following Modbus function requests:\n\nBit access:\n- Read Discrete Inputs\n- Read Coils\n- Write Single Coil\n- Write Multiple Coils\n\n16-bit acess:\n- Read Input Registers\n- Read Multiple Holding Registers\n- Write Single Holding Register\n- Write Multiple Holding Registers\n\nTCP and serial RTU access is supported.\nAlso added support for RTU over TCP.\n\nThe server internally allocates memory for 65536 coils, 65536 discrete inputs, 653356 holding registers and 65536 input registers.\nOn start, all values are initialzied to zero.  Modbus requests are processed in the order they are received and will not overlap/interfere with each other.\n\nThe golang [mbserver documentation](https://godoc.org/github.com/tbrandon/mbserver).\n\n## Example Modbus TCP Server\n\nCreate a Modbus TCP Server (Slave):\n\n```\npackage main\n\nimport (\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/tbrandon/mbserver\"\n)\n\nfunc main() {\n\tserv := mbserver.NewServer()\n\terr := serv.ListenTCP(\"127.0.0.1:1502\")\n\tif err != nil {\n\t\tlog.Printf(\"%v\\n\", err)\n\t}\n\tdefer serv.Close()\n\n\t// Wait forever\n\tfor {\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n```\nThe server will continue to listen until killed (\u0026lt;ctrl\u003e-c).\nModbus typically uses port 502 (standard users require special permissions to listen on port 502). Change the port number as required.\nChange the address to 0.0.0.0 to listen on all network interfaces.\n\nAn example of a client writing and reading holding regsiters:\n```\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/goburrow/modbus\"\n)\n\nfunc main() {\n\thandler := modbus.NewTCPClientHandler(\"localhost:1502\")\n\t// Connect manually so that multiple requests are handled in one session\n\terr := handler.Connect()\n\tdefer handler.Close()\n\tclient := modbus.NewClient(handler)\n\n\t_, err = client.WriteMultipleRegisters(0, 3, []byte{0, 3, 0, 4, 0, 5})\n\tif err != nil {\n\t\tfmt.Printf(\"%v\\n\", err)\n\t}\n\n\tresults, err := client.ReadHoldingRegisters(0, 3)\n\tif err != nil {\n\t\tfmt.Printf(\"%v\\n\", err)\n\t}\n\tfmt.Printf(\"results %v\\n\", results)\n}\n\nOutputs:\nresults [0 3 0 4 0 5]\n```\n\n## Example Listening on Multiple TCP Ports and Serial Devices\n\nThe Golang Modbus Server can listen on multiple TCP ports and serial devices.\nIn the following example, the Modbus server will be configured to listen on\n127.0.0.1:1502, 0.0.0.0:3502, /dev/ttyUSB0 and /dev/ttyACM0\n\n```\n\tserv := mbserver.NewServer()\n\terr := serv.ListenTCP(\"127.0.0.1:1502\")\n\tif err != nil {\n\t\tlog.Printf(\"%v\\n\", err)\n\t}\n\n\terr := serv.ListenTCP(\"0.0.0.0:3502\")\n\tif err != nil {\n\t\tlog.Printf(\"%v\\n\", err)\n\t}\n\n\terr := s.ListenRTU(\u0026serial.Config{\n\t\tAddress:  \"/dev/ttyUSB0\",\n\t\tBaudRate: 115200,\n\t\tDataBits: 8,\n\t\tStopBits: 1,\n\t\tParity:   \"N\",\n\t\tTimeout:  10 * time.Second})\n\tif err != nil {\n\t\tt.Fatalf(\"failed to listen, got %v\\n\", err)\n\t}\n\n\terr := s.ListenRTU(\u0026serial.Config{\n\t\tAddress:  \"/dev/ttyACM0\",\n\t\tBaudRate: 9600,\n\t\tDataBits: 8,\n\t\tStopBits: 1,\n\t\tParity:   \"N\",\n\t\tTimeout:  10 * time.Second,\n\t\tRS485: serial.RS485Config{\n\t\t\tEnabled: true,\n\t\t\tDelayRtsBeforeSend: 2 * time.Millisecond\n\t\t\tDelayRtsAfterSend: 3 * time.Millisecond\n\t\t\tRtsHighDuringSend: false,\n\t\t\tRtsHighAfterSend: false,\n\t\t\tRxDuringTx: false\n\t\t\t})\n\tif err != nil {\n\t\tt.Fatalf(\"failed to listen, got %v\\n\", err)\n\t}\n\n\tdefer serv.Close()\n```\n\nInformation on [serial port settings](https://godoc.org/github.com/goburrow/serial).\n\n## Server Customization\n\n RegisterFunctionHandler allows the default server functionality to be overridden for a Modbus function code.\n ```\nfunc (s *Server) RegisterFunctionHandler(funcCode uint8, function func(*Server, Framer) ([]byte, *Exception))\n ```\n\nExample of overriding the default ReadDiscreteInputs funtion:\n\n```\nserv := NewServer()\n\n// Override ReadDiscreteInputs function.\nserv.RegisterFunctionHandler(2,\n    func(s *Server, frame Framer) ([]byte, *Exception) {\n        register, numRegs, endRegister := frame.registerAddressAndNumber()\n        // Check the request is within the allocated memory\n        if endRegister \u003e 65535 {\n            return []byte{}, \u0026IllegalDataAddress\n        }\n        dataSize := numRegs / 8\n        if (numRegs % 8) != 0 {\n            dataSize++\n        }\n        data := make([]byte, 1+dataSize)\n        data[0] = byte(dataSize)\n        for i := range s.DiscreteInputs[register:endRegister] {\n            // Return all 1s, regardless of the value in the DiscreteInputs array.\n            shift := uint(i) % 8\n            data[1+i/8] |= byte(1 \u003c\u003c shift)\n        }\n        return data, \u0026Success\n    })\n\n// Start the server.\nerr := serv.ListenTCP(\"localhost:4321\")\nif err != nil {\n    log.Printf(\"%v\\n\", err)\n    return\n}\ndefer serv.Close()\n\n// Wait for the server to start\ntime.Sleep(1 * time.Millisecond)\n\n// Example of a client reading from the server started above.\n// Connect a client.\nhandler := modbus.NewTCPClientHandler(\"localhost:4321\")\nerr = handler.Connect()\nif err != nil {\n    log.Printf(\"%v\\n\", err)\n    return\n}\ndefer handler.Close()\nclient := modbus.NewClient(handler)\n\n// Read discrete inputs.\nresults, err := client.ReadDiscreteInputs(0, 16)\nif err != nil {\n    log.Printf(\"%v\\n\", err)\n}\n\nfmt.Printf(\"results %v\\n\", results)\n```\nOutput:\n```\nresults [255 255]\n```\n\n## Benchmarks\n\nQuanitify server read/write performance.  Benchmarks are for Modbus TCP operations.\n\nRun benchmarks:\n```\n$ go test -bench=.\nBenchmarkModbusWrite1968MultipleCoils-8            50000             30912 ns/op\nBenchmarkModbusRead2000Coils-8                     50000             27875 ns/op\nBenchmarkModbusRead2000DiscreteInputs-8            50000             27335 ns/op\nBenchmarkModbusWrite123MultipleRegisters-8        100000             22655 ns/op\nBenchmarkModbusRead125HoldingRegisters-8          100000             21117 ns/op\nPASS\n```\nOperations per second are higher when requests are not forced to be  synchronously processed.\nIn the case of simultaneous client access, synchronous Modbus request processing prevents data corruption.\n\nTo understand performanc limitations, create a CPU profile graph for the WriteMultipleCoils benchmark:\n```\ngo test -bench=.MultipleCoils -cpuprofile=cpu.out\ngo tool pprof modbus-server.test cpu.out\n(pprof) web\n```\n\n## Race Conditions\n\nThere is a [known](https://github.com/golang/go/issues/10001) race condition in the code relating to calling Serial Read() and Close() functions in different go routines.\n\nTo check for race conditions, run:\n```\ngo test --race\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpostmannen%2Fmodbusgenerator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpostmannen%2Fmodbusgenerator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpostmannen%2Fmodbusgenerator/lists"}