33 Commits
Author SHA1 Message Date
Paul Brinkmeier 56d67889b4 fixup! Add version to image meta
Build image / build-image (push) Has been cancelled
2025-04-23 17:05:57 +02:00
Paul Brinkmeier eceec32264 Add version to image meta
Build image / build-image (push) Failing after 2m18s
2025-04-23 17:01:00 +02:00
Paul Brinkmeier bc15cfd21d fixup! Run on tags instead of push
Build image / build-image (push) Successful in 3m55s
2025-04-23 16:45:49 +02:00
Paul Brinkmeier 2f0dd1b59b Run on tags instead of push 2025-04-23 16:44:29 +02:00
Paul Brinkmeier 2c1d1e42bd fixup! fixup! fixup! fixup! fixup! fixup! fixup! Add docker commands 2025-04-23 16:43:19 +02:00
Paul Brinkmeier e125aa5703 fixup! fixup! fixup! fixup! fixup! fixup! Add docker commands
Build image / build-image (push) Successful in 4m17s
2025-04-23 16:35:40 +02:00
Paul Brinkmeier ec87e87fd1 fixup! fixup! fixup! fixup! fixup! Add docker commands
Build image / build-image (push) Has been cancelled
2025-04-23 16:19:31 +02:00
Paul Brinkmeier 76ea336d6f fixup! fixup! fixup! fixup! Add docker commands
Build image / build-image (push) Waiting to run
2025-04-23 15:33:29 +02:00
Paul Brinkmeier 74be0b8a90 fixup! fixup! fixup! Add docker commands
Build image / build-image (push) Waiting to run
2025-04-23 14:50:24 +02:00
Paul Brinkmeier 28bb8be8b2 fixup! fixup! Add docker commands
Build image / build-image (push) Failing after 2m53s
2025-04-23 14:31:51 +02:00
Paul Brinkmeier 225b231262 fixup! Add docker commands
Build image / build-image (push) Failing after 3m12s
2025-04-23 14:27:36 +02:00
Paul Brinkmeier 309266e8e7 Add docker commands
Build image / build-image (push) Failing after 3m35s
2025-04-23 14:23:36 +02:00
Paul Brinkmeier 0b6b3a021b Try using catthehacker/ubuntu container
Build image / build-image (push) Successful in 5m16s
2025-04-23 14:00:56 +02:00
Paul Brinkmeier d3022c010f fixup! fixup! fixup! fixup! Add version file for crane push
Build image / build-image (push) Failing after 4m9s
2025-04-23 13:45:18 +02:00
Paul Brinkmeier 116cc5ce51 fixup! fixup! fixup! Add version file for crane push
Build image / build-image (push) Failing after 3m51s
2025-04-23 13:41:00 +02:00
Paul Brinkmeier 6bab8ac3a9 fixup! fixup! Add version file for crane push
Build image / build-image (push) Failing after 2m48s
2025-04-23 13:36:43 +02:00
Paul Brinkmeier f86c94ae99 fixup! Add version file for crane push
Build image / build-image (push) Failing after 3m2s
2025-04-23 13:32:22 +02:00
Paul Brinkmeier b91ab5d603 Add version file for crane push
Build image / build-image (push) Failing after 3m13s
2025-04-23 13:27:11 +02:00
Paul Brinkmeier c4017843ec fixup! fixup! Add crane tool
Build container / build-container (push) Failing after 3m5s
2025-04-23 13:07:57 +02:00
Paul Brinkmeier b0a989109a fixup! Add crane tool
Build container / build-container (push) Failing after 3m18s
2025-04-23 13:03:10 +02:00
Paul Brinkmeier e8b1556770 Add crane tool
Build container / build-container (push) Failing after 3m27s
2025-04-23 12:57:29 +02:00
Paul Brinkmeier b990bf2b8f Add docker load command
Build container / build-container (push) Failing after 3m48s
2025-04-23 12:42:25 +02:00
Paul Brinkmeier 389b83c72f fixup! fixup! Add sudo installation step
Build container / build-container (push) Successful in 3m28s
2025-04-23 12:35:39 +02:00
Paul Brinkmeier ee00ba9b34 fixup! Add sudo installation step
Build container / build-container (push) Failing after 3m47s
2025-04-23 12:30:47 +02:00
Paul Brinkmeier fa68ab9bcc Add sudo installation step
Build container / build-container (push) Failing after 16s
2025-04-23 12:29:01 +02:00
Paul Brinkmeier 1bcf8e30a0 Try nix-quick-install-action
Build container / build-container (push) Failing after 10s
2025-04-23 12:27:47 +02:00
Paul Brinkmeier 8189157608 fixup! fixup! fixup! Add install nix and build container steps
Build container / build-container (push) Failing after 9s
2025-04-23 12:25:37 +02:00
Paul Brinkmeier 3f9cee98f4 fixup! fixup! Add install nix and build container steps 2025-04-23 12:20:48 +02:00
Paul Brinkmeier 0c8a48cd41 fixup! Add install nix and build container steps
Build container / build-container (push) Failing after 1m18s
2025-04-23 12:20:34 +02:00
Paul Brinkmeier 09bdd39fd8 Add install nix and build container steps
Build container / build-container (push) Failing after 20s
2025-04-23 12:19:13 +02:00
Paul Brinkmeier 9506864c2d Set runs-on
Gitea Actions Demo / build-container (push) Successful in 8s
2025-04-23 12:12:19 +02:00
Paul Brinkmeier aca227bdc6 fixup! Init CI
Gitea Actions Demo / build-container (push) Has been cancelled
2025-04-23 12:10:21 +02:00
Paul Brinkmeier 1020564f8f Init CI 2025-04-23 12:08:46 +02:00
4 changed files with 83 additions and 106 deletions
@@ -10,9 +10,7 @@ jobs:
steps:
- name: Checkout repo
uses: actions/checkout@v4
# Required for installing nix
- name: Install sudo
run: apt-get update && apt-get install -y sudo
- run: apt-get update && apt-get install -y sudo
- name: Install Nix
uses: cachix/install-nix-action@v31
with:
@@ -22,7 +20,8 @@ jobs:
- name: Version check
run: |
VERSION=$(cat image-meta/version)
[ "$GITHUB_REF_NAME" = v"$VERSION" ]
echo "$GITHUB_REF_NAME" == v"$VERSION"
test "$GITHUB_REF_NAME" == v"$VERSION"
- name: Build image
run: nix build .#image -o image.tar.gz
- run: docker login -u paul -p "$PASSWORD" git.pbrinkmeier.de
+75 -95
View File
@@ -1,68 +1,37 @@
#include <ArduinoJson.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecureBearSSL.h>
#include "bitmaps.h"
#include "wifi_credentials.h"
#define TFT_CS D8
#define TFT_DC D4
#define TFT_RESET D3
// Screen stuff
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C
// Button stuff
#define BUTTON_PIN D6
// Display stuff
#define FRAME_DELAY 80
#define COLOR_BG (rgb(0, 0, 0))
#define COLOR_DIVIDER (rgb(0xff, 0x60, 0x0d))
#define COLOR_TEXT (rgb(0xff, 0x60, 0x0d))
// Layout
#define WIDTH 160
#define HEIGHT 128
#define CX 6
#define CY 8
#define MARGIN_LEFT 5
#define MARGIN_RIGHT 5
#define MAIN_X MARGIN_LEFT
#define MAIN_Y 9
#define MAIN_WIDTH (WIDTH - 2 * MARGIN_LEFT)
#define MAIN_HEIGHT (8 * CY)
#define CLOCK_X MARGIN_LEFT
#define CLOCK_Y 113
#define STATUS_X 95
#define STATUS_Y CLOCK_Y
#define STATUS_WIDTH (10 * CX)
#define STATUS_HEIGHT CY
// Peripherals
Adafruit_ST7735 display(TFT_CS, TFT_DC, TFT_RESET);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// App state
typedef enum {
enum {
CONNECTING,
SHOWING_DEPARTURES
} State;
State state = CONNECTING;
State previous_state = state;
} state = CONNECTING;
int frame = 0;
// Use this variable to trigger state enter effects
int state_changed = 1;
int buttonPushed = false;
JsonDocument departures;
@@ -70,38 +39,27 @@ void ICACHE_RAM_ATTR onButtonFalling() {
buttonPushed = true;
}
uint16_t rgb(uint16_t r, uint16_t g, uint16_t b) {
// Color layout: RRRRRGGGGGGBBBBB (5, 6, 5)
return ((b >> 3) & 0b11111) << 11 | ((g >> 2) & 0b111111) << 5 | ((r >> 3) & 0b11111);
}
void drawLayout() {
display.fillScreen(COLOR_BG);
display.drawFastHLine(5, 4, 150, COLOR_DIVIDER);
display.drawFastHLine(5, 109, 150, COLOR_DIVIDER);
display.drawFastHLine(5, 123, 150, COLOR_DIVIDER);
display.setCursor(CLOCK_X, CLOCK_Y);
display.print("13:12");
}
void setup() {
Serial.begin(9600);
Serial.println("serial init");
display.initR();
display.setSPISpeed(40000000);
display.setRotation(3);
Serial.println("display initialized");
Serial.printf("display dimensions: %" PRId16 "x%" PRId16 "\n", display.width(), display.height());
display.setTextSize(1);
display.setTextColor(COLOR_TEXT);
drawLayout();
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println("SSD1306 allocation failed");
for(;;); // Don't proceed, loop forever
}
Serial.println("SSD1306 initialized");
display.clearDisplay();
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
pinMode(BUTTON_PIN, INPUT_PULLUP);
attachInterrupt(BUTTON_PIN, onButtonFalling, FALLING);
display.setTextColor(SSD1306_INVERSE);
display.setTextSize(1);
display.setCursor(0, 0);
display.printf("Connecting to\n%s\n", WIFI_SSID);
display.display();
/*
display.print(" 5 Mannheim sofort\n"
"26 Kirchheim 9 min\n"
@@ -109,6 +67,11 @@ void setup() {
*/
}
void drawSpinner(int frame) {
display.fillRect(120, 56, spinny_dims[0], spinny_dims[1], SSD1306_BLACK);
display.drawBitmap(120, 56, spinny[frame % spinny_dims[2]], spinny_dims[0], spinny_dims[1], SSD1306_WHITE);
}
// TODO: Error handling
String fetchDepartures() {
std::unique_ptr<BearSSL::WiFiClientSecure> https(new BearSSL::WiFiClientSecure);
@@ -116,9 +79,10 @@ String fetchDepartures() {
HTTPClient client;
if (!client.begin(*https, "vrnp.beany.club", 443, "/departures?stop_id=de:08221:1225&platform=A")) {
display.fillScreen(COLOR_BG);
display.clearDisplay();
display.setCursor(0, 0);
display.print("begin failed");
display.display();
for (;;);
}
@@ -126,9 +90,10 @@ String fetchDepartures() {
client.addHeader("Accept", "application/json");
int statusCode = client.GET();
if (statusCode != 200) {
display.fillScreen(COLOR_BG);
display.clearDisplay();
display.setCursor(0, 0);
display.printf("http non-ok: %d\n", statusCode);
display.display();
for (;;);
}
@@ -136,29 +101,20 @@ String fetchDepartures() {
}
void loop() {
if (state != previous_state) {
state_changed = 1;
previous_state = state;
}
logic_loop();
frame++;
state_changed = 0;
}
void logic_loop() {
switch (state) {
case CONNECTING: {
if (WiFi.status() != WL_CONNECTED) {
display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG);
display.setCursor(STATUS_X, STATUS_Y);
display.printf(" connect %c", "|/-\\"[frame % 4]);
drawSpinner(frame++);
display.display();
delay(FRAME_DELAY);
return;
}
display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG);
display.setCursor(STATUS_X, STATUS_Y);
display.print(" fetching");
display.clearDisplay();
display.fillRect(0, 56, 128, 8, SSD1306_INVERSE);
display.setCursor(0, 56);
display.print("requesting departures");
display.display();
String departuresRaw = fetchDepartures();
deserializeJson(departures, departuresRaw);
@@ -174,24 +130,48 @@ void logic_loop() {
return;
}
display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG);
display.fillRect(MAIN_X, MAIN_Y, MAIN_WIDTH, MAIN_HEIGHT, COLOR_BG);
/*
if (buttonPushed) {
buttonPushed = false;
delay(FRAME_DELAY);
return;
}
*/
int line = 0;
// Marquee effect for direction strings longer than 11 characters
// Neither particularly efficient nor legible
// (This could use some love)
int availableWidth = 11;
display.clearDisplay();
display.setCursor(0, 0);
for (JsonVariant departure : departures["departures"].as<JsonArray>()) {
const char *directionStr = departure["direction"].as<const char *>();
display.setCursor(MAIN_X, MAIN_Y + (CY + 3) * line);
display.printf("%2s %-15.15s %6s",
char buf[128] = {0};
const char *dirStart;
if (strlen(directionStr) <= availableWidth || (strlen(directionStr) + 3 + availableWidth) > 127) {
dirStart = directionStr;
} else {
memcpy(buf, directionStr, strlen(directionStr));
memcpy(buf + strlen(directionStr), " ", 3);
memcpy(buf + strlen(directionStr) + 3, directionStr, availableWidth);
buf[strlen(directionStr) + 3 + availableWidth] = '\0';
dirStart = buf + frame % (strlen(directionStr) + 3);
}
display.printf("%2s %-11.11s %6s\n",
departure["symbol"].as<const char*>(),
directionStr,
dirStart,
departure["leaving"].as<const char*>()
);
line++;
}
display.display();
delay(25000);
state = CONNECTING;
frame++;
delay(500);
return;
}
}
+1 -1
View File
@@ -16,7 +16,7 @@
vrnp-static = pkgs.buildGoModule {
pname = "vrnp";
version = "0.0.5";
version = "0.0.4";
vendorHash = null;
# For building the package, we use only the files not ignored by Git as inputs.
+4 -6
View File
@@ -117,12 +117,6 @@ func ParseDepartures(response DMResponse, allowedPlatform *string) (Departures,
return Departures{}, errors.New("could not parse countdown")
}
matchesPlatform := allowedPlatform == nil || d.Platform == *allowedPlatform
isInThePast := countdown < 0
if !matchesPlatform || isInThePast {
continue
}
if countdown == 0 {
leaving = "sofort"
}
@@ -134,6 +128,10 @@ func ParseDepartures(response DMResponse, allowedPlatform *string) (Departures,
leaving = fmt.Sprintf("%02s:%02s", dt.Hour, dt.Minute)
}
if allowedPlatform != nil && d.Platform != *allowedPlatform {
continue
}
ds = append(ds, Departure{
d.Platform,
d.ServingLine.Symbol,