package main import ( "bytes" "encoding/base64" "errors" "github.com/gofiber/fiber/v2" "image" "image/jpeg" "log" "math/rand" "time" ) type TestCommand struct { Command string `json:"cmd"` SequenceId int64 `json:"sequenceId"` } type TestData struct { State string `json:"state"` ExceptionType int `json:"exceptionType,omitempty"` ExceptionDesc string `json:"exceptionDesc,omitempty"` Image string `json:"image,omitempty"` DistanceCM int `json:"distanceCM,omitempty"` Counts int `json:"counts,omitempty"` DurationSec float32 `json:"durationSec,omitempty"` } type TestStat struct { Scene string `json:"scene"` Timestamp int64 `json:"timestamp"` SequenceId int64 `json:"sequenceId"` TestData TestData `json:"sceneData"` } type TestException struct { Type int Desc string } var currentTest *TestCommand var random *rand.Rand var testExceptChannel chan TestException var testStopChannel chan bool var testDurations map[string][2]int func setTestCmd(c *fiber.Ctx) error { var testCmd TestCommand if err := c.BodyParser(&testCmd); nil != err { log.Println("ERROR:", c.Body()) return failureResponse(c, "-1", err.Error(), 200) } else if nil == currentScene { return failureResponse(c, "1", "not in a working scene", 200) } else { switch testCmd.Command { case "START": log.Println("START ...") if nil != currentTest { return failureResponse(c, "2", "A test already started", 200) } if e := recogTaskProc(&testCmd); nil != e { return failureResponse(c, "3", e.Error(), 200) } case "STOP": if nil != testStopChannel { testStopChannel <- true log.Println("STOP ...") } case "CANCEL": if nil != testStopChannel { testStopChannel <- true log.Println("CANCEL ...") } default: log.Println("UNKNOWN ...") } return successResponse(c, "Updated successfully.") } } func pullUpTask(seq int64) error { var tk = time.NewTicker(time.Millisecond * time.Duration(1500+random.Intn(800))) defer func() { tk.Stop() }() time.Sleep(time.Millisecond * 1300) total := 3 + random.Intn(10) n := 0 go pushEvent(currentScene.PushUrl, currentScene.Scene, currentTest.SequenceId, "TEST READY", nil) var started = false for { select { case <-testStopChannel: go pushEvent(currentScene.PushUrl, currentScene.Scene, seq, "TEST END", capturedImage, n) return nil case except := <-testExceptChannel: if except.Type != 0 { go pushException(currentScene.PushUrl, currentScene.Scene, currentTest.SequenceId, except.Type, except.Desc, capturedImage) } case <-tk.C: log.Println("===") if !started { go pushEvent(currentScene.PushUrl, currentScene.Scene, seq, "START", capturedImage, n) started = true } else if n >= total { go pushEvent(currentScene.PushUrl, currentScene.Scene, seq, "TEST END", capturedImage, n) return nil } else { n += 1 go func() { if e := pushEvent(currentScene.PushUrl, currentScene.Scene, seq, "COUNT", capturedImage, n); nil != e { log.Println("pushEvent failed:>", e) testStopChannel <- true } }() } } } } func standJumpTask(seq int64) error { var tk = time.NewTicker(time.Millisecond * 400) defer func() { tk.Stop() }() time.Sleep(time.Millisecond * 500) go pushEvent(currentScene.PushUrl, currentScene.Scene, currentTest.SequenceId, "TEST READY", nil) var started = false for { select { case <-testStopChannel: go pushEvent(currentScene.PushUrl, currentScene.Scene, seq, "TEST END", capturedImage, 1000+random.Intn(1000)) return nil case except := <-testExceptChannel: if except.Type != 0 { go pushException(currentScene.PushUrl, currentScene.Scene, currentTest.SequenceId, except.Type, except.Desc, capturedImage) } case <-tk.C: log.Println("===") if !started { go pushEvent(currentScene.PushUrl, currentScene.Scene, seq, "START", capturedImage) started = true } } } } func sitUpsTask(seq int64) error { var tk = time.NewTicker(time.Millisecond * 500) defer func() { tk.Stop() }() time.Sleep(time.Millisecond * 600) total := 40 + random.Intn(20) n := 0 go pushEvent(currentScene.PushUrl, currentScene.Scene, currentTest.SequenceId, "TEST READY", nil) var started = false for { select { case <-testStopChannel: go pushEvent(currentScene.PushUrl, currentScene.Scene, seq, "TEST END", capturedImage, n) return nil case except := <-testExceptChannel: if except.Type != 0 { go pushException(currentScene.PushUrl, currentScene.Scene, currentTest.SequenceId, except.Type, except.Desc, capturedImage) } case <-tk.C: log.Println("===") if !started { go pushEvent(currentScene.PushUrl, currentScene.Scene, seq, "START", capturedImage, n) started = true } else if n >= total { go pushEvent(currentScene.PushUrl, currentScene.Scene, seq, "TEST END", capturedImage, n) return nil } else { n += 1 go func() { if e := pushEvent(currentScene.PushUrl, currentScene.Scene, seq, "COUNT", capturedImage, n); nil != e { log.Println("pushEvent failed:>", e) testStopChannel <- true } }() } } } } func raceTask(seq int64) error { var tk = time.NewTicker(time.Millisecond * 800) defer func() { tk.Stop() }() time.Sleep(time.Millisecond * 800) go pushEvent(currentScene.PushUrl, currentScene.Scene, currentTest.SequenceId, "TEST READY", nil) var started = false for { select { case <-testStopChannel: go pushEvent(currentScene.PushUrl, currentScene.Scene, currentTest.SequenceId, "TEST END", capturedImage, random.Intn(40)+100) return nil case except := <-testExceptChannel: if except.Type != 0 { go pushException(currentScene.PushUrl, currentScene.Scene, currentTest.SequenceId, except.Type, except.Desc, capturedImage) } case <-tk.C: log.Println("===") if !started { go pushEvent(currentScene.PushUrl, currentScene.Scene, seq, "START", capturedImage) started = true } } } } func encodeImage(img image.Image) string { if nil == img { return "" } log.Println("encodeImage:> Bounds:", img.Bounds()) // _ = saveToFile(img) buf := new(bytes.Buffer) if e := jpeg.Encode(buf, img, &jpeg.Options{ Quality: 60, }); nil != e { log.Println("encodeImage:> failed:", e) } log.Println("encodeImage:> image size:", buf.Len()) s := base64.StdEncoding.EncodeToString(buf.Bytes()) log.Println("encodeImage:> encoded len:", len(s)) return s } func pushException(url string, scene string, seq int64, tp int, desc string, img image.Image) { log.Println("pushException:1>", scene, seq, tp, desc, time.Now()) defer func() { log.Println("pushException:2>", scene, seq, tp, desc, time.Now()) }() var st = TestStat{ Scene: scene, Timestamp: time.Now().UnixMilli(), SequenceId: seq, TestData: TestData{ State: "EXCEPT", ExceptionType: tp, ExceptionDesc: desc, }, } if nil != img { st.TestData.Image = encodeImage(img) } httpPostEx(url, st, 2) } func pushEvent(url string, scene string, seq int64, state string, img image.Image, vals ...int) error { log.Println("pushEvent:1>", scene, seq, state, vals, time.Now()) defer func() { log.Println("pushEvent:2>", scene, seq, state, vals, time.Now()) }() var st = TestStat{ Scene: scene, Timestamp: time.Now().UnixMilli(), SequenceId: seq, TestData: TestData{ State: state, }, } if nil != img { st.TestData.Image = encodeImage(img) } if len(vals) > 0 { switch scene { case "pullUp": st.TestData.Counts = vals[0] case "standJump": st.TestData.DistanceCM = vals[0] case "sitUps": st.TestData.Counts = vals[0] case "race": st.TestData.DurationSec = float32(vals[0]) / 10.0 default: } } return httpPostEx(url, st, 2) } func recogTaskProc(cmd *TestCommand) error { testStopChannel = make(chan bool) testExceptChannel = make(chan TestException) random = rand.New(rand.NewSource(time.Now().UnixNano())) if nil == currentScene { return errors.New("invalid scene state") } if nil == currentScene.proc { return errors.New("invalid scene mode processor") } currentTest = cmd go func() { defer func() { close(testStopChannel) close(testExceptChannel) testStopChannel = nil testExceptChannel = nil currentTest = nil }() if e := currentScene.proc(cmd.SequenceId); nil != e { log.Println(">>>>", e) } }() go func() { var min = testDurations[currentScene.Scene][0] var max = testDurations[currentScene.Scene][1] for i := 0; i < random.Intn(max-min)+min; i++ { time.Sleep(time.Second) } testStopChannel <- true }() return nil } func init() { random = rand.New(rand.NewSource(time.Now().UnixNano())) currentTest = nil testExceptChannel = nil testStopChannel = nil testDurations = map[string][2]int{ "pullUp": [2]int{30, 80}, "standJump": [2]int{10, 30}, "sitUps": [2]int{60, 70}, "race": [2]int{9, 15}, } }