local-tool/: visionA-local desktop app
- M1: Wails shell + Go server + Next.js UI + Mock mode (macOS dmg ready)
- M2: i18n (zh-TW/en) + Settings 4-tab refactor
- M3: Embedded Python 3.12 runtime (python-build-standalone) + KneronPLUS wheels
- M4: Windows Inno Setup script (build on Windows runner)
- M5: Linux AppImage script + udev rule (build on Linux runner)
- M6: ffmpeg (GPL, pending legal review) + yt-dlp bundled
- Lifecycle: watchServer health check, fatal native dialog,
Wails IPC raise endpoint, stale process cleanup
.autoflow/: full PRD / Design Spec / Architecture / Testing docs
(4 rounds tri-party discussion + cross review)
.github/workflows/: macOS / Windows / Linux build CI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
184 lines
4.2 KiB
Go
184 lines
4.2 KiB
Go
package mock
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"sync"
|
|
"time"
|
|
|
|
"visiona-local/server/internal/driver"
|
|
)
|
|
|
|
var mockLabels = []string{"person", "car", "bicycle", "dog", "cat", "chair", "bottle", "phone"}
|
|
|
|
type MockDriver struct {
|
|
info driver.DeviceInfo
|
|
connected bool
|
|
inferring bool
|
|
modelLoaded string
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func NewMockDriver(info driver.DeviceInfo) *MockDriver {
|
|
return &MockDriver{info: info}
|
|
}
|
|
|
|
func Factory(id string, index int) driver.DeviceDriver {
|
|
info := driver.DeviceInfo{
|
|
ID: id,
|
|
Name: fmt.Sprintf("Kneron KL720 (Mock #%d)", index+1),
|
|
Type: "kneron_kl720",
|
|
Port: fmt.Sprintf("/dev/ttyMOCK%d", index),
|
|
Status: driver.StatusDetected,
|
|
FirmwareVer: "2.2.0-mock",
|
|
}
|
|
return NewMockDriver(info)
|
|
}
|
|
|
|
func (d *MockDriver) Info() driver.DeviceInfo {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
return d.info
|
|
}
|
|
|
|
func (d *MockDriver) Connect() error {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
time.Sleep(200 * time.Millisecond)
|
|
d.connected = true
|
|
d.info.Status = driver.StatusConnected
|
|
return nil
|
|
}
|
|
|
|
func (d *MockDriver) Disconnect() error {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
d.connected = false
|
|
d.inferring = false
|
|
d.info.Status = driver.StatusDisconnected
|
|
return nil
|
|
}
|
|
|
|
func (d *MockDriver) IsConnected() bool {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
return d.connected
|
|
}
|
|
|
|
func (d *MockDriver) Flash(modelPath string, progressCh chan<- driver.FlashProgress) error {
|
|
d.mu.Lock()
|
|
d.info.Status = driver.StatusFlashing
|
|
d.mu.Unlock()
|
|
|
|
type stage struct {
|
|
name string
|
|
duration time.Duration
|
|
startPct int
|
|
endPct int
|
|
}
|
|
|
|
stages := []stage{
|
|
{"preparing", 1 * time.Second, 0, 10},
|
|
{"transferring", 6 * time.Second, 10, 80},
|
|
{"verifying", 2 * time.Second, 80, 95},
|
|
{"rebooting", 1 * time.Second, 95, 99},
|
|
}
|
|
|
|
for _, s := range stages {
|
|
steps := (s.endPct - s.startPct) / 5
|
|
if steps < 1 {
|
|
steps = 1
|
|
}
|
|
interval := s.duration / time.Duration(steps)
|
|
for i := 0; i <= steps; i++ {
|
|
pct := s.startPct + (s.endPct-s.startPct)*i/steps
|
|
progressCh <- driver.FlashProgress{
|
|
Percent: pct,
|
|
Stage: s.name,
|
|
Message: fmt.Sprintf("%s... %d%%", s.name, pct),
|
|
}
|
|
time.Sleep(interval)
|
|
}
|
|
}
|
|
|
|
d.mu.Lock()
|
|
d.modelLoaded = modelPath
|
|
d.info.FlashedModel = modelPath
|
|
d.info.Status = driver.StatusConnected
|
|
d.mu.Unlock()
|
|
|
|
progressCh <- driver.FlashProgress{Percent: 100, Stage: "done", Message: "Flash complete"}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *MockDriver) StartInference() error {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
d.inferring = true
|
|
d.info.Status = driver.StatusInferencing
|
|
return nil
|
|
}
|
|
|
|
func (d *MockDriver) StopInference() error {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
d.inferring = false
|
|
d.info.Status = driver.StatusConnected
|
|
return nil
|
|
}
|
|
|
|
func (d *MockDriver) ReadInference() (*driver.InferenceResult, error) {
|
|
return d.RunInference(nil)
|
|
}
|
|
|
|
func (d *MockDriver) RunInference(imageData []byte) (*driver.InferenceResult, error) {
|
|
time.Sleep(30 * time.Millisecond)
|
|
|
|
numDetections := rand.Intn(3) + 1
|
|
detections := make([]driver.DetectionResult, numDetections)
|
|
for i := 0; i < numDetections; i++ {
|
|
w := 0.1 + rand.Float64()*0.3
|
|
h := 0.1 + rand.Float64()*0.3
|
|
detections[i] = driver.DetectionResult{
|
|
Label: mockLabels[rand.Intn(len(mockLabels))],
|
|
Confidence: 0.3 + rand.Float64()*0.7,
|
|
BBox: driver.BBox{
|
|
X: rand.Float64() * (1 - w),
|
|
Y: rand.Float64() * (1 - h),
|
|
Width: w,
|
|
Height: h,
|
|
},
|
|
}
|
|
}
|
|
|
|
classifications := []driver.ClassResult{
|
|
{Label: "person", Confidence: 0.5 + rand.Float64()*0.5},
|
|
{Label: "car", Confidence: rand.Float64() * 0.5},
|
|
{Label: "dog", Confidence: rand.Float64() * 0.3},
|
|
{Label: "cat", Confidence: rand.Float64() * 0.2},
|
|
{Label: "bicycle", Confidence: rand.Float64() * 0.15},
|
|
}
|
|
|
|
return &driver.InferenceResult{
|
|
TaskType: "detection",
|
|
Timestamp: time.Now().UnixMilli(),
|
|
LatencyMs: 20 + rand.Float64()*30,
|
|
Detections: detections,
|
|
Classifications: classifications,
|
|
}, nil
|
|
}
|
|
|
|
func (d *MockDriver) GetModelInfo() (*driver.ModelInfo, error) {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
if d.modelLoaded == "" {
|
|
return nil, fmt.Errorf("no model loaded")
|
|
}
|
|
return &driver.ModelInfo{
|
|
ID: d.modelLoaded,
|
|
Name: d.modelLoaded,
|
|
LoadedAt: time.Now(),
|
|
}, nil
|
|
}
|