liuxiulin hai 1 ano
achega
206c78aa8a
Modificáronse 15 ficheiros con 1105 adicións e 0 borrados
  1. 8 0
      .idea/.gitignore
  2. 8 0
      .idea/modules.xml
  3. 6 0
      .idea/vcs.xml
  4. 9 0
      .idea/water-system-gateway.iml
  5. BIN=BIN
      bin/linux_amd64/water-gateway
  6. BIN=BIN
      bin/linux_amd64/water-system
  7. 12 0
      config/config.toml
  8. 40 0
      go.mod
  9. 212 0
      go.sum
  10. 86 0
      main.go
  11. 128 0
      protocol/procotol.go
  12. 265 0
      server/client.go
  13. 68 0
      server/crc.go
  14. 184 0
      server/modbus.go
  15. 79 0
      server/server.go

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/water-system-gateway.iml" filepath="$PROJECT_DIR$/.idea/water-system-gateway.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 9 - 0
.idea/water-system-gateway.iml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="Go" enabled="true" />
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

BIN=BIN
bin/linux_amd64/water-gateway


BIN=BIN
bin/linux_amd64/water-system


+ 12 - 0
config/config.toml

@@ -0,0 +1,12 @@
+[Server]
+Addr = "0.0.0.0"
+Port = 9099
+RunMode = "debug"
+[Sparrow]
+Server = "http://114.115.211.247:8082"
+ProductKey = "2295f7bd46e0d0a235c48a22a9a210a879641d3851ed94b9c35893a4bde92372fef72f7c50dd2c13cd20d34ab351010c"
+DeviceCode = "Water-System"
+Debug = true
+
+
+

+ 40 - 0
go.mod

@@ -0,0 +1,40 @@
+module water-system-gateway
+
+go 1.21
+
+require (
+	github.com/gogf/gf/v2 v2.2.2
+	sparrow-sdk v1.0.0
+)
+
+require (
+	github.com/BurntSushi/toml v1.3.2 // indirect
+	github.com/cespare/xxhash/v2 v2.1.2 // indirect
+	github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28 // indirect
+	github.com/clbanning/mxj/v2 v2.7.0 // indirect
+	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+	github.com/eclipse/paho.mqtt.golang v1.3.5 // indirect
+	github.com/fatih/color v1.16.0 // indirect
+	github.com/fsnotify/fsnotify v1.7.0 // indirect
+	github.com/go-logr/logr v1.2.3 // indirect
+	github.com/go-logr/stdr v1.2.2 // indirect
+	github.com/go-redis/redis/v8 v8.11.5 // indirect
+	github.com/gogf/gf v1.16.9 // indirect
+	github.com/gomodule/redigo v1.8.5 // indirect
+	github.com/gorilla/websocket v1.5.1 // indirect
+	github.com/grokify/html-strip-tags-go v0.1.0 // indirect
+	github.com/magiconair/properties v1.8.7 // indirect
+	github.com/mattn/go-colorable v0.1.13 // indirect
+	github.com/mattn/go-isatty v0.0.20 // indirect
+	github.com/mattn/go-runewidth v0.0.9 // indirect
+	github.com/olekukonko/tablewriter v0.0.5 // indirect
+	go.opentelemetry.io/otel v1.14.0 // indirect
+	go.opentelemetry.io/otel/sdk v1.14.0 // indirect
+	go.opentelemetry.io/otel/trace v1.14.0 // indirect
+	golang.org/x/net v0.24.0 // indirect
+	golang.org/x/sys v0.19.0 // indirect
+	golang.org/x/text v0.14.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)
+
+replace sparrow-sdk v1.0.0 => gogs.yehaoji.cn/yongxu/sparrow-sdk.git v1.1.6

+ 212 - 0
go.sum

@@ -0,0 +1,212 @@
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
+github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
+github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28 h1:LdXxtjzvZYhhUaonAaAKArG3pyC67kGL3YY+6hGG8G4=
+github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
+github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
+github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
+github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/eclipse/paho.mqtt.golang v1.3.5 h1:sWtmgNxYM9P2sP+xEItMozsR3w0cqZFlqnNN1bdl41Y=
+github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc=
+github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
+github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
+github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
+github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
+github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
+github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
+github.com/gogf/gf v1.16.6/go.mod h1:4LoHfEBl2jbVmZpVx+qk2La3zWr1V315FtF2PVZuyQ8=
+github.com/gogf/gf v1.16.9 h1:Q803UmmRo59+Ws08sMVFOcd8oNpkSWL9vS33hlo/Cyk=
+github.com/gogf/gf v1.16.9/go.mod h1:8Q/kw05nlVRp+4vv7XASBsMe9L1tsVKiGoeP2AHnlkk=
+github.com/gogf/gf/v2 v2.2.2 h1:ew4k/VSr/gcPdMZcI8/HZYBYPjB0KOk6bQqA61M8bYE=
+github.com/gogf/gf/v2 v2.2.2/go.mod h1:thvkyb43RWUu/m05sRm4CbH9r7t7/FrW2M56L9Ystwk=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/gomodule/redigo v1.8.5 h1:nRAxCa+SVsyjSBrtZmG/cqb6VbTmuRzpg/PoTFlpumc=
+github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
+github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
+github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
+github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
+github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
+github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
+github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
+github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
+github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
+github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
+github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
+github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
+github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
+github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+go.opentelemetry.io/otel v1.0.0-RC2/go.mod h1:w1thVQ7qbAy8MHb0IFj8a5Q2QU0l2ksf8u/CN8m3NOM=
+go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg=
+go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
+go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM=
+go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU=
+go.opentelemetry.io/otel/oteltest v1.0.0-RC2/go.mod h1:kiQ4tw5tAL4JLTbcOYwK1CWI1HkT5aiLzHovgOVnz/A=
+go.opentelemetry.io/otel/sdk v1.7.0/go.mod h1:uTEOTwaqIVuTGiJN7ii13Ibp75wJmYUDe374q6cZwUU=
+go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY=
+go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDsnjhvyZCALM=
+go.opentelemetry.io/otel/trace v1.0.0-RC2/go.mod h1:JPQ+z6nNw9mqEGT8o3eoPTdnNI+Aj5JcxEsVGREIAy4=
+go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs=
+go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
+go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M=
+go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8=
+gogs.yehaoji.cn/yongxu/sparrow-sdk.git v1.1.6 h1:eWYj9ZjaKC2p8mxBgCIZgrpNZXQDMN9R+J1d2cATJdI=
+gogs.yehaoji.cn/yongxu/sparrow-sdk.git v1.1.6/go.mod h1:hWw7D5hrW8f8cOKKdhtlt8HQbdfD2o6PllWMhs0BdQs=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
+golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
+golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
+golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

