package main import ( "strconv" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/huh" ) func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: if msg.Type == tea.KeyCtrlC { return m, tea.Quit } if m.state == StateSettings { if m.scanning { return m, nil } if msg.Type == tea.KeyEsc { if m.settingSubState != SubMenu { m.settingSubState = SubMenu m.initSettingsMenu() return m, m.settingsForm.Init() } m.state = StateDashboard m.err = nil return m, nil } } if m.state == StateDashboard { switch msg.String() { case "enter": m.state = StateTerminal return m, tea.Sequence(tea.ExitAltScreen, startRawTerminal(&m)) case "s": return m, fetchSettings(*m.connDetails) case "u": return m, toggleLoad(*m.connDetails, "load_5v_on", !m.status.Sw.UsbOn) case "m": return m, toggleLoad(*m.connDetails, "load_12v_on", !m.status.Sw.MainOn) case "q": return m, tea.Quit } } case tea.WindowSizeMsg: m.width = msg.Width m.height = msg.Height case LoginSuccessMsg: m.connDetails.Token = string(msg) m.state = StateDashboard return m, tea.Batch( connectWebSocket(*m.connDetails), fetchInitialState(*m.connDetails), ) case InitStateMsg: m.status.Sw.MainOn = msg.MainOn m.status.Sw.UsbOn = msg.UsbOn return m, nil case SettingsFetchedMsg: m.settingsData = SettingsPayload(msg) if m.settingsData.CurIP.IP != "" { m.settingsData.IP = m.settingsData.CurIP.IP m.settingsData.Gateway = m.settingsData.CurIP.Gateway m.settingsData.Subnet = m.settingsData.CurIP.Subnet m.settingsData.DNS1 = m.settingsData.CurIP.DNS1 m.settingsData.DNS2 = m.settingsData.CurIP.DNS2 } m.settingSubState = SubMenu m.initSettingsMenu() m.state = StateSettings return m, m.settingsForm.Init() case WifiScanListMsg: m.scanning = false m.wifiScanList = []WifiAP(msg) m.settingSubState = SubWifiScanList m.initWifiScanForm() return m, m.settingsForm.Init() case ActionDoneMsg: m.err = nil return m, fetchSettings(*m.connDetails) case WsPartialMsg: if msg.Vin != nil { m.status.Vin = *msg.Vin } if msg.Main != nil { m.status.Main = *msg.Main } if msg.Usb != nil { m.status.Usb = *msg.Usb } if msg.Wifi != nil { m.status.Wifi = *msg.Wifi } if msg.Sw != nil { m.status.Sw = *msg.Sw } return m, waitForWebSocketMessage(msg.Conn) case TerminalFinishedMsg: m.state = StateDashboard return m, tea.Sequence(tea.EnterAltScreen, tea.ClearScreen) case error: m.scanning = false m.err = msg if m.state == StateLogin { if m.loginForm == nil { m.initLoginForm() } return m, m.loginForm.Init() } return m, nil } // 1. Login Form Update if m.state == StateLogin && m.loginForm != nil { form, cmd := m.loginForm.Update(msg) if f, ok := form.(*huh.Form); ok { m.loginForm = f if m.loginForm.State == huh.StateCompleted { *m.connDetails = *m.formData return m, performLogin(*m.connDetails) } } return m, cmd } // 2. Settings Form Update if m.state == StateSettings && m.settingsForm != nil { form, cmd := m.settingsForm.Update(msg) if f, ok := form.(*huh.Form); ok { m.settingsForm = f if m.settingsForm.State == huh.StateCompleted { if m.settingSubState == SubMenu { selection := f.GetString("menu_select") switch selection { case "scan": m.scanning = true return m, scanWifi(*m.connDetails) case "manual": m.settingSubState = SubWifiConnect m.initWifiForm() case "ip": m.settingSubState = SubIPConfig m.initIPForm() case "ap": m.settingSubState = SubAPMode m.initAPForm() case "system": m.settingSubState = SubSystem m.initSystemForm() case "safety": m.settingSubState = SubSafety m.initSafetyForm() case "account": m.settingSubState = SubAccount m.initAccountForm() case "reboot": return m, rebootDevice(*m.connDetails) case "exit": m.state = StateDashboard return m, nil } return m, m.settingsForm.Init() } if m.settingSubState == SubWifiScanList { selectedSSID := f.GetString("scan_ssid") if selectedSSID == "" { m.settingSubState = SubMenu m.initSettingsMenu() return m, m.settingsForm.Init() } m.settingsData.WifiSSID = selectedSSID m.settingSubState = SubWifiConnect m.initWifiForm() return m, m.settingsForm.Init() } switch m.settingSubState { case SubWifiConnect: payload := map[string]string{ "ssid": m.settingsData.WifiSSID, "password": m.settingsData.WifiPass, } return m, saveSettingsMap(*m.connDetails, payload) case SubIPConfig: payload := map[string]interface{}{"net_type": m.settingsData.NetType} if m.settingsData.NetType == "static" { payload["ip"] = m.settingsData.IP payload["gateway"] = m.settingsData.Gateway payload["subnet"] = m.settingsData.Subnet payload["dns1"] = m.settingsData.DNS1 payload["dns2"] = m.settingsData.DNS2 } return m, saveSettingsMap(*m.connDetails, payload) case SubAPMode: payload := map[string]string{"mode": m.settingsData.Mode} if m.settingsData.Mode == "apsta" { payload["ap_ssid"] = m.settingsData.APSSID payload["ap_password"] = m.settingsData.APPass } return m, saveSettingsMap(*m.connDetails, payload) case SubSystem: // [수정] 백엔드 한계로 인해 순차적(Sequential) 전송 필수 // baudrate 변경 요청 -> period 변경 요청 return m, tea.Sequence( saveSettingsMap(*m.connDetails, map[string]interface{}{"baudrate": m.settingsData.Baudrate}), saveSettingsMap(*m.connDetails, map[string]interface{}{"period": m.settingsData.Period}), ) case SubSafety: // Safety 설정은 백엔드에서 한 번에 처리가 가능하므로 하나로 묶음 valVin, _ := strconv.ParseFloat(f.GetString("vin_limit"), 64) valMain, _ := strconv.ParseFloat(f.GetString("main_limit"), 64) valUsb, _ := strconv.ParseFloat(f.GetString("usb_limit"), 64) payload := map[string]interface{}{ "vin_current_limit": valVin, "main_current_limit": valMain, "usb_current_limit": valUsb, } return m, saveSettingsMap(*m.connDetails, payload) case SubAccount: payload := map[string]string{ "new_username": m.settingsData.NewUser, "new_password": m.settingsData.NewPass, } return m, saveSettingsMap(*m.connDetails, payload) } } } return m, cmd } return m, nil }