liangliangit 1 month ago
commit 38bf981652

@ -46,6 +46,16 @@ var forbiddenFirefoxDebPackages = map[string]struct{}{
"util-linux": {},
}
var optionalFirefoxDebPackages = map[string]string{
"lubuntu-icon-theme": "theme package may conflict with existing adwaita-icon-theme-full and is not required by Firefox",
}
type firefoxBundleInstallPlan struct {
TotalPackageCount int
HasFirefoxPackage bool
BaseSystemPackages []string
}
func isFirefoxUsable() bool {
cmd := exec.Command("firefox", "--version")
out, err := cmd.CombinedOutput()
@ -55,19 +65,41 @@ func isFirefoxUsable() bool {
}
func isFirefoxInstalled() bool {
cmd := exec.Command("which", "firefox")
out, err := cmd.CombinedOutput()
ok := err == nil
kioskDebugf("firefox install path check ok=%v output=%q err=%v", ok, strings.TrimSpace(string(out)), err)
return ok
if path, err := exec.LookPath("firefox"); err == nil {
kioskDebugf("firefox install path check ok=true path=%s source=PATH", path)
return true
}
for _, path := range []string{
"/usr/bin/firefox",
"/usr/local/bin/firefox",
"/snap/bin/firefox",
} {
if isExecutableFile(path) {
kioskDebugf("firefox install path check ok=true path=%s source=fallback", path)
return true
}
}
kioskDebugf("firefox install path check ok=false")
return false
}
func isExecutableFile(path string) bool {
info, err := os.Stat(path)
return err == nil && !info.IsDir() && info.Mode()&0111 != 0
}
func installFirefox() error {
bundlePath := firefoxBundlePath()
if err := validateFirefoxBundle(bundlePath); err != nil {
plan, err := validateFirefoxBundle(bundlePath)
if err != nil {
kioskErrorf("firefox install refused bundle=%s err=%v", bundlePath, err)
return err
}
baseSystemPackages := strings.Join(plan.BaseSystemPackages, " ")
optionalSkipPackages := strings.Join(optionalFirefoxDebPackageNames(), " ")
kioskInfof("firefox install package filter baseSystemPackages=%q optionalSkipPackages=%q", baseSystemPackages, optionalSkipPackages)
var p struct {
DebPkgPath string `json:"DebPkgPath"`
@ -89,7 +121,7 @@ echo "skip automatic dpkg purge to avoid touching base system packages"
echo ""
echo "=== clean previous offline bundle files ==="
rm -rf /opt/firefox-deb /opt/firefox-deb.tar.gz 2>/dev/null
rm -rf /opt/firefox-deb /opt/firefox-installable /opt/firefox-deb.tar.gz 2>/dev/null
echo ""
echo "=== start Firefox install ==="
@ -101,20 +133,112 @@ echo "2. extract offline bundle..."
cd /opt && tar -xzvf firefox-deb.tar.gz || { echo "error: extract offline bundle failed"; exit 1; }
echo ""
echo "3. install Firefox deb packages..."
cd /opt/firefox-deb && sudo dpkg -i *.deb || { echo "error: install Firefox deb packages failed"; exit 1; }
echo "3. filter Firefox deb packages..."
base_system_packages=%s
optional_skip_packages=%s
mkdir -p /opt/firefox-installable
selected_count=0
skipped_installed_count=0
skipped_optional_count=0
selected_missing_base_count=0
selected_regular_count=0
firefox_selected=0
select_deb() {
cp -v "$deb" /opt/firefox-installable/
selected_count=$((selected_count + 1))
}
package_name_from_deb() {
pkg=$(dpkg-deb -f "$1" Package 2>/dev/null || true)
if [ -n "$pkg" ]; then
echo "$pkg"
return 0
fi
file=$(basename "$1")
echo "$file" | cut -d '_' -f 1 | sed 's/[.]deb$//'
}
for deb in /opt/firefox-deb/*.deb; do
[ -e "$deb" ] || continue
file=$(basename "$deb")
pkg=$(package_name_from_deb "$deb")
if [ -z "$pkg" ]; then
echo "skip package with empty name: $file"
continue
fi
echo "package candidate: $file package=$pkg"
status=$(dpkg-query -W -f='${Status}' "$pkg" 2>/dev/null || true)
installed=0
if [ "$status" = "install ok installed" ]; then
installed=1
fi
if [ "$pkg" = "firefox" ]; then
select_deb
firefox_selected=1
continue
fi
case " $optional_skip_packages " in
*" $pkg "*)
echo "skip optional package: $file package=$pkg"
skipped_optional_count=$((skipped_optional_count + 1))
continue
;;
esac
case " $base_system_packages " in
*" $pkg "*)
if [ "$installed" -eq 1 ]; then
echo "skip installed base system package: $file"
skipped_installed_count=$((skipped_installed_count + 1))
continue
fi
echo "select missing base system package: $file"
select_deb
selected_missing_base_count=$((selected_missing_base_count + 1))
continue
;;
esac
if [ "$installed" -eq 1 ]; then
echo "skip installed package: $file"
skipped_installed_count=$((skipped_installed_count + 1))
continue
fi
select_deb
selected_regular_count=$((selected_regular_count + 1))
done
echo "selected packages: $selected_count, selected missing base: $selected_missing_base_count, selected regular: $selected_regular_count, skipped installed: $skipped_installed_count, skipped optional: $skipped_optional_count"
if [ "$selected_count" -eq 0 ]; then
echo "error: no packages selected for install"
exit 1
fi
if [ "$firefox_selected" -ne 1 ]; then
echo "error: Firefox deb package was not selected"
exit 1
fi
echo ""
echo "4. install selected Firefox packages..."
cd /opt/firefox-installable || { echo "error: enter installable package directory failed"; exit 1; }
install_status=0
sudo dpkg -i ./*.deb || install_status=$?
if [ "$install_status" -ne 0 ]; then
echo "warning: selected Firefox deb package install returned status $install_status, verify Firefox next"
fi
echo ""
echo "4. verify Firefox..."
echo "5. verify Firefox..."
firefox --version || { echo "error: Firefox verification failed"; exit 1; }
echo ""
echo "=== Firefox install complete ==="
`, shellQuote(bundlePath)))
`, shellQuote(bundlePath), shellQuote(baseSystemPackages), shellQuote(optionalSkipPackages)))
command.Stdout = os.Stdout
command.Stderr = os.Stderr
err := command.Run()
err = command.Run()
if err != nil {
kioskErrorf("firefox install command failed err=%v", err)
return err
@ -155,22 +279,38 @@ func firefoxBundlePath() string {
return filepath.Join(system.GetPathOfProgram(), g.Config().Firefox.DebPkgPath, g.Config().Firefox.DebPkgName)
}
func validateFirefoxBundle(bundlePath string) error {
func validateFirefoxBundle(bundlePath string) (firefoxBundleInstallPlan, error) {
packages, err := firefoxBundlePackageNames(bundlePath)
if err != nil {
return fmt.Errorf("read firefox bundle packages: %w", err)
return firefoxBundleInstallPlan{}, fmt.Errorf("read firefox bundle packages: %w", err)
}
if len(packages) == 0 {
return fmt.Errorf("firefox bundle has no deb packages")
return firefoxBundleInstallPlan{}, fmt.Errorf("firefox bundle has no deb packages")
}
blocked := unsafeFirefoxDebPackages(packages)
if len(blocked) > 0 {
return fmt.Errorf("firefox bundle contains blocked base system packages: %s", strings.Join(blocked, ", "))
plan := firefoxBundleInstallPlanFromPackages(packages)
if !plan.HasFirefoxPackage {
return firefoxBundleInstallPlan{}, fmt.Errorf("firefox bundle has no firefox deb package")
}
kioskInfof("firefox bundle validation passed bundle=%s packages=%d", bundlePath, len(packages))
return nil
if len(plan.BaseSystemPackages) > 0 {
kioskWarnf("firefox bundle contains base system packages; installed ones will be skipped and missing ones may be installed packages=%s", strings.Join(plan.BaseSystemPackages, ", "))
}
kioskInfof("firefox bundle validation passed bundle=%s totalPackages=%d baseSystemPackages=%d hasFirefox=%v", bundlePath, plan.TotalPackageCount, len(plan.BaseSystemPackages), plan.HasFirefoxPackage)
return plan, nil
}
func firefoxBundleInstallPlanFromPackages(packages []string) firefoxBundleInstallPlan {
plan := firefoxBundleInstallPlan{
TotalPackageCount: len(packages),
BaseSystemPackages: unsafeFirefoxDebPackages(packages),
}
for _, name := range packages {
if name == "firefox" {
plan.HasFirefoxPackage = true
}
}
return plan
}
func firefoxBundlePackageNames(bundlePath string) ([]string, error) {
@ -232,6 +372,15 @@ func unsafeFirefoxDebPackages(packages []string) []string {
return blocked
}
func optionalFirefoxDebPackageNames() []string {
names := make([]string, 0, len(optionalFirefoxDebPackages))
for name := range optionalFirefoxDebPackages {
names = append(names, name)
}
sort.Strings(names)
return names
}
func shellQuote(value string) string {
return "'" + strings.ReplaceAll(value, "'", "'\\''") + "'"
}

@ -35,6 +35,37 @@ func TestUnsafeFirefoxDebPackages(t *testing.T) {
}
}
func TestFirefoxBundleInstallPlanDetectsBasePackages(t *testing.T) {
packages := []string{
"firefox",
"libgtk-3-0",
"dpkg",
"coreutils",
"libc6",
"tar",
}
plan := firefoxBundleInstallPlanFromPackages(packages)
wantBlocked := []string{"coreutils", "dpkg", "libc6", "tar"}
if !plan.HasFirefoxPackage {
t.Fatal("plan should detect firefox package")
}
if plan.TotalPackageCount != len(packages) {
t.Fatalf("TotalPackageCount = %d, want %d", plan.TotalPackageCount, len(packages))
}
if !stringSlicesEqual(plan.BaseSystemPackages, wantBlocked) {
t.Fatalf("BaseSystemPackages = %v, want %v", plan.BaseSystemPackages, wantBlocked)
}
}
func TestOptionalFirefoxDebPackageNames(t *testing.T) {
got := optionalFirefoxDebPackageNames()
want := []string{"lubuntu-icon-theme"}
if !stringSlicesEqual(got, want) {
t.Fatalf("optionalFirefoxDebPackageNames() = %v, want %v", got, want)
}
}
func stringSlicesEqual(left []string, right []string) bool {
if len(left) != len(right) {
return false

@ -7,20 +7,33 @@ import (
)
func install() error {
kioskInfof("checking firefox usability")
kioskInfof("checking firefox installation")
var firefoxInstallErr error
if isFirefoxUsable() {
kioskInfof("firefox is usable")
firefoxInstalled := isFirefoxInstalled()
firefoxUsable := false
if firefoxInstalled {
firefoxUsable = isFirefoxUsable()
}
markerExists := hasFirefoxInstallMarker()
kioskInfof("firefox startup state installed=%v usable=%v markerExists=%v", firefoxInstalled, firefoxUsable, markerExists)
switch {
case firefoxInstalled && firefoxUsable:
kioskInfof("firefox installed and usable")
fmt.Println("Firefox is installed and usable")
if err := writeFirefoxInstallMarker("usable"); err != nil {
kioskWarnf("write firefox install marker failed err=%v", err)
}
} else if hasFirefoxInstallMarker() {
kioskWarnf("firefox unusable but install marker exists; skip automatic repair")
fmt.Println("Firefox unusable; skip automatic repair on startup")
} else {
kioskWarnf("firefox unusable and no install marker; starting first-time install")
fmt.Println("Firefox missing or unusable, installing...")
case firefoxInstalled:
kioskWarnf("firefox is installed but version check failed; skip automatic reinstall")
fmt.Println("Firefox installed but unusable; skip automatic reinstall on startup")
case markerExists:
kioskWarnf("firefox executable missing but install marker exists; skip automatic reinstall")
fmt.Println("Firefox missing; skip automatic reinstall on startup")
case shouldAutoInstallFirefox(firefoxInstalled, markerExists):
kioskWarnf("firefox executable missing and no install marker; starting first-time install")
fmt.Println("Firefox missing, installing...")
if err := installFirefox(); err != nil {
kioskErrorf("firefox first-time install failed err=%v", err)
fmt.Printf("install Firefox failed: %v\n", err)
@ -59,3 +72,7 @@ func install() error {
}
return nil
}
func shouldAutoInstallFirefox(installed bool, markerExists bool) bool {
return !installed && !markerExists
}

@ -0,0 +1,39 @@
package main
import "testing"
func TestShouldAutoInstallFirefox(t *testing.T) {
tests := []struct {
name string
installed bool
markerExists bool
want bool
}{
{
name: "missing without marker can install",
installed: false,
markerExists: false,
want: true,
},
{
name: "installed must not reinstall even when unusable",
installed: true,
markerExists: false,
want: false,
},
{
name: "missing with marker must not reinstall",
installed: false,
markerExists: true,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := shouldAutoInstallFirefox(tt.installed, tt.markerExists); got != tt.want {
t.Fatalf("shouldAutoInstallFirefox(%v, %v) = %v, want %v", tt.installed, tt.markerExists, got, tt.want)
}
})
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

@ -85,23 +85,27 @@ func start() {
}
}
kioskInfof("starting firefox client goroutine port=%d", g.Config().Server.Port)
go func(port int) {
time.Sleep(500 * time.Millisecond)
runClient(port)
}(g.Config().Server.Port)
go runStartupChecks()
if err := runStartupChecks(); err != nil {
kioskErrorf("skip firefox client startup because startup checks failed err=%v", err)
} else {
kioskInfof("starting firefox client goroutine port=%d", g.Config().Server.Port)
go func(port int) {
time.Sleep(500 * time.Millisecond)
runClient(port)
}(g.Config().Server.Port)
}
core.RunServer()
}
func runStartupChecks() {
func runStartupChecks() error {
startedAt := time.Now()
kioskInfof("startup checks begin")
if err := install(); err != nil {
kioskErrorf("startup checks failed elapsed=%s err=%v", time.Since(startedAt).Round(time.Millisecond), err)
return
return err
}
kioskInfof("startup checks done elapsed=%s", time.Since(startedAt).Round(time.Millisecond))
return nil
}
// TODO Build Cmd CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o m9zCtrlTty-arm64 .

@ -0,0 +1,138 @@
package m9zApi
import (
"sync"
"time"
"tgk-touch/internal/library/m9z"
)
const automaticModeUseDemoAPI = false
type automaticModeTaskReq struct {
CommUid string `json:"comm_uid"`
Mode uint8 `json:"mode"`
}
type automaticModeDemoStore struct {
mu sync.RWMutex
currentMode uint8
startTasks [3]m9z.ControlTask
stopTasks [3]m9z.ControlTask
middleTasks [3]m9z.MiddleControlTask
}
var automaticModeDemo = newAutomaticModeDemoStore()
func newAutomaticModeDemoStore() *automaticModeDemoStore {
store := &automaticModeDemoStore{}
for mode := uint8(0); mode < 3; mode++ {
store.startTasks[mode] = buildAutomaticModeDemoTask(6+int(mode), 30, true, uint8(60+mode*10))
store.stopTasks[mode] = buildAutomaticModeDemoTask(22, int(mode)*10, false, 0)
store.middleTasks[mode] = m9z.MiddleControlTask{
ControlTaskList: []m9z.ControlTask{
buildAutomaticModeDemoTask(12, int(mode)*10, true, uint8(45+mode*5)),
},
}
}
return store
}
func buildAutomaticModeDemoTask(hour int, minute int, turnOn bool, pwm uint8) m9z.ControlTask {
loops := make([]m9z.TaskLoop, 10)
for i := range loops {
loops[i] = m9z.TaskLoop{
Index: uint(i),
TurnOn: turnOn,
Pwm: pwm,
}
}
return m9z.ControlTask{
ExecTime: time.Date(2025, 6, 7, hour, minute, 0, 0, time.Local),
TimeType: 0,
Loops: loops,
}
}
func normalizeAutomaticMode(mode uint8) uint8 {
if mode > 2 {
return 0
}
return mode
}
func cloneAutomaticControlTask(task m9z.ControlTask) m9z.ControlTask {
task.Loops = append([]m9z.TaskLoop(nil), task.Loops...)
return task
}
func cloneAutomaticMiddleTask(task m9z.MiddleControlTask) m9z.MiddleControlTask {
task.ControlTaskList = append([]m9z.ControlTask(nil), task.ControlTaskList...)
for i := range task.ControlTaskList {
task.ControlTaskList[i] = cloneAutomaticControlTask(task.ControlTaskList[i])
}
return task
}
func (s *automaticModeDemoStore) getMode() uint8 {
s.mu.RLock()
defer s.mu.RUnlock()
return s.currentMode
}
func (s *automaticModeDemoStore) setMode(mode uint8) {
s.mu.Lock()
defer s.mu.Unlock()
s.currentMode = normalizeAutomaticMode(mode)
}
func (s *automaticModeDemoStore) getStartTask(mode uint8) m9z.ControlTask {
s.mu.RLock()
defer s.mu.RUnlock()
return cloneAutomaticControlTask(s.startTasks[normalizeAutomaticMode(mode)])
}
func (s *automaticModeDemoStore) getStartTaskProgram(mode uint8) (*m9z.TaskProgram, error) {
normalizedMode := normalizeAutomaticMode(mode)
return m9z.ControlTaskToTaskProgram(s.getStartTask(normalizedMode), normalizedMode)
}
func (s *automaticModeDemoStore) setStartTask(mode uint8, task m9z.ControlTask) {
s.mu.Lock()
defer s.mu.Unlock()
s.startTasks[normalizeAutomaticMode(mode)] = cloneAutomaticControlTask(task)
}
func (s *automaticModeDemoStore) getStopTask(mode uint8) m9z.ControlTask {
s.mu.RLock()
defer s.mu.RUnlock()
return cloneAutomaticControlTask(s.stopTasks[normalizeAutomaticMode(mode)])
}
func (s *automaticModeDemoStore) getStopTaskProgram(mode uint8) (*m9z.TaskProgram, error) {
normalizedMode := normalizeAutomaticMode(mode)
return m9z.ControlTaskToTaskProgram(s.getStopTask(normalizedMode), normalizedMode)
}
func (s *automaticModeDemoStore) setStopTask(mode uint8, task m9z.ControlTask) {
s.mu.Lock()
defer s.mu.Unlock()
s.stopTasks[normalizeAutomaticMode(mode)] = cloneAutomaticControlTask(task)
}
func (s *automaticModeDemoStore) getMiddleTask(mode uint8) m9z.MiddleControlTask {
s.mu.RLock()
defer s.mu.RUnlock()
return cloneAutomaticMiddleTask(s.middleTasks[normalizeAutomaticMode(mode)])
}
func (s *automaticModeDemoStore) getMiddleTaskProgram(mode uint8) (*m9z.TaskProgram, error) {
normalizedMode := normalizeAutomaticMode(mode)
return m9z.MiddleControlTaskToTaskProgram(s.getMiddleTask(normalizedMode), normalizedMode)
}
func (s *automaticModeDemoStore) setMiddleTask(mode uint8, task m9z.MiddleControlTask) {
s.mu.Lock()
defer s.mu.Unlock()
s.middleTasks[normalizeAutomaticMode(mode)] = cloneAutomaticMiddleTask(task)
}

@ -0,0 +1,71 @@
package m9zApi
import (
"testing"
"tgk-touch/internal/library/m9z"
)
func TestAutomaticModeDemoStoreReturnsCopies(t *testing.T) {
store := newAutomaticModeDemoStore()
start := store.getStartTask(0)
if len(start.Loops) == 0 {
t.Fatal("demo start task should include loops")
}
start.Loops[0].TurnOn = !start.Loops[0].TurnOn
start.Loops[0].Pwm = 1
got := store.getStartTask(0)
if got.Loops[0].Pwm == 1 {
t.Fatal("demo start task loop was mutated through returned value")
}
middle := store.getMiddleTask(0)
if len(middle.ControlTaskList) == 0 || len(middle.ControlTaskList[0].Loops) == 0 {
t.Fatal("demo middle task should include control tasks and loops")
}
middle.ControlTaskList[0].Loops[0].Pwm = 1
gotMiddle := store.getMiddleTask(0)
if gotMiddle.ControlTaskList[0].Loops[0].Pwm == 1 {
t.Fatal("demo middle task loop was mutated through returned value")
}
}
func TestNormalizeAutomaticMode(t *testing.T) {
if got := normalizeAutomaticMode(2); got != 2 {
t.Fatalf("normalizeAutomaticMode(2) = %d, want 2", got)
}
if got := normalizeAutomaticMode(3); got != 0 {
t.Fatalf("normalizeAutomaticMode(3) = %d, want 0", got)
}
}
func TestAutomaticModeDemoStoreBuildsTaskPrograms(t *testing.T) {
store := newAutomaticModeDemoStore()
programs := map[string]func(uint8) (*m9z.TaskProgram, error){
"start": store.getStartTaskProgram,
"middle": store.getMiddleTaskProgram,
"stop": store.getStopTaskProgram,
}
for name, build := range programs {
t.Run(name, func(t *testing.T) {
program, err := build(1)
if err != nil {
t.Fatalf("build %s demo task program failed: %v", name, err)
}
if program == nil {
t.Fatalf("%s demo task program is nil", name)
}
if program.Index != 1 {
t.Fatalf("%s demo task program index = %d, want 1", name, program.Index)
}
if len(program.Instructions) == 0 {
t.Fatalf("%s demo task program should include instructions", name)
}
})
}
}

@ -39,6 +39,18 @@ func getOutStatus(rpc towgo.JsonRpcConnection) {
// todo 有问题 不管写入什么模式返回都是FF 获取设备编程模式 1-3 对应模式01~模式03 编程模式0~2
func getDeviceMode(rpc towgo.JsonRpcConnection) {
if automaticModeUseDemoAPI {
getDeviceModeDemo(rpc)
return
}
getDeviceModeReal(rpc)
}
func getDeviceModeDemo(rpc towgo.JsonRpcConnection) {
rpc.WriteResult(automaticModeDemo.getMode())
}
func getDeviceModeReal(rpc towgo.JsonRpcConnection) {
cuid, _ := getDeviceId(rpc)
read, err := m9z.ModeRead(cuid)
@ -135,8 +147,9 @@ type DeviceStatusLoop struct {
}
type DeviceStatus struct {
m9z.DeviceStatus
ScanDeviceCount int `json:"scan_device_count"`
Loops []DeviceStatusLoop `json:"Loops"`
ScanDeviceCount int `json:"scan_device_count"`
AlarmDisplayEnabled bool `json:"alarm_display_enabled"`
Loops []DeviceStatusLoop `json:"Loops"`
}
// 实时监控数据
@ -157,7 +170,9 @@ func getDeviceStatus2Real(rpc towgo.JsonRpcConnection) {
d.DeviceStatus = *read
scanLoopCount := configuredLoopCount()
displayLoopCount := monitorDisplayLoopCount()
alarmDisplayEnabled := scanConfigManager.GetAlarmDisplayEnabled()
d.ScanDeviceCount = scanLoopCount
d.AlarmDisplayEnabled = alarmDisplayEnabled
loops := make([]DeviceStatusLoop, displayLoopCount)
var wg sync.WaitGroup
for i := 0; i < displayLoopCount; i++ {
@ -176,7 +191,7 @@ func getDeviceStatus2Real(rpc towgo.JsonRpcConnection) {
loop.A = parametersRead.DCCurrent
kw := float64(loop.V*loop.A) / 1000
loop.Kw = float32(math.Round(kw*1000) / 1000)
if idx < scanLoopCount {
if alarmDisplayEnabled && idx < scanLoopCount {
loop.Faults = m9z.BuildLoopFaults(uint(idx+1), parametersRead.AlarmStatus, parametersRead.AlarmCode)
loop.HasFault = len(loop.Faults) > 0
loop.FaultMsg = joinLoopFaultMessages(loop.Faults)
@ -194,7 +209,7 @@ func getDeviceStatus2Real(rpc towgo.JsonRpcConnection) {
return d.Loops[i].Index < d.Loops[j].Index
})
for _, loop := range d.Loops {
if loop.HasFault && loop.Index < scanLoopCount {
if alarmDisplayEnabled && loop.HasFault && loop.Index < scanLoopCount {
recordLoopFaults(cuid, uint(loop.Index+1), loop.V, loop.Faults)
}
}
@ -205,9 +220,11 @@ func getDeviceStatus2Real(rpc towgo.JsonRpcConnection) {
func buildDemoDeviceStatus2() *DeviceStatus {
displayLoopCount := monitorDisplayLoopCount()
scanLoopCount := configuredLoopCount()
alarmDisplayEnabled := scanConfigManager.GetAlarmDisplayEnabled()
d := &DeviceStatus{
ScanDeviceCount: scanLoopCount,
Loops: make([]DeviceStatusLoop, displayLoopCount),
ScanDeviceCount: scanLoopCount,
AlarmDisplayEnabled: alarmDisplayEnabled,
Loops: make([]DeviceStatusLoop, displayLoopCount),
}
d.Mode = 0
d.DeviceTime = time.Now().UTC()
@ -230,7 +247,7 @@ func buildDemoDeviceStatus2() *DeviceStatus {
kw := float64(loop.V*loop.A) / 1000
loop.Kw = float32(math.Round(kw*1000) / 1000)
if i < scanLoopCount {
if alarmDisplayEnabled && i < scanLoopCount {
switch i {
case 0:
loop.Faults = m9z.BuildLoopFaults(uint(i+1), 0x01, "01 00")
@ -299,6 +316,24 @@ func getDeviceSunRiseTime(rpc towgo.JsonRpcConnection) {
// 读取开始任务 (优化指令 CMD=0x13)
func getStartTask(rpc towgo.JsonRpcConnection) {
if automaticModeUseDemoAPI {
getStartTaskDemo(rpc)
return
}
getStartTaskReal(rpc)
}
func getStartTaskDemo(rpc towgo.JsonRpcConnection) {
var p automaticModeTaskReq
rpc.ReadParams(&p)
program, err := automaticModeDemo.getStartTaskProgram(p.Mode)
if err != nil {
panic(err)
}
rpc.WriteResult(program)
}
func getStartTaskReal(rpc towgo.JsonRpcConnection) {
var p struct {
CommUid string `json:"comm_uid"`
Mode uint8 `json:"mode"` // 0x00-0x02
@ -326,6 +361,24 @@ func getStartTask(rpc towgo.JsonRpcConnection) {
// 读取中间任务 (优化指令 CMD=0x14)
func getMiddleTaskRead(rpc towgo.JsonRpcConnection) {
if automaticModeUseDemoAPI {
getMiddleTaskReadDemo(rpc)
return
}
getMiddleTaskReadReal(rpc)
}
func getMiddleTaskReadDemo(rpc towgo.JsonRpcConnection) {
var p automaticModeTaskReq
rpc.ReadParams(&p)
program, err := automaticModeDemo.getMiddleTaskProgram(p.Mode)
if err != nil {
panic(err)
}
rpc.WriteResult(program)
}
func getMiddleTaskReadReal(rpc towgo.JsonRpcConnection) {
var p struct {
CommUid string `json:"comm_uid"`
Mode uint8 `json:"mode"` // 0x00-0x02
@ -344,6 +397,24 @@ func getMiddleTaskRead(rpc towgo.JsonRpcConnection) {
// 读取结束任务 (优化指令 CMD=0x15)
func getStopTaskRead(rpc towgo.JsonRpcConnection) {
if automaticModeUseDemoAPI {
getStopTaskReadDemo(rpc)
return
}
getStopTaskReadReal(rpc)
}
func getStopTaskReadDemo(rpc towgo.JsonRpcConnection) {
var p automaticModeTaskReq
rpc.ReadParams(&p)
program, err := automaticModeDemo.getStopTaskProgram(p.Mode)
if err != nil {
panic(err)
}
rpc.WriteResult(program)
}
func getStopTaskReadReal(rpc towgo.JsonRpcConnection) {
var p struct {
CommUid string `json:"comm_uid"`
Mode uint8 `json:"mode"` // 0x00-0x02
@ -361,6 +432,20 @@ func getStopTaskRead(rpc towgo.JsonRpcConnection) {
rpc.WriteResult(read)
}
func getStartTask2(rpc towgo.JsonRpcConnection) {
if automaticModeUseDemoAPI {
getStartTask2Demo(rpc)
return
}
getStartTask2Real(rpc)
}
func getStartTask2Demo(rpc towgo.JsonRpcConnection) {
var p automaticModeTaskReq
rpc.ReadParams(&p)
rpc.WriteResult(automaticModeDemo.getStartTask(p.Mode))
}
func getStartTask2Real(rpc towgo.JsonRpcConnection) {
var p struct {
CommUid string `json:"comm_uid"`
Mode uint8 `json:"mode"` // 0x00-0x02
@ -390,6 +475,20 @@ func getStartTask2(rpc towgo.JsonRpcConnection) {
}
func getStopTask2(rpc towgo.JsonRpcConnection) {
if automaticModeUseDemoAPI {
getStopTask2Demo(rpc)
return
}
getStopTask2Real(rpc)
}
func getStopTask2Demo(rpc towgo.JsonRpcConnection) {
var p automaticModeTaskReq
rpc.ReadParams(&p)
rpc.WriteResult(automaticModeDemo.getStopTask(p.Mode))
}
func getStopTask2Real(rpc towgo.JsonRpcConnection) {
var p struct {
CommUid string `json:"comm_uid"`
Mode uint8 `json:"mode"` // 0x00-0x02
@ -419,6 +518,20 @@ func getStopTask2(rpc towgo.JsonRpcConnection) {
}
func getMiddleTask2(rpc towgo.JsonRpcConnection) {
if automaticModeUseDemoAPI {
getMiddleTask2Demo(rpc)
return
}
getMiddleTask2Real(rpc)
}
func getMiddleTask2Demo(rpc towgo.JsonRpcConnection) {
var p automaticModeTaskReq
rpc.ReadParams(&p)
rpc.WriteResult(automaticModeDemo.getMiddleTask(p.Mode))
}
func getMiddleTask2Real(rpc towgo.JsonRpcConnection) {
var p struct {
CommUid string `json:"comm_uid"`

@ -12,16 +12,20 @@ import (
"github.com/towgo/towgo/towgo"
)
const defaultAlarmDisplayEnabled = true
type ScanConfigManager struct {
mu sync.RWMutex
loopCount int
mu sync.RWMutex
loopCount int
alarmDisplayEnabled bool
}
type ScanConfigData struct {
LoopCount int `json:"loop_count"`
LoopCount int `json:"loop_count"`
AlarmDisplayEnabled *bool `json:"alarm_display_enabled,omitempty"`
}
var scanConfigManager = &ScanConfigManager{}
var scanConfigManager = &ScanConfigManager{alarmDisplayEnabled: defaultAlarmDisplayEnabled}
func getScanConfigFilePath() string {
return filepath.Join(system.GetPathOfProgram(), "data", "scanConfig.json")
@ -33,15 +37,38 @@ func (m *ScanConfigManager) GetLoopCount() int {
return normalizeLoopCount(m.loopCount)
}
func (m *ScanConfigManager) GetAlarmDisplayEnabled() bool {
m.mu.RLock()
defer m.mu.RUnlock()
return m.alarmDisplayEnabled
}
func (m *ScanConfigManager) SetLoopCount(loopCount int) error {
m.mu.Lock()
defer m.mu.Unlock()
m.loopCount = loopCount
m.loopCount = normalizeLoopCount(loopCount)
if err := m.saveToFile(); err != nil {
return err
}
g.GVA_Config.DeviceInfo.LoopCount = m.loopCount
return nil
}
func (m *ScanConfigManager) SetConfig(loopCount int, alarmDisplayEnabled *bool) error {
m.mu.Lock()
defer m.mu.Unlock()
if loopCount > 0 {
m.loopCount = normalizeLoopCount(loopCount)
g.GVA_Config.DeviceInfo.LoopCount = m.loopCount
}
if alarmDisplayEnabled != nil {
m.alarmDisplayEnabled = *alarmDisplayEnabled
}
if err := m.saveToFile(); err != nil {
return err
}
g.GVA_Config.DeviceInfo.LoopCount = loopCount
return nil
}
@ -54,6 +81,7 @@ func (m *ScanConfigManager) loadFromFile() error {
if err != nil {
if os.IsNotExist(err) {
m.loopCount = normalizeLoopCount(g.Config().DeviceInfo.LoopCount)
m.alarmDisplayEnabled = defaultAlarmDisplayEnabled
return m.saveToFile()
}
return err
@ -64,6 +92,11 @@ func (m *ScanConfigManager) loadFromFile() error {
return err
}
m.loopCount = normalizeLoopCount(data.LoopCount)
if data.AlarmDisplayEnabled == nil {
m.alarmDisplayEnabled = defaultAlarmDisplayEnabled
} else {
m.alarmDisplayEnabled = *data.AlarmDisplayEnabled
}
g.GVA_Config.DeviceInfo.LoopCount = m.loopCount
return nil
}
@ -76,7 +109,8 @@ func (m *ScanConfigManager) saveToFile() error {
}
jsonData, err := json.Marshal(ScanConfigData{
LoopCount: normalizeLoopCount(m.loopCount),
LoopCount: normalizeLoopCount(m.loopCount),
AlarmDisplayEnabled: boolPtr(m.alarmDisplayEnabled),
})
if err != nil {
return err
@ -86,32 +120,43 @@ func (m *ScanConfigManager) saveToFile() error {
func getScanConfig(rpcConn towgo.JsonRpcConnection) {
rpcConn.WriteResult(map[string]interface{}{
"loop_count": scanConfigManager.GetLoopCount(),
"max_loop_count": maxLoopCount,
"min_loop_count": 1,
"loop_count": scanConfigManager.GetLoopCount(),
"alarm_display_enabled": scanConfigManager.GetAlarmDisplayEnabled(),
"max_loop_count": maxLoopCount,
"min_loop_count": 1,
})
}
type SetScanConfigReq struct {
LoopCount int `json:"loop_count"`
LoopCount int `json:"loop_count"`
AlarmDisplayEnabled *bool `json:"alarm_display_enabled"`
}
func setScanConfig(rpcConn towgo.JsonRpcConnection) {
var req SetScanConfigReq
rpcConn.ReadParams(&req)
if req.LoopCount < 1 || req.LoopCount > maxLoopCount {
if req.LoopCount != 0 && (req.LoopCount < 1 || req.LoopCount > maxLoopCount) {
rpcConn.WriteError(400, "loop_count must be between 1 and 10")
return
}
if err := scanConfigManager.SetLoopCount(req.LoopCount); err != nil {
if req.LoopCount == 0 && req.AlarmDisplayEnabled == nil {
rpcConn.WriteError(400, "loop_count or alarm_display_enabled is required")
return
}
if err := scanConfigManager.SetConfig(req.LoopCount, req.AlarmDisplayEnabled); err != nil {
rpcConn.WriteError(500, "save scan config failed: "+err.Error())
return
}
rpcConn.WriteResult(map[string]interface{}{
"success": true,
"loop_count": scanConfigManager.GetLoopCount(),
"max_loop_count": maxLoopCount,
"min_loop_count": 1,
"success": true,
"loop_count": scanConfigManager.GetLoopCount(),
"alarm_display_enabled": scanConfigManager.GetAlarmDisplayEnabled(),
"max_loop_count": maxLoopCount,
"min_loop_count": 1,
})
}
func boolPtr(value bool) *bool {
return &value
}

@ -32,11 +32,14 @@ func TestMonitorDisplayLoopCountAlwaysUsesMaxLoopCount(t *testing.T) {
func TestBuildDemoDeviceStatus2KeepsDisplayLoopsIndependentFromScanCount(t *testing.T) {
scanConfigManager.mu.Lock()
oldLoopCount := scanConfigManager.loopCount
oldAlarmDisplayEnabled := scanConfigManager.alarmDisplayEnabled
scanConfigManager.loopCount = 6
scanConfigManager.alarmDisplayEnabled = true
scanConfigManager.mu.Unlock()
t.Cleanup(func() {
scanConfigManager.mu.Lock()
scanConfigManager.loopCount = oldLoopCount
scanConfigManager.alarmDisplayEnabled = oldAlarmDisplayEnabled
scanConfigManager.mu.Unlock()
})
@ -53,4 +56,29 @@ func TestBuildDemoDeviceStatus2KeepsDisplayLoopsIndependentFromScanCount(t *test
if status.Loops[6].HasFault {
t.Fatal("status.Loops[6].HasFault = true, want false outside the scan loop count")
}
if !status.AlarmDisplayEnabled {
t.Fatal("status.AlarmDisplayEnabled = false, want true")
}
}
func TestBuildDemoDeviceStatus2IncludesAlarmDisplayConfig(t *testing.T) {
scanConfigManager.mu.Lock()
oldAlarmDisplayEnabled := scanConfigManager.alarmDisplayEnabled
scanConfigManager.alarmDisplayEnabled = false
scanConfigManager.mu.Unlock()
t.Cleanup(func() {
scanConfigManager.mu.Lock()
scanConfigManager.alarmDisplayEnabled = oldAlarmDisplayEnabled
scanConfigManager.mu.Unlock()
})
status := buildDemoDeviceStatus2()
if status.AlarmDisplayEnabled {
t.Fatal("status.AlarmDisplayEnabled = true, want false")
}
for _, loop := range status.Loops {
if loop.HasFault || loop.FaultMsg != "" || len(loop.Faults) > 0 {
t.Fatalf("loop %d still has fault data when alarm display disabled", loop.Index)
}
}
}

@ -15,6 +15,21 @@ import (
// 写入设备编程模式
func setDeviceMode(rpc towgo.JsonRpcConnection) {
if automaticModeUseDemoAPI {
setDeviceModeDemo(rpc)
return
}
setDeviceModeReal(rpc)
}
func setDeviceModeDemo(rpc towgo.JsonRpcConnection) {
var p automaticModeTaskReq
rpc.ReadParams(&p)
automaticModeDemo.setMode(p.Mode)
rpc.WriteResult("ok")
}
func setDeviceModeReal(rpc towgo.JsonRpcConnection) {
var p struct {
CommUid string `json:"comm_uid"`
Mode uint8 `json:"mode"`
@ -292,6 +307,30 @@ func setStopTaskWrite(rpc towgo.JsonRpcConnection) {
// 写入开始任务 (优化指令 CMD=0x13经纬度模式使用日落时间)
func setStartTask2(rpc towgo.JsonRpcConnection) {
if automaticModeUseDemoAPI {
setStartTask2Demo(rpc)
return
}
setStartTask2Real(rpc)
}
func setStartTask2Demo(rpc towgo.JsonRpcConnection) {
var p struct {
ExecTime string `json:"execTime"`
Mode uint8 `json:"mode"`
m9z.ControlTask
}
rpc.ReadParams(&p)
parse, err := utility.ParseTime(p.ExecTime)
if err != nil {
panic(err)
}
p.ControlTask.ExecTime = parse
automaticModeDemo.setStartTask(p.Mode, p.ControlTask)
rpc.WriteResult("ok")
}
func setStartTask2Real(rpc towgo.JsonRpcConnection) {
var p struct {
ExecTime string `json:"execTime"`
CommUid string `json:"comm_uid"`
@ -331,6 +370,30 @@ func setStartTask2(rpc towgo.JsonRpcConnection) {
// 写入结束任务 (优化指令 CMD=0x15经纬度模式使用日出时间)
func setStopTask2(rpc towgo.JsonRpcConnection) {
if automaticModeUseDemoAPI {
setStopTask2Demo(rpc)
return
}
setStopTask2Real(rpc)
}
func setStopTask2Demo(rpc towgo.JsonRpcConnection) {
var p struct {
ExecTime string `json:"execTime"`
Mode uint8 `json:"mode"`
m9z.ControlTask
}
rpc.ReadParams(&p)
parse, err := utility.ParseTime(p.ExecTime)
if err != nil {
panic(err)
}
p.ControlTask.ExecTime = parse
automaticModeDemo.setStopTask(p.Mode, p.ControlTask)
rpc.WriteResult("ok")
}
func setStopTask2Real(rpc towgo.JsonRpcConnection) {
var p struct {
ExecTime string `json:"execTime"`
CommUid string `json:"comm_uid"`
@ -371,6 +434,37 @@ func setStopTask2(rpc towgo.JsonRpcConnection) {
}
func setMiddleTask2(rpc towgo.JsonRpcConnection) {
if automaticModeUseDemoAPI {
setMiddleTask2Demo(rpc)
return
}
setMiddleTask2Real(rpc)
}
func setMiddleTask2Demo(rpc towgo.JsonRpcConnection) {
var p struct {
Mode uint8 `json:"mode"`
ControlTaskList []struct {
m9z.ControlTask
ExecTime string `json:"execTime"`
} `json:"controlTaskList"`
}
rpc.ReadParams(&p)
middleTask := m9z.MiddleControlTask{}
middleTask.ControlTaskList = make([]m9z.ControlTask, len(p.ControlTaskList))
for i, controlTask := range p.ControlTaskList {
parse, err := utility.ParseTime(controlTask.ExecTime)
if err != nil {
panic(gerror.NewCodef(gcode.CodeInvalidParameter, " %s parse control task time error, %s", controlTask.ExecTime, err))
}
controlTask.ControlTask.ExecTime = parse
middleTask.ControlTaskList[i] = controlTask.ControlTask
}
automaticModeDemo.setMiddleTask(p.Mode, middleTask)
rpc.WriteResult("ok")
}
func setMiddleTask2Real(rpc towgo.JsonRpcConnection) {
var p struct {
CommUid string `json:"comm_uid"`
Mode uint8 `json:"mode"`

@ -1,6 +1,61 @@
/Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/log1.png
/Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/log2.png
看一下这两个图片,现在那个我们的软件安到那个弯图的触摸屏上
第一次就是可以正常启动。然后我只要把那个触摸屏一重启它就会卡在系统的logo上面进不去了。然后这两个图片是开发人员截的日志。
他说有下载联网,下载什么东西?我记得我们安装呃火狐浏览器的都是离线安装包,没有这个联网下载的东西。然后我也看不懂这个日志啥的,你帮我分析一下,很着急。
ebDriver BiDi listening on ws://127.0.0.1:18089
2026-08-10 23:27:17.020 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox remote debug request url=http://127.0.0.1:18089/json/list
2026-08-10 23:27:17.675 WARN /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:16 [touch-kiosk] firefox remote debug bad status url=http://127.0.0.1:18089/json/list status=404
2026-08-10 23:27:17.676 WARN /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:16 [touch-kiosk] firefox kiosk url check failed hasKioskURL=false err=firefox remote debugging returned status 404
2026-08-10 23:27:17.677 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox health check pending during startup grace elapsed=6s err=firefox remote debugging returned status 404
2026-08-10 23:27:22.023 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox remote debug request url=http://127.0.0.1:18089/json/list
2026-08-10 23:27:22.057 WARN /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:16 [touch-kiosk] firefox remote debug bad status url=http://127.0.0.1:18089/json/list status=404
2026-08-10 23:27:22.058 WARN /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:16 [touch-kiosk] firefox kiosk url check failed hasKioskURL=false err=firefox remote debugging returned status 404
2026-08-10 23:27:22.059 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox health check pending during startup grace elapsed=10s err=firefox remote debugging returned status 404
[ERROR glean_core] Error setting metrics feature config: Json(Error("EOF while parsing a value", line: 1, column: 0))
DevTools listening on ws://127.0.0.1:18089/devtools/browser/21bb3f75-cb31-419e-be27-cb315e2546ea
2026-08-10 23:27:27.020 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox remote debug request url=http://127.0.0.1:18089/json/list
2026-08-10 23:27:27.047 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox remote debug targets=1 expectedURL=http://127.0.0.1:8089
2026-08-10 23:27:27.047 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox target type=page url=http://127.0.0.1:8089/menu
2026-08-10 23:27:27.048 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox kiosk url matched targetURL=http://127.0.0.1:8089/menu
2026-08-10 23:27:27.057 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] command availability name=xdotool ok=false
2026-08-10 23:27:27.057 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] fullscreen check skipped because xdotool/xprop unavailable
2026-08-10 23:27:27.057 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox health check ok elapsed=15s
2026-08-10 23:27:32.020 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox remote debug request url=http://127.0.0.1:18089/json/list
2026-08-10 23:27:32.070 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox remote debug targets=1 expectedURL=http://127.0.0.1:8089
2026-08-10 23:27:32.074 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox target type=page url=http://127.0.0.1:8089/menu
2026-08-10 23:27:32.074 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox kiosk url matched targetURL=http://127.0.0.1:8089/menu
2026-08-10 23:27:32.081 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] command availability name=xdotool ok=false
2026-08-10 23:27:32.082 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] fullscreen check skipped because xdotool/xprop unavailable
2026-08-10 23:27:32.082 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox health check ok elapsed=20s
ALSA lib seq_hw.c:466:(snd_seq_hw_open) open /dev/snd/seq failed: No such file or directory
2026-08-10 23:27:37.020 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox remote debug request url=http://127.0.0.1:18089/json/list
2026-08-10 23:27:37.061 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox remote debug targets=1 expectedURL=http://127.0.0.1:8089
2026-08-10 23:27:37.062 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox target type=page url=http://127.0.0.1:8089/menu
2026-08-10 23:27:37.062 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox kiosk url matched targetURL=http://127.0.0.1:8089/menu
2026-08-10 23:27:37.076 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] command availability name=xdotool ok=false
2026-08-10 23:27:37.076 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] fullscreen check skipped because xdotool/xprop unavailable
2026-08-10 23:27:37.077 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox health check ok elapsed=25s
2026-08-10 23:27:42.020 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox remote debug request url=http://127.0.0.1:18089/json/list
2026-08-10 23:27:42.052 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox remote debug targets=1 expectedURL=http://127.0.0.1:8089
2026-08-10 23:27:42.065 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox target type=page url=http://127.0.0.1:8089/menu
2026-08-10 23:27:42.065 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox kiosk url matched targetURL=http://127.0.0.1:8089/menu
2026-08-10 23:27:42.074 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] command availability name=xdotool ok=false
2026-08-10 23:27:42.074 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] fullscreen check skipped because xdotool/xprop unavailable
2026-08-10 23:27:42.074 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox health check ok elapsed=30s
2026-08-10 23:27:47.021 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox remote debug request url=http://127.0.0.1:18089/json/list
2026-08-10 23:27:47.048 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox remote debug targets=1 expectedURL=http://127.0.0.1:8089
2026-08-10 23:27:47.052 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox target type=page url=http://127.0.0.1:8089/menu
2026-08-10 23:27:47.053 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox kiosk url matched targetURL=http://127.0.0.1:8089/menu
2026-08-10 23:27:47.062 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] command availability name=xdotool ok=false
2026-08-10 23:27:47.063 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] fullscreen check skipped because xdotool/xprop unavailable
2026-08-10 23:27:47.063 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox health check ok elapsed=35s
2026-08-10 23:27:52.020 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox remote debug request url=http://127.0.0.1:18089/json/list
2026-08-10 23:27:52.047 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox remote debug targets=1 expectedURL=http://127.0.0.1:8089
2026-08-10 23:27:52.049 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox target type=page url=http://127.0.0.1:8089/menu
2026-08-10 23:27:52.049 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox kiosk url matched targetURL=http://127.0.0.1:8089/menu
2026-08-10 23:27:52.061 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] command availability name=xdotool ok=false
2026-08-10 23:27:52.062 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] fullscreen check skipped because xdotool/xprop unavailable
2026-08-10 23:27:52.062 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox health check ok elapsed=40s
2026-08-10 23:27:57.020 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox remote debug request url=http://127.0.0.1:18089/json/list
2026-08-10 23:27:57.057 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox remote debug targets=1 expectedURL=http://127.0.0.1:8089
2026-08-10 23:27:57.059 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox target type=page url=http://127.0.0.1:8089/menu
2026-08-10 23:27:57.059 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox kiosk url matched targetURL=http://127.0.0.1:8089/menu
2026-08-10 23:27:57.071 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] command availability name=xdotool ok=false
2026-08-10 23:27:57.072 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] fullscreen check skipped because xdotool/xprop unavailable
2026-08-10 23:27:57.072 DEBUG /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:12 [touch-kiosk] firefox health check ok elapsed=45s

Loading…
Cancel
Save