TableTennisDisplay added. Together with new documentation and even more infos.

This commit is contained in:
Nicolas Bachschwell 2022-05-25 10:58:02 +02:00
parent 41b9d2d175
commit 5eb3ab6bc9
Signed by: NBSgamesAT
GPG Key ID: B2F1E0D04F024688
3 changed files with 435 additions and 1 deletions

View File

@ -37,4 +37,32 @@ Topic `tabletenniscounter/control/score/count` will be send with the value of ei
### Undoing last points (Up to 10 points)
Topic `tabletenniscounter/control/score/undo` will be sent with no payload to worry about. The counting part on the respberry pi is supposed to keep a list of the last 10 points.
More infos are added soon!
## Table tennis LED display
### Overall
This is probably the more exciting bit about this. It's the part that actually displays the numbers on the display. The part, that tries to displays all the numbers in a way people can use it to read their current score.
We have a LOT of stuff of stuff to discuss here.
First of all: It does take advantage of custom components. CustomMQTTDevices to be precise.
Second of all: It uses the NeoPixelBus Library to communicate with the ledstrip.
Third of all: It does also use the esphome native api.
And a disclaimer as well: The native api part was the latest thing added and is by no means ready to be used. In fact it is only used right now to publish the current score to home assistent but nothing else as it cannot display the numbers sent to the esphome over the native api. This feature is planned but not yet implemented tho.
For the MQTT part we have only one topic to worry about.
### MQTT JSON message
MQTT topic `tabletenniscounter/display/number/command` is used to send the score that should be display encoded as a JSON message. So let's go over the different key/values attributes.
- state => "ON"/"OFF": This must always be included and must be ON if any other fuctionallity is intended to be used as OFF is going to turn the ledstrip OFF no matter what
- player1 => Number 0 - 99: This tells the display the score of player red (which is also the left number). This MUST be used together with player2 and player1Start/displayGreen.
- player2 => Number 0 - 99: This tells the display the score of player blue (which is also the right number). This MUST be used together with player1 and player1Start/displayGreen.
- player1Start => true/false: This if this is used to display which player is supposed to start. If the value is true, the colon between the two numbers is displayed as red, if it's false, the colon is blue. This attribute can be replaced with displayGreen.
- displayGreen: The value for this does not matter. If it's there, the colon is green.
- player1Winner => true/false (OPTIONAL): Set this to true, and player 1 (red) will be displayed as the winner by displaying the red number as yellow. If it's false, player 2 (blue) will be displayed as the winner. Omit this attribute if no one should be displayed at the winner.
Should state ON be sent without any other attribute, the nbs logo is displayed with a little minus at the end for no particular reason.
More infos are coming soon.

View File

