Skip to content

Commit

Permalink
Merge pull request #16 from graugans/feature/swupdater
Browse files Browse the repository at this point in the history
Add a restart command to the swupdater
  • Loading branch information
graugans authored Jul 5, 2024
2 parents b5ee90a + 01e1b65 commit 6b8fadf
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 18 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"cSpell.words": [
"PCIC"
"PCIC",
"swupdater"
]
}
55 changes: 53 additions & 2 deletions cmd/ovp8xx/cmd/swupdate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/graugans/go-ovp8xx/v2

go 1.21
go 1.22

require (
alexejk.io/go-xmlrpc v0.4.0
Expand Down
80 changes: 66 additions & 14 deletions pkg/swupdater/swupdater.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"time"

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand All @@ -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
}

0 comments on commit 6b8fadf

Please sign in to comment.