diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 93047989c..7d02b8178 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -269,8 +269,10 @@ jobs: - name: Copy README.md run: cp README.md common-files/ - - name: Copy VERSION.md - run: cp backend/cmd/VERSION.md common-files/ + - name: Create VERSION.txt + run: | + VERSION="${{ github.event.inputs.version }}" + echo "$VERSION" > common-files/VERSION.txt - name: Upload common files artifact uses: actions/upload-artifact@v4 @@ -292,7 +294,7 @@ jobs: - name: Create release directories for each platform run: | - VERSION="${{ github.event.inputs.version || github.event.release.tag_name || 'latest' }}" + VERSION="${{ github.event.inputs.version }}" # Create directory structure for each platform mkdir -p release-linux @@ -302,7 +304,7 @@ jobs: - name: Organize Linux release files run: | - VERSION="${{ github.event.inputs.version || github.event.release.tag_name || 'latest' }}" + VERSION="${{ github.event.inputs.version }}" # Copy Linux backend cp artifacts/backend-linux/backend-linux-amd64 release-linux/backend @@ -325,7 +327,7 @@ jobs: - name: Organize Windows release files run: | - VERSION="${{ github.event.inputs.version || github.event.release.tag_name || 'latest' }}" + VERSION="${{ github.event.inputs.version }}" # Copy Windows backend cp artifacts/backend-windows/backend-windows-amd64.exe release-windows/backend.exe @@ -348,7 +350,7 @@ jobs: - name: Organize macOS Intel release files run: | - VERSION="${{ github.event.inputs.version || github.event.release.tag_name || 'latest' }}" + VERSION="${{ github.event.inputs.version }}" # Copy macOS Intel backend cp artifacts/backend-macos/backend-macos-amd64 release-macos/backend @@ -371,7 +373,7 @@ jobs: - name: Organize macOS ARM64 release files run: | - VERSION="${{ github.event.inputs.version || github.event.release.tag_name || 'latest' }}" + VERSION="${{ github.event.inputs.version }}" # Copy macOS ARM64 backend cp artifacts/backend-macos/backend-macos-arm64 release-macos-arm64/backend diff --git a/.gitignore b/.gitignore index 769d96cab..ddc94a4f5 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,7 @@ backend/cmd/cmd .idea/ .vscode/ .prettierrc + +# Claude +CLAUDE.md +.claude diff --git a/backend/cmd/VERSION.md b/backend/cmd/VERSION.md deleted file mode 100644 index 4a36342fc..000000000 --- a/backend/cmd/VERSION.md +++ /dev/null @@ -1 +0,0 @@ -3.0.0 diff --git a/backend/cmd/config.go b/backend/cmd/config.go index a586a6533..9aeb4208b 100644 --- a/backend/cmd/config.go +++ b/backend/cmd/config.go @@ -14,9 +14,14 @@ type Network struct { Manual bool } +type Transport struct { + PropagateFault bool +} + type Config struct { - Vehicle vehicle.Config - Server server.Config - Adj Adj - Network Network + Vehicle vehicle.Config + Server server.Config + Adj Adj + Network Network + Transport Transport } diff --git a/backend/cmd/config.toml b/backend/cmd/config.toml index 1d4be2c2e..7ac70bf6f 100644 --- a/backend/cmd/config.toml +++ b/backend/cmd/config.toml @@ -23,3 +23,5 @@ branch = "main" # Leave blank when using ADJ as a submodule (like this: "") test = true [network] manual = false +[transport] +propagate_fault = true diff --git a/backend/cmd/main.go b/backend/cmd/main.go index 860259acc..944ecf952 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -33,8 +33,8 @@ import ( vehicle_models "github.com/HyperloopUPV-H8/h9-backend/internal/vehicle/models" "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" "github.com/HyperloopUPV-H8/h9-backend/pkg/broker" - connection_topic "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/connection" blcu_topics "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/blcu" + connection_topic "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/connection" data_topic "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/data" logger_topic "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/logger" message_topic "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/message" @@ -87,7 +87,7 @@ var currentVersion string func main() { - versionFile := "VERSION.md" + versionFile := "VERSION.txt" versionData, err := os.ReadFile(versionFile) if err != nil { fmt.Fprintf(os.Stderr, "Error reading version file (%s): %v\n", versionFile, err) @@ -185,41 +185,35 @@ func main() { } else { fmt.Println("Backend folder not detected. Launching existing updater...") - osType := detectOS() execPath, err := os.Executable() if err != nil { fmt.Fprintf(os.Stderr, "Error getting executable path: %v\n", err) os.Exit(1) } - updatersDir := filepath.Join(filepath.Dir(execPath), "updaters") - - var updaterExe string - switch osType { - case "updaters/updater-windows-64.exe": - updaterExe = filepath.Join(updatersDir, "updater-windows-64") - case "updaters/updater-linux": - updaterExe = filepath.Join(updatersDir, "updater-linux") - case "updaters/updater-macos-m1": - updaterExe = filepath.Join(updatersDir, "updater-macos-m1") - case "updaters/updater-macos-64": - updaterExe = filepath.Join(updatersDir, "updater-macos-64") - default: - fmt.Fprintf(os.Stderr, "Unsupported updater: %s\n", osType) - os.Exit(1) + execDir := filepath.Dir(execPath) + + updaterExe := filepath.Join(execDir, "updater") + // En Windows el ejecutable lleva extensión .exe + if runtime.GOOS == "windows" { + updaterExe += ".exe" } - cmd := exec.Command(updaterExe) - cmd.Dir = updatersDir - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - fmt.Fprintf(os.Stderr, "Error launching updater: %v\n", err) - os.Exit(1) + if _, err := os.Stat(updaterExe); err == nil { + cmd := exec.Command(updaterExe) + cmd.Dir = execDir + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + fmt.Fprintf(os.Stderr, "Error launching updater: %v\n", err) + os.Exit(1) + } + } else { + fmt.Fprintf(os.Stderr, "Updater not found: %s\n", updaterExe) + fmt.Println("Skipping update. Proceeding with the current version.") } } - os.Exit(0) } else { fmt.Println("Skipping update. Proceeding with the current version.") } @@ -322,6 +316,7 @@ func main() { // <--- transport ---> transp := transport.NewTransport(trace.Logger) + transp.SetpropagateFault(config.Transport.PropagateFault) // <--- vehicle ---> ipToBoardId := make(map[string]abstraction.BoardId) @@ -742,14 +737,9 @@ func getLatestVersionFromGitHub() (string, error) { func detectOS() string { switch runtime.GOOS { case "windows": - return "updaters/updater-windows-64.exe" - case "darwin": - if strings.Contains(runtime.GOARCH, "arm") { - return "updaters/updater-macos-m1" - } - return "updaters/updater-macos-64" - case "linux": - return "updaters/updater-linux" + return "updater.exe" + case "darwin", "linux": + return "updater" default: fmt.Fprintf(os.Stderr, "Unsupported operating system: %s\n", runtime.GOOS) os.Exit(1) diff --git a/backend/internal/adj/git.go b/backend/internal/adj/git.go index 8eb3a11b8..95b48d20d 100644 --- a/backend/internal/adj/git.go +++ b/backend/internal/adj/git.go @@ -1,6 +1,7 @@ package adj import ( + "log" "os" "path/filepath" @@ -35,6 +36,7 @@ func updateRepo(AdjBranch string) error { _, err = git.PlainClone(tempPath, false, cloneOptions) if err != nil { // If the clone fails, work with the local ADJ + log.Printf("Warning: Could not clone ADJ branch '%s' from remote. Working with local ADJ. Error: %v", AdjBranch, err) return nil } diff --git a/backend/pkg/adj/git.go b/backend/pkg/adj/git.go index 8eb3a11b8..95b48d20d 100644 --- a/backend/pkg/adj/git.go +++ b/backend/pkg/adj/git.go @@ -1,6 +1,7 @@ package adj import ( + "log" "os" "path/filepath" @@ -35,6 +36,7 @@ func updateRepo(AdjBranch string) error { _, err = git.PlainClone(tempPath, false, cloneOptions) if err != nil { // If the clone fails, work with the local ADJ + log.Printf("Warning: Could not clone ADJ branch '%s' from remote. Working with local ADJ. Error: %v", AdjBranch, err) return nil } diff --git a/backend/pkg/transport/transport.go b/backend/pkg/transport/transport.go index 1c0d96eb8..8fb199e4e 100644 --- a/backend/pkg/transport/transport.go +++ b/backend/pkg/transport/transport.go @@ -36,6 +36,8 @@ type Transport struct { tftp *tftp.Client + propagateFault bool + api abstraction.TransportAPI logger zerolog.Logger @@ -170,6 +172,14 @@ func (transport *Transport) handleTCPConn(conn net.Conn) error { return } + if transport.propagateFault && packet.Id() == 0 { + connectionLogger.Info().Msg("replicating packet with id 0 to all boards") + err := transport.handlePacketEvent(NewPacketMessage(packet)) + if err != nil { + connectionLogger.Error().Err(err).Msg("failed to replicate packet") + } + } + from := conn.RemoteAddr().String() to := conn.LocalAddr().String() @@ -208,6 +218,37 @@ func (transport *Transport) SendMessage(message abstraction.TransportMessage) er // handlePacketEvent is used to send an order to one of the connected boards func (transport *Transport) handlePacketEvent(message PacketMessage) error { eventLogger := transport.logger.With().Str("type", fmt.Sprintf("%T", message.Packet)).Uint16("id", uint16(message.Id())).Logger() + + if message.Id() == 0 { + eventLogger.Info().Msg("broadcasting packet id 0") + data, err := transport.encoder.Encode(message.Packet) + if err != nil { + eventLogger.Error().Stack().Err(err).Msg("encode") + transport.errChan <- err + return err + } + + transport.connectionsMx.Lock() + defer transport.connectionsMx.Unlock() + for target, conn := range transport.connections { + eventLogger := eventLogger.With().Str("target", string(target)).Logger() + + totalWritten := 0 + for totalWritten < len(data) { + n, err := conn.Write(data[totalWritten:]) + eventLogger.Trace().Int("amount", n).Msg("written chunk") + totalWritten += n + if err != nil { + eventLogger.Error().Stack().Err(err).Msg("write") + transport.errChan <- err + return err + } + } + eventLogger.Info().Msg("sent") + } + return nil + } + target, ok := transport.idToTarget[message.Id()] if !ok { eventLogger.Debug().Msg("target not found") @@ -299,6 +340,15 @@ func (transport *Transport) handleConversation(socket network.Socket, reader io. return } + // Intercept packets with id == 0 and replicate + if transport.propagateFault && packet.Id() == 0 { + conversationLogger.Info().Msg("replicating packet with id 0 to all boards") + err := transport.handlePacketEvent(NewPacketMessage(packet)) + if err != nil { + conversationLogger.Error().Err(err).Msg("failed to replicate packet") + } + } + transport.api.Notification(NewPacketNotification(packet, srcAddr, dstAddr, time.Now())) } }() @@ -322,3 +372,7 @@ func (transport *Transport) SendFault() { // transport.errChan <- err // } } + +func (transport *Transport) SetpropagateFault(enabled bool) { + transport.propagateFault = enabled +} diff --git a/updater/main.go b/updater/main.go index ba08f9e4a..8e456b5c5 100644 --- a/updater/main.go +++ b/updater/main.go @@ -21,7 +21,7 @@ const ( func main() { // Detect the operating system - osType := detectOS() + zipName := getZipName() // Check if the `../backend/` folder exists if _, err := os.Stat("../backend"); err == nil { @@ -29,21 +29,21 @@ func main() { updateFromGit() } else { fmt.Println("Directory '../backend' not found. Checking binaries...") - updateFromBinaries(osType) + updateFromBinaries(zipName) } } -func detectOS() string { +func getZipName() string { switch runtime.GOOS { case "windows": - return "backend-windows-64.exe" // Incluye la extensión .exe para Windows + return "windows" case "darwin": if strings.Contains(runtime.GOARCH, "arm") { - return "backend-macos-m1-64" + return "macos-arm64" } - return "backend-macos-64" + return "macos-intel" case "linux": - return "backend-linux-64" + return "linux" default: fmt.Fprintf(os.Stderr, "Unsupported operating system: %s\n", runtime.GOOS) os.Exit(1) @@ -104,42 +104,41 @@ func stopProcess(processName string) error { return nil } -func updateFromBinaries(osType string) { - binaries := []string{"backend-windows-64.exe", "backend-linux-64", "backend-macos-64", "backend-macos-m1-64"} - for _, binary := range binaries { - if _, err := os.Stat("./" + binary); err == nil { - // Check if the backend process is running - isRunning, err := isProcessRunning(binary) - if err != nil { - fmt.Fprintf(os.Stderr, "Error checking if process is running: %v\n", err) - os.Exit(1) - } - - // Stop the process if it's running - if isRunning { - fmt.Printf("Process %s is running. Stopping it...\n", binary) - if err := stopProcess(binary); err != nil { - fmt.Fprintf(os.Stderr, "Error stopping process %s: %v\n", binary, err) - os.Exit(1) - } - time.Sleep(500 * time.Millisecond) // waits 1/2 second to ensure the process is stopped - } +func updateFromBinaries(zipBase string) { + binary := "backend" + if runtime.GOOS == "windows" { + binary += ".exe" + } - fmt.Printf("Deleting old binary: %s\n", binary) - deleted := false - for i := 0; i < 5; i++ { - if err := os.Remove("./" + binary); err == nil { - deleted = true - break - } else { - fmt.Printf("Retrying delete (%d/5)...\n", i+1) - time.Sleep(300 * time.Millisecond) - } - } - if !deleted { - fmt.Fprintf(os.Stderr, "Error deleting old binary after multiple attempts.\n") + // Stop and remove old binary if exists + if _, err := os.Stat("./" + binary); err == nil { + isRunning, err := isProcessRunning(binary) + if err != nil { + fmt.Fprintf(os.Stderr, "Error checking if process is running: %v\n", err) + os.Exit(1) + } + if isRunning { + fmt.Printf("Process %s is running. Stopping it...\n", binary) + if err := stopProcess(binary); err != nil { + fmt.Fprintf(os.Stderr, "Error stopping process %s: %v\n", binary, err) os.Exit(1) } + time.Sleep(500 * time.Millisecond) + } + fmt.Printf("Deleting old binary: %s\n", binary) + deleted := false + for i := 0; i < 5; i++ { + if err := os.Remove("./" + binary); err == nil { + deleted = true + break + } else { + fmt.Printf("Retrying delete (%d/5)...\n", i+1) + time.Sleep(300 * time.Millisecond) + } + } + if !deleted { + fmt.Fprintf(os.Stderr, "Error deleting old binary after multiple attempts.\n") + os.Exit(1) } } @@ -150,8 +149,7 @@ func updateFromBinaries(osType string) { os.Exit(1) } - // Construct the ZIP file URL - zipFileName := fmt.Sprintf("control-station-v%s.zip", strings.ReplaceAll(latestVersion, ".", "-")) + zipFileName := fmt.Sprintf("%s-%s.zip", zipBase, latestVersion) url := fmt.Sprintf("https://github.com/%s/%s/releases/download/v%s/%s", repoOwner, repoName, latestVersion, zipFileName) fmt.Printf("Downloading ZIP from: %s\n", url) @@ -162,8 +160,8 @@ func updateFromBinaries(osType string) { os.Exit(1) } - // Extract the binary from the ZIP file - binaryPath, err := extractBinaryFromZip("./"+zipFileName, osType) + // Extract the binary and VERSION.md from the ZIP file + binaryPath, err := extractBinaryFromZip("./"+zipFileName, binary) if err != nil { fmt.Fprintf(os.Stderr, "Error extracting the binary: %v\n", err) os.Exit(1)