From a442ed013d7821388e7d4ca88c0d2ec16835a710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Ribal=20del=20R=C3=ADo?= Date: Wed, 20 May 2026 23:43:38 +0200 Subject: [PATCH 1/6] chore(adj-validator): copy ADJ-Validator from test-adj --- .github/workflows/adj-tester.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/adj-tester.yaml b/.github/workflows/adj-tester.yaml index af65939..d89174a 100644 --- a/.github/workflows/adj-tester.yaml +++ b/.github/workflows/adj-tester.yaml @@ -68,7 +68,13 @@ jobs: --arg message "chore: add compacted ADJ from ${{ github.repository }}@${{ github.sha }}" \ --rawfile content "${{ runner.temp }}/adj.b64" \ --arg branch "$TARGET_BRANCH" \ - '{message: $message, content: $content, branch: $branch}' \ + '{ + message: $message, + content: $content, + branch: $branch, + committer: {name: "github-actions[bot]", email: "41898282+github-actions[bot]@users.noreply.github.com"}, + author: {name: "github-actions[bot]", email: "41898282+github-actions[bot]@users.noreply.github.com"} + }' \ > "${{ runner.temp }}/body.json" curl --fail-with-body -sS -X PUT \ -H "Authorization: Bearer $GH_TOKEN" \ From 2991caff279254120df5c67afc8ed9097a3e0ecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Ribal=20del=20R=C3=ADo?= Date: Wed, 10 Jun 2026 10:55:05 +0200 Subject: [PATCH 2/6] chore(adj-validator): copy ADJ-Validator from test-adj --- .../adj-tester/schema/board.schema.json | 4 +--- .../adj-tester/schema/socket.schema.json | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/scripts/adj-tester/schema/board.schema.json b/.github/workflows/scripts/adj-tester/schema/board.schema.json index f640ac7..3c189c8 100644 --- a/.github/workflows/scripts/adj-tester/schema/board.schema.json +++ b/.github/workflows/scripts/adj-tester/schema/board.schema.json @@ -6,9 +6,7 @@ "additionalProperties": true, "required": [ "board_id", - "board_ip", - "measurements", - "packets" + "board_ip" ], "properties": { "board_id": { diff --git a/.github/workflows/scripts/adj-tester/schema/socket.schema.json b/.github/workflows/scripts/adj-tester/schema/socket.schema.json index 5c9644c..79ef9e9 100644 --- a/.github/workflows/scripts/adj-tester/schema/socket.schema.json +++ b/.github/workflows/scripts/adj-tester/schema/socket.schema.json @@ -43,10 +43,20 @@ "description": "Remote IPv4 address" }, "remote_port": { - "type": "integer", - "minimum": 1, - "maximum": 65535, - "description": "Remote port number for Socket" + "oneOf": [ + { + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + { + "type": "string", + "enum": [ + "backend" + ] + } + ], + "description": "Remote port number for Socket or the string \"backend\"" } } } From 21622c671cb8785edc1774791e5768dc25bf7bf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Ribal=20del=20R=C3=ADo?= Date: Wed, 10 Jun 2026 11:06:42 +0200 Subject: [PATCH 3/6] docs: update readme.md to include new spcify "backend" feature --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4531829..13517b0 100644 --- a/README.md +++ b/README.md @@ -235,8 +235,8 @@ Array of socket definitions for network communication. - `name`: Socket name - `port`: Optional number that describes the port number used for a ServerSocket or a DatagramSocket - `local_port`: Optional number that describes the local port number used for a Socket -- `remote_ip`: Optional string that describes the remote ip you want to connect to in a DatagramSocket or Socket -- `remote_port`: Optional number that describes the remote port number used for a Socket +- `remote_ip`: Optional string that describes the remote ip you want to connect to in a DatagramSocket or Socket. If set to `"backend"`, the address defined under `addresses.backend` in `general_info` is used +- `remote_port`: Optional number that describes the remote port number used for a Socket. If set to `"backend"`, the UDP port defined under `ports.UDP` in `general_info` is used **Example:** ```json From ae62bee5398d80fa61f10c3fd966b2d589166493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Ribal=20del=20R=C3=ADo?= Date: Wed, 10 Jun 2026 11:11:27 +0200 Subject: [PATCH 4/6] test: adj escalafon --- .github/workflows/adj-escalafon.yaml | 54 ++++ .../adj-compact/action_success_by_user.R | 232 ++++++++++++++++++ .../workflows/scripts/adj-compact/logo.png | Bin 0 -> 15015 bytes 3 files changed, 286 insertions(+) create mode 100644 .github/workflows/adj-escalafon.yaml create mode 100644 .github/workflows/scripts/adj-compact/action_success_by_user.R create mode 100644 .github/workflows/scripts/adj-compact/logo.png diff --git a/.github/workflows/adj-escalafon.yaml b/.github/workflows/adj-escalafon.yaml new file mode 100644 index 0000000..865846d --- /dev/null +++ b/.github/workflows/adj-escalafon.yaml @@ -0,0 +1,54 @@ +name: ADJ Escalaf贸n + +on: + schedule: + # Days 1 and 15 of every month at 11:05 (UTC). + - cron: "5 11 1,15 * *" + workflow_dispatch: + +jobs: + escalafon: + runs-on: ubuntu-latest + env: + CHART: ${{ github.workspace }}/action_success_by_user.png + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up R + uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - name: Install R packages + run: | + Rscript -e 'install.packages(c("httr", "jsonlite", "ggplot2", "png"))' + + - name: Generate escalaf贸n chart + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + Rscript .github/workflows/scripts/adj-compact/action_success_by_user.R \ + "${{ github.repository }}" \ + --out "$CHART" \ + --logo .github/workflows/scripts/adj-compact/logo.png + + - name: Upload chart artifact + uses: actions/upload-artifact@v4 + with: + name: adj-escalafon + path: ${{ env.CHART }} + + - name: Notify Slack + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} + run: | + jq -n \ + --arg repo "${{ github.repository }}" \ + --arg run_url "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" \ + '{ + text: " 馃搳 ADJ Escalaf贸n\n\nRepository: \($repo)\nNuevo escalaf贸n de 茅xito de Actions por usuario generado.\n\nDescarga el gr谩fico (artifact \"adj-escalafon\") desde el run:\n\($run_url)" + }' | curl -X POST -H "Content-type: application/json" \ + --data @- \ + $SLACK_WEBHOOK diff --git a/.github/workflows/scripts/adj-compact/action_success_by_user.R b/.github/workflows/scripts/adj-compact/action_success_by_user.R new file mode 100644 index 0000000..a5d586b --- /dev/null +++ b/.github/workflows/scripts/adj-compact/action_success_by_user.R @@ -0,0 +1,232 @@ +#!/usr/bin/env Rscript + +# action_success_by_user.R +# --------------------------------------------------------------------------- +# Fetch GitHub Actions workflow-run usage and plot the relative percentage of +# successful runs, grouped by the user who triggered each run. +# +# Usage: +# Rscript action_success_by_user.R owner/repo +# Rscript action_success_by_user.R --org my-org +# Rscript action_success_by_user.R owner/repo --out chart.png --max 1000 +# +# Auth: +# Set a token in the environment to avoid the 60 req/hour unauthenticated +# limit (and to read private repos): +# export GITHUB_TOKEN=ghp_xxx +# --------------------------------------------------------------------------- + +suppressWarnings(suppressMessages({ + ok <- requireNamespace("httr", quietly = TRUE) && + requireNamespace("jsonlite", quietly = TRUE) +})) +if (!ok) { + stop("This script needs the 'httr' and 'jsonlite' packages.\n", + "Install them with: install.packages(c('httr','jsonlite'))", + call. = FALSE) +} + +# ---- argument parsing ------------------------------------------------------ + +DEFAULT_REPO <- "Hyperloop-UPV/adj" + +parse_args <- function(args) { + out <- list(repo = NULL, org = NULL, out = "action_success_by_user.png", + max = Inf, logo = NULL) + i <- 1 + while (i <= length(args)) { + a <- args[[i]] + if (a == "--org") { + out$org <- args[[i + 1]]; i <- i + 2 + } else if (a == "--out") { + out$out <- args[[i + 1]]; i <- i + 2 + } else if (a == "--max") { + out$max <- as.numeric(args[[i + 1]]); i <- i + 2 + } else if (a == "--logo") { + out$logo <- args[[i + 1]]; i <- i + 2 + } else if (!startsWith(a, "--")) { + out$repo <- a; i <- i + 1 + } else { + stop("Unknown argument: ", a, call. = FALSE) + } + } + out +} + +opts <- parse_args(commandArgs(trailingOnly = TRUE)) + +if (is.null(opts$repo) && is.null(opts$org)) { + opts$repo <- DEFAULT_REPO + message("No repo argument given - using default: ", DEFAULT_REPO) +} + +# Auto-detect a logo.png next to the script if none was passed. +if (is.null(opts$logo) && file.exists("logo.png")) opts$logo <- "logo.png" + +token <- Sys.getenv("GITHUB_TOKEN", Sys.getenv("GH_TOKEN", "")) +gh_headers <- httr::add_headers( + Accept = "application/vnd.github+json", + `X-GitHub-Api-Version` = "2022-11-28", + Authorization = if (nzchar(token)) paste("Bearer", token) else NULL +) +if (!nzchar(token)) { + message("No GITHUB_TOKEN found - using unauthenticated API (60 req/hour limit).") +} + +# ---- generic paginated GET ------------------------------------------------- + +gh_get_all <- function(url, query = list(), item_key = NULL, max_items = Inf) { + per_page <- 100 + page <- 1 + items <- list() + repeat { + q <- modifyList(query, list(per_page = per_page, page = page)) + resp <- httr::GET(url, gh_headers, query = q) + if (httr::status_code(resp) == 403 && + identical(httr::headers(resp)[["x-ratelimit-remaining"]], "0")) { + stop("GitHub API rate limit exceeded. Set GITHUB_TOKEN to raise it.", + call. = FALSE) + } + httr::stop_for_status(resp, task = paste("fetch", url)) + body <- httr::content(resp, as = "text", encoding = "UTF-8") + parsed <- jsonlite::fromJSON(body, simplifyVector = FALSE) + batch <- if (is.null(item_key)) parsed else parsed[[item_key]] + if (length(batch) == 0) break + items <- c(items, batch) + if (length(items) >= max_items) { + items <- items[seq_len(min(length(items), max_items))] + break + } + if (length(batch) < per_page) break + page <- page + 1 + } + items +} + +# ---- collect workflow runs ------------------------------------------------- + +list_org_repos <- function(org) { + message("Listing repositories for org/user: ", org) + # Try org endpoint first, fall back to user endpoint. + url <- sprintf("https://api.github.com/orgs/%s/repos", org) + resp <- httr::GET(url, gh_headers, query = list(per_page = 1)) + if (httr::status_code(resp) == 404) { + url <- sprintf("https://api.github.com/users/%s/repos", org) + } + repos <- gh_get_all(url) + vapply(repos, function(r) r$full_name, character(1)) +} + +fetch_runs_for_repo <- function(full_name, max_items = Inf) { + message("Fetching workflow runs for ", full_name, " ...") + url <- sprintf("https://api.github.com/repos/%s/actions/runs", full_name) + gh_get_all(url, item_key = "workflow_runs", max_items = max_items) +} + +repos <- if (!is.null(opts$org)) list_org_repos(opts$org) else opts$repo + +runs <- list() +for (r in repos) { + remaining <- opts$max - length(runs) + if (remaining <= 0) break + runs <- c(runs, fetch_runs_for_repo(r, max_items = remaining)) +} + +if (length(runs) == 0) { + stop("No workflow runs found.", call. = FALSE) +} +message("Collected ", length(runs), " workflow runs.") + +# ---- reduce to a tidy data frame ------------------------------------------ + +safe <- function(x, default = NA_character_) if (is.null(x)) default else x + +df <- do.call(rbind, lapply(runs, function(run) { + data.frame( + user = safe(run$actor$login, "(unknown)"), + status = safe(run$status), + conclusion = safe(run$conclusion, "(none)"), + stringsAsFactors = FALSE + ) +})) + +# Users to exclude from the analysis (e.g. bots). +EXCLUDE_USERS <- c("Copilot") +df <- df[!df$user %in% EXCLUDE_USERS, , drop = FALSE] + +# Only count runs that have actually finished (have a conclusion). +df <- df[df$status == "completed" & !is.na(df$conclusion), , drop = FALSE] +if (nrow(df) == 0) { + stop("No completed runs with a conclusion to summarise.", call. = FALSE) +} + +# ---- per-user success percentage ------------------------------------------ + +agg <- aggregate( + list(total = rep(1, nrow(df)), + success = as.integer(df$conclusion == "success")), + by = list(user = df$user), + FUN = sum +) +agg$pct_success <- round(100 * agg$success / agg$total, 1) +# Order from lowest to highest success rate. +agg <- agg[order(agg$pct_success, agg$total), ] + +message("\nSuccess rate by user:") +print(agg, row.names = FALSE) + +# ---- plot ------------------------------------------------------------------ + +title <- "Hyperloop-UPV" +stamp <- format(Sys.time(), "Generado: %Y-%m-%d %H:%M:%S") + +if (requireNamespace("ggplot2", quietly = TRUE)) { + library(ggplot2) + # Keep the low-to-high ordering on the x axis (vertical bars). + agg$user <- factor(agg$user, levels = agg$user) + p <- ggplot(agg, aes(x = user, y = pct_success)) + + geom_col(fill = "#20274c") + + geom_text(aes(label = sprintf("%.0f%%\n(%d/%d)", pct_success, success, total)), + vjust = -0.3, size = 3, lineheight = 0.9) + + scale_y_continuous(limits = c(0, 100), breaks = seq(0, 100, 20), + expand = c(0, 0)) + + scale_x_discrete(expand = expansion(add = 0.7)) + + coord_cartesian(ylim = c(0, 100), clip = "off") + + labs(title = title, + subtitle = "Percentage of completed workflow runs that succeeded (by user)", + caption = stamp, + x = NULL, y = "Success rate (%)") + + theme_minimal(base_size = 12) + + theme(axis.text.x = element_text(angle = 45, hjust = 1), + panel.border = element_rect(color = "black", fill = NA, linewidth = 0.8), + plot.margin = margin(t = 20, r = 10, b = 5, l = 5)) + + w <- max(7, 0.7 * nrow(agg) + 2); h <- 6 + png(opts$out, width = w, height = h, units = "in", res = 120) + print(p) + # Overlay the team logo in the top-right corner, preserving its aspect ratio. + if (!is.null(opts$logo) && file.exists(opts$logo) && + requireNamespace("png", quietly = TRUE)) { + logo <- png::readPNG(opts$logo) + grid::grid.raster(logo, x = 0.985, y = 0.97, width = grid::unit(0.05, "npc"), + just = c("right", "top")) + } else if (!is.null(opts$logo)) { + message("Logo not drawn (file or 'png' package missing): ", opts$logo) + } + dev.off() +} else { + message("ggplot2 not installed - using base R barplot.") + png(opts$out, width = max(700, 70 * nrow(agg) + 120), height = 600) + par(mar = c(9, 5, 4, 2)) + bp <- barplot(agg$pct_success, names.arg = agg$user, horiz = FALSE, + las = 2, col = "#20274c", border = NA, ylim = c(0, 100), + ylab = "Success rate (%)", main = title) + box() # black frame around the plot + text(bp, agg$pct_success + 4, + labels = sprintf("%.0f%% (%d/%d)", agg$pct_success, agg$success, agg$total), + cex = 0.8, xpd = TRUE) + mtext(stamp, side = 1, line = 7, adj = 1, cex = 0.7, col = "gray30") + dev.off() +} + +message("\nSaved chart to: ", normalizePath(opts$out)) diff --git a/.github/workflows/scripts/adj-compact/logo.png b/.github/workflows/scripts/adj-compact/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9096371a8350b3e653424f8e3e745cc50974f74c GIT binary patch literal 15015 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 L17enj)s6oHMI6}$ literal 0 HcmV?d00001 From 8e93f0f757a77cc1ec5ff76acbc62b8eb355daee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Ribal=20del=20R=C3=ADo?= Date: Wed, 10 Jun 2026 11:19:35 +0200 Subject: [PATCH 5/6] test --- .github/workflows/adj-escalafon.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/adj-escalafon.yaml b/.github/workflows/adj-escalafon.yaml index 865846d..0ee6a04 100644 --- a/.github/workflows/adj-escalafon.yaml +++ b/.github/workflows/adj-escalafon.yaml @@ -1,6 +1,7 @@ name: ADJ Escalaf贸n on: + push: schedule: # Days 1 and 15 of every month at 11:05 (UTC). - cron: "5 11 1,15 * *" From 65515e36eb944b39bce7899808669af2a4b0d402 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Wed, 24 Jun 2026 16:38:03 +0200 Subject: [PATCH 6/6] docs: update readme to include BLCU restrictions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 13517b0..c6059bf 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ Array of packet definitions for network communication. Packets are separated by ``` **Field Descriptions:** -- `id`: Optional 32-bit unsigned integer packet identifier +- `id`: Optional 16-bit unsigned integer packet identifier. **MUST BE DIFFERENT TO 1 & 700** (BLCU uses) - `type`: Packet type string (e.g., "data", "order", "status") - `name`: Human-readable packet name - `variables`: Array of variable names/measurement IDs included in this packet