Compare commits

..

10 Commits

@ -46,6 +46,10 @@ 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
@ -94,6 +98,8 @@ func installFirefox() error {
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"`
@ -129,9 +135,11 @@ echo ""
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
@ -139,11 +147,24 @@ 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=${file%%_*}
pkg=${pkg%%.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
@ -157,6 +178,14 @@ for deb in /opt/firefox-deb/*.deb; do
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
@ -180,7 +209,7 @@ for deb in /opt/firefox-deb/*.deb; do
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"
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
@ -192,7 +221,12 @@ fi
echo ""
echo "4. install selected Firefox packages..."
cd /opt/firefox-installable && sudo dpkg -i ./*.deb || { echo "error: install selected Firefox deb packages failed"; exit 1; }
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 "5. verify Firefox..."
@ -200,7 +234,7 @@ firefox --version || { echo "error: Firefox verification failed"; exit 1; }
echo ""
echo "=== Firefox install complete ==="
`, shellQuote(bundlePath), shellQuote(baseSystemPackages)))
`, shellQuote(bundlePath), shellQuote(baseSystemPackages), shellQuote(optionalSkipPackages)))
command.Stdout = os.Stdout
command.Stderr = os.Stderr
@ -338,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, "'", "'\\''") + "'"
}

@ -58,6 +58,14 @@ func TestFirefoxBundleInstallPlanDetectsBasePackages(t *testing.T) {
}
}
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

@ -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)
@ -136,6 +148,7 @@ type DeviceStatusLoop struct {
type DeviceStatus struct {
m9z.DeviceStatus
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,8 +220,10 @@ func getDeviceStatus2Real(rpc towgo.JsonRpcConnection) {
func buildDemoDeviceStatus2() *DeviceStatus {
displayLoopCount := monitorDisplayLoopCount()
scanLoopCount := configuredLoopCount()
alarmDisplayEnabled := scanConfigManager.GetAlarmDisplayEnabled()
d := &DeviceStatus{
ScanDeviceCount: scanLoopCount,
AlarmDisplayEnabled: alarmDisplayEnabled,
Loops: make([]DeviceStatusLoop, displayLoopCount),
}
d.Mode = 0
@ -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
alarmDisplayEnabled bool
}
type ScanConfigData struct {
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
}
@ -77,6 +110,7 @@ func (m *ScanConfigManager) saveToFile() error {
jsonData, err := json.Marshal(ScanConfigData{
LoopCount: normalizeLoopCount(m.loopCount),
AlarmDisplayEnabled: boolPtr(m.alarmDisplayEnabled),
})
if err != nil {
return err
@ -87,6 +121,7 @@ func (m *ScanConfigManager) saveToFile() error {
func getScanConfig(rpcConn towgo.JsonRpcConnection) {
rpcConn.WriteResult(map[string]interface{}{
"loop_count": scanConfigManager.GetLoopCount(),
"alarm_display_enabled": scanConfigManager.GetAlarmDisplayEnabled(),
"max_loop_count": maxLoopCount,
"min_loop_count": 1,
})
@ -94,24 +129,34 @@ func getScanConfig(rpcConn towgo.JsonRpcConnection) {
type SetScanConfigReq struct {
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(),
"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=0x15)
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"`
@ -322,6 +361,30 @@ func setStartTask2(rpc towgo.JsonRpcConnection) {
rpc.WriteResult("ok")
}
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"`
@ -355,6 +418,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"`

@ -0,0 +1,421 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>金泰访客查询</title>
<style>
:root {
color-scheme: light;
--bg: #f5f7fb;
--panel: #ffffff;
--text: #1f2937;
--muted: #6b7280;
--line: #d9e2ef;
--primary: #0877d8;
--primary-dark: #075da9;
--danger: #b91c1c;
--ok: #047857;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", sans-serif;
color: var(--text);
background:
linear-gradient(180deg, rgba(8, 119, 216, 0.12), rgba(8, 119, 216, 0) 280px),
var(--bg);
}
.shell {
width: min(560px, calc(100% - 32px));
margin: 0 auto;
padding: 28px 0 40px;
}
.brand {
margin-bottom: 18px;
}
h1 {
margin: 0 0 6px;
font-size: 26px;
line-height: 1.25;
letter-spacing: 0;
}
.sub {
margin: 0;
color: var(--muted);
font-size: 14px;
line-height: 1.6;
}
.panel {
background: var(--panel);
border: 1px solid var(--line);
border-radius: 8px;
box-shadow: 0 16px 45px rgba(31, 41, 55, 0.08);
overflow: hidden;
}
.row {
padding: 16px;
border-bottom: 1px solid var(--line);
}
.row:last-child {
border-bottom: 0;
}
label {
display: block;
margin-bottom: 8px;
font-size: 14px;
color: var(--muted);
}
input {
display: block;
width: 100%;
height: 44px;
border: 1px solid var(--line);
border-radius: 6px;
padding: 0 12px;
color: var(--text);
font-size: 16px;
outline: none;
background: #fff;
}
input:focus {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(8, 119, 216, 0.14);
}
.captcha-line {
display: grid;
grid-template-columns: 1fr 132px;
gap: 10px;
align-items: center;
}
.captcha-box {
height: 44px;
border: 1px solid var(--line);
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
background: #fbfdff;
overflow: hidden;
cursor: pointer;
}
.captcha-box img {
max-width: 100%;
max-height: 42px;
display: block;
}
.actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
button {
height: 44px;
border: 0;
border-radius: 6px;
padding: 0 14px;
font-size: 15px;
color: #fff;
background: var(--primary);
cursor: pointer;
}
button:hover {
background: var(--primary-dark);
}
button.secondary {
color: var(--primary);
background: #eaf4ff;
}
button.secondary:hover {
background: #d8ebff;
}
button:disabled {
opacity: 0.65;
cursor: not-allowed;
}
.status {
min-height: 22px;
font-size: 14px;
line-height: 1.6;
color: var(--muted);
}
.status.ok {
color: var(--ok);
}
.status.err {
color: var(--danger);
}
pre {
margin: 0;
min-height: 120px;
max-height: 360px;
overflow: auto;
white-space: pre-wrap;
word-break: break-word;
font-size: 13px;
line-height: 1.55;
color: #111827;
background: #f8fafc;
border: 1px solid var(--line);
border-radius: 6px;
padding: 12px;
}
@media (max-width: 460px) {
.captcha-line,
.actions {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<main class="shell">
<header class="brand">
<h1>金泰访客查询</h1>
<p class="sub">手动验证码单次查询。把页面源码里的 ID_CARD 改成你自己的身份证号。</p>
</header>
<section class="panel">
<div class="row">
<label for="idCard">身份证号</label>
<input id="idCard" autocomplete="off">
</div>
<div class="row">
<label for="visitDate">来访日期</label>
<input id="visitDate" placeholder="YYYY-MM-DD" autocomplete="off">
</div>
<div class="row">
<label for="captcha">验证码</label>
<div class="captcha-line">
<input id="captcha" placeholder="请输入右侧验证码" autocomplete="off">
<button class="captcha-box" id="captchaBox" type="button" title="点击刷新验证码">
<img id="captchaImg" alt="验证码">
</button>
</div>
</div>
<div class="row">
<div class="actions">
<button class="secondary" id="refreshBtn" type="button">刷新验证码</button>
<button id="queryBtn" type="button">查询</button>
</div>
</div>
<div class="row">
<div class="status" id="status">页面已就绪。</div>
</div>
<div class="row">
<pre id="result">等待查询结果...</pre>
</div>
</section>
</main>
<script>
const BASE_URL = "https://www.watvt.com";
// 在这里写死你自己的身份证号。
const ID_CARD = "请改成你的身份证号";
const idCardEl = document.getElementById("idCard");
const visitDateEl = document.getElementById("visitDate");
const captchaEl = document.getElementById("captcha");
const captchaImgEl = document.getElementById("captchaImg");
const statusEl = document.getElementById("status");
const resultEl = document.getElementById("result");
const refreshBtn = document.getElementById("refreshBtn");
const queryBtn = document.getElementById("queryBtn");
const captchaBox = document.getElementById("captchaBox");
function setStatus(text, kind) {
statusEl.textContent = text;
statusEl.className = "status" + (kind ? " " + kind : "");
}
function setBusy(isBusy) {
refreshBtn.disabled = isBusy;
queryBtn.disabled = isBusy;
}
function todayText() {
const d = new Date();
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
return `${y}-${m}-${day}`;
}
function encodeStr(payload) {
return btoa(encodeURIComponent(JSON.stringify(payload)));
}
function decodeStr(value) {
return decodeURIComponent(atob(value));
}
async function warmup() {
try {
await fetch(BASE_URL + "/app/#/user/visitorSearch", {
method: "GET",
credentials: "include",
mode: "cors"
});
} catch (_) {
// The API call below is the useful signal. Some browsers block this warmup from file://.
}
}
async function postEncoded(path, payload) {
const body = JSON.stringify({ encodeStr: encodeStr(payload) });
const resp = await fetch(BASE_URL + path, {
method: "POST",
credentials: "include",
mode: "cors",
headers: {
"Content-Type": "application/json;charset=UTF-8",
"X-Access-Type": "app"
},
body
});
const text = await resp.text();
if (!resp.ok) {
throw new Error(`HTTP ${resp.status}: ${text.slice(0, 500)}`);
}
let data;
try {
data = JSON.parse(text);
} catch (_) {
throw new Error(text);
}
if (data && data.responseEncodeStr) {
return JSON.parse(decodeStr(data.responseEncodeStr));
}
return data;
}
function validateIdCard(value) {
return /^(\d{6})(\d{4})(\d{2})(\d{2})(\d{3})([0-9]|X)$/.test(value);
}
async function loadCaptcha() {
const idCard = idCardEl.value.trim();
if (!validateIdCard(idCard)) {
setStatus("先把源码里的 ID_CARD 改成你的身份证号,或在输入框里填入身份证号。", "err");
return;
}
setBusy(true);
setStatus("正在获取验证码...");
resultEl.textContent = "等待输入验证码...";
try {
await warmup();
const data = await postEncoded("/boot/system/getCaptcha", { username: idCard });
if (!data.success) {
setStatus(data.message || "验证码接口返回失败。", "err");
resultEl.textContent = JSON.stringify(data, null, 2);
return;
}
captchaImgEl.src = "data:image/png;base64," + data.message;
captchaEl.value = "";
captchaEl.focus();
setStatus("验证码已加载。", "ok");
} catch (err) {
setStatus("获取验证码失败。", "err");
resultEl.textContent =
String(err.message || err) +
"\n\n如果这里显示 Failed to fetch / CORS本地 HTML 被浏览器跨域策略拦截了,需要放到同源环境或改用 Go 脚本。";
} finally {
setBusy(false);
}
}
async function queryVisitor() {
const idCard = idCardEl.value.trim();
const visitDate = visitDateEl.value.trim();
const captcha = captchaEl.value.trim();
if (!validateIdCard(idCard)) {
setStatus("身份证号格式不对。", "err");
return;
}
if (!/^\d{4}-\d{2}-\d{2}$/.test(visitDate)) {
setStatus("来访日期格式应为 YYYY-MM-DD。", "err");
return;
}
if (!captcha) {
setStatus("请输入验证码。", "err");
return;
}
setBusy(true);
setStatus("正在查询...");
resultEl.textContent = "查询中...";
try {
const data = await postEncoded("/boot/system/visitor/getVisitorHisByCard", {
"身份证号": idCard,
"随行人员": visitDate,
"备注": captcha
});
setStatus(data.success ? "查询完成。" : (data.message || "查询返回失败。"), data.success ? "ok" : "err");
resultEl.textContent = JSON.stringify(data, null, 2);
} catch (err) {
setStatus("查询失败。", "err");
resultEl.textContent = String(err.message || err);
} finally {
setBusy(false);
}
}
idCardEl.value = ID_CARD === "请改成你的身份证号" ? "" : ID_CARD;
visitDateEl.value = todayText();
refreshBtn.addEventListener("click", loadCaptcha);
queryBtn.addEventListener("click", queryVisitor);
captchaBox.addEventListener("click", loadCaptcha);
window.addEventListener("DOMContentLoaded", () => {
if (idCardEl.value) {
loadCaptcha();
}
});
</script>
</body>
</html>

@ -1,2 +1,61 @@
/Users/rice/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.26.2.darwin-arm64/src/runtime/proc.go:290
2026-08-10 22:58:31.124 ERROR /Users/rice/Documents/300-code/310-projects/313-smertLamp/tgk-touch/apps/m9zCtrlTty/kiosk_log.go:20 [touch-kiosk] skip firefox client startup because startup checks failed err=install Firefox failed: exit status 1
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