#include "config.h"
#include "odibee_font.h"
#include "gauge_bg.h"
#include <TFT_eSPI.h>
#include <SPI.h> // this is needed for display
#include <TJpg_Decoder.h>
// The display also uses hardware SPI, plus #9 & #10
#define TFT_CS 15
#define TFT_DC 2
#define TFT_MOSI 23
#define TFT_SCLK 18
#define SPRITE_NEEDLE_WIDTH 40
#define SPRITE_NEEDLE_HEIGHT 160
#define BUTTON_PIN 14
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite sprite_needle = TFT_eSprite(&tft); // Sprite object for needle
TFT_eSprite sprite_gaugeface = TFT_eSprite(&tft); // Sprite for Gauge background
TFT_eSprite sprite_screenbuffer = TFT_eSprite(&tft); // full screen buffer sprite
volatile int currentMeasureId = 0;
uint32_t colorFont;
uint32_t colorCenterCircle;
uint32_t colorFontValue;
// =======================================================================================
// This function will be called during decoding of the jpeg file
// =======================================================================================
bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap) {
// Stop further decoding as image is running off bottom of screen
if (y >= tft.height()) return 0;
// This function will clip the image block rendering automatically at the TFT boundaries
tft.pushImage(x, y, w, h, bitmap); //Keeping the TFT push just to have the bottom 10px displayed as we are limited to a 230px high sprite.
sprite_gaugeface.pushImage(x, y, w, h, bitmap);
// Return 1 to decode next block
return 1;
}
// =======================================================================================
// Setup
// =======================================================================================
void setup(void) {
Serial.begin(115200); // Debug only 115200
pinMode(BUTTON_PIN, INPUT_PULLUP);
// Prepare JPEG decoder
TJpgDec.setSwapBytes(true);
TJpgDec.setCallback(tft_output);
tft.begin();
tft.setRotation(0);
tft.setPivot(120, 120);
colorFont = rgb2int("#b8c974"); //#a7b473
colorCenterCircle = rgb2int("#635e47");
colorFontValue = rgb2int("#8b8660");
//Creating the NEEDLE sprite
if (sprite_needle.createSprite(SPRITE_NEEDLE_WIDTH, SPRITE_NEEDLE_HEIGHT) == nullptr) Serial.println("Error: sprite_needle not created, not enough free RAM!");
sprite_needle.setPivot(sprite_needle.width() / 2, 120); // Set pivot point in this Sprite
sprite_needle.loadFont(OdibeeSansRegular28);
sprite_needle.setTextDatum(TC_DATUM);
sprite_needle.setTextColor(colorFont, TFT_BLACK);
//Creating the GAUGE FACE sprite
if (sprite_gaugeface.createSprite(240, 230) == nullptr) Serial.println("Error: sprite_gaugeface not created, not enough free RAM!");
sprite_gaugeface.setPivot(120, 120); // Set pivot point in this Sprite
sprite_gaugeface.setTextDatum(TC_DATUM);
//Creating the SCREEN BUFFER sprite
if (sprite_screenbuffer.createSprite(240, 230) == nullptr) Serial.println("Error: sprite_screenbuffer not created, not enough free RAM!");
sprite_screenbuffer.setSwapBytes(true);
sprite_screenbuffer.setPivot(120, 120); // Set pivot point in this Sprite
Serial.println("--- SET UP DONE ---");
prepareGaugeSprites();
}
void loop() {
if (digitalRead(BUTTON_PIN) == LOW) {
Serial.println("Button pressed!");
currentMeasureId++;
if(currentMeasureId == MEASURES_COUNT ){
Serial.println("back to Zero!");
currentMeasureId = 0;
}
Serial.println(currentMeasureId);
prepareGaugeSprites();
delay(200);
}
//read measures values
for (int i = 0; i < MEASURES_COUNT; i++) {
measures[i].value = 0; // TO DO
//int value = analogRead(13); // forcing read on Pin w/ potentiometer for tests
int value = analogRead(13); // forcing read on Pin w/ potentiometer for tests
measures[i].value = (double)(map(value, 0, 4095, measures[i].valueMin*100, measures[i].valueMax*100))/100;
measures[i].angle = (measures[i].value - measures[i].valueMin) * (measures[i].angleMax - measures[i].angleMin) / (measures[i].valueMax - measures[i].valueMin) + measures[i].angleMin;
measures[i].valueTxt = measures[i].value;
}
// Update gauge & needle position
updateGauge(measures[currentMeasureId].angle);
}
// =======================================================================================
// PREPARE GAUGE SPRITES
// =======================================================================================
void prepareGaugeSprites() {
Serial.print("Preparing sprites for ");
Serial.println(measures[currentMeasureId].name);
sprite_screenbuffer.fillScreen(TFT_BLACK);
TJpgDec.drawJpg(0, 0, gauge_bg, sizeof(gauge_bg));
if ( measures[currentMeasureId].type==MEASURES_TYPE_ALL){
sprite_screenbuffer.unloadFont();
sprite_screenbuffer.setTextColor(TFT_WHITE,TFT_BLACK);
sprite_screenbuffer.setTextDatum(TR_DATUM);
}
else{
sprite_screenbuffer.loadFont(OdibeeSansRegular28);
sprite_screenbuffer.setTextColor(colorFontValue, colorCenterCircle);
sprite_screenbuffer.setTextDatum(TC_DATUM);
sprite_gaugeface.fillSmoothCircle(120, 120, 63, colorCenterCircle );
sprite_gaugeface.fillSmoothCircle(120, 120, 20, TFT_BLACK );
}
if(measures[currentMeasureId].redZoneAngleMin != 0 && measures[currentMeasureId].redZoneAngleMax != 0){
for(uint32_t i=0 ; i<4 ; i++ ){
//sprite_gaugeface.drawSmoothArc(120, 120, 133-(i*5), 130-(i*5), measure.redZoneAngleMin, measure.redZoneAngleMax, TFT_RED, TFT_BLACK, false);
//sprite_gaugeface.drawArc(120, 120, 133-(i*5), 130-(i*5), measure.redZoneAngleMin, measure.redZoneAngleMax, TFT_RED, TFT_BLACK, true);
}
}
int16_t valueIncrement = (measures[currentMeasureId].valueMax-measures[currentMeasureId].valueMin)/(measures[currentMeasureId].dots-1);
float angleIncrement = (float)(measures[currentMeasureId].angleMax-measures[currentMeasureId].angleMin)/((float)measures[currentMeasureId].dots-1);
for(uint32_t i=0 ; i<measures[currentMeasureId].dots ; i++ ){
float currAngle = ((float)i*angleIncrement)+measures[currentMeasureId].angleMin;
sprite_needle.fillScreen(TFT_BLACK);
String currValue;
if(measures[currentMeasureId].labels.length()!=0){
currValue = getLabel(measures[currentMeasureId].labels, i);
}
else{
currValue = String((i*valueIncrement) + measures[currentMeasureId].valueMin);
}
sprite_needle.drawString(currValue, SPRITE_NEEDLE_WIDTH/2, 15);
// Drawing the rectangular "Dot" below the number/label
sprite_needle.fillRect((SPRITE_NEEDLE_WIDTH/2)-3, 43, 6, 13, colorFont);
sprite_needle.pushRotated(&sprite_gaugeface, currAngle, TFT_BLACK);
//drawing interdots between current and previous dot :
if(i>0){ //not for the first dot obviously
float angleIncrement2 = (float)(angleIncrement)/(float)(measures[currentMeasureId].interDots + 1);
sprite_needle.fillScreen(TFT_BLACK);
// Drawing the smaller rectangular "interDot"
sprite_needle.fillRect((SPRITE_NEEDLE_WIDTH/2)-1, 46, 2, 10, colorFont);
for(uint32_t j=1 ; j<=measures[currentMeasureId].interDots ; j++ ){
float currAngle2 = currAngle - ((float)j*angleIncrement2);
sprite_needle.pushRotated(&sprite_gaugeface, currAngle2, TFT_BLACK);
}
}
}//end for dots
sprite_gaugeface.drawString(measures[currentMeasureId].name, 120, 185);
sprite_gaugeface.pushToSprite(&sprite_screenbuffer, 0, 0);
// Preparing the Needle sprite
sprite_needle.fillSprite(TFT_BLACK); // Fill with black
// Draw the needle in the Sprite
sprite_needle.drawWedgeLine(sprite_needle.width() / 2, 40,
sprite_needle.width() / 2, 155,
2, 5,
TFT_WHITE);
sprite_needle.fillSmoothCircle(sprite_needle.width() / 2, 120, 17, TFT_BLACK );
// And finally pushing the buffer to the TFT screen
sprite_screenbuffer.pushSprite(0,0);
}//end prepare gauges
// =======================================================================================
// updateGauge
// =======================================================================================
void updateGauge(int16_t angle) {
// Drawing the gauge face on the screen buffer sprite :
sprite_gaugeface.pushToSprite(&sprite_screenbuffer, 0, 0);
if ( measures[currentMeasureId].type==MEASURES_TYPE_ALL){
sprite_screenbuffer.setTextFont(NULL);
for (int i = 0; i < MEASURES_COUNT; i++) {
if(measures[i].type != MEASURES_TYPE_ALL){
sprite_screenbuffer.drawString(String(measures[i].name), 110, 80+(20*i));
sprite_screenbuffer.drawString(String(measures[i].value), 170, 80+(20*i));
}
}
}
else {
// Drawing the value on the buffer sprite :
sprite_screenbuffer.drawString(String(measures[currentMeasureId].value), 120, 145);
if(measures[currentMeasureId].showFuelRes){
sprite_screenbuffer.fillSmoothCircle(120, 78, 17, TFT_BLACK );
sprite_screenbuffer.fillSmoothCircle(120, 78, 15, TFT_RED );
}
// Drawing the needle on the screen buffer sprite :
sprite_needle.pushRotated(&sprite_screenbuffer, angle, TFT_BLACK);
}
// and finally pushing screen buffer sprite to TFT display!
sprite_screenbuffer.pushSprite(0, 0);
}
// =======================================================================================
// RGB 2 INT
// returns the int color code from a hexString containing something like "#a7b473"
// =======================================================================================
int32_t rgb2int(String hexstring) {
long number = strtoll( &hexstring[1], NULL, 16);
// Split them up into r, g, b values
long r = number >> 16;
long g = number >> 8 & 0xFF;
long b = number & 0xFF;
return (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3));
}
// =======================================================================================
// GET LABEL
// =======================================================================================
String getLabel(String data, int index){
char separator = ';';
int found = 0;
int strIndex[] = { 0, -1 };
int maxIndex = data.length() - 1;
for (int i = 0; i <= maxIndex && found <= index; i++) {
if (data.charAt(i) == separator || i == maxIndex) {
found++;
strIndex[0] = strIndex[1] + 1;
strIndex[1] = (i == maxIndex) ? i+1 : i;
}
}
return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
}