feat(v2): T-308C editable settings form (camera + params) applied on start
Settings tab is now an editable, scrollable form: camera connection (host/port/channel/user/masked password/transport/timeout/low-latency) and common detection params, each with a save button that writes the untracked local config. cmd/silver-pose pre-fills from the local file, passes the config path, and the controller reloads config on each Start so saved edits apply next run. UI cross-compiles for Windows. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+10
@@ -612,3 +612,13 @@
|
||||
- T-308 落 DOING:分三段——A 界面四项(80%窗口/2×3画廊/Esc关弹窗/按钮灰显)、B V2 配置支持结构化摄像头(账号密码,百分号编码,仅未跟踪本地配置)、C Gio 可编辑设置表单(摄像头+常用参数,保存下次启动生效)。用户明确要求内网演示要账号密码框;合规做法=凭证仅入未跟踪 config.local.json,公开示例禁凭证、凭证不进 config_version/日志/事件(V2 已脱敏 ffmpeg 日志、版本已排除 URL)。顺带解决手动 %40 编码痛点。
|
||||
- 验证:每段 `GOOS=windows CGO_ENABLED=0 go build -mod=vendor` 交叉编译;配置层本地 go test;窗口目视需 Windows。
|
||||
- 下一步:先做 A。
|
||||
|
||||
## 【2026-07-23】T-308 A/B/C 代码完成(交叉编译验证)
|
||||
|
||||
- 状态:DOING(三段代码完成 + Windows 交叉编译/本地 go test 通过;窗口目视需 Windows)
|
||||
- A(界面四项):80% 屏幕开窗(GetSystemMetrics)、画廊 2×3 且右栏缩小、Esc 关全图弹窗(key focus+filter)、开始/停止按运行状态灰显禁用(gtx.Disabled;硬失败 ReportIssue 复位 running)。`GOOS=windows` 交叉编译通过。
|
||||
- B(V2 结构化摄像头):`config` 接受 host/port/channel/username/password(仅本地)或 rtsp_url_env,`net/url` 自动百分号编码(修掉手动 %40);新增 `WriteLocalCameraSource`/`WriteLocalEventTuning`;护栏保留(拒绝整段 url/rtsp_url、凭证不进 config_version、公开示例无凭证测试)。本地 `go test ./internal/config` 通过。
|
||||
- C(Gio 可编辑设置表单):设置页改为可编辑——摄像头(host/端口/通道/账号/密码掩码/传输/超时/低延迟)与常用参数(关键点/确认窗/水平角/模型置信度/快速下移/膝踝),两个保存按钮写回本地配置;`cmd/silver-pose` 预填(读本地文件)、传 ConfigPath,控制器在每次开始监控时 `config.Load` 重载使保存生效。UI 交叉编译通过、gofmt 干净。
|
||||
- 阻塞:`cmd/silver-pose` 全量构建需 Windows+CGo(onnxruntime)+Gio;窗口目视/表单交互需 Windows。
|
||||
- 决策:账号密码按用户要求进设置(内网演示),但仅存未跟踪 config.local.json、UI 掩码、不入日志/版本/事件;source.id 用固定/原值避免把内网 IP 写进 JSONL。设置改动"下次开始监控生效"经控制器重载 config 实现(对齐 V1 草稿→运行快照语义)。
|
||||
- 下一步:用户 Windows `go run ./cmd/silver-pose` 目视——80% 窗口、2×3 画廊、Esc 关图、按钮灰显、设置页可编辑并保存后重启监控生效。
|
||||
|
||||
+75
-23
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -32,8 +33,8 @@ func main() {
|
||||
if err := validateStartup(cfg); err != nil {
|
||||
log.Fatal("启动预检失败:", err)
|
||||
}
|
||||
controller := &controller{config: cfg}
|
||||
window, err := ui.NewWindow(settingsFor(cfg), ui.Commands{Start: controller.Start, Stop: controller.Stop})
|
||||
controller := &controller{config: cfg, configPath: *configPath}
|
||||
window, err := ui.NewWindow(settingsFor(cfg, *configPath), ui.Commands{Start: controller.Start, Stop: controller.Stop})
|
||||
if err != nil {
|
||||
log.Fatal("创建操作窗口失败:", err)
|
||||
}
|
||||
@@ -43,11 +44,12 @@ func main() {
|
||||
}
|
||||
|
||||
type controller struct {
|
||||
config config.Config
|
||||
window *ui.Window
|
||||
mu sync.Mutex
|
||||
cancel func()
|
||||
runID uint64
|
||||
config config.Config
|
||||
configPath string
|
||||
window *ui.Window
|
||||
mu sync.Mutex
|
||||
cancel func()
|
||||
runID uint64
|
||||
}
|
||||
|
||||
func (controller *controller) Start() {
|
||||
@@ -83,22 +85,27 @@ func (controller *controller) run(ctx context.Context, cancel func(), runID uint
|
||||
controller.mu.Unlock()
|
||||
cancel()
|
||||
}()
|
||||
runtime, err := spike.OpenRuntime(controller.config.Model.ONNXPath, controller.config.Model.ONNXRuntimeDLLPath)
|
||||
cfg, err := config.Load(controller.configPath)
|
||||
if err != nil {
|
||||
controller.window.ReportIssue("配置无效,无法开始监控")
|
||||
return
|
||||
}
|
||||
runtime, err := spike.OpenRuntime(cfg.Model.ONNXPath, cfg.Model.ONNXRuntimeDLLPath)
|
||||
if err != nil {
|
||||
log.Printf("初始化 ONNX Runtime 失败:%v", err)
|
||||
controller.window.ReportIssue("无法初始化 ONNX Runtime")
|
||||
return
|
||||
}
|
||||
engine, err := fall.NewEngine(fall.EngineConfig{
|
||||
KeypointConfidenceThreshold: controller.config.Event.KeypointConfidenceThreshold,
|
||||
SuspectWindowSeconds: controller.config.Event.SuspectWindowSeconds,
|
||||
ConfirmWindowSeconds: controller.config.Event.ConfirmWindowSeconds,
|
||||
RecoveryWindowSeconds: controller.config.Event.RecoveryWindowSeconds,
|
||||
CooldownSeconds: controller.config.Event.CooldownSeconds,
|
||||
RequireRapidDrop: controller.config.Event.RequireRapidDrop,
|
||||
RequireLowerBody: controller.config.Event.RequireLowerBody,
|
||||
HorizontalAngleThresholdDegrees: controller.config.Event.HorizontalAngleThresholdDegrees,
|
||||
ConfigVersion: controller.config.RuntimeConfigVersion,
|
||||
KeypointConfidenceThreshold: cfg.Event.KeypointConfidenceThreshold,
|
||||
SuspectWindowSeconds: cfg.Event.SuspectWindowSeconds,
|
||||
ConfirmWindowSeconds: cfg.Event.ConfirmWindowSeconds,
|
||||
RecoveryWindowSeconds: cfg.Event.RecoveryWindowSeconds,
|
||||
CooldownSeconds: cfg.Event.CooldownSeconds,
|
||||
RequireRapidDrop: cfg.Event.RequireRapidDrop,
|
||||
RequireLowerBody: cfg.Event.RequireLowerBody,
|
||||
HorizontalAngleThresholdDegrees: cfg.Event.HorizontalAngleThresholdDegrees,
|
||||
ConfigVersion: cfg.RuntimeConfigVersion,
|
||||
SessionID: time.Now().UTC().Format("20060102-150405"),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -108,13 +115,13 @@ func (controller *controller) run(ctx context.Context, cancel func(), runID uint
|
||||
return
|
||||
}
|
||||
stream := source.Start(ctx, source.Config{
|
||||
SourceURL: controller.config.Source.URL, FFmpegPath: controller.config.Tools.FFmpegPath,
|
||||
FFprobePath: controller.config.Tools.FFprobePath, Transport: controller.config.Source.Transport,
|
||||
Timeout: controller.config.Source.Timeout, LowLatency: controller.config.Source.LowLatency,
|
||||
SourceURL: cfg.Source.URL, FFmpegPath: cfg.Tools.FFmpegPath,
|
||||
FFprobePath: cfg.Tools.FFprobePath, Transport: cfg.Source.Transport,
|
||||
Timeout: cfg.Source.Timeout, LowLatency: cfg.Source.LowLatency,
|
||||
}, source.Dependencies{})
|
||||
monitored, err := monitor.New(
|
||||
stream, runtime, engine, controller.config.Model.ConfidenceThreshold,
|
||||
alert.NewDispatcher(controller.config.Artifacts.EventDirectory, controller.config.Source.ID), time.Now,
|
||||
stream, runtime, engine, cfg.Model.ConfidenceThreshold,
|
||||
alert.NewDispatcher(cfg.Artifacts.EventDirectory, cfg.Source.ID), time.Now,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("启动监控管线失败:%v", err)
|
||||
@@ -172,7 +179,7 @@ func sha256File(path string) (string, error) {
|
||||
return fmt.Sprintf("%x", digest.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func settingsFor(cfg config.Config) ui.Settings {
|
||||
func settingsFor(cfg config.Config, configPath string) ui.Settings {
|
||||
return ui.Settings{
|
||||
SourceEnvironment: cfg.Source.RTSPURLEnv,
|
||||
ModelSHA256: cfg.Model.SHA256,
|
||||
@@ -180,5 +187,50 @@ func settingsFor(cfg config.Config) ui.Settings {
|
||||
EventSummary: fmt.Sprintf("确认 %.1f 秒;水平角 %.0f°;快速下移=%t;下肢=%t",
|
||||
cfg.Event.ConfirmWindowSeconds, cfg.Event.HorizontalAngleThresholdDegrees,
|
||||
cfg.Event.RequireRapidDrop, cfg.Event.RequireLowerBody),
|
||||
ConfigPath: configPath,
|
||||
Camera: cameraFieldsFrom(configPath),
|
||||
Params: ui.ParamFields{
|
||||
KeypointConfidence: float64(cfg.Event.KeypointConfidenceThreshold),
|
||||
ConfirmWindowSeconds: cfg.Event.ConfirmWindowSeconds,
|
||||
HorizontalAngleDegrees: float64(cfg.Event.HorizontalAngleThresholdDegrees),
|
||||
ModelConfidence: float64(cfg.Model.ConfidenceThreshold),
|
||||
RequireRapidDrop: cfg.Event.RequireRapidDrop,
|
||||
RequireLowerBody: cfg.Event.RequireLowerBody,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// cameraFieldsFrom pre-fills the settings form from the untracked local config's
|
||||
// source block. It reads credentials only to display them in the (masked) form;
|
||||
// they are never logged.
|
||||
func cameraFieldsFrom(configPath string) ui.CameraFields {
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return ui.CameraFields{}
|
||||
}
|
||||
var document struct {
|
||||
Source struct {
|
||||
ID string `json:"id"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Channel string `json:"channel"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Transport string `json:"transport"`
|
||||
TimeoutSeconds float64 `json:"timeout_seconds"`
|
||||
LowLatency *bool `json:"low_latency"`
|
||||
} `json:"source"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &document); err != nil {
|
||||
return ui.CameraFields{}
|
||||
}
|
||||
lowLatency := true
|
||||
if document.Source.LowLatency != nil {
|
||||
lowLatency = *document.Source.LowLatency
|
||||
}
|
||||
return ui.CameraFields{
|
||||
ID: document.Source.ID, Host: document.Source.Host, Port: document.Source.Port,
|
||||
Channel: document.Source.Channel, Username: document.Source.Username, Password: document.Source.Password,
|
||||
Transport: document.Source.Transport, TimeoutSeconds: document.Source.TimeoutSeconds, LowLatency: lowLatency,
|
||||
}
|
||||
}
|
||||
|
||||
+236
-8
@@ -6,6 +6,7 @@ import (
|
||||
"image/color"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
"gioui.org/widget/material"
|
||||
|
||||
"silverpose/v2/internal/alert"
|
||||
"silverpose/v2/internal/config"
|
||||
"silverpose/v2/internal/fall"
|
||||
"silverpose/v2/internal/monitor"
|
||||
)
|
||||
@@ -41,6 +43,7 @@ var (
|
||||
colorSubtle = color.NRGBA{R: 0x52, G: 0x65, B: 0x7C, A: 0xFF}
|
||||
colorAccent = color.NRGBA{R: 0x25, G: 0x63, B: 0xEB, A: 0xFF}
|
||||
colorAlert = color.NRGBA{R: 0xC6, G: 0x28, B: 0x28, A: 0xFF}
|
||||
colorStroke = color.NRGBA{R: 0xC8, G: 0xD4, B: 0xE3, A: 0xFF}
|
||||
colorScrim = color.NRGBA{A: 0xB0}
|
||||
)
|
||||
|
||||
@@ -49,6 +52,33 @@ type Settings struct {
|
||||
ModelSHA256 string
|
||||
RuntimeSummary string
|
||||
EventSummary string
|
||||
ConfigPath string
|
||||
Camera CameraFields
|
||||
Params ParamFields
|
||||
}
|
||||
|
||||
// CameraFields pre-fill the editable camera connection form. Credentials live
|
||||
// only in the untracked local config; the form saves them back there.
|
||||
type CameraFields struct {
|
||||
ID string
|
||||
Host string
|
||||
Port int
|
||||
Channel string
|
||||
Username string
|
||||
Password string
|
||||
Transport string
|
||||
TimeoutSeconds float64
|
||||
LowLatency bool
|
||||
}
|
||||
|
||||
// ParamFields pre-fill the editable common detection parameters.
|
||||
type ParamFields struct {
|
||||
KeypointConfidence float64
|
||||
ConfirmWindowSeconds float64
|
||||
HorizontalAngleDegrees float64
|
||||
ModelConfidence float64
|
||||
RequireRapidDrop bool
|
||||
RequireLowerBody bool
|
||||
}
|
||||
|
||||
type Commands struct {
|
||||
@@ -89,6 +119,7 @@ type Window struct {
|
||||
incoming []galleryIncoming
|
||||
seenEvents map[string]struct{}
|
||||
running bool
|
||||
configPath string
|
||||
|
||||
// Touched only by the event-loop goroutine.
|
||||
imageOp paint.ImageOp
|
||||
@@ -102,6 +133,32 @@ type Window struct {
|
||||
gallery []*galleryItem
|
||||
viewing *galleryItem
|
||||
viewerClose widget.Clickable
|
||||
|
||||
// Settings form (event-loop goroutine only).
|
||||
editHost widget.Editor
|
||||
editPort widget.Editor
|
||||
editChannel widget.Editor
|
||||
editUsername widget.Editor
|
||||
editPassword widget.Editor
|
||||
editTransport widget.Editor
|
||||
editTimeout widget.Editor
|
||||
editKeypoint widget.Editor
|
||||
editConfirm widget.Editor
|
||||
editAngle widget.Editor
|
||||
editModelConf widget.Editor
|
||||
lowLatency widget.Bool
|
||||
requireRapid widget.Bool
|
||||
requireLower widget.Bool
|
||||
saveCamera widget.Clickable
|
||||
saveParams widget.Clickable
|
||||
settingsStatus string
|
||||
settingsList widget.List
|
||||
cameraID string
|
||||
}
|
||||
|
||||
func setEditor(editor *widget.Editor, value string) {
|
||||
editor.SingleLine = true
|
||||
editor.SetText(value)
|
||||
}
|
||||
|
||||
func NewWindow(settings Settings, commands Commands) (*Window, error) {
|
||||
@@ -139,9 +196,55 @@ func NewWindow(settings Settings, commands Commands) (*Window, error) {
|
||||
tabs: []string{"监控", "设置"},
|
||||
}
|
||||
window.tabButton = make([]widget.Clickable, len(window.tabs))
|
||||
|
||||
window.configPath = settings.ConfigPath
|
||||
camera, params := settings.Camera, settings.Params
|
||||
setEditor(&window.editHost, camera.Host)
|
||||
setEditor(&window.editPort, strconv.Itoa(defaultPort(camera.Port)))
|
||||
setEditor(&window.editChannel, defaultString(camera.Channel, "102"))
|
||||
setEditor(&window.editUsername, camera.Username)
|
||||
setEditor(&window.editPassword, camera.Password)
|
||||
window.editPassword.Mask = '•'
|
||||
setEditor(&window.editTransport, defaultString(camera.Transport, "tcp"))
|
||||
setEditor(&window.editTimeout, formatFloat(defaultFloat(camera.TimeoutSeconds, 5)))
|
||||
window.lowLatency.Value = camera.LowLatency
|
||||
setEditor(&window.editKeypoint, formatFloat(defaultFloat(params.KeypointConfidence, 0.4)))
|
||||
setEditor(&window.editConfirm, formatFloat(defaultFloat(params.ConfirmWindowSeconds, 1.8)))
|
||||
setEditor(&window.editAngle, formatFloat(defaultFloat(params.HorizontalAngleDegrees, 45)))
|
||||
setEditor(&window.editModelConf, formatFloat(defaultFloat(params.ModelConfidence, 0.25)))
|
||||
window.requireRapid.Value = params.RequireRapidDrop
|
||||
window.requireLower.Value = params.RequireLowerBody
|
||||
window.settingsList.Axis = layout.Vertical
|
||||
window.cameraID = defaultString(camera.ID, "ip-camera")
|
||||
|
||||
return window, nil
|
||||
}
|
||||
|
||||
func defaultPort(port int) int {
|
||||
if port <= 0 {
|
||||
return 554
|
||||
}
|
||||
return port
|
||||
}
|
||||
|
||||
func defaultString(value, fallback string) string {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func defaultFloat(value, fallback float64) float64 {
|
||||
if value == 0 {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func formatFloat(value float64) string {
|
||||
return strconv.FormatFloat(value, 'f', -1, 64)
|
||||
}
|
||||
|
||||
func (window *Window) Run() int {
|
||||
go func() {
|
||||
err := window.loop()
|
||||
@@ -551,18 +654,143 @@ func (window *Window) layoutDialog(gtx layout.Context, record alert.Record) layo
|
||||
}
|
||||
|
||||
func (window *Window) layoutSettings(gtx layout.Context, model ViewModel) layout.Dimensions {
|
||||
if window.saveCamera.Clicked(gtx) {
|
||||
window.onSaveCamera()
|
||||
}
|
||||
if window.saveParams.Clicked(gtx) {
|
||||
window.onSaveParams()
|
||||
}
|
||||
rows := []layout.Widget{
|
||||
window.heading("摄像头连接(账号密码仅存本地配置,下次启动生效)"),
|
||||
window.editorRow("IP / 主机", &window.editHost),
|
||||
window.editorRow("端口", &window.editPort),
|
||||
window.editorRow("通道(101 主 / 102 子)", &window.editChannel),
|
||||
window.editorRow("账号", &window.editUsername),
|
||||
window.editorRow("密码", &window.editPassword),
|
||||
window.editorRow("传输 (tcp/udp)", &window.editTransport),
|
||||
window.editorRow("连接超时 (秒)", &window.editTimeout),
|
||||
window.checkRow("低延迟", &window.lowLatency),
|
||||
window.buttonRow(&window.saveCamera, "保存摄像头"),
|
||||
window.spacerRow(),
|
||||
window.heading("常用检测参数(下次启动生效)"),
|
||||
window.editorRow("关键点置信度", &window.editKeypoint),
|
||||
window.editorRow("确认窗口 (秒, 1-3)", &window.editConfirm),
|
||||
window.editorRow("水平角度阈值 (°)", &window.editAngle),
|
||||
window.editorRow("模型置信度", &window.editModelConf),
|
||||
window.checkRow("要求先快速下移", &window.requireRapid),
|
||||
window.checkRow("要求膝踝清晰", &window.requireLower),
|
||||
window.buttonRow(&window.saveParams, "保存参数"),
|
||||
window.labelRow(window.settingsStatus),
|
||||
window.spacerRow(),
|
||||
window.heading("运行依赖(只读)"),
|
||||
window.labelRow("模型 SHA-256:" + shortHash(window.settings.ModelSHA256)),
|
||||
window.labelRow("运行依赖:" + window.settings.RuntimeSummary),
|
||||
}
|
||||
return layout.UniformInset(unit.Dp(12)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(material.H6(window.theme, "来源与运行依赖(只读,不显示 RTSP 地址或密码)").Layout),
|
||||
layout.Rigid(material.Body1(window.theme, model.SettingsSummary).Layout),
|
||||
layout.Rigid(material.Body1(window.theme, "模型 SHA-256:"+shortHash(window.settings.ModelSHA256)).Layout),
|
||||
layout.Rigid(material.Body1(window.theme, "运行依赖:"+window.settings.RuntimeSummary).Layout),
|
||||
layout.Rigid(material.Body1(window.theme, "事件参数:"+window.settings.EventSummary).Layout),
|
||||
layout.Rigid(material.Body2(window.theme, "参数在配置文件中维护;修改后请停止并重新开始监控。").Layout),
|
||||
)
|
||||
return material.List(window.theme, &window.settingsList).Layout(gtx, len(rows), func(gtx layout.Context, index int) layout.Dimensions {
|
||||
return layout.UniformInset(unit.Dp(4)).Layout(gtx, rows[index])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (window *Window) heading(text string) layout.Widget {
|
||||
return func(gtx layout.Context) layout.Dimensions {
|
||||
return material.Body1(window.theme, text).Layout(gtx)
|
||||
}
|
||||
}
|
||||
|
||||
func (window *Window) labelRow(text string) layout.Widget {
|
||||
return func(gtx layout.Context) layout.Dimensions {
|
||||
return material.Body2(window.theme, text).Layout(gtx)
|
||||
}
|
||||
}
|
||||
|
||||
func (window *Window) spacerRow() layout.Widget {
|
||||
return func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Spacer{Height: unit.Dp(8)}.Layout(gtx)
|
||||
}
|
||||
}
|
||||
|
||||
func (window *Window) editorRow(label string, editor *widget.Editor) layout.Widget {
|
||||
return func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
gtx.Constraints.Min.X = gtx.Dp(150)
|
||||
return material.Body2(window.theme, label).Layout(gtx)
|
||||
}),
|
||||
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
||||
border := widget.Border{Color: colorStroke, Width: unit.Dp(1), CornerRadius: unit.Dp(4)}
|
||||
return border.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.UniformInset(unit.Dp(6)).Layout(gtx, material.Editor(window.theme, editor, "").Layout)
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (window *Window) checkRow(label string, value *widget.Bool) layout.Widget {
|
||||
return func(gtx layout.Context) layout.Dimensions {
|
||||
return material.CheckBox(window.theme, value, label).Layout(gtx)
|
||||
}
|
||||
}
|
||||
|
||||
func (window *Window) buttonRow(click *widget.Clickable, label string) layout.Widget {
|
||||
return func(gtx layout.Context) layout.Dimensions {
|
||||
return smallButton(gtx, window.theme, click, label)
|
||||
}
|
||||
}
|
||||
|
||||
func (window *Window) onSaveCamera() {
|
||||
if window.configPath == "" {
|
||||
window.settingsStatus = "未指定本地配置路径,无法保存"
|
||||
return
|
||||
}
|
||||
port, err := strconv.Atoi(strings.TrimSpace(window.editPort.Text()))
|
||||
if err != nil || port < 1 || port > 65535 {
|
||||
window.settingsStatus = "端口无效"
|
||||
return
|
||||
}
|
||||
timeout, err := strconv.ParseFloat(strings.TrimSpace(window.editTimeout.Text()), 64)
|
||||
if err != nil {
|
||||
window.settingsStatus = "连接超时无效"
|
||||
return
|
||||
}
|
||||
transport := strings.ToLower(strings.TrimSpace(window.editTransport.Text()))
|
||||
if transport != "tcp" && transport != "udp" {
|
||||
window.settingsStatus = "传输须为 tcp 或 udp"
|
||||
return
|
||||
}
|
||||
if err := config.WriteLocalCameraSource(window.configPath, window.cameraID,
|
||||
strings.TrimSpace(window.editHost.Text()), port, strings.TrimSpace(window.editChannel.Text()),
|
||||
strings.TrimSpace(window.editUsername.Text()), window.editPassword.Text(),
|
||||
transport, timeout, window.lowLatency.Value); err != nil {
|
||||
window.settingsStatus = "保存失败:" + err.Error()
|
||||
return
|
||||
}
|
||||
window.settingsStatus = "摄像头已保存,将在下次开始监控时生效"
|
||||
}
|
||||
|
||||
func (window *Window) onSaveParams() {
|
||||
if window.configPath == "" {
|
||||
window.settingsStatus = "未指定本地配置路径,无法保存"
|
||||
return
|
||||
}
|
||||
keypoint, keypointErr := strconv.ParseFloat(strings.TrimSpace(window.editKeypoint.Text()), 64)
|
||||
confirm, confirmErr := strconv.ParseFloat(strings.TrimSpace(window.editConfirm.Text()), 64)
|
||||
angle, angleErr := strconv.ParseFloat(strings.TrimSpace(window.editAngle.Text()), 64)
|
||||
modelConfidence, modelErr := strconv.ParseFloat(strings.TrimSpace(window.editModelConf.Text()), 64)
|
||||
if keypointErr != nil || confirmErr != nil || angleErr != nil || modelErr != nil {
|
||||
window.settingsStatus = "参数须为数字"
|
||||
return
|
||||
}
|
||||
if err := config.WriteLocalEventTuning(window.configPath, keypoint, confirm, angle, modelConfidence,
|
||||
window.requireRapid.Value, window.requireLower.Value); err != nil {
|
||||
window.settingsStatus = "保存失败:" + err.Error()
|
||||
return
|
||||
}
|
||||
window.settingsStatus = "参数已保存,将在下次开始监控时生效"
|
||||
}
|
||||
|
||||
func highestState(result fall.FrameResult) fall.State {
|
||||
state := fall.Normal
|
||||
for _, person := range result.People {
|
||||
|
||||
Reference in New Issue
Block a user