@ -0,0 +1,355 @@
#include "esphome.h"
#include "NeoPixelBus.h"
//#include "multi_effect_handler.h"
#define SEGMENT_LENGTH 5
class NbsEffect {
private:
int goalRed = 0, goalGreen = 0, goalBlue = 0;
double currentRed = 0, currentGreen = 0, currentBlue = 0;
double calcRed = 0, calcGreen = 0, calcBlue = 0;
int stepsPerSecond = 0;
int totalSteps = 0;
int currentStep = 0;
bool finalStep = true;
public:
NbsEffect(int stepsPerSecond_){
stepsPerSecond = stepsPerSecond_;
}
void setGoalColorAsRgb(int red, int green, int blue, int timeInMilliseconds){
if(goalRed == red && goalGreen == green && goalBlue == blue){
return;
}
finalStep = false;
currentRed = (double) goalRed;
currentGreen = (double) goalGreen;
currentBlue = (double) goalBlue;
goalRed = red;
goalGreen = green;
goalBlue = blue;
double timeInSeconds = (double) timeInMilliseconds / 1000.0;
totalSteps = (int) (timeInSeconds * (double) stepsPerSecond);
currentStep = totalSteps;
calcRed = ((double) goalRed - currentRed) / totalSteps;
calcGreen = ((double) goalGreen - currentGreen) / totalSteps;
calcBlue = ((double) goalBlue - currentBlue) / totalSteps;
currentStep = 0;
}
int debugGoalRed(){
return goalRed;
}
double debugCurrentRed(){
return currentRed;
}
double debugCalcRed(){
return calcRed;
}
bool step(){
if(currentStep >= totalSteps){
return false;
}
++currentStep;
if(currentStep == totalSteps){
currentRed = (double) goalRed;
currentGreen = (double) goalGreen;
currentBlue = (double) goalBlue;
finalStep = true;
return true;
}
currentRed += calcRed;
currentGreen += calcGreen;
currentBlue += calcBlue;
return true;
}
bool wasFinalStep(){
return finalStep;
}
int getCurrentRed(){
return (int) currentRed;
}
int getCurrentGreen(){
return (int) currentGreen;
}
int getCurrentBlue(){
return (int) currentBlue;
}
};
NeoPixelBus<NeoGrbFeature, NeoEsp8266BitBangWs2812xMethod> nbsStrip(143, D5);
class CustomNumberDisplayComponent : public Component, public CustomMQTTDevice {
const int middleSegment = SEGMENT_LENGTH * 14;
const int transitionLength = 500; // milliseconds
const int middleLength = 2;
const double brightness = 0.17;
NbsEffect *colorAnimation[4][7];
NbsEffect *middleSegmentAnim;
unsigned long lastUpdate = 0;
bool shouldShowNBS = true;
bool numbers[14][7] = {
{0, 1, 1, 1, 1, 1, 1}, // 0
{0, 1, 0, 0, 0, 0, 1}, // 1
{1, 1, 1, 0, 1, 1, 0}, // 2
{1, 1, 1, 0, 0, 1, 1}, // 3
{1, 1, 0, 1, 0, 0, 1}, // 4
{1, 0, 1, 1, 0, 1, 1}, // 5
{1, 0, 1, 1, 1, 1, 1}, // 6
{0, 1, 1, 0, 0, 0, 1}, // 7
{1, 1, 1, 1, 1, 1, 1}, // 8
{1, 1, 1, 1, 0, 1, 1}, // 9
{0, 0, 0, 0, 0, 0, 0}, // No Display (10)
{1, 0, 0, 0, 0, 0, 0}, // - (11)
{1, 0, 0, 0, 1, 0, 1}, // n (12)
{1, 0, 0, 1, 1, 1, 1} // b (13)
};
public:
void setup() override {
// Segment mapping
int segPixelCounter = 0;
for(int i = 0; i < 4; ++i){
for(int j = 0; j < 7; ++j){
colorAnimation[i][j] = new NbsEffect(40);
}
}
middleSegmentAnim = new NbsEffect(40);
nbsStrip.Begin();
setNumber(0, 12, 1.0, 0.0, 0.0);
setNumber(1, 13, 0.0, 1.0, 0.0);
setNumber(2, 5, 0.0, 0.0, 1.0);
setNumber(3, 11, 0.5, 0.5, 0.5);
for(int led = middleSegment; led < middleSegment + 2; ++led){
nbsStrip.SetPixelColor(led, RgbColor(0, 0, 0));
}
nbsStrip.Show();
lastUpdate = millis();
// also supports JSON messages
subscribe_json("tabletenniscounter/display/number/command", &CustomNumberDisplayComponent::on_json_message, 2);
}
void on_json_message(const std::string &topic, JsonObject root) {
//checker = 0;
if(!root.containsKey("state")){
return;
}
if(root["state"] != "ON"){
setNumber(0, 10);
setNumber(1, 10);
setNumber(2, 10);
setNumber(3, 10);
turnSegmentOff(middleSegmentAnim);
publish_json("tabletenniscounter/display/number/state", [=](JsonObject root2) {
root2["state"] = "OFF";
}, 2, false);
return;
}
if(shouldShowNBS){
shouldShowNBS = false;
lastUpdate = millis();
}
if (!root.containsKey("player1") || !root.containsKey("player2") || !(root.containsKey("player1Start") || root.containsKey("displayGreen"))){
setNumber(0, 12, 1.0, 0.0, 0.0);
setNumber(1, 13, 0.0, 1.0, 0.0);
setNumber(2, 5, 0.0, 0.0, 1.0);
setNumber(3, 11, 0.5, 0.5, 0.5);
turnSegmentOff(middleSegmentAnim);
publish_json("tabletenniscounter/display/number/state", [=](JsonObject root2) {
root2["state"] = "ON";
}, 2, false);
return;
}
int player1 = root["player1"];
int player2 = root["player2"];
id(red_num).set(player1);
id(blue_num).set(player2);
bool player1Start = root["player1Start"];
// do something with Json Object
if(player1 > 99) { player1 = 99; }
else if(player1 < 0) { player1 = 0; }
if(player2 > 99) { player2 = 99; }
else if(player2 < 0) { player2 = 0; }
int player1T = player1 / 10;
int player1O = player1 % 10;
int player2T = player2 / 10;
int player2O = player2 % 10;
int handleColorWinChange = 0;
if(root.containsKey("player1Winner")){
if(root["player1Winner"]){
handleColorWinChange = 1;
}
else{
handleColorWinChange = 2;
}
}
if(player1T == 0)
setNumber(0, 10);
else{
if(handleColorWinChange == 1){
setNumber(0, player1T, 1.0, 1.0, 0.0);
}else{
setNumber(0, player1T);
}
}
if(handleColorWinChange == 1){
setNumber(1, player1O, 1.0, 1.0, 0.0);
}else{
setNumber(1, player1O);
}
if(player2T == 0){
if(handleColorWinChange == 2){
setNumber(2, player2O, 1.0, 1.0, 0.0);
}else{
setNumber(2, player2O);
}
setNumber(3, 10);
}
else{
if(handleColorWinChange == 2){
setNumber(2, player2T, 1.0, 1.0, 0.0);
setNumber(3, player2O, 1.0, 1.0, 0.0);
}else{
setNumber(2, player2T);
setNumber(3, player2O);
}
}
if(root.containsKey("displayGreen")){
turnSegmentOn(middleSegmentAnim, brightness, 0.0, 1.0, 0.0);
}
else{
if(player1Start){
turnSegmentOn(middleSegmentAnim, brightness, 1.0, 0.0, 0.0);
}
else{
turnSegmentOn(middleSegmentAnim, brightness, 0.0, 0.0, 1.0);
}
}
// publish JSON using lambda syntax
publish_json("tabletenniscounter/display/number/state", [=](JsonObject root2) {
root2["state"] = "ON";
root2["player1"] = player1;
root2["player2"] = player2;
root2["player1Start"] = player1Start;
}, 2, false);
}
void loop() override{
if(lastUpdate + 25 >= millis()){
return;
}
if(lastUpdate + 50 < millis()){
//ESP_LOGW("custom_display", "LAGWARN, lastUpdate timer was reseted!");
lastUpdate = millis();
}
lastUpdate += 25;
bool didUpdate = false;
for(int i = 0; i < 4; ++i){
for(int j = 0; j < 7; ++j){
NbsEffect *effect = colorAnimation[i][j];
int segStart = calculateSegStart(i, j);
if(effect->step()){
didUpdate = true;
for(int led = segStart; led < segStart + SEGMENT_LENGTH; ++led){
nbsStrip.SetPixelColor(led, RgbColor(effect->getCurrentRed(), effect->getCurrentGreen(), effect->getCurrentBlue()));
}
}
}
}
if(colorAnimation[0][0]->wasFinalStep() && shouldShowNBS){
shouldShowNBS = false;
lastUpdate = millis() + 5000;
setNumber(0, 10, 0.0, 0.0, 0.0);
setNumber(1, 10, 0.0, 0.0, 0.0);
setNumber(2, 10, 0.0, 0.0, 0.0);
setNumber(3, 10, 0.0, 0.0, 0.0);
}
if(middleSegmentAnim->step()){
didUpdate = true;
//ESP_LOGD("custom", "#%d, Color Code: %d, %d, %d", checker++, middleSegmentAnim->getCurrentRed(), middleSegmentAnim->getCurrentGreen(), middleSegmentAnim->getCurrentBlue());
for(int led = middleSegment; led < middleSegment + 2; ++led){
nbsStrip.SetPixelColor(led, RgbColor(middleSegmentAnim->getCurrentRed(), middleSegmentAnim->getCurrentGreen(), middleSegmentAnim->getCurrentBlue()));
}
}
if(didUpdate){
nbsStrip.Show();
}
}
int calculateSegStart(int digit, int segment){
int start = (digit * SEGMENT_LENGTH * 7) + (segment * SEGMENT_LENGTH);
if(digit >= 2){
start += 2;
}
return start;
}
void turnSegmentOn(NbsEffect* segment, double brightness, double red, double green, double blue){
segment->setGoalColorAsRgb(makeColorAsInt(brightness, red, 220), makeColorAsInt(brightness, green, 170), makeColorAsInt(brightness, blue, 235), 500);
}
void turnSegmentOff(NbsEffect* segment){
segment->setGoalColorAsRgb(0, 0, 0, 500);
}
void setNumber(int digit, int number, double red, double green, double blue){
if(digit < 0 || digit > 3){
return;
}
for(int i = 0; i < 7; ++i){
if(numbers[number][i]){
turnSegmentOn(colorAnimation[digit][i], brightness, red, green, blue);
}
else{
turnSegmentOff(colorAnimation[digit][i]);
}
}
}
void setNumber(int digit, int number){
if(digit < 0 || digit > 3){
return;
}
double red = 0;
double blue = 0;
if(digit == 0 || digit == 1){
red = 1.0;
blue = 0.0;
}
else {
red = 0.0;
blue = 1.0;
}
setNumber(digit, number, red, 0.0, blue);
}
int makeColorAsInt(double brightness, double value, double colorMax){
return (int) (colorMax * value * brightness);
}
};

51
tableTennisDisplay.yaml Normal file
View File

@ -0,0 +1,51 @@
esphome:
name: tabletenniscounter
platform: ESP8266
board: nodemcuv2
includes:
- cpp-files/tableTennisDisplay.h
libraries:
- "makuna/NeoPixelBus"
custom_component:
- lambda: |-
auto customNumberDisplay = new CustomNumberDisplayComponent();
return {customNumberDisplay};
# Enable logging
logger:
ota:
password: !secret ttdpassword
api:
password: !secret ttdpassword
encryption:
key: !secret ttdkey
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
fast_connect: true
number:
- platform: template
name: "Red Number"
id: red_num
optimistic: true
min_value: 0
max_value: 99
step: 1
- platform: template
name: "Blue Number"
id: blue_num
optimistic: true
min_value: 0
max_value: 99
step: 1
mqtt:
broker: !secret mqtt_broker_1
client_id: "tableTennisCounter"
username: !secret mqtt_broker_1_username
password: !secret mqtt_broker_1_password