diff --git a/.github/workflows/go-vet.yml b/.github/workflows/go-full-test.yml similarity index 75% rename from .github/workflows/go-vet.yml rename to .github/workflows/go-full-test.yml index 1ea36c7..3731692 100644 --- a/.github/workflows/go-vet.yml +++ b/.github/workflows/go-full-test.yml @@ -38,7 +38,15 @@ jobs: ${{ runner.os }}-go- - name: Install Dependencies - run: go mod download + run: go mod tidy - - name: Run Go Vet - run: go vet ./... \ No newline at end of file + - name: Build + run: go build -v ./... + + - name: Run tests + run: go test -v ./... + + - name: Run lint + run: | + go install golang.org/x/lint/golint@latest + golint ./... \ No newline at end of file diff --git a/README.md b/README.md index 1bde9e7..5206fd1 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ The library support the following IEC 104 protocol features: go get -u github.com/wendy512/iec104 ``` -- [Client reads and writes values](tests/clien_test.go) +- [Server and client test examples](tests/iec104_test.go) ## License iec104 is based on the [Apache License 2.0](./LICENSE) agreement. diff --git a/README_zh_CN.md b/README_zh_CN.md index 9a10f27..18f6ffe 100644 --- a/README_zh_CN.md +++ b/README_zh_CN.md @@ -33,7 +33,7 @@ IEC 104 是在电力系统和工业自动化领域广泛使用的协议,旨在 go get -u github.com/wendy512/iec104 ``` -- [客户端读和写入值](tests/clien_test.go) +- [服务端和客户端测试示例](tests/iec104_test.go) ## 开源许可 iec104 基于 [Apache License 2.0](./LICENSE) 协议。 diff --git a/client/interface.go b/client/interface.go index e84bbb1..1a7a0a3 100644 --- a/client/interface.go +++ b/client/interface.go @@ -18,6 +18,6 @@ type ASDUCall interface { OnResetProcess(*asdu.ASDU) error // OnDelayAcquisition 延迟获取回复 OnDelayAcquisition(*asdu.ASDU) error - // OnASDU 数据正体 + // OnASDU 数据回复或控制回复 OnASDU(*asdu.ASDU) error } diff --git a/server/core.go b/server/core.go new file mode 100644 index 0000000..785a4cf --- /dev/null +++ b/server/core.go @@ -0,0 +1,73 @@ +package server + +import ( + "github.com/wendy512/go-iecp5/asdu" + "github.com/wendy512/go-iecp5/clog" + "github.com/wendy512/go-iecp5/cs104" + "strconv" +) + +// Settings 连接配置 +type Settings struct { + Host string + Port int + Cfg104 *cs104.Config //104协议规范配置 + Params *asdu.Params //ASDU相关特定参数 + LogCfg *LogCfg +} + +type LogCfg struct { + Enable bool //是否开启log + LogProvider clog.LogProvider +} + +type Server struct { + settings *Settings + cs104Server *cs104.Server +} + +func NewSettings() *Settings { + cfg104 := cs104.DefaultConfig() + return &Settings{ + Host: "localhost", + Port: 2404, + Cfg104: &cfg104, + Params: asdu.ParamsWide, + } +} + +func New(settings *Settings, handler CommandHandler) *Server { + cs104Server := cs104.NewServer(&serverHandler{h: handler}) + cs104Server.SetConfig(*settings.Cfg104) + cs104Server.SetParams(settings.Params) + + logCfg := settings.LogCfg + if logCfg != nil { + cs104Server.LogMode(logCfg.Enable) + cs104Server.SetLogProvider(logCfg.LogProvider) + } + + return &Server{ + settings: settings, + cs104Server: cs104Server, + } +} + +func (s *Server) Start() { + addr := s.settings.Host + ":" + strconv.Itoa(s.settings.Port) + go s.cs104Server.ListenAndServer(addr) +} + +func (s *Server) Stop() { + _ = s.cs104Server.Close() +} + +// SetOnConnectionHandler set on connect handler +func (s *Server) SetOnConnectionHandler(f func(asdu.Connect)) { + s.SetOnConnectionHandler(f) +} + +// SetConnectionLostHandler set connect lost handler +func (s *Server) SetConnectionLostHandler(f func(asdu.Connect)) { + s.SetConnectionLostHandler(f) +} diff --git a/server/handle.go b/server/handle.go new file mode 100644 index 0000000..dcc6b55 --- /dev/null +++ b/server/handle.go @@ -0,0 +1,38 @@ +package server + +import ( + "github.com/wendy512/go-iecp5/asdu" + "time" +) + +type serverHandler struct { + h CommandHandler +} + +func (s *serverHandler) InterrogationHandler(conn asdu.Connect, pack *asdu.ASDU, quality asdu.QualifierOfInterrogation) error { + return s.h.OnInterrogation(conn, pack, quality) +} + +func (s *serverHandler) CounterInterrogationHandler(conn asdu.Connect, pack *asdu.ASDU, quality asdu.QualifierCountCall) error { + return s.h.OnCounterInterrogation(conn, pack, quality) +} + +func (s *serverHandler) ReadHandler(conn asdu.Connect, pack *asdu.ASDU, addr asdu.InfoObjAddr) error { + return s.h.OnRead(conn, pack, addr) +} + +func (s *serverHandler) ClockSyncHandler(conn asdu.Connect, pack *asdu.ASDU, time time.Time) error { + return s.h.OnClockSync(conn, pack, time) +} + +func (s *serverHandler) ResetProcessHandler(conn asdu.Connect, pack *asdu.ASDU, quality asdu.QualifierOfResetProcessCmd) error { + return s.h.OnResetProcess(conn, pack, quality) +} + +func (s *serverHandler) DelayAcquisitionHandler(conn asdu.Connect, pack *asdu.ASDU, msec uint16) error { + return s.h.OnDelayAcquisition(conn, pack, msec) +} + +func (s *serverHandler) ASDUHandler(conn asdu.Connect, pack *asdu.ASDU) error { + return s.h.OnASDU(conn, pack) +} diff --git a/server/interface.go b/server/interface.go new file mode 100644 index 0000000..599003e --- /dev/null +++ b/server/interface.go @@ -0,0 +1,23 @@ +package server + +import ( + "github.com/wendy512/go-iecp5/asdu" + "time" +) + +type CommandHandler interface { + // OnInterrogation 总召唤请求 + OnInterrogation(asdu.Connect, *asdu.ASDU, asdu.QualifierOfInterrogation) error + // OnCounterInterrogation 总计数器请求 + OnCounterInterrogation(asdu.Connect, *asdu.ASDU, asdu.QualifierCountCall) error + // OnRead 读定值请求 + OnRead(asdu.Connect, *asdu.ASDU, asdu.InfoObjAddr) error + // OnClockSync 时钟同步请求 + OnClockSync(asdu.Connect, *asdu.ASDU, time.Time) error + // OnResetProcess 进程重置请求 + OnResetProcess(asdu.Connect, *asdu.ASDU, asdu.QualifierOfResetProcessCmd) error + // OnDelayAcquisition 延迟获取请求 + OnDelayAcquisition(asdu.Connect, *asdu.ASDU, uint16) error + // OnASDU 控制命令请求 + OnASDU(asdu.Connect, *asdu.ASDU) error +} diff --git a/tests/clien_test.go b/tests/client_handle.go similarity index 78% rename from tests/clien_test.go rename to tests/client_handle.go index eb3317e..eb5bf4e 100644 --- a/tests/clien_test.go +++ b/tests/client_handle.go @@ -4,84 +4,8 @@ import ( "fmt" "github.com/wendy512/go-iecp5/asdu" "github.com/wendy512/iec104/client" - "sync" - "testing" - "time" ) -const ( - commonAddr = 1 -) - -func TestClient(t *testing.T) { - settings := client.NewSettings() - settings.Host = "192.168.33.12" - settings.LogCfg = &client.LogCfg{Enable: true} - c := client.New(settings, &clientCall{}) - - wg := sync.WaitGroup{} - wg.Add(1) - c.SetOnConnectHandler(func(c *client.Client) { - // 连接成功以后做的操作 - fmt.Printf("connected %s iec104 server\n", settings.Host) - }) - - // server active确认后回调 - c.SetServerActiveHandler(func(c *client.Client) { - // 发送总召唤 - if err := c.SendInterrogationCmd(commonAddr); err != nil { - t.Errorf("send interrogation cmd error %v\n", err) - t.FailNow() - } - - // 累积量召唤 - if err := c.SendCounterInterrogationCmd(commonAddr); err != nil { - t.Errorf("send counter interrogation cmd error %v\n", err) - t.FailNow() - } - - // read cmd - if err := c.SendReadCmd(commonAddr, 400); err != nil { - t.Errorf("send counter interrogation cmd error %v\n", err) - t.FailNow() - } - - // 时钟同步 - if err := c.SendClockSynchronizationCmd(commonAddr, time.Now()); err != nil { - t.Errorf("send clock sync cmd error %v\n", err) - t.FailNow() - } - - // test cmd - if err := c.SendTestCmd(commonAddr); err != nil { - t.Errorf("send test cmd error %v\n", err) - t.FailNow() - } - - // 单点控制 - if err := c.SendCmd(commonAddr, asdu.C_SC_NA_1, asdu.InfoObjAddr(1000), true); err != nil { - t.Errorf("send single cmd error %v\n", err) - t.FailNow() - } - - // 测试等待回复,不能结束太快 - time.Sleep(time.Second * 10) - wg.Done() - }) - - // Connect后会发送server active - if err := c.Connect(); err != nil { - t.Errorf("client connect error %v\n", err) - t.FailNow() - } - wg.Wait() - - if err := c.Close(); err != nil { - t.Errorf("close error %v\n", err) - t.FailNow() - } -} - type clientCall struct { } diff --git a/tests/iec104_test.go b/tests/iec104_test.go new file mode 100644 index 0000000..631365a --- /dev/null +++ b/tests/iec104_test.go @@ -0,0 +1,87 @@ +package tests + +import ( + "fmt" + "github.com/wendy512/go-iecp5/asdu" + "github.com/wendy512/iec104/client" + "github.com/wendy512/iec104/server" + "sync" + "testing" + "time" +) + +func TestClient(t *testing.T) { + srv := startServer() + settings := client.NewSettings() + settings.LogCfg = &client.LogCfg{Enable: true} + c := client.New(settings, &clientCall{}) + + wg := sync.WaitGroup{} + wg.Add(1) + c.SetOnConnectHandler(func(c *client.Client) { + // 连接成功以后做的操作 + fmt.Printf("connected %s iec104 server\n", settings.Host) + }) + + // server active确认后回调 + c.SetServerActiveHandler(func(c *client.Client) { + //// 发送总召唤 + if err := c.SendInterrogationCmd(commonAddr); err != nil { + t.Errorf("send interrogation cmd error %v\n", err) + t.FailNow() + } + + // 累积量召唤 + if err := c.SendCounterInterrogationCmd(commonAddr); err != nil { + t.Errorf("send counter interrogation cmd error %v\n", err) + t.FailNow() + } + + // read cmd + if err := c.SendReadCmd(commonAddr, 100); err != nil { + t.Errorf("send counter interrogation cmd error %v\n", err) + t.FailNow() + } + + // 时钟同步 + if err := c.SendClockSynchronizationCmd(commonAddr, time.Now()); err != nil { + t.Errorf("send clock sync cmd error %v\n", err) + t.FailNow() + } + + // test cmd + if err := c.SendTestCmd(commonAddr); err != nil { + t.Errorf("send test cmd error %v\n", err) + t.FailNow() + } + + // 单点控制 + if err := c.SendCmd(commonAddr, asdu.C_SC_NA_1, asdu.InfoObjAddr(1000), false); err != nil { + t.Errorf("send single cmd error %v\n", err) + t.FailNow() + } + + // 测试等待回复,不能结束太快 + time.Sleep(time.Second * 10) + wg.Done() + }) + + // Connect后会发送server active + if err := c.Connect(); err != nil { + t.Errorf("client connect error %v\n", err) + t.FailNow() + } + wg.Wait() + + if err := c.Close(); err != nil { + t.Errorf("close error %v\n", err) + t.FailNow() + } + srv.Stop() +} + +func startServer() *server.Server { + s := server.New(server.NewSettings(), &myServerHandler{}) + s.Start() + return s +} diff --git a/tests/server_handle.go b/tests/server_handle.go new file mode 100644 index 0000000..6477f39 --- /dev/null +++ b/tests/server_handle.go @@ -0,0 +1,87 @@ +package tests + +import ( + "github.com/wendy512/go-iecp5/asdu" + "time" +) + +const ( + commonAddr = 1 +) + +type myServerHandler struct { +} + +func (ms *myServerHandler) OnInterrogation(conn asdu.Connect, pack *asdu.ASDU, quality asdu.QualifierOfInterrogation) error { + _ = pack.SendReplyMirror(conn, asdu.ActivationCon) + // TODO + _ = asdu.Single(conn, false, asdu.CauseOfTransmission{Cause: asdu.InterrogatedByStation}, commonAddr, asdu.SinglePointInfo{ + Ioa: 100, + Value: true, + Qds: asdu.QDSGood, + }) + _ = asdu.Double(conn, false, asdu.CauseOfTransmission{Cause: asdu.InterrogatedByStation}, commonAddr, asdu.DoublePointInfo{ + Ioa: 200, + Value: asdu.DPIDeterminedOn, + Qds: asdu.QDSGood, + }) + _ = pack.SendReplyMirror(conn, asdu.ActivationTerm) + return nil +} + +func (ms *myServerHandler) OnCounterInterrogation(conn asdu.Connect, pack *asdu.ASDU, quality asdu.QualifierCountCall) error { + _ = pack.SendReplyMirror(conn, asdu.ActivationCon) + // TODO + _ = asdu.CounterInterrogationCmd(conn, asdu.CauseOfTransmission{Cause: asdu.Activation}, commonAddr, asdu.QualifierCountCall{asdu.QCCGroup1, asdu.QCCFrzRead}) + _ = pack.SendReplyMirror(conn, asdu.ActivationTerm) + return nil +} + +func (ms *myServerHandler) OnRead(conn asdu.Connect, pack *asdu.ASDU, addr asdu.InfoObjAddr) error { + _ = pack.SendReplyMirror(conn, asdu.ActivationCon) + // TODO + _ = asdu.Single(conn, false, asdu.CauseOfTransmission{Cause: asdu.InterrogatedByStation}, commonAddr, asdu.SinglePointInfo{ + Ioa: addr, + Value: true, + Qds: asdu.QDSGood, + }) + _ = pack.SendReplyMirror(conn, asdu.ActivationTerm) + return nil +} + +func (ms *myServerHandler) OnClockSync(conn asdu.Connect, pack *asdu.ASDU, tm time.Time) error { + _ = pack.SendReplyMirror(conn, asdu.ActivationCon) + now := time.Now() + _ = asdu.ClockSynchronizationCmd(conn, asdu.CauseOfTransmission{Cause: asdu.Activation}, commonAddr, now) + _ = pack.SendReplyMirror(conn, asdu.ActivationTerm) + return nil +} + +func (ms *myServerHandler) OnResetProcess(conn asdu.Connect, pack *asdu.ASDU, quality asdu.QualifierOfResetProcessCmd) error { + _ = pack.SendReplyMirror(conn, asdu.ActivationCon) + // TODO + _ = asdu.ResetProcessCmd(conn, asdu.CauseOfTransmission{Cause: asdu.Activation}, commonAddr, asdu.QPRGeneralRest) + _ = pack.SendReplyMirror(conn, asdu.ActivationTerm) + return nil +} + +func (ms *myServerHandler) OnDelayAcquisition(conn asdu.Connect, pack *asdu.ASDU, msec uint16) error { + _ = pack.SendReplyMirror(conn, asdu.ActivationCon) + // TODO + _ = asdu.DelayAcquireCommand(conn, asdu.CauseOfTransmission{Cause: asdu.Activation}, commonAddr, msec) + _ = pack.SendReplyMirror(conn, asdu.ActivationTerm) + return nil +} + +func (ms *myServerHandler) OnASDU(conn asdu.Connect, pack *asdu.ASDU) error { + _ = pack.SendReplyMirror(conn, asdu.ActivationCon) + // TODO + cmd := pack.GetSingleCmd() + _ = asdu.SingleCmd(conn, pack.Type, pack.Coa, pack.CommonAddr, asdu.SingleCommandInfo{ + Ioa: cmd.Ioa, + Value: cmd.Value, + Qoc: cmd.Qoc, + }) + _ = pack.SendReplyMirror(conn, asdu.ActivationCon) + return nil +}