Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e2981189f |
@@ -1,2 +0,0 @@
|
||||
*.FCBak
|
||||
*.stl
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -20,5 +20,3 @@ uploadfs:
|
||||
update:
|
||||
pio -f -c vim update
|
||||
|
||||
cleanESlabs:
|
||||
rm -rf .pio/libdeps/esp32-s3-devkitc-1/eslabsCurses
|
||||
|
||||
@@ -8,16 +8,10 @@ Currently I have a way to mount it on a G29 because its what I have.
|
||||
|
||||
To drive it I one of my other projects: [ESDI](https://github.com/ESilva15/ESDI)
|
||||
|
||||
## Many thanks
|
||||
- to [mgaman/ESP32-4827S043-LVGL](https://github.com/mgaman/ESP32-4827S043-LVGL):
|
||||
don't remember exactly how but it helped me understand how to set up this ESP32
|
||||
for development.
|
||||
|
||||
|
||||

|
||||
|
||||
## Assembly
|
||||
It requires a very simple modification to the G29. The display will seat between
|
||||
It requires a very simple modification to the G29. The display will seet between
|
||||
the base and the steering wheel and for the stock G29 paddles to remain functional,
|
||||
the steering wheel must be spaced.
|
||||
|
||||
@@ -27,25 +21,13 @@ go on the screws that screw the wheel to the base:
|
||||

|
||||
|
||||
# Roadmap
|
||||
- [ ] Simhub support
|
||||
- [ ] Better layout tooling
|
||||
- [ ] Dynamic layout/data
|
||||
|
||||
|
||||
### Development
|
||||
#### Debugging wires
|
||||
| CYD | Serial Cable |
|
||||
| -------------- | ------------ |
|
||||
| Yellow [RX 18] | Green |
|
||||
| Blue [TX 19] | White |
|
||||
|
||||
|
||||
The `RX18` and `TX19` pins are the `IO18` and `IO19` pins on the board and
|
||||
probably are labeled as `UART1` and `USB`.
|
||||
|
||||
|
||||
This is the `Serial2` we define on `main.cpp` currently.
|
||||
Then we listen to it with something like ``.
|
||||
### Debugging wires
|
||||
RX 18 -> yellow -> green
|
||||
TX 19 -> blue -> white
|
||||
|
||||
### Cool fonts I like:
|
||||
- u8g2_font_bubble_tr (really cool for splash screens)
|
||||
@@ -64,3 +46,7 @@ int ascent = u8g2.getAscent();
|
||||
// will probably vary from font to font tho, right now I'm using
|
||||
// monospace and that's enough
|
||||
```
|
||||
|
||||
### Shameless begging
|
||||
Hey, doesn't hurt to try, its free either way:
|
||||
[Buy me a coffee!](buymeacoffee.com/ESilva_15)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "ili9488CursesUI",
|
||||
"version": "0.1.1",
|
||||
"description": "Simple UI for creating interfaces with the ili9488 and arduinos",
|
||||
"keywords": "ili9488, pro micro, curses",
|
||||
"repository":
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/ESilva15/ili9488CursesUI"
|
||||
},
|
||||
"authors":
|
||||
[
|
||||
{
|
||||
"name": "Eduardo Silva",
|
||||
"maintainer": true
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"Adafruit_GFX_Library": "~1.11.3",
|
||||
"Adafruit_BusIO": "1.16.1"
|
||||
},
|
||||
"frameworks": "*",
|
||||
"platforms": "*"
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
#include "UIBar.h"
|
||||
#include "UIDrawing.h"
|
||||
|
||||
UIBar::UIBar(Arduino_GFX *d, UIDimensions dims, UIDecorations *decor,
|
||||
char *title)
|
||||
: UIElement(d, dims, decor, title) {}
|
||||
|
||||
void UIBar::Update(char *v) {
|
||||
unsigned long time = millis();
|
||||
if ((time - this->lastUpdate) <= this->refreshRate) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Conver the string to an integer
|
||||
uint32_t newVal = atoi(v);
|
||||
|
||||
if (this->value == newVal) {
|
||||
// If the value hasn't changed we do not need to re-render
|
||||
return;
|
||||
}
|
||||
|
||||
this->DrawBar(&this->value, &newVal);
|
||||
this->value = newVal;
|
||||
}
|
||||
|
||||
void UIBar::DrawBar(uint32_t *oldValue, uint32_t *newValue) {
|
||||
// We can help identifying cur RPM visually by using different
|
||||
// colors on the bar, for example, at a given distance of the ideal shift
|
||||
// we can change the color gradually
|
||||
|
||||
// If the new value is larger than the old one
|
||||
// we just paint a new rectangle
|
||||
// If the new value is smaller than the old one
|
||||
// we have to delete a chunk of the old rectangle
|
||||
int16_t startX = 0;
|
||||
int16_t len = 0;
|
||||
uint16_t colour = WHITE;
|
||||
|
||||
if (*newValue > *oldValue) { // Increment the bar
|
||||
int32_t newV =
|
||||
(*newValue * (uint32_t)(this->dims.width - 4)) / (this->range * 1000);
|
||||
int32_t oldV =
|
||||
(this->value * (uint32_t)(this->dims.width - 4)) / (this->range * 1000);
|
||||
|
||||
startX = oldV;
|
||||
len = newV - oldV;
|
||||
|
||||
} else { // Decrement the bar
|
||||
int32_t newV =
|
||||
(*newValue * (uint32_t)(this->dims.width - 4)) / (this->range * 1000);
|
||||
int32_t oldV =
|
||||
(this->value * (uint32_t)(this->dims.width - 4)) / (this->range * 1000);
|
||||
|
||||
startX = newV;
|
||||
len = oldV - newV;
|
||||
colour = BLACK;
|
||||
}
|
||||
this->display->fillRect(this->dims.x + 2 + startX, this->dims.y + 2, len,
|
||||
this->dims.height - 4, colour);
|
||||
|
||||
this->value = *newValue;
|
||||
}
|
||||
|
||||
void UIBar::renderBlank() {
|
||||
this->display->setCursor(this->dims.x, this->dims.y);
|
||||
this->display->print("!RANGE");
|
||||
}
|
||||
|
||||
void UIBar::Box() {
|
||||
// If the range was not set, we cannot draw our ruler
|
||||
if (this->range <= 0) {
|
||||
this->renderBlank();
|
||||
return;
|
||||
}
|
||||
|
||||
// Bounding box for the bar
|
||||
this->display->drawRect(this->dims.x, this->dims.y, this->dims.width,
|
||||
this->dims.height, RED);
|
||||
|
||||
// Now we can draw the scale (this should be easy to modify if needed)
|
||||
// dividirs will be the range of the tach - 8 = 8000rpm
|
||||
|
||||
// Calculate step size as a float for accurate positioning
|
||||
float step = (float)this->dims.width / this->range;
|
||||
for (int k = 0; k <= this->range; k++) {
|
||||
uint16_t x = round(k * step) + this->dims.x;
|
||||
|
||||
// To ensure it doesn't go beyond the bounding box
|
||||
if (x >= this->dims.x + this->dims.width) {
|
||||
x = this->dims.x + this->dims.width - 1;
|
||||
}
|
||||
|
||||
this->display->setTextColor(RED);
|
||||
this->display->drawFastVLine(x, this->dims.y + this->dims.height, 7, RED);
|
||||
|
||||
char legend[5];
|
||||
sprintf(legend, "%d", k);
|
||||
this->display->setCursor(x - (CHR_WIDTH(this->decor->textSize) / 2),
|
||||
this->dims.y + this->dims.height + 7 + 2);
|
||||
this->display->setTextColor(WHITE);
|
||||
this->display->setTextSize(this->decor->textSize);
|
||||
this->display->print(legend);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef _UI_BAR
|
||||
#define _UI_BAR
|
||||
|
||||
#include "UIComponent.h"
|
||||
|
||||
struct UIBar : UIElement {
|
||||
// This might be the only when where its more efficient to have the data
|
||||
// as integers instead
|
||||
uint32_t value = 0; // current tach value
|
||||
uint8_t range = 0; // define the tach range, ie: 8 for 8000rpm
|
||||
|
||||
UIBar(Arduino_GFX *d, UIDimensions dims, UIDecorations *decor, char *title);
|
||||
|
||||
void Update(char *val);
|
||||
void DrawBar(uint32_t *oldValue, uint32_t *newValue);
|
||||
void renderBlank();
|
||||
void Box();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,108 @@
|
||||
#include "UIComponent.h"
|
||||
// #include "u8g2.h"
|
||||
#include "Arduino.h"
|
||||
#include "HardwareSerial.h"
|
||||
#include "UIDrawing.h"
|
||||
// #include <U8g2lib.h>
|
||||
#include <cstdint>
|
||||
|
||||
#define DEBUG 1
|
||||
|
||||
UIElement::UIElement(Arduino_GFX *d, UIDimensions dims, UIDecorations *decor,
|
||||
char *title)
|
||||
: display(d), dims(dims), decor(decor), title(title) {}
|
||||
|
||||
UIDimensions::UIDimensions(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
|
||||
: x(x), y(y), width(w), height(h) {}
|
||||
|
||||
int16_t UIElement::getContentAreaX0() {
|
||||
if (this->decor->hasBorder) {
|
||||
return this->dims.x + DEFAULT_BORDER_THICKNESS + DEFAULT_MARGIN;
|
||||
}
|
||||
|
||||
return this->dims.x;
|
||||
}
|
||||
|
||||
int16_t UIElement::getContentAreaY0() {
|
||||
if (this->decor->hasBorder) {
|
||||
return this->dims.y + this->getTitleAreaHeight() +
|
||||
DEFAULT_TITLE_CONTENT_SPACING;
|
||||
}
|
||||
|
||||
return this->dims.y;
|
||||
}
|
||||
|
||||
uint16_t UIElement::getContentAreaHeight() {
|
||||
return this->dims.height - (DEFAULT_MARGIN * 2) - DEFAULT_BORDER_THICKNESS -
|
||||
this->getTitleAreaHeight();
|
||||
}
|
||||
|
||||
uint16_t UIElement::getContentAreaWidth() {
|
||||
return this->dims.width - ((DEFAULT_BORDER_THICKNESS + DEFAULT_MARGIN) * 2);
|
||||
}
|
||||
|
||||
int16_t UIElement::getTitleAreaX0() {
|
||||
return this->dims.x + DEFAULT_BORDER_THICKNESS + DEFAULT_MARGIN;
|
||||
}
|
||||
|
||||
int16_t UIElement::getTitleAreaY0() { return this->dims.y; }
|
||||
|
||||
uint16_t UIElement::getTitleAreaHeight() {
|
||||
int16_t x = 0, y = 0;
|
||||
uint16_t w = 0, h = 0;
|
||||
|
||||
this->display->setTextSize(this->decor->titleSize);
|
||||
this->display->getTextBounds((const char *)this->title, 0, 0, &x, &y, &w, &h);
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
uint16_t UIElement::getTitleAreaWidth() {
|
||||
int16_t x = 0, y = 0;
|
||||
uint16_t w = 0, h = 0;
|
||||
|
||||
this->display->setTextSize(this->decor->titleSize);
|
||||
this->display->getTextBounds((const char *)this->title, 0, 0, &x, &y, &w, &h);
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
void UIElement::replaceString(char *oldVal, char *newVal) {
|
||||
size_t newValLen = strlen(newVal);
|
||||
size_t oldValLen = strlen(oldVal);
|
||||
|
||||
// It crashs somewhere around here
|
||||
int16_t x0 = this->getContentAreaX0();
|
||||
int16_t y0 = this->getContentAreaY0(); // <- Crashes here
|
||||
// Before here
|
||||
|
||||
this->display->setTextSize(this->decor->textSize);
|
||||
|
||||
// If the new value is shorter, delete the extra in advance
|
||||
if (oldValLen > newValLen) {
|
||||
// If the old value is longer than the new value, we have to delete the
|
||||
// extra of the old value, which is whatever starts at the new value len
|
||||
for (size_t start = newValLen; start < oldValLen; start++) {
|
||||
this->display->setCursor(x0 + start * (CHR_WIDTH(this->decor->textSize)),
|
||||
y0);
|
||||
this->display->setTextColor(this->decor->bgColor);
|
||||
this->display->print(oldVal[start]);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t k = 0; k < newValLen; k++) {
|
||||
if (k <= oldValLen) {
|
||||
if (oldVal[k] != newVal[k]) {
|
||||
this->display->setCursor(x0 + k * (CHR_WIDTH(this->decor->textSize)),
|
||||
y0 + 0);
|
||||
this->display->setTextColor(this->decor->bgColor);
|
||||
this->display->print(oldVal[k]);
|
||||
}
|
||||
}
|
||||
|
||||
this->display->setCursor(x0 + k * (CHR_WIDTH(this->decor->textSize)),
|
||||
y0 + 0);
|
||||
this->display->setTextColor(this->decor->fgColor);
|
||||
this->display->print(newVal[k]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#ifndef __UI_COMPONENT
|
||||
#define __UI_COMPONENT
|
||||
|
||||
#include "Arduino_GFX.h"
|
||||
#include "UIDecorations.h"
|
||||
#include <stdint.h>
|
||||
|
||||
enum ComponentType {
|
||||
STRING,
|
||||
TABLE,
|
||||
};
|
||||
|
||||
struct UIDimensions {
|
||||
uint16_t x = 0, y = 0;
|
||||
uint16_t width = 0, height = 0;
|
||||
|
||||
UIDimensions(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
|
||||
};
|
||||
|
||||
struct UIElement {
|
||||
Arduino_GFX *display;
|
||||
UIDimensions dims;
|
||||
UIDecorations *decor;
|
||||
char *title;
|
||||
uint64_t lastUpdate = 0;
|
||||
uint16_t refreshRate = 0;
|
||||
ComponentType type;
|
||||
|
||||
UIElement(Arduino_GFX *d, UIDimensions dims, UIDecorations *decor,
|
||||
char *Title);
|
||||
|
||||
// Getters
|
||||
int16_t getContentAreaX0();
|
||||
int16_t getContentAreaY0();
|
||||
uint16_t getContentAreaHeight();
|
||||
uint16_t getContentAreaWidth();
|
||||
int16_t getTitleAreaX0();
|
||||
int16_t getTitleAreaY0();
|
||||
uint16_t getTitleAreaHeight();
|
||||
uint16_t getTitleAreaWidth();
|
||||
|
||||
// Positioning
|
||||
void horizontalCenter(UIElement *reference);
|
||||
void verticalCenter(UIElement *reference);
|
||||
void placeBelow(UIElement *reference);
|
||||
void placeRight(UIElement *reference);
|
||||
void placeLeft(UIElement *reference);
|
||||
|
||||
// Drawing
|
||||
void drawBox();
|
||||
void noBox();
|
||||
void replaceString(char *oldVal, char *newVal);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
#include "UIDecorations.h"
|
||||
|
||||
UIDecorations::UIDecorations() {}
|
||||
UIDecorations::UIDecorations(uint16_t bg, uint16_t fg, uint16_t title,
|
||||
uint16_t border, uint8_t titleSize,
|
||||
uint8_t textSize) {}
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef __UI_DECORATIONS
|
||||
#define __UI_DECORATIONS
|
||||
|
||||
#include "UIDrawing.h"
|
||||
#include <stdint.h>
|
||||
|
||||
struct UIDecorations {
|
||||
bool hasBorder = true;
|
||||
uint16_t bgColor = MAIN_BG_COLOR, fgColor = MAIN_FG_COLOR;
|
||||
uint16_t titleColor = MAIN_FG_COLOR, borderColor = RED;
|
||||
uint8_t titleSize = 2, textSize = 4;
|
||||
|
||||
UIDecorations();
|
||||
UIDecorations(uint16_t bg, uint16_t fg, uint16_t title, uint16_t border,
|
||||
uint8_t titleSize, uint8_t textSize);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,107 @@
|
||||
#include "UIDrawing.h"
|
||||
#include "Arduino_GFX.h"
|
||||
#include "HardwareSerial.h"
|
||||
#include "UIComponent.h"
|
||||
#include <cstdint>
|
||||
|
||||
/* TODO:
|
||||
* On the placement functions, change 5 to a variable or something
|
||||
*/
|
||||
|
||||
// #define DEBUG
|
||||
|
||||
uint16_t calculateHeight(int16_t titleSize, int16_t textSize, int16_t nLines) {
|
||||
return CHR_HEIGHT(titleSize) + CHR_HEIGHT(textSize) +
|
||||
DEFAULT_TITLE_CONTENT_SPACING +
|
||||
(2 * (DEFAULT_BORDER_THICKNESS + DEFAULT_MARGIN));
|
||||
}
|
||||
|
||||
uint16_t calculateWidth(int16_t textSize, int16_t nChars) {
|
||||
return CHR_WIDTH(textSize) * nChars +
|
||||
(2 * (DEFAULT_MARGIN + DEFAULT_BORDER_THICKNESS));
|
||||
}
|
||||
|
||||
void UIElement::horizontalCenter(UIElement *ref) {
|
||||
uint16_t middlePoint = 0;
|
||||
|
||||
if (ref != nullptr) {
|
||||
Serial2.println("We are here indeed!");
|
||||
middlePoint = ref->dims.x + (ref->dims.width / 2);
|
||||
} else {
|
||||
middlePoint = this->display->width() / 2;
|
||||
}
|
||||
|
||||
this->dims.x = middlePoint - (this->dims.width / 2);
|
||||
}
|
||||
|
||||
// We can add a mode of alignment here, center, left, right, whatever
|
||||
void UIElement::placeBelow(UIElement *ref) {
|
||||
// For now the default alignment will be left until I need some other
|
||||
this->dims.x = ref->dims.x;
|
||||
this->dims.y = ref->dims.y + ref->dims.height + 5;
|
||||
}
|
||||
|
||||
void UIElement::placeRight(UIElement *ref) {
|
||||
this->dims.x = ref->dims.x + ref->dims.width + 5;
|
||||
}
|
||||
|
||||
void UIElement::placeLeft(UIElement *ref) {
|
||||
this->dims.x = ref->dims.x - this->dims.width - 5;
|
||||
}
|
||||
|
||||
void UIElement::drawBox() {
|
||||
// This is the border rectangle
|
||||
if (this->decor->hasBorder) {
|
||||
for (int k = 0; k < DEFAULT_BORDER_THICKNESS; k++) {
|
||||
this->display->drawRect(
|
||||
this->dims.x + k, this->dims.y + k, this->dims.width - (k * 2),
|
||||
this->dims.height - (k * 2), this->decor->borderColor);
|
||||
}
|
||||
}
|
||||
|
||||
// For debugging purpouses
|
||||
// title area
|
||||
int16_t x = 0, y = 0;
|
||||
uint16_t w = 0, h = 0;
|
||||
|
||||
// U8G2 u8g2; // No display pin setup needed since we're only using font data
|
||||
// u8g2.setFont(u8g2_font_9x18_tf);
|
||||
// int ascent = u8g2.getAscent();
|
||||
|
||||
h = this->getTitleAreaHeight();
|
||||
uint16_t titleAreaWidth = this->getTitleAreaWidth();
|
||||
int16_t titleTopLeftX = this->getTitleAreaX0();
|
||||
#ifdef DEBUG
|
||||
// Title area bounds
|
||||
this->display->drawRect(titleTopLeftX, this->getTitleAreaY0(), titleAreaWidth,
|
||||
h, GREEN);
|
||||
#endif
|
||||
|
||||
// content area
|
||||
x = this->getContentAreaX0();
|
||||
y = this->getContentAreaY0();
|
||||
w = this->getContentAreaWidth();
|
||||
h = this->getContentAreaHeight();
|
||||
|
||||
#ifdef DEBUG
|
||||
// Content area bounds
|
||||
this->display->drawRect(x, y, w, h, BLUE);
|
||||
#endif
|
||||
|
||||
if (this->decor->hasBorder) {
|
||||
// Remove the border line behind the title
|
||||
this->display->fillRect(titleTopLeftX - DEFAULT_MARGIN, this->dims.y,
|
||||
titleAreaWidth + DEFAULT_MARGIN,
|
||||
DEFAULT_BORDER_THICKNESS, MAIN_BG_COLOR);
|
||||
|
||||
// Render the title
|
||||
this->display->setCursor(titleTopLeftX, this->dims.y);
|
||||
this->display->print(this->title);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
// Element bounds
|
||||
this->display->drawRect(this->dims.x, this->dims.y, this->dims.width,
|
||||
this->dims.height, PURPLE);
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef __CURSES_UI_VISUALS
|
||||
#define __CURSES_UI_VISUALS
|
||||
|
||||
#include <Arduino_GFX.h>
|
||||
|
||||
#define RIGHT 0
|
||||
#define INVERTED 1
|
||||
#define LEFT 2
|
||||
#define NORMAL 3
|
||||
|
||||
#define DEFAULT_BORDER_THICKNESS 3
|
||||
#define DEFAULT_TITLE_CONTENT_SPACING 3
|
||||
#define DEFAULT_MARGIN 3
|
||||
|
||||
#define MAIN_BG_COLOR RGB565(20, 10, 10)
|
||||
#define MAIN_FG_COLOR RGB565(255, 255, 255)
|
||||
|
||||
#define CHR_WIDTH(size) (6 * size)
|
||||
#define CHR_HEIGHT(size) (8 * size)
|
||||
#define CHR_SPACE(size) (size)
|
||||
|
||||
uint16_t calculateHeight(int16_t titleSize, int16_t textSize, int16_t nLines);
|
||||
uint16_t calculateWidth(int16_t textSize, int16_t nChars);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,48 @@
|
||||
#include "UIString.h"
|
||||
#include "HardwareSerial.h"
|
||||
#include "UIComponent.h"
|
||||
#include <Arduino_GFX.h>
|
||||
|
||||
UIString::UIString() : UIElement(nullptr, {0, 0, 0, 0}, nullptr, (char *)"") {
|
||||
this->type = STRING;
|
||||
memset(this->value, 0, this->bufferSize);
|
||||
}
|
||||
|
||||
UIString::UIString(Arduino_GFX *d, UIDimensions dims, UIDecorations *decor,
|
||||
char *title)
|
||||
: UIElement(d, dims, decor, title) {
|
||||
this->type = STRING;
|
||||
memset(this->value, 0, this->bufferSize);
|
||||
}
|
||||
|
||||
void UIString::Update(const char *v) {
|
||||
uint64_t time = millis();
|
||||
if ((time - this->lastUpdate) <= this->refreshRate) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the value is larger than our buffer - should add a way of knowing we
|
||||
// got an error here
|
||||
if (strlen(v) >= this->bufferSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the value hasn't changed we do not need to re-render
|
||||
if (strcmp(this->value, v) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
char newVal[this->bufferSize];
|
||||
memset(newVal, 0, this->bufferSize);
|
||||
// strncpy(newVal, v, this->bufferSize - 1);
|
||||
sprintf(newVal, "%s", v);
|
||||
|
||||
char oldVal[this->bufferSize];
|
||||
memset(oldVal, 0, this->bufferSize);
|
||||
// strncpy(oldVal, v, this->bufferSize - 1);
|
||||
sprintf(oldVal, "%s", this->value);
|
||||
|
||||
// Figure out the x0 and y0 for the text
|
||||
replaceString(oldVal, newVal);
|
||||
strncpy(this->value, v, this->bufferSize);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef __UI_STRING
|
||||
#define __UI_STRING
|
||||
|
||||
#include "UIComponent.h"
|
||||
|
||||
// Representation of single line strings
|
||||
struct UIString : UIElement {
|
||||
static const size_t bufferSize = 64;
|
||||
char value[bufferSize];
|
||||
|
||||
UIString();
|
||||
UIString(Arduino_GFX *d, UIDimensions dims, UIDecorations *decor,
|
||||
char *title);
|
||||
void Update(const char *value);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,82 @@
|
||||
#include "UITable.h"
|
||||
#include "HardwareSerial.h"
|
||||
#include "UIDecorations.h"
|
||||
#include "UIDrawing.h"
|
||||
#include <cstdint>
|
||||
|
||||
#define DEBUG
|
||||
|
||||
UITable::UITable(Arduino_GFX *gfx, UIDimensions dims, UIDecorations *decor,
|
||||
int nRows, int nCols, int *colWidths, char *title)
|
||||
: UIElement(gfx, dims, decor, title) {
|
||||
this->type = TABLE;
|
||||
this->nRows = nRows;
|
||||
this->nColumns = nCols;
|
||||
this->tableData = new UIString *[this->nRows * this->nColumns];
|
||||
this->colWidths = colWidths;
|
||||
|
||||
// Default decoration for every cell
|
||||
this->decor->textSize = 2;
|
||||
|
||||
for (int k = 0; k < ROWS * COLUMNS; k++) {
|
||||
UIDecorations *cellDecor = new UIDecorations();
|
||||
cellDecor->textSize = this->decor->textSize;
|
||||
|
||||
UIDimensions dims = {0, 0, 0, 0};
|
||||
|
||||
this->tableData[k] = new UIString(display, dims, cellDecor, (char *)"");
|
||||
}
|
||||
|
||||
int cellH = CHR_HEIGHT(this->decor->textSize) + CELL_MARGIN;
|
||||
|
||||
// Initialize the table dimensions
|
||||
//// Calculate the height
|
||||
this->dims.height = ROWS * cellH + this->getTitleAreaHeight() +
|
||||
DEFAULT_MARGIN + DEFAULT_BORDER_THICKNESS;
|
||||
|
||||
//// Calculate the width
|
||||
for (int c = 0; c < this->nColumns; c++) {
|
||||
this->dims.width +=
|
||||
(CHR_WIDTH(this->decor->textSize) + CELL_MARGIN) * this->colWidths[c];
|
||||
}
|
||||
this->dims.width += (DEFAULT_BORDER_THICKNESS + DEFAULT_MARGIN) * 2;
|
||||
}
|
||||
|
||||
uint16_t UITable::getContentAreaHeight() {
|
||||
return this->dims.height - (DEFAULT_MARGIN * 2) - DEFAULT_BORDER_THICKNESS -
|
||||
this->getTitleAreaHeight();
|
||||
}
|
||||
|
||||
/*
|
||||
* This was hardcoded for the relative and had a warning for that but
|
||||
* I recon its dynamic enough for now
|
||||
*/
|
||||
void UITable::setup() {
|
||||
int cellH = CHR_HEIGHT(this->decor->textSize) + CELL_MARGIN;
|
||||
|
||||
// Initialize the cells
|
||||
uint16_t yOffset = this->getContentAreaY0();
|
||||
for (int r = 0; r < ROWS; r++) {
|
||||
int16_t xOffset = this->getContentAreaX0();
|
||||
for (int c = 0; c < COLUMNS; c++) {
|
||||
this->tableData[r * COLUMNS + c]->refreshRate = this->refreshRate;
|
||||
this->tableData[r * COLUMNS + c]->decor->textSize = this->decor->textSize;
|
||||
this->tableData[r * COLUMNS + c]->decor->hasBorder = false;
|
||||
this->tableData[r * COLUMNS + c]->dims.x = xOffset + CELL_MARGIN;
|
||||
this->tableData[r * COLUMNS + c]->dims.y = (cellH * r) + yOffset;
|
||||
this->tableData[r * COLUMNS + c]->dims.height = cellH;
|
||||
this->tableData[r * COLUMNS + c]->dims.width =
|
||||
this->colWidths[c] * (CHR_WIDTH(this->decor->textSize) + CELL_MARGIN);
|
||||
memset(this->tableData[r * COLUMNS + c]->value, 0,
|
||||
this->tableData[r * COLUMNS + c]->bufferSize);
|
||||
|
||||
xOffset +=
|
||||
this->colWidths[c] * (CHR_WIDTH(this->decor->textSize) + CELL_MARGIN);
|
||||
|
||||
#ifdef DEBUG
|
||||
this->tableData[r * COLUMNS + c]->drawBox();
|
||||
this->tableData[r * COLUMNS + c]->Update("---");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifndef __UI_TABLE
|
||||
#define __UI_TABLE
|
||||
|
||||
#include "Arduino_GFX.h"
|
||||
#include "UIComponent.h"
|
||||
#include "UIString.h"
|
||||
|
||||
#define COLUMNS 3
|
||||
#define ROWS 5
|
||||
#define CELL_MARGIN 2
|
||||
|
||||
/* Very shitty table element, only works for the relative as of now
|
||||
*/
|
||||
struct UITable : UIElement {
|
||||
// will hold rows * columns UIStrings to fill the table
|
||||
int nRows = 0;
|
||||
int nColumns = 0;
|
||||
int *colWidths;
|
||||
UIString **tableData;
|
||||
|
||||
UITable(Arduino_GFX *gfx, UIDimensions dims, UIDecorations *decor, int nR,
|
||||
int nC, int *cWidth, char *t);
|
||||
|
||||
// void Update(StandingLine standings[5]);
|
||||
void setup();
|
||||
|
||||
// Getters
|
||||
uint16_t getContentAreaHeight();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -17,15 +17,6 @@ build_flags = -Iinclude/
|
||||
-DBOARD_HAS_PSRAM
|
||||
-mfix-esp32-psram-cache-issue
|
||||
-std=gnu++17
|
||||
-DLOG_LEVEL=LOG_LEVEL_INFO
|
||||
|
||||
build_unflags = -std=gnu++11
|
||||
# -Ilib/u8g2/csrc
|
||||
# -Ilib/u8g2/cppsrc
|
||||
board_build.arduino.memory_type = qio_opi
|
||||
|
||||
lib_deps =
|
||||
../lib/embeslogger/
|
||||
../lib/eslabsCurses/
|
||||
|
||||
lib_extra_dirs = ../lib
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#include "commands.h"
|
||||
|
||||
char* CommandToStr(Command id) {
|
||||
switch(id) {
|
||||
case CmdRequestID:
|
||||
return (char*)"CmdRequestID";
|
||||
case CmdAckID:
|
||||
return (char*)"CmdAckID";
|
||||
case CmdCreateWindow:
|
||||
return (char*)"CmdCreateWindow";
|
||||
case CmdDestroyWindow:
|
||||
return (char*)"CmdDestroyWindow";
|
||||
case CmdUpdateWinDims:
|
||||
return (char*)"CmdUpdateWinDims";
|
||||
case CMDData:
|
||||
return (char*)"CMDData";
|
||||
default:
|
||||
return (char*)"CmdUnknown";
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef __COMMANDS__
|
||||
#define __COMMANDS__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum : uint8_t {
|
||||
CmdUnknown = 0,
|
||||
CmdRequestID = 1,
|
||||
CmdAckID = 2,
|
||||
CmdCreateWindow = 3,
|
||||
CmdDestroyWindow = 4,
|
||||
CmdUpdateWinDims = 5, // NOTE: change this to a move cmd instead
|
||||
CmdUpdateWin = 6,
|
||||
CMDData = 7,
|
||||
} Command;
|
||||
|
||||
char* CommandToStr(Command id);
|
||||
|
||||
#endif
|
||||
@@ -1,10 +0,0 @@
|
||||
#include "communication.h"
|
||||
|
||||
uint8_t CRC8(const uint8_t *data, size_t len) {
|
||||
uint8_t crc = 0x00;
|
||||
while (len--) {
|
||||
crc = pgm_read_byte(&crc8_table[crc ^ *data++]);
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
#ifndef __COMMUNICATION__
|
||||
#define __COMMUNICATION__
|
||||
|
||||
#include "pgmspace.h"
|
||||
#include <Arduino.h>
|
||||
#include <cstdint>
|
||||
|
||||
const byte STX = 0x02;
|
||||
const byte ETX = 0x03;
|
||||
const byte ACK = 0x06;
|
||||
|
||||
static const uint8_t crc8_table[256] PROGMEM = {
|
||||
0x00,0x07,0x0E,0x09,0x1C,0x1B,0x12,0x15,
|
||||
0x38,0x3F,0x36,0x31,0x24,0x23,0x2A,0x2D,
|
||||
0x70,0x77,0x7E,0x79,0x6C,0x6B,0x62,0x65,
|
||||
0x48,0x4F,0x46,0x41,0x54,0x53,0x5A,0x5D,
|
||||
0xE0,0xE7,0xEE,0xE9,0xFC,0xFB,0xF2,0xF5,
|
||||
0xD8,0xDF,0xD6,0xD1,0xC4,0xC3,0xCA,0xCD,
|
||||
0x90,0x97,0x9E,0x99,0x8C,0x8B,0x82,0x85,
|
||||
0xA8,0xAF,0xA6,0xA1,0xB4,0xB3,0xBA,0xBD,
|
||||
0xC7,0xC0,0xC9,0xCE,0xDB,0xDC,0xD5,0xD2,
|
||||
0xFF,0xF8,0xF1,0xF6,0xE3,0xE4,0xED,0xEA,
|
||||
0xB7,0xB0,0xB9,0xBE,0xAB,0xAC,0xA5,0xA2,
|
||||
0x8F,0x88,0x81,0x86,0x93,0x94,0x9D,0x9A,
|
||||
0x27,0x20,0x29,0x2E,0x3B,0x3C,0x35,0x32,
|
||||
0x1F,0x18,0x11,0x16,0x03,0x04,0x0D,0x0A,
|
||||
0x57,0x50,0x59,0x5E,0x4B,0x4C,0x45,0x42,
|
||||
0x6F,0x68,0x61,0x66,0x73,0x74,0x7D,0x7A,
|
||||
0x89,0x8E,0x87,0x80,0x95,0x92,0x9B,0x9C,
|
||||
0xB1,0xB6,0xBF,0xB8,0xAD,0xAA,0xA3,0xA4,
|
||||
0xF9,0xFE,0xF7,0xF0,0xE5,0xE2,0xEB,0xEC,
|
||||
0xC1,0xC6,0xCF,0xC8,0xDD,0xDA,0xD3,0xD4,
|
||||
0x69,0x6E,0x67,0x60,0x75,0x72,0x7B,0x7C,
|
||||
0x51,0x56,0x5F,0x58,0x4D,0x4A,0x43,0x44,
|
||||
0x19,0x1E,0x17,0x10,0x05,0x02,0x0B,0x0C,
|
||||
0x21,0x26,0x2F,0x28,0x3D,0x3A,0x33,0x34,
|
||||
0x4E,0x49,0x40,0x47,0x52,0x55,0x5C,0x5B,
|
||||
0x76,0x71,0x78,0x7F,0x6A,0x6D,0x64,0x63,
|
||||
0x3E,0x39,0x30,0x37,0x22,0x25,0x2C,0x2B,
|
||||
0x06,0x01,0x08,0x0F,0x1A,0x1D,0x14,0x13,
|
||||
0xAE,0xA9,0xA0,0xA7,0xB2,0xB5,0xBC,0xBB,
|
||||
0x96,0x91,0x98,0x9F,0x8A,0x8D,0x84,0x83,
|
||||
0xDE,0xD9,0xD0,0xD7,0xC2,0xC5,0xCC,0xCB,
|
||||
0xE6,0xE1,0xE8,0xEF,0xFA,0xFD,0xF4,0xF3
|
||||
};
|
||||
uint8_t CRC8(const uint8_t *data, size_t len);
|
||||
|
||||
#endif
|
||||
@@ -1,26 +0,0 @@
|
||||
#include "packets.h"
|
||||
#include <Arduino.h>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
void initIdentificationPacket(IdentificationPacket* packet, const char* name,
|
||||
uint8_t id) {
|
||||
memset(packet, 0, sizeof(*packet));
|
||||
|
||||
packet->StartMarker = 0x02; // Start of text
|
||||
packet->EndMarker = 0x03; // End of text
|
||||
packet->DeviceID = id;
|
||||
packet->PktType = identificationPacket;
|
||||
|
||||
// snprintf(packet->deviceName, sizeof(packet->deviceName), "%s", name);
|
||||
strncpy(packet->deviceName, name, strlen(name));
|
||||
packet->deviceName[strlen(name)] = '\0';
|
||||
}
|
||||
|
||||
void initWindowIDReply(WindowIDReply* pkt, int16_t id) {
|
||||
memset(pkt, 0, sizeof(*pkt));
|
||||
|
||||
pkt->StarMarker = 0x02;
|
||||
pkt->EndMarker = 0x03;
|
||||
pkt->wID = id;
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
#ifndef __PACKETS__
|
||||
#define __PACKETS__
|
||||
|
||||
#include "UIDimensions.h"
|
||||
#include <stdint.h>
|
||||
|
||||
typedef uint8_t PacketType;
|
||||
const uint8_t identificationPacket = 0;
|
||||
|
||||
struct __attribute__((packed)) IdentificationPacket {
|
||||
char StartMarker;
|
||||
uint8_t DeviceID;
|
||||
PacketType PktType;
|
||||
char deviceName[32];
|
||||
char EndMarker;
|
||||
};
|
||||
|
||||
void initIdentificationPacket(IdentificationPacket* packet, const char* name,
|
||||
uint8_t id);
|
||||
|
||||
struct __attribute__((packed)) NewWindowBody {
|
||||
char StartMarker;
|
||||
uint16_t x0;
|
||||
uint16_t y0;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
char title[32];
|
||||
char EndMarker;
|
||||
};
|
||||
|
||||
struct __attribute__((packed)) WindowIDReply {
|
||||
char StarMarker;
|
||||
int16_t wID;
|
||||
char EndMarker;
|
||||
};
|
||||
|
||||
void initWindowIDReply(WindowIDReply* pkt, int16_t id);
|
||||
|
||||
#endif
|
||||
@@ -1,143 +0,0 @@
|
||||
#include "walkieTalkie.h"
|
||||
#include "HardwareSerial.h"
|
||||
#include "logger.h"
|
||||
#include "communication/commands.h"
|
||||
#include "communication/communication.h"
|
||||
#include <cstdint>
|
||||
#include <stdint.h>
|
||||
#include <Arduino.h>
|
||||
|
||||
namespace WalkieTalkie {
|
||||
static inline void WaitingSTXRoutine(byte *b) {
|
||||
LOG_TRACE(F("First byte is: %d\n"), *b);
|
||||
if (*b == STX) {
|
||||
state = RecvCMD;
|
||||
resetBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
static inline void ResetStateMachine() {
|
||||
state = WaitingSTX;
|
||||
resetBuffer();
|
||||
}
|
||||
|
||||
static inline void RecvCMDRoutine(byte *b) {
|
||||
LOG_TRACE(F("Reading the command byte\n"));
|
||||
|
||||
uint8_t cmdVal = Command(*b);
|
||||
if (cmdVal == 0 || cmdVal > CMDData) {
|
||||
LOG_TRACE(F("Invalid CMD byte: 0x%02x. Resetting parser.\n"), cmdVal);
|
||||
ResetStateMachine();
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_TRACE(F(" Command: %d\n"), *b);
|
||||
tempCommand = (Command)cmdVal;
|
||||
state = RecvLen;
|
||||
|
||||
resetBuffer();
|
||||
}
|
||||
|
||||
static inline void RecvLenRoutine(byte *b) {
|
||||
LOG_TRACE(F("Reading the len\n"));
|
||||
buffer[bufferIndex++] = *b;
|
||||
// we are currently using a int16_t for the len so: 2 bytes
|
||||
if (bufferIndex > 1) {
|
||||
len = buffer[0] | (buffer[1] << 8);
|
||||
|
||||
if (len > 2056 || len == 0) {
|
||||
LOG_WARN(F("Corrupt length received: %d. Resetting parser.\n"), len);
|
||||
ResetStateMachine();
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_TRACE(F("Len: %d\n"), len);
|
||||
state = RecvPayload;
|
||||
resetBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
static inline void RecvPayloadRoutine(uint8_t *payload, byte *b) {
|
||||
LOG_TRACE(F("Reading payload: %-.3d %s 0x%02x\n"), bufferIndex, " - ", *b);
|
||||
|
||||
buffer[bufferIndex++] = *b;
|
||||
if (bufferIndex == len) {
|
||||
// put the buffer data somewhere
|
||||
memcpy(payload, buffer, len);
|
||||
state = RecvCRC;
|
||||
resetBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
static inline int RecvCRCRoutine(byte *b) {
|
||||
crc = *b;
|
||||
LOG_TRACE(F("Reacing CRC: %d\n"), crc);
|
||||
|
||||
state = RecvETX;
|
||||
resetBuffer();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int RecvETXRoutine(Command *command, uint8_t *payload, byte *b) {
|
||||
LOG_TRACE(F("READING ETX\n"));
|
||||
state = WaitingSTX;
|
||||
|
||||
uint8_t computedCRC = CRC8(payload, len);
|
||||
if (*b != ETX) {
|
||||
LOG_TRACE(F("Failure, last byte was: %d\n"), *b);
|
||||
return -1;
|
||||
} else if (crc != computedCRC) {
|
||||
LOG_TRACE(F("CRC don't match. Expected: %d, got: %d\n"), crc, computedCRC);
|
||||
return -2;
|
||||
} else {
|
||||
*command = tempCommand;
|
||||
return len;
|
||||
}
|
||||
}
|
||||
|
||||
int16_t RecvStream(Command *command, uint8_t *payload, uint16_t payloadMax) {
|
||||
if (state != WaitingSTX && (millis() - lastByteTime > SERIAL_TIMEOUT_MS)) {
|
||||
state = WaitingSTX;
|
||||
resetBuffer();
|
||||
}
|
||||
|
||||
while (Serial.available() > 0) {
|
||||
lastByteTime = millis();
|
||||
uint8_t byte = Serial.read();
|
||||
|
||||
switch(state) {
|
||||
case WaitingSTX: {
|
||||
WaitingSTXRoutine(&byte);
|
||||
break;
|
||||
}
|
||||
|
||||
case RecvCMD: {
|
||||
RecvCMDRoutine(&byte);
|
||||
break;
|
||||
}
|
||||
|
||||
case RecvLen: {
|
||||
RecvLenRoutine(&byte);
|
||||
break;
|
||||
}
|
||||
|
||||
case RecvPayload: {
|
||||
RecvPayloadRoutine(payload, &byte);
|
||||
break;
|
||||
}
|
||||
|
||||
case RecvCRC: {
|
||||
RecvCRCRoutine(&byte);
|
||||
break;
|
||||
}
|
||||
|
||||
case RecvETX: {
|
||||
return RecvETXRoutine(command, payload, &byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
@@ -1,48 +0,0 @@
|
||||
#ifndef __WALKIE_TALKIE__
|
||||
#define __WALKIE_TALKIE__
|
||||
|
||||
#include "communication/commands.h"
|
||||
#include <cstdint>
|
||||
#include <Arduino.h>
|
||||
|
||||
namespace WalkieTalkie {
|
||||
typedef enum {
|
||||
WaitingSTX = 0,
|
||||
RecvCMD = 1,
|
||||
RecvLen = 2,
|
||||
RecvPayload = 3,
|
||||
RecvCRC = 4,
|
||||
RecvETX = 5,
|
||||
} RecvState;
|
||||
|
||||
static uint8_t tempLenBuffer[2];
|
||||
inline uint8_t buffer[4096];
|
||||
inline int16_t bufferIndex = 0;
|
||||
inline RecvState state = WaitingSTX;
|
||||
inline int16_t len = 0;
|
||||
inline uint8_t crc = 0;
|
||||
inline Command tempCommand = CmdUnknown;
|
||||
// Timing control
|
||||
inline int64_t lastByteTime = 0;
|
||||
inline int64_t SERIAL_TIMEOUT_MS = 15;
|
||||
|
||||
inline static void resetBuffer() {
|
||||
bufferIndex = 0;
|
||||
}
|
||||
|
||||
struct ACKPacket {
|
||||
uint8_t StartMarker;
|
||||
uint8_t ACK;
|
||||
uint8_t EndMarker;
|
||||
};
|
||||
|
||||
template<typename PacketType>
|
||||
void SendData(PacketType *packet) {
|
||||
Serial.write((uint8_t*)packet, sizeof(*packet));
|
||||
Serial.flush();
|
||||
}
|
||||
|
||||
int16_t RecvStream(Command *command, uint8_t *payload, uint16_t payloadMax);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,84 @@
|
||||
#include "data.h"
|
||||
#include <Arduino.h>
|
||||
#include <cstring>
|
||||
|
||||
uint8_t packetBuffer[sizeof(DataPacket)] = {0};
|
||||
int bufferIndex = 0;
|
||||
bool isReceiving = false;
|
||||
|
||||
void SendDataRequest() {
|
||||
DataReq req = {5};
|
||||
Serial.write((uint8_t *)&req, sizeof(req));
|
||||
Serial.flush();
|
||||
}
|
||||
|
||||
int RecvDataPacket(DataPacket *p) {
|
||||
while (Serial.available() > 0) {
|
||||
uint8_t byte = Serial.read();
|
||||
|
||||
if (!isReceiving) {
|
||||
if (byte == 0x02) {
|
||||
isReceiving = true;
|
||||
bufferIndex = 0;
|
||||
}
|
||||
}
|
||||
packetBuffer[bufferIndex++] = byte;
|
||||
|
||||
if (bufferIndex >= sizeof(DataPacket)) {
|
||||
Serial.flush();
|
||||
bufferIndex = 0;
|
||||
isReceiving = false;
|
||||
|
||||
memcpy(p, packetBuffer, sizeof(DataPacket));
|
||||
|
||||
if (p->EndMarker != 0x03) {
|
||||
// Serial2.println(F("\r////////////// DATA IS MALFORMED
|
||||
// //////////////"));
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
// for(int k = 0; k < sizeof(DataPacket); k++) {
|
||||
// if (k % 8 == 0) {
|
||||
// Serial2.printf("\r\n");
|
||||
// }
|
||||
// Serial2.printf(" 0x%02x", packetBuffer[k]);
|
||||
// }
|
||||
// Serial2.printf("\r\n");
|
||||
|
||||
// Serial2.printf("%d / %d [%2d] || %d / %d [%2d]\n\n\r",
|
||||
// ESP.getFreeHeap(),
|
||||
// ESP.getHeapSize(),
|
||||
// (int)(((float)(ESP.getHeapSize() - ESP.getFreeHeap()) /
|
||||
// ESP.getHeapSize()) *
|
||||
// 100),
|
||||
// ESP.getFreePsram(), ESP.getPsramSize(),
|
||||
// (int)(((float)(ESP.getPsramSize() - ESP.getFreePsram())
|
||||
// /
|
||||
// ESP.getPsramSize()) *
|
||||
// 100));
|
||||
|
||||
// debugDataPacket(&packet);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DebugDataPacket(DataPacket *p) {
|
||||
Serial2.printf("\rSize: %zu\n", sizeof(struct DataPacket));
|
||||
Serial2.printf("\rStartMarker: 0x%02x\n", p->StartMarker);
|
||||
Serial2.printf("\rSpeed: %s\n", p->speed);
|
||||
Serial2.printf("\rGear: %s\n", p->gear);
|
||||
Serial2.printf("\rRPM: %s\n", p->rpm);
|
||||
Serial2.printf("\rLapNumber: %s\n", p->LapNumber);
|
||||
Serial2.printf("\rcurrLapTime: %s\n", p->currLapTime);
|
||||
Serial2.printf("\rlastLapTime: %s\n", p->lastLapTime);
|
||||
Serial2.printf("\rfuelEst: %s\n", p->FuelEst);
|
||||
Serial2.printf("\rStandings:\n");
|
||||
for (int k = 0; k < 5; k++) {
|
||||
Serial2.printf("\r Lap: %s\n", p->standings[k].Lap);
|
||||
Serial2.printf("\r DriverName: %s\n", p->standings[k].DriverName);
|
||||
Serial2.printf("\r TimeBehind: %s\n", p->standings[k].TimeBehindString);
|
||||
}
|
||||
Serial2.printf("\rEndMarker: 0x%02x\n\n", p->EndMarker);
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
#ifndef __DASH_DISPLAY_DATA
|
||||
#define __DASH_DISPLAY_DATA
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct DataReq {
|
||||
uint8_t type;
|
||||
};
|
||||
|
||||
// Car data
|
||||
const int speedLen = 5;
|
||||
const int gearLen = 3;
|
||||
const int rpmLen = 6;
|
||||
const int brakeBiasLen = 6;
|
||||
|
||||
// Lap data
|
||||
const int lapNumberLen = 5;
|
||||
const int DeltaToBestLapLen = 6;
|
||||
const int bestLapTimeLen = 10;
|
||||
const int currLapTimeLen = 10;
|
||||
const int lastLapTimeLen = 10;
|
||||
|
||||
// Fuel data
|
||||
const int FuelTankLen = 15;
|
||||
const int FuelEstLen = 15;
|
||||
|
||||
const int LapStringLen = 4;
|
||||
const int DriverNameLen = 24;
|
||||
const int TimeBehindStringLen = 8;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct StandingLine {
|
||||
char Lap[LapStringLen];
|
||||
char DriverName[DriverNameLen];
|
||||
char TimeBehindString[TimeBehindStringLen];
|
||||
};
|
||||
|
||||
struct DataPacket {
|
||||
uint8_t StartMarker;
|
||||
char speed[speedLen];
|
||||
char gear[gearLen];
|
||||
char rpm[rpmLen];
|
||||
char brakeBias[brakeBiasLen];
|
||||
char LapNumber[lapNumberLen];
|
||||
char DeltaToBestLap[DeltaToBestLapLen];
|
||||
char bestLapTime[bestLapTimeLen];
|
||||
char currLapTime[currLapTimeLen];
|
||||
char lastLapTime[lastLapTimeLen];
|
||||
char FuelEst[FuelEstLen];
|
||||
StandingLine standings[5];
|
||||
uint8_t EndMarker;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
void DebugDataPacket(DataPacket *p);
|
||||
int RecvDataPacket(DataPacket *p);
|
||||
void SendDataRequest();
|
||||
|
||||
#endif
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
#define PWM_FREQUENCY 5000
|
||||
#define PWM_RESOLUTION 12
|
||||
|
||||
#define SCREEN_ORIENTATION 4
|
||||
#define SCREEN_ORIENTATION 2
|
||||
#define SCREEN_LANDSCAPE
|
||||
|
||||
/* More display class:
|
||||
|
||||
+216
-173
@@ -9,24 +9,14 @@ to have more ways to run analytics
|
||||
|
||||
#include "Arduino_GFX.h"
|
||||
#include "HardwareSerial.h"
|
||||
#include "UIComponent.h"
|
||||
#include "UIDecorations.h"
|
||||
#include "UIDimensions.h"
|
||||
#include "UIDrawing.h"
|
||||
#include "UIScreen.h"
|
||||
#include "communication/commands.h"
|
||||
#include "communication/packets.h"
|
||||
#include "communication/walkieTalkie.h"
|
||||
#include "logger.h"
|
||||
#include "values.h"
|
||||
#include "windowPool.h"
|
||||
#include "methods/methods.h"
|
||||
// #include "UIDrawing.h"
|
||||
#include "UIString.h"
|
||||
// #include "UITable.h"
|
||||
#include "UITable.h"
|
||||
#include "data.h"
|
||||
#include "displaySetup.h"
|
||||
#include "esp32-hal-psram.h"
|
||||
// #include "ui.h"
|
||||
#include "ui.h"
|
||||
#include <Arduino.h>
|
||||
#include <Arduino_GFX_Library.h>
|
||||
#include <cstdint>
|
||||
@@ -35,9 +25,6 @@ to have more ways to run analytics
|
||||
// #include <nvs.h>
|
||||
// #include <nvs_flash.h>
|
||||
|
||||
#define ESLABS_DEVICE_ID 0x01
|
||||
#define ESLABS_DEVICE_NAME "ESLabs CDashDisplay"
|
||||
|
||||
#define UART1_TX 19
|
||||
#define UART1_RX 18
|
||||
|
||||
@@ -56,182 +43,238 @@ Arduino_ESP32RGBPanel *panel = new Arduino_ESP32RGBPanel(
|
||||
//
|
||||
Arduino_GFX *gfx = new Arduino_RGB_Display(TFT_HOR_RES, TFT_VER_RES, panel, 16);
|
||||
|
||||
Curses::Screen mainScreen(gfx, UIDimensions(0, 0, TFT_HOR_RES, TFT_VER_RES),
|
||||
UIDecorations());
|
||||
// HardwareSerial debugSerial(1);
|
||||
|
||||
// UIelements
|
||||
// UIDecorations *gearTextDecor = new UIDecorations();
|
||||
// UIString gearText(gfx, UIDimensions(0, 0, 0, 0), gearTextDecor, (char
|
||||
// *)"Gear");
|
||||
//
|
||||
// UIElement mainWindow(
|
||||
// gfx,
|
||||
// UIDimensions(0, 0, TFT_HOR_RES, TFT_VER_RES),
|
||||
// gearTextDecor,
|
||||
// (char*)"MAIN WINDOW"
|
||||
// );
|
||||
DashUI *ui = new DashUI();
|
||||
UIDecorations *rpmBarDecor = new UIDecorations();
|
||||
UIDecorations *rpmTextDecor = new UIDecorations();
|
||||
UIDecorations *speedTextDecor = new UIDecorations();
|
||||
UIDecorations *gearTextDecor = new UIDecorations();
|
||||
UIDecorations *lapDeltaDecor = new UIDecorations();
|
||||
UIDecorations *bestLapDecor = new UIDecorations();
|
||||
UIDecorations *lastLapDecor = new UIDecorations();
|
||||
UIDecorations *curLapDecor = new UIDecorations();
|
||||
UIDecorations *fuelTankDecor = new UIDecorations();
|
||||
UIDecorations *fuelConsumptionDecor = new UIDecorations();
|
||||
UIDecorations *brakeBiasDecor = new UIDecorations();
|
||||
UIDecorations *relativeDecor = new UIDecorations();
|
||||
|
||||
UIBar rpmBar(gfx, UIDimensions(0, 0, 0, 0), rpmBarDecor, (char *)"Tach");
|
||||
UIString rpmText(gfx, UIDimensions(0, 0, 0, 0), rpmTextDecor, (char *)"RPM");
|
||||
UIString speedText(gfx, UIDimensions(0, 0, 0, 0), speedTextDecor,
|
||||
(char *)"Speed");
|
||||
UIString gearText(gfx, UIDimensions(0, 0, 0, 0), rpmTextDecor, (char *)"Gear");
|
||||
|
||||
UIString lapDelta(gfx, UIDimensions(0, 0, 0, 0), lapDeltaDecor,
|
||||
(char *)"Delta");
|
||||
UIString bestLap(gfx, UIDimensions(0, 0, 0, 0), bestLapDecor,
|
||||
(char *)"Best Lap");
|
||||
UIString lastLap(gfx, UIDimensions(0, 0, 0, 0), lastLapDecor,
|
||||
(char *)"Last Lap");
|
||||
UIString curLap(gfx, UIDimensions(0, 0, 0, 0), curLapDecor, (char *)"Cur. Lap");
|
||||
UIString fuelTank(gfx, UIDimensions(0, 0, 0, 0), fuelTankDecor,
|
||||
(char *)"Fuel Tank");
|
||||
UIString fuelConsumption(gfx, UIDimensions(0, 0, 0, 0), fuelConsumptionDecor,
|
||||
(char *)"Fuel Consumption");
|
||||
UIString brakeBias(gfx, UIDimensions(0, 0, 0, 0), brakeBiasDecor,
|
||||
(char *)"Brakes");
|
||||
|
||||
void setup() {
|
||||
psramInit();
|
||||
Serial.setRxBufferSize(8192);
|
||||
Serial.begin(115200);
|
||||
|
||||
// Initialize the debug serial object
|
||||
Logger::Initialize(UART1_RX, UART1_TX);
|
||||
Serial2.begin(115200, SERIAL_8N1, UART1_RX, UART1_TX);
|
||||
Serial2.flush();
|
||||
|
||||
LOG_INFO(F("=== ESP32 SimRacing DashDisplay ===\r\n"));
|
||||
LOG_INFO(F("* Initiating display\r\n"));
|
||||
Serial2.print(F("\r=== ESP32 SimRacing DashDisplay ===\r\n"));
|
||||
|
||||
Serial2.print(F("* Initiating display\r\n"));
|
||||
initialDisplaySetup(gfx);
|
||||
|
||||
// Only setup the main screen after initializing the Serial2
|
||||
mainScreen.Setup("Main Window");
|
||||
UIElement *mainWindow = mainScreen.mainWindowHandle;
|
||||
mainWindow->drawBox();
|
||||
// Setup the relative
|
||||
// Define the width of the columns in number of chars
|
||||
int *colW = (int *)malloc(sizeof(int) * 3);
|
||||
colW[0] = 3;
|
||||
colW[1] = 18;
|
||||
colW[2] = 9;
|
||||
UITable *relative =
|
||||
new UITable(gfx, UIDimensions(250, 250, 0, 0), relativeDecor, 5, 3, colW,
|
||||
(char *)"Relative");
|
||||
relativeDecor->textSize = 2;
|
||||
relative->dims.x = gfx->width() - relative->dims.width;
|
||||
relative->dims.y = gfx->height() - relative->dims.height;
|
||||
relative->refreshRate = 750;
|
||||
relative->setup();
|
||||
relative->drawBox();
|
||||
ui->relative = relative;
|
||||
|
||||
delay(500);
|
||||
LOG_INFO(F("* Ready for loop"));
|
||||
}
|
||||
|
||||
const uint16_t PayloadMax = 2056;
|
||||
uint8_t payload[PayloadMax] = {0};
|
||||
char lineBuffer[64]; // Enough for "XX XX XX XX XX XX XX XX "
|
||||
//
|
||||
void print_memory_stats() {
|
||||
multi_heap_info_t info;
|
||||
|
||||
// MALLOC_CAP_8BIT ensures we are looking at memory capable of
|
||||
// storing data (Internal RAM + PSRAM if available)
|
||||
heap_caps_get_info(&info, MALLOC_CAP_8BIT);
|
||||
|
||||
size_t total_free = info.total_free_bytes;
|
||||
size_t total_allocated = info.total_allocated_bytes;
|
||||
size_t total_size = total_free + total_allocated;
|
||||
size_t min_free = info.minimum_free_bytes; // "Low water mark"
|
||||
|
||||
LOG_INFO(F("Memory Stats:\r\n"));
|
||||
LOG_INFO(F(" Total: %u bytes\r\n"), total_size);
|
||||
LOG_INFO(F(" Used: %u bytes\r\n"), total_allocated);
|
||||
LOG_INFO(F(" Free: %u bytes\r\n"), total_free);
|
||||
LOG_INFO(F(" Min Free: %u bytes (Historic peak usage)\r\n"), min_free);
|
||||
// Setup the rpmBar box
|
||||
rpmBarDecor->textSize = 3;
|
||||
rpmBar.dims.height = 50;
|
||||
rpmBar.dims.width = 450;
|
||||
rpmBar.horizontalCenter(nullptr);
|
||||
rpmBar.dims.x += 10;
|
||||
rpmBar.range = 8;
|
||||
rpmBar.refreshRate = 50;
|
||||
ui->barTacho = &rpmBar;
|
||||
ui->barTacho->Box();
|
||||
ui->barTacho->Update((char*)"");
|
||||
|
||||
// Setup the rpmText box
|
||||
rpmTextDecor->textSize = 7;
|
||||
rpmText.dims.height =
|
||||
calculateHeight(rpmTextDecor->titleSize, rpmTextDecor->textSize, 1);
|
||||
rpmText.dims.width = calculateWidth(rpmTextDecor->textSize, 5);
|
||||
rpmText.placeBelow(&rpmBar);
|
||||
rpmText.horizontalCenter(&rpmBar);
|
||||
rpmText.dims.y += 40; // We gotta fix how the bar declares its height instead
|
||||
// of doing this here
|
||||
rpmText.refreshRate = 100;
|
||||
ui->digiTacho = &rpmText;
|
||||
ui->digiTacho->drawBox();
|
||||
ui->digiTacho->Update("-----");
|
||||
|
||||
// Setup the speedText box
|
||||
speedTextDecor->textSize = 4;
|
||||
speedText.dims.height =
|
||||
calculateHeight(speedTextDecor->titleSize, speedTextDecor->textSize, 1);
|
||||
speedText.dims.width = calculateWidth(speedTextDecor->textSize, 4);
|
||||
speedText.placeBelow(&rpmBar);
|
||||
speedText.placeRight(&rpmText);
|
||||
speedText.dims.y += 40; // We gotta fix how the bar declares its height
|
||||
// instead of doing this here
|
||||
ui->digiSpeedo = &speedText;
|
||||
ui->digiSpeedo->drawBox();
|
||||
ui->digiSpeedo->Update("---");
|
||||
|
||||
// Setup the gear text box
|
||||
gearTextDecor->textSize = 7;
|
||||
gearText.dims.height =
|
||||
calculateHeight(gearTextDecor->titleSize, gearTextDecor->textSize, 1);
|
||||
gearText.dims.width = calculateWidth(gearTextDecor->textSize, 2);
|
||||
gearText.dims.y = rpmText.dims.height + 5;
|
||||
gearText.placeLeft(&rpmText);
|
||||
ui->digiGear = &gearText;
|
||||
ui->digiGear->drawBox();
|
||||
ui->digiGear->Update("-");
|
||||
|
||||
// Setup the lap delta box
|
||||
lapDeltaDecor->textSize = 3;
|
||||
lapDelta.dims.height =
|
||||
calculateHeight(lapDeltaDecor->titleSize, lapDeltaDecor->textSize, 1);
|
||||
lapDelta.dims.width = calculateWidth(lapDeltaDecor->textSize, 6);
|
||||
lapDelta.placeBelow(&rpmText);
|
||||
lapDelta.horizontalCenter(&rpmText);
|
||||
ui->lapDelta = &lapDelta;
|
||||
ui->lapDelta->drawBox();
|
||||
ui->lapDelta->Update("--.-");
|
||||
|
||||
// Setup the best lap box
|
||||
bestLapDecor->textSize = 3;
|
||||
bestLap.dims.height =
|
||||
calculateHeight(bestLapDecor->titleSize, bestLapDecor->textSize, 1);
|
||||
bestLap.dims.width = calculateWidth(bestLapDecor->textSize, 8);
|
||||
ui->bestLap = &bestLap;
|
||||
ui->bestLap->drawBox();
|
||||
ui->bestLap->Update("--:--.--");
|
||||
|
||||
// Setup the last lap box
|
||||
lastLapDecor->textSize = 3;
|
||||
lastLap.dims.height =
|
||||
calculateHeight(lastLapDecor->titleSize, lastLapDecor->textSize, 1);
|
||||
lastLap.dims.width = calculateWidth(lastLapDecor->textSize, 8);
|
||||
lastLap.placeBelow(&bestLap);
|
||||
ui->lastLap = &lastLap;
|
||||
ui->lastLap->drawBox();
|
||||
ui->lastLap->Update("--:--.--");
|
||||
|
||||
// Setup the cur lap box
|
||||
curLapDecor->textSize = 3;
|
||||
curLap.dims.height =
|
||||
calculateHeight(curLapDecor->titleSize, curLapDecor->textSize, 1);
|
||||
curLap.dims.width = calculateWidth(curLapDecor->textSize, 8);
|
||||
curLap.placeBelow(&lastLap);
|
||||
ui->curLap = &curLap;
|
||||
ui->curLap->drawBox();
|
||||
ui->curLap->Update("--:--.--");
|
||||
|
||||
// Fuel tank
|
||||
fuelTankDecor->textSize = 3;
|
||||
fuelTank.dims.height =
|
||||
calculateHeight(fuelTankDecor->titleSize, fuelTankDecor->textSize, 1);
|
||||
fuelTank.dims.width = calculateWidth(fuelTankDecor->textSize, 12);
|
||||
fuelTank.placeBelow(&curLap);
|
||||
ui->fuelTank = &fuelTank;
|
||||
ui->fuelTank->drawBox();
|
||||
ui->fuelTank->Update("--- / ---");
|
||||
|
||||
// Fuel consumption
|
||||
fuelConsumptionDecor->textSize = 3;
|
||||
fuelConsumption.dims.height = calculateHeight(
|
||||
fuelConsumptionDecor->titleSize, fuelConsumptionDecor->textSize, 1);
|
||||
fuelConsumption.dims.width =
|
||||
calculateWidth(fuelConsumptionDecor->textSize, 12);
|
||||
fuelConsumption.placeBelow(&fuelTank);
|
||||
ui->fuelConsumption = &fuelConsumption;
|
||||
ui->fuelConsumption->drawBox();
|
||||
ui->fuelConsumption->Update("--.- | --.-");
|
||||
|
||||
// Brake bias
|
||||
brakeBiasDecor->textSize = 3;
|
||||
brakeBias.dims.height =
|
||||
calculateHeight(brakeBiasDecor->titleSize, brakeBiasDecor->textSize, 1);
|
||||
brakeBias.dims.width = calculateWidth(brakeBiasDecor->textSize, 5);
|
||||
brakeBias.placeBelow(&fuelConsumption);
|
||||
ui->brakeBias = &brakeBias;
|
||||
ui->brakeBias->drawBox();
|
||||
ui->brakeBias->Update("--.-");
|
||||
|
||||
// const char *words[] = {"hel", "wor", "why", "is", "thi", "hap", "to",
|
||||
// "me"};
|
||||
//
|
||||
// int wIndex = 0;
|
||||
// while (1) {
|
||||
// for (int row = 0; row < ROWS; row++) {
|
||||
// for (int col = 0; col < COLUMNS; col++) {
|
||||
// ui->relative->tableData[row * COLUMNS +
|
||||
// col]->Update(words[wIndex++]); if (wIndex >= 8) {
|
||||
// wIndex = 0;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// nvs_handle_t handle;
|
||||
// nvs_open("storage", NVS_READWRITE, &handle);
|
||||
// nvs_set_i32(handle, "counter", 42);
|
||||
// nvs_commit(handle);
|
||||
// nvs_close(handle);
|
||||
|
||||
Serial2.println("* Ready for loop");
|
||||
}
|
||||
|
||||
uint64_t lastDataRead = 0;
|
||||
void loop(void) {
|
||||
uint64_t cur = millis();
|
||||
// Request data here
|
||||
SendDataRequest();
|
||||
|
||||
Command cmd = CmdUnknown;
|
||||
// Receive data
|
||||
DataPacket telemetryPacket;
|
||||
int res = RecvDataPacket(&telemetryPacket);
|
||||
|
||||
if (Serial.available() > 0) {
|
||||
int16_t resp = WalkieTalkie::RecvStream(&cmd, payload, PayloadMax);
|
||||
if (resp < 0) {
|
||||
LOG_WARN(F("Failed to receive data from serial"));
|
||||
return;
|
||||
} else if (resp == 0) {
|
||||
return;
|
||||
}
|
||||
if (res == 0) {
|
||||
ui->Update(&telemetryPacket);
|
||||
|
||||
LOG_DEBUG(F("Command: %s\n"), CommandToStr(cmd));
|
||||
LOG_DEBUG(F("Response Payload Len: %d\n"), resp);
|
||||
lastDataRead = millis();
|
||||
}
|
||||
|
||||
// print_memory_stats();
|
||||
|
||||
switch(cmd) {
|
||||
case CmdRequestID:
|
||||
IdentificationPacket papers;
|
||||
initIdentificationPacket(&papers, ESLABS_DEVICE_NAME, ESLABS_DEVICE_ID);
|
||||
WalkieTalkie::SendData(&papers);
|
||||
break;
|
||||
case CmdCreateWindow: {
|
||||
int8_t newWindowID = Window::Create(payload, &mainScreen);
|
||||
if (newWindowID < 0) {
|
||||
LOG_WARN(F("Failed to add new window!\n"));
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_INFO(F("Successfully added a new window: %d\n"), newWindowID);
|
||||
|
||||
// Now we return the ID of this new window - esdi should expect it
|
||||
WindowIDReply wID;
|
||||
initWindowIDReply(&wID, newWindowID);
|
||||
WalkieTalkie::SendData(&wID);
|
||||
|
||||
break;
|
||||
}
|
||||
case CmdUpdateWinDims: {
|
||||
LOG_INFO(F("Updating Win DIMS\n"));
|
||||
bool res = Window::UpdateDims(payload, &mainScreen);
|
||||
if (!res) {
|
||||
LOG_WARN(F("Failed to update window dims\n"));
|
||||
}
|
||||
break;
|
||||
};
|
||||
case CmdUpdateWin: {
|
||||
LOG_INFO(F("UPDATEING WINDOW\n"));
|
||||
bool res = Window::UpdateWindow(payload, &mainScreen);
|
||||
if (!res) {
|
||||
LOG_WARN(F("Failed to update window\n"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CmdDestroyWindow: {
|
||||
uint8_t destroyedWinID = Window::Destroy(payload, &mainScreen);
|
||||
if (destroyedWinID < 0) {
|
||||
LOG_WARN(F("Failed to destroy window\n"));
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_INFO(F("Succesfully destroyed window: %d\n"), destroyedWinID);
|
||||
|
||||
break;
|
||||
}
|
||||
case CMDData: {
|
||||
LOG_DEBUG(F("Data Received: %d bytes\r\n"), resp);
|
||||
|
||||
// int pos = 0;
|
||||
// for (int k = 0; k < resp; k++) {
|
||||
// // Write 2 hex digits and a space to our local buffer
|
||||
// // %02X ensures leading zeros (e.g., 0A instead of A)
|
||||
// pos += sprintf(lineBuffer + pos, "%02X ", payload[k]);
|
||||
//
|
||||
// // Every 8 bytes OR if it's the very last byte in the payload
|
||||
// if ((k + 1) % 8 == 0 || k == resp - 1) {
|
||||
// // Send the completed line to the logger
|
||||
// // We use LOG_DEBUG or similar so we don't spam [WARN] on every line
|
||||
// LOG_DEBUG(F("%s\r\n"), lineBuffer);
|
||||
//
|
||||
// // Reset buffer position for the next line
|
||||
// pos = 0;
|
||||
// memset(lineBuffer, 0, sizeof(lineBuffer));
|
||||
// }
|
||||
// }
|
||||
// LOG_DEBUG(F("\r\n"));
|
||||
|
||||
Data::Parse(payload, resp, &mainScreen);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(F("Command: %s\n"), CommandToStr(cmd));
|
||||
LOG_ERROR(F("Unknown Command: `%02X` has not been implemented yet.\n"), cmd);
|
||||
int pos = 0;
|
||||
for (int k = 0; k < resp; k++) {
|
||||
// Write 2 hex digits and a space to our local buffer
|
||||
// %02X ensures leading zeros (e.g., 0A instead of A)
|
||||
pos += sprintf(lineBuffer + pos, "%02X ", payload[k]);
|
||||
|
||||
// Every 8 bytes OR if it's the very last byte in the payload
|
||||
if ((k + 1) % 8 == 0 || k == resp - 1) {
|
||||
// Send the completed line to the logger
|
||||
// We use LOG_DEBUG or similar so we don't spam [WARN] on every line
|
||||
LOG_WARN(F("%s\r\n"), lineBuffer);
|
||||
|
||||
// Reset buffer position for the next line
|
||||
pos = 0;
|
||||
memset(lineBuffer, 0, sizeof(lineBuffer));
|
||||
}
|
||||
}
|
||||
LOG_WARN(F("\r\n"));
|
||||
break;
|
||||
}
|
||||
// If no data is received for 5 seconds, reset the display
|
||||
if (millis() - lastDataRead >= 5000) {
|
||||
// ui.speedometer->Update(0);
|
||||
// ui.numberTach->Update(0);
|
||||
// ui.gear->Update(0);
|
||||
// ui.barTach->Update(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,342 +0,0 @@
|
||||
#include "methods.h"
|
||||
#include "Arduino.h"
|
||||
#include "UIBar.h"
|
||||
#include "UIDecorations.h"
|
||||
#include "UIDrawing.h"
|
||||
#include "communication/packets.h"
|
||||
#include "logger.h"
|
||||
#include "UIComponent.h"
|
||||
#include "UIScreen.h"
|
||||
#include "values.h"
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <string.h>
|
||||
|
||||
namespace Window {
|
||||
// Window creation
|
||||
void printUIDimsPacket(UIDimensions *dims) {
|
||||
LOG_INFO(F(" Dimensions:\n"));
|
||||
LOG_INFO(F(" X0 : %d\n"), dims->x);
|
||||
LOG_INFO(F(" Y0 : %d\n"), dims->y);
|
||||
LOG_INFO(F(" Width : %d\n"), dims->width);
|
||||
LOG_INFO(F(" Height: %d\n"), dims->height);
|
||||
}
|
||||
|
||||
void printUIDecorPacket(UIDecorations *decor) {
|
||||
LOG_INFO(F(" Decorations:\n"));
|
||||
LOG_INFO(F(" HasBorder : %d\n"), decor->hasBorder);
|
||||
LOG_INFO(F(" BGColour : %d\n"), decor->bgColor);
|
||||
LOG_INFO(F(" FGColour : %d\n"), decor->fgColor);
|
||||
LOG_INFO(F(" TitleColour : %d\n"), decor->titleColor);
|
||||
LOG_INFO(F(" BorderColour: %d\n"), decor->borderColor);
|
||||
LOG_INFO(F(" TitleSize : %d\n"), decor->titleSize);
|
||||
LOG_INFO(F(" TextSize : %d\n"), decor->textSize);
|
||||
}
|
||||
|
||||
void printUIOptionsPacket(UIWindowOpts* opts) {
|
||||
LOG_INFO(F(" Options:\n"));
|
||||
LOG_INFO(F(" ShowID : %d\n"), opts->ShowID);
|
||||
LOG_INFO(F(" Type : %d\n"), opts->WinType);
|
||||
LOG_INFO(F(" PreviewValue: %s\n"), opts->PreviewValue);
|
||||
}
|
||||
|
||||
void printUIWindowPacket(UICreateWindowPacket *win) {
|
||||
LOG_INFO(F("New window:\n"));
|
||||
printUIDimsPacket(&win->dims);
|
||||
printUIDecorPacket(&win->decor);
|
||||
printUIOptionsPacket(&win->opts);
|
||||
LOG_INFO(F(" Title: %s\n"), win->title);
|
||||
}
|
||||
|
||||
void printUpdateDimsPacket(UpdateDimsPacket *pkt) {
|
||||
LOG_INFO(F("NEW DIMENSIONS:\n"));
|
||||
LOG_INFO(F(" TARGET WINDOW: %d\n"), pkt->wID);
|
||||
printUIDimsPacket(&pkt->dims);
|
||||
}
|
||||
|
||||
UICreateWindowPacket::UICreateWindowPacket() {}
|
||||
|
||||
void setTitleWithOpts(UICreateWindowPacket* win, UIElement* elem, int16_t ID) {
|
||||
if (win->opts.ShowID == ShowIDTrue) {
|
||||
elem->SetTitle((char *)"%s [%2d]", (const char *)win->title, ID);
|
||||
} else {
|
||||
elem->SetTitle((char *)"%s", (const char *)win->title);
|
||||
}
|
||||
}
|
||||
|
||||
int8_t Create(uint8_t *payload, Curses::Screen *mainScreen) {
|
||||
// Load the payload into a struct
|
||||
UICreateWindowPacket win;
|
||||
// Maybe find a better way to copy this - if we had metadata to the payload
|
||||
// for example
|
||||
memcpy((void*)&win, payload, sizeof(UICreateWindowPacket));
|
||||
printUIWindowPacket(&win);
|
||||
|
||||
UIElement *mainWindow = mainScreen->mainWindowHandle;
|
||||
int16_t childID = mainWindow->AddChild((ComponentType)win.opts.WinType);
|
||||
if (childID < 0) {
|
||||
LOG_DEBUG(F("COULD NOT ADD CHILD\r\n"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
UIElement *childComponent = mainWindow->GetChild(childID);
|
||||
|
||||
setTitleWithOpts(&win, childComponent, childID);
|
||||
|
||||
// This are universal things
|
||||
childComponent->SetUIDecorations(win.decor);
|
||||
childComponent->SetUIDimensions(UIDimensions(win.dims.x, win.dims.y,
|
||||
win.dims.width, win.dims.height));
|
||||
childComponent->SetDisplay(mainScreen->display);
|
||||
|
||||
// NOTE: if I move to a composition style thing I guess I can do all this
|
||||
// setup at start up time.
|
||||
// I can also just have a base void* chunk of data with options I send to
|
||||
// each UI type and the UI type handles it somehow, we'll see
|
||||
// Handle each type of window
|
||||
switch ((ComponentType)win.opts.WinType) {
|
||||
case STRING:
|
||||
LOG_DEBUG(F("Setting up the STRING type UIWindow\r\n"));
|
||||
childComponent->drawBox();
|
||||
break;
|
||||
case BAR: {
|
||||
LOG_DEBUG(F("Setting up the BAR type UIWindow\r\n"));
|
||||
UIBar* bar = (UIBar*)childComponent;
|
||||
|
||||
bar->range = 9;
|
||||
bar->drawBox();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
childComponent->Update(win.opts.PreviewValue, true);
|
||||
|
||||
return childID;
|
||||
}
|
||||
|
||||
bool UpdateDims(uint8_t *payload, Curses::Screen *mainScreen) {
|
||||
UpdateDimsPacket pkt;
|
||||
memcpy((void*)&pkt, payload, sizeof(UpdateDimsPacket));
|
||||
printUpdateDimsPacket(&pkt);
|
||||
|
||||
UIElement *mainWindow = mainScreen->mainWindowHandle;
|
||||
UIElement* win = mainWindow->GetChild(pkt.wID);
|
||||
if (win == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete the old window
|
||||
fillRect(win->dims.x, win->dims.y, win->dims.width,
|
||||
win->dims.height, win->decor.bgColor, win->display);
|
||||
|
||||
// Update the windows dimensions
|
||||
win->dims = pkt.dims;
|
||||
|
||||
LOG_DEBUG(F("Win Type is: %d\r\n"), win->type);
|
||||
|
||||
// Redraw the window
|
||||
switch ((ComponentType)win->type) {
|
||||
case STRING:
|
||||
win->drawBox();
|
||||
break;
|
||||
case BAR:
|
||||
{
|
||||
LOG_DEBUG(F("Updating UIBar dimensions\r\n"));
|
||||
UIBar* bar = (UIBar*)win;
|
||||
bar->drawBox();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
win->Redraw();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UpdateWindow(uint8_t *payload, Curses::Screen *mainScreen) {
|
||||
UIUpdateWindowPacket pkt;
|
||||
memcpy((void*)&pkt, payload, sizeof(UICreateWindowPacket));
|
||||
|
||||
printUIWindowPacket(&pkt.data);
|
||||
|
||||
UIElement* mainWindow = mainScreen->mainWindowHandle;
|
||||
UIElement* win = mainWindow->GetChild(pkt.WinID);
|
||||
if (win == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete the old window
|
||||
fillRect(win->dims.x, win->dims.y, win->dims.width,
|
||||
win->dims.height, win->decor.bgColor, win->display);
|
||||
|
||||
// Update the windows dimensons, decor and title
|
||||
win->dims = pkt.data.dims;
|
||||
win->decor = pkt.data.decor;
|
||||
setTitleWithOpts(&pkt.data, win, pkt.WinID);
|
||||
|
||||
switch ((ComponentType)win->type) {
|
||||
case STRING:
|
||||
win->drawBox();
|
||||
break;
|
||||
case BAR:
|
||||
{
|
||||
LOG_DEBUG(F("Updating UIBar\r\n"));
|
||||
UIBar* bar = (UIBar*)win;
|
||||
bar->drawBox();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
win->Update(pkt.data.opts.PreviewValue, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Window destruction
|
||||
void printUIDestroyWindowPacket(UIDestroyWindowPacket *win) {
|
||||
LOG_INFO(F("Destroy window:\n"));
|
||||
LOG_INFO(F(" WinID: %d\n"), win->WinID);
|
||||
}
|
||||
|
||||
int8_t Destroy(uint8_t *payload, Curses::Screen *mainScreen) {
|
||||
UIDestroyWindowPacket win;
|
||||
memcpy(&win, payload, sizeof(UIDestroyWindowPacket));
|
||||
printUIDestroyWindowPacket(&win);
|
||||
|
||||
UIElement *mainWindow = mainScreen->mainWindowHandle;
|
||||
mainWindow->RemoveChild(win.WinID);
|
||||
|
||||
return win.WinID;
|
||||
}
|
||||
};
|
||||
|
||||
namespace Data {
|
||||
char buffer[128];
|
||||
char strValue[128];
|
||||
|
||||
uint8_t Parse(uint8_t *payload, size_t payloadSize, Curses::Screen* mainScreen) {
|
||||
uint16_t curPos = 0;
|
||||
|
||||
for (; curPos < payloadSize;) {
|
||||
// Get the data type from the current position
|
||||
int16_t wID;
|
||||
memcpy(&wID, payload + curPos, 2);
|
||||
curPos += 2;
|
||||
|
||||
uint8_t type = payload[curPos];
|
||||
|
||||
switch (type) {
|
||||
case DataTypeUINT8: {
|
||||
uint8_t value = payload[curPos + 1];
|
||||
LOG_DEBUG(F("[0x%02X] RECEIVED [%2d]: %d\r\n"), type, wID, value);
|
||||
|
||||
sprintf(buffer, "%d", value);
|
||||
|
||||
curPos += 2;
|
||||
break;
|
||||
}
|
||||
case DataTypeINT8: {
|
||||
int8_t value = (int8_t)payload[curPos + 1];
|
||||
LOG_DEBUG(F("[0x%02X] RECEIVED [%2d]: %d\r\n"), type, wID, value);
|
||||
|
||||
sprintf(buffer, "%d", value);
|
||||
|
||||
curPos += 2;
|
||||
break;
|
||||
}
|
||||
case DataTypeUINT16: {
|
||||
uint16_t value;
|
||||
memcpy(&value, payload + curPos + 1, 2);
|
||||
|
||||
LOG_DEBUG(F("[0x%02X] RECEIVED [%2d]: %d\r\n"), type, wID, value);
|
||||
sprintf(buffer, "%d", value);
|
||||
|
||||
curPos += 3; // 1 (Type) + 2 (Value)
|
||||
break;
|
||||
}
|
||||
case DataTypeINT16: {
|
||||
int16_t value;
|
||||
memcpy(&value, payload + curPos + 1, 2);
|
||||
LOG_DEBUG(F("[0x%02X] RECEIVED [%2d]: %d\r\n"), type, wID, value);
|
||||
sprintf(buffer, "%d", value);
|
||||
|
||||
curPos += 3;
|
||||
break;
|
||||
}
|
||||
case DataTypeUINT32: {
|
||||
uint32_t value;
|
||||
memcpy(&value, payload + curPos + 1, 4);
|
||||
LOG_DEBUG(F("[0x%02X] RECEIVED [%2d]: %d\r\n"), type, wID, value);
|
||||
sprintf(buffer, "%d", value);
|
||||
|
||||
curPos += 5; // 1 (Type) + 4 (Value)
|
||||
break;
|
||||
}
|
||||
case DataTypeINT32: {
|
||||
int32_t value;
|
||||
memcpy(&value, payload + curPos + 1, 4);
|
||||
LOG_DEBUG(F("[0x%02X] RECEIVED [%2d]: %d\r\n"), type, wID, value);
|
||||
sprintf(buffer, "%d", value);
|
||||
|
||||
curPos += 5;
|
||||
break;
|
||||
}
|
||||
case DataTypeUINT64: {
|
||||
uint64_t value;
|
||||
memcpy(&value, payload + curPos + 1, 8);
|
||||
LOG_DEBUG(F("[0x%02X] RECEIVED [%2d]: %d\r\n"), type, wID, value);
|
||||
sprintf(buffer, "%ld", value);
|
||||
|
||||
curPos += 9; // 1 (Type) + 8 (Value)
|
||||
break;
|
||||
}
|
||||
case DataTypeINT64: {
|
||||
int64_t value;
|
||||
memcpy(&value, payload + curPos + 1, 8);
|
||||
LOG_DEBUG(F("[0x%02X] RECEIVED [%2d]: %d\r\n"), type, wID, value);
|
||||
sprintf(buffer, "%ld", value);
|
||||
|
||||
curPos += 9;
|
||||
break;
|
||||
}
|
||||
case DataTypeCHAR: {
|
||||
uint8_t value = payload[curPos + 1];
|
||||
LOG_DEBUG(F("[0x%02X] RECEIVED [%2d]: %c\r\n"), type, wID, value);
|
||||
sprintf(buffer, "%c", value);
|
||||
|
||||
curPos += 2;
|
||||
break;
|
||||
}
|
||||
case DataTypeSTRING: {
|
||||
// Strings have: Type (1), Len (1), Data (Len)
|
||||
uint8_t len = payload[curPos + 1];
|
||||
memcpy(strValue, payload + curPos + 2, len);
|
||||
strValue[len] = '\0'; // Null terminator
|
||||
//
|
||||
LOG_DEBUG(F("[0x%02X] RECEIVED [%2d]: %s\r\n"), type, wID, strValue);
|
||||
sprintf(buffer, "%s", strValue);
|
||||
|
||||
curPos += (2 + len);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// If we hit an unknown type, we are desynced.
|
||||
// Better to stop than to read garbage.
|
||||
LOG_ERROR(F("Unknown Type 0x%02X at pos %d\r\n"), type, curPos);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update the window value here
|
||||
UIElement* mainWindow = mainScreen->mainWindowHandle;
|
||||
UIElement* win = mainWindow->GetChild(wID);
|
||||
if (win == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
win->Update(buffer, false);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
#ifndef __METHODS__
|
||||
#define __METHODS__
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdint.h>
|
||||
#include "UIScreen.h"
|
||||
#include "UIDimensions.h"
|
||||
#include "UIDecorations.h"
|
||||
|
||||
namespace Window {
|
||||
const uint8_t ShowIDFalse = 0;
|
||||
const uint8_t ShowIDTrue = 1;
|
||||
|
||||
struct UIWindowOpts {
|
||||
uint8_t ShowID;
|
||||
uint8_t WinType;
|
||||
// NOTE: I recon we need to change this yo
|
||||
char PreviewValue[32];
|
||||
};
|
||||
|
||||
// Window creation
|
||||
struct UICreateWindowPacket {
|
||||
UIDimensions dims;
|
||||
UIDecorations decor;
|
||||
UIWindowOpts opts;
|
||||
char title[32]; // Note: we optimize this type of data transfer if necessary
|
||||
// we are sending 32 - len(title) extra bytes everytime
|
||||
|
||||
UICreateWindowPacket();
|
||||
};
|
||||
|
||||
void printUIWindowPacket(UICreateWindowPacket *win);
|
||||
int8_t Create(uint8_t *payload, Curses::Screen *mainScreen);
|
||||
|
||||
// Window destruction
|
||||
struct UIDestroyWindowPacket {
|
||||
int16_t WinID;
|
||||
};
|
||||
|
||||
void printUIDestroyWindowPacket(UIDestroyWindowPacket *win);
|
||||
int8_t Destroy(uint8_t *payload, Curses::Screen *mainScreen);
|
||||
|
||||
// NOTE: if I make the window update send these fields with the current
|
||||
// values I can reduce the code for this to a single struct instead I reckon
|
||||
// Window Dimensions Updates
|
||||
struct UpdateDimsPacket {
|
||||
int16_t wID;
|
||||
UIDimensions dims;
|
||||
};
|
||||
|
||||
void printUpdateDimsPacket(UpdateDimsPacket *pkt);
|
||||
bool UpdateDims(uint8_t *payload, Curses::Screen *mainScreen);
|
||||
|
||||
struct UIUpdateWindowPacket {
|
||||
int16_t WinID;
|
||||
UICreateWindowPacket data;
|
||||
};
|
||||
|
||||
bool UpdateWindow(uint8_t *payload, Curses::Screen *mainScreen);
|
||||
};
|
||||
|
||||
namespace Screen {
|
||||
struct UICreateScreenPacket{
|
||||
uint16_t x0;
|
||||
uint16_t y0;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
char title[32]; // Note: we optimize this type of data transfer if necessary
|
||||
// we are sending 32 - len(title) extra bytes everytime
|
||||
};
|
||||
}
|
||||
|
||||
namespace Data {
|
||||
typedef uint8_t DataType;
|
||||
const DataType DataTypeUINT8 = 0;
|
||||
const DataType DataTypeINT8 = 1;
|
||||
const DataType DataTypeUINT16 = 2;
|
||||
const DataType DataTypeINT16 = 3;
|
||||
const DataType DataTypeUINT32 = 4;
|
||||
const DataType DataTypeINT32 = 5;
|
||||
const DataType DataTypeUINT64 = 6;
|
||||
const DataType DataTypeINT64 = 7;
|
||||
const DataType DataTypeSTRING = 8;
|
||||
const DataType DataTypeCHAR = 9;
|
||||
|
||||
uint8_t Parse(uint8_t *payload, size_t payloadSize, Curses::Screen* mainScreen);
|
||||
}
|
||||
|
||||
#endif
|
||||
+27
-26
@@ -1,26 +1,27 @@
|
||||
// #include "ui.h"
|
||||
// #include "HardwareSerial.h"
|
||||
// #include <cstdint>
|
||||
//
|
||||
// DashUI::DashUI() {}
|
||||
//
|
||||
// void DashUI::Update(DataPacket *p) {
|
||||
// this->barTacho->Update(p->rpm);
|
||||
// this->digiTacho->Update(p->rpm);
|
||||
// this->digiSpeedo->Update(p->speed);
|
||||
// this->digiGear->Update(p->gear);
|
||||
// this->lapDelta->Update(p->DeltaToBestLap);
|
||||
// this->bestLap->Update(p->bestLapTime);
|
||||
// this->curLap->Update(p->currLapTime);
|
||||
// this->lastLap->Update(p->lastLapTime);
|
||||
// this->fuelConsumption->Update(p->FuelEst);
|
||||
// this->brakeBias->Update(p->brakeBias);
|
||||
//
|
||||
// for (int row = 0; row < ROWS; row++) {
|
||||
// this->relative->tableData[row * COLUMNS + 0]->Update(p->standings[row].Lap);
|
||||
// this->relative->tableData[row * COLUMNS + 1]->Update(
|
||||
// p->standings[row].DriverName);
|
||||
// this->relative->tableData[row * COLUMNS + 2]->Update(
|
||||
// p->standings[row].TimeBehindString);
|
||||
// }
|
||||
// }
|
||||
#include "ui.h"
|
||||
#include "HardwareSerial.h"
|
||||
#include "data.h"
|
||||
#include <cstdint>
|
||||
|
||||
DashUI::DashUI() {}
|
||||
|
||||
void DashUI::Update(DataPacket *p) {
|
||||
this->barTacho->Update(p->rpm);
|
||||
this->digiTacho->Update(p->rpm);
|
||||
this->digiSpeedo->Update(p->speed);
|
||||
this->digiGear->Update(p->gear);
|
||||
this->lapDelta->Update(p->DeltaToBestLap);
|
||||
this->bestLap->Update(p->bestLapTime);
|
||||
this->curLap->Update(p->currLapTime);
|
||||
this->lastLap->Update(p->lastLapTime);
|
||||
this->fuelConsumption->Update(p->FuelEst);
|
||||
this->brakeBias->Update(p->brakeBias);
|
||||
|
||||
for (int row = 0; row < ROWS; row++) {
|
||||
this->relative->tableData[row * COLUMNS + 0]->Update(p->standings[row].Lap);
|
||||
this->relative->tableData[row * COLUMNS + 1]->Update(
|
||||
p->standings[row].DriverName);
|
||||
this->relative->tableData[row * COLUMNS + 2]->Update(
|
||||
p->standings[row].TimeBehindString);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
// #ifndef __DASH_DISPLAY_UI
|
||||
// #define __DASH_DISPLAY_UI
|
||||
//
|
||||
// #include "Arduino_GFX.h"
|
||||
// #include "UIString.h"
|
||||
// #include "UITable.h"
|
||||
// #include "UIBar.h"
|
||||
// #include "data.h"
|
||||
//
|
||||
// struct DashUI {
|
||||
// UIBar *barTacho;
|
||||
// UIString *digiTacho;
|
||||
// UIString *digiSpeedo;
|
||||
// UIString *digiGear;
|
||||
// UIString *lapDelta;
|
||||
// UIString *bestLap;
|
||||
// UIString *lastLap;
|
||||
// UIString *curLap;
|
||||
// UIString *fuelTank;
|
||||
// UIString *fuelConsumption;
|
||||
// UIString *brakeBias;
|
||||
// UITable *relative;
|
||||
//
|
||||
// DashUI();
|
||||
// void Update(DataPacket *p);
|
||||
// };
|
||||
//
|
||||
// #endif
|
||||
#ifndef __DASH_DISPLAY_UI
|
||||
#define __DASH_DISPLAY_UI
|
||||
|
||||
#include "Arduino_GFX.h"
|
||||
#include "UIString.h"
|
||||
#include "UITable.h"
|
||||
#include "UIBar.h"
|
||||
#include "data.h"
|
||||
|
||||
struct DashUI {
|
||||
UIBar *barTacho;
|
||||
UIString *digiTacho;
|
||||
UIString *digiSpeedo;
|
||||
UIString *digiGear;
|
||||
UIString *lapDelta;
|
||||
UIString *bestLap;
|
||||
UIString *lastLap;
|
||||
UIString *curLap;
|
||||
UIString *fuelTank;
|
||||
UIString *fuelConsumption;
|
||||
UIString *brakeBias;
|
||||
UITable *relative;
|
||||
|
||||
DashUI();
|
||||
void Update(DataPacket *p);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user