WLED/usermods/seven_segment_display/usermod_v2_seven_segment_display.h

474 lines
15 KiB
C
Raw Normal View History

2021-09-30 05:23:32 +02:00
#pragma once
#include "wled.h"
class SevenSegmentDisplay : public Usermod
{
#define WLED_SS_BUFFLEN 6
#define REFRESHTIME 497
private:
//Runtime variables.
unsigned long lastRefresh = 0;
unsigned long lastCharacterStep = 0;
//char ssDisplayBuffer[WLED_SS_BUFFLEN+1]; //Runtime buffer of what should be displayed.
String ssDisplayBuffer = "";
char ssCharacterMask[36] = {0x77, 0x11, 0x6B, 0x3B, 0x1D, 0x3E, 0x7E, 0x13, 0x7F, 0x1F, 0x5F, 0x7C, 0x66, 0x79, 0x6E, 0x4E, 0x76, 0x5D, 0x44, 0x71, 0x5E, 0x64, 0x27, 0x58, 0x77, 0x4F, 0x1F, 0x48, 0x3E, 0x6C, 0x75, 0x25, 0x7D, 0x2A, 0x3D, 0x6B};
int ssDisplayMessageIdx = 0; //Position of the start of the message to be physically displayed.
bool ssDoDisplayTime = true;
int ssVirtualDisplayMessageIdxStart = 0;
int ssVirtualDisplayMessageIdxEnd = 0;
unsigned long resfreshTime = 497;
// set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer)
int ssLEDPerSegment = 1; //The number of LEDs in each segment of the 7 seg (total per digit is 7 * ssLedPerSegment)
int ssLEDPerPeriod = 1; //A Period will have 1x and a Colon will have 2x
int ssStartLED = 0; //The pixel that the display starts at.
/* HH - 0-23. hh - 1-12, kk - 1-24 hours
2021-09-30 06:01:26 +02:00
// MM or mm - 0-59 minutes
// SS or ss = 0-59 seconds
// : for a colon
// All others for alpha numeric, (will be blank when displaying time)
2021-10-02 07:34:20 +02:00
*/
//char ssDisplayMask[WLED_SS_BUFFLEN+1] = "HHMMSS"; //Physical Display Mask, this should reflect physical equipment.
String ssDisplayMask = "HHMMSS";
/* ssDisplayConfig
2021-09-30 06:01:26 +02:00
// -------
// / A / 0 - EDCGFAB
// / F / B 1 - EDCBAFG
// / / 2 - GCDEFAB
// ------- 3 - GBAFEDC
// / G / 4 - FABGEDC
// / E / C 5 - FABCDEG
// / /
// -------
// D
2021-10-02 07:34:20 +02:00
*/
int ssDisplayConfig = 5; //Physical configuration of the Seven segment display
String ssDisplayMessage = "testing123";
bool ssTimeEnabled = true; //If not, display message.
unsigned long ssScrollSpeed = 1000; //Time between advancement of extended message scrolling, in milliseconds.
unsigned long _overlaySevenSegmentProcess()
{
//Do time for now.
if (ssDoDisplayTime)
2021-09-30 06:01:26 +02:00
{
//Format the ssDisplayBuffer based on ssDisplayMask
int displayMaskLen = static_cast<int>(ssDisplayMask.length());
for (int index = 0; index < displayMaskLen; index++)
2021-09-30 06:01:26 +02:00
{
//Only look for time formatting if there are at least 2 characters left in the buffer.
if ((index < displayMaskLen - 1) && (ssDisplayMask[index] == ssDisplayMask[index + 1]))
2021-09-30 06:01:26 +02:00
{
int timeVar = 0;
switch (ssDisplayMask[index])
2021-09-30 06:01:26 +02:00
{
case 'h':
timeVar = hourFormat12(localTime);
break;
case 'H':
timeVar = hour(localTime);
break;
case 'k':
timeVar = hour(localTime) + 1;
break;
case 'M':
case 'm':
timeVar = minute(localTime);
break;
case 'S':
case 's':
timeVar = second(localTime);
break;
2021-09-30 06:01:26 +02:00
}
//Only want to leave a blank in the hour formatting.
if ((ssDisplayMask[index] == 'h' || ssDisplayMask[index] == 'H' || ssDisplayMask[index] == 'k') && timeVar < 10)
ssDisplayBuffer[index] = ' ';
else
ssDisplayBuffer[index] = 0x30 + (timeVar / 10);
ssDisplayBuffer[index + 1] = 0x30 + (timeVar % 10);
2021-10-02 07:34:20 +02:00
//Need to increment the index because of the second digit.
index++;
2021-10-02 07:34:20 +02:00
}
else
{
ssDisplayBuffer[index] = (ssDisplayMask[index] == ':' ? ':' : ' ');
2021-09-30 06:01:26 +02:00
}
}
return REFRESHTIME;
2021-09-30 06:01:26 +02:00
}
else
2021-09-30 06:01:26 +02:00
{
/* This will handle displaying a message and the scrolling of the message if its longer than the buffer length */
//Check to see if the message has scrolled completely
int len = static_cast<int>(ssDisplayMessage.length());
if (ssDisplayMessageIdx > len)
2021-09-30 06:01:26 +02:00
{
//If it has scrolled the whole message, reset it.
setSevenSegmentMessage(ssDisplayMessage);
return REFRESHTIME;
}
//Display message
int displayMaskLen = static_cast<int>(ssDisplayMask.length());
for (int index = 0; index < displayMaskLen; index++)
{
if (ssDisplayMessageIdx + index < len && ssDisplayMessageIdx + index >= 0)
ssDisplayBuffer[index] = ssDisplayMessage[ssDisplayMessageIdx + index];
2021-09-30 06:01:26 +02:00
else
ssDisplayBuffer[index] = ' ';
2021-09-30 06:01:26 +02:00
}
2021-09-30 05:23:32 +02:00
//Increase the displayed message index to progress it one character if the length exceeds the display length.
if (len > displayMaskLen)
ssDisplayMessageIdx++;
return ssScrollSpeed;
2021-09-30 06:01:26 +02:00
}
}
2021-09-30 06:01:26 +02:00
void _overlaySevenSegmentDraw()
{
//Start pixels at ssStartLED, Use ssLEDPerSegment, ssLEDPerPeriod, ssDisplayBuffer
int indexLED = 0;
int displayMaskLen = static_cast<int>(ssDisplayMask.length());
for (int indexBuffer = 0; indexBuffer < displayMaskLen; indexBuffer++)
2021-09-30 06:01:26 +02:00
{
if (ssDisplayBuffer[indexBuffer] == 0)
break;
else if (ssDisplayBuffer[indexBuffer] == '.')
2021-09-30 06:01:26 +02:00
{
//Won't ever turn off LED lights for a period. (or will we?)
indexLED += ssLEDPerPeriod;
continue;
}
else if (ssDisplayBuffer[indexBuffer] == ':')
{
//Turn off colon if odd second?
indexLED += ssLEDPerPeriod * 2;
}
else if (ssDisplayBuffer[indexBuffer] == ' ')
{
//Turn off all 7 segments.
_overlaySevenSegmentLEDOutput(0, indexLED);
indexLED += ssLEDPerSegment * 7;
}
else
{
//Turn off correct segments.
_overlaySevenSegmentLEDOutput(_overlaySevenSegmentGetCharMask(ssDisplayBuffer[indexBuffer]), indexLED);
indexLED += ssLEDPerSegment * 7;
2021-09-30 06:01:26 +02:00
}
}
}
2021-09-30 06:01:26 +02:00
void _overlaySevenSegmentLEDOutput(char mask, int indexLED)
{
for (char index = 0; index < 7; index++)
2021-09-30 06:01:26 +02:00
{
if ((mask & (0x40 >> index)) != (0x40 >> index))
{
for (int numPerSeg = 0; numPerSeg < ssLEDPerSegment; numPerSeg++)
{
strip.setPixelColor(indexLED, 0x000000);
}
}
indexLED += ssLEDPerSegment;
}
}
char _overlaySevenSegmentGetCharMask(char var)
{
//ssCharacterMask
if (var > 0x60) //Essentially a "toLower" call.
var -= 0x20;
if (var > 0x39) //Meaning it is a non-numeric
var -= 0x07;
var -= 0x30; //Shift ascii down to start numeric 0 at index 0.
char mask = ssCharacterMask[static_cast<int>(var)];
2021-09-30 06:01:26 +02:00
/*
0 - EDCGFAB
1 - EDCBAFG
2 - GCDEFAB
3 - GBAFEDC
4 - FABGEDC
5 - FABCDEG
*/
switch (ssDisplayConfig)
{
case 1:
mask = _overlaySevenSegmentSwapBits(mask, 0, 3, 1);
mask = _overlaySevenSegmentSwapBits(mask, 1, 2, 1);
break;
case 2:
mask = _overlaySevenSegmentSwapBits(mask, 3, 6, 1);
mask = _overlaySevenSegmentSwapBits(mask, 4, 5, 1);
break;
case 3:
mask = _overlaySevenSegmentSwapBits(mask, 0, 4, 3);
mask = _overlaySevenSegmentSwapBits(mask, 3, 6, 1);
mask = _overlaySevenSegmentSwapBits(mask, 4, 5, 1);
break;
case 4:
mask = _overlaySevenSegmentSwapBits(mask, 0, 4, 3);
break;
case 5:
mask = _overlaySevenSegmentSwapBits(mask, 0, 4, 3);
mask = _overlaySevenSegmentSwapBits(mask, 0, 3, 1);
mask = _overlaySevenSegmentSwapBits(mask, 1, 2, 1);
break;
2021-09-30 06:01:26 +02:00
}
return mask;
}
2021-09-30 06:01:26 +02:00
char _overlaySevenSegmentSwapBits(char x, char p1, char p2, char n)
{
/* Move all bits of first set to rightmost side */
char set1 = (x >> p1) & ((1U << n) - 1);
/* Move all bits of second set to rightmost side */
char set2 = (x >> p2) & ((1U << n) - 1);
/* Xor the two sets */
char Xor = (set1 ^ set2);
/* Put the Xor bits back to their original positions */
Xor = (Xor << p1) | (Xor << p2);
/* Xor the 'Xor' with the original number so that the
2021-09-30 06:01:26 +02:00
two sets are swapped */
char result = x ^ Xor;
return result;
}
void _publishMQTTint(const char* subTopic, int value)
{
char buffer[64];
char valBuffer[12];
sprintf_P(buffer, PSTR("%s/sevenSeg/%s"), mqttDeviceTopic, subTopic);
sprintf_P(valBuffer, PSTR("%d"), value);
mqtt->publish(buffer, 2, true, valBuffer);
}
void _publishMQTTstr(const char* subTopic, String Value)
{
char buffer[64];
sprintf_P(buffer, PSTR("%s/sevenSeg/%s"), mqttDeviceTopic, subTopic);
mqtt->publish(buffer, 2, true, Value.c_str(), Value.length());
}
void _updateMQTT()
{
_publishMQTTint(PSTR("perSegment"), ssLEDPerSegment);
_publishMQTTint(PSTR("perPeriod"), ssLEDPerPeriod);
_publishMQTTint(PSTR("startIdx"), ssStartLED);
_publishMQTTint(PSTR("displayCfg"), ssDisplayConfig);
_publishMQTTint(PSTR("timeEnable"), ssTimeEnabled);
_publishMQTTint(PSTR("scrollSpd"), ssScrollSpeed);
_publishMQTTstr(PSTR("displayMask"), ssDisplayMask);
_publishMQTTstr(PSTR("displayMsg"), ssDisplayMessage);
}
void _handleMQTT(char *topic, char *payload)
{
if(strcmp_P(topic, PSTR("perSegment"))==0)
{
ssLEDPerSegment = strtol(payload, NULL, 10);
_publishMQTTint(topic, ssLEDPerSegment);
return;
}
if(strcmp_P(topic, PSTR("perPeriod"))==0)
{
ssLEDPerPeriod = strtol(payload, NULL, 10);
_publishMQTTint(topic, ssLEDPerPeriod);
return;
}
if(strcmp_P(topic, PSTR("startIdx"))==0)
{
ssStartLED = strtol(payload, NULL, 10);
_publishMQTTint(topic, ssStartLED);
return;
}
if(strcmp_P(topic, PSTR("displayCfg"))==0)
{
ssDisplayConfig = strtol(payload, NULL, 10);
_publishMQTTint(topic, ssDisplayConfig);
return;
}
if(strcmp_P(topic, PSTR("timeEnable"))==0)
{
ssTimeEnabled = strtol(payload, NULL, 10);
ssDoDisplayTime = ssTimeEnabled;
_publishMQTTint(topic, ssTimeEnabled);
return;
}
if(strcmp_P(topic, PSTR("scrollSpd"))==0)
{
ssScrollSpeed = strtol(payload, NULL, 10);
_publishMQTTint(topic, ssScrollSpeed);
return;
}
if(strcmp_P(topic, PSTR("displayMask"))==0)
{
ssDisplayMask = String(payload);
ssDisplayBuffer = ssDisplayMask;
_publishMQTTstr(topic, ssDisplayMask);
return;
}
if(strcmp_P(topic, PSTR("displayMsg"))==0)
{
setSevenSegmentMessage(String(payload));
return;
}
}
public:
void setSevenSegmentMessage(String message)
{
//If the message isn't blank display it otherwise show time, if enabled.
if (message.length() < 1 || message == "~")
ssDoDisplayTime = ssTimeEnabled;
else
ssDoDisplayTime = false;
//Determine is the message is longer than the display, if it is configure it to scroll the message.
if (message.length() > ssDisplayMask.length())
ssDisplayMessageIdx = -ssDisplayMask.length();
else
ssDisplayMessageIdx = 0;
2021-09-30 06:01:26 +02:00
//If the message isn't the same, update runtime/mqtt (most calls will be resetting message scroll)
if(!ssDisplayMessage.equals(message))
{
_publishMQTTstr(PSTR("displayMsg"), message);
ssDisplayMessage = message;
}
}
//Functions called by WLED
2021-09-30 05:23:32 +02:00
/*
2021-09-30 05:23:32 +02:00
* setup() is called once at boot. WiFi is not yet connected at this point.
* You can use it to initialize variables, sensors or similar.
*/
void setup()
{
ssDisplayBuffer = ssDisplayMask;
}
2021-09-30 05:23:32 +02:00
/*
2021-09-30 05:23:32 +02:00
* loop() is called continuously. Here you can check for events, read sensors, etc.
*/
void loop()
{
if (millis() - lastRefresh > resfreshTime)
{
//In theory overlaySevenSegmentProcess should return the amount of time until it changes next.
//So we should be okay to trigger the stripi on every process loop.
resfreshTime = _overlaySevenSegmentProcess();
lastRefresh = millis();
strip.trigger();
2021-09-30 05:23:32 +02:00
}
}
void handleOverlayDraw()
{
_overlaySevenSegmentDraw();
}
2021-09-30 05:23:32 +02:00
void onMqttConnect(bool sessionPresent)
{
char subBuffer[48];
if (mqttDeviceTopic[0] != 0)
{
_updateMQTT();
//subscribe for sevenseg messages on the device topic
sprintf_P(subBuffer, PSTR("%s/sevenSeg/+/set"), mqttDeviceTopic);
mqtt->subscribe(subBuffer, 2);
2021-09-30 06:01:26 +02:00
}
if (mqttGroupTopic[0] != 0)
{
//subcribe for sevenseg messages on the group topic
sprintf_P(subBuffer, PSTR("%s/sevenSeg/+/set"), mqttGroupTopic);
mqtt->subscribe(subBuffer, 2);
}
}
bool onMqttMessage(char *topic, char *payload)
{
//If topic beings iwth sevenSeg cut it off, otherwise not our message.
size_t topicPrefixLen = strlen_P(PSTR("/sevenSeg/"));
if (strncmp_P(topic, PSTR("/sevenSeg/"), topicPrefixLen) == 0)
topic += topicPrefixLen;
else
return false;
//We only care if the topic ends with /set
size_t topicLen = strlen(topic);
if (topicLen > 4 &&
topic[topicLen - 4] == '/' &&
topic[topicLen - 3] == 's' &&
topic[topicLen - 2] == 'e' &&
topic[topicLen - 1] == 't')
{
//Trim /set and handle it
topic[topicLen - 4] = '\0';
_handleMQTT(topic, payload);
}
return true;
}
void addToConfig(JsonObject& root)
{
JsonObject top = root[FPSTR("sevenseg")];
if (top.isNull()) {
top = root.createNestedObject(FPSTR("sevenseg"));
}
top[FPSTR("perSegment")] = ssLEDPerSegment;
top[FPSTR("perPeriod")] = ssLEDPerPeriod;
top[FPSTR("startIdx")] = ssStartLED;
top[FPSTR("displayMask")] = ssDisplayMask;
top[FPSTR("displayCfg")] = ssDisplayConfig;
top[FPSTR("displayMsg")] = ssDisplayMessage;
top[FPSTR("timeEnable")] = ssTimeEnabled;
top[FPSTR("scrollSpd")] = ssScrollSpeed;
}
bool readFromConfig(JsonObject& root)
{
JsonObject top = root[FPSTR("sevenseg")];
bool configComplete = !top.isNull();
//if sevenseg section doesn't exist return
if(!configComplete) return configComplete;
configComplete &= getJsonValue(top[FPSTR("perSegment")], ssLEDPerSegment);
configComplete &= getJsonValue(top[FPSTR("perPeriod")], ssLEDPerPeriod);
configComplete &= getJsonValue(top[FPSTR("startIdx")], ssStartLED);
configComplete &= getJsonValue(top[FPSTR("displayMask")], ssDisplayMask);
configComplete &= getJsonValue(top[FPSTR("displayCfg")], ssDisplayConfig);
String newDisplayMessage;
configComplete &= getJsonValue(top[FPSTR("displayMsg")], newDisplayMessage);
setSevenSegmentMessage(newDisplayMessage);
configComplete &= getJsonValue(top[FPSTR("timeEnable")], ssTimeEnabled);
configComplete &= getJsonValue(top[FPSTR("scrollSpd")], ssScrollSpeed);
return configComplete;
}
/*
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId()
{
return USERMOD_ID_SEVEN_SEGMENT_DISPLAY;
}
2021-09-30 05:23:32 +02:00
};