Crafting Your Own Railway Display with Java!

Rijo Sam

Have you fancied to have your own railway display at home? If you love traveling by public transport and always jump on the train just before the door closes like me, it’s really cool and highly efficient to have your own personalized display.

Background

Without live data, this project could not work. Luckily, all the data that was needed, was publicly and freely available with Nederlandse Spoorwegen (NS) APIs. To access them, you only need to create an account in the NS API Portal and subscribe to the APIs you want to use.

For the application’s backend, Java, Jakarta EE, and SpringBoot were chosen. For the front end, Vaadin was selected because it is closely related to Java and is perfect for a simple screen. Raspberry Pi 4 and an existing monitor were used as the hardware for the project.

Implementation

1. Setting up the backend

Jakarta WebTarget was used to connect to the Departures API from NS, which requires the query parameter ‘uicCode’, an international unique identifier for railway stations. Stations in Netherlands have a UIC code that starts with 84 (e.g., 8400058 for Amsterdam Centraal).

Sample code for connecting to the NS API using WebTarget

private Response getTrainsInfo(String stationUicCode) {
  return webTargetProvider.getWebTarget(getUri(stationUicCode))
    .request().accept(MediaType.APPLICATION_JSON)
    .header(HttpHeaders.CACHE_CONTROL, "no-cache")
    .header("Ocp-Apim-Subscription-Key", subscriptionKey)
    .buildGet().invoke();
}

private URI getUri(String stationUicCode) {
  return UriBuilder
    .fromUri(baseUrl).path(uriPath)
    .queryParam("uicCode", stationUicCode)
    .build();
}

The required data from the API response was captured as TrainInfo with the following fields.

public record TrainInfo(String direction,
  String plannedDepartureTime,
  String actualDepartureTime,
  String actualTrack,
  String trainCategory,
  String routeStations,
  String departureStatus,
  boolean isCancelled)
}

2. Building the view

The Grid component was used from Vaadin flow. It is a simple component for displaying tabular data with different rendering options. Renderers like Component Renderer or Lit Renderer can then customize the content displayed in specific columns

public MainView(TrainDepartureService trainDepartureService) {
  grid = new Grid<>();
  var trainDepartures = trainDepartureService.getDepartureInfo();
  grid.setItems(trainDepartures);
  grid.addThemeVariants(GridVariant.LUMO_ROW_STRIPES);
  grid.addColumn(createPlatformRenderer()).setHeader("PLATFORM");
  grid.addColumn(createStatusRenderer()).setHeader("STATUS");
  add(grid);
}

Both the renderers have been explored to show data in the columns. Lit Renderer offers quick rendering but requires writing HTML code. Components can be used in Lit Renderer through their custom HTML tags. Component Renderers are easy to build but slow to render, as they generate a component for each item in the dataset for a given column.

Sample code for Lit Renderer

private static Renderer<TrainDeparture> createPlatformRenderer() {
  return LitRenderer.<TrainDeparture>of(
    "<vaadin-horizontal-layout 
    style=\"align-items: center;\"theme=\"spacing\">" +
    "<span part=\"platformStyle\"> ${item.actualTrack} </span>" +
    "</vaadin-horizontal-layout>") 
    .withProperty("actualTrack", TrainDeparture::actualTrack); 
}

Sample code for Component Renderer

private static ComponentRenderer<Span, TrainDeparture> createStatusRenderer() { 
  return new ComponentRenderer<>(Span::new,(span, trainDeparture) -> { 
    span.setText(trainDeparture.status().name()); }); 
}

3. Styling the View

Finding the right color and font was the trickiest part. Luckily, there are websites that can help you find the exact RGB code by uploading your image and zooming over it.
There are also a few sites that can extract the different fonts used in your image, but be aware that the fonts suggested might not always be readily available or compatible.

4. Scheduled refresh

To show the updated information on screen with an interval of one minute, the combination of scheduler from Spring Boot and Push function from Vaadin were used.

// executed at the start of every minute.
@Scheduled(cron = "0 * * * * ?")
public void updateGrid() {
  var trainDepartures = trainDepartureService.getDepartureInfo();
  getUI().ifPresent(ui -> {
  if (ui.isAttached()) ui.access(() -> grid.setItems(trainDepartures));
  });
}

Vaadin Server push is based on a client-server connection established by the client. The server can then use the connection to send updates to the client. The @Push annotation was used on the application class to enable server push.

@Push
@EnableScheduling
@SpringBootApplication
public class Application implements AppShellConfigurator {
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);}
}

5. Setting up the Raspberry Pi 4

Raspberry Pi Imager was used to install the Raspberry Pi OS and SDKMAN to install Java 17. To run the NS application on the Raspberry Pi, a fat jar file was created and the application was started by executing the JAR file. For additional debugging within the Raspberry Pi, the already shipped VSCode was used.

Conclusion

In this project, Java, SpringBoot and Vaadin were explored to create a UI application seamlessly. Vaadin wrapped many complexities in annotations like @Push or the Component Grid frameworks. It was truly amazing to develop and run this application on Raspberry Pi 4. Finally, it’s not so much about technology as about the basic human need to solve problems and the curious mind that gets creative with every challenge. If you would like to know more about the project, please check out on GitHub.

References

Total
0
Shares
Previous Post

Call for Papers for JCON EUROPE 2026 Extended Until October 24

Next Post

Tame Your Llama: Run AI in Java

Related Posts