+ 86 - 0
main.go

@@ -0,0 +1,86 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"github.com/gogf/gf/v2/encoding/gjson"
+	"github.com/gogf/gf/v2/os/glog"
+	"github.com/gogf/gf/v2/os/gproc"
+	"os"
+	"sparrow-sdk/config"
+	"sparrow-sdk/protocal"
+	gatewayV2 "sparrow-sdk/v2"
+	"water-system-gateway/protocol"
+	"water-system-gateway/server"
+)
+
+func main() {
+	ctx := context.Background()
+	err := glog.SetLevelStr(protocol.GetConfig("Server.RunMode").String())
+	if err != nil {
+		panic(err)
+	}
+	gw := gatewayV2.NewGateway(&config.Config{
+		SparrowServer: protocol.GetConfig("Sparrow.Server").String(),
+		ProductKey:    protocol.GetConfig("Sparrow.ProductKey").String(),
+		Protocol:      "mqtt",
+		DeviceCode:    protocol.GetConfig("Sparrow.DeviceCode").String(),
+		Version:       "1.0.0",
+		Debug:         protocol.GetConfig("Sparrow.Debug").Bool(),
+	})
+	if _, err = gw.Register(); err != nil {
+		panic(err)
+	}
+	if _, err = gw.Authentication(); err != nil {
+		panic(err)
+	}
+	// 通用指令回调
+	gw.SetReportCommandCallback(func(deviceCode, subId string) error {
+		return nil
+	})
+	go gw.Connect()
+
+	srv := server.NewServer(
+		ctx,
+		protocol.GetConfig("Server.Addr").String(),
+		protocol.GetConfig("Server.Port").Int(),
+		gw,
+	)
+	go func() {
+		if err := srv.Start(); err != nil {
+			panic(err)
+		}
+	}()
+	closeReportChan := make(chan struct{})
+	go func() {
+		for {
+			select {
+			case msg := <-gw.RecvCommand():
+				fmt.Println(msg.Data.Cmd)
+			case <-closeReportChan:
+				return
+			}
+		}
+	}()
+	if err = gw.RegisterCommand("waterSystemControl", func(msg protocal.CloudSend) error {
+		var params protocol.ControlArgs
+		j := gjson.New(msg.Data.Params)
+		err = j.Scan(&params)
+		glog.Debugf(ctx, "指令:%s, 子设备Id:%s, 参数:%v", msg.Data.Cmd, msg.SubDeviceId, params)
+		client := srv.GetClient(msg.SubDeviceId)
+		if client != nil {
+			if err = client.WaterSystemControl(uint16(params.Address), params.Value); err != nil {
+				glog.Errorf(ctx, "执行命令出错:%s", err.Error())
+				return err
+			}
+		}
+		return nil
+	}); err != nil {
+		panic(err)
+	}
+	gproc.AddSigHandlerShutdown(func(sig os.Signal) {
+		gw.Close()
+		srv.Stop()
+	})
+	gproc.Listen()
+}

+ 128 - 0
protocol/procotol.go

@@ -0,0 +1,128 @@
+package protocol
+
+import (
+	"context"
+	"github.com/gogf/gf/v2/frame/g"
+)
+
+type WaterSystem struct {
+	WaterHeaderStatus
+	WaterCollector
+	OutdoorUnit
+}
+
+type WaterHeaderStatus struct {
+	Power1             int     `json:"power_1"`             // 分水器-1开关 1关 0开
+	Power2             int     `json:"power_2"`             // 分水器-2开关 1关 0开
+	Power3             int     `json:"power_3"`             // 分水器-3开关 1关 0开
+	Power4             int     `json:"power_4"`             // 分水器-4开关 1关 0开
+	Power5             int     `json:"power_5"`             // 分水器-5开关 1关 0开
+	Power6             int     `json:"power_6"`             // 分水器-6开关 1关 0开
+	Power7             int     `json:"power_7"`             // 分水器-7开关 1关 0开
+	Power8             int     `json:"power_8"`             // 分水器-8开关 1关 0开
+	HeaderPower        int     `json:"header_power"`        // 运行状态 1开 0关
+	InletPressure      float32 `json:"inlet_pressure"`      // 进水压力
+	OutletPressure     float32 `json:"outlet_pressure"`     // 出水压力
+	PressureDifference float32 `json:"pressure_difference"` // 压力差
+	FaultCode          int     `json:"fault_code"`          // 故障码
+	FillValve          int     `json:"fill_valve"`          // 补水阀 0-关阀; 1-打开
+	/*
+		0-第 0 档(全关)
+		1-第 1 档(30%)
+		2-第 2 挡(40%)
+		3-第 3 挡(50%)
+		4-第 4 挡(60%)
+		5-第 5 挡(70%)
+		6-第 6 档(80%)
+		7-第 7 档(全开)
+	*/
+	BypassValve            int     `json:"bypass_valve"`             // 旁通阀
+	SetInletWaterPre       float32 `json:"set_inlet_water_pre"`      // 设定出水压力值
+	SetMaxDifferencePre    float32 `json:"set_max_difference_pre"`   // 设定最大压差值
+	SetMinDifferencePre    float32 `json:"set_min_difference_pre"`   // 设定最小压差值
+	HeaderMode             int     `json:"header_mode"`              // 模式 0-手动; 1-自动; 2-调试;
+	PrimaryPumpStatus      int     `json:"primary_pump_status"`      // 一级泵运行状态 0-关闭; 1-打开;
+	SecondaryPumpStatus    int     `json:"secondary_pump_status"`    // 二级泵运行状态 0-关闭; 1-打开;
+	PrimaryPumpFrequency   float32 `json:"primary_pump_frequency"`   // 一级泵运行频率
+	SecondaryPumpFrequency float32 `json:"secondary_pump_frequency"` // 二级泵运行频率
+	WetNodeInput1          int     `json:"wet_node_input_1"`         // 湿节点信号输入1 0-断开; 1-闭合;
+	WetNodeInput2          int     `json:"wet_node_input_2"`         // 湿节点信号输入2 0-断开; 1-闭合;
+	WetNodeInput           int     `json:"wet_node_input"`           // 湿节点信号输入 0-断开; 1-闭合;
+	DryNodeInput           int     `json:"dry_node_input"`           // 干节点信号输入 0-断开; 1-闭合;
+	PrimaryOutletTemp      float32 `json:"primary_outlet_temp"`      // 一级出水温度
+	PrimaryReturnTemp      float32 `json:"primary_return_temp"`      // 一级回水温度
+	SecondaryOutletTemp    float32 `json:"secondary_outlet_temp"`    // 二级出水温度
+	SecondaryReturnTemp    float32 `json:"secondary_return_temp"`    // 二级回水温度
+}
+
+type WaterCollector struct {
+	CollectorPower                         int     `json:"collector_power"`                             // 开关机设定
+	PumpStationPower                       int     `json:"pump_station_power"`                          // 泵站开关设定
+	DeviceReset                            int     `json:"device_reset"`                                // 设备软件复位
+	CollectorMode                          int     `json:"collector_mode"`                              // 模式
+	SetTargetOutletPressure                float32 `json:"set_target_outlet_pressure"`                  // 目标出水压力设定
+	SetMaxiPressureDifference              float32 `json:"set_maxi_pressure_difference"`                // 最大压差设定
+	SetMiniPressureDifference              float32 `json:"set_mini_pressure_difference"`                // 最小压差设定
+	SetBypassPipeDiameter                  int     `json:"set_bypass_pipe_diameter"`                    // 旁通管径设定  0 表征 25 管径; 1 表征 50 管径; 2 表征 32 管径;注:出厂已配置完成
+	FillValvePower                         int     `json:"fill_valve_power"`                            // 补水阀开关 0-关闭; 1-打开
+	PrimaryPumpTypeConf                    int     `json:"primary_pump_type_conf"`                      // 一级泵类型配置 0-定频泵;(出厂默认) 1-PWM 变频泵(定流量); 2-PWM 变频泵(变流量); 3-0-10V 变频泵(定流量); 4-0-10V 变频泵(变流量);
+	SecondaryPumpTypeConf                  int     `json:"secondary_pump_type_conf"`                    // 二级泵类型配置 0-定频泵; 1-PWM 变频泵(定流量); 2-PWM 变频泵(变流量);(出厂默认) 3-0-10V 变频泵(定流量); 4-0-10V 变频泵(变流量)
+	PrimaryPumpControlMethod               int     `json:"primary_pump_control_method"`                 // 一级泵控制方式 0-恒压控制; 1-温差控制
+	SecondaryPumpControlMethod             int     `json:"secondary_pump_control_method"`               // 二级泵控制方式 0-恒压控制; 1-温差控制
+	SetPrimaryPumpConstantPressure         float32 `json:"set_primary_pump_constant_pressure"`          // 一级泵恒压设定值
+	SetSecondaryPumpConstantPressure       float32 `json:"set_secondary_pump_constant_pressure"`        // 二级泵恒压设定值
+	SetPrimaryPumpConstantTempDifference   float32 `json:"set_primary_pump_constant_temp_difference"`   // 一级泵恒温差设定值
+	SetSecondaryPumpConstantTempDifference float32 `json:"set_secondary_pump_constant_temp_difference"` // 二级泵恒温差设定值
+	PrimaryPumpFrequencyLowerLimit         float32 `json:"primary_pump_frequency_lower_limit"`          // 一级泵泵频率下限
+	PrimaryPumpFrequencyUpperLimit         float32 `json:"primary_pump_frequency_upper_limit"`          // 一级泵泵频率上限
+	PumpPWMControlTypeConf                 int     `json:"pump_pwm_control_type_conf"`                  // 泵 PWM 控类型配置 0-表征正序; 1-表征逆序
+	SecondaryPumpFrequencyLowerLimit       float32 `json:"secondary_pump_frequency_lower_limit"`        // 二级泵泵频率下限
+	SecondaryPumpFrequencyUpperLimit       float32 `json:"secondary_pump_frequency_upper_limit"`        // 二级泵泵频率上限
+	AntiJammingFunction                    int     `json:"anti_jamming_function"`                       // 防卡功能
+	/*
+		0-第 0 档(全关)
+		1-第 1 档(30%)
+		2-第 2 挡(40%)
+		3-第 3 挡(50%)
+		4-第 4 挡(60%)
+		5-第 5 挡(70%)
+		6-第 6 档(80%)
+		7-第 7 档(全开)
+	*/
+	BypassValvePower int `json:"bypass_valve_power"` // 旁通阀手动开关
+	OperatingMode    int `json:"operating_mode"`     // 运行方式 0-单品联动;(出厂默认) 1-管家联动;
+}
+
+type OutdoorUnit struct {
+	OutdoorMode            int     `json:"outdoor_mode"`             // 工作模式(1制冷 2制热)
+	RefrigerationWaterTemp float32 `json:"refrigeration_water_temp"` // 制冷水温设置温度
+	HeatingWaterTemp       float32 `json:"heating_water_temp"`       // 制热水温设置温度
+	OutdoorCirculationTemp float32 `json:"outdoor_circulation_temp"` // 室外循环温度
+	InletWaterTemp         float32 `json:"inlet_water_temp"`         // 出水温度
+	OutletWaterTemp        float32 `json:"outlet_water_temp"`        // 进水温度
+	OutdoorPower           int     `json:"outdoor_power"`            // 开关机 (1开 0关)
+	Auxiliary              int     `json:"auxiliary"`                // 辅热开关 (1开 0关)
+	FaultMark              int     `json:"fault_mark"`               // 故障标志
+}
+
+type TempControlCenter struct {
+	PrimaryInletTemp    float32 `json:"primary_inlet_temp"`    // 一级进水温度
+	PrimaryOutletTemp   float32 `json:"primary_outlet_temp"`   // 一级出水温度
+	SecondaryInletTemp  float32 `json:"secondary_inlet_temp"`  // 二级进水温度
+	SecondaryOutletTemp float32 `json:"secondary_outlet_temp"` // 二级出水温度
+	DewPointTemp        float32 `json:"dew_point_temp"`        //  环境露点温度
+	Mode                int     `json:"mode"`                  // 模式
+}
+
+type ControlArgs struct {
+	Address int         `json:"address"`
+	Value   interface{} `json:"value"`
+}
+
+func GetConfig(key string) *g.Var {
+	ret, err := g.Cfg().Get(context.Background(), key)
+	if err != nil {
+
+	}
+	return ret
+}

+ 265 - 0
server/client.go

@@ -0,0 +1,265 @@
+package server
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"github.com/gogf/gf/v2/encoding/gbinary"
+	"github.com/gogf/gf/v2/net/gtcp"
+	"github.com/gogf/gf/v2/os/glog"
+	"github.com/gogf/gf/v2/util/gconv"
+	"io"
+	"math"
+	"net"
+	"strings"
+	"syscall"
+	"time"
+	"water-system-gateway/protocol"
+)
+
+type Client struct {
+	Id           string
+	ctx          context.Context
+	srv          *Server
+	conn         *gtcp.Conn
+	sendChan     chan []byte
+	closeChan    chan struct{}
+	closeHandler func(id string, c *Client)
+	regHandler   func(id string, c *Client)
+	isReg        bool
+}
+
+func NewClient(s *Server, conn *gtcp.Conn) *Client {
+	return &Client{
+		srv:       s,
+		ctx:       context.Background(),
+		conn:      conn,
+		sendChan:  make(chan []byte),
+		closeChan: make(chan struct{}),
+	}
+}
+
+func (c *Client) SetId(id string) {
+	c.Id = id
+}
+
+func (c *Client) SendLoop() {
+	for {
+		select {
+		case buf := <-c.sendChan:
+			err := c.send(buf)
+			if err != nil {
+				glog.Errorf(c.ctx, "指令发送失败:%s", err.Error())
+				continue
+			}
+			for {
+				select {
+				case <-c.closeChan:
+					return
+				case <-time.After(2 * time.Second):
+					glog.Error(c.ctx, "接收指令超时")
+					break
+				default:
+					receiveBuf, err := c.conn.Recv(-1)
+					if err != nil {
+						c.readError(err)
+						break
+					}
+					if !c.isReg {
+						fmt.Println(receiveBuf)
+						id := gbinary.DecodeToString(receiveBuf)
+						glog.Debugf(c.ctx, "收到注册包!id:%s", id)
+						c.SetId(id)
+						c.isReg = true
+						if c.regHandler != nil {
+							c.regHandler(c.Id, c)
+						}
+						continue
+					}
+					glog.Debugf(c.ctx, "收到数据:%2X", receiveBuf)
+					if err := c.decodeAndReport(receiveBuf); err != nil {
+						glog.Debugf(c.ctx, "处理数据失败:%s", err.Error())
+						break
+					}
+				}
+				break
+			}
+		}
+	}
+}
+
+// 收到数据:01 03 04 00 FD 00 8A EA 64
+func (c *Client) decodeAndReport(buf []byte) error {
+	//length := len(buf)
+	//var crc crc
+	//crc.reset().pushBytes(buf[0 : length-2])
+	//checksum := uint16(buf[length-1])<<8 | uint16(buf[length-2])
+	//if checksum != crc.value() {
+	//	return errors.New(fmt.Sprintf("modbus: response crc '%v' does not match expected '%v'", checksum, crc.value()))
+	//}
+	if len(buf) <= 8 {
+		return nil
+	}
+	if buf[1] == 0x03 {
+		if buf[2] == 0xBE {
+			data := &protocol.WaterSystem{}
+			data.Power1 = gconv.Int(gbinary.BeDecodeToUint16(buf[3:5]))
+			data.Power2 = gconv.Int(gbinary.BeDecodeToUint16(buf[5:7]))
+			data.Power3 = gconv.Int(gbinary.BeDecodeToUint16(buf[7:9]))
+			data.Power4 = gconv.Int(gbinary.BeDecodeToUint16(buf[9:11]))
+			data.Power5 = gconv.Int(gbinary.BeDecodeToUint16(buf[11:13]))
+			data.Power6 = gconv.Int(gbinary.BeDecodeToUint16(buf[13:15]))
+			data.Power7 = gconv.Int(gbinary.BeDecodeToUint16(buf[15:17]))
+			data.Power8 = gconv.Int(gbinary.BeDecodeToUint16(buf[17:19]))
+			data.HeaderPower = gconv.Int(gbinary.BeDecodeToUint16(buf[21:23]))
+			data.InletPressure = gconv.Float32(gbinary.BeDecodeToUint16(buf[23:25])) / 10
+			data.OutletPressure = gconv.Float32(gbinary.BeDecodeToUint16(buf[25:27])) / 10
+			data.PressureDifference = gconv.Float32(gbinary.BeDecodeToUint16(buf[27:29])) / 10
+			data.FaultCode = gconv.Int(gbinary.BeDecodeToUint16(buf[29:31]))
+			data.FillValve = gconv.Int(gbinary.BeDecodeToUint16(buf[31:33]))
+			data.BypassValve = gconv.Int(gbinary.BeDecodeToUint16(buf[33:35]))
+			data.SetInletWaterPre = gconv.Float32(gbinary.BeDecodeToUint16(buf[35:37])) / 10
+			data.SetMaxDifferencePre = gconv.Float32(gbinary.BeDecodeToUint16(buf[37:39])) / 10
+			data.SetMinDifferencePre = gconv.Float32(gbinary.BeDecodeToUint16(buf[39:41])) / 10
+			data.HeaderMode = gconv.Int(gbinary.BeDecodeToUint16(buf[41:43]))
+
+			data.PrimaryPumpStatus = gconv.Int(gbinary.BeDecodeToUint16(buf[43:45]))
+			data.SecondaryPumpStatus = gconv.Int(gbinary.BeDecodeToUint16(buf[45:47]))
+			data.PrimaryPumpFrequency = gconv.Float32(gbinary.BeDecodeToUint16(buf[47:49])) / 10
+			data.SecondaryPumpFrequency = gconv.Float32(gbinary.BeDecodeToUint16(buf[49:51])) / 10
+			data.WetNodeInput1 = gconv.Int(gbinary.BeDecodeToUint16(buf[51:53]))
+			data.WetNodeInput2 = gconv.Int(gbinary.BeDecodeToUint16(buf[53:55]))
+			data.WetNodeInput = gconv.Int(gbinary.BeDecodeToUint16(buf[55:57]))
+			data.DryNodeInput = gconv.Int(gbinary.BeDecodeToUint16(buf[57:59]))
+			data.PrimaryOutletTemp = gconv.Float32(gbinary.BeDecodeToUint16(buf[59:61])) / 10
+			data.PrimaryReturnTemp = gconv.Float32(gbinary.BeDecodeToUint16(buf[61:63])) / 10
+			data.SecondaryOutletTemp = gconv.Float32(gbinary.BeDecodeToUint16(buf[63:65])) / 10
+			data.SecondaryReturnTemp = gconv.Float32(gbinary.BeDecodeToUint16(buf[65:67])) / 10
+
+			data.CollectorPower = gconv.Int(gbinary.BeDecodeToUint16(buf[81:83]))
+			data.PumpStationPower = gconv.Int(gbinary.BeDecodeToUint16(buf[83:85]))
+			data.DeviceReset = gconv.Int(gbinary.BeDecodeToUint16(buf[85:87]))
+			data.CollectorMode = gconv.Int(gbinary.BeDecodeToUint16(buf[87:89]))
+			data.SetTargetOutletPressure = gconv.Float32(gbinary.BeDecodeToUint16(buf[89:91])) / 10
+			data.SetMaxiPressureDifference = gconv.Float32(gbinary.BeDecodeToUint16(buf[91:93])) / 10
+			data.SetMiniPressureDifference = gconv.Float32(gbinary.BeDecodeToUint16(buf[93:95])) / 10
+			data.SetBypassPipeDiameter = gconv.Int(gbinary.BeDecodeToUint16(buf[95:97]))
+			data.FillValvePower = gconv.Int(gbinary.BeDecodeToUint16(buf[97:99]))
+			data.PrimaryPumpTypeConf = gconv.Int(gbinary.BeDecodeToUint16(buf[99:101]))
+			data.SecondaryPumpTypeConf = gconv.Int(gbinary.BeDecodeToUint16(buf[101:103]))
+			data.PrimaryPumpControlMethod = gconv.Int(gbinary.BeDecodeToUint16(buf[103:105]))
+			data.SecondaryPumpControlMethod = gconv.Int(gbinary.BeDecodeToUint16(buf[105:107]))
+			data.SetPrimaryPumpConstantPressure = gconv.Float32(gbinary.BeDecodeToUint16(buf[107:109])) / 10
+			data.SetSecondaryPumpConstantPressure = gconv.Float32(gbinary.BeDecodeToUint16(buf[109:111])) / 10
+			data.SetPrimaryPumpConstantTempDifference = gconv.Float32(gbinary.BeDecodeToUint16(buf[111:113])) / 10
+			data.SetSecondaryPumpConstantTempDifference = gconv.Float32(gbinary.BeDecodeToUint16(buf[113:115])) / 10
+			data.PrimaryPumpFrequencyLowerLimit = gconv.Float32(gbinary.BeDecodeToUint16(buf[115:117]))
+			data.PrimaryPumpFrequencyUpperLimit = gconv.Float32(gconv.Float32(gbinary.BeDecodeToUint16(buf[117:119])))
+			data.PumpPWMControlTypeConf = gconv.Int(gbinary.BeDecodeToUint16(buf[119:121]))
+			data.SecondaryPumpFrequencyLowerLimit = gconv.Float32(gbinary.BeDecodeToUint16(buf[121:123]))
+			data.SecondaryPumpFrequencyUpperLimit = gconv.Float32(gbinary.BeDecodeToUint16(buf[123:125]))
+			data.AntiJammingFunction = gconv.Int(gbinary.BeDecodeToUint16(buf[125:127]))
+			data.BypassValvePower = gconv.Int(gbinary.BeDecodeToUint16(buf[127:129]))
+			data.OperatingMode = gconv.Int(gbinary.BeDecodeToUint16(buf[129:131]))
+
+			data.OutdoorMode = gconv.Int(gbinary.BeDecodeToUint16(buf[173:175]))
+			data.RefrigerationWaterTemp = gconv.Float32(gbinary.BeDecodeToUint16(buf[175:177])) / 10
+			data.HeatingWaterTemp = gconv.Float32(gbinary.BeDecodeToUint16(buf[177:179])) / 10
+			data.OutdoorCirculationTemp = gconv.Float32(gbinary.BeDecodeToUint16(buf[151:153]))
+			data.InletWaterTemp = gconv.Float32(gbinary.BeDecodeToUint16(buf[153:155]))
+			data.OutletWaterTemp = gconv.Float32(gbinary.BeDecodeToUint16(buf[155:157]))
+			data.OutdoorPower = gconv.Int(gbinary.BeDecodeToUint16(buf[179:181]))
+			data.Auxiliary = gconv.Int(gbinary.BeDecodeToUint16(buf[159:161]))
+			data.FaultMark = gconv.Int(gbinary.BeDecodeToUint16(buf[161:163]))
+
+			if err := c.srv.ReportStatus(c.Id, data, "status"); err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
+func (c *Client) readError(err error) {
+	defer c.closeConnection()
+	if err == io.EOF || isErrConnReset(err) {
+		return
+	}
+	glog.Errorf(c.ctx, "读取数据发生错误:%s", err.Error())
+}
+
+func (c *Client) closeConnection() {
+	_ = c.conn.Close()
+	c.conn = nil
+	close(c.closeChan)
+	c.isReg = false
+	if c.closeHandler != nil {
+		c.closeHandler(c.Id, c)
+	}
+}
+
+// 计算温度值, 处理零下的情况
+func caleTemperature(data []byte) float32 {
+	value := gconv.Float32(gbinary.BeDecodeToUint16(data)&0x7FFF) / 10
+	if value < 1 {
+		value = float32(math.Floor(float64(value) + 0.5))
+	}
+	if gbinary.BeDecodeToUint16(data)&0x8000 == 0x8000 && value != 0 {
+		value = value * -1
+	}
+	return value
+}
+
+// isErrConnReset read: connection reset by peer
+func isErrConnReset(err error) bool {
+	var ne *net.OpError
+	if errors.As(err, &ne) {
+		return strings.Contains(ne.Err.Error(), syscall.ECONNRESET.Error())
+	}
+	return false
+}
+
+//func dataBlock(value float32) []byte {
+//	buffer := &bytes.Buffer{}
+//	var a uint16
+//	if value >= 0 {
+//		a = uint16(value*100) | 0x8000
+//	} else {
+//		a = uint16(math.Abs(float64(value))*100) | 0x0000
+//	}
+//	buffer.Write(gbinary.BeEncodeUint16(a))
+//	return buffer.Bytes()
+//}
+
+func (c *Client) GetSendByte() {
+	for {
+		c.sendChan <- []byte{0x01, 0x03, 0x00, 0x00, 0x00, 0x5F, 0x05, 0xF2}
+		time.Sleep(10 * time.Second)
+	}
+}
+
+func (c *Client) send(buf []byte) error {
+	if c.conn == nil {
+		return nil
+	}
+	glog.Debugf(c.ctx, "----->%2X", buf)
+	err := c.conn.Send(buf)
+	if err != nil {
+		glog.Error(c.srv.ctx, err)
+		c.closeConnection()
+		return err
+	}
+	return nil
+}
+
+func (c *Client) WaterSystemControl(address uint16, value interface{}) error {
+	result, err := WriteMultipleRegisters(address, 1, gbinary.BeEncodeUint16(gconv.Uint16(value)))
+	if err != nil {
+		return err
+	}
+	c.sendChan <- result
+	time.Sleep(100 * time.Millisecond)
+	c.sendChan <- []byte{0x01, 0x03, 0x00, 0x00, 0x00, 0x5F, 0x05, 0xF2}
+	return nil
+}

+ 68 - 0
server/crc.go

@@ -0,0 +1,68 @@
+package server
+
+// Table of CRC values for high–order byte
+var crcHighBytes = []byte{
+	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+}
+
+// Table of CRC values for low-order byte
+var crcLowBytes = []byte{
+	0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04,
+	0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8,
+	0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
+	0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10,
+	0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
+	0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
+	0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C,
+	0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0,
+	0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
+	0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
+	0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C,
+	0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
+	0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54,
+	0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98,
+	0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
+	0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40,
+}
+
+// Cyclical Redundancy Checking
+type crc struct {
+	high byte
+	low  byte
+}
+
+func (crc *crc) reset() *crc {
+	crc.high = 0xFF
+	crc.low = 0xFF
+	return crc
+}
+
+func (crc *crc) pushBytes(bs []byte) *crc {
+	var idx, b byte
+
+	for _, b = range bs {
+		idx = crc.low ^ b
+		crc.low = crc.high ^ crcHighBytes[idx]
+		crc.high = crcLowBytes[idx]
+	}
+	return crc
+}
+
+func (crc *crc) value() uint16 {
+	return uint16(crc.high)<<8 | uint16(crc.low)
+}

+ 184 - 0
server/modbus.go

@@ -0,0 +1,184 @@
+package server
+
+import (
+	"encoding/binary"
+	"fmt"
+	"time"
+)
+
+const (
+	// Bit access
+	FuncCodeReadDiscreteInputs = 2
+	FuncCodeReadCoils          = 1
+	FuncCodeWriteSingleCoil    = 5
+	FuncCodeWriteMultipleCoils = 15
+
+	// 16-bit access
+	FuncCodeReadInputRegisters         = 4
+	FuncCodeReadHoldingRegisters       = 3
+	FuncCodeWriteSingleRegister        = 6
+	FuncCodeWriteMultipleRegisters     = 16
+	FuncCodeReadWriteMultipleRegisters = 23
+	FuncCodeMaskWriteRegister          = 22
+	FuncCodeReadFIFOQueue              = 24
+)
+
+const (
+	ExceptionCodeIllegalFunction                    = 1
+	ExceptionCodeIllegalDataAddress                 = 2
+	ExceptionCodeIllegalDataValue                   = 3
+	ExceptionCodeServerDeviceFailure                = 4
+	ExceptionCodeAcknowledge                        = 5
+	ExceptionCodeServerDeviceBusy                   = 6
+	ExceptionCodeMemoryParityError                  = 8
+	ExceptionCodeGatewayPathUnavailable             = 10
+	ExceptionCodeGatewayTargetDeviceFailedToRespond = 11
+)
+
+const (
+	rtuMinSize = 4
+	rtuMaxSize = 256
+
+	rtuExceptionSize = 5
+
+	tcpProtocolIdentifier uint16 = 0x0000
+
+	// Modbus Application Protocol
+	tcpHeaderSize = 7
+	tcpMaxLength  = 260
+	// Default TCP timeout is not set
+	tcpTimeout     = 10 * time.Second
+	tcpIdleTimeout = 60 * time.Second
+)
+
+// ModbusError implements error interface.
+type ModbusError struct {
+	FunctionCode  byte
+	ExceptionCode byte
+}
+
+// Error converts known modbus exception code to error message.
+func (e *ModbusError) Error() string {
+	var name string
+	switch e.ExceptionCode {
+	case ExceptionCodeIllegalFunction:
+		name = "illegal function"
+	case ExceptionCodeIllegalDataAddress:
+		name = "illegal data address"
+	case ExceptionCodeIllegalDataValue:
+		name = "illegal data value"
+	case ExceptionCodeServerDeviceFailure:
+		name = "server device failure"
+	case ExceptionCodeAcknowledge:
+		name = "acknowledge"
+	case ExceptionCodeServerDeviceBusy:
+		name = "server device busy"
+	case ExceptionCodeMemoryParityError:
+		name = "memory parity error"
+	case ExceptionCodeGatewayPathUnavailable:
+		name = "gateway path unavailable"
+	case ExceptionCodeGatewayTargetDeviceFailedToRespond:
+		name = "gateway target device failed to respond"
+	default:
+		name = "unknown"
+	}
+	return fmt.Sprintf("modbus: exception '%v' (%s), function '%v'", e.ExceptionCode, name, e.FunctionCode)
+}
+
+// ProtocolDataUnit (PDU) is independent of underlying communication layers.
+type ProtocolDataUnit struct {
+	FunctionCode byte
+	Data         []byte
+}
+
+// Transporter specifies the transport layer.
+type Transporter interface {
+	Send(aduRequest []byte) (aduResponse []byte, err error)
+}
+
+// dataBlock creates a sequence of uint16 data.
+func dataBlock(value ...uint16) []byte {
+	data := make([]byte, 2*len(value))
+	for i, v := range value {
+		binary.BigEndian.PutUint16(data[i*2:], v)
+	}
+	return data
+}
+
+// dataBlockSuffix creates a sequence of uint16 data and append the suffix plus its length.
+func dataBlockSuffix(suffix []byte, value ...uint16) []byte {
+	length := 2 * len(value)
+	data := make([]byte, length+1+len(suffix))
+	for i, v := range value {
+		binary.BigEndian.PutUint16(data[i*2:], v)
+	}
+	data[length] = uint8(len(suffix))
+	copy(data[length+1:], suffix)
+	return data
+}
+
+func ReadHoldingRegisters(address, quantity uint16) (results []byte, err error) {
+	if quantity < 1 || quantity > 125 {
+		err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 125)
+		return
+	}
+	request := ProtocolDataUnit{
+		FunctionCode: FuncCodeReadHoldingRegisters,
+		Data:         dataBlock(address, quantity),
+	}
+	aduRequest, err := Encode(&request)
+	if err != nil {
+		return
+	}
+	return aduRequest, nil
+}
+
+// WriteMultipleRegisters
+// Request:
+//
+//	Function code         : 1 byte (0x10)
+//	Starting address      : 2 bytes
+//	Quantity of outputs   : 2 bytes
+//	Byte count            : 1 byte
+//	Registers value       : N* bytes
+//
+// Response:
+//
+//	Function code         : 1 byte (0x10)
+//	Starting address      : 2 bytes
+//	Quantity of registers : 2 bytes
+func WriteMultipleRegisters(address, quantity uint16, value []byte) (results []byte, err error) {
+	if quantity < 1 || quantity > 123 {
+		err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 123)
+		return
+	}
+	request := ProtocolDataUnit{
+		FunctionCode: FuncCodeWriteMultipleRegisters,
+		Data:         dataBlockSuffix(value, address, quantity),
+	}
+	aduRequest, err := Encode(&request)
+	if err != nil {
+		return
+	}
+	return aduRequest, nil
+}
+
+func Encode(pdu *ProtocolDataUnit) (adu []byte, err error) {
+	length := len(pdu.Data) + 4
+	if length > rtuMaxSize {
+		err = fmt.Errorf("modbus: length of data '%v' must not be bigger than '%v'", length, rtuMaxSize)
+		return
+	}
+	adu = make([]byte, length)
+	adu[0] = 0x01
+	adu[1] = pdu.FunctionCode
+	copy(adu[2:], pdu.Data)
+	// Append Crc
+	var crc crc
+	crc.reset().pushBytes(adu[0 : length-2])
+	checksum := crc.value()
+
+	adu[length-1] = byte(checksum >> 8)
+	adu[length-2] = byte(checksum)
+	return
+}

+ 79 - 0
server/server.go

@@ -0,0 +1,79 @@
+package server
+
+import (
+	"context"
+	"fmt"
+	"github.com/gogf/gf/v2/container/gmap"
+	"github.com/gogf/gf/v2/net/gtcp"
+	"github.com/gogf/gf/v2/os/glog"
+	gatewayV2 "sparrow-sdk/v2"
+	"time"
+	"water-system-gateway/protocol"
+)
+
+type Server struct {
+	closeChan chan struct{}
+	srv       *gtcp.Server
+	ctx       context.Context
+	addr      string
+	port      int
+	gateWay   *gatewayV2.Gateway
+	clients   *gmap.Map
+}
+
+func NewServer(ctx context.Context, addr string, port int, gw *gatewayV2.Gateway) *Server {
+	return &Server{
+		closeChan: make(chan struct{}),
+		ctx:       ctx,
+		addr:      addr,
+		port:      port,
+		gateWay:   gw,
+		clients:   gmap.New(false),
+	}
+}
+
+func (s *Server) Start() error {
+	glog.Printf(s.ctx, "服务端启动[%s:%d]", s.addr, s.port)
+	srv := gtcp.NewServer(fmt.Sprintf("%s:%d", s.addr, s.port), s.onClientConnect)
+	s.srv = srv
+	return s.srv.Run()
+}
+
+func (s *Server) Stop() {
+	s.clients.Iterator(func(k interface{}, v interface{}) bool {
+		client := v.(*Client)
+		close(client.closeChan)
+		return true
+	})
+	_ = s.srv.Close()
+}
+
+func (s *Server) onClientConnect(conn *gtcp.Conn) {
+	client := NewClient(s, conn)
+	client.closeHandler = func(id string, c *Client) {
+		glog.Debugf(s.ctx, "客户端断开:%s", id)
+		if id != "" {
+			_ = s.gateWay.SubDeviceLogout(protocol.GetConfig("sparrow.DeviceCode").String(), id)
+			s.clients.Remove(id)
+		}
+	}
+	client.regHandler = func(id string, c *Client) {
+		_ = s.gateWay.SubDeviceLogin(protocol.GetConfig("Sparrow.DeviceCode").String(), id)
+		s.clients.Set(id, c)
+	}
+	go client.SendLoop()
+	time.Sleep(10 * time.Second)
+	go client.GetSendByte()
+}
+
+func (s *Server) ReportStatus(subId string, data interface{}, cmd string) error {
+	return s.gateWay.ReportStatus(subId, cmd, data)
+}
+
+func (s *Server) GetClient(subId string) *Client {
+	client := s.clients.Get(subId)
+	if client != nil {
+		return client.(*Client)
+	}
+	return nil
+}