DIY server in PHP/Laravel for self hosted screen generation.
Find a file
Benjamin Nussbaum 2906dd9071 feat: add recipe preview
wip

wip

feat: render preview
2025-05-28 20:16:26 +02:00
.devcontainer chore: updates 2025-03-14 18:26:29 +01:00
.github feat: show version number on welcome screen when logged in 2025-05-06 13:19:02 +02:00
app fix: show error bag on markup page 2025-05-22 09:44:27 +03:00
bootstrap add features 2025-03-03 21:24:22 +01:00
config fix: increase terminal wait time to 30s to avoid image timeout 2025-05-13 19:43:45 +02:00
database fix: docker-compose for production 2025-05-15 20:41:37 +02:00
docker/prod fix: docker-compose for production 2025-05-15 20:41:37 +02:00
docs chore: refresh Readme 2025-05-13 21:30:38 +02:00
public feat: add favicon 2025-04-26 14:59:49 +02:00
resources feat: add recipe preview 2025-05-28 20:16:26 +02:00
routes fix: reset cache for devices with different dimensions 2025-05-13 19:29:03 +02:00
screenshots chore: replace files with cdn links 2025-05-13 21:39:17 +02:00
storage initial commit 2025-02-12 22:15:57 +01:00
tests feat: prefer png format on firmware versions >=1.5.2 2025-05-13 11:42:27 +02:00
.dockerignore ignore more things 2025-03-28 12:55:28 +01:00
.editorconfig Set up a fresh Laravel app 2025-02-08 15:05:41 +01:00
.env.example Move DB_DATABASE to docker-compose 2025-05-11 18:25:17 +02:00
.env.example.local chore: updates 2025-03-14 18:26:29 +01:00
.gitattributes Set up a fresh Laravel app 2025-02-08 15:05:41 +01:00
.gitignore feat: update to Laravel 12 starter kit (#1) 2025-02-25 12:15:35 +01:00
artisan Set up a fresh Laravel app 2025-02-08 15:05:41 +01:00
composer.json feat: add support for screen generation on AWS Lambda 2025-05-08 11:25:55 +02:00
composer.lock chore: update dependencies 2025-05-27 19:40:36 +02:00
CONTRIBUTING.md Create CONTRIBUTING.md 2025-05-05 21:53:03 +02:00
docker-compose.yml Move DB_DATABASE to docker-compose 2025-05-11 18:25:17 +02:00
Dockerfile feat: show version number on welcome screen when logged in 2025-05-06 13:19:02 +02:00
example.png initial commit 2025-02-12 22:15:57 +01:00
LICENSE.md Create LICENSE.md 2025-02-12 22:35:04 +01:00
package-lock.json chore: update dependencies 2025-05-05 15:13:52 +02:00
package.json chore(deps): bump vite from 6.3.3 to 6.3.4 2025-05-01 08:34:43 +02:00
phpunit.xml feat: update to Laravel 12 starter kit (#1) 2025-02-25 12:15:35 +01:00
README.md chore: refresh Readme 2025-05-13 21:34:41 +02:00
README_byos-devices.jpeg chore: refresh Readme 2025-05-13 21:34:41 +02:00
README_byos-screenshot-dark.png chore: refresh Readme 2025-05-13 21:30:38 +02:00
README_byos-screenshot.png chore: refresh Readme 2025-05-13 21:30:38 +02:00
vite.config.js feat: update to Laravel 12 starter kit (#1) 2025-02-25 12:15:35 +01:00

TRMNL BYOS (PHP/Laravel)

tests

TRMNL BYOS Laravel is a self-hostable implementation of a TRMNL server, built with Laravel. It enables you to manage TRMNL devices, generate screens dynamically, and can act as a proxy for the native cloud service (native plugins, recipes).

If you are looking for a Laravel package designed to streamline the development of both public and private TRMNL plugins, check out bnussbau/trmnl-laravel.

Screenshot Screenshot

Key Features

  • 📡 Device Information Display battery status, WiFi strength, firmware version, and more.
  • 🔍 Auto-Join Automatically detects and adds devices from your local network.
  • 🖥️ Screen Generation Supports Plugins, Recipes, API, Markup, or updates via Code.
  • 🔄 TRMNL API Proxy Can act as a proxy for the native cloud service (requires TRMNL Developer Edition).
    • This enables a hybrid setup for example, you can update your custom Train Monitor every 5 minutes in the morning, while displaying native TRMNL plugins throughout the day.
  • 🌙 Dark Mode Switch between light and dark mode.
  • 🐳 Deployment Dockerized setup for easier hosting (Dockerfile, docker-compose).
  • 🛠️ Devcontainer support for easier development.

Devices

🎯 Target Audience

This project is for developers who are looking for a self-hosted server for devices running the TRMNL firmware. It serves as a starter kit, giving you the flexibility to build and extend it however you like.

Support ❤️

This repo is maintained voluntarily by @bnussbau.

Support the development of this package by purchasing a TRMNL device through the referral link: https://usetrmnl.com/?ref=laravel-trmnl. At checkout, use the code laravel-trmnl to receive a $15 discount on your purchase.

or

"Buy Me A Coffee"

Hosting

Run everywhere, where Docker is supported: Raspberry Pi, VPS, NAS, Container Cloud Service (Cloud Run, ...)

Docker Compose file located at: docker/prod/docker-compose.yml.

For production use, generate a new APP_KEY and set the environment variable APP_KEY=. For personal use, you can disable registration (see section Environment Variables).

Laravel Forge, or bare metal PHP server with Nginx or Apache is also supported.

Requirements

Local Development

see docs/DEVELOPMENT.md

Demo Plugins

Run the ExampleRecipesSeeder to seed the database with example plugins:

php artisan db:seed --class=ExampleRecipesSeeder
  • Zen Quotes
  • This Day in History
  • Weather
  • Train Departure Monitor
  • Home Assistant
  • Sunrise/Sunset

Usage

Environment Variables

Environment Variable Description Default
TRMNL_PROXY_BASE_URL Base URL of the native TRMNL service https://trmnl.app
TRMNL_PROXY_REFRESH_MINUTES How often should the server fetch new images from native service 15
REGISTRATION_ENABLED Allow user registration via Webinterface 1
FORCE_HTTPS If your server handles SSL termination, enforce HTTPS. 0
PHP_OPCACHE_ENABLE Enable PHP Opcache 0
TRMNL_IMAGE_URL_TIMEOUT How long TRMNL waits for a response on the display endpoint. (sec) 30

Login

If your environment is local, you can access the server at http://localhost:4567 and login with user / password admin@example.com / admin@example.com, otherwise register. With environment variable REGISTRATION_ENABLED you can control, if registration is allowed.

Add Your TRMNL Device

Auto-Join (Local Network)
  1. Switch on the “Permit Auto-Join” toggle in the header. For that to work only one user can be registered.
  2. New devices on your local network will be detected and added automatically when connecting to the server.

This is the easiest way to connect your devices with minimal effort.

Manually
  1. Open the Devices page: 👉 http://localhost:4567/devices
  2. Click “Add New Device”.
  3. Retrieve your TRMNL MAC Address and API Key:
  • You can grab the TRMNL Mac Address and API Key from the TRMNL Dashboard
  • Alternatively, debug incoming requests to /api/setup to determine them

⚙️ Configure Server for Device

📌 Firmware Version 1.4.6 or Newer

  • Setup device
  • After entering Wifi credentials, choose "Custom Server"
  • Point to the URL of your server

Firmware Older Than 1.4.6

If your device firmware is older than 1.4.6, you need to flash a new firmware version to point it to your server.

See this YouTube guide: https://www.youtube.com/watch?v=3xehPW-PCOM

☁️ Activate fresh TRMNL Device with Cloud Proxy

  1. Setup the TRMNL as in the official docs with the cloud service (connect one of the plugins to later verify it works)
  2. Setup Laravel BYOS, create a user and login
  3. In Laravel BYOS in the header bar, activate the toggle "Permit Auto-Join"
  4. Press and hold the button on the back of your TRMNL for 5 seconds to reactivate the captive portal (or reflash).
  5. Go through the setup process again, in the screen where you provide the Wi-Fi credentials there is also option to set the Server URL. Use the local address of your Laravel BYOS
  6. The device should automatically appear in the device list; you can deactivate the "Permit Auto-Join" toggle again.
  7. In the devices list, activate the toggle "☁️ Proxy" for your device. (Make sure that the queue worker is active. In the docker image it should be running automatically.)
  8. As long as no Laravel BYOS plugin is scheduled, the device will show your cloud plugins.
Troubleshooting

Make sure that your device has a Developer license, you should be able to verify by calling the https://trmnl.app/api/display endpoint.

🖥️ Generate Screens

Markup via Web Interface

  1. Navigate to Plugins > Markup in the Web Interface.
  2. Enter your markup manually or select from the available templates.
  3. Save and apply the changes.

🎨 Blade View

php artisan trmnl:screen:generate

Generate via API

You can dynamically update screens by sending a POST request.

  • Send a POST request to /api/screen with the following payload
Header

Authorization Bearer <TOKEN>

Body
{
    "markup": "<h1>Hello World</h1>"
}

🤝 Contribution

Contributions are welcome! See CONTRIBUTING.md for details.

License

MIT