github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/tests/plugins/modbus_tcp/modbus_tcp_test.go (about) 1 // This file is part of the Smart Home 2 // Program complex distribution https://github.com/e154/smart-home 3 // Copyright (C) 2016-2023, Filippov Alex 4 // 5 // This library is free software: you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 3 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Library General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library. If not, see 17 // <https://www.gnu.org/licenses/>. 18 19 package modbus_tcp 20 21 import ( 22 "context" 23 "encoding/json" 24 "fmt" 25 "testing" 26 "time" 27 28 "github.com/e154/smart-home/common/events" 29 30 "github.com/e154/smart-home/adaptors" 31 m "github.com/e154/smart-home/models" 32 "github.com/e154/smart-home/plugins/modbus_tcp" 33 "github.com/e154/smart-home/plugins/node" 34 "github.com/e154/smart-home/system/automation" 35 "github.com/e154/smart-home/system/bus" 36 "github.com/e154/smart-home/system/migrations" 37 "github.com/e154/smart-home/system/mqtt" 38 "github.com/e154/smart-home/system/scripts" 39 "github.com/e154/smart-home/system/supervisor" 40 . "github.com/e154/smart-home/tests/plugins" 41 . "github.com/smartystreets/goconvey/convey" 42 ) 43 44 func TestModbusTcp(t *testing.T) { 45 46 const plugActionOnOffSourceScript = ` 47 48 writeRegisters =(d, c, r)-> 49 return ModbusTcp 'WriteMultipleRegisters', d, c, r 50 51 checkStatus =-> 52 COMMAND = [] 53 FUNC = 'ReadHoldingRegisters' 54 ADDRESS = 0 55 COUNT = 16 56 57 res = ModbusTcp FUNC, ADDRESS, COUNT, COMMAND 58 So(res.error, 'ShouldEqual', '') 59 So(res.result, 'ShouldEqual', '[1 0 1]') 60 So(res.time, 'ShouldNotBeBlank', '') 61 62 doOnAction = -> 63 res = writeRegisters(0, 1, [1]) 64 So(res.error, 'ShouldEqual', '') 65 So(res.result, 'ShouldEqual', '[]') 66 So(res.time, 'ShouldNotBeBlank', '') 67 68 doOnErrAction = -> 69 res = writeRegisters(0, 1, [1]) 70 So(res.error, 'ShouldEqual', 'some error') 71 So(res.result, 'ShouldEqual', '[]') 72 So(res.time, 'ShouldNotBeBlank', '') 73 74 doOffAction = -> 75 res = writeRegisters(0, 1, [0]) 76 So(res.error, 'ShouldEqual', '') 77 So(res.result, 'ShouldEqual', '[]') 78 So(res.time, 'ShouldNotBeBlank', '') 79 80 entityAction = (entityId, actionName)-> 81 #print '---action on/off--' 82 switch actionName 83 when 'ON' then doOnAction() 84 when 'OFF' then doOffAction() 85 when 'CHECK' then checkStatus() 86 when 'ON_WITH_ERR' then doOnErrAction() 87 ` 88 89 Convey("modbus_tcp", t, func(ctx C) { 90 _ = container.Invoke(func(adaptors *adaptors.Adaptors, 91 migrations *migrations.Migrations, 92 scriptService scripts.ScriptService, 93 supervisor supervisor.Supervisor, 94 mqttServer mqtt.MqttServ, 95 automation automation.Automation, 96 eventBus bus.Bus) { 97 98 // register plugins 99 AddPlugin(adaptors, "node") 100 AddPlugin(adaptors, "triggers") 101 AddPlugin(adaptors, "modbus_tcp") 102 103 serviceCh := WaitService(eventBus, time.Second*5, "Supervisor", "Automation", "Mqtt") 104 pluginsCh := WaitPlugins(eventBus, time.Second*5, "triggers", "modbus_tcp", "node") 105 automation.Start() 106 go mqttServer.Start() 107 supervisor.Start(context.Background()) 108 defer automation.Shutdown() 109 defer mqttServer.Shutdown() 110 defer supervisor.Shutdown(context.Background()) 111 So(<-serviceCh, ShouldBeTrue) 112 So(<-pluginsCh, ShouldBeTrue) 113 114 // bind convey 115 RegisterConvey(scriptService, ctx) 116 117 // add scripts 118 // ------------------------------------------------ 119 120 plugActionOnOffScript, err := AddScript("plug script", plugActionOnOffSourceScript, adaptors, scriptService) 121 So(err, ShouldBeNil) 122 123 // add entity 124 // ------------------------------------------------ 125 nodeEnt := GetNewNode("second") 126 err = adaptors.Entity.Add(context.Background(), nodeEnt) 127 So(err, ShouldBeNil) 128 129 plugEnt := GetNewModbusTcp("plug") 130 plugEnt.ParentId = &nodeEnt.Id 131 plugEnt.Actions = []*m.EntityAction{ 132 { 133 Name: "ON", 134 Description: "включить", 135 Script: plugActionOnOffScript, 136 }, 137 { 138 Name: "OFF", 139 Description: "выключить", 140 Script: plugActionOnOffScript, 141 }, 142 { 143 Name: "CHECK", 144 Description: "condition check", 145 Script: plugActionOnOffScript, 146 }, 147 { 148 Name: "ON_WITH_ERR", 149 Description: "error case", 150 Script: plugActionOnOffScript, 151 }, 152 } 153 plugEnt.States = []*m.EntityState{ 154 { 155 Name: "ON", 156 Description: "on state", 157 }, 158 { 159 Name: "OFF", 160 Description: "off state", 161 }, 162 { 163 Name: "ERROR", 164 Description: "error state", 165 }, 166 } 167 plugEnt.Settings[modbus_tcp.AttrSlaveId].Value = 1 168 plugEnt.Settings[modbus_tcp.AttrAddressPort].Value = "office:502" 169 err = adaptors.Entity.Add(context.Background(), plugEnt) 170 So(err, ShouldBeNil) 171 _, err = adaptors.EntityStorage.Add(context.Background(), &m.EntityStorage{ 172 EntityId: plugEnt.Id, 173 Attributes: plugEnt.Attributes.Serialize(), 174 }) 175 So(err, ShouldBeNil) 176 177 eventBus.Publish("system/models/entities/"+nodeEnt.Id.String(), events.EventCreatedEntityModel{ 178 EntityId: nodeEnt.Id, 179 }) 180 eventBus.Publish("system/models/entities/"+nodeEnt.Id.String(), events.EventCreatedEntityModel{ 181 EntityId: plugEnt.Id, 182 }) 183 184 time.Sleep(time.Second) 185 186 // ------------------------------------------------ 187 188 ch := make(chan []byte) 189 mqttCli := mqttServer.NewClient("cli") 190 _ = mqttCli.Subscribe("system/plugins/node/second/req/#", func(cli mqtt.MqttCli, message mqtt.Message) { 191 ch <- message.Payload 192 }) 193 defer mqttCli.UnsubscribeAll() 194 195 // commands 196 t.Run("on command", func(t *testing.T) { 197 Convey("stats", t, func(ctx C) { 198 supervisor.CallAction(plugEnt.Id, "ON", nil) 199 200 // wait message 201 req, ok := WaitT[[]byte](time.Second*2, ch) 202 ctx.So(ok, ShouldBeTrue) 203 204 // what see node 205 request := node.MessageRequest{} 206 err = json.Unmarshal(req, &request) 207 ctx.So(err, ShouldBeNil) 208 209 cmd := modbus_tcp.ModBusCommand{} 210 err = json.Unmarshal(request.Command, &cmd) 211 ctx.So(err, ShouldBeNil) 212 213 prop := map[string]interface{}{} 214 err = json.Unmarshal(request.Properties, &prop) 215 ctx.So(err, ShouldBeNil) 216 217 ctx.So(request.EntityId, ShouldEqual, plugEnt.Id) 218 ctx.So(request.DeviceType, ShouldEqual, modbus_tcp.DeviceTypeModbusTcp) 219 ctx.So(prop["slave_id"], ShouldEqual, 1) 220 ctx.So(prop["address_port"], ShouldEqual, "office:502") 221 222 ctx.So(cmd.Function, ShouldEqual, "WriteMultipleRegisters") 223 ctx.So(cmd.Address, ShouldEqual, 0) 224 ctx.So(cmd.Count, ShouldEqual, 1) 225 ctx.So(cmd.Command, ShouldResemble, []uint16{1}) 226 227 // response from node 228 r := modbus_tcp.ModBusResponse{ 229 Error: "", 230 Result: []uint16{}, 231 } 232 b, _ := json.Marshal(r) 233 resp := node.MessageResponse{ 234 EntityId: plugEnt.Id, 235 DeviceType: modbus_tcp.DeviceTypeModbusTcp, 236 Properties: nil, 237 Response: b, 238 Status: "", 239 } 240 b, _ = json.Marshal(resp) 241 _ = mqttCli.Publish("system/plugins/node/second/resp/plugin.test", b) 242 _ = mqttCli.Publish(fmt.Sprintf("system/plugins/node/second/resp/%s", plugEnt.Id), b) 243 244 time.Sleep(time.Millisecond * 500) 245 }) 246 }) 247 248 t.Run("off command", func(t *testing.T) { 249 Convey("stats", t, func(ctx C) { 250 supervisor.CallAction(plugEnt.Id, "OFF", nil) 251 252 // wait message 253 req, ok := WaitT[[]byte](time.Second*2, ch) 254 ctx.So(ok, ShouldBeTrue) 255 256 // what see node 257 request := node.MessageRequest{} 258 err = json.Unmarshal(req, &request) 259 ctx.So(err, ShouldBeNil) 260 261 cmd := modbus_tcp.ModBusCommand{} 262 err = json.Unmarshal(request.Command, &cmd) 263 ctx.So(err, ShouldBeNil) 264 265 prop := map[string]interface{}{} 266 err = json.Unmarshal(request.Properties, &prop) 267 ctx.So(err, ShouldBeNil) 268 269 ctx.So(request.EntityId, ShouldEqual, plugEnt.Id) 270 ctx.So(request.DeviceType, ShouldEqual, modbus_tcp.DeviceTypeModbusTcp) 271 ctx.So(prop["slave_id"], ShouldEqual, 1) 272 ctx.So(prop["address_port"], ShouldEqual, "office:502") 273 274 ctx.So(cmd.Function, ShouldEqual, "WriteMultipleRegisters") 275 ctx.So(cmd.Address, ShouldEqual, 0) 276 ctx.So(cmd.Count, ShouldEqual, 1) 277 ctx.So(cmd.Command, ShouldResemble, []uint16{0}) 278 279 // response from node 280 r := modbus_tcp.ModBusResponse{ 281 Error: "", 282 Result: []uint16{}, 283 } 284 b, _ := json.Marshal(r) 285 resp := node.MessageResponse{ 286 EntityId: plugEnt.Id, 287 DeviceType: modbus_tcp.DeviceTypeModbusTcp, 288 Properties: nil, 289 Response: b, 290 Status: "", 291 } 292 b, _ = json.Marshal(resp) 293 _ = mqttCli.Publish("system/plugins/node/second/resp/plugin.test", b) 294 _ = mqttCli.Publish(fmt.Sprintf("system/plugins/node/second/resp/%s", plugEnt.Id), b) 295 296 time.Sleep(time.Millisecond * 500) 297 }) 298 }) 299 300 t.Run("check command", func(t *testing.T) { 301 Convey("stats", t, func(ctx C) { 302 supervisor.CallAction(plugEnt.Id, "CHECK", nil) 303 304 // wait message 305 req, ok := WaitT[[]byte](time.Second*2, ch) 306 ctx.So(ok, ShouldBeTrue) 307 308 // what see node 309 request := node.MessageRequest{} 310 err = json.Unmarshal(req, &request) 311 ctx.So(err, ShouldBeNil) 312 313 cmd := modbus_tcp.ModBusCommand{} 314 err = json.Unmarshal(request.Command, &cmd) 315 ctx.So(err, ShouldBeNil) 316 317 prop := map[string]interface{}{} 318 err = json.Unmarshal(request.Properties, &prop) 319 ctx.So(err, ShouldBeNil) 320 321 ctx.So(request.EntityId, ShouldEqual, plugEnt.Id) 322 ctx.So(request.DeviceType, ShouldEqual, modbus_tcp.DeviceTypeModbusTcp) 323 ctx.So(prop["slave_id"], ShouldEqual, 1) 324 ctx.So(prop["address_port"], ShouldEqual, "office:502") 325 326 ctx.So(cmd.Function, ShouldEqual, "ReadHoldingRegisters") 327 ctx.So(cmd.Address, ShouldEqual, 0) 328 ctx.So(cmd.Count, ShouldEqual, 16) 329 ctx.So(cmd.Command, ShouldResemble, []uint16{}) 330 331 // response from node 332 r := modbus_tcp.ModBusResponse{ 333 Error: "", 334 Result: []uint16{1, 0, 1}, 335 } 336 b, _ := json.Marshal(r) 337 resp := node.MessageResponse{ 338 EntityId: plugEnt.Id, 339 DeviceType: modbus_tcp.DeviceTypeModbusTcp, 340 Properties: nil, 341 Response: b, 342 Status: "", 343 } 344 b, _ = json.Marshal(resp) 345 _ = mqttCli.Publish("system/plugins/node/second/resp/plugin.test", b) 346 _ = mqttCli.Publish(fmt.Sprintf("system/plugins/node/second/resp/%s", plugEnt.Id), b) 347 348 time.Sleep(time.Millisecond * 500) 349 }) 350 }) 351 352 t.Run("bad command", func(t *testing.T) { 353 Convey("stats", t, func(ctx C) { 354 supervisor.CallAction(plugEnt.Id, "NULL", nil) 355 356 // wait message 357 _, ok := WaitT[[]byte](time.Second*2, ch) 358 ctx.So(ok, ShouldBeFalse) 359 }) 360 }) 361 362 t.Run("response with error", func(t *testing.T) { 363 Convey("stats", t, func(ctx C) { 364 supervisor.CallAction(plugEnt.Id, "ON_WITH_ERR", nil) 365 366 // wait message 367 _, ok := WaitT[[]byte](time.Second*2, ch) 368 ctx.So(ok, ShouldBeTrue) 369 370 r := modbus_tcp.ModBusResponse{ 371 Error: "some error", 372 } 373 b, _ := json.Marshal(r) 374 resp := node.MessageResponse{ 375 EntityId: plugEnt.Id, 376 DeviceType: modbus_tcp.DeviceTypeModbusTcp, 377 Properties: nil, 378 Response: b, 379 Status: "", 380 } 381 b, _ = json.Marshal(resp) 382 _ = mqttCli.Publish("system/plugins/node/second/resp/plugin.test", b) 383 _ = mqttCli.Publish(fmt.Sprintf("system/plugins/node/second/resp/%s", plugEnt.Id), b) 384 385 }) 386 }) 387 }) 388 }) 389 }