Skip to content
This repository was archived by the owner on Feb 19, 2026. It is now read-only.
/ byos_sinatra Public archive

DIY server in Ruby/Sinatra for self hosted screen generation

License

Notifications You must be signed in to change notification settings

usetrmnl/byos_sinatra

Repository files navigation

TRMNL BYOS (Ruby/Sinatra)

‼️ This implementation has been eclipsed by our Terminus (BYOS Hanami) implementation which is our flagship implementation. Terminus is where all of our active development is and provides a lot more features. Please use Terminus instead. Thanks and enjoy!

This project allows you to point a TRMNL device to your own server which can be running on your local network or in the cloud.

Features

  • Allows you to run your own server.

  • Supports Puma.

  • Supports SQLite.

  • Supports Docker.

  • Supports TRMNL devices.

The following is a high level overview you can use to compare/contrast when deciding between using this BYOS implementation or our hosted solution.

Legend

  • ⚪️ Planned.

  • 🟢 Supported.

  • 🟡 Partially supported.

  • 🔴 Not supported, not implemented, or isn’t applicable.

Matrix

BYOS Hosted

Dashboard

⚪️

🟢

Plugins

⚪️

🟢

Playlists

🟡

🟢

Playlist Previews

⚪️

🟢

Recipes

🔴

🟢

Devices

🟢

🟢

Account

🔴

🟢

JSON Data API

🟢

🟢

Open Source Components

🟡

🟡

Docker

🟢

🔴

The goal isn’t for BYOS to match parity with our hosted solution but to provide enough of a pleasant solution for your own customized experience. There are trade offs either way but we’ve got you covered for whatever path you wish to travel. 🎉

Screencasts

Requirements

  1. Ruby.

  2. Docker (optional).

  3. A TRMNL device.

Setup

To set up project, run:

git clone https://github.com/usetrmnl/byos_sinatra
cd byos_sinatra
bin/setup

Usage

To launch the server, run:

bundle exec puma --config ./config/puma.rb

💡 Install Overmind and run overmind start to run with full access to all processes (including remote debugging).

APIs

The following APIs are supported:

## Display
curl "http://localhost:4567/api/display/" \
     -H 'ID: <redacted>' \
     -H 'Access-Token: <redacted>' \
     -H 'Accept: application/json' \
     -H 'Content-Type: application/json'

## Setup
curl "http://localhost:4567/api/setup/" \
    -H 'ID: <redacted>' \
    -H 'Access-Token: <redacted>' \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json'


## Logs
curl -X "POST" "http://localhost:4567/api/log" \
     -H 'ID: <redacted>' \
     -H 'Access-Token: <redacted>' \
     -H 'Accept: application/json' \
     -H 'Content-Type: application/json'

## Images
curl -X "POST" "http://localhost:4567/api/images" \
    -H 'ID: <redacted>' \
    -H 'Access-Token: <redacted>' \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json' \
    -d $'{
 "image": {
   "content": "<p>Test</p>"
   "file_name": "test"
 }
}'

💡 The images API supports full HTML so you can supply CSS styles, full DOM, etc. At a minimum, you’ll want to use the following to prevent white borders showing up around your generated images:

* {
  margin: 0;
}

If you don’t supply a file_name, the server will generate one for you using a UUID for the file name. You can find all generated images in public/images/generated.

💡 The ID is your device’s MAC and the Access-Token is your device API Key.

Development

To contribute, run:

git clone https://github.com/usetrmnl/byos_sinatra
cd byos_sinatra
bin/setup

You can also use the IRB console for direct access to all objects:

bin/console

Once in the console, you can then do the following:

# View all devices.
Device.all

# Fetch upcoming render, sorts in descending order by created timestamp.
Images::Fetcher.new.call

# To generate default image.
Images::Creator.new.call "<p>Test.</p>"
#<Pathname:byos_sinatra/public/images/generated/81673687-28b0-4a0c-8efc-ebb344b63cf9.bmp>

# To generate image with custom path.
Images::Creator.new.call "<p>Test.</p>", Pathname.pwd.join("test.bmp")
#<Pathname:byos_sinatra/test.bmp>

# To generate image with custom path and dynamic name.
Images::Creator.new.call "<p>Test.</p>", Pathname.pwd.join("%<name>s.bmp")
#<Pathname:byos_sinatra/c8e41972-c7bb-47d8-b927-ddcf50d20367.bmp>

When creating images, you might find this HTML template valuable as a starting point as this let’s you use the full capabilities of HTML to create new images for your device.

HTML Template
<!DOCTYPE html>

<html lang="en">
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">

    <title>Demo</title>

    <meta charset="utf-8">

    <style type="text/css">
      * {
        margin: 0;
      }
    </style>

    <script type="text/javascript">
    </script>
  </head>

  <body>
    <img src="uri/to/image" alt="Image"/>
  </body>
</html>

Use of margin zero is important to prevent default browser styles from creating borders around your image which will show up when rendered on your device. Otherwise, you have full capabilities to render any kind of page you want using whatever HTML you like. Anything is possible because Images::Creator is designed to screenshot your rendered HTML as a 800x480 image to render on your device. If you put all this together, that means you can do this in the console:

Console Image Generation
creator = Images::Creator.new

creator.call(<<~CONTENT, Pathname("public/images/generated/"%<name>s.bmp""))
  <!DOCTYPE html>

  <html lang="en">
    <head>
      <meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">

      <title>Demo</title>

      <meta charset="utf-8">

      <style type="text/css">
        * {
          margin: 0;
        }
      </style>

      <script type="text/javascript">
      </script>
    </head>

    <body>
      <h1>Hello, World!</h1>
    </body>
  </html>
CONTENT

The above will create a new image in the public/images/generated folder of this application which will eventually render on your device. 🎉

To build a Docker image, run:

bin/docker/build

To work within your Docker image, run:

bin/docker/console

Tests

To test, run:

bin/rake

Deployment

Local

  1. Configure APP_URL within .env to where your app is hosted (i.e. http://192.168.x.x:4567). 💡 Lack of trailing slash is important.

  2. Prefix RACK_ENV=production before launching the server to run in production mode.

  3. Retrieve your machine’s local IP, ex 192.168.x.x (Mac: ifconfig | grep "inet " | grep -Fv 127.0.0.1 | awk '{print $2}')

  4. Confirm the application works by visiting http://192.168.x.x:4567/devices from any device also on the network.

  5. Point your [forked firmware](https://github.com/usetrmnl/firmware) API_BASE_URL ([source](https://github.com/usetrmnl/firmware/blob/2ee0723c66a3468b969c83d7663ffb3f8322ad99/include/config.h#L56)) to same value as APP_URL.

Hosted

More details to be provided soon.

Credits

About

DIY server in Ruby/Sinatra for self hosted screen generation

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors