Skip to content

Commit 10c2829

Browse files
authored
Merge branch 'develop' into control-station/gui-booster
2 parents 52931b5 + 53a8e6d commit 10c2829

31 files changed

Lines changed: 1365 additions & 745 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
build
22
profiles
33
backend/cmd/cmd
4+
backend/cmd/adj/
5+
.ropeproject
46

57
# MacOS Files
68
.DS_Store

.gitmodules

Lines changed: 0 additions & 4 deletions
This file was deleted.

README.md

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,20 @@
44
[![CI](https://github.com/HyperloopUPV-H8/software/actions/workflows/build-ethernet-view.yaml/badge.svg)](https://github.com/HyperloopUPV-H8/software/actions/workflows/build-ethernet-view.yaml)
55
[![CI](https://github.com/HyperloopUPV-H8/software/actions/workflows/build-control-station.yaml/badge.svg)](https://github.com/HyperloopUPV-H8/software/actions/workflows/build-control-station.yaml)
66

7-
Hyperloop UPV's Control STtaion is a unified software solution for monitoring and commanding the pod in real time. It combines a back-end (Go) that ingests and interprets sensor data–defined via the JSON-based "ADJ" specifications–and a front-end (Typescript/React) that displays metrics, logs, and diagnostics to operators. With features like packet parsing, logging, and live dashboards, it acts as the central hub to safely interface the pod, making it easier for team members to oversee performance, detect faults, and send precise orders to the vehicle.
7+
Hyperloop UPV's Control Station is a unified software solution for real-time monitoring and commanding of the pod. It combines a back-end (Go) that ingests and interprets sensor data–defined via the JSON-based "ADJ" specifications–and a front-end (Typescript/React) that displays metrics, logs, and diagnostics to operators. With features like packet parsing, logging, and live dashboards, it acts as the central hub to safely interface the pod, making it easier for team members to oversee performance, detect faults, and send precise orders to the vehicle.
88

99
### Installation
1010

1111
Download the last release, unzip it and leave the executable compatible with your OS in the folder.
1212

1313
### Developing
1414

15-
The main project file is inside `backend/cmd`. Make sure to have the proper `config.toml` configuration and that you are in the `develop` branch. To build the project just run `go build` inside the folder. With everything set up execute the `cmd` executable, then move to `ethernet-view` and run `npm run dev`, then to the `control-station` and do the same.
15+
The main project file is inside `backend/cmd`. Ensure you have the proper `config.toml` configuration and are in the `develop` branch. To build the project, just run `go build` inside the folder. With everything set up, execute the `cmd` executable, then move to `ethernet-view` and run `npm run dev`, then to the `control-station` and do the same.
1616

1717
### Contributing
1818

1919
See [CONTRIBUTING.md](./CONTRIBUTING.md) for ways to contribute to the Control Station.
2020

21-
### Authors
22-
23-
- [Juan Martinez Alonso](https://github.com/jmaralo)
24-
- [Marc Sanchis Llinares](https://github.com/msanlli)
25-
- [Sergio Moreno Suay](https://github.com/smorsua)
26-
- [Felipe Zaballa Martinez](https://github.com/lipezaballa)
27-
- [Andrés de la Torre Mora](https://github.com/andresdlt03)
28-
- [Alejandro Losa](https://github.com/Losina24)
29-
3021
### About
3122

3223
HyperloopUPV is a student team based at Universitat Politècnica de València (Spain), which works every year to develop the transport of the future, the hyperloop. Check out [our website](https://hyperloopupv.com/#/)

backend/cmd/JSON_ADE

Lines changed: 0 additions & 1 deletion
This file was deleted.

backend/cmd/config.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,18 @@ import (
55
"github.com/HyperloopUPV-H8/h9-backend/internal/vehicle"
66
)
77

8+
type Adj struct {
9+
Branch string
10+
Test bool
11+
}
12+
13+
type Network struct {
14+
Manual bool
15+
}
16+
817
type Config struct {
918
Vehicle vehicle.Config
1019
Server server.Config
20+
Adj Adj
21+
Network Network
1122
}

backend/cmd/config.toml

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
1-
2-
[server.local]
1+
[vehicle]
2+
boards = ["HVSCU", "PCU"]
3+
[server.ethernet-view]
4+
address = "127.0.0.1:4040"
5+
static = "./ethernet-view"
6+
[server.ethernet-view.endpoints]
7+
pod_data = "/podDataStructure"
8+
order_data = "/orderStructures"
9+
programable_boards = "/uploadableBoards"
10+
connections = "/backend"
11+
files = "/"
12+
[server.control-station]
313
address = "127.0.0.1:4000"
4-
static = "./static"
5-
6-
[server.local.endpoints]
14+
static = "./control-station"
15+
[server.control-station.endpoints]
716
pod_data = "/podDataStructure"
817
order_data = "/orderStructures"
918
programable_boards = "/uploadableBoards"
1019
connections = "/backend"
1120
files = "/"
12-
13-
[vehicle]
14-
boardsList = ["VCU"]
21+
[adj]
22+
branch = "sw-bcu" # Leave blank when using ADJ as a submodule (like this: "")
23+
test = true
24+
[network]
25+
manual = false

backend/cmd/main.go

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import (
5353
"github.com/google/gopacket/pcap"
5454
"github.com/jmaralo/sntp"
5555
"github.com/pelletier/go-toml/v2"
56+
"github.com/pkg/browser"
5657
trace "github.com/rs/zerolog/log"
5758
)
5859

@@ -96,11 +97,12 @@ func main() {
9697
defer pprof.StopCPUProfile()
9798
}
9899
runtime.SetBlockProfileRate(*blockprofile)
100+
99101
config := getConfig("./config.toml")
100102

101103
// <--- ADJ --->
102104

103-
adj, err := adj_module.NewADJ()
105+
adj, err := adj_module.NewADJ(config.Adj.Branch, config.Adj.Test)
104106
if err != nil {
105107
trace.Fatal().Err(err).Msg("setting up ADJ")
106108
}
@@ -120,7 +122,7 @@ func main() {
120122

121123
dev = devs[*networkDevice]
122124
} else {
123-
dev, err = selectDev()
125+
dev, err = selectDev(adj.Info.Addresses, config)
124126
if err != nil {
125127
trace.Fatal().Err(err).Msg("Error selecting device")
126128
}
@@ -323,6 +325,9 @@ func main() {
323325
}()
324326
}
325327

328+
browser.OpenURL("http://" + config.Server["ethernet-view"].Addr)
329+
browser.OpenURL("http://" + config.Server["control-station"].Addr)
330+
326331
interrupt := make(chan os.Signal, 1)
327332
signal.Notify(interrupt, os.Interrupt)
328333

@@ -345,26 +350,39 @@ func createPid(path string) {
345350
}
346351
}
347352

348-
func selectDev() (pcap.Interface, error) {
353+
func selectDev(adjAddr map[string]string, conf Config) (pcap.Interface, error) {
349354
devs, err := pcap.FindAllDevs()
350355
if err != nil {
351356
return pcap.Interface{}, err
352357
}
353358

354-
cyan := color.New(color.FgCyan)
359+
if conf.Network.Manual {
360+
cyan := color.New(color.FgCyan)
355361

356-
cyan.Print("select a device: ")
357-
fmt.Printf("(0-%d)\n", len(devs)-1)
358-
for i, dev := range devs {
359-
displayDev(i, dev)
360-
}
362+
cyan.Print("select a device: ")
363+
fmt.Printf("(0-%d)\n", len(devs)-1)
364+
for i, dev := range devs {
365+
displayDev(i, dev)
366+
}
361367

362-
dev, err := acceptInput(len(devs))
363-
if err != nil {
364-
return pcap.Interface{}, err
365-
}
368+
dev, err := acceptInput(len(devs))
369+
if err != nil {
370+
return pcap.Interface{}, err
371+
}
366372

367-
return devs[dev], nil
373+
return devs[dev], nil
374+
} else {
375+
for _, dev := range devs {
376+
for _, addr := range dev.Addresses {
377+
if addr.IP.String() == adjAddr["backend"] {
378+
return dev, nil
379+
}
380+
}
381+
}
382+
383+
log.Fatal("backend address not found in any device")
384+
return pcap.Interface{}, nil
385+
}
368386
}
369387

370388
func displayDev(i int, dev pcap.Interface) {
@@ -559,7 +577,7 @@ func getIPIPfilter() string {
559577
}
560578

561579
func getUDPFilter(addrs []net.IP, backendAddr net.IP, port uint16) string {
562-
udpPort := "udp" // TODO use proper ports for the filter
580+
udpPort := fmt.Sprintf("udp port %d", port) // TODO use proper ports for the filter
563581
srcUdpAddrs := common.Map(addrs, func(addr net.IP) string {
564582
return fmt.Sprintf("(src host %s)", addr)
565583
})

backend/cmd/testadj.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import os
2+
import json
3+
4+
5+
def validate_json_structure(data):
6+
errors = []
7+
8+
if "board_id" in data:
9+
if not isinstance(data["board_id"], int):
10+
errors.append(f"'board_id' debe ser un entero, pero se encontró: {type(data['board_id']).__name__}.")
11+
12+
if "board_ip" in data:
13+
if not isinstance(data["board_ip"], str):
14+
errors.append(f"'board_ip' debe ser una cadena, pero se encontró: {type(data['board_ip']).__name__}.")
15+
16+
17+
if "measurements" in data:
18+
if not isinstance(data["measurements"], list):
19+
errors.append("La clave 'measurements' debe ser una lista.")
20+
else:
21+
for measurement in data["measurements"]:
22+
if isinstance(measurement, dict):
23+
required_keys = ["id", "name", "type", "podUnits", "displayUnits"]
24+
for key in required_keys:
25+
if key not in measurement:
26+
errors.append(f"Falta la clave '{key}' en un objeto de 'measurements'.")
27+
if "id" in measurement and not isinstance(measurement["id"], str):
28+
errors.append(f"El 'id' debe ser una cadena en: {measurement}")
29+
if "name" in measurement and not isinstance(measurement["name"], str):
30+
errors.append(f"El 'name' debe ser una cadena en: {measurement}")
31+
if "type" in measurement and not isinstance(measurement["type"], str):
32+
errors.append(f"El 'type' debe ser una cadena en: {measurement}")
33+
if "safeRange" in measurement and not isinstance(measurement.get("safeRange", []), list):
34+
errors.append(f"'safeRange' debe ser una lista en: {measurement}")
35+
if "warningRange" in measurement and not isinstance(measurement.get("warningRange", []), list):
36+
errors.append(f"'warningRange' debe ser una lista en: {measurement}")
37+
if "displayUnits" in measurement and not isinstance(measurement["displayUnits"], str):
38+
errors.append(f"El 'podUnits' debe ser una cadena en: {measurement}")
39+
if "podUnits" in measurement and not isinstance(measurement["podUnits"], str):
40+
errors.append(f"El 'podUnits' debe ser una cadena en: {measurement}") #esto se puede quitar (json 516)
41+
elif not isinstance(measurement, str):
42+
errors.append("Cada elemento en 'measurements' debe ser un objeto o una cadena (nombre de archivo).")
43+
44+
45+
if "packets" in data:
46+
if not isinstance(data["packets"], list):
47+
errors.append("La clave 'packets' debe ser una lista.")
48+
else:
49+
for packet in data["packets"]:
50+
if isinstance(packet, dict):
51+
required_keys = ["id", "name", "type","variable"]
52+
for key in required_keys:
53+
if key not in packet:
54+
errors.append(f"Falta la clave '{key}' en un objeto de 'packets'.")
55+
if "id" in packet and not isinstance(packet["id"], str):
56+
errors.append(f"El 'id' debe ser una cadena en: {packet}")
57+
if "name" in packet and not isinstance(packet["name"], str):
58+
errors.append(f"El 'name' debe ser una cadena en: {packet}")
59+
if "type" in packet and not isinstance(packet["type"], str):
60+
errors.append(f"El 'type' debe ser una cadena en: {packet}")
61+
if "variables" in packet:
62+
if not isinstance(packet["variables"], list):
63+
errors.append(f"'variables' debe ser una lista en: {packet}")
64+
else:
65+
for variable in packet["variables"]:
66+
if not isinstance(variable, dict):
67+
errors.append(f"Cada elemento en 'variables' debe ser un objeto en: {packet}")
68+
if "name" not in variable:
69+
errors.append(f"Falta la clave 'name' en un objeto de 'variables' en: {packet}")
70+
elif not isinstance(packet, str):
71+
errors.append("Cada elemento en 'packets' debe ser un objeto o una cadena (nombre de archivo).")
72+
73+
return errors
74+
75+
76+
77+
def validate_json_folder(folder_path):
78+
boards_file_path = os.path.join(folder_path, "boards.json")
79+
80+
try:
81+
with open(boards_file_path, 'r') as boards_file:
82+
boards_data = json.load(boards_file)
83+
boards = boards_data.get("boards", {})
84+
85+
86+
board_keys = list(boards.keys())
87+
duplicate_keys = [key for key in board_keys if board_keys.count(key) > 1]
88+
89+
if duplicate_keys:
90+
print(f"Error: El archivo boards.json contiene claves duplicadas: {', '.join(duplicate_keys)}")
91+
return
92+
93+
except json.JSONDecodeError as e:
94+
print(f"Error al decodificar JSON en {boards_file_path}: {e}")
95+
return
96+
except Exception as e:
97+
print(f"Error al procesar el archivo {boards_file_path}: {e}")
98+
return
99+
100+
101+
for board_name, board_file_path in boards.items():
102+
full_path = os.path.join(folder_path, board_file_path)
103+
try:
104+
with open(full_path, 'r') as board_file:
105+
data = json.load(board_file)
106+
errors = validate_json_structure(data)
107+
if errors:
108+
print(f"Errores encontrados en {full_path} para la placa '{board_name}':")
109+
for error in errors:
110+
print(f"- {error}")
111+
112+
except json.JSONDecodeError as e:
113+
print(f"Error al decodificar JSON en {full_path}: {e}")
114+
except Exception as e:
115+
print(f"Error al procesar el archivo {full_path}: {e}")
116+
117+
118+
if os.path.exists('./adj/') == False:
119+
print("La carpeta ./adj/ no existe")
120+
if __name__ == "__main__":
121+
validate_json_folder("./adj/")

backend/go.mod

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,18 @@ module github.com/HyperloopUPV-H8/h9-backend
33
go 1.21.3
44

55
require (
6-
github.com/HyperloopUPV-H8/ade-linter v0.0.0-20230530153315-3379f05a664f
76
github.com/go-git/go-git/v5 v5.12.0
87
github.com/google/gopacket v1.1.19
98
github.com/google/uuid v1.3.0
109
github.com/gorilla/websocket v1.5.0
1110
github.com/jmaralo/sntp v0.0.0-20240116111937-45a0a3419272
1211
github.com/pelletier/go-toml/v2 v2.0.7
1312
github.com/pin/tftp/v3 v3.0.0
14-
github.com/pkg/errors v0.9.1
1513
github.com/rs/zerolog v1.29.0
16-
github.com/xuri/excelize/v2 v2.7.1
1714
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
18-
google.golang.org/api v0.114.0
1915
)
2016

2117
require (
22-
cloud.google.com/go/compute v1.19.1 // indirect
23-
cloud.google.com/go/compute/metadata v0.2.3 // indirect
2418
dario.cat/mergo v1.0.0 // indirect
2519
github.com/Microsoft/go-winio v0.6.1 // indirect
2620
github.com/ProtonMail/go-crypto v1.0.0 // indirect
@@ -30,33 +24,20 @@ require (
3024
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
3125
github.com/go-git/go-billy/v5 v5.5.0 // indirect
3226
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
33-
github.com/golang/protobuf v1.5.3 // indirect
34-
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
35-
github.com/googleapis/gax-go/v2 v2.7.1 // indirect
3627
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
3728
github.com/kevinburke/ssh_config v1.2.0 // indirect
3829
github.com/mattn/go-colorable v0.1.13 // indirect
3930
github.com/mattn/go-isatty v0.0.17 // indirect
40-
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
4131
github.com/pjbgf/sha1cd v0.3.0 // indirect
42-
github.com/richardlehane/mscfb v1.0.4 // indirect
43-
github.com/richardlehane/msoleps v1.0.3 // indirect
32+
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
33+
github.com/pkg/errors v0.9.1 // indirect
4434
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
4535
github.com/skeema/knownhosts v1.2.2 // indirect
4636
github.com/xanzy/ssh-agent v0.3.3 // indirect
47-
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect
48-
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect
49-
go.opencensus.io v0.24.0 // indirect
5037
golang.org/x/crypto v0.21.0 // indirect
5138
golang.org/x/mod v0.12.0 // indirect
52-
golang.org/x/oauth2 v0.7.0 // indirect
5339
golang.org/x/sys v0.18.0 // indirect
54-
golang.org/x/text v0.14.0 // indirect
5540
golang.org/x/tools v0.13.0 // indirect
56-
google.golang.org/appengine v1.6.7 // indirect
57-
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
58-
google.golang.org/grpc v1.56.3 // indirect
59-
google.golang.org/protobuf v1.30.0 // indirect
6041
gopkg.in/warnings.v0 v0.1.2 // indirect
6142
)
6243

0 commit comments

Comments
 (0)