Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ had changed in them - which is the gap this file exists to close.

### Added

- **Two more boards emulate.** Heltec E290 and Heltec Wireless Tracker V2
passed the board probe against v1.17.1 (build, boot, radio, transmit,
receive and flood), so they are offered for emulation.
- **Script input at an instant of simulated time.** `console.type`,
`console.cli` and the `companion.*` verbs that write to a node take `at_ms`:
the call is held and runs when the run reaches that instant, so a script
Expand Down Expand Up @@ -52,6 +55,10 @@ had changed in them - which is the gap this file exists to close.

### Fixed

- **A board probe says when GitHub's rate limit stopped it.** Out of
unauthenticated API allowance, it reported that the firmware catalogue
could not be reached; it now says the allowance is used up, until when, and
that `GITHUB_TOKEN` raises it.
- **Emulated nRF52 boards keep what they store.** The factory registers never
said how big the flash was, so the SoftDevice refused every erase and write
with `NRF_ERROR_FORBIDDEN`: the filesystem could not be formatted, and
Expand Down
3 changes: 3 additions & 0 deletions internal/firmware/board/board_heltec_e290.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ var heltecE290Board = Board{
// Serial is the USB port on this board: its MeshCore variant is
// built with ARDUINO_USB_CDC_ON_BOOT.
ConsoleOnUSB: true,
// Watched booting, transmitting and hearing a neighbour by the board
// probe against v1.17.1 (#603).
Verified: true,
},
Hardware: &Panel{
Parts: []Part{
Expand Down
3 changes: 3 additions & 0 deletions internal/firmware/board/board_heltec_tracker_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ var heltecTrackerV2Board = Board{
// Serial is the USB port on this board: its MeshCore variant is
// built with ARDUINO_USB_CDC_ON_BOOT.
ConsoleOnUSB: true,
// Watched booting, transmitting and hearing a neighbour by the board
// probe against v1.17.1 (#603).
Verified: true,
},
Hardware: &Panel{
Parts: []Part{
Expand Down
5 changes: 5 additions & 0 deletions internal/firmware/board/boards.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,11 @@ var EmulationVerified = []string{
"Xiao_S3",
"Xiao_S3_WIO",
"RAK_3112",
// Probed for #603 against v1.17.1: build, boot, radio, tx, rx and flood
// all passed. The E290's e-paper panel is still not drawn - the board
// runs, its display is the gap.
"Heltec_E290",
"heltec_tracker_v2",
}

// EmulationSupported reports whether a board can be run under emulation today.
Expand Down
24 changes: 24 additions & 0 deletions internal/firmware/emulated/board_catalogue.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import (
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"time"

"github.com/MeshBench/meshbench/internal/firmware"
)
Expand Down Expand Up @@ -386,11 +388,33 @@ func (c *BoardCatalogue) get(ctx context.Context, url, accept string) (io.ReadCl
}
if resp.StatusCode != http.StatusOK {
_ = resp.Body.Close()
if limited := rateLimited(resp); limited != "" {
return nil, fmt.Errorf("%s returned %s: %s", url, resp.Status, limited)
}
return nil, fmt.Errorf("%s returned %s", url, resp.Status)
}
return resp.Body, nil
}

// rateLimited says so when GitHub refused because this address has used its
// allowance, which reads otherwise as the catalogue being unreachable. Sixty
// unauthenticated requests an hour is a handful of board probes.
func rateLimited(resp *http.Response) string {
if resp.StatusCode != http.StatusForbidden && resp.StatusCode != http.StatusTooManyRequests {
return ""
}
if resp.Header.Get("X-RateLimit-Remaining") != "0" {
return ""
}
when := ""
if reset, err := strconv.ParseInt(resp.Header.Get("X-RateLimit-Reset"), 10, 64); err == nil {
when = " until " + time.Unix(reset, 0).Format("15:04")
}
return "GitHub's API allowance for this address is used up" + when +
"; set GITHUB_TOKEN (export GITHUB_TOKEN=$(gh auth token)) to raise it. " +
"Already-downloaded images keep working without it"
}

// countingReader reports as it goes, throttled to whole percent so a slow
// link does not spend its time posting verbs instead of reading bytes.
type countingReader struct {
Expand Down
23 changes: 23 additions & 0 deletions internal/firmware/emulated/ratelimit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package emulated

import (
"net/http"
"strings"
"testing"
)

// A refusal because this address has used its API allowance says so, rather
// than reading as a catalogue that could not be reached (#603: five board
// probes in a row failed that way).
func TestARateLimitIsSaid(t *testing.T) {
resp := &http.Response{StatusCode: http.StatusForbidden, Header: http.Header{}}
resp.Header.Set("X-RateLimit-Remaining", "0")
resp.Header.Set("X-RateLimit-Reset", "1792894182")
if got := rateLimited(resp); !strings.Contains(got, "GITHUB_TOKEN") {
t.Errorf("a rate limit read as %q", got)
}
resp.Header.Set("X-RateLimit-Remaining", "12")
if got := rateLimited(resp); got != "" {
t.Errorf("a 403 with allowance left read as a rate limit: %q", got)
}
}