2026-10-13 00:56:32 +02:00
|
|
|
#include "display/frames.h"
|
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
2026-10-13 01:52:34 +02:00
|
|
|
#include <string.h>
|
2026-10-13 00:56:32 +02:00
|
|
|
|
2026-10-15 21:01:14 +02:00
|
|
|
#include "config.h"
|
2026-10-13 01:34:04 +02:00
|
|
|
#include "display/text.h"
|
2026-10-13 00:56:32 +02:00
|
|
|
|
2026-10-20 17:52:25 +02:00
|
|
|
FlipdotFrames::FlipdotFrames(BROSE9323 &display, PageResources &resources, LifePlayer &life)
|
|
|
|
|
: _display(display), _resources(resources), _life(life)
|
2026-10-13 00:56:32 +02:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-15 21:14:48 +02:00
|
|
|
// Only the dots that changed are sent (unless redraw: the Nano's image is unknown)
|
2026-10-15 21:01:14 +02:00
|
|
|
uint32_t FlipdotFrames::dynamicPage(const LocalTime &time, uint32_t now, bool redraw)
|
2026-10-13 00:56:32 +02:00
|
|
|
{
|
2026-10-15 19:43:59 +02:00
|
|
|
uint8_t bitmap[BITMAP_BYTES];
|
2026-10-20 17:52:25 +02:00
|
|
|
_life.tick(now);
|
2026-10-15 21:01:14 +02:00
|
|
|
uint32_t wait = renderPage(_dynamic, time, now, _resources, bitmap);
|
|
|
|
|
bool moving = wait != 0;
|
2026-10-15 21:14:48 +02:00
|
|
|
// The Nano has no acknowledgement: the next frame waits until it has received this one
|
|
|
|
|
// and flipped its dots, or the next frame overflows its serial buffer
|
2026-10-17 16:57:31 +02:00
|
|
|
_useEffect(_page_effect, redraw);
|
2026-10-15 21:36:56 +02:00
|
|
|
uint32_t busy = _show(bitmap, !redraw, moving || _instant_next);
|
|
|
|
|
_instant_next = false;
|
2026-10-15 21:01:14 +02:00
|
|
|
return moving && wait < busy ? busy : wait;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FlipdotFrames::setDrive(uint16_t flip_time, const DriveSettings &drive)
|
|
|
|
|
{
|
|
|
|
|
_dot_us = (uint32_t)drive.pulses * (flip_time + drive.flip_gap);
|
2026-10-17 16:57:31 +02:00
|
|
|
_drive = drive;
|
|
|
|
|
_nano_effect = EFFECT_UNKNOWN;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FlipdotFrames::_useEffect(uint8_t effect, bool full)
|
|
|
|
|
{
|
|
|
|
|
if (effect == PAGE_EFFECT_DEFAULT || effect >= DRIVE_EFFECT_COUNT)
|
|
|
|
|
effect = _drive.effect;
|
|
|
|
|
if (effect == _nano_effect && !full)
|
|
|
|
|
return;
|
|
|
|
|
uint8_t flags = EFFECT_FLAG_NO_SAVE | (_drive.effect_small_only ? EFFECT_FLAG_SMALL_ONLY : 0);
|
|
|
|
|
_display.setEffect(effect, _drive.effect_time, flags);
|
|
|
|
|
_nano_effect = effect;
|
2026-10-15 21:01:14 +02:00
|
|
|
}
|
|
|
|
|
|
2026-10-15 21:14:48 +02:00
|
|
|
// Sends the bitmap; returns about how long the Nano takes to receive and show it in ms
|
|
|
|
|
uint32_t FlipdotFrames::_show(const uint8_t *bitmap, bool changes_only, bool instant)
|
2026-10-15 21:01:14 +02:00
|
|
|
{
|
|
|
|
|
uint32_t dots = 0;
|
|
|
|
|
for (int y = 0; y < BITMAP_HEIGHT; y++)
|
|
|
|
|
for (int x = 0; x < BITMAP_WIDTH; x++)
|
2026-10-15 21:14:48 +02:00
|
|
|
dots += bitmapPixel(bitmap, x, y) != _display.shownPixel(x, y);
|
|
|
|
|
size_t chars = _display.showBitmap(bitmap, changes_only, instant);
|
|
|
|
|
if (chars == 0)
|
|
|
|
|
return 0;
|
|
|
|
|
return chars / NANO_CHARS_PER_MS + dots * _dot_us / 1000 + FRAME_MARGIN;
|
2026-10-13 00:56:32 +02:00
|
|
|
}
|
|
|
|
|
|
2026-10-13 01:34:04 +02:00
|
|
|
void FlipdotFrames::noWifi(int hour, int minute)
|
|
|
|
|
{
|
|
|
|
|
char time[6];
|
|
|
|
|
snprintf(time, sizeof(time), "%02d:%02d", hour, minute);
|
2026-10-17 16:57:31 +02:00
|
|
|
_useEffect(PAGE_EFFECT_DEFAULT, false);
|
2026-10-13 01:34:04 +02:00
|
|
|
drawTextLines(_display, "No WiFi", time);
|
|
|
|
|
_display.display();
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-13 01:52:34 +02:00
|
|
|
void FlipdotFrames::page()
|
|
|
|
|
{
|
2026-10-17 16:57:31 +02:00
|
|
|
_useEffect(_page_effect, true);
|
2026-10-15 21:36:56 +02:00
|
|
|
_show(_page, false, _instant_next);
|
|
|
|
|
_instant_next = false;
|
2026-10-13 01:52:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FlipdotFrames::live()
|
|
|
|
|
{
|
|
|
|
|
_drawFullScreen(_live);
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-15 01:29:01 +02:00
|
|
|
void FlipdotFrames::off()
|
2026-10-13 00:56:32 +02:00
|
|
|
{
|
2026-10-15 01:29:01 +02:00
|
|
|
_drawFullScreen(_off);
|
2026-10-13 00:56:32 +02:00
|
|
|
}
|
2026-10-13 01:52:34 +02:00
|
|
|
|
2026-10-15 19:43:59 +02:00
|
|
|
void FlipdotFrames::setDynamicPage(const DynamicPage &page)
|
|
|
|
|
{
|
|
|
|
|
_dynamic = page;
|
2026-10-20 17:52:25 +02:00
|
|
|
_life.setPage(page);
|
2026-10-15 19:43:59 +02:00
|
|
|
}
|
|
|
|
|
|
2026-10-13 01:52:34 +02:00
|
|
|
void FlipdotFrames::setPageBitmap(const uint8_t *bitmap)
|
|
|
|
|
{
|
|
|
|
|
memcpy(_page, bitmap, BITMAP_BYTES);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FlipdotFrames::setLiveBitmap(const uint8_t *bitmap)
|
|
|
|
|
{
|
|
|
|
|
memcpy(_live, bitmap, BITMAP_BYTES);
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-15 01:29:01 +02:00
|
|
|
void FlipdotFrames::setOffBitmap(const uint8_t *bitmap)
|
|
|
|
|
{
|
|
|
|
|
if (bitmap)
|
|
|
|
|
memcpy(_off, bitmap, BITMAP_BYTES);
|
|
|
|
|
else
|
|
|
|
|
memset(_off, 0, BITMAP_BYTES);
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-15 21:14:48 +02:00
|
|
|
void FlipdotFrames::_drawFullScreen(const uint8_t *bitmap)
|
2026-10-13 01:52:34 +02:00
|
|
|
{
|
2026-10-17 16:57:31 +02:00
|
|
|
_useEffect(PAGE_EFFECT_DEFAULT, true);
|
2026-10-15 21:14:48 +02:00
|
|
|
_show(bitmap, false, false);
|
2026-10-13 01:52:34 +02:00
|
|
|
}
|