release
This commit is contained in:
222
src/AudioManager.cpp
Normal file
222
src/AudioManager.cpp
Normal file
@@ -0,0 +1,222 @@
|
||||
#include <AudioManager.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <Preferences.h>
|
||||
#include <LittleFS.h>
|
||||
#include "Audio.h"
|
||||
|
||||
Preferences sender;
|
||||
|
||||
#define LRCLK 18
|
||||
#define BCLK 17
|
||||
#define DOUT 16
|
||||
|
||||
const char *StreamTitle;
|
||||
|
||||
Audio audio;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char url[150];
|
||||
char name[32];
|
||||
uint8_t enabled;
|
||||
} Station;
|
||||
|
||||
#define STATIONS 30
|
||||
|
||||
Station stationlist[STATIONS];
|
||||
#define DEFAULTSTATIONS 24
|
||||
|
||||
AudioManager_ &AudioManager_::getInstance()
|
||||
{
|
||||
static AudioManager_ instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
AudioManager_ &AudioManager = AudioManager.getInstance();
|
||||
|
||||
void loadStations()
|
||||
{
|
||||
// Open the JSON file
|
||||
File file = LittleFS.open("/stations.json", "r");
|
||||
|
||||
if (!file)
|
||||
{
|
||||
Serial.println("Failed to open stations.json file");
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the JSON data from the file
|
||||
DynamicJsonDocument doc(2048);
|
||||
DeserializationError error = deserializeJson(doc, file);
|
||||
|
||||
if (error)
|
||||
{
|
||||
Serial.println("Failed to parse stations.json file");
|
||||
file.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the array of stations from the JSON data
|
||||
JsonArray stations = doc["stations"];
|
||||
|
||||
// Save each station in the array to the stationlist array
|
||||
int i = 0;
|
||||
for (JsonObject station : stations)
|
||||
{
|
||||
const char *url = station["url"];
|
||||
const char *name = station["name"];
|
||||
uint8_t enabled = station["enabled"];
|
||||
strncpy(stationlist[i].url, url, sizeof(stationlist[i].url) - 1);
|
||||
strncpy(stationlist[i].name, name, sizeof(stationlist[i].name) - 1);
|
||||
stationlist[i].enabled = enabled;
|
||||
i++;
|
||||
if (i >= STATIONS)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
void AudioManager_::setup()
|
||||
{
|
||||
audio.setPinout(BCLK, LRCLK, DOUT); // 0...21
|
||||
// audio.connecttohost("http://mp3.ffh.de/radioffh/hqlivestream.mp3"); // 128k mp3
|
||||
audio.setVolume(14);
|
||||
loadStations();
|
||||
}
|
||||
|
||||
void AudioManager_::tick()
|
||||
{
|
||||
audio.loop();
|
||||
isRunning = audio.isRunning();
|
||||
}
|
||||
|
||||
void AudioManager_::increaseVol()
|
||||
{
|
||||
if (curGain < 21)
|
||||
{
|
||||
curGain++;
|
||||
}
|
||||
audio.setVolume(curGain);
|
||||
}
|
||||
|
||||
void AudioManager_::decreaseVol()
|
||||
{
|
||||
if (curGain > 0)
|
||||
{
|
||||
curGain--;
|
||||
}
|
||||
audio.setVolume(curGain);
|
||||
}
|
||||
|
||||
void AudioManager_::playText(String text)
|
||||
{
|
||||
audio.connecttospeech(text.c_str(), "de");
|
||||
}
|
||||
|
||||
void audio_showstation(const char *info)
|
||||
{
|
||||
Serial.print("station ");
|
||||
Serial.println(info);
|
||||
}
|
||||
|
||||
void audio_showstreamtitle(const char *info)
|
||||
{
|
||||
Serial.print("Streamtitle ");
|
||||
Serial.println(info);
|
||||
AudioManager_::getInstance().StreamInfo = String(info);
|
||||
}
|
||||
|
||||
String AudioManager_::getNextRadioStation(bool playPrevious)
|
||||
{
|
||||
static int currentStation = 0; // Speichere die Indexnummer der letzten gespielten Station
|
||||
int startStation = currentStation + 1; // Starte mit der nächsten Station
|
||||
|
||||
if (playPrevious)
|
||||
{
|
||||
// Wenn die vorherige Station aktiviert ist, aktualisiere die Indexnummer
|
||||
int prevStation = currentStation - 1;
|
||||
if (prevStation >= 0 && stationlist[prevStation].enabled)
|
||||
{
|
||||
currentStation = prevStation;
|
||||
return String(stationlist[currentStation].name);
|
||||
}
|
||||
}
|
||||
|
||||
// Durchlaufe die Liste von Stationen, beginnend mit der nächsten Station
|
||||
for (int i = startStation; i < STATIONS; i++)
|
||||
{
|
||||
// Wenn die Station aktiviert ist, aktualisiere die Indexnummer und gib den Namen zurück
|
||||
if (stationlist[i].enabled)
|
||||
{
|
||||
currentStation = i;
|
||||
return String(stationlist[currentStation].name);
|
||||
}
|
||||
}
|
||||
|
||||
// Wenn keine Station aktiviert ist, beginne wieder von vorne
|
||||
for (int i = 0; i <= currentStation; i++)
|
||||
{
|
||||
// Wenn die Station aktiviert ist, aktualisiere die Indexnummer und gib den Namen zurück
|
||||
if (stationlist[i].enabled)
|
||||
{
|
||||
currentStation = i;
|
||||
return String(stationlist[currentStation].name);
|
||||
}
|
||||
}
|
||||
|
||||
// Wenn keine Station aktiviert ist, gebe einen leeren String zurück
|
||||
return "";
|
||||
}
|
||||
|
||||
void AudioManager_::startRadioStation(String stationName)
|
||||
{
|
||||
// Durchlaufe die Liste von Stationen, um die URL der Station mit dem gegebenen Namen zu finden
|
||||
for (int i = 0; i < STATIONS; i++)
|
||||
{
|
||||
if (stationlist[i].enabled && String(stationlist[i].name) == stationName)
|
||||
{
|
||||
audio.connecttohost(stationlist[i].url);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String AudioManager_::getCurrentRadioStation()
|
||||
{
|
||||
int currentStation = stationIndex;
|
||||
if (currentStation >= 0 && currentStation < STATIONS && stationlist[currentStation].enabled)
|
||||
{
|
||||
return String(stationlist[currentStation].name);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void AudioManager_::prevStation()
|
||||
{
|
||||
do
|
||||
{
|
||||
stationIndex--;
|
||||
if (stationIndex < 0)
|
||||
{
|
||||
stationIndex = STATIONS - 1; // Wrap around to the last station
|
||||
}
|
||||
} while (!stationlist[stationIndex].enabled);
|
||||
}
|
||||
|
||||
void AudioManager_::nextStation()
|
||||
{
|
||||
do
|
||||
{
|
||||
stationIndex++;
|
||||
if (stationIndex >= STATIONS)
|
||||
{
|
||||
stationIndex = 0; // Wrap around to the first station
|
||||
}
|
||||
} while (!stationlist[stationIndex].enabled);
|
||||
}
|
||||
|
||||
void AudioManager_::stopPlay(){
|
||||
audio.stopSong();
|
||||
}
|
||||
35
src/AudioManager.h
Normal file
35
src/AudioManager.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef AudioManager_h
|
||||
#define AudioManager_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <MqttManager.h>
|
||||
|
||||
class AudioManager_
|
||||
{
|
||||
private:
|
||||
// The pins the buttons are connected to
|
||||
AudioManager_() = default;
|
||||
|
||||
public:
|
||||
static AudioManager_ &getInstance();
|
||||
void setup();
|
||||
void tick();
|
||||
uint8_t curGain = 15;
|
||||
String StreamInfo = "";
|
||||
bool isRunning;
|
||||
void increaseVol();
|
||||
void decreaseVol();
|
||||
void setGain(uint8_t);
|
||||
void playText(String text);
|
||||
String getNextRadioStation(bool playPrevious);
|
||||
void startRadioStation(String stationName);
|
||||
String getCurrentRadioStation();
|
||||
void prevStation();
|
||||
void nextStation();
|
||||
void stopPlay();
|
||||
int stationIndex;
|
||||
};
|
||||
|
||||
extern AudioManager_ &AudioManager;
|
||||
|
||||
#endif
|
||||
526
src/DisplayManager.cpp
Normal file
526
src/DisplayManager.cpp
Normal file
@@ -0,0 +1,526 @@
|
||||
#include <DisplayManager.h>
|
||||
#include <FastLED_NeoMatrix.h>
|
||||
#include "MatrixDisplayUi.h"
|
||||
#include <TJpg_Decoder.h>
|
||||
#include "icons.h"
|
||||
#include "Settings.h"
|
||||
#include <ArduinoJson.h>
|
||||
#include "PeripheryManager.h"
|
||||
#include "MQTTManager.h"
|
||||
#include "GifPlayer.h"
|
||||
#include "AudioManager.h"
|
||||
#include <Ticker.h>
|
||||
#include "Functions.h"
|
||||
#include "ServerManager.h"
|
||||
#include "MenuManager.h"
|
||||
#include "Frames.h"
|
||||
|
||||
Ticker AlarmTicker;
|
||||
Ticker TimerTicker;
|
||||
|
||||
#define MATRIX_PIN 32
|
||||
#define MATRIX_WIDTH 32
|
||||
#define MATRIX_HEIGHT 8
|
||||
|
||||
bool appIsSwitching;
|
||||
|
||||
GifPlayer gif;
|
||||
|
||||
CRGB leds[MATRIX_WIDTH * MATRIX_HEIGHT];
|
||||
FastLED_NeoMatrix matrix(leds, 32, 8, NEO_MATRIX_TOP + NEO_MATRIX_LEFT + NEO_MATRIX_ROWS + NEO_MATRIX_ZIGZAG);
|
||||
|
||||
MatrixDisplayUi ui(&matrix);
|
||||
|
||||
DisplayManager_ &DisplayManager_::getInstance()
|
||||
{
|
||||
static DisplayManager_ instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
DisplayManager_ &DisplayManager = DisplayManager.getInstance();
|
||||
|
||||
void DisplayManager_::setBrightness(uint8_t bri)
|
||||
{
|
||||
if (MATRIX_OFF && !ALARM_ACTIVE)
|
||||
{
|
||||
matrix.setBrightness(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
matrix.setBrightness(bri);
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayManager_::setFPS(uint8_t fps)
|
||||
{
|
||||
ui.setTargetFPS(fps);
|
||||
}
|
||||
|
||||
void DisplayManager_::setTextColor(uint16_t color)
|
||||
{
|
||||
matrix.setTextColor(color);
|
||||
}
|
||||
|
||||
void DisplayManager_::MatrixState(bool on)
|
||||
{
|
||||
MATRIX_OFF = !on;
|
||||
setBrightness(BRIGHTNESS);
|
||||
}
|
||||
|
||||
void DisplayManager_::disableAutoTransition()
|
||||
{
|
||||
ui.disableAutoTransition();
|
||||
}
|
||||
|
||||
void DisplayManager_::enableAutoTransition()
|
||||
{
|
||||
ui.enableAutoTransition();
|
||||
}
|
||||
|
||||
void DisplayManager_::drawGIF(uint16_t x, uint16_t y, fs::File gifFile)
|
||||
{
|
||||
gif.setFile(gifFile);
|
||||
gif.drawFrame(x, y);
|
||||
}
|
||||
|
||||
void DisplayManager_::drawJPG(uint16_t x, uint16_t y, fs::File jpgFile)
|
||||
{
|
||||
TJpgDec.drawFsJpg(x, y, jpgFile);
|
||||
}
|
||||
|
||||
void DisplayManager_::setSettings()
|
||||
{
|
||||
ui.setTargetFPS(MATRIX_FPS);
|
||||
ui.setTimePerApp(TIME_PER_FRAME);
|
||||
ui.setTimePerTransition(TIME_PER_TRANSITION);
|
||||
}
|
||||
|
||||
void DisplayManager_::resetTextColor()
|
||||
{
|
||||
matrix.setTextColor(TEXTCOLOR_565);
|
||||
}
|
||||
|
||||
void DisplayManager_::clearMatrix()
|
||||
{
|
||||
matrix.clear();
|
||||
matrix.show();
|
||||
}
|
||||
|
||||
bool jpg_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap)
|
||||
{
|
||||
uint16_t bitmapIndex = 0;
|
||||
|
||||
for (uint16_t row = 0; row < h; row++)
|
||||
{
|
||||
for (uint16_t col = 0; col < w; col++)
|
||||
{
|
||||
uint16_t color = bitmap[bitmapIndex++];
|
||||
uint8_t r = ((color & 0xF800) >> 11) << 3;
|
||||
uint8_t g = ((color & 0x07E0) >> 5) << 2;
|
||||
uint8_t b = (color & 0x001F) << 3;
|
||||
matrix.drawPixel(x + col, y + row, matrix.Color(r, g, b));
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DisplayManager_::printText(int16_t x, int16_t y, const char *text, bool centered)
|
||||
{
|
||||
if (centered)
|
||||
{
|
||||
uint16_t textWidth = getTextWidth(text);
|
||||
int16_t textX = ((32 - textWidth) / 2);
|
||||
matrix.setCursor(textX, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
matrix.setCursor(x, y);
|
||||
}
|
||||
|
||||
if (UPPERCASE_LETTERS)
|
||||
{
|
||||
size_t length = strlen(text);
|
||||
char upperText[length + 1]; // +1 for the null terminator
|
||||
|
||||
for (size_t i = 0; i < length; ++i)
|
||||
{
|
||||
upperText[i] = toupper(text[i]);
|
||||
}
|
||||
|
||||
upperText[length] = '\0'; // Null terminator
|
||||
matrix.print(upperText);
|
||||
}
|
||||
else
|
||||
{
|
||||
matrix.print(text);
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayManager_::HSVtext(int16_t x, int16_t y, const char *text, bool clear)
|
||||
{
|
||||
if (clear)
|
||||
matrix.clear();
|
||||
static uint8_t hueOffset = 0;
|
||||
uint16_t xpos = 0;
|
||||
for (uint16_t i = 0; i < strlen(text); i++)
|
||||
{
|
||||
uint8_t hue = map(i, 0, strlen(text), 0, 255) + hueOffset;
|
||||
uint32_t textColor = hsvToRgb(hue, 255, 255);
|
||||
matrix.setTextColor(textColor);
|
||||
const char *myChar = &text[i];
|
||||
|
||||
matrix.setCursor(xpos + x, y);
|
||||
if (UPPERCASE_LETTERS)
|
||||
{
|
||||
matrix.print((char)toupper(text[i]));
|
||||
}
|
||||
else
|
||||
{
|
||||
matrix.print(&text[i]);
|
||||
}
|
||||
char temp_str[2] = {'\0', '\0'};
|
||||
temp_str[0] = text[i];
|
||||
xpos += getTextWidth(temp_str);
|
||||
}
|
||||
hueOffset++;
|
||||
if (clear)
|
||||
matrix.show();
|
||||
}
|
||||
|
||||
void pushCustomFrame(uint16_t id)
|
||||
{
|
||||
if (customFrames.count(id) == 0)
|
||||
{
|
||||
uint16_t newID = nativeAppsCount + id;
|
||||
switch (id)
|
||||
{
|
||||
case 1:
|
||||
Apps.push_back(std::make_pair(newID, CFrame1));
|
||||
break;
|
||||
case 2:
|
||||
Apps.push_back(std::make_pair(newID, CFrame2));
|
||||
break;
|
||||
case 3:
|
||||
Apps.push_back(std::make_pair(newID, CFrame3));
|
||||
break;
|
||||
case 4:
|
||||
Apps.push_back(std::make_pair(newID, CFrame4));
|
||||
break;
|
||||
case 5:
|
||||
Apps.push_back(std::make_pair(newID, CFrame5));
|
||||
break;
|
||||
case 6:
|
||||
Apps.push_back(std::make_pair(newID, CFrame6));
|
||||
break;
|
||||
case 7:
|
||||
Apps.push_back(std::make_pair(newID, CFrame7));
|
||||
break;
|
||||
case 8:
|
||||
Apps.push_back(std::make_pair(newID, CFrame8));
|
||||
break;
|
||||
case 9:
|
||||
Apps.push_back(std::make_pair(newID, CFrame9));
|
||||
break;
|
||||
case 10:
|
||||
Apps.push_back(std::make_pair(newID, CFrame10));
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
break;
|
||||
}
|
||||
ui.setApps(Apps); // Add frames
|
||||
}
|
||||
}
|
||||
|
||||
void removeCustomFrame(uint16_t id)
|
||||
{
|
||||
id += nativeAppsCount;
|
||||
// Suchen Sie nach dem Element, das der ID entspricht
|
||||
auto it = std::find_if(Apps.begin(), Apps.end(), [id](const std::pair<uint16_t, AppCallback> &appPair)
|
||||
{ return appPair.first == id; });
|
||||
|
||||
// Wenn das Element gefunden wurde, entfernen Sie es aus dem Vektor
|
||||
if (it != Apps.end())
|
||||
{
|
||||
Apps.erase(it);
|
||||
ui.setApps(Apps); // Aktualisieren Sie die Frames
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayManager_::generateCustomPage(uint16_t id, String payload)
|
||||
{
|
||||
|
||||
if (payload == "" && customFrames.count(id))
|
||||
{
|
||||
customFrames.erase(customFrames.find(id));
|
||||
removeCustomFrame(id);
|
||||
return;
|
||||
}
|
||||
|
||||
DynamicJsonDocument doc(1024);
|
||||
DeserializationError error = deserializeJson(doc, payload);
|
||||
if (error)
|
||||
return;
|
||||
|
||||
CustomFrame customFrame;
|
||||
|
||||
if (id == 0)
|
||||
{
|
||||
if (doc.containsKey("id"))
|
||||
{
|
||||
customFrame.id = doc["id"].as<uint8_t>();
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (id > 10)
|
||||
return;
|
||||
|
||||
if (doc.containsKey("sound"))
|
||||
{
|
||||
customFrame.sound = ("/" + doc["sound"].as<String>() + ".txt");
|
||||
}
|
||||
else
|
||||
{
|
||||
customFrame.sound = "";
|
||||
}
|
||||
|
||||
if (doc.containsKey("name"))
|
||||
{
|
||||
customFrame.name = doc["name"].as<String>();
|
||||
}
|
||||
else
|
||||
{
|
||||
customFrame.name = "Custom " + String(id);
|
||||
}
|
||||
|
||||
customFrame.rainbow = doc.containsKey("rainbow") ? doc["rainbow"] : false;
|
||||
customFrame.pushIcon = doc.containsKey("pushIcon") ? doc["pushIcon"] : 0;
|
||||
customFrame.id = id;
|
||||
customFrame.text = utf8ascii(doc["text"].as<String>());
|
||||
|
||||
customFrame.color = doc.containsKey("color") ? doc["color"].is<String>() ? hexToRgb565(doc["color"]) : doc["color"].is<JsonArray>() ? hexToRgb565(doc["color"].as<String>())
|
||||
: TEXTCOLOR_565
|
||||
: TEXTCOLOR_565;
|
||||
|
||||
if (currentCustomFrame != id)
|
||||
{
|
||||
customFrame.scrollposition = 34;
|
||||
}
|
||||
|
||||
customFrame.repeat = doc.containsKey("repeat") ? doc["repeat"].as<uint8_t>() : -1;
|
||||
|
||||
if (doc.containsKey("icon"))
|
||||
{
|
||||
String iconFileName = String(doc["icon"].as<String>());
|
||||
|
||||
if (LittleFS.exists("/ICONS/" + iconFileName + ".jpg"))
|
||||
{
|
||||
customFrame.isGif = false;
|
||||
customFrame.icon = LittleFS.open("/ICONS/" + iconFileName + ".jpg");
|
||||
}
|
||||
else if (LittleFS.exists("/ICONS/" + iconFileName + ".gif"))
|
||||
{
|
||||
customFrame.isGif = true;
|
||||
customFrame.icon = LittleFS.open("/ICONS/" + iconFileName + ".gif");
|
||||
}
|
||||
}
|
||||
|
||||
pushCustomFrame(id);
|
||||
customFrames[id] = customFrame;
|
||||
}
|
||||
|
||||
void DisplayManager_::generateNotification(String payload)
|
||||
{
|
||||
StaticJsonDocument<128> doc;
|
||||
deserializeJson(doc, payload);
|
||||
|
||||
notify.duration = doc.containsKey("duration") ? doc["duration"].as<int>() * 1000 : TIME_PER_FRAME;
|
||||
notify.text = utf8ascii(doc["text"].as<String>());
|
||||
notify.repeat = doc.containsKey("repeat") ? doc["repeat"].as<uint16_t>() : -1;
|
||||
notify.rainbow = doc.containsKey("rainbow") ? doc["rainbow"].as<bool>() : false;
|
||||
notify.hold = doc.containsKey("hold") ? doc["hold"].as<bool>() : false;
|
||||
notify.pushIcon = doc.containsKey("pushIcon") ? doc["pushIcon"] : 0;
|
||||
notify.flag = true;
|
||||
notify.startime = millis();
|
||||
notify.scrollposition = 34;
|
||||
notify.iconWasPushed = false;
|
||||
notify.iconPosition = 0;
|
||||
|
||||
if (doc.containsKey("sound"))
|
||||
{
|
||||
PeripheryManager.playFromFile("/MELODIES/" + doc["sound"].as<String>() + ".txt");
|
||||
}
|
||||
|
||||
notify.color = doc.containsKey("color") ? doc["color"].is<String>() ? hexToRgb565(doc["color"]) : doc["color"].is<JsonArray>() ? hexToRgb565(doc["color"].as<String>())
|
||||
: TEXTCOLOR_565
|
||||
: TEXTCOLOR_565;
|
||||
|
||||
if (doc.containsKey("icon"))
|
||||
{
|
||||
String iconFileName = doc["icon"].as<String>();
|
||||
if (LittleFS.exists("/ICONS/" + iconFileName + ".jpg"))
|
||||
{
|
||||
notify.isGif = false;
|
||||
notify.icon = LittleFS.open("/ICONS/" + iconFileName + ".jpg");
|
||||
}
|
||||
else if (LittleFS.exists("/ICONS/" + iconFileName + ".gif"))
|
||||
{
|
||||
notify.isGif = true;
|
||||
notify.icon = LittleFS.open("/ICONS/" + iconFileName + ".gif");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
File f;
|
||||
notify.icon = f;
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayManager_::loadApps()
|
||||
{
|
||||
Apps.clear();
|
||||
Apps.push_back(std::make_pair(0, TimeFrame));
|
||||
if (SHOW_DATE)
|
||||
Apps.push_back(std::make_pair(1, DateFrame));
|
||||
if (SHOW_TEMP)
|
||||
Apps.push_back(std::make_pair(2, TempFrame));
|
||||
if (SHOW_HUM)
|
||||
Apps.push_back(std::make_pair(3, HumFrame));
|
||||
if (SHOW_BATTERY)
|
||||
Apps.push_back(std::make_pair(4, BatFrame));
|
||||
// if (SHOW_WEATHER)
|
||||
// Apps.push_back(std::make_pair(5, WeatherFrame));
|
||||
nativeAppsCount = Apps.size();
|
||||
ui.setApps(Apps); // Add frames
|
||||
StartAppUpdater();
|
||||
}
|
||||
|
||||
void DisplayManager_::setup()
|
||||
{
|
||||
TJpgDec.setCallback(jpg_output);
|
||||
FastLED.addLeds<NEOPIXEL, MATRIX_PIN>(leds, MATRIX_WIDTH * MATRIX_HEIGHT);
|
||||
gif.setMatrix(&matrix);
|
||||
ui.setAppAnimation(SLIDE_DOWN);
|
||||
ui.setOverlays(overlays, 4);
|
||||
ui.init();
|
||||
}
|
||||
|
||||
void DisplayManager_::tick()
|
||||
{
|
||||
if (AP_MODE)
|
||||
{
|
||||
HSVtext(2, 6, "AP MODE", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui.update();
|
||||
if (ui.getUiState()->frameState == IN_TRANSITION && !appIsSwitching)
|
||||
{
|
||||
appIsSwitching = true;
|
||||
MQTTManager.setCurrentApp(CURRENT_APP);
|
||||
}
|
||||
else if (ui.getUiState()->frameState == FIXED && appIsSwitching)
|
||||
{
|
||||
appIsSwitching = false;
|
||||
MQTTManager.setCurrentApp(CURRENT_APP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayManager_::leftButton()
|
||||
{
|
||||
if (!MenuManager.inMenu)
|
||||
ui.previousApp();
|
||||
}
|
||||
|
||||
void DisplayManager_::rightButton()
|
||||
{
|
||||
if (!MenuManager.inMenu)
|
||||
ui.nextApp();
|
||||
}
|
||||
|
||||
void DisplayManager_::nextApp()
|
||||
{
|
||||
if (!MenuManager.inMenu)
|
||||
ui.nextApp();
|
||||
}
|
||||
|
||||
void DisplayManager_::previousApp()
|
||||
{
|
||||
if (!MenuManager.inMenu)
|
||||
ui.previousApp();
|
||||
}
|
||||
|
||||
void snozzeTimerCallback()
|
||||
{
|
||||
ALARM_ACTIVE = true;
|
||||
AlarmTicker.detach();
|
||||
}
|
||||
|
||||
void DisplayManager_::selectButton()
|
||||
{
|
||||
if (!MenuManager.inMenu)
|
||||
{
|
||||
if (notify.flag && notify.hold)
|
||||
{
|
||||
DisplayManager.getInstance().dismissNotify();
|
||||
}
|
||||
if (ALARM_ACTIVE && SNOOZE_TIME > 0)
|
||||
{
|
||||
PeripheryManager.stopSound();
|
||||
ALARM_ACTIVE = false;
|
||||
AlarmTicker.once(SNOOZE_TIME * 60, snozzeTimerCallback);
|
||||
}
|
||||
|
||||
if (TIMER_ACTIVE)
|
||||
{
|
||||
PeripheryManager.stopSound();
|
||||
TIMER_ACTIVE = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayManager_::selectButtonLong()
|
||||
{
|
||||
if (ALARM_ACTIVE)
|
||||
{
|
||||
PeripheryManager.stopSound();
|
||||
ALARM_ACTIVE = false;
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayManager_::dismissNotify()
|
||||
{
|
||||
notify.hold = false;
|
||||
notify.flag = false;
|
||||
}
|
||||
|
||||
void timerCallback()
|
||||
{
|
||||
TIMER_ACTIVE = true;
|
||||
TimerTicker.detach();
|
||||
}
|
||||
|
||||
void DisplayManager_::gererateTimer(String Payload)
|
||||
{
|
||||
DynamicJsonDocument doc(1024);
|
||||
DeserializationError error = deserializeJson(doc, Payload);
|
||||
if (error)
|
||||
return;
|
||||
int hours = doc["hours"] | 0;
|
||||
int minutes = doc["minutes"] | 0;
|
||||
int seconds = doc["seconds"] | 0;
|
||||
TIMER_SOUND = doc.containsKey("sound") ? doc["sound"].as<String>() : "";
|
||||
time_t now = time(nullptr);
|
||||
struct tm futureTimeinfo = *localtime(&now);
|
||||
futureTimeinfo.tm_hour += hours;
|
||||
futureTimeinfo.tm_min += minutes;
|
||||
futureTimeinfo.tm_sec += seconds;
|
||||
time_t futureTime = mktime(&futureTimeinfo);
|
||||
int interval = difftime(futureTime, now) * 1000;
|
||||
TimerTicker.attach_ms(interval, timerCallback);
|
||||
}
|
||||
57
src/DisplayManager.h
Normal file
57
src/DisplayManager.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#ifndef DisplayManager_h
|
||||
#define DisplayManager_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <LittleFS.h>
|
||||
|
||||
class DisplayManager_
|
||||
{
|
||||
private:
|
||||
// The pins the buttons are connected to
|
||||
|
||||
DisplayManager_() = default;
|
||||
|
||||
const int BatReadings = 10;
|
||||
uint16_t TotalBatReadings[10];
|
||||
int readIndex = 0;
|
||||
uint16_t total = 0;
|
||||
uint16_t average = 0;
|
||||
const int LDRReadings = 30;
|
||||
uint16_t TotalLDRReadings[30];
|
||||
int sampleIndex = 0;
|
||||
unsigned long previousMillis = 0;
|
||||
const unsigned long interval = 1000;
|
||||
|
||||
public:
|
||||
static DisplayManager_ &getInstance();
|
||||
void setup();
|
||||
void tick();
|
||||
void setSettings();
|
||||
void rightButton();
|
||||
void dismissNotify();
|
||||
void HSVtext(int16_t, int16_t, const char *, bool);
|
||||
void loadApps();
|
||||
void nextApp();
|
||||
void previousApp();
|
||||
void leftButton();
|
||||
void resetTextColor();
|
||||
void gererateTimer(String);
|
||||
void clearMatrix();
|
||||
void selectButton();
|
||||
void selectButtonLong();
|
||||
void setBrightness(uint8_t);
|
||||
void setTextColor(uint16_t color);
|
||||
void setFPS(uint8_t);
|
||||
void MatrixState(bool);
|
||||
void generateNotification(String);
|
||||
void generateCustomPage(uint16_t, String);
|
||||
void printText(int16_t x, int16_t y, const char *text, bool centered);
|
||||
void disableAutoTransition();
|
||||
void enableAutoTransition();
|
||||
void drawGIF(uint16_t x, uint16_t y, fs::File gifFile);
|
||||
void drawJPG(uint16_t x, uint16_t y, fs::File jpgFile);
|
||||
};
|
||||
|
||||
extern DisplayManager_ &DisplayManager;
|
||||
|
||||
#endif
|
||||
652
src/Frames.h
Normal file
652
src/Frames.h
Normal file
@@ -0,0 +1,652 @@
|
||||
#ifndef FRAMES_H
|
||||
#define FRAMES_H
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "icons.h"
|
||||
#include <FastLED_NeoMatrix.h>
|
||||
#include "MatrixDisplayUi.h"
|
||||
#include "Settings.h"
|
||||
#include "Functions.h"
|
||||
#include "MenuManager.h"
|
||||
#include "PeripheryManager.h"
|
||||
#include "DisplayManager.h"
|
||||
#include "LittleFS.h"
|
||||
#include <WiFi.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <Ticker.h>
|
||||
|
||||
Ticker downloader;
|
||||
|
||||
tm timeInfo;
|
||||
uint16_t nativeAppsCount;
|
||||
|
||||
int WEATHER_CODE;
|
||||
String WEATHER_TEMP;
|
||||
String WEATHER_HUM;
|
||||
|
||||
struct CustomFrame
|
||||
{
|
||||
uint8_t id;
|
||||
int16_t scrollposition = 34;
|
||||
String text;
|
||||
uint16_t color;
|
||||
File icon;
|
||||
bool isGif;
|
||||
bool rainbow;
|
||||
bool soundPlayed;
|
||||
String sound;
|
||||
int16_t repeat = 0;
|
||||
int16_t currentRepeat = 0;
|
||||
String name;
|
||||
byte pushIcon = 0;
|
||||
int16_t iconPosition = 0;
|
||||
bool iconWasPushed = false;
|
||||
};
|
||||
|
||||
uint8_t currentCustomFrame;
|
||||
std::map<uint8_t, CustomFrame> customFrames;
|
||||
|
||||
struct Notification
|
||||
{
|
||||
uint8_t id;
|
||||
int16_t scrollposition = 34;
|
||||
String text;
|
||||
uint16_t color;
|
||||
File icon;
|
||||
bool rainbow;
|
||||
bool isGif;
|
||||
bool flag = false;
|
||||
unsigned long startime = 0;
|
||||
unsigned long duration = 0;
|
||||
int16_t repeat = -1;
|
||||
bool hold = false;
|
||||
byte pushIcon = 0;
|
||||
int16_t iconPosition = 0;
|
||||
bool iconWasPushed = false;
|
||||
};
|
||||
|
||||
Notification notify;
|
||||
|
||||
CustomFrame *getCustomFrameById(uint8_t id)
|
||||
{
|
||||
return customFrames.count(id) ? &customFrames[id] : nullptr;
|
||||
}
|
||||
|
||||
void TimeFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
|
||||
{
|
||||
if (notify.flag)
|
||||
return;
|
||||
CURRENT_APP = "Time";
|
||||
DisplayManager.getInstance().resetTextColor();
|
||||
time_t now1 = time(nullptr);
|
||||
struct tm *timeInfo;
|
||||
timeInfo = localtime(&now1);
|
||||
char t[14];
|
||||
if (SHOW_SECONDS)
|
||||
{
|
||||
sprintf_P(t, PSTR("%02d:%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec);
|
||||
matrix->setCursor(2 + x, 6 + y);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf_P(t, PSTR("%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min);
|
||||
matrix->setCursor(7 + x, 6 + y);
|
||||
}
|
||||
|
||||
matrix->println(t);
|
||||
|
||||
if (!SHOW_WEEKDAY)
|
||||
return;
|
||||
for (int i = 0; i <= 6; i++)
|
||||
{
|
||||
if (i == (timeInfo->tm_wday + 6) % 7)
|
||||
{
|
||||
matrix->drawLine((2 + i * 4) + x, y + 7, (i * 4 + 4) + x, y + 7, matrix->Color(200, 200, 200));
|
||||
}
|
||||
else
|
||||
{
|
||||
matrix->drawLine((2 + i * 4) + x, y + 7, (i * 4 + 4) + x, y + 7, matrix->Color(100, 100, 100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DateFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
|
||||
{
|
||||
if (notify.flag)
|
||||
return;
|
||||
CURRENT_APP = "Date";
|
||||
DisplayManager.getInstance().resetTextColor();
|
||||
time_t now1 = time(nullptr);
|
||||
struct tm *timeInfo;
|
||||
timeInfo = localtime(&now1);
|
||||
char d[11];
|
||||
sprintf_P(d, PSTR("%02d.%02d.%02d"), timeInfo->tm_mday, timeInfo->tm_mon + 1, timeInfo->tm_year % 100);
|
||||
matrix->setCursor(2 + x, 6 + y);
|
||||
matrix->println(d);
|
||||
if (!SHOW_WEEKDAY)
|
||||
return;
|
||||
for (int i = 0; i <= 6; i++)
|
||||
{
|
||||
if (i == (timeInfo->tm_wday + 6) % 7)
|
||||
{
|
||||
matrix->drawLine((2 + i * 4) + x, y + 7, (i * 4 + 4) + x, y + 7, matrix->Color(200, 200, 200));
|
||||
}
|
||||
else
|
||||
{
|
||||
matrix->drawLine((2 + i * 4) + x, y + 7, (i * 4 + 4) + x, y + 7, matrix->Color(100, 100, 100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TempFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
|
||||
{
|
||||
if (notify.flag)
|
||||
return;
|
||||
CURRENT_APP = "Temperature";
|
||||
DisplayManager.getInstance().resetTextColor();
|
||||
matrix->drawRGBBitmap(x, y, get_icon(234), 8, 8);
|
||||
matrix->setCursor(14 + x, 6 + y);
|
||||
matrix->print((int)CURRENT_TEMP); // Ausgabe der Temperatur
|
||||
matrix->print(utf8ascii("°"));
|
||||
}
|
||||
|
||||
void HumFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
|
||||
{
|
||||
if (notify.flag)
|
||||
return;
|
||||
CURRENT_APP = "Humidity";
|
||||
DisplayManager.getInstance().resetTextColor();
|
||||
matrix->drawRGBBitmap(x, y + 1, get_icon(2075), 8, 8);
|
||||
matrix->setCursor(14 + x, 6 + y);
|
||||
int humidity = CURRENT_HUM; // Temperatur ohne Nachkommastellen
|
||||
matrix->print(humidity); // Ausgabe der Temperatur
|
||||
matrix->print("%");
|
||||
}
|
||||
|
||||
void BatFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
|
||||
{
|
||||
if (notify.flag)
|
||||
return;
|
||||
CURRENT_APP = "Battery";
|
||||
DisplayManager.getInstance().resetTextColor();
|
||||
matrix->drawRGBBitmap(x, y, get_icon(1486), 8, 8);
|
||||
matrix->setCursor(14 + x, 6 + y);
|
||||
matrix->print(BATTERY_PERCENT); // Ausgabe des Ladezustands
|
||||
matrix->print("%");
|
||||
}
|
||||
|
||||
void MenuFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state)
|
||||
{
|
||||
if (!MenuManager.inMenu)
|
||||
return;
|
||||
matrix->fillScreen(0);
|
||||
DisplayManager.printText(0, 6, MenuManager.menutext().c_str(), true);
|
||||
}
|
||||
|
||||
void AlarmFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state)
|
||||
{
|
||||
if (ALARM_ACTIVE)
|
||||
|
||||
{
|
||||
matrix->fillScreen(matrix->Color(255, 0, 0));
|
||||
CURRENT_APP = "Alarm";
|
||||
uint16_t textWidth = getTextWidth("ALARM");
|
||||
int16_t textX = ((32 - textWidth) / 2);
|
||||
matrix->setTextColor(0);
|
||||
matrix->setCursor(textX, 6);
|
||||
matrix->print("ALARM");
|
||||
if (ALARM_SOUND != "")
|
||||
{
|
||||
if (!PeripheryManager.isPlaying())
|
||||
PeripheryManager.playFromFile("/MELODIES/" + ALARM_SOUND + ".txt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TimerFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state)
|
||||
{
|
||||
if (TIMER_ACTIVE)
|
||||
{
|
||||
matrix->fillScreen(matrix->Color(0, 255, 0));
|
||||
CURRENT_APP = "Timer";
|
||||
String menuText = "TIMER";
|
||||
uint16_t textWidth = getTextWidth(menuText.c_str());
|
||||
int16_t textX = ((32 - textWidth) / 2);
|
||||
matrix->setTextColor(0);
|
||||
matrix->setCursor(textX, 6);
|
||||
matrix->print(menuText);
|
||||
if (TIMER_SOUND != "")
|
||||
{
|
||||
if (!PeripheryManager.isPlaying())
|
||||
PeripheryManager.playFromFile("/MELODIES/" + TIMER_SOUND + ".txt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShowCustomFrame(uint8_t id, FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
|
||||
{
|
||||
// Abort if notify.flag is set
|
||||
if (notify.flag)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get custom frame by ID
|
||||
CustomFrame *cf = getCustomFrameById(id);
|
||||
|
||||
// Abort if custom frame not found
|
||||
if (cf == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize custom frame properties if first frame
|
||||
if (lastFrame)
|
||||
{
|
||||
cf->iconWasPushed = false;
|
||||
cf->scrollposition = 34;
|
||||
cf->iconPosition = 0;
|
||||
}
|
||||
|
||||
// Update current app and custom frame IDs
|
||||
CURRENT_APP = cf->name;
|
||||
currentCustomFrame = id;
|
||||
|
||||
// Check if there is an icon
|
||||
bool hasIcon = cf->icon;
|
||||
|
||||
// Calculate available display width based on icon
|
||||
uint16_t availableWidth = (hasIcon) ? 24 : 32;
|
||||
|
||||
// Disable auto transition if text is repeating and too wide
|
||||
if ((cf->repeat > 0) && (getTextWidth(cf->text.c_str()) > availableWidth) && (state->frameState == FIXED))
|
||||
{
|
||||
DisplayManager.disableAutoTransition();
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayManager.enableAutoTransition();
|
||||
}
|
||||
|
||||
// Check if text is wider than available display width and frame is not in transition
|
||||
if (getTextWidth(cf->text.c_str()) > availableWidth && !(state->frameState == IN_TRANSITION))
|
||||
{
|
||||
// Reset scroll position when text has finished scrolling all the way to the left
|
||||
if (cf->scrollposition <= -getTextWidth(cf->text.c_str()))
|
||||
{
|
||||
cf->scrollposition = 38;
|
||||
|
||||
// Set iconWasPushed to false if icon has been pushed
|
||||
if (cf->iconWasPushed && cf->pushIcon == 2)
|
||||
{
|
||||
cf->iconWasPushed = false;
|
||||
}
|
||||
|
||||
// Transition to next app if frame is repeating and repeat limit has been reached
|
||||
if ((cf->currentRepeat + 1 >= cf->repeat) && (cf->repeat > 0))
|
||||
{
|
||||
DisplayManager.enableAutoTransition();
|
||||
cf->currentRepeat = 0;
|
||||
DisplayManager.nextApp();
|
||||
return;
|
||||
}
|
||||
else if (cf->repeat > 0)
|
||||
{
|
||||
++cf->currentRepeat;
|
||||
}
|
||||
}
|
||||
|
||||
// Update scroll position
|
||||
--cf->scrollposition;
|
||||
}
|
||||
|
||||
// Calculate horizontal text alignment
|
||||
int16_t textX = (hasIcon) ? ((24 - getTextWidth(cf->text.c_str())) / 2) + 9 : ((32 - getTextWidth(cf->text.c_str())) / 2);
|
||||
|
||||
// Set text color
|
||||
matrix->setTextColor(cf->color);
|
||||
|
||||
// Check if text is scrolling or not
|
||||
bool noScrolling = getTextWidth(cf->text.c_str()) <= availableWidth;
|
||||
|
||||
if (noScrolling)
|
||||
{
|
||||
cf->repeat = -1; // Disable repeat if text is too short for scrolling
|
||||
|
||||
// Display text with rainbow effect if enabled
|
||||
if (cf->rainbow)
|
||||
{
|
||||
DisplayManager.getInstance().HSVtext(x + textX, 6 + y, cf->text.c_str(), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Display text
|
||||
|
||||
DisplayManager.printText(x + textX, y + 6, cf->text.c_str(), false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Display scrolling text with rainbow effect if enabled
|
||||
if (cf->rainbow)
|
||||
{
|
||||
DisplayManager.getInstance().HSVtext(x + cf->scrollposition, 6 + y, cf->text.c_str(), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayManager.printText(x + cf->scrollposition, 6 + y, cf->text.c_str(), false);
|
||||
}
|
||||
} // Display icon if present and not pushed
|
||||
if (hasIcon)
|
||||
{
|
||||
// Push icon if enabled and text is scrolling
|
||||
if (cf->pushIcon > 0 && !noScrolling)
|
||||
{
|
||||
if (cf->iconPosition < 0 && cf->iconWasPushed == false && cf->scrollposition > 9)
|
||||
{
|
||||
++cf->iconPosition;
|
||||
}
|
||||
|
||||
if (cf->scrollposition < 9 && !cf->iconWasPushed)
|
||||
{
|
||||
cf->iconPosition = cf->scrollposition - 9;
|
||||
|
||||
if (cf->iconPosition <= -9)
|
||||
{
|
||||
cf->iconWasPushed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Display animated GIF if enabled and frame is fixed, since we have only one gifplayer instance, it looks weird when 2 apps want to draw a different gif
|
||||
if (cf->isGif)
|
||||
{
|
||||
if (state->frameState == FIXED)
|
||||
{
|
||||
DisplayManager.drawGIF(x + cf->iconPosition, y, cf->icon);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Display JPEG image
|
||||
DisplayManager.drawJPG(x + cf->iconPosition, y, cf->icon);
|
||||
}
|
||||
|
||||
// Draw vertical line if text is scrolling
|
||||
if (!noScrolling)
|
||||
{
|
||||
matrix->drawLine(8 + x + notify.iconPosition, 0 + y, 8 + x + notify.iconPosition, 7 + y, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset text color
|
||||
DisplayManager.getInstance().resetTextColor();
|
||||
}
|
||||
|
||||
void NotifyFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state)
|
||||
{
|
||||
// Check if notification flag is set
|
||||
if (!notify.flag)
|
||||
{
|
||||
return; // Exit function if flag is not set
|
||||
}
|
||||
|
||||
// Set current app name
|
||||
CURRENT_APP = "Notification";
|
||||
// Get current time
|
||||
unsigned long currentTime = millis();
|
||||
|
||||
// Check if notification duration has expired or if repeat count is 0 and hold is not enabled
|
||||
if ((((currentTime - notify.startime >= notify.duration) && notify.repeat == -1) || notify.repeat == 0) && !notify.hold)
|
||||
{
|
||||
// Reset notification flags and exit function
|
||||
notify.flag = false;
|
||||
notify.duration = 0;
|
||||
notify.startime = 0;
|
||||
notify.scrollposition = 34;
|
||||
notify.iconWasPushed = false;
|
||||
notify.iconPosition = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if notification has an icon
|
||||
bool hasIcon = notify.icon;
|
||||
|
||||
// Clear the matrix display
|
||||
matrix->fillRect(0, 0, 32, 8, 0);
|
||||
|
||||
// Calculate text and available width
|
||||
uint16_t textWidth = getTextWidth(notify.text.c_str());
|
||||
uint16_t availableWidth = hasIcon ? 24 : 32;
|
||||
|
||||
// Check if text needs to be scrolled
|
||||
if (textWidth > availableWidth && notify.scrollposition <= -textWidth)
|
||||
{
|
||||
// Reset scroll position and icon position if needed
|
||||
notify.scrollposition = 38;
|
||||
|
||||
if (notify.pushIcon == 2)
|
||||
{
|
||||
notify.iconWasPushed = false;
|
||||
// notify.iconPosition = 0;
|
||||
}
|
||||
|
||||
if (notify.repeat > 0)
|
||||
{
|
||||
--notify.repeat;
|
||||
}
|
||||
}
|
||||
else if (textWidth > availableWidth)
|
||||
{
|
||||
// Update scroll position
|
||||
--notify.scrollposition;
|
||||
}
|
||||
|
||||
// Calculate text X position based on icon presence
|
||||
int16_t textX = hasIcon ? ((24 - textWidth) / 2) + 9 : ((32 - textWidth) / 2);
|
||||
|
||||
// Set text color
|
||||
matrix->setTextColor(notify.color);
|
||||
|
||||
// Check if text is scrolling
|
||||
bool noScrolling = textWidth <= availableWidth;
|
||||
|
||||
if (noScrolling)
|
||||
{
|
||||
// Disable repeat if text is not scrolling
|
||||
notify.repeat = -1;
|
||||
|
||||
if (notify.rainbow)
|
||||
{
|
||||
// Display text in rainbow color if enabled
|
||||
DisplayManager.getInstance().HSVtext(textX, 6, notify.text.c_str(), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Display text in solid color
|
||||
DisplayManager.printText(textX, 6, notify.text.c_str(), false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (notify.rainbow)
|
||||
{
|
||||
// Display scrolling text in rainbow color if enabled
|
||||
DisplayManager.getInstance().HSVtext(notify.scrollposition, 6, notify.text.c_str(), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Display scrolling text in solid color
|
||||
DisplayManager.printText(notify.scrollposition, 6, notify.text.c_str(), false);
|
||||
}
|
||||
}
|
||||
|
||||
// Display icon if present and not pushed
|
||||
if (hasIcon)
|
||||
{
|
||||
// Push icon if enabled and text is scrolling
|
||||
if (notify.pushIcon > 0 && !noScrolling)
|
||||
{
|
||||
if (notify.iconPosition < 0 && notify.iconWasPushed == false && notify.scrollposition > 9)
|
||||
{
|
||||
++notify.iconPosition;
|
||||
}
|
||||
|
||||
if (notify.scrollposition < 9 && !notify.iconWasPushed)
|
||||
{
|
||||
notify.iconPosition = notify.scrollposition - 9;
|
||||
|
||||
if (notify.iconPosition <= -9)
|
||||
{
|
||||
notify.iconWasPushed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Display animated GIF if
|
||||
if (notify.isGif)
|
||||
{
|
||||
// Display GIF if present
|
||||
DisplayManager.drawGIF(notify.iconPosition, 0, notify.icon);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Display JPG image if present
|
||||
DisplayManager.drawJPG(notify.iconPosition, 0, notify.icon);
|
||||
}
|
||||
|
||||
// Display icon divider line if text is scrolling
|
||||
if (!noScrolling)
|
||||
{
|
||||
matrix->drawLine(8 + notify.iconPosition, 0, 8 + notify.iconPosition, 7, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset text color after displaying notification
|
||||
DisplayManager.getInstance().resetTextColor();
|
||||
}
|
||||
|
||||
void CFrame1(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
|
||||
{
|
||||
ShowCustomFrame(1, matrix, state, x, y, firstFrame, lastFrame);
|
||||
}
|
||||
|
||||
void CFrame2(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
|
||||
{
|
||||
ShowCustomFrame(2, matrix, state, x, y, firstFrame, lastFrame);
|
||||
}
|
||||
|
||||
void CFrame3(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
|
||||
{
|
||||
ShowCustomFrame(3, matrix, state, x, y, firstFrame, lastFrame);
|
||||
}
|
||||
|
||||
void CFrame4(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
|
||||
{
|
||||
ShowCustomFrame(4, matrix, state, x, y, firstFrame, lastFrame);
|
||||
}
|
||||
|
||||
void CFrame5(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
|
||||
{
|
||||
ShowCustomFrame(5, matrix, state, x, y, firstFrame, lastFrame);
|
||||
}
|
||||
|
||||
void CFrame6(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
|
||||
{
|
||||
ShowCustomFrame(6, matrix, state, x, y, firstFrame, lastFrame);
|
||||
}
|
||||
|
||||
void CFrame7(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
|
||||
{
|
||||
ShowCustomFrame(7, matrix, state, x, y, firstFrame, lastFrame);
|
||||
}
|
||||
|
||||
void CFrame8(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
|
||||
{
|
||||
ShowCustomFrame(8, matrix, state, x, y, firstFrame, lastFrame);
|
||||
}
|
||||
|
||||
void CFrame9(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
|
||||
{
|
||||
ShowCustomFrame(9, matrix, state, x, y, firstFrame, lastFrame);
|
||||
}
|
||||
|
||||
void CFrame10(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
|
||||
{
|
||||
ShowCustomFrame(10, matrix, state, x, y, firstFrame, lastFrame);
|
||||
}
|
||||
|
||||
const uint16_t *getWeatherIcon(int code)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case 1:
|
||||
return icon_475;
|
||||
break;
|
||||
|
||||
default:
|
||||
return icon_475;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WeatherFrame(FastLED_NeoMatrix *matrix, MatrixDisplayUiState *state, int16_t x, int16_t y, bool firstFrame, bool lastFrame)
|
||||
{
|
||||
if (notify.flag)
|
||||
return;
|
||||
CURRENT_APP = "Weather";
|
||||
DisplayManager.getInstance().resetTextColor();
|
||||
matrix->drawRGBBitmap(x, y, getWeatherIcon(WEATHER_CODE), 8, 8);
|
||||
String text = WEATHER_TEMP + "°" + WEATHER_HUM + "%";
|
||||
uint16_t textWidth = getTextWidth(text.c_str());
|
||||
int16_t textX = ((23 - textWidth) / 2);
|
||||
matrix->setCursor(textX + 11, 6 + y);
|
||||
matrix->print(utf8ascii(text));
|
||||
}
|
||||
|
||||
void getWeatherData()
|
||||
{
|
||||
Serial.println("UPDATE");
|
||||
String weatherUrl = "https://wttr.in/" + CITY + "?format=p1";
|
||||
if ((WiFi.status() == WL_CONNECTED))
|
||||
{
|
||||
HTTPClient http;
|
||||
http.begin(weatherUrl);
|
||||
http.setTimeout(5000);
|
||||
int httpCode = http.GET();
|
||||
|
||||
if (httpCode > 0)
|
||||
{
|
||||
String payload = http.getString();
|
||||
int temperatureIndex = payload.indexOf("temperature_celsius{forecast=\"current\"}");
|
||||
int humIndex = payload.indexOf("humidity_percentage{forecast=\"current\"}");
|
||||
int weatherCodeIndex = payload.indexOf("weather_code{forecast=\"current\"}");
|
||||
|
||||
if (temperatureIndex >= 0 && weatherCodeIndex >= 0)
|
||||
{
|
||||
int tempEndIndex = payload.indexOf('\n', temperatureIndex);
|
||||
int codeEndIndex = payload.indexOf('\n', weatherCodeIndex);
|
||||
int humEndIndex = payload.indexOf('\n', humIndex);
|
||||
String temperatureValue = payload.substring(temperatureIndex + 40, tempEndIndex);
|
||||
String humValue = payload.substring(humIndex + 40, humEndIndex);
|
||||
String weatherCodeValue = payload.substring(weatherCodeIndex + 33, codeEndIndex);
|
||||
|
||||
WEATHER_TEMP = temperatureValue;
|
||||
WEATHER_HUM = humValue;
|
||||
WEATHER_CODE = weatherCodeValue.toInt();
|
||||
}
|
||||
}
|
||||
http.end();
|
||||
}
|
||||
}
|
||||
|
||||
void StartAppUpdater()
|
||||
{
|
||||
// downloader.attach(60,getWeatherData);
|
||||
// getWeatherData();
|
||||
}
|
||||
|
||||
std::vector<std::pair<uint16_t, AppCallback>> Apps;
|
||||
OverlayCallback overlays[] = {MenuFrame, NotifyFrame, AlarmFrame, TimerFrame};
|
||||
#endif
|
||||
656
src/GifPlayer.h
Normal file
656
src/GifPlayer.h
Normal file
@@ -0,0 +1,656 @@
|
||||
#ifndef GifPlayer_H
|
||||
#define GifPlayer_H
|
||||
|
||||
class GifPlayer
|
||||
{
|
||||
public:
|
||||
#define ERROR_NONE 0
|
||||
#define ERROR_FILEOPEN 1
|
||||
#define ERROR_FILENOTGIF 2
|
||||
#define ERROR_BADGIFFORMAT 3
|
||||
#define ERROR_UNKNOWNCONTROLEXT 4
|
||||
#define ERROR_FINISHED 5
|
||||
private:
|
||||
bool needNewFrame;
|
||||
long lastFrameTime;
|
||||
bool firstFrameDone;
|
||||
int newframeDelay;
|
||||
int lastFrame[8 * 8];
|
||||
bool lastFrameDrawn = false;
|
||||
unsigned long nextFrameTime = 0;
|
||||
#define GIFHDRTAGNORM "GIF87a"
|
||||
#define GIFHDRTAGNORM1 "GIF89a"
|
||||
#define GIFHDRSIZE 6
|
||||
FastLED_NeoMatrix *mtx;
|
||||
#define COLORTBLFLAG 0x80
|
||||
#define INTERLACEFLAG 0x40
|
||||
#define TRANSPARENTFLAG 0x01
|
||||
#define NO_TRANSPARENT_INDEX -1
|
||||
#define DISPOSAL_NONE 0
|
||||
#define DISPOSAL_LEAVE 1
|
||||
#define DISPOSAL_BACKGROUND 2
|
||||
#define DISPOSAL_RESTORE 3
|
||||
typedef struct
|
||||
{
|
||||
byte Red;
|
||||
byte Green;
|
||||
byte Blue;
|
||||
} _RGB;
|
||||
int lsdWidth;
|
||||
int lsdHeight;
|
||||
int lsdPackedField;
|
||||
int lsdAspectRatio;
|
||||
int lsdBackgroundIndex;
|
||||
int offsetX;
|
||||
int offsetY;
|
||||
int tbiImageX;
|
||||
int tbiImageY;
|
||||
int tbiWidth;
|
||||
int tbiHeight;
|
||||
int tbiPackedBits;
|
||||
boolean tbiInterlaced;
|
||||
|
||||
public:
|
||||
int frameDelay;
|
||||
int transparentColorIndex;
|
||||
int prevBackgroundIndex;
|
||||
int prevDisposalMethod;
|
||||
int disposalMethod;
|
||||
int lzwCodeSize;
|
||||
boolean keyFrame;
|
||||
int rectX;
|
||||
int rectY;
|
||||
int rectWidth;
|
||||
int rectHeight;
|
||||
int colorCount;
|
||||
_RGB gifPalette[256];
|
||||
byte lzwImageData[1280];
|
||||
char tempBuffer[260];
|
||||
File file;
|
||||
byte imageData[8 * 8];
|
||||
byte imageDataBU[8 * 8];
|
||||
|
||||
void backUpStream(int n)
|
||||
{
|
||||
file.seek(file.position() - n, SeekSet);
|
||||
}
|
||||
|
||||
int readByte()
|
||||
{
|
||||
int b = file.read();
|
||||
return b;
|
||||
}
|
||||
|
||||
int readWord()
|
||||
{
|
||||
int b0 = readByte();
|
||||
int b1 = readByte();
|
||||
return (b1 << 8) | b0;
|
||||
}
|
||||
|
||||
int readIntoBuffer(void *buffer, int numberOfBytes)
|
||||
{
|
||||
int result = file.read((uint8_t *)buffer, numberOfBytes);
|
||||
return result;
|
||||
}
|
||||
|
||||
void fillImageDataRect(byte colorIndex, int x, int y, int width, int height)
|
||||
{
|
||||
int yOffset;
|
||||
for (int yy = y; yy < height + y; yy++)
|
||||
{
|
||||
yOffset = yy * 8;
|
||||
for (int xx = x; xx < width + x; xx++)
|
||||
{
|
||||
imageData[yOffset + xx] = colorIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fillImageData(byte colorIndex)
|
||||
{
|
||||
memset(imageData, colorIndex, sizeof(imageData));
|
||||
}
|
||||
|
||||
void copyImageDataRect(byte *src, byte *dst, int x, int y, int width, int height)
|
||||
{
|
||||
|
||||
int yOffset, offset;
|
||||
|
||||
for (int yy = y; yy < height + y; yy++)
|
||||
{
|
||||
yOffset = yy * 8;
|
||||
for (int xx = x; xx < width + x; xx++)
|
||||
{
|
||||
offset = yOffset + xx;
|
||||
dst[offset] = src[offset];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void parsePlainTextExtension()
|
||||
{
|
||||
byte len = readByte();
|
||||
readIntoBuffer(tempBuffer, len);
|
||||
len = readByte();
|
||||
while (len != 0)
|
||||
{
|
||||
readIntoBuffer(tempBuffer, len);
|
||||
len = readByte();
|
||||
}
|
||||
}
|
||||
|
||||
void parseGraphicControlExtension()
|
||||
{
|
||||
readByte();
|
||||
int packedBits = readByte();
|
||||
frameDelay = readWord();
|
||||
transparentColorIndex = readByte();
|
||||
|
||||
if ((packedBits & TRANSPARENTFLAG) == 0)
|
||||
{
|
||||
// Indicate no transparent index
|
||||
transparentColorIndex = NO_TRANSPARENT_INDEX;
|
||||
}
|
||||
disposalMethod = (packedBits >> 2) & 7;
|
||||
if (disposalMethod > 3)
|
||||
{
|
||||
disposalMethod = 0;
|
||||
}
|
||||
|
||||
readByte(); // Toss block end
|
||||
}
|
||||
|
||||
void parseApplicationExtension()
|
||||
{
|
||||
memset(tempBuffer, 0, sizeof(tempBuffer));
|
||||
byte len = readByte();
|
||||
readIntoBuffer(tempBuffer, len);
|
||||
len = readByte();
|
||||
while (len != 0)
|
||||
{
|
||||
readIntoBuffer(tempBuffer, len);
|
||||
len = readByte();
|
||||
}
|
||||
}
|
||||
|
||||
void parseCommentExtension()
|
||||
{
|
||||
byte len = readByte();
|
||||
while (len != 0)
|
||||
{
|
||||
memset(tempBuffer, 0, sizeof(tempBuffer));
|
||||
readIntoBuffer(tempBuffer, len);
|
||||
len = readByte();
|
||||
}
|
||||
}
|
||||
|
||||
int parseGIFFileTerminator()
|
||||
{
|
||||
byte b = readByte();
|
||||
if (b != 0x3B)
|
||||
{
|
||||
return ERROR_BADGIFFORMAT;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ERROR_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long parseTableBasedImage()
|
||||
{
|
||||
tbiImageX = readWord();
|
||||
tbiImageY = readWord();
|
||||
tbiWidth = readWord();
|
||||
tbiHeight = readWord();
|
||||
tbiPackedBits = readByte();
|
||||
tbiInterlaced = ((tbiPackedBits & INTERLACEFLAG) != 0);
|
||||
boolean localColorTable = ((tbiPackedBits & COLORTBLFLAG) != 0);
|
||||
if (localColorTable)
|
||||
{
|
||||
int colorBits = ((tbiPackedBits & 7) + 1);
|
||||
colorCount = 1 << colorBits;
|
||||
int colorTableBytes = sizeof(_RGB) * colorCount;
|
||||
readIntoBuffer(gifPalette, colorTableBytes);
|
||||
}
|
||||
|
||||
if (keyFrame)
|
||||
{
|
||||
if (transparentColorIndex == NO_TRANSPARENT_INDEX)
|
||||
{
|
||||
fillImageData(lsdBackgroundIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
fillImageData(transparentColorIndex);
|
||||
}
|
||||
keyFrame = false;
|
||||
|
||||
rectX = 0;
|
||||
rectY = 0;
|
||||
rectWidth = 8;
|
||||
rectHeight = 8;
|
||||
}
|
||||
|
||||
if ((prevDisposalMethod != DISPOSAL_NONE) && (prevDisposalMethod != DISPOSAL_LEAVE))
|
||||
{
|
||||
// mtx->clear();
|
||||
}
|
||||
|
||||
if (prevDisposalMethod == DISPOSAL_BACKGROUND)
|
||||
{
|
||||
fillImageDataRect(prevBackgroundIndex, rectX, rectY, rectWidth, rectHeight);
|
||||
}
|
||||
else if (prevDisposalMethod == DISPOSAL_RESTORE)
|
||||
{
|
||||
copyImageDataRect(imageDataBU, imageData, rectX, rectY, rectWidth, rectHeight);
|
||||
}
|
||||
prevDisposalMethod = disposalMethod;
|
||||
if (disposalMethod != DISPOSAL_NONE)
|
||||
{
|
||||
rectX = tbiImageX;
|
||||
rectY = tbiImageY;
|
||||
rectWidth = tbiWidth;
|
||||
rectHeight = tbiHeight;
|
||||
if (disposalMethod == DISPOSAL_BACKGROUND)
|
||||
{
|
||||
if (transparentColorIndex != NO_TRANSPARENT_INDEX)
|
||||
{
|
||||
prevBackgroundIndex = transparentColorIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
prevBackgroundIndex = lsdBackgroundIndex;
|
||||
}
|
||||
}
|
||||
else if (disposalMethod == DISPOSAL_RESTORE)
|
||||
{
|
||||
copyImageDataRect(imageData, imageDataBU, rectX, rectY, rectWidth, rectHeight);
|
||||
}
|
||||
}
|
||||
lzwCodeSize = readByte();
|
||||
int offset = 0;
|
||||
int dataBlockSize = readByte();
|
||||
while (dataBlockSize != 0)
|
||||
{
|
||||
backUpStream(1);
|
||||
dataBlockSize++;
|
||||
if (offset + dataBlockSize <= (int)sizeof(lzwImageData))
|
||||
{
|
||||
readIntoBuffer(lzwImageData + offset, dataBlockSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < dataBlockSize; i++)
|
||||
file.read();
|
||||
}
|
||||
|
||||
offset += dataBlockSize;
|
||||
dataBlockSize = readByte();
|
||||
}
|
||||
lzw_decode_init(lzwCodeSize, lzwImageData);
|
||||
decompressAndDisplayFrame();
|
||||
transparentColorIndex = NO_TRANSPARENT_INDEX;
|
||||
disposalMethod = DISPOSAL_NONE;
|
||||
if (frameDelay < 1)
|
||||
{
|
||||
frameDelay = 1;
|
||||
}
|
||||
newframeDelay = frameDelay * 10;
|
||||
return frameDelay * 10;
|
||||
}
|
||||
|
||||
#define LZW_MAXBITS 10
|
||||
#define LZW_SIZTABLE (1 << LZW_MAXBITS)
|
||||
unsigned int mask[17] = {
|
||||
0x0000, 0x0001, 0x0003, 0x0007,
|
||||
0x000F, 0x001F, 0x003F, 0x007F,
|
||||
0x00FF, 0x01FF, 0x03FF, 0x07FF,
|
||||
0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF,
|
||||
0xFFFF};
|
||||
|
||||
byte *pbuf;
|
||||
int bbits;
|
||||
int bbuf;
|
||||
int cursize; // The current code size
|
||||
int curmask;
|
||||
int codesize;
|
||||
int clear_code;
|
||||
int end_code;
|
||||
int newcodes; // First available code
|
||||
int top_slot; // Highest code for current size
|
||||
int extra_slot;
|
||||
int slot; // Last read code
|
||||
int fc, oc;
|
||||
int bs; // Current buffer size for GIF
|
||||
byte *sp;
|
||||
byte stack[LZW_SIZTABLE];
|
||||
byte suffix[LZW_SIZTABLE];
|
||||
unsigned int prefix[LZW_SIZTABLE];
|
||||
|
||||
void lzw_decode_init(int csize, byte *buf)
|
||||
{
|
||||
pbuf = buf;
|
||||
bbuf = 0;
|
||||
bbits = 0;
|
||||
bs = 0;
|
||||
codesize = csize;
|
||||
cursize = codesize + 1;
|
||||
curmask = mask[cursize];
|
||||
top_slot = 1 << cursize;
|
||||
clear_code = 1 << codesize;
|
||||
end_code = clear_code + 1;
|
||||
slot = newcodes = clear_code + 2;
|
||||
oc = fc = -1;
|
||||
sp = stack;
|
||||
}
|
||||
|
||||
int lzw_get_code()
|
||||
{
|
||||
while (bbits < cursize)
|
||||
{
|
||||
if (!bs)
|
||||
{
|
||||
bs = *pbuf++;
|
||||
}
|
||||
bbuf |= (*pbuf++) << bbits;
|
||||
bbits += 8;
|
||||
bs--;
|
||||
}
|
||||
int c = bbuf;
|
||||
bbuf >>= cursize;
|
||||
bbits -= cursize;
|
||||
return c & curmask;
|
||||
}
|
||||
|
||||
int lzw_decode(byte *buf, int len)
|
||||
{
|
||||
int l, c, code;
|
||||
|
||||
if (end_code < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
l = len;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
while (sp > stack)
|
||||
{
|
||||
*buf++ = *(--sp);
|
||||
if ((--l) == 0)
|
||||
{
|
||||
goto the_end;
|
||||
}
|
||||
}
|
||||
c = lzw_get_code();
|
||||
if (c == end_code)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (c == clear_code)
|
||||
{
|
||||
cursize = codesize + 1;
|
||||
curmask = mask[cursize];
|
||||
slot = newcodes;
|
||||
top_slot = 1 << cursize;
|
||||
fc = oc = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
code = c;
|
||||
if ((code == slot) && (fc >= 0))
|
||||
{
|
||||
*sp++ = fc;
|
||||
code = oc;
|
||||
}
|
||||
else if (code >= slot)
|
||||
{
|
||||
break;
|
||||
}
|
||||
while (code >= newcodes)
|
||||
{
|
||||
*sp++ = suffix[code];
|
||||
code = prefix[code];
|
||||
}
|
||||
*sp++ = code;
|
||||
if ((slot < top_slot) && (oc >= 0))
|
||||
{
|
||||
suffix[slot] = code;
|
||||
prefix[slot++] = oc;
|
||||
}
|
||||
fc = code;
|
||||
oc = c;
|
||||
if (slot >= top_slot)
|
||||
{
|
||||
if (cursize < LZW_MAXBITS)
|
||||
{
|
||||
top_slot <<= 1;
|
||||
curmask = mask[++cursize];
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end_code = -1;
|
||||
the_end:
|
||||
return len - l;
|
||||
}
|
||||
|
||||
void redrawLastFrame()
|
||||
{
|
||||
if (needNewFrame)
|
||||
return;
|
||||
CRGB color;
|
||||
int yOffset, pixel;
|
||||
for (int y = tbiImageY; y < tbiHeight + tbiImageY; y++)
|
||||
{
|
||||
yOffset = y * 8;
|
||||
for (int x = tbiImageX; x < tbiWidth + tbiImageX; x++)
|
||||
{
|
||||
pixel = lastFrame[yOffset + x];
|
||||
if (pixel == -99)
|
||||
{
|
||||
mtx->drawPixel(x + offsetX, y + offsetY, mtx->Color(0, 0, 0));
|
||||
continue;
|
||||
}
|
||||
color.red = gifPalette[pixel].Red;
|
||||
color.green = gifPalette[pixel].Green;
|
||||
color.blue = gifPalette[pixel].Blue;
|
||||
mtx->drawPixel(x + offsetX, y + offsetY, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void decompressAndDisplayFrame()
|
||||
{
|
||||
CRGB color;
|
||||
if (tbiInterlaced)
|
||||
{
|
||||
for (int line = tbiImageY + 0; line < tbiHeight + tbiImageY; line += 8)
|
||||
{
|
||||
lzw_decode(imageData + (line * 8) + tbiImageX, tbiWidth);
|
||||
}
|
||||
for (int line = tbiImageY + 4; line < tbiHeight + tbiImageY; line += 8)
|
||||
{
|
||||
lzw_decode(imageData + (line * 8) + tbiImageX, tbiWidth);
|
||||
}
|
||||
for (int line = tbiImageY + 2; line < tbiHeight + tbiImageY; line += 4)
|
||||
{
|
||||
lzw_decode(imageData + (line * 8) + tbiImageX, tbiWidth);
|
||||
}
|
||||
for (int line = tbiImageY + 1; line < tbiHeight + tbiImageY; line += 2)
|
||||
{
|
||||
lzw_decode(imageData + (line * 8) + tbiImageX, tbiWidth);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int line = tbiImageY; line < tbiHeight + tbiImageY; line++)
|
||||
{
|
||||
lzw_decode(imageData + (line * 8) + tbiImageX, tbiWidth);
|
||||
}
|
||||
}
|
||||
|
||||
// Ersetze alle transparenten Pixel durch schwarze Pixel
|
||||
for (int i = 0; i < tbiWidth * tbiHeight; i++)
|
||||
{
|
||||
if (imageData[i] == transparentColorIndex)
|
||||
{
|
||||
imageData[i] = -99; // Indexwert für Schwarz
|
||||
}
|
||||
}
|
||||
|
||||
// Zeichne das Bild auf die Matrix
|
||||
int yOffset, pixel;
|
||||
for (int y = tbiImageY; y < tbiHeight + tbiImageY; y++)
|
||||
{
|
||||
yOffset = y * 8;
|
||||
for (int x = tbiImageX; x < tbiWidth + tbiImageX; x++)
|
||||
{
|
||||
pixel = imageData[yOffset + x];
|
||||
if (pixel == -99)
|
||||
{
|
||||
mtx->drawPixel(x + offsetX, y + offsetY, mtx->Color(0, 0, 0));
|
||||
continue;
|
||||
}
|
||||
|
||||
lastFrame[yOffset + x] = pixel;
|
||||
color.red = gifPalette[pixel].Red;
|
||||
color.green = gifPalette[pixel].Green;
|
||||
color.blue = gifPalette[pixel].Blue;
|
||||
mtx->drawPixel(x + offsetX, y + offsetY, color);
|
||||
}
|
||||
}
|
||||
needNewFrame = false;
|
||||
}
|
||||
|
||||
public:
|
||||
void setMatrix(FastLED_NeoMatrix *matrix)
|
||||
{
|
||||
mtx = matrix;
|
||||
}
|
||||
void setFile(File imageFile)
|
||||
{
|
||||
if (imageFile.name() == file.name())
|
||||
return;
|
||||
|
||||
needNewFrame = true;
|
||||
file = imageFile;
|
||||
memset(lastFrame, 0, sizeof(lastFrame));
|
||||
memset(gifPalette, 0, sizeof(gifPalette));
|
||||
memset(lzwImageData, 0, sizeof(lzwImageData));
|
||||
memset(imageData, 0, sizeof(imageData));
|
||||
memset(imageDataBU, 0, sizeof(imageDataBU));
|
||||
memset(stack, 0, sizeof(stack));
|
||||
memset(suffix, 0, sizeof(suffix));
|
||||
memset(prefix, 0, sizeof(prefix));
|
||||
parseGifHeader();
|
||||
parseLogicalScreenDescriptor();
|
||||
parseGlobalColorTable();
|
||||
drawFrame(offsetX, offsetY);
|
||||
}
|
||||
|
||||
boolean parseGifHeader()
|
||||
{
|
||||
char buffer[10];
|
||||
readIntoBuffer(buffer, GIFHDRSIZE);
|
||||
if ((strncmp(buffer, GIFHDRTAGNORM, GIFHDRSIZE) != 0) &&
|
||||
(strncmp(buffer, GIFHDRTAGNORM1, GIFHDRSIZE) != 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void parseLogicalScreenDescriptor()
|
||||
{
|
||||
lsdWidth = readWord();
|
||||
lsdHeight = readWord();
|
||||
lsdPackedField = readByte();
|
||||
lsdBackgroundIndex = readByte();
|
||||
lsdAspectRatio = readByte();
|
||||
}
|
||||
|
||||
void parseGlobalColorTable()
|
||||
{
|
||||
if (lsdPackedField & COLORTBLFLAG)
|
||||
{
|
||||
colorCount = 1 << ((lsdPackedField & 7) + 1);
|
||||
int colorTableBytes = sizeof(_RGB) * colorCount;
|
||||
readIntoBuffer(gifPalette, colorTableBytes);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long drawFrame(int x, int y)
|
||||
{
|
||||
if (!file)
|
||||
return 0;
|
||||
|
||||
unsigned long now = millis();
|
||||
|
||||
if (now - lastFrameTime < newframeDelay)
|
||||
{
|
||||
redrawLastFrame();
|
||||
return 0;
|
||||
}
|
||||
|
||||
lastFrameTime = now;
|
||||
lastFrameDrawn = false;
|
||||
|
||||
offsetX = x;
|
||||
offsetY = y;
|
||||
boolean done = false;
|
||||
while (!done)
|
||||
{
|
||||
byte b = readByte();
|
||||
if (b == 0x2c)
|
||||
{
|
||||
unsigned int fdelay = parseTableBasedImage();
|
||||
return fdelay;
|
||||
}
|
||||
else if (b == 0x21)
|
||||
{
|
||||
b = readByte();
|
||||
switch (b)
|
||||
{
|
||||
case 0x01:
|
||||
parsePlainTextExtension();
|
||||
break;
|
||||
case 0xf9:
|
||||
parseGraphicControlExtension();
|
||||
break;
|
||||
case 0xfe:
|
||||
parseCommentExtension();
|
||||
break;
|
||||
case 0xff:
|
||||
parseApplicationExtension();
|
||||
break;
|
||||
default:
|
||||
return ERROR_UNKNOWNCONTROLEXT;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
done = true;
|
||||
backUpStream(1);
|
||||
file.seek(0);
|
||||
parseGifHeader();
|
||||
parseLogicalScreenDescriptor();
|
||||
parseGlobalColorTable();
|
||||
drawFrame(offsetX, offsetY);
|
||||
return ERROR_FINISHED;
|
||||
}
|
||||
}
|
||||
return ERROR_NONE;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
296
src/MQTTManager.cpp
Normal file
296
src/MQTTManager.cpp
Normal file
@@ -0,0 +1,296 @@
|
||||
#include <MQTTManager.h>
|
||||
#include "Settings.h"
|
||||
#include "DisplayManager.h"
|
||||
#include "ServerManager.h"
|
||||
#include <ArduinoHA.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
WiFiClient espClient;
|
||||
uint8_t lastBrightness;
|
||||
HADevice device;
|
||||
HAMqtt mqtt(espClient, device, 15);
|
||||
|
||||
unsigned long reconnectTimer = 0;
|
||||
const unsigned long reconnectInterval = 30000; // 30 Sekunden
|
||||
|
||||
HALight Matrix("bri", HALight::BrightnessFeature | HALight::RGBFeature);
|
||||
HASelect BriMode("BriMode");
|
||||
HAButton dismiss("myButtonA");
|
||||
HAButton nextApp("myButtonB");
|
||||
HAButton prevApp("myButtonC");
|
||||
HASensor curApp("curApp");
|
||||
HASensor battery("battery");
|
||||
HASensor temperature("temperature");
|
||||
HASensor humidity("huminity");
|
||||
HASensor illuminance("illuminance");
|
||||
|
||||
// The getter for the instantiated singleton instance
|
||||
MQTTManager_ &MQTTManager_::getInstance()
|
||||
{
|
||||
static MQTTManager_ instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Initialize the global shared instance
|
||||
MQTTManager_ &MQTTManager = MQTTManager.getInstance();
|
||||
|
||||
void onButtonCommand(HAButton *sender)
|
||||
{
|
||||
if (sender == &dismiss)
|
||||
{
|
||||
DisplayManager.dismissNotify();
|
||||
}
|
||||
else if (sender == &nextApp)
|
||||
{
|
||||
DisplayManager.nextApp();
|
||||
}
|
||||
else if (sender == &prevApp)
|
||||
{
|
||||
DisplayManager.previousApp();
|
||||
}
|
||||
}
|
||||
|
||||
void onSelectCommand(int8_t index, HASelect *sender)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
AUTO_BRIGHTNESS = true;
|
||||
break;
|
||||
case 1:
|
||||
AUTO_BRIGHTNESS = false;
|
||||
|
||||
break;
|
||||
default:
|
||||
AUTO_BRIGHTNESS = true;
|
||||
return;
|
||||
}
|
||||
Matrix.setBrightness(BRIGHTNESS);
|
||||
saveSettings();
|
||||
sender->setState(index); // report the selected option back to the HA panel
|
||||
}
|
||||
|
||||
void onRGBColorCommand(HALight::RGBColor color, HALight *sender)
|
||||
{
|
||||
TEXTCOLOR_565 = ((color.red & 0x1F) << 11) | ((color.green & 0x3F) << 5) | (color.blue & 0x1F);
|
||||
saveSettings();
|
||||
sender->setRGBColor(color); // report color back to the Home Assistant
|
||||
}
|
||||
|
||||
void onStateCommand(bool state, HALight *sender)
|
||||
{
|
||||
if (state)
|
||||
{
|
||||
MATRIX_OFF = false;
|
||||
DisplayManager.setBrightness(lastBrightness);
|
||||
}
|
||||
else
|
||||
{
|
||||
MATRIX_OFF = true;
|
||||
lastBrightness = BRIGHTNESS;
|
||||
DisplayManager.setBrightness(0);
|
||||
}
|
||||
|
||||
sender->setState(state);
|
||||
}
|
||||
|
||||
void onBrightnessCommand(uint8_t brightness, HALight *sender)
|
||||
{
|
||||
sender->setBrightness(brightness);
|
||||
if (AUTO_BRIGHTNESS)
|
||||
return;
|
||||
BRIGHTNESS = brightness;
|
||||
lastBrightness = brightness;
|
||||
saveSettings();
|
||||
DisplayManager.setBrightness(brightness);
|
||||
}
|
||||
|
||||
void onMqttMessage(const char *topic, const uint8_t *payload, uint16_t length)
|
||||
{
|
||||
String strTopic = String(topic);
|
||||
String strPayload = String((const char *)payload).substring(0, length);
|
||||
|
||||
if (strTopic == MQTT_PREFIX + "/notify")
|
||||
{
|
||||
if (payload[0] != '{' || payload[length - 1] != '}')
|
||||
{
|
||||
return;
|
||||
}
|
||||
DisplayManager.generateNotification(strPayload);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strTopic == MQTT_PREFIX + "/timer")
|
||||
{
|
||||
DisplayManager.gererateTimer(strPayload);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strTopic == MQTT_PREFIX + "/notify/dismiss")
|
||||
{
|
||||
DisplayManager.dismissNotify();
|
||||
return;
|
||||
}
|
||||
|
||||
else if (strTopic.startsWith(MQTT_PREFIX + "/custom"))
|
||||
{
|
||||
String topic_str = topic;
|
||||
String prefix = MQTT_PREFIX + "/custom/";
|
||||
if (topic_str.startsWith(prefix))
|
||||
{
|
||||
topic_str = topic_str.substring(prefix.length());
|
||||
}
|
||||
uint16_t id = topic_str.toInt();
|
||||
DisplayManager.generateCustomPage(id, strPayload);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void onMqttConnected()
|
||||
{
|
||||
String prefix = MQTT_PREFIX;
|
||||
mqtt.subscribe((prefix + String("/brightness")).c_str());
|
||||
mqtt.subscribe((prefix + String("/notify/dismiss")).c_str());
|
||||
mqtt.subscribe((prefix + String("/notify")).c_str());
|
||||
mqtt.subscribe((prefix + String("/timer")).c_str());
|
||||
mqtt.subscribe((prefix + String("/custom/#")).c_str());
|
||||
Serial.println("MQTT Connected");
|
||||
}
|
||||
|
||||
void connect()
|
||||
{
|
||||
mqtt.onMessage(onMqttMessage);
|
||||
mqtt.onConnected(onMqttConnected);
|
||||
if (MQTT_USER == "" || MQTT_PASS == "")
|
||||
{
|
||||
Serial.println("Connecting to MQTT");
|
||||
mqtt.begin(MQTT_HOST.c_str(), MQTT_PORT, nullptr, nullptr, MQTT_PREFIX.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("Connecting to MQTT");
|
||||
mqtt.begin(MQTT_HOST.c_str(), MQTT_PORT, MQTT_USER.c_str(), MQTT_PASS.c_str(), MQTT_PREFIX.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void MQTTManager_::setup()
|
||||
{
|
||||
byte mac[6];
|
||||
WiFi.macAddress(mac);
|
||||
|
||||
if (HA_DISCOVERY)
|
||||
{
|
||||
Serial.println("Starting Homeassistant discorvery");
|
||||
|
||||
device.setUniqueId(mac, sizeof(mac));
|
||||
device.setName(MQTT_PREFIX.c_str());
|
||||
device.setSoftwareVersion(VERSION);
|
||||
device.setManufacturer("Blueforcer");
|
||||
device.setModel("AWTRIX Light");
|
||||
device.setAvailability(true);
|
||||
Matrix.setIcon("mdi:lightbulb");
|
||||
Matrix.setName("Matrix");
|
||||
Matrix.onStateCommand(onStateCommand);
|
||||
Matrix.onBrightnessCommand(onBrightnessCommand);
|
||||
Matrix.onRGBColorCommand(onRGBColorCommand);
|
||||
Matrix.setCurrentState(true);
|
||||
Matrix.setBRIGHTNESS(BRIGHTNESS);
|
||||
|
||||
HALight::RGBColor color;
|
||||
color.red = (TEXTCOLOR_565 >> 11) << 3;
|
||||
color.green = ((TEXTCOLOR_565 >> 5) & 0x3F) << 2;
|
||||
color.blue = (TEXTCOLOR_565 & 0x1F) << 3;
|
||||
Matrix.setCurrentRGBColor(color);
|
||||
Matrix.setState(true, true);
|
||||
|
||||
BriMode.setOptions("Manual;Auto"); // use semicolons as separator of options
|
||||
BriMode.onCommand(onSelectCommand);
|
||||
BriMode.setIcon("mdi:brightness-auto"); // optional
|
||||
BriMode.setName("Brightness mode"); // optional
|
||||
BriMode.setState(AUTO_BRIGHTNESS, true);
|
||||
|
||||
dismiss.setIcon("mdi:bell-off");
|
||||
dismiss.setName("Dismiss notification");
|
||||
nextApp.setIcon("mdi:arrow-right-bold");
|
||||
nextApp.setName("Next app");
|
||||
prevApp.setIcon("mdi:arrow-left-bold");
|
||||
prevApp.setName("Previous app");
|
||||
|
||||
dismiss.onCommand(onButtonCommand);
|
||||
nextApp.onCommand(onButtonCommand);
|
||||
prevApp.onCommand(onButtonCommand);
|
||||
|
||||
curApp.setIcon("mdi:apps");
|
||||
curApp.setName("Current app");
|
||||
|
||||
temperature.setIcon("mdi:thermometer");
|
||||
temperature.setName("Temperature");
|
||||
temperature.setUnitOfMeasurement("°C");
|
||||
|
||||
humidity.setIcon("mdi:water-percent");
|
||||
humidity.setName("humidity");
|
||||
humidity.setUnitOfMeasurement("%");
|
||||
|
||||
battery.setIcon("mdi:battery-90");
|
||||
battery.setName("Battery");
|
||||
battery.setUnitOfMeasurement("%");
|
||||
|
||||
illuminance.setIcon("mdi:sun-wireless");
|
||||
illuminance.setName("Illuminance");
|
||||
illuminance.setUnitOfMeasurement("lx");
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("Homeassistant discovery disabled");
|
||||
mqtt.disableHA();
|
||||
}
|
||||
connect();
|
||||
}
|
||||
|
||||
void MQTTManager_::tick()
|
||||
{
|
||||
if (MQTT_HOST != "")
|
||||
{
|
||||
mqtt.loop();
|
||||
}
|
||||
}
|
||||
|
||||
void MQTTManager_::publish(const char *topic, const char *payload)
|
||||
{
|
||||
if (!mqtt.isConnected())
|
||||
return;
|
||||
char result[100];
|
||||
strcpy(result, MQTT_PREFIX.c_str());
|
||||
strcat(result, "/");
|
||||
strcat(result, topic);
|
||||
mqtt.publish(result, payload, false);
|
||||
}
|
||||
|
||||
void MQTTManager_::setCurrentApp(String value)
|
||||
{
|
||||
if (HA_DISCOVERY)
|
||||
curApp.setValue(value.c_str());
|
||||
}
|
||||
|
||||
void MQTTManager_::sendStats()
|
||||
{
|
||||
if (HA_DISCOVERY)
|
||||
{
|
||||
char buffer[5];
|
||||
snprintf(buffer, 5, "%d", BATTERY_PERCENT); // Formatieren von BATTERY_PERCENT als Integer
|
||||
battery.setValue(buffer); // Senden von BATTERY_PERCENT als const char*
|
||||
|
||||
snprintf(buffer, 5, "%.0f", CURRENT_TEMP); // Formatieren von CURRENT_TEMP als Float ohne Nachkommastellen
|
||||
temperature.setValue(buffer); // Senden von CURRENT_TEMP als const char*
|
||||
|
||||
snprintf(buffer, 5, "%.0f", CURRENT_HUM); // Formatieren von CURRENT_HUM als Float ohne Nachkommastellen
|
||||
humidity.setValue(buffer); // Senden von CURRENT_HUM als const char*
|
||||
|
||||
snprintf(buffer, 5, "%.0f", CURRENT_LUX); // Formatieren von CURRENT_LUX als Double ohne Nachkommastellen
|
||||
illuminance.setValue(buffer); // Senden von CURRENT_LUX als const char*
|
||||
|
||||
BriMode.setState(AUTO_BRIGHTNESS, true);
|
||||
Matrix.setBRIGHTNESS(BRIGHTNESS);
|
||||
Matrix.setState(!MATRIX_OFF, false);
|
||||
}
|
||||
}
|
||||
22
src/MQTTManager.h
Normal file
22
src/MQTTManager.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef MQTTManager_h
|
||||
#define MQTTManager_h
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
class MQTTManager_
|
||||
{
|
||||
private:
|
||||
MQTTManager_() = default;
|
||||
|
||||
public:
|
||||
static MQTTManager_ &getInstance();
|
||||
void setup();
|
||||
void tick();
|
||||
void publish(const char *topic, const char *payload);
|
||||
void setCurrentApp(String value);
|
||||
void sendStats();
|
||||
};
|
||||
|
||||
extern MQTTManager_ &MQTTManager;
|
||||
|
||||
#endif
|
||||
240
src/MenuManager.cpp
Normal file
240
src/MenuManager.cpp
Normal file
@@ -0,0 +1,240 @@
|
||||
#include <MenuManager.h>
|
||||
#include <Arduino.h>
|
||||
#include <AudioManager.h>
|
||||
#include <Settings.h>
|
||||
#include <ServerManager.h>
|
||||
#include <DisplayManager.h>
|
||||
|
||||
String menuText;
|
||||
int menuSelection;
|
||||
|
||||
int8_t menuIndex = 0;
|
||||
int8_t stationIndex = 0;
|
||||
bool isPlayingRadio = false;
|
||||
|
||||
enum MenuState
|
||||
{
|
||||
MainMenu,
|
||||
RadioMenu,
|
||||
StationSelection,
|
||||
PlayingStation,
|
||||
Reset,
|
||||
Volume,
|
||||
Brightness,
|
||||
FPS,
|
||||
Color
|
||||
};
|
||||
|
||||
const char *menuItems[] = {
|
||||
"BRIGHT",
|
||||
"FPS",
|
||||
"COLOR",
|
||||
"RESET"};
|
||||
|
||||
byte menuItemCount = 4;
|
||||
|
||||
MenuState currentState = MainMenu;
|
||||
|
||||
uint16_t textColors[] = {
|
||||
0xFFFF, // White
|
||||
0xF800, // Red
|
||||
0xF812, // Dark orange
|
||||
0xF81F, // Yellow
|
||||
0x881F, // Dark green
|
||||
0x001F, // Blue
|
||||
0x04FF, // Light blue
|
||||
0x07FC, // Cyan
|
||||
0x07E2, // Seafoam green
|
||||
0xAFE0, // Light green
|
||||
0xFFE0, // Light yellow
|
||||
0xFD60, // Dark yellow
|
||||
0xFBC0}; // Pink
|
||||
|
||||
uint8_t currentColor;
|
||||
|
||||
MenuManager_ &MenuManager_::getInstance()
|
||||
{
|
||||
static MenuManager_ instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Initialize the global shared instance
|
||||
MenuManager_ &MenuManager = MenuManager.getInstance();
|
||||
|
||||
String MenuManager_::menutext()
|
||||
{
|
||||
if (currentState == MainMenu)
|
||||
{
|
||||
return (menuItems[menuIndex]);
|
||||
}
|
||||
else if (currentState == Brightness)
|
||||
{
|
||||
if (AUTO_BRIGHTNESS)
|
||||
{
|
||||
return ("AUTO");
|
||||
}
|
||||
else
|
||||
{
|
||||
return (String(BRIGHTNESS_PERCENT) + "%");
|
||||
}
|
||||
}
|
||||
else if (currentState == FPS)
|
||||
{
|
||||
return String(MATRIX_FPS) + " FPS";
|
||||
}
|
||||
else if (currentState == Color)
|
||||
{
|
||||
DisplayManager.setTextColor(textColors[currentColor]);
|
||||
String colorStr = String(textColors[currentColor], HEX);
|
||||
while (colorStr.length() < 4)
|
||||
{
|
||||
colorStr = "0" + colorStr;
|
||||
}
|
||||
return colorStr;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void MenuManager_::rightButton()
|
||||
{
|
||||
if (!inMenu)
|
||||
return;
|
||||
if (currentState == MainMenu)
|
||||
{
|
||||
menuIndex++;
|
||||
if (menuIndex > menuItemCount - 1)
|
||||
{
|
||||
menuIndex = 0; // Wrap around to the first menu item
|
||||
}
|
||||
}
|
||||
else if (currentState == RadioMenu || currentState == StationSelection)
|
||||
{
|
||||
AudioManager.nextStation();
|
||||
}
|
||||
else if (currentState == Brightness)
|
||||
{
|
||||
if (!AUTO_BRIGHTNESS)
|
||||
{
|
||||
++BRIGHTNESS_PERCENT;
|
||||
if (BRIGHTNESS_PERCENT > 100)
|
||||
BRIGHTNESS_PERCENT = 1;
|
||||
BRIGHTNESS = map(BRIGHTNESS_PERCENT, 0, 100, 0, 255);
|
||||
DisplayManager.setBrightness(BRIGHTNESS);
|
||||
}
|
||||
}
|
||||
else if (currentState == FPS)
|
||||
{
|
||||
if (MATRIX_FPS < 30)
|
||||
++MATRIX_FPS;
|
||||
}
|
||||
else if (currentState == Color)
|
||||
{
|
||||
int arraySize = sizeof(textColors) / sizeof(textColors[0]);
|
||||
currentColor = (currentColor + 1) % arraySize;
|
||||
}
|
||||
}
|
||||
|
||||
void MenuManager_::leftButton()
|
||||
{
|
||||
if (!inMenu)
|
||||
return;
|
||||
if (currentState == MainMenu)
|
||||
{
|
||||
menuIndex--;
|
||||
if (menuIndex < 0)
|
||||
{
|
||||
menuIndex = menuItemCount - 1; // Wrap around to the last menu item
|
||||
}
|
||||
}
|
||||
else if (currentState == RadioMenu || currentState == StationSelection)
|
||||
{
|
||||
AudioManager.prevStation();
|
||||
}
|
||||
else if (currentState == Brightness)
|
||||
{
|
||||
if (!AUTO_BRIGHTNESS)
|
||||
{
|
||||
--BRIGHTNESS_PERCENT;
|
||||
if (BRIGHTNESS_PERCENT < 1)
|
||||
BRIGHTNESS_PERCENT = 100;
|
||||
BRIGHTNESS = map(BRIGHTNESS_PERCENT, 0, 100, 0, 255);
|
||||
DisplayManager.setBrightness(BRIGHTNESS);
|
||||
}
|
||||
}
|
||||
else if (currentState == FPS)
|
||||
{
|
||||
if (MATRIX_FPS > 15)
|
||||
--MATRIX_FPS;
|
||||
}
|
||||
else if (currentState == Color)
|
||||
{
|
||||
int arraySize = sizeof(textColors) / sizeof(textColors[0]);
|
||||
currentColor = (currentColor - 1 + arraySize) % arraySize;
|
||||
}
|
||||
}
|
||||
|
||||
void MenuManager_::selectButton()
|
||||
{
|
||||
if (!inMenu)
|
||||
return;
|
||||
if (currentState == MainMenu)
|
||||
{
|
||||
if (menuIndex == 0) // BRIGHT
|
||||
{
|
||||
BRIGHTNESS_PERCENT = map(BRIGHTNESS, 0, 255, 0, 100);
|
||||
currentState = Brightness;
|
||||
}
|
||||
else if (menuIndex == 1) // RESET
|
||||
{
|
||||
currentState = FPS;
|
||||
}
|
||||
else if (menuIndex == 2) // COLOR
|
||||
{
|
||||
currentState = Color;
|
||||
}
|
||||
else if (menuIndex == 3) // FPS
|
||||
{
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
else if (currentState == StationSelection)
|
||||
{
|
||||
AudioManager.startRadioStation(AudioManager.getCurrentRadioStation());
|
||||
}
|
||||
else if (currentState == Brightness)
|
||||
{
|
||||
AUTO_BRIGHTNESS = !AUTO_BRIGHTNESS;
|
||||
}
|
||||
}
|
||||
|
||||
void MenuManager_::selectButtonLong()
|
||||
{
|
||||
if (inMenu)
|
||||
{
|
||||
if (currentState == Brightness)
|
||||
{
|
||||
BRIGHTNESS = map(BRIGHTNESS_PERCENT, 0, 100, 0, 255);
|
||||
saveSettings();
|
||||
}
|
||||
else if (currentState == FPS)
|
||||
{
|
||||
DisplayManager.setFPS(MATRIX_FPS);
|
||||
saveSettings();
|
||||
}
|
||||
else if (currentState == Color)
|
||||
{
|
||||
TEXTCOLOR_565 = textColors[currentColor];
|
||||
saveSettings();
|
||||
}
|
||||
else if (currentState == MainMenu)
|
||||
{
|
||||
inMenu = false;
|
||||
}
|
||||
|
||||
currentState = MainMenu;
|
||||
}
|
||||
else
|
||||
{
|
||||
inMenu = true;
|
||||
}
|
||||
}
|
||||
24
src/MenuManager.h
Normal file
24
src/MenuManager.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef MenuManager_h
|
||||
#define MenuManager_h
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
|
||||
class MenuManager_
|
||||
{
|
||||
private:
|
||||
MenuManager_() = default;
|
||||
|
||||
public:
|
||||
static MenuManager_ &getInstance();
|
||||
bool inMenu;
|
||||
String menutext();
|
||||
void rightButton();
|
||||
void leftButton();
|
||||
void selectButton();
|
||||
void selectButtonLong();
|
||||
};
|
||||
|
||||
extern MenuManager_ &MenuManager;
|
||||
|
||||
#endif
|
||||
276
src/PeripheryManager.cpp
Normal file
276
src/PeripheryManager.cpp
Normal file
@@ -0,0 +1,276 @@
|
||||
#include <PeripheryManager.h>
|
||||
#include <melody_player.h>
|
||||
#include <melody_factory.h>
|
||||
#include "Settings.h"
|
||||
#include "DisplayManager.h"
|
||||
#include "MQTTManager.h"
|
||||
#include <ArduinoJson.h>
|
||||
#include <LittleFS.h>
|
||||
#include <LightDependentResistor.h>
|
||||
#include <MenuManager.h>
|
||||
|
||||
#define SOUND_OFF false
|
||||
#define BATTERY_PIN 34
|
||||
#define BUZZER_PIN 15
|
||||
#define LDR_PIN 35
|
||||
#define BUTTON_UP_PIN 26
|
||||
#define BUTTON_DOWN_PIN 14
|
||||
#define BUTTON_SELECT_PIN 27
|
||||
|
||||
Adafruit_SHT31 sht31;
|
||||
EasyButton button_left(BUTTON_UP_PIN);
|
||||
EasyButton button_right(BUTTON_DOWN_PIN);
|
||||
EasyButton button_select(BUTTON_SELECT_PIN);
|
||||
MelodyPlayer player(BUZZER_PIN, LOW);
|
||||
|
||||
#define USED_PHOTOCELL LightDependentResistor::GL5516
|
||||
|
||||
LightDependentResistor photocell(LDR_PIN,
|
||||
10000,
|
||||
USED_PHOTOCELL,
|
||||
10,
|
||||
10);
|
||||
|
||||
int readIndex = 0;
|
||||
int sampleIndex = 0;
|
||||
unsigned long previousMillis_BatTempHum = 0;
|
||||
unsigned long previousMillis_LDR = 0;
|
||||
const unsigned long interval_BatTempHum = 10000;
|
||||
const unsigned long interval_LDR = 100;
|
||||
int total = 0;
|
||||
|
||||
const int LDRReadings = 10;
|
||||
int TotalLDRReadings[LDRReadings];
|
||||
float sampleSum = 0.0;
|
||||
float sampleAverage = 0.0;
|
||||
float brightnessPercent = 0.0;
|
||||
|
||||
unsigned long startTime;
|
||||
|
||||
// The getter for the instantiated singleton instance
|
||||
PeripheryManager_ &PeripheryManager_::getInstance()
|
||||
{
|
||||
static PeripheryManager_ instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Initialize the global shared instance
|
||||
PeripheryManager_ &PeripheryManager = PeripheryManager.getInstance();
|
||||
|
||||
void left_button_pressed()
|
||||
{
|
||||
DisplayManager.leftButton();
|
||||
MenuManager.leftButton();
|
||||
}
|
||||
|
||||
void right_button_pressed()
|
||||
{
|
||||
DisplayManager.rightButton();
|
||||
MenuManager.rightButton();
|
||||
}
|
||||
|
||||
void select_button_pressed()
|
||||
{
|
||||
DisplayManager.selectButton();
|
||||
MenuManager.selectButton();
|
||||
}
|
||||
|
||||
void select_button_pressed_long()
|
||||
{
|
||||
DisplayManager.selectButtonLong();
|
||||
MenuManager.selectButtonLong();
|
||||
}
|
||||
|
||||
void select_button_tripple()
|
||||
{
|
||||
if (MATRIX_OFF)
|
||||
{
|
||||
DisplayManager.MatrixState(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayManager.MatrixState(false);
|
||||
}
|
||||
}
|
||||
|
||||
void PeripheryManager_::playBootSound()
|
||||
{
|
||||
if (SOUND_OFF)
|
||||
return;
|
||||
const int nNotes = 6;
|
||||
String notes[nNotes] = {"E5", "C5", "G4", "E4", "G4", "C5"};
|
||||
const int timeUnit = 150;
|
||||
// create a melody
|
||||
Melody melody = MelodyFactory.load("Nice Melody", timeUnit, notes, nNotes);
|
||||
player.playAsync(melody);
|
||||
}
|
||||
|
||||
void PeripheryManager_::stopSound()
|
||||
{
|
||||
player.stop();
|
||||
}
|
||||
|
||||
void PeripheryManager_::playFromFile(String file)
|
||||
{
|
||||
if (SOUND_OFF)
|
||||
return;
|
||||
Melody melody = MelodyFactory.loadRtttlFile(file);
|
||||
player.playAsync(melody);
|
||||
}
|
||||
|
||||
bool PeripheryManager_::isPlaying()
|
||||
{
|
||||
return player.isPlaying();
|
||||
}
|
||||
|
||||
void fistStart()
|
||||
{
|
||||
|
||||
uint16_t ADCVALUE = analogRead(BATTERY_PIN);
|
||||
Serial.println(ADCVALUE);
|
||||
BATTERY_PERCENT = min((int)map(ADCVALUE, 2160, 2800, 0, 100), 100);
|
||||
sht31.readBoth(&CURRENT_TEMP, &CURRENT_HUM);
|
||||
|
||||
uint16_t LDRVALUE = analogRead(LDR_PIN);
|
||||
brightnessPercent = LDRVALUE / 4095.0 * 100.0;
|
||||
int brightness = map(brightnessPercent, 0, 100, 10, 120);
|
||||
DisplayManager.setBrightness(brightness);
|
||||
}
|
||||
|
||||
void PeripheryManager_::setup()
|
||||
{
|
||||
pinMode(LDR_PIN, INPUT);
|
||||
pinMode(BUZZER_PIN, OUTPUT);
|
||||
digitalWrite(BUZZER_PIN, LOW);
|
||||
button_left.begin();
|
||||
button_right.begin();
|
||||
button_select.begin();
|
||||
button_left.onPressed(left_button_pressed);
|
||||
button_right.onPressed(right_button_pressed);
|
||||
button_select.onPressed(select_button_pressed);
|
||||
button_select.onPressedFor(1000, select_button_pressed_long);
|
||||
button_select.onSequence(2, 300, select_button_tripple);
|
||||
Wire.begin(21, 22);
|
||||
sht31.begin(0x44);
|
||||
photocell.setPhotocellPositionOnGround(false);
|
||||
fistStart();
|
||||
}
|
||||
|
||||
void PeripheryManager_::tick()
|
||||
{
|
||||
button_left.read();
|
||||
button_right.read();
|
||||
button_select.read();
|
||||
|
||||
// Auslesen von Batterie-, Temperatur- und Luftfeuchtigkeitswerten alle 10 Sekunden
|
||||
unsigned long currentMillis_BatTempHum = millis();
|
||||
if (currentMillis_BatTempHum - previousMillis_BatTempHum >= interval_BatTempHum)
|
||||
{
|
||||
previousMillis_BatTempHum = currentMillis_BatTempHum;
|
||||
uint16_t ADCVALUE = analogRead(BATTERY_PIN);
|
||||
BATTERY_PERCENT = min((int)map(ADCVALUE, 2160, 2800, 0, 100), 100);
|
||||
CURRENT_LUX = (roundf(photocell.getSmoothedLux() * 1000) / 1000);
|
||||
sht31.readBoth(&CURRENT_TEMP, &CURRENT_HUM);
|
||||
CURRENT_TEMP -= 9.0;
|
||||
checkAlarms();
|
||||
MQTTManager.sendStats();
|
||||
uint32_t freeHeap = esp_get_free_heap_size(); // Freien Heap-Speicher in Bytes erhalten
|
||||
float freeHeapKB = freeHeap / 1024.0; // Freien Heap-Speicher in Kilobytes umrechnen
|
||||
}
|
||||
|
||||
// Auslesen des LDR-Werts alle 500 ms
|
||||
unsigned long currentMillis_LDR = millis();
|
||||
if (currentMillis_LDR - previousMillis_LDR >= interval_LDR && AUTO_BRIGHTNESS)
|
||||
{
|
||||
previousMillis_LDR = currentMillis_LDR;
|
||||
TotalLDRReadings[sampleIndex] = analogRead(LDR_PIN);
|
||||
sampleIndex = (sampleIndex + 1) % LDRReadings;
|
||||
sampleSum = 0.0;
|
||||
for (int i = 0; i < LDRReadings; i++)
|
||||
{
|
||||
sampleSum += TotalLDRReadings[i];
|
||||
}
|
||||
sampleAverage = sampleSum / (float)LDRReadings;
|
||||
|
||||
brightnessPercent = sampleAverage / 4095.0 * 100.0;
|
||||
int brightness = map(brightnessPercent, 0, 100, 10, 120);
|
||||
BRIGHTNESS = map(brightnessPercent, 0, 100, 0, 255);
|
||||
DisplayManager.setBrightness(brightness);
|
||||
}
|
||||
}
|
||||
|
||||
void readUptime()
|
||||
{
|
||||
unsigned long currentTime = millis();
|
||||
unsigned long elapsedTime = currentTime - startTime;
|
||||
int hours = (elapsedTime / 1000) / 3600;
|
||||
int minutes = ((elapsedTime / 1000) % 3600) / 60;
|
||||
int seconds = (elapsedTime / 1000) % 60;
|
||||
char timeString[10];
|
||||
sprintf(timeString, "%02d:%02d:%02d", hours, minutes, seconds);
|
||||
}
|
||||
|
||||
const int MIN_ALARM_INTERVAL = 60; // 1 Minute
|
||||
time_t lastAlarmTime = 0;
|
||||
|
||||
void PeripheryManager_::checkAlarms()
|
||||
{
|
||||
File file = LittleFS.open("/alarms.json", "r");
|
||||
if (!file)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DynamicJsonDocument doc(file.size() * 1.33);
|
||||
DeserializationError error = deserializeJson(doc, file);
|
||||
if (error)
|
||||
{
|
||||
Serial.println("Failed to read Alarm file");
|
||||
return;
|
||||
}
|
||||
JsonArray alarms = doc["alarms"];
|
||||
file.close();
|
||||
|
||||
time_t now1 = time(nullptr);
|
||||
struct tm *timeInfo;
|
||||
timeInfo = localtime(&now1);
|
||||
int currentHour = timeInfo->tm_hour;
|
||||
int currentMinute = timeInfo->tm_min;
|
||||
int currentDay = timeInfo->tm_wday - 1;
|
||||
|
||||
for (JsonObject alarm : alarms)
|
||||
{
|
||||
int alarmHour = alarm["hour"];
|
||||
int alarmMinute = alarm["minute"];
|
||||
String alarmDays = alarm["days"];
|
||||
|
||||
if (currentHour == alarmHour && currentMinute == alarmMinute && alarmDays.indexOf(String(currentDay)) != -1)
|
||||
{
|
||||
if (difftime(now1, lastAlarmTime) < MIN_ALARM_INTERVAL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ALARM_ACTIVE = true;
|
||||
lastAlarmTime = now1;
|
||||
|
||||
if (alarm.containsKey("sound"))
|
||||
{
|
||||
ALARM_SOUND = alarm["sound"].as<String>();
|
||||
}
|
||||
else
|
||||
{
|
||||
ALARM_SOUND = "";
|
||||
}
|
||||
|
||||
if (alarm.containsKey("snooze"))
|
||||
{
|
||||
SNOOZE_TIME = alarm["snooze"].as<uint8_t>();
|
||||
}
|
||||
else
|
||||
{
|
||||
SNOOZE_TIME = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/PeripheryManager.h
Normal file
36
src/PeripheryManager.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef PeripheryManager_h
|
||||
#define PeripheryManager_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <EasyButton.h>
|
||||
#include "Adafruit_SHT31.h"
|
||||
|
||||
class PeripheryManager_
|
||||
{
|
||||
private:
|
||||
PeripheryManager_() = default;
|
||||
void checkAlarms();
|
||||
const int BatReadings = 10;
|
||||
uint16_t TotalBatReadings[10];
|
||||
int readIndex = 0;
|
||||
uint16_t total = 0;
|
||||
uint16_t average = 0;
|
||||
const int LDRReadings = 30;
|
||||
uint16_t TotalLDRReadings[30];
|
||||
int sampleIndex = 0;
|
||||
unsigned long previousMillis = 0;
|
||||
const unsigned long interval = 1000;
|
||||
|
||||
public:
|
||||
static PeripheryManager_ &getInstance();
|
||||
void setup();
|
||||
void tick();
|
||||
void playBootSound();
|
||||
void playFromFile(String file);
|
||||
bool isPlaying();
|
||||
void stopSound();
|
||||
};
|
||||
|
||||
extern PeripheryManager_ &PeripheryManager;
|
||||
|
||||
#endif
|
||||
225
src/ServerManager.cpp
Normal file
225
src/ServerManager.cpp
Normal file
@@ -0,0 +1,225 @@
|
||||
#include "ServerManager.h"
|
||||
#include "Settings.h"
|
||||
#include <Webserver.h>
|
||||
#include <esp-fs-webserver.h>
|
||||
#include "icondownloader.h"
|
||||
#include <Update.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include "SPI.h"
|
||||
#include <LittleFS.h>
|
||||
#include <WiFi.h>
|
||||
#include "DisplayManager.h"
|
||||
|
||||
#include <Preferences.h>
|
||||
|
||||
Preferences preferences;
|
||||
|
||||
|
||||
WebServer server(80);
|
||||
FSWebServer mws(LittleFS, server);
|
||||
bool FSOPEN;
|
||||
void startLittleFS()
|
||||
{
|
||||
|
||||
if (LittleFS.begin())
|
||||
{
|
||||
LittleFS.mkdir("/MELODIES");
|
||||
LittleFS.mkdir("/ICONS");
|
||||
FSOPEN = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("ERROR on mounting LittleFS. It will be formmatted!");
|
||||
LittleFS.format();
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
|
||||
// The getter for the instantiated singleton instance
|
||||
ServerManager_ &ServerManager_::getInstance()
|
||||
{
|
||||
static ServerManager_ instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Initialize the global shared instance
|
||||
ServerManager_ &ServerManager = ServerManager.getInstance();
|
||||
|
||||
void versionHandler()
|
||||
{
|
||||
WebServerClass *webRequest = mws.getRequest();
|
||||
webRequest->send(200, "text/plain", VERSION);
|
||||
}
|
||||
|
||||
void saveHandler()
|
||||
{
|
||||
WebServerClass *webRequest = mws.getRequest();
|
||||
Serial.println("Save");
|
||||
ServerManager.getInstance().loadSettings();
|
||||
webRequest->send(200);
|
||||
}
|
||||
|
||||
void ServerManager_::setup()
|
||||
{
|
||||
|
||||
if (!local_IP.fromString(NET_IP) || !gateway.fromString(NET_GW) || !subnet.fromString(NET_SN) || !primaryDNS.fromString(NET_PDNS) || !secondaryDNS.fromString(NET_SDNS))
|
||||
NET_STATIC = false;
|
||||
if (NET_STATIC)
|
||||
{
|
||||
WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS);
|
||||
}
|
||||
IPAddress myIP = mws.startWiFi(150000, "AWTRIX LIGHT", "12345678");
|
||||
isConnected = !(myIP == IPAddress(192, 168, 4, 1));
|
||||
Serial.println(myIP.toString());
|
||||
|
||||
if (isConnected)
|
||||
{
|
||||
mws.addOptionBox("Network");
|
||||
mws.addOption("Static IP", NET_STATIC);
|
||||
mws.addOption("Local IP", NET_IP);
|
||||
mws.addOption("Gateway", NET_GW);
|
||||
mws.addOption("Subnet", NET_SN);
|
||||
mws.addOption("Primary DNS", NET_PDNS);
|
||||
mws.addOption("Secondary DNS", NET_SDNS);
|
||||
mws.addOptionBox("MQTT");
|
||||
mws.addOption("Broker", MQTT_HOST);
|
||||
mws.addOption("Port", MQTT_PORT);
|
||||
mws.addOption("Username", MQTT_USER);
|
||||
mws.addOption("Password", MQTT_PASS);
|
||||
mws.addOption("Prefix", MQTT_PREFIX);
|
||||
mws.addOption("Homeassistant Discovery", HA_DISCOVERY);
|
||||
mws.addOptionBox("Time");
|
||||
mws.addOption("Show seconds", SHOW_SECONDS);
|
||||
mws.addOption("Show weekday", SHOW_WEEKDAY);
|
||||
mws.addOption("NTP Server", NTP_SERVER);
|
||||
mws.addOption("Timezone", NTP_TZ);
|
||||
mws.addHTML("<p>Find your timezone at <a href='https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv' target='_blank' rel='noopener noreferrer'>posix_tz_db</a>.</p>", "tz_link");
|
||||
mws.addOptionBox("Icons");
|
||||
mws.addHTML(custom_html, "icon_html");
|
||||
mws.addCSS(custom_css);
|
||||
mws.addJavascript(custom_script);
|
||||
mws.addOptionBox("General");
|
||||
mws.addOption("Duration per Page", TIME_PER_FRAME);
|
||||
mws.addOption("Transistion duration", TIME_PER_TRANSITION);
|
||||
mws.addOption("Uppercase letters", UPPERCASE_LETTERS);
|
||||
mws.addOption("Show date", SHOW_DATE);
|
||||
mws.addOption("Show temperature", SHOW_TEMP);
|
||||
mws.addOption("Show humidity", SHOW_HUM);
|
||||
mws.addOption("Show battery", SHOW_BATTERY);
|
||||
mws.addHandler("/save", HTTP_GET, saveHandler);
|
||||
}
|
||||
|
||||
mws.addHandler("/version", HTTP_GET, versionHandler);
|
||||
mws.begin();
|
||||
|
||||
if (!MDNS.begin(MQTT_PREFIX.c_str()))
|
||||
{
|
||||
Serial.println("Error starting mDNS");
|
||||
return;
|
||||
}
|
||||
|
||||
configTzTime(NTP_TZ.c_str(), NTP_SERVER.c_str());
|
||||
tm timeInfo;
|
||||
getLocalTime(&timeInfo);
|
||||
}
|
||||
|
||||
void ServerManager_::tick()
|
||||
{
|
||||
mws.run();
|
||||
}
|
||||
|
||||
uint16_t stringToColor(const String &str)
|
||||
{
|
||||
// Aufteilen des Strings in seine Bestandteile
|
||||
int comma1 = str.indexOf(',');
|
||||
int comma2 = str.lastIndexOf(',');
|
||||
if (comma1 < 0 || comma2 < 0 || comma2 == comma1)
|
||||
{
|
||||
// Ungültiges Format
|
||||
return 0xFFFF;
|
||||
}
|
||||
String rStr = str.substring(0, comma1);
|
||||
String gStr = str.substring(comma1 + 1, comma2);
|
||||
String bStr = str.substring(comma2 + 1);
|
||||
|
||||
// Konvertieren der Werte von Strings zu Zahlen
|
||||
int r = rStr.toInt();
|
||||
int g = gStr.toInt();
|
||||
int b = bStr.toInt();
|
||||
|
||||
// Sicherheitsabfrage: Werte müssen zwischen 0 und 255 liegen
|
||||
if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255)
|
||||
{
|
||||
return 0xFFFF;
|
||||
}
|
||||
|
||||
// Konvertieren der Werte zu 5-6-5 Bitformat
|
||||
uint16_t color = ((r >> 3) << 11) | ((g >> 2) & 0x3F) << 5 | (b >> 3);
|
||||
return color;
|
||||
}
|
||||
|
||||
String colorToString(uint16_t color)
|
||||
{
|
||||
// Konvertieren der Farbwerte von 5-6-5 Bitformat zu 8 Bit
|
||||
uint8_t r = (color >> 11) << 3;
|
||||
uint8_t g = ((color >> 5) & 0x3F) << 2;
|
||||
uint8_t b = (color & 0x1F) << 3;
|
||||
|
||||
// Sicherheitsabfrage: Werte müssen zwischen 0 und 255 liegen
|
||||
if (r > 255 || g > 255 || b > 255)
|
||||
{
|
||||
return "#FFFFFF";
|
||||
}
|
||||
|
||||
// Konvertieren der Farbwerte zu Strings und Zusammenführen
|
||||
String rStr = String(r);
|
||||
String gStr = String(g);
|
||||
String bStr = String(b);
|
||||
return rStr + "," + gStr + "," + bStr;
|
||||
}
|
||||
|
||||
void ServerManager_::loadSettings()
|
||||
{
|
||||
if (!FSOPEN)
|
||||
startLittleFS();
|
||||
|
||||
if (LittleFS.exists("/config.json"))
|
||||
{
|
||||
File file = LittleFS.open("/config.json", "r");
|
||||
DynamicJsonDocument doc(file.size() * 1.33);
|
||||
DeserializationError error = deserializeJson(doc, file);
|
||||
if (error)
|
||||
return;
|
||||
|
||||
NTP_SERVER = doc["NTP Server"].as<String>();
|
||||
NTP_TZ = doc["Timezone"].as<String>();
|
||||
MQTT_HOST = doc["Broker"].as<String>();
|
||||
MQTT_PORT = doc["Port"].as<uint16_t>();
|
||||
MQTT_USER = doc["Username"].as<String>();
|
||||
MQTT_PASS = doc["Password"].as<String>();
|
||||
MQTT_PREFIX = doc["Prefix"].as<String>();
|
||||
TIME_PER_FRAME = doc["Duration per Page"].as<int>();
|
||||
TIME_PER_TRANSITION = doc["Transistion duration"].as<int>();
|
||||
NET_STATIC = doc["Static IP"];
|
||||
HA_DISCOVERY = doc["Homeassistant Discovery"];
|
||||
NET_IP = doc["Local IP"].as<String>();
|
||||
NET_GW = doc["Gateway"].as<String>();
|
||||
NET_SN = doc["Subnet"].as<String>();
|
||||
NET_PDNS = doc["Primary DNS"].as<String>();
|
||||
NET_SDNS = doc["Secondary DNS"].as<String>();
|
||||
UPPERCASE_LETTERS = doc["Uppercase letters"];
|
||||
SHOW_SECONDS = doc["Show seconds"];
|
||||
SHOW_WEEKDAY = doc["Show weekday"];
|
||||
SHOW_DATE = doc["Show date"];
|
||||
SHOW_TEMP = doc["Show temperature"];
|
||||
SHOW_HUM = doc["Show humidity"];
|
||||
SHOW_BATTERY = doc["Show battery"];
|
||||
file.close();
|
||||
DisplayManager.setSettings();
|
||||
Serial.println(F("Configuration loaded"));
|
||||
return;
|
||||
}
|
||||
else
|
||||
Serial.println(F("Configuration file not exist"));
|
||||
return;
|
||||
}
|
||||
22
src/ServerManager.h
Normal file
22
src/ServerManager.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef ServerManager_h
|
||||
#define ServerManager_h
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
class ServerManager_
|
||||
{
|
||||
private:
|
||||
ServerManager_() = default;
|
||||
|
||||
public:
|
||||
static ServerManager_ &getInstance();
|
||||
void setup();
|
||||
void tick();
|
||||
void loadSettings();
|
||||
bool isConnected;
|
||||
void SaveSettings();
|
||||
};
|
||||
|
||||
extern ServerManager_ &ServerManager;
|
||||
|
||||
#endif
|
||||
81
src/Settings.cpp
Normal file
81
src/Settings.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#include "Settings.h"
|
||||
#include "Preferences.h"
|
||||
|
||||
Preferences Settings;
|
||||
|
||||
void loadSettings()
|
||||
{
|
||||
Settings.begin("awtrix", false);
|
||||
MATRIX_FPS = Settings.getUChar("FPS", 23);
|
||||
BRIGHTNESS = Settings.getUChar("BRI", 120);
|
||||
AUTO_BRIGHTNESS = Settings.getBool("ABRI", true);
|
||||
TEXTCOLOR_565 = Settings.getUInt("COL", 0xFFFF);
|
||||
Settings.end();
|
||||
}
|
||||
|
||||
void saveSettings()
|
||||
{
|
||||
Settings.begin("awtrix", false);
|
||||
Settings.putUChar("FPS", MATRIX_FPS);
|
||||
Settings.putUChar("BRI", BRIGHTNESS);
|
||||
Settings.putBool("ABRI", AUTO_BRIGHTNESS);
|
||||
Settings.putUInt("COL", TEXTCOLOR_565);
|
||||
Settings.end();
|
||||
}
|
||||
|
||||
IPAddress local_IP;
|
||||
IPAddress gateway;
|
||||
IPAddress subnet;
|
||||
IPAddress primaryDNS;
|
||||
IPAddress secondaryDNS;
|
||||
const char *VERSION = "0.30";
|
||||
String MQTT_HOST = "";
|
||||
uint16_t MQTT_PORT = 1883;
|
||||
String MQTT_USER;
|
||||
String MQTT_PASS;
|
||||
String MQTT_PREFIX = "AwtrixLight";
|
||||
String CITY = "Berlin,de";
|
||||
bool IO_BROKER = false;
|
||||
bool NET_STATIC = false;
|
||||
bool SHOW_DATE = true;
|
||||
bool SHOW_WEATHER = true;
|
||||
bool SHOW_BATTERY = true;
|
||||
bool SHOW_TEMP = true;
|
||||
bool SHOW_HUM = true;
|
||||
bool SHOW_SECONDS = true;
|
||||
bool SHOW_WEEKDAY = true;
|
||||
String NET_IP = "192.168.178.10";
|
||||
String NET_GW = "192.168.178.1";
|
||||
String NET_SN = "255.255.255.0";
|
||||
String NET_PDNS = "8.8.8.8";
|
||||
String NET_SDNS = "1.1.1.1";
|
||||
int TIME_PER_FRAME = 7000;
|
||||
uint8_t MATRIX_FPS = 23;
|
||||
int TIME_PER_TRANSITION = 500;
|
||||
String NTP_SERVER = "de.pool.ntp.org";
|
||||
String NTP_TZ = "CET-1CEST,M3.5.0,M10.5.0/3";
|
||||
bool HA_DISCOVERY = false;
|
||||
|
||||
// Periphery
|
||||
String CURRENT_APP;
|
||||
float CURRENT_TEMP;
|
||||
float CURRENT_HUM;
|
||||
float CURRENT_LUX;
|
||||
uint8_t BRIGHTNESS = 120;
|
||||
uint8_t BRIGHTNESS_PERCENT;
|
||||
uint8_t BATTERY_PERCENT;
|
||||
|
||||
String ALARM_SOUND;
|
||||
uint8_t SNOOZE_TIME;
|
||||
|
||||
String TIMER_SOUND;
|
||||
|
||||
// Matrix States
|
||||
bool AUTO_BRIGHTNESS = true;
|
||||
bool UPPERCASE_LETTERS = true;
|
||||
bool AP_MODE;
|
||||
bool MATRIX_OFF;
|
||||
bool TIMER_ACTIVE;
|
||||
bool ALARM_ACTIVE;
|
||||
uint16_t TEXTCOLOR_565 = 0xFFFF;
|
||||
|
||||
59
src/Settings.h
Normal file
59
src/Settings.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#ifndef SETTINGS_H
|
||||
#define SETTINGS_H
|
||||
#include <Arduino.h>
|
||||
|
||||
extern const char *VERSION;
|
||||
extern IPAddress local_IP;
|
||||
extern IPAddress gateway;
|
||||
extern IPAddress subnet;
|
||||
extern IPAddress primaryDNS;
|
||||
extern IPAddress secondaryDNS;
|
||||
extern String MQTT_HOST;
|
||||
extern uint16_t MQTT_PORT;
|
||||
extern String MQTT_USER;
|
||||
extern String MQTT_PASS;
|
||||
extern String MQTT_PREFIX;
|
||||
extern String CITY;
|
||||
extern bool IO_BROKER;
|
||||
extern bool NET_STATIC;
|
||||
extern bool SHOW_DATE;
|
||||
extern bool SHOW_WEATHER;
|
||||
extern bool SHOW_BATTERY;
|
||||
extern bool SHOW_TEMP;
|
||||
extern bool SHOW_HUM;
|
||||
extern bool SHOW_SECONDS;
|
||||
extern bool SHOW_WEEKDAY;
|
||||
extern String NET_IP;
|
||||
extern String NET_GW;
|
||||
extern String NET_SN;
|
||||
extern String NET_PDNS;
|
||||
extern String NET_SDNS;
|
||||
extern int TIME_PER_FRAME;
|
||||
extern uint8_t MATRIX_FPS;
|
||||
extern int TIME_PER_TRANSITION;
|
||||
extern String NTP_SERVER;
|
||||
extern String NTP_TZ;
|
||||
extern bool HA_DISCOVERY;
|
||||
extern bool UPPERCASE_LETTERS;
|
||||
extern float CURRENT_TEMP;
|
||||
extern float CURRENT_HUM;
|
||||
extern float CURRENT_LUX;
|
||||
extern String CURRENT_APP;
|
||||
extern uint8_t BATTERY_PERCENT;
|
||||
extern uint8_t BRIGHTNESS;
|
||||
extern uint8_t BRIGHTNESS_PERCENT;
|
||||
extern String TEXTCOLOR;
|
||||
extern bool AUTO_BRIGHTNESS;
|
||||
extern bool AP_MODE;
|
||||
extern bool ALARM_ACTIVE;
|
||||
extern bool TIMER_ACTIVE;
|
||||
extern bool MATRIX_OFF;
|
||||
extern String ALARM_SOUND;
|
||||
extern String TIMER_SOUND;
|
||||
extern uint16_t TEXTCOLOR_565;
|
||||
extern uint8_t SNOOZE_TIME;
|
||||
|
||||
|
||||
void loadSettings();
|
||||
void saveSettings();
|
||||
#endif // Globals_H
|
||||
90
src/functions.h
Normal file
90
src/functions.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#ifndef FUNCTIONS_H
|
||||
#define FUNCTIONS_H
|
||||
|
||||
#include <map>
|
||||
#include <FastLED_NeoMatrix.h>
|
||||
#include <Settings.h>
|
||||
|
||||
std::map<char, uint16_t> CharMap = {
|
||||
{32, 3}, {33, 2}, {34, 4}, {35, 4}, {36, 4}, {37, 4}, {38, 4}, {39, 2}, {40, 3}, {41, 3}, {42, 4}, {43, 4}, {44, 3}, {45, 4}, {46, 2}, {47, 4}, {48, 4}, {49, 4}, {50, 4}, {51, 4}, {52, 4}, {53, 4}, {54, 4}, {55, 4}, {56, 4}, {57, 4}, {58, 2}, {59, 3}, {60, 4}, {61, 4}, {62, 4}, {63, 4}, {64, 4}, {65, 4}, {66, 4}, {67, 4}, {68, 4}, {69, 4}, {70, 4}, {71, 4}, {72, 4}, {73, 2}, {74, 4}, {75, 4}, {76, 4}, {77, 6}, {78, 5}, {79, 4}, {80, 4}, {81, 5}, {82, 4}, {83, 4}, {84, 4}, {85, 4}, {86, 4}, {87, 6}, {88, 4}, {89, 4}, {90, 4}, {91, 4}, {92, 4}, {93, 4}, {94, 4}, {95, 4}, {96, 3}, {97, 4}, {98, 4}, {99, 4}, {100, 4}, {101, 4}, {102, 4}, {103, 4}, {104, 4}, {105, 2}, {106, 4}, {107, 4}, {108, 4}, {109, 4}, {110, 4}, {111, 4}, {112, 4}, {113, 4}, {114, 4}, {115, 4}, {116, 4}, {117, 4}, {118, 4}, {119, 4}, {120, 4}, {121, 4}, {122, 4}, {123, 4}, {124, 2}, {125, 4}, {126, 4}, {161, 2}, {162, 4}, {163, 4}, {164, 4}, {165, 4}, {166, 2}, {167, 4}, {168, 4}, {169, 4}, {170, 4}, {171, 3}, {172, 4}, {173, 3}, {174, 4}, {175, 4}, {176, 4}, {177, 4}, {178, 4}, {179, 4}, {180, 3}, {181, 4}, {182, 4}, {183, 4}, {184, 4}, {185, 2}, {186, 4}, {187, 3}, {188, 4}, {189, 4}, {190, 4}, {191, 4}, {192, 4}, {193, 4}, {194, 4}, {195, 4}, {196, 4}, {197, 4}, {198, 4}, {199, 4}, {200, 4}, {201, 4}, {202, 4}, {203, 4}, {204, 4}, {205, 4}, {206, 4}, {207, 4}, {208, 4}, {209, 4}, {210, 4}, {211, 4}, {212, 4}, {213, 4}, {214, 4}, {215, 4}, {216, 4}, {217, 4}, {218, 4}, {219, 4}, {220, 4}, {221, 4}, {222, 4}, {223, 4}, {224, 4}, {225, 4}, {226, 4}, {227, 4}, {228, 4}, {229, 4}, {230, 4}, {231, 4}, {232, 4}, {233, 4}, {234, 4}, {235, 4}, {236, 3}, {237, 3}, {238, 4}, {239, 4}, {240, 4}, {241, 4}, {242, 4}, {243, 4}, {244, 4}, {245, 4}, {246, 4}, {247, 4}, {248, 4}, {249, 4}, {250, 4}, {251, 4}, {252, 4}, {253, 4}, {254, 4}, {255, 4}, {285, 2}, {338, 4}, {339, 4}, {352, 4}, {353, 4}, {376, 4}, {381, 4}, {382, 4}, {3748, 2}, {5024, 2}, {8226, 2}, {8230, 4}, {8364, 4}, {65533, 4}};
|
||||
|
||||
uint32_t hsvToRgb(uint8_t h, uint8_t s, uint8_t v)
|
||||
{
|
||||
CHSV hsv(h, s, v);
|
||||
CRGB rgb;
|
||||
hsv2rgb_spectrum(hsv, rgb);
|
||||
return ((uint16_t)(rgb.r & 0xF8) << 8) |
|
||||
((uint16_t)(rgb.g & 0xFC) << 3) |
|
||||
(rgb.b >> 3);
|
||||
}
|
||||
|
||||
uint16_t hexToRgb565(String hexValue)
|
||||
{
|
||||
hexValue.replace("#", "");
|
||||
uint8_t r = strtol(hexValue.substring(0, 2).c_str(), NULL, 16);
|
||||
uint8_t g = strtol(hexValue.substring(2, 4).c_str(), NULL, 16);
|
||||
uint8_t b = strtol(hexValue.substring(4, 6).c_str(), NULL, 16);
|
||||
uint16_t color = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
|
||||
return color;
|
||||
}
|
||||
|
||||
uint16_t getTextWidth(const char *text)
|
||||
{
|
||||
uint16_t width = 0;
|
||||
for (const char *c = text; *c != '\0'; ++c)
|
||||
{
|
||||
char current_char = *c;
|
||||
if (UPPERCASE_LETTERS)
|
||||
{
|
||||
current_char = toupper(current_char);
|
||||
}
|
||||
if (CharMap.count(current_char) > 0)
|
||||
{
|
||||
width += CharMap[current_char];
|
||||
}
|
||||
else
|
||||
{
|
||||
width += 4;
|
||||
}
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
static byte c1;
|
||||
byte utf8ascii(byte ascii)
|
||||
{
|
||||
if (ascii < 128)
|
||||
{
|
||||
c1 = 0;
|
||||
return (ascii);
|
||||
}
|
||||
byte last = c1;
|
||||
c1 = ascii;
|
||||
switch (last)
|
||||
{
|
||||
case 0xC2:
|
||||
return (ascii)-34;
|
||||
break;
|
||||
case 0xC3:
|
||||
return (ascii | 0xC0) - 34;
|
||||
break;
|
||||
case 0x82:
|
||||
if (ascii == 0xAC)
|
||||
return (0xEA);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
String utf8ascii(String s)
|
||||
{
|
||||
String r = "";
|
||||
char c;
|
||||
for (unsigned int i = 0; i < s.length(); i++)
|
||||
{
|
||||
c = utf8ascii(s.charAt(i));
|
||||
if (c != 0)
|
||||
r += c;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
#endif
|
||||
84
src/icondownloader.h
Normal file
84
src/icondownloader.h
Normal file
File diff suppressed because one or more lines are too long
3603
src/icons.h
Normal file
3603
src/icons.h
Normal file
File diff suppressed because it is too large
Load Diff
98
src/main.cpp
Normal file
98
src/main.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
|
||||
/* ___ ___ ___ ___
|
||||
/ /\ /__/\ ___ / /\ ___ /__/|
|
||||
/ /::\ _\_ \:\ / /\ / /::\ / /\ | |:|
|
||||
/ /:/\:\ /__/\ \:\ / /:/ / /:/\:\ / /:/ | |:|
|
||||
/ /:/~/::\ _\_ \:\ \:\ / /:/ / /:/~/:/ /__/::\ __|__|:|
|
||||
/__/:/ /:/\:\ /__/\ \:\ \:\ / /::\ /__/:/ /:/___ \__\/\:\__ /__/::::\____
|
||||
\ \:\/:/__\/ \ \:\ \:\/:/ /__/:/\:\ \ \:\/:::::/ \ \:\/\ ~\~~\::::/
|
||||
\ \::/ \ \:\ \::/ \__\/ \:\ \ \::/~~~~ \__\::/ |~~|:|~~
|
||||
\ \:\ \ \:\/:/ \ \:\ \ \:\ /__/:/ | |:|
|
||||
\ \:\ \ \::/ \__\/ \ \:\ \__\/ | |:|
|
||||
\__\/ \__\/ \__\/ |__|/
|
||||
|
||||
***************************************************************************
|
||||
* *
|
||||
* AWTRIX Light, a custom firmware for the Ulanzi clock *
|
||||
* *
|
||||
* Copyright (C) 2023 Stephan Mühl aka Blueforcer *
|
||||
* *
|
||||
* This work is licensed under a *
|
||||
* Creative Commons Attribution-NonCommercial-ShareAlike *
|
||||
* 4.0 International License. *
|
||||
* *
|
||||
* More information: *
|
||||
* https://github.com/Blueforcer/awtrix-light/blob/main/LICENSE.md *
|
||||
* *
|
||||
* This firmware is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "DisplayManager.h"
|
||||
#include "PeripheryManager.h"
|
||||
#include "MQTTManager.h"
|
||||
#include "ServerManager.h"
|
||||
#include "Settings.h"
|
||||
#include "AudioManager.h"
|
||||
|
||||
TaskHandle_t taskHandle;
|
||||
volatile bool StopTask = false;
|
||||
|
||||
void BootAnimation(void *parameter)
|
||||
{
|
||||
const TickType_t xDelay = 1 / portTICK_PERIOD_MS;
|
||||
while (true)
|
||||
{
|
||||
if (StopTask)
|
||||
{
|
||||
break;
|
||||
}
|
||||
DisplayManager.HSVtext(4, 6, "AWTRIX", true);
|
||||
vTaskDelay(xDelay);
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
PeripheryManager.setup();
|
||||
delay(500);
|
||||
Serial.begin(9600);
|
||||
loadSettings();
|
||||
ServerManager.loadSettings();
|
||||
DisplayManager.setup();
|
||||
PeripheryManager.playBootSound();
|
||||
xTaskCreatePinnedToCore(BootAnimation, "Task", 10000, NULL, 1, &taskHandle, 1);
|
||||
ServerManager.setup();
|
||||
|
||||
if (ServerManager.isConnected)
|
||||
{
|
||||
MQTTManager.setup();
|
||||
AudioManager.setup();
|
||||
DisplayManager.loadApps();
|
||||
}
|
||||
else
|
||||
{
|
||||
AP_MODE = true;
|
||||
}
|
||||
|
||||
StopTask = true;
|
||||
delay(200);
|
||||
DisplayManager.clearMatrix();
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
ServerManager.tick();
|
||||
DisplayManager.tick();
|
||||
if (ServerManager.isConnected)
|
||||
{
|
||||
AudioManager.tick();
|
||||
PeripheryManager.tick();
|
||||
MQTTManager.tick();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user