diff --git a/.vscode/settings.json b/.vscode/settings.json index 7a53f8d..5645ae6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "cSpell.words": [ - "PCIC" + "PCIC", + "swupdater" ] } \ No newline at end of file diff --git a/cmd/ovp8xx/cmd/swupdate.go b/cmd/ovp8xx/cmd/swupdate.go index 0f1548f..cce8247 100644 --- a/cmd/ovp8xx/cmd/swupdate.go +++ b/cmd/ovp8xx/cmd/swupdate.go @@ -94,9 +94,60 @@ The command establishes a connection to the device, uploads the firmware file, a RunE: swupdateCommand, } +// restartCmd represents the restart command. +// It restarts the device using the SWUpdater service. +// Depending on the state of the device, this command will either reboot to productive mode +// or restart the SWUpdater service again. +// If a previous update was initiated but not successful, the device will restart the SWUpdater service again. +var restartCmd = &cobra.Command{ + Use: "restart", + Short: "Restart the device", + Long: `This command restarts the device using the SWUpdater service. +Depending on the state of the device this will reboot to productive mode +or restart the SWUpdater service again. + +In case a previous update was initiated but not successful the device will restart +the SWUpdater service again. +`, + RunE: func(cmd *cobra.Command, args []string) error { + // Retrieve host and port from the parent command's flags + host, err := rootCmd.PersistentFlags().GetString("ip") + if err != nil { + return fmt.Errorf("cannot get host: %w", err) + } + + port, err := cmd.Parent().Flags().GetUint16("port") + if err != nil { + // If the port is not set on the parent, use a default value or handle the error + return fmt.Errorf("cannot get port: %w", err) + } + + connectionTimeout, err := cmd.Flags().GetDuration("online") + if err != nil { + return fmt.Errorf("cannot get timeout: %w", err) + } + + updater := swupdater.NewSWUpdater(host, port, nil) + + // Call the Restart method on the SWUpdater instance + if err := updater.Restart(connectionTimeout); err != nil { + return fmt.Errorf("failed to restart the device: %w", err) + } + + fmt.Println("Device restart initiated successfully.") + return nil + }, +} + func init() { rootCmd.AddCommand(swupdateCmd) - swupdateCmd.Flags().Uint16("port", 8080, "Port number for SWUpdate") - swupdateCmd.Flags().Duration("timeout", 5*time.Minute, "The timeout for the upload") + + swupdateCmd.PersistentFlags().Uint16("port", 8080, "Port number for SWUpdate") + swupdateCmd.PersistentFlags().Duration("online", 2*time.Minute, "The time to wait for the device to become available") swupdateCmd.Flags().Duration("online", 2*time.Minute, "The time to wait for the device to become available") + swupdateCmd.Flags().Duration("timeout", 5*time.Minute, "The timeout for the upload") + + // The restart sub command + swupdateCmd.AddCommand(restartCmd) + restartCmd.Flags().Duration("online", 3*time.Second, "The time to wait for the device to become available") } diff --git a/pkg/swupdater/swupdater.go b/pkg/swupdater/swupdater.go index 6538533..7ce1d56 100644 --- a/pkg/swupdater/swupdater.go +++ b/pkg/swupdater/swupdater.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "os" + "path/filepath" "strings" "time" @@ -40,7 +41,10 @@ func NewSWUpdater(hostName string, port uint16, notifications chan SWUpdaterNoti // The filename parameter specifies the name of the file to be uploaded. // Returns an error if the upload fails. func (s *SWUpdater) upload(filename string) error { - s.statusUpdate(fmt.Sprintf("Uploading software image to %s\n", s.urlUpload)) + basename := filepath.Base(filename) + s.statusUpdate( + fmt.Sprintf("Uploading software image: %s to %s\n", basename, s.urlUpload), + ) const fieldname string = "file" file, err := os.Open(filename) @@ -155,25 +159,17 @@ func (s *SWUpdater) statusUpdate(status string) { // It returns an error if the upload fails, or if the operation times out. func (s *SWUpdater) Update(filename string, connectionTimeout, timeout time.Duration) error { done := make(chan error) - start := time.Now() - s.statusUpdate("Waiting for the Device to become ready...") // Retry connection until successful or connectionTimeout occurs - for { - err := s.connect() - if err == nil { - s.statusUpdate("Device is ready now") - break - } - if time.Since(start) > connectionTimeout { - return fmt.Errorf("connection timeout: %w", err) - } - time.Sleep(3 * time.Second) // wait for a second before retrying + online, err := s.waitForOnline(connectionTimeout) + if !online { + return err } + // close the websocket after the Update operation defer s.disconnect() s.statusUpdate("Starting the Software Update process...") go s.waitForFinished(done) - err := s.upload(filename) + err = s.upload(filename) if err != nil { return fmt.Errorf("cannot upload software image: %w", err) } @@ -189,3 +185,59 @@ func (s *SWUpdater) Update(filename string, connectionTimeout, timeout time.Dura return errors.New("a timeout occurred while waiting for the update to finish") } } + +func (s *SWUpdater) waitForOnline(connectionTimeout time.Duration) (bool, error) { + start := time.Now() + s.statusUpdate("Waiting for the Device to become ready...") + + for { + err := s.connect() + if err == nil { + s.statusUpdate("Device is ready now") + break + } + if time.Since(start) > connectionTimeout { + return false, fmt.Errorf("connection timeout: %w", err) + } + // Retry after 3 seconds + time.Sleep(3 * time.Second) + } + return true, nil +} + +// Restart reboots the device by sending a POST request to the restart endpoint. +func (s *SWUpdater) Restart(timeout time.Duration) error { + // Construct the URL for the restart endpoint + restartURL := fmt.Sprintf("http://%s:%d/restart", s.hostName, s.port) + + online, err := s.waitForOnline(timeout) + if !online { + return err + } + // close the websocket after the Restart operation + defer s.disconnect() + + // Create a POST request with an empty body + req, err := http.NewRequest("POST", restartURL, nil) + if err != nil { + return fmt.Errorf("failed to create request (%s): %w", restartURL, err) + } + + // Send the request + resp, err := http.DefaultClient.Do(req) + if err != nil { + return fmt.Errorf("failed to send restart request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusServiceUnavailable { + return fmt.Errorf("the SWUpdate service is not available at the moment, please try again later") + } + + // Check if the response status code indicates success + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return fmt.Errorf("restart request (%s) failed with status code: %d", restartURL, resp.StatusCode) + } + + return nil +}