Public Access
Add HA dropdowns, off image, flip fixes and refresh effects
Home Assistant: remove the Clock switch (its entity is deleted on connect), add an IP sensor and Mode / Off image dropdowns whose options follow the saved bitmaps. Remove the built-in "Nintendo border" bitmap. Off image: a bitmap (or blank) left on the dots when the display switches off. Nano: pause after each flip pulse and repeat pulses for changed dots, both stored in EEPROM (G/N commands). Refresh effects (E command): instant, sparkle, wipe, rain, from the middle out, diagonal, spiral, flip all then reveal, push and random, optionally only for small changes. Effects run in steps from loop() so serial input keeps flowing. Web UI: flip settings, refresh effect and test patterns under Settings; the System tab shows why the ESP32 last restarted. Settings records from versions 1 and 2 are migrated. Firmware 2.3.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4a5f6a8b5d
commit
1d3b084de1
42 files changed
+1631
-230
No files matched your search
@@ -15,10 +15,10 @@ struct BuiltinBitmap
|
||||
|
||||
static const BuiltinBitmap BUILTIN[] = {
|
||||
{"Nintendo", nintendo_bitmap_Nintendo_new},
|
||||
{"Nintendo border", nintendo_bitmap_nintendo_border},
|
||||
};
|
||||
|
||||
static bool mounted = false;
|
||||
static uint32_t revision = 0;
|
||||
|
||||
static const BuiltinBitmap *builtin(const char *name)
|
||||
{
|
||||
@@ -77,6 +77,11 @@ std::vector<BitmapInfo> listBitmaps()
|
||||
return list;
|
||||
}
|
||||
|
||||
uint32_t bitmapListRevision()
|
||||
{
|
||||
return revision;
|
||||
}
|
||||
|
||||
bool bitmapExists(const char *name)
|
||||
{
|
||||
if (builtin(name))
|
||||
@@ -118,6 +123,7 @@ const char *saveBitmap(const char *name, const uint8_t *bitmap)
|
||||
return "Could not write the file";
|
||||
bool ok = file.write(bitmap, BITMAP_BYTES) == (size_t)BITMAP_BYTES;
|
||||
file.close();
|
||||
revision++;
|
||||
return ok ? nullptr : "Storage full";
|
||||
}
|
||||
|
||||
@@ -127,5 +133,8 @@ const char *deleteBitmap(const char *name)
|
||||
return "Built-in bitmaps can't be deleted";
|
||||
if (!mounted || !validBitmapName(name) || !LittleFS.exists(path(name)))
|
||||
return "Bitmap not found";
|
||||
return LittleFS.remove(path(name)) ? nullptr : "Could not delete the file";
|
||||
if (!LittleFS.remove(path(name)))
|
||||
return "Could not delete the file";
|
||||
revision++;
|
||||
return nullptr;
|
||||
}
|
||||
@@ -24,6 +24,8 @@ bool bitmapStorageAvailable();
|
||||
void bitmapStorageUsage(size_t &used, size_t &total);
|
||||
|
||||
std::vector<BitmapInfo> listBitmaps();
|
||||
// Changes each time a bitmap is saved or deleted
|
||||
uint32_t bitmapListRevision();
|
||||
bool bitmapExists(const char *name);
|
||||
bool loadBitmap(const char *name, uint8_t *bitmap);
|
||||
|
||||
|
||||
@@ -17,23 +17,6 @@ const unsigned char nintendo_bitmap_Nintendo_new[] PROGMEM = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc,
|
||||
0x00};
|
||||
|
||||
// 'nintendo border', 84x19px
|
||||
const unsigned char nintendo_bitmap_nintendo_border[] PROGMEM = {
|
||||
0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
0x80, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x60, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x60, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xc0, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x30, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xc0, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x30, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x60,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x60, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0,
|
||||
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc,
|
||||
0x00};
|
||||
|
||||
// 'panda', 17x18px
|
||||
const unsigned char nintendo_bitmap_panda_inverted[] PROGMEM = {
|
||||
0xfe, 0x7f, 0x80, 0xfc, 0x3c, 0x80, 0xfc, 0x38, 0x00, 0xdd, 0xfe, 0x00, 0xbb, 0xff, 0x80, 0x73,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "display/bitmap_store.h"
|
||||
#include "display/bitmaps.h"
|
||||
#include "display/text.h"
|
||||
|
||||
@@ -51,17 +50,9 @@ void FlipdotFrames::live()
|
||||
_drawFullScreen(_live);
|
||||
}
|
||||
|
||||
void FlipdotFrames::logo()
|
||||
void FlipdotFrames::off()
|
||||
{
|
||||
uint8_t logo[BITMAP_BYTES];
|
||||
if (loadBitmap("Nintendo", logo))
|
||||
_drawFullScreen(logo);
|
||||
}
|
||||
|
||||
void FlipdotFrames::blank()
|
||||
{
|
||||
_display.fillScreen(0);
|
||||
_display.display();
|
||||
_drawFullScreen(_off);
|
||||
}
|
||||
|
||||
void FlipdotFrames::setPageBitmap(const uint8_t *bitmap)
|
||||
@@ -74,6 +65,14 @@ void FlipdotFrames::setLiveBitmap(const uint8_t *bitmap)
|
||||
memcpy(_live, bitmap, BITMAP_BYTES);
|
||||
}
|
||||
|
||||
void FlipdotFrames::setOffBitmap(const uint8_t *bitmap)
|
||||
{
|
||||
if (bitmap)
|
||||
memcpy(_off, bitmap, BITMAP_BYTES);
|
||||
else
|
||||
memset(_off, 0, BITMAP_BYTES);
|
||||
}
|
||||
|
||||
void FlipdotFrames::_drawFullScreen(const uint8_t *bitmap)
|
||||
{
|
||||
_display.fillScreen(0);
|
||||
|
||||
@@ -18,17 +18,19 @@ public:
|
||||
void page() override;
|
||||
// The image set with setLiveBitmap()
|
||||
void live() override;
|
||||
// Nintendo logo
|
||||
void logo() override;
|
||||
void blank() override;
|
||||
// Blank, or the bitmap set with setOffBitmap()
|
||||
void off() override;
|
||||
|
||||
void setPageBitmap(const uint8_t *bitmap);
|
||||
void setLiveBitmap(const uint8_t *bitmap);
|
||||
// nullptr: blank
|
||||
void setOffBitmap(const uint8_t *bitmap);
|
||||
|
||||
private:
|
||||
BROSE9323 &_display;
|
||||
uint8_t _page[BITMAP_BYTES] = {};
|
||||
uint8_t _live[BITMAP_BYTES] = {};
|
||||
uint8_t _off[BITMAP_BYTES] = {}; // all zero: blank
|
||||
|
||||
void _drawFullScreen(const uint8_t *bitmap);
|
||||
};
|
||||
@@ -9,9 +9,27 @@ StatusScreen::StatusScreen(BROSE9323 &display, NanoLink &nano) : _display(displa
|
||||
{
|
||||
}
|
||||
|
||||
void StatusScreen::setFlipTime(uint16_t flip_time)
|
||||
void StatusScreen::setDrive(uint16_t flip_time, const DriveSettings &drive)
|
||||
{
|
||||
_flip_time = flip_time;
|
||||
_drive = drive;
|
||||
}
|
||||
|
||||
void StatusScreen::sendDrive()
|
||||
{
|
||||
// Not sent right away: the Nano could be in its bootloader (flashing)
|
||||
_drive_pending = true;
|
||||
}
|
||||
|
||||
void StatusScreen::loop()
|
||||
{
|
||||
if (!_drive_pending || !_started || !_nano.isOn())
|
||||
return;
|
||||
_drive_pending = false;
|
||||
_display.setTiming(_flip_time);
|
||||
_display.setGap(_drive.flip_gap);
|
||||
_display.setPulses(_drive.pulses);
|
||||
_display.setEffect(_drive.effect, _drive.effect_time, _drive.effect_small_only ? EFFECT_FLAG_SMALL_ONLY : 0);
|
||||
}
|
||||
|
||||
void StatusScreen::show(const char *line1, const char *line2, bool keep_on)
|
||||
@@ -21,8 +39,9 @@ void StatusScreen::show(const char *line1, const char *line2, bool keep_on)
|
||||
if (!_started)
|
||||
{
|
||||
_display.begin(&_nano.stream());
|
||||
_display.setTiming(_flip_time);
|
||||
_started = true;
|
||||
_drive_pending = true;
|
||||
loop();
|
||||
}
|
||||
drawTextLines(_display, line1, line2);
|
||||
_display.display();
|
||||
@@ -30,7 +49,8 @@ void StatusScreen::show(const char *line1, const char *line2, bool keep_on)
|
||||
if (!keep_on)
|
||||
{
|
||||
// The first frame after power on flips every dot, give the Nano time for that
|
||||
delay(STATUS_FLIP_TIME);
|
||||
// (a push can take longer, but these messages are short: at worst it is cut off)
|
||||
delay(STATUS_FLIP_TIME + (_drive.effect == DRIVE_EFFECT_INSTANT ? 0 : _drive.effect_time));
|
||||
_nano.off();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "BROSE9323.h"
|
||||
#include "logic/settings.h"
|
||||
#include "nano_link/nano_link.h"
|
||||
|
||||
// Short messages for situations outside the normal clock: booting, crashes, recovery app.
|
||||
@@ -14,12 +15,19 @@ public:
|
||||
// power off again once the dots have flipped: flipdots keep their image without power.
|
||||
void show(const char *line1, const char *line2, bool keep_on = false);
|
||||
|
||||
// Flip time sent to the Nano with the first message (default FLIP_TIME)
|
||||
void setFlipTime(uint16_t flip_time);
|
||||
// How the Nano drives the dots, sent with the first message (default: FLIP_TIME and
|
||||
// the DriveSettings defaults, so the recovery app always shows its messages instantly)
|
||||
void setDrive(uint16_t flip_time, const DriveSettings &drive);
|
||||
// Send them again (they changed), from loop()
|
||||
void sendDrive();
|
||||
// Sends changed drive settings once the display is on. Not while the Nano is flashed.
|
||||
void loop();
|
||||
|
||||
private:
|
||||
BROSE9323 &_display;
|
||||
NanoLink &_nano;
|
||||
bool _started = false;
|
||||
bool _drive_pending = false;
|
||||
uint16_t _flip_time;
|
||||
DriveSettings _drive;
|
||||
};
|
||||
Reference in new issue
Block a user