github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/tests/plugins/cgminer/cgminer_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 cgminer 20 21 import ( 22 "context" 23 "testing" 24 "time" 25 26 "github.com/e154/smart-home/adaptors" 27 "github.com/e154/smart-home/common" 28 "github.com/e154/smart-home/common/debug" 29 "github.com/e154/smart-home/common/events" 30 m "github.com/e154/smart-home/models" 31 "github.com/e154/smart-home/plugins/cgminer" 32 "github.com/e154/smart-home/system/bus" 33 "github.com/e154/smart-home/system/scripts" 34 "github.com/e154/smart-home/system/supervisor" 35 . "github.com/e154/smart-home/tests/plugins" 36 . "github.com/smartystreets/goconvey/convey" 37 ) 38 39 func TestCgMiner(t *testing.T) { 40 41 const cgminerSourceScript = ` 42 43 ifError =(res)-> 44 return !res || res.error || res.Error 45 46 checkStatus =-> 47 stats = Miner.stats() 48 if ifError(stats) 49 EntitySetStateName ENTITY_ID, 'ERROR' 50 return 51 p = unmarshal stats.result 52 attrs = { 53 heat: false 54 chain1_temp_chip: p.temp2_1 55 chain2_temp_chip: p.temp2_2 56 chain3_temp_chip: p.temp2_3 57 chain4_temp_chip: p.temp2_4 58 chain1_temp_pcb: p.temp1 59 chain2_temp_pcb: p.temp2 60 chain3_temp_pcb: p.temp3 61 chain4_temp_pcb: p.temp4 62 fan1: p.fan1 63 fan2: p.fan2 64 } 65 66 EntitySetState ENTITY_ID, 67 new_state: 'ENABLED' 68 attribute_values: attrs 69 storage_save: true 70 71 checkSum =-> 72 summary = Miner.summary() 73 if ifError(summary) 74 EntitySetStateName ENTITY_ID, 'ERROR' 75 return 76 p = unmarshal summary.result 77 attrs = {} 78 attrs["ghs_av"] = p["GHS av"] 79 attrs["hardware_errors"] = p["Hardware Errors"] 80 EntitySetState ENTITY_ID, 81 new_state: 'ENABLED' 82 attribute_values: attrs 83 storage_save: true 84 85 checkDevs =-> 86 devs = Miner.devs() 87 if ifError(devs) 88 EntitySetStateName ENTITY_ID, 'ERROR' 89 return 90 p = unmarshal devs.result 91 attrs = {} 92 attrs["ghs_av"] = p["GHS av"] 93 attrs["hardware_errors"] = p["Hardware Errors"] 94 EntitySetState ENTITY_ID, 95 new_state: 'ENABLED' 96 attribute_values: attrs 97 storage_save: true 98 99 checkPools =-> 100 pools = Miner.pools() 101 if ifError(pools) 102 EntitySetStateName ENTITY_ID, 'ERROR' 103 return 104 p = unmarshal pools.result 105 So(p.length, 'ShouldEqual', '4') 106 107 So(p[0].POOL, 'ShouldEqual', '0') 108 So(p[0].Accepted, 'ShouldEqual', '47169') 109 So(p[0]["Stratum URL"], 'ShouldEqual', 'litecoinpool.org') 110 So(p[0]["User"], 'ShouldEqual', 'user.4') 111 So(p[0]["URL"], 'ShouldEqual', 'stratum+tcp://litecoinpool.org:3333') 112 113 So(p[1].POOL, 'ShouldEqual', '1') 114 So(p[1].Accepted, 'ShouldEqual', '0') 115 So(p[1]["Stratum URL"], 'ShouldEqual', '') 116 So(p[1]["User"], 'ShouldEqual', 'user.4') 117 So(p[1]["URL"], 'ShouldEqual', 'stratum+tcp://us.litecoinpool.org:3333') 118 119 So(p[2].POOL, 'ShouldEqual', '2') 120 So(p[2].Accepted, 'ShouldEqual', '0') 121 So(p[2]["Stratum URL"], 'ShouldEqual', '') 122 So(p[2]["User"], 'ShouldEqual', 'user.4') 123 So(p[2]["URL"], 'ShouldEqual', 'stratum+tcp://us2.litecoinpool.org:3333') 124 125 So(p[3].POOL, 'ShouldEqual', '3') 126 So(p[3].Accepted, 'ShouldEqual', '5502') 127 So(p[3]["Stratum URL"], 'ShouldEqual', 'proto3.bytewall.net') 128 So(p[3]["User"], 'ShouldEqual', '*') 129 So(p[3]["URL"], 'ShouldEqual', '*') 130 131 checkVer =(entityId)-> 132 ver = Miner.version() 133 if ifError(ver) 134 EntitySetStateName ENTITY_ID, 'ERROR' 135 return 136 p = unmarshal ver.result 137 So(p.API, 'ShouldEqual', '3.1') 138 So(p.Miner, 'ShouldEqual', '1.0.1.3') 139 So(p.CompileTime, 'ShouldEqual', 'Fri 11 Jun 15:32:42 MSK 2021') 140 So(p.Type, 'ShouldEqual', 'Antminer L3+ (024)') 141 142 entityAction = (entityId, actionName)-> 143 switch actionName 144 when 'CHECK' then checkStatus() 145 when 'SUM' then checkSum() 146 when 'DEVS' then checkDevs() 147 when 'POOLS' then checkPools() 148 when 'VER' then checkVer(entityId) 149 ` 150 151 const L3PlusStatsJson = `{"STATUS":[{"STATUS":"S","When":1633268527,"Code":70,"Msg":"CGMiner stats","Description":"cgminer 4.9.0 rwglr"}],"STATS":[{"CGMiner":"4.9.0 rwglr","Miner":"1.0.1.3","CompileTime":"Fri 11 Jun 15:32:42 MSK 2021","Type":"Antminer L3+ (024)"}{"STATS":0,"ID":"L30","Elapsed":373469,"Calls":0,"Wait":0.000000,"Max":0.000000,"Min":99999999.000000,"GHS 5s":"415.065","GHS av":411.84,"miner_count":3,"frequency":"425","fan_num":2,"frequency1":"425","frequency2":"422","frequency3":"423","frequency4":"419","fan1":3180,"fan2":3090,"temp_num":3,"temp1":0,"temp2":37,"temp3":37,"temp4":36,"temp2_1":0,"temp2_2":46,"temp2_3":44,"temp2_4":42,"temp31":0,"temp32":0,"temp33":0,"temp34":0,"temp4_1":0,"temp4_2":0,"temp4_3":0,"temp4_4":0,"temp_max":38,"Device Hardware%":0.0000,"no_matching_work":327,"chain_acn1":0,"chain_acn2":72,"chain_acn3":72,"chain_acn4":72,"chain_acs1":"","chain_acs2":" oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo","chain_acs3":" oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo","chain_acs4":" oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo oooooooo","chain_hw1":0,"chain_hw2":26,"chain_hw3":43,"chain_hw4":258,"chain_rate1":"","chain_rate2":"138.50","chain_rate3":"138.85","chain_rate4":"137.72","chain_power1":0.00,"chain_power2":187.79,"chain_power3":188.24,"chain_power4":186.46,"chain_power":562.48,"voltage1":9.44,"voltage2":9.44,"voltage3":9.44,"voltage4":9.44}],"id":1}` 152 const L3PlusSummaryJson = `{"STATUS":[{"STATUS":"S","When":1633279121,"Code":11,"Msg":"Summary","Description":"cgminer 4.9.0 rwglr"}],"SUMMARY":[{"Elapsed":384063,"GHS 5s":"412.925","GHS av":411.85,"Found Blocks":0,"Getworks":37619,"Accepted":46423,"Rejected":530,"Hardware Errors":338,"Utility":7.25,"Discarded":226550,"Stale":21,"Get Failures":169,"Local Work":338070,"Remote Failures":4,"Network Blocks":2603,"Total MH":158174645.0000,"Work Utility":377447.21,"Difficulty Accepted":2397855744.00000000,"Difficulty Rejected":18202624.00000000,"Difficulty Stale":0.00000000,"Best Share":7510923133,"Device Hardware%":0.0000,"Device Rejected%":0.7534,"Pool Rejected%":0.7534,"Pool Stale%":0.0000,"Last getwork":1633279119}],"id":1}` 153 const L3PlusPoolsJson = `{"STATUS":[{"STATUS":"S","When":1633329118,"Code":7,"Msg":"4 Pool(s)","Description":"cgminer 4.9.0 rwglr"}],"POOLS":[{"POOL":0,"URL":"stratum+tcp://litecoinpool.org:3333","Status":"Alive","Priority":0,"Quota":1,"Long Poll":"N","Getworks":19515,"Accepted":47169,"Rejected":331,"Discarded":250226,"Stale":16,"Get Failures":2,"Remote Failures":0,"User":"user.4","Last Share Time":"0:00:06","Diff":"65.5K","Diff1 Shares":10494726,"Proxy Type":"","Proxy":"","Difficulty Accepted":2659909632.00000000,"Difficulty Rejected":18219008.00000000,"Difficulty Stale":0.00000000,"Last Share Difficulty":65536.00000000,"Has Stratum":true,"Stratum Active":true,"Stratum URL":"litecoinpool.org","Has GBT":false,"Best Share":7510923133,"Pool Rejected%":0.6803,"Pool Stale%":0.0000},{"POOL":1,"URL":"stratum+tcp://us.litecoinpool.org:3333","Status":"Alive","Priority":1,"Quota":1,"Long Poll":"N","Getworks":1,"Accepted":0,"Rejected":0,"Discarded":0,"Stale":0,"Get Failures":0,"Remote Failures":0,"User":"user.4","Last Share Time":"0","Diff":"","Diff1 Shares":0,"Proxy Type":"","Proxy":"","Difficulty Accepted":0.00000000,"Difficulty Rejected":0.00000000,"Difficulty Stale":0.00000000,"Last Share Difficulty":0.00000000,"Has Stratum":true,"Stratum Active":false,"Stratum URL":"","Has GBT":false,"Best Share":0,"Pool Rejected%":0.0000,"Pool Stale%":0.0000},{"POOL":2,"URL":"stratum+tcp://us2.litecoinpool.org:3333","Status":"Alive","Priority":2,"Quota":1,"Long Poll":"N","Getworks":1,"Accepted":0,"Rejected":0,"Discarded":0,"Stale":0,"Get Failures":0,"Remote Failures":0,"User":"user.4","Last Share Time":"0","Diff":"","Diff1 Shares":0,"Proxy Type":"","Proxy":"","Difficulty Accepted":0.00000000,"Difficulty Rejected":0.00000000,"Difficulty Stale":0.00000000,"Last Share Difficulty":0.00000000,"Has Stratum":true,"Stratum Active":false,"Stratum URL":"","Has GBT":false,"Best Share":0,"Pool Rejected%":0.0000,"Pool Stale%":0.0000},{"POOL":3,"URL":"*","Status":"Alive","Priority":999,"Quota":1,"Long Poll":"N","Getworks":22556,"Accepted":5502,"Rejected":288,"Discarded":5377,"Stale":5,"Get Failures":200,"Remote Failures":4,"User":"*","Last Share Time":"0:14:56","Diff":"131K","Diff1 Shares":225056,"Proxy Type":"","Proxy":"","Difficulty Accepted":54272000.00000000,"Difficulty Rejected":2957312.00000000,"Difficulty Stale":0.00000000,"Last Share Difficulty":131072.00000000,"Has Stratum":true,"Stratum Active":true,"Stratum URL":"proto3.bytewall.net","Has GBT":false,"Best Share":66992548,"Pool Rejected%":5.1675,"Pool Stale%":0.0000}],"id":1}` 154 const L3PlusVerJson = `{"STATUS":[{"STATUS":"S","When":1633332261,"Code":22,"Msg":"CGMiner versions","Description":"cgminer 4.9.0 rwglr"}],"VERSION":[{"CGMiner":"4.9.0 rwglr","API":"3.1","Miner":"1.0.1.3","CompileTime":"Fri 11 Jun 15:32:42 MSK 2021","Type":"Antminer L3+ (024)"}],"id":1}` 155 156 var getFixture = func(str string) []byte { 157 return append([]byte(str), byte(0x00)) 158 } 159 160 Convey("cgminer", t, func(ctx C) { 161 _ = container.Invoke(func(adaptors *adaptors.Adaptors, 162 scriptService scripts.ScriptService, 163 supervisor supervisor.Supervisor, 164 eventBus bus.Bus) { 165 166 // register plugins 167 AddPlugin(adaptors, "cgminer") 168 169 serviceCh := WaitService(eventBus, time.Second*5, "Supervisor") 170 pluginsCh := WaitPlugins(eventBus, time.Second*5, "cgminer") 171 supervisor.Start(context.Background()) 172 defer supervisor.Shutdown(context.Background()) 173 So(<-serviceCh, ShouldBeTrue) 174 So(<-pluginsCh, ShouldBeTrue) 175 176 // bind convey 177 RegisterConvey(scriptService, ctx) 178 179 // add scripts 180 // ------------------------------------------------ 181 182 plugScript, err := AddScript("cgminer script", cgminerSourceScript, adaptors, scriptService) 183 So(err, ShouldBeNil) 184 185 // add entity 186 // ------------------------------------------------ 187 var port = GetPort() 188 var host = "127.0.0.1" 189 190 l3Ent := GetNewBitmineL3("device1") 191 l3Ent.Settings[cgminer.SettingHost].Value = host 192 l3Ent.Settings[cgminer.SettingPort].Value = port 193 l3Ent.Actions = []*m.EntityAction{ 194 { 195 Name: "ENABLE", 196 Description: "включить", 197 Script: plugScript, 198 }, 199 { 200 Name: "DISABLE", 201 Description: "выключить", 202 Script: plugScript, 203 }, 204 { 205 Name: "CHECK", 206 Description: "condition check", 207 Script: plugScript, 208 }, 209 { 210 Name: "SUM", 211 Description: "summary", 212 Script: plugScript, 213 }, 214 { 215 Name: "DEVS", 216 Description: "devs", 217 Script: plugScript, 218 }, 219 { 220 Name: "POOLS", 221 Description: "pools", 222 Script: plugScript, 223 }, 224 { 225 Name: "VER", 226 Description: "ver", 227 Script: plugScript, 228 }, 229 } 230 l3Ent.States = []*m.EntityState{ 231 { 232 Name: "ENABLED", 233 Description: "enabled state", 234 }, 235 { 236 Name: "DISABLED", 237 Description: "disabled state", 238 }, 239 { 240 Name: "ERROR", 241 Description: "error state", 242 }, 243 } 244 l3Ent.Attributes = m.Attributes{ 245 "heat": { 246 Name: "heat", 247 Type: common.AttributeBool, 248 }, 249 "chain1_temp_chip": { 250 Name: "chain1_temp_chip", 251 Type: common.AttributeInt, 252 }, 253 "chain2_temp_chip": { 254 Name: "chain2_temp_chip", 255 Type: common.AttributeInt, 256 }, 257 "chain3_temp_chip": { 258 Name: "chain3_temp_chip", 259 Type: common.AttributeInt, 260 }, 261 "chain4_temp_chip": { 262 Name: "chain4_temp_chip", 263 Type: common.AttributeInt, 264 }, 265 "chain1_temp_pcb": { 266 Name: "chain1_temp_pcb", 267 Type: common.AttributeInt, 268 }, 269 "chain2_temp_pcb": { 270 Name: "chain2_temp_pcb", 271 Type: common.AttributeInt, 272 }, 273 "chain3_temp_pcb": { 274 Name: "chain3_temp_pcb", 275 Type: common.AttributeInt, 276 }, 277 "chain4_temp_pcb": { 278 Name: "chain4_temp_pcb", 279 Type: common.AttributeInt, 280 }, 281 "fan1": { 282 Name: "fan1", 283 Type: common.AttributeInt, 284 }, 285 "fan2": { 286 Name: "fan2", 287 Type: common.AttributeInt, 288 }, 289 "ghs_av": { 290 Name: "ghs_av", 291 Type: common.AttributeInt, 292 }, 293 "hardware_errors": { 294 Name: "hardware_errors", 295 Type: common.AttributeInt, 296 }, 297 } 298 err = adaptors.Entity.Add(context.Background(), l3Ent) 299 ctx.So(err, ShouldBeNil) 300 _, err = adaptors.EntityStorage.Add(context.Background(), &m.EntityStorage{ 301 EntityId: l3Ent.Id, 302 Attributes: l3Ent.Attributes.Serialize(), 303 }) 304 So(err, ShouldBeNil) 305 306 eventBus.Publish("system/models/entities/"+l3Ent.Id.String(), events.EventCreatedEntityModel{ 307 EntityId: l3Ent.Id, 308 }) 309 310 time.Sleep(time.Second) 311 312 // ------------------------------------------------ 313 314 t.Run("antminer L3+ stats", func(t *testing.T) { 315 Convey("stats", t, func(ctx C) { 316 317 ctx2, cancel := context.WithCancel(context.Background()) 318 go func() { _ = MockTCPServer(ctx2, host, port, getFixture(L3PlusStatsJson)) }() 319 time.Sleep(time.Millisecond * 500) 320 supervisor.CallAction(l3Ent.Id, "CHECK", nil) 321 time.Sleep(time.Second) 322 cancel() 323 324 lastState, err := adaptors.EntityStorage.GetLastByEntityId(context.Background(), l3Ent.Id) 325 ctx.So(err, ShouldBeNil) 326 327 debug.Println(lastState) 328 329 ctx.So(lastState.Attributes["chain1_temp_chip"], ShouldEqual, 0) 330 ctx.So(lastState.Attributes["chain1_temp_pcb"], ShouldEqual, 0) 331 ctx.So(lastState.Attributes["chain2_temp_chip"], ShouldEqual, 46) 332 ctx.So(lastState.Attributes["chain2_temp_pcb"], ShouldEqual, 37) 333 ctx.So(lastState.Attributes["chain3_temp_chip"], ShouldEqual, 44) 334 ctx.So(lastState.Attributes["chain3_temp_pcb"], ShouldEqual, 37) 335 ctx.So(lastState.Attributes["chain4_temp_chip"], ShouldEqual, 42) 336 ctx.So(lastState.Attributes["chain4_temp_pcb"], ShouldEqual, 36) 337 ctx.So(lastState.Attributes["fan1"], ShouldEqual, 3180) 338 ctx.So(lastState.Attributes["fan2"], ShouldEqual, 3090) 339 }) 340 }) 341 342 t.Run("antminer L3+ summary", func(t *testing.T) { 343 Convey("summary", t, func(ctx C) { 344 345 ctx2, cancel := context.WithCancel(context.Background()) 346 go func() { _ = MockTCPServer(ctx2, host, port, getFixture(L3PlusSummaryJson)) }() 347 time.Sleep(time.Millisecond * 500) 348 supervisor.CallAction(l3Ent.Id, "SUM", nil) 349 time.Sleep(time.Second) 350 cancel() 351 352 lastState, err := adaptors.EntityStorage.GetLastByEntityId(context.Background(), l3Ent.Id) 353 ctx.So(err, ShouldBeNil) 354 355 ctx.So(lastState.Attributes["ghs_av"], ShouldEqual, 411.85) 356 ctx.So(lastState.Attributes["hardware_errors"], ShouldEqual, 338) 357 }) 358 }) 359 360 t.Run("antminer L3+ pools", func(t *testing.T) { 361 Convey("pools", t, func(ctx C) { 362 363 ctx2, cancel := context.WithCancel(context.Background()) 364 go func() { _ = MockTCPServer(ctx2, host, port, getFixture(L3PlusPoolsJson)) }() 365 time.Sleep(time.Millisecond * 500) 366 supervisor.CallAction(l3Ent.Id, "POOLS", nil) 367 time.Sleep(time.Second) 368 cancel() 369 }) 370 }) 371 372 t.Run("antminer L3+ version", func(t *testing.T) { 373 Convey("version", t, func(ctx C) { 374 375 ctx2, cancel := context.WithCancel(context.Background()) 376 go func() { _ = MockTCPServer(ctx2, host, port, getFixture(L3PlusVerJson)) }() 377 time.Sleep(time.Millisecond * 500) 378 supervisor.CallAction(l3Ent.Id, "VER", nil) 379 time.Sleep(time.Second) 380 cancel() 381 }) 382 }) 383 384 }) 385 }) 386 }