Skip to content
Snippets Groups Projects
Commit e15794ea authored by HoelShare's avatar HoelShare
Browse files

GPN

parents
No related branches found
No related tags found
No related merge requests found
#include <Adafruit_GFX.h>
#include "Fonts/FreeSans24pt7b.h"
#include "Fonts/FreeSans9pt7b.h"
#include "Fonts/FreeSans7pt7b.h"
#include "Fonts/Org_01.h"
#include <FS.h>
#include <TFT_ILI9163C.h>
#include "BadgeUI.h"
//Stolen form https://github.com/Jan--Henrik/TFT_ILI9163C/blob/master/examples/SD_example/SD_example.ino
uint32_t read32(File &f) {
uint32_t result;
((uint8_t *)&result)[0] = f.read(); // LSB
((uint8_t *)&result)[1] = f.read();
((uint8_t *)&result)[2] = f.read();
((uint8_t *)&result)[3] = f.read(); // MSB
return result;
}
//Stolen form https://github.com/Jan--Henrik/TFT_ILI9163C/blob/master/examples/SD_example/SD_example.ino
uint16_t read16(File &f) {
uint16_t result;
((uint8_t *)&result)[0] = f.read(); // LSB
((uint8_t *)&result)[1] = f.read(); // MSB
return result;
}
void SimpleTextDisplay::draw(TFT_ILI9163C* tft, Theme * theme, uint16_t offsetX, uint16_t offsetY) {
tft->fillScreen(theme->backgroundColor);
tft->setTextColor(theme->textColor);
tft->setFont(&FreeSans9pt7b);
tft->setCursor(2 + offsetX, 18 + offsetY);
tft->print(this->text);
this->dirty = false;
}
void FullScreenStatus::draw(TFT_ILI9163C* tft, Theme * theme, uint16_t offsetX, uint16_t offsetY) {
tft->fillScreen(theme->backgroundColor);
tft->setTextColor(theme->textColor);
tft->setFont(&FreeSans24pt7b);
tft->setTextSize(1);
tft->setCursor(5, 75);
tft->print(this->main);
tft->setFont(&FreeSans9pt7b);
tft->setCursor(12, 105);
tft->print(this->sub);
this->dirty = false;
}
// Stolen from https://github.com/Jan--Henrik/TFT_ILI9163C/blob/master/examples/SD_example/SD_example.ino
void FullScreenBMPStatus::draw(TFT_ILI9163C* tft, Theme * theme, uint16_t offsetX, uint16_t offsetY) {
tft->fillScreen(theme->backgroundColor);
tft->setTextColor(theme->textColor);
if(bmp) {
bmpDraw(this->bmp, bmpx, bmpy, tft);
}
tft->setFont(&FreeSans9pt7b);
tft->setCursor(subx, suby);
tft->print(this->sub);
this->dirty = false;
}
void FullScreenBMPDisplay::draw(TFT_ILI9163C* tft, Theme * theme, uint16_t offsetX, uint16_t offsetY) {
tft->fillScreen(theme->backgroundColor);
if(bmp) {
bmpDraw(this->bmp, 0, 0, tft);
this->dirty = false;
}
}
void Menu::draw(TFT_ILI9163C* tft, Theme * theme, uint16_t offsetX, uint16_t offsetY) {
tft->fillScreen(theme->backgroundColor);
MenuItem * currentDraw = firstVisible;
uint16_t width = _TFTWIDTH-offsetX-1;
uint16_t height = _TFTHEIGHT-offsetY-1;
int i = 0;
while(currentDraw && i < itemsPerPage) {
tft->setCursor(10+offsetX, (i*height/itemsPerPage)+25+offsetY);
tft->drawLine(offsetX, i*height/itemsPerPage+offsetY, width, i*height/itemsPerPage+offsetY, theme->foregroundColor);
currentDraw->draw(tft, theme, 0, 0);
currentDraw = currentDraw->next;
i++;
}
this->dirty = false;
}
void MenuItem::draw(TFT_ILI9163C* tft, Theme * theme, uint16_t offsetX, uint16_t offsetY){
tft->setFont(&FreeSans7pt7b);
tft->setTextSize(1);
if(selected) {
tft->setTextColor(theme->selectedTextColor);
} else {
tft->setTextColor(theme->textColor);
}
tft->print(this->text);
}
void StatusOverlay::draw(TFT_ILI9163C* tft, Theme * theme, uint16_t offsetX, uint16_t offsetY) {
tft->fillRect(0, 0,_TFTWIDTH, 11, theme->backgroundColor);
tft->setTextColor(theme->textColor);
tft->setCursor(0, 7);
tft->setTextSize(1);
tft->setFont(&Org_01);
tft->print(wifi);
tft->setCursor(70, 7);
tft->printf("% 2d:%02d %d%%", this->hours, this->minutes,
std::max(std::min(int((bat-batCritical)/float(batFull-batCritical)*100), 100),0));
tft->drawLine(0, 11, _TFTWIDTH-1, 11, theme->foregroundColor);
dirty = false;
}
uint16_t StatusOverlay::getOffsetX(){
return 0;
}
uint16_t StatusOverlay::getOffsetY(){
return 11;
}
void NotificationScreen::draw(TFT_ILI9163C * tft, Theme * theme, uint16_t offsetX, uint16_t offsetY) {
tft->fillScreen(theme->backgroundColor);
tft->setCursor(0,15);
tft->setTextColor(theme->textColor);
tft->setFont(&FreeSans7pt7b);
if(location == "") {
tft->printf("%s\n\n%s", summary.c_str(), description.c_str());
} else {
tft->printf("%s\n@%s\n\n%s", summary.c_str(), location.c_str(), description.c_str());
}
dirty = false;
}
void BMPRender::bmpDraw(const char *filename, uint8_t x, uint16_t y, TFT_ILI9163C* tft) {
File bmpFile;
uint16_t bmpWidth, bmpHeight; // W+H in pixels
uint8_t bmpDepth; // Bit depth (currently must be 24)
uint32_t bmpImageoffset; // Start of image data in file
uint32_t rowSize; // Not always = bmpWidth; may have padding
uint8_t sdbufferLen = BUFFPIXEL * 3;
uint8_t sdbuffer[sdbufferLen]; // pixel buffer (R+G+B per pixel)
uint8_t buffidx = sdbufferLen; // Current position in sdbuffer
boolean goodBmp = false; // Set to true on valid header parse
boolean flip = true; // BMP is stored bottom-to-top
uint16_t w, h, row, col;
uint8_t r, g, b;
uint32_t pos = 0;
if((x >= tft->width()) || (y >= tft->height())) return;
// Open requested file on SD card
if ((bmpFile = SPIFFS.open(filename,"r")) == NULL) {
tft->setCursor(20,20);
tft->print("file not found!");
return;
}
// Parse BMP header
if(read16(bmpFile) == 0x4D42) { // BMP signature
read32(bmpFile);
(void)read32(bmpFile); // Read & ignore creator bytes
bmpImageoffset = read32(bmpFile); // Start of image data
// Read DIB header
read32(bmpFile);
bmpWidth = read32(bmpFile);
bmpHeight = read32(bmpFile);
if(read16(bmpFile) == 1) { // # planes -- must be '1'
bmpDepth = read16(bmpFile); // bits per pixel
if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed
goodBmp = true; // Supported BMP format -- proceed!
rowSize = (bmpWidth * 3 + 3) & ~3;// BMP rows are padded (if needed) to 4-byte boundary
if (bmpHeight < 0) {
bmpHeight = -bmpHeight;
flip = false;
}
// Crop area to be loaded
w = bmpWidth;
h = bmpHeight;
if((x+w-1) >= tft->width()) w = tft->width() - x;
if((y+h-1) >= tft->height()) h = tft->height() - y;
//tft->startPushData(x, y, x+w-1, y+h-1);
for (row=0; row<h; row++) { // For each scanline...
uint8_t row_data[w * 2];
if (flip){ // Bitmap is stored bottom-to-top order (normal BMP)
pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
}
else { // Bitmap is stored top-to-bottom
pos = bmpImageoffset + row * rowSize;
}
if (bmpFile.position() != pos) { // Need seek?
bmpFile.seek(pos, SeekSet);
buffidx = sdbufferLen; // Force buffer reload
}
for (col=0; col<w; col++) { // For each pixel...
// Time to read more pixel data?
if (buffidx >= sdbufferLen) { // Indeed
bmpFile.read(sdbuffer, sdbufferLen);
buffidx = 0; // Set index to beginning
}
// Convert pixel from BMP to TFT format, push to display
b = sdbuffer[buffidx++];
g = sdbuffer[buffidx++];
r = sdbuffer[buffidx++];
uint16_t val = tft->Color565(r,g,b);
row_data[2 * col + 1] = (uint8_t)val;
row_data[2 * col] = *(((uint8_t*)&val) + 1);
} // end pixel
tft->writeRow(y + row, x, w, row_data);
} // end scanline
//tft->endPushData();
} // end goodBmp
}
}
bmpFile.close();
if(!goodBmp) {
tft->setCursor(20,20);
tft->print("file unrecognized!");
}
}
BadgeUI.h 0 → 100644
// vim: noai:ts=2:sw=2
#pragma once
#define BUFFPIXEL 20
#include <GPNBadge.hpp>
#include "UIThemes.h"
#define BLUE 0x001F
class UIElement {
public:
virtual void draw(TFT_ILI9163C* tft, Theme * theme, uint16_t offsetX, uint16_t offsetY) = 0;
virtual bool isDirty() = 0;
virtual void dispatchInput(JoystickState state) {}
virtual ~UIElement() {}
UIElement* parent = nullptr;
virtual bool isValid() {
return true;
}
virtual bool requiresFullScreen() {
return false;
}
};
class FullScreenStatus: public UIElement {
public:
void draw(TFT_ILI9163C* tft, Theme * theme, uint16_t offsetX, uint16_t offsetY);
bool isDirty() {
return dirty;
}
void setMain(String main) {
this->main = main;
this->dirty = true;
}
void setSub(String sub) {
this->sub = sub;
this->dirty = true;
}
bool requiresFullScreen() {
return true;
}
private:
String main = "FULL";
String sub ="screen status";
bool dirty = true;
};
class Overlay: public UIElement {
public:
virtual uint16_t getOffsetX() = 0;
virtual uint16_t getOffsetY() = 0;
bool isValid() {
return true;
}
bool requiresFullScreen() {
return false;
}
virtual void draw(TFT_ILI9163C* tft, Theme * theme, uint16_t offsetX, uint16_t offsetY) = 0;
virtual bool isDirty() = 0;
};
class StatusOverlay: public Overlay {
public:
StatusOverlay(uint16_t batCritical, uint16_t batFull): batCritical(batCritical), batFull(batFull), bat(batCritical) {}
bool isDirty() {
return dirty;
}
void draw(TFT_ILI9163C* tft, Theme * Theme, uint16_t offsetX, uint16_t offsetY);
bool isValid() {
return true;
}
void updateBat(uint16_t bat) {
if(this->bat == bat) {
return;
}
this->bat = bat;
this->dirty = true;
}
void updateClock(uint8_t hours, uint8_t minutes) {
if(this->hours == hours && this->minutes == minutes) {
return;
}
this->minutes = minutes;
this->hours = hours;
this->dirty = true;
}
void updateWiFiState(String wifi) {
if(this->wifi.equals(wifi)) {
return;
}
this->wifi = wifi;
this->dirty = true;
}
uint16_t getOffsetX();
uint16_t getOffsetY();
private:
String wifi;
uint16_t bat, batCritical, batFull;
uint8_t hours,minutes;
bool dirty = true;
};
class NotificationScreen: public UIElement {
public:
NotificationScreen(String summary, String location, String description): summary(summary), location(location), description(description){}
void draw(TFT_ILI9163C* tft, Theme * theme, uint16_t offsetX, uint16_t offsetY);
bool isDirty() {
return dirty;
}
void dispatchInput(JoystickState state) {
if(state == JoystickState::BTN_ENTER) {
valid = false;
}
}
bool isValid() {
return valid;
}
bool requiresFullScreen() {
return true;
}
private:
String summary, location, description;
bool dirty = true;
bool valid = true;
};
class BMPRender {
public:
void bmpDraw(const char *filename, uint8_t x, uint16_t y, TFT_ILI9163C* tft);
};
class FullScreenBMPStatus: public UIElement, BMPRender {
public:
void draw(TFT_ILI9163C* tft, Theme * theme, uint16_t offsetX, uint16_t offsetY);
bool isDirty() {
return dirty;
}
void setBmp(char* path, uint16_t x, uint16_t y) {
this->bmp = path;
this->bmpx = x;
this->bmpy = y;
this->dirty = true;
}
void setSub(String sub) {
this->setSub(sub, 12, 105);
}
void setSub(String sub, uint16_t x, uint16_t y) {
if(sub == this->sub && subx == x && suby == y) {
return;
}
this->sub = sub;
this->subx = x;
this->suby = y;
this->dirty = true;
}
bool requiresFullScreen() {
return true;
}
private:
char* bmp = nullptr;
String sub ="screen status";
bool dirty = true;
uint16_t bmpx = 0;
uint16_t bmpy = 0;
uint16_t subx = 12;
uint16_t suby = 105;
};
class FullScreenBMPDisplay: public UIElement, BMPRender {
public:
void draw(TFT_ILI9163C* tft, Theme * theme, uint16_t offsetX, uint16_t offsetY);
bool isDirty() {
return dirty;
}
void setBmp(char* path) {
this->bmp = path;
this->dirty = true;
}
bool requiresFullScreen() {
return true;
}
void dispatchInput(JoystickState state) {
if(state == JoystickState::BTN_ENTER) {
valid = false;
}
}
bool isValid() {
return valid;
}
private:
char* bmp = nullptr;
bool dirty = true;
bool valid = true;
};
class SimpleTextDisplay: public UIElement {
public:
void draw(TFT_ILI9163C* tft, Theme * theme, uint16_t offsetX, uint16_t offsetY);
bool isDirty() {
return dirty;
}
void setText(String text) {
this->text = text;
this->dirty = true;
}
private:
String text = "FULL";
bool dirty = true;
};
class ClosableTextDisplay: public SimpleTextDisplay {
public:
void dispatchInput(JoystickState state) {
if(state == JoystickState::BTN_ENTER) {
valid = false;
onClose();
}
}
bool isValid() {
return valid;
}
void setOnClose(std::function<void()> onClose) {
this->onClose = onClose;
}
private:
bool valid = true;
std::function<void()> onClose;
};
class WindowSystem {
public:
FullScreenBMPStatus* root;
UIElement* head;
WindowSystem(TFT_ILI9163C* tft): tft(tft) {
head = root = new FullScreenBMPStatus();
}
void open(UIElement* element) {
element->parent = head;
head = element;
}
void closeCurrent() {
UIElement* old = head;
if(!old->parent) {
return;
}
head = old->parent;
forceRedraw = true;
delete old;
}
void draw() {
if(!head->isValid()) {
closeCurrent();
return;
}
if(forceRedraw || head->isDirty()) {
if(overlay && !head->requiresFullScreen()) {
head->draw(this->tft, theme, overlay->getOffsetX(), overlay->getOffsetY());
} else {
head->draw(this->tft, theme,0 ,0);
}
if(overlay && !head->requiresFullScreen()) {
overlay->draw(this->tft, theme, 0, 0);
}
this->tft->writeFramebuffer();
forceRedraw = false;
}
if(overlay && !head->requiresFullScreen() && overlay->isDirty()) {
overlay->draw(this->tft, theme, 0, 0);
this->tft->writeFramebuffer();
}
}
void dispatchInput(JoystickState state){
if(prevState == state) {
return;
}
prevState = state;
head->dispatchInput(state);
}
void setTheme(Theme * theme) {
delete this->theme;
this->theme = theme;
forceRedraw = true;
}
void setOverlay(Overlay * overlay) {
this->overlay = overlay;
forceRedraw = true;
}
Theme * getTheme() {
return theme;
}
protected:
TFT_ILI9163C* tft;
private:
Overlay * overlay = nullptr;
bool forceRedraw = false;
JoystickState prevState = JoystickState::BTN_NOTHING;
Theme * theme = new ThemeLight();
};
class MenuItem: public UIElement {
public:
friend class Menu;
MenuItem(String text, std::function<void(void)> trigger): text(text), triggerFunc(trigger) {
}
void draw(TFT_ILI9163C* tft, Theme * theme, uint16_t offsetX, uint16_t offsetY);
bool isDirty() {
return true;
}
void setSelect(bool selected) {
this->selected = selected;
}
void setText(String text) {
this->text = text;
}
void setTrigger(std::function<void()> triggr) {
this->triggerFunc = triggr;
}
private:
String text;
std::function<void(void)> triggerFunc;
bool selected = false;
protected:
MenuItem* prev = nullptr;
MenuItem* next = nullptr;
void trigger() {
triggerFunc();
}
};
class Menu: public UIElement {
public:
Menu(int itemsPerPage): itemsPerPage(itemsPerPage) {}
Menu():Menu(3) {}
~Menu(){
MenuItem * ite = tail;
while(ite) {
MenuItem * pre = ite->prev;
delete ite;
ite = pre;
}
}
void draw(TFT_ILI9163C* tft, Theme * theme, uint16_t offsetX, uint16_t offsetY);
void dispatchInput(JoystickState state) {
switch(state) {
case JoystickState::BTN_UP:
if(!focus->prev) {
return;
}
focus->setSelect(false);
focus = focus->prev;
focus->setSelect(true);
break;
case JoystickState::BTN_DOWN:
if(!focus->next) {
return;
}
focus->setSelect(false);
focus = focus->next;
focus->setSelect(true);
break;
case JoystickState::BTN_ENTER:
focus->trigger();
break;
default:
return;
}
int distanceDown = 0;
MenuItem * temp = firstVisible;
while(temp != focus) {
if(!temp->next) {
distanceDown = 0;
break;
}
temp = temp->next;
distanceDown++;
}
if(distanceDown >= itemsPerPage) {
firstVisible = firstVisible->next;
}
if(focus->next == firstVisible) {
firstVisible = firstVisible->prev;
}
dirty = true;
}
bool isDirty() {
return dirty;
}
void addMenuItem(MenuItem * item) {
item->prev = tail;
item->next = nullptr;
if(tail) {
tail->next = item;
} else {
firstVisible = item;
focus = item;
item->setSelect(true);
}
tail = item;
dirty = true;
}
void Clear() {
MenuItem* tail = this->tail;
while(tail) {
MenuItem* prev = tail->prev;
delete tail;
tail = prev;
}
dirty = true;
tail = nullptr;
}
private:
MenuItem * tail = nullptr;
bool dirty = true;
MenuItem * firstVisible = nullptr;
MenuItem * focus = nullptr;
int itemsPerPage;
};
const uint8_t FreeSans7pt7bBitmaps[] PROGMEM = {
0xFF, 0x40, 0xB6, 0x80, 0x12, 0x14, 0x7F, 0x24, 0x24, 0xFE, 0x28, 0x48,
0x48, 0x21, 0xEA, 0xE9, 0xA2, 0x87, 0x8B, 0xA6, 0xB7, 0x88, 0x70, 0x88,
0x90, 0x89, 0x08, 0xA0, 0x72, 0x00, 0x4E, 0x05, 0x10, 0x91, 0x09, 0x11,
0x0E, 0x30, 0x48, 0x48, 0x78, 0x20, 0x52, 0x9E, 0x8C, 0x8E, 0x73, 0xE0,
0x29, 0x49, 0x24, 0x91, 0x22, 0x89, 0x12, 0x49, 0x25, 0x28, 0x27, 0xC8,
0xA0, 0x21, 0x3E, 0x42, 0x10, 0xE0, 0xE0, 0x80, 0x11, 0x22, 0x24, 0x44,
0x88, 0x79, 0x28, 0x61, 0x86, 0x18, 0x61, 0x49, 0xE0, 0x2F, 0x92, 0x49,
0x24, 0x7B, 0x38, 0x41, 0x0C, 0x66, 0x10, 0x83, 0xF0, 0x7B, 0x18, 0x41,
0x38, 0x10, 0x61, 0xCD, 0xE0, 0x08, 0x63, 0x8A, 0x4A, 0x2F, 0xC2, 0x08,
0x20, 0x7E, 0x08, 0x2E, 0xCC, 0x10, 0x41, 0x89, 0xE0, 0x39, 0x28, 0x60,
0xFB, 0x38, 0x61, 0x4D, 0xE0, 0xFC, 0x30, 0x84, 0x10, 0x82, 0x08, 0x41,
0x00, 0x7A, 0x18, 0x61, 0x7B, 0x38, 0x61, 0xCD, 0xE0, 0x7B, 0x28, 0x61,
0xCD, 0xD0, 0x41, 0x89, 0xC0, 0x81, 0x83, 0x80, 0x00, 0x77, 0x20, 0x60,
0x70, 0x40, 0xFC, 0x0F, 0xC0, 0x01, 0xC0, 0xE0, 0x31, 0xCC, 0x20, 0x00,
0x7A, 0x38, 0x41, 0x18, 0xC2, 0x08, 0x00, 0x80, 0x07, 0xC0, 0xC3, 0x08,
0x04, 0x86, 0x98, 0x4C, 0xC4, 0x66, 0x22, 0x31, 0x32, 0xC6, 0xE3, 0x00,
0x0C, 0x00, 0x1F, 0x80, 0x0C, 0x0E, 0x05, 0x02, 0xC3, 0x21, 0x11, 0xFC,
0x82, 0x41, 0xE0, 0xC0, 0xFD, 0x06, 0x0C, 0x1F, 0xD0, 0xE0, 0xC1, 0x87,
0xF8, 0x3C, 0x42, 0x41, 0x80, 0x80, 0x80, 0x81, 0xC1, 0x62, 0x3C, 0xFC,
0x82, 0x83, 0x81, 0x81, 0x81, 0x81, 0x83, 0x82, 0xFC, 0xFF, 0x02, 0x04,
0x0F, 0xD0, 0x20, 0x40, 0x81, 0xFC, 0xFE, 0x08, 0x20, 0xFE, 0x08, 0x20,
0x82, 0x00, 0x1E, 0x30, 0x90, 0x30, 0x08, 0x04, 0x3E, 0x03, 0x81, 0x61,
0x9F, 0x40, 0x81, 0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81,
0xFF, 0xC0, 0x04, 0x10, 0x41, 0x04, 0x10, 0x61, 0x85, 0xE0, 0x82, 0x84,
0x88, 0x90, 0xB0, 0xD8, 0x88, 0x84, 0x86, 0x82, 0x82, 0x08, 0x20, 0x82,
0x08, 0x20, 0x83, 0xF0, 0xC1, 0xE0, 0xF0, 0x74, 0x5A, 0x2D, 0x16, 0x53,
0x29, 0x94, 0xC4, 0x40, 0xC1, 0xC1, 0xE1, 0xB1, 0x91, 0x89, 0x8D, 0x87,
0x83, 0x83, 0x3E, 0x31, 0xB0, 0x50, 0x18, 0x0C, 0x06, 0x03, 0x82, 0x63,
0x1F, 0x00, 0xFD, 0x0E, 0x0C, 0x18, 0x7F, 0xA0, 0x40, 0x81, 0x00, 0x3E,
0x31, 0xB0, 0x50, 0x18, 0x0C, 0x06, 0x03, 0x8B, 0x63, 0x1F, 0x80, 0x20,
0xFC, 0x82, 0x82, 0x82, 0x82, 0xFC, 0x82, 0x82, 0x82, 0x83, 0x7D, 0x8E,
0x0E, 0x07, 0x81, 0xC0, 0xC1, 0xC6, 0xF8, 0xFE, 0x20, 0x40, 0x81, 0x02,
0x04, 0x08, 0x10, 0x20, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
0xC3, 0x3C, 0xC1, 0xA0, 0x90, 0x4C, 0x62, 0x21, 0x90, 0x58, 0x28, 0x1C,
0x04, 0x00, 0xC2, 0x1A, 0x28, 0xD1, 0x44, 0xCA, 0x26, 0x5B, 0x14, 0x50,
0xA2, 0x87, 0x14, 0x38, 0xE0, 0x82, 0x00, 0x41, 0x31, 0x8C, 0x82, 0x80,
0xC0, 0xE0, 0x58, 0x44, 0x63, 0x20, 0xC0, 0xC1, 0xB1, 0x88, 0x86, 0xC1,
0x40, 0x40, 0x20, 0x10, 0x08, 0x04, 0x00, 0x7F, 0x03, 0x06, 0x04, 0x0C,
0x18, 0x30, 0x20, 0x40, 0xFF, 0xEA, 0xAA, 0xAA, 0xC0, 0x88, 0x44, 0x42,
0x22, 0x11, 0xD5, 0x55, 0x55, 0xC0, 0x23, 0x15, 0x28, 0x80, 0xFF, 0x44,
0x79, 0x08, 0x10, 0x27, 0xD0, 0xA3, 0x3B, 0x82, 0x0B, 0xB3, 0x86, 0x18,
0x61, 0xCA, 0xE0, 0x79, 0x18, 0x20, 0x82, 0x04, 0x5E, 0x02, 0x04, 0xEA,
0x38, 0x30, 0x60, 0xC1, 0x46, 0x74, 0x3C, 0x8A, 0x0F, 0xF8, 0x10, 0x11,
0x9E, 0x6B, 0xA4, 0x92, 0x48, 0x3A, 0x8E, 0x0C, 0x18, 0x30, 0x51, 0xBD,
0x03, 0x09, 0xF0, 0x84, 0x2D, 0x98, 0xC6, 0x31, 0x8C, 0x40, 0xBF, 0xC0,
0x45, 0x55, 0x55, 0xC0, 0x82, 0x08, 0xA4, 0xA3, 0x89, 0x26, 0x8A, 0x30,
0xFF, 0xC0, 0xB7, 0x66, 0x62, 0x31, 0x18, 0x8C, 0x46, 0x23, 0x11, 0xBB,
0x18, 0x61, 0x86, 0x18, 0x61, 0x38, 0x8A, 0x0C, 0x18, 0x30, 0x51, 0x1C,
0xBB, 0x28, 0x61, 0x86, 0x1C, 0xAE, 0x82, 0x00, 0x3A, 0x8E, 0x0C, 0x18,
0x30, 0x51, 0xBD, 0x02, 0x04, 0xBA, 0x49, 0x24, 0x74, 0x61, 0x87, 0x06,
0x2E, 0x5D, 0x24, 0x92, 0x60, 0x86, 0x18, 0x61, 0x86, 0x18, 0xDD, 0xC6,
0x89, 0x13, 0x62, 0x85, 0x0E, 0x08, 0x8C, 0x73, 0x34, 0xC9, 0x72, 0x52,
0x9C, 0xE3, 0x30, 0xCC, 0x44, 0xD0, 0xA1, 0x83, 0x05, 0x13, 0x62, 0xC6,
0x89, 0x13, 0x62, 0x85, 0x0C, 0x08, 0x10, 0x41, 0x80, 0x7C, 0x30, 0x84,
0x31, 0x84, 0x3F, 0x69, 0x24, 0xA2, 0x49, 0x26, 0xFF, 0xF8, 0xC9, 0x24,
0x8A, 0x49, 0x2C, 0x63, 0x3C };
const GFXglyph FreeSans7pt7bGlyphs[] PROGMEM = {
{ 0, 0, 0, 4, 0, 1 }, // 0x20 ' '
{ 0, 1, 10, 5, 2, -9 }, // 0x21 '!'
{ 2, 3, 3, 5, 1, -9 }, // 0x22 '"'
{ 4, 8, 9, 8, 0, -8 }, // 0x23 '#'
{ 13, 6, 12, 8, 1, -10 }, // 0x24 '$'
{ 22, 12, 10, 12, 0, -9 }, // 0x25 '%'
{ 37, 8, 10, 9, 1, -9 }, // 0x26 '&'
{ 47, 1, 3, 3, 1, -9 }, // 0x27 '''
{ 48, 3, 13, 5, 1, -9 }, // 0x28 '('
{ 53, 3, 13, 5, 0, -9 }, // 0x29 ')'
{ 58, 5, 4, 5, 0, -9 }, // 0x2A '*'
{ 61, 5, 6, 8, 2, -5 }, // 0x2B '+'
{ 65, 1, 3, 4, 1, 0 }, // 0x2C ','
{ 66, 3, 1, 5, 1, -3 }, // 0x2D '-'
{ 67, 1, 1, 4, 1, 0 }, // 0x2E '.'
{ 68, 4, 10, 4, 0, -9 }, // 0x2F '/'
{ 73, 6, 10, 8, 1, -9 }, // 0x30 '0'
{ 81, 3, 10, 8, 2, -9 }, // 0x31 '1'
{ 85, 6, 10, 8, 1, -9 }, // 0x32 '2'
{ 93, 6, 10, 8, 1, -9 }, // 0x33 '3'
{ 101, 6, 10, 8, 1, -9 }, // 0x34 '4'
{ 109, 6, 10, 8, 1, -9 }, // 0x35 '5'
{ 117, 6, 10, 8, 1, -9 }, // 0x36 '6'
{ 125, 6, 10, 8, 1, -9 }, // 0x37 '7'
{ 133, 6, 10, 8, 1, -9 }, // 0x38 '8'
{ 141, 6, 10, 8, 1, -9 }, // 0x39 '9'
{ 149, 1, 8, 4, 1, -7 }, // 0x3A ':'
{ 150, 1, 9, 4, 1, -6 }, // 0x3B ';'
{ 152, 6, 7, 8, 1, -6 }, // 0x3C '<'
{ 158, 6, 3, 8, 1, -4 }, // 0x3D '='
{ 161, 7, 7, 8, 1, -6 }, // 0x3E '>'
{ 168, 6, 10, 8, 1, -9 }, // 0x3F '?'
{ 176, 13, 12, 14, 0, -9 }, // 0x40 '@'
{ 196, 9, 10, 9, 0, -9 }, // 0x41 'A'
{ 208, 7, 10, 9, 1, -9 }, // 0x42 'B'
{ 217, 8, 10, 10, 1, -9 }, // 0x43 'C'
{ 227, 8, 10, 10, 1, -9 }, // 0x44 'D'
{ 237, 7, 10, 9, 1, -9 }, // 0x45 'E'
{ 246, 6, 10, 8, 1, -9 }, // 0x46 'F'
{ 254, 9, 10, 11, 1, -9 }, // 0x47 'G'
{ 266, 8, 10, 10, 1, -9 }, // 0x48 'H'
{ 276, 1, 10, 4, 1, -9 }, // 0x49 'I'
{ 278, 6, 10, 7, 0, -9 }, // 0x4A 'J'
{ 286, 8, 10, 9, 1, -9 }, // 0x4B 'K'
{ 296, 6, 10, 8, 1, -9 }, // 0x4C 'L'
{ 304, 9, 10, 12, 1, -9 }, // 0x4D 'M'
{ 316, 8, 10, 10, 1, -9 }, // 0x4E 'N'
{ 326, 9, 10, 11, 1, -9 }, // 0x4F 'O'
{ 338, 7, 10, 9, 1, -9 }, // 0x50 'P'
{ 347, 9, 11, 11, 1, -9 }, // 0x51 'Q'
{ 360, 8, 10, 10, 1, -9 }, // 0x52 'R'
{ 370, 7, 10, 9, 1, -9 }, // 0x53 'S'
{ 379, 7, 10, 9, 1, -9 }, // 0x54 'T'
{ 388, 8, 10, 10, 1, -9 }, // 0x55 'U'
{ 398, 9, 10, 9, 0, -9 }, // 0x56 'V'
{ 410, 13, 10, 13, 0, -9 }, // 0x57 'W'
{ 427, 9, 10, 9, 0, -9 }, // 0x58 'X'
{ 439, 9, 10, 9, 0, -9 }, // 0x59 'Y'
{ 451, 8, 10, 9, 0, -9 }, // 0x5A 'Z'
{ 461, 2, 13, 4, 1, -9 }, // 0x5B '['
{ 465, 4, 10, 4, 0, -9 }, // 0x5C '\'
{ 470, 2, 13, 4, 0, -9 }, // 0x5D ']'
{ 474, 5, 5, 7, 1, -9 }, // 0x5E '^'
{ 478, 8, 1, 8, 0, 2 }, // 0x5F '_'
{ 479, 3, 2, 4, 0, -9 }, // 0x60 '`'
{ 480, 7, 8, 8, 0, -7 }, // 0x61 'a'
{ 487, 6, 10, 8, 1, -9 }, // 0x62 'b'
{ 495, 6, 8, 7, 0, -7 }, // 0x63 'c'
{ 501, 7, 10, 8, 0, -9 }, // 0x64 'd'
{ 510, 7, 8, 7, 0, -7 }, // 0x65 'e'
{ 517, 3, 10, 4, 0, -9 }, // 0x66 'f'
{ 521, 7, 11, 8, 0, -7 }, // 0x67 'g'
{ 531, 5, 10, 8, 1, -9 }, // 0x68 'h'
{ 538, 1, 10, 3, 1, -9 }, // 0x69 'i'
{ 540, 2, 13, 3, 0, -9 }, // 0x6A 'j'
{ 544, 6, 10, 7, 1, -9 }, // 0x6B 'k'
{ 552, 1, 10, 3, 1, -9 }, // 0x6C 'l'
{ 554, 9, 8, 11, 1, -7 }, // 0x6D 'm'
{ 563, 6, 8, 8, 1, -7 }, // 0x6E 'n'
{ 569, 7, 8, 7, 0, -7 }, // 0x6F 'o'
{ 576, 6, 10, 8, 1, -7 }, // 0x70 'p'
{ 584, 7, 10, 8, 0, -7 }, // 0x71 'q'
{ 593, 3, 8, 5, 1, -7 }, // 0x72 'r'
{ 596, 5, 8, 7, 1, -7 }, // 0x73 's'
{ 601, 3, 9, 4, 0, -8 }, // 0x74 't'
{ 605, 6, 8, 8, 1, -7 }, // 0x75 'u'
{ 611, 7, 8, 7, 0, -7 }, // 0x76 'v'
{ 618, 10, 8, 10, 0, -7 }, // 0x77 'w'
{ 628, 7, 8, 7, 0, -7 }, // 0x78 'x'
{ 635, 7, 11, 7, 0, -7 }, // 0x79 'y'
{ 645, 6, 8, 7, 0, -7 }, // 0x7A 'z'
{ 651, 3, 13, 5, 1, -9 }, // 0x7B '{'
{ 656, 1, 13, 4, 1, -9 }, // 0x7C '|'
{ 658, 3, 13, 5, 1, -9 }, // 0x7D '}'
{ 663, 7, 2, 7, 0, -5 } }; // 0x7E '~'
const GFXfont FreeSans7pt7b PROGMEM = {
(uint8_t *)FreeSans7pt7bBitmaps,
(GFXglyph *)FreeSans7pt7bGlyphs,
0x20, 0x7E, 17 };
// Approx. 1337 bytes
#define DEBUG
#include <GPNBadge.hpp>
#include "BadgeUI.h"
#include "LaserMenuItem.hpp"
#include "Player.hpp"
#include "url-encode.h"
#include "PlayerListMenu.hpp"
#include "GameClient.hpp"
#include "GameServer.hpp"
#include "rboot.h"
#include "rboot-api.h"
#define USEIR
#ifdef DEBUG
#define BADGE_PULL_INTERVAL 6000
#else
#define BADGE_PULL_INTERVAL 5*60*1000
#endif
// Color definitions
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
void hostGame();
void joinGame();
Badge badge;
WindowSystem *ui = new WindowSystem(&tft);
Menu *role_menu = new Menu(2); // 2 Items per page
Player *player;
GameServer *server;
GameClient *client;
unsigned long lastNotificationPull = 0;
void setup() {
badge.init();
char *nickname = loadNick();
player = new Player(&badge, nickname);
badge.setBacklight(true);
badge.setGPIO(IR_EN, LOW);
badge.setAnalogMUX(MUX_JOY);
ui->setTheme(new ThemeDark());
ui->open(role_menu);
role_menu->addMenuItem(new MenuItem("Host", hostGame));
role_menu->addMenuItem(new MenuItem("Join", joinGame));
ui->draw();
rboot_config rboot_config = rboot_get_config();
SPIFFS.begin();
File f = SPIFFS.open("/rom" + String(rboot_config.current_rom), "w");
f.println("Lasertag\n");
connectBadge();
}
void hostGame() {
#ifdef DEBUG
Serial.printf("Hostotter\n");
#endif
server = new GameServer(ui, &badge);
#ifdef DEBUG
Serial.printf("Host otter nach init\n");
#endif
client = new GameClient(ui, &badge, player);
#ifdef DEBUG
Serial.printf("Host - Ende\n");
#endif
}
String recv_ir(){
badge.setGPIO(IR_EN, HIGH);
Serial.println("Entering receive mode");
String received = "";
bool stringCompleted = false;
bool dataCompleted = false;
bool dataVerified = false;
uint8_t checksumRec = 0;
bool done = false;
while (!dataCompleted && !done) {
int on_time = 0;
decode_results results; // Somewhere to store the results
if (irrecv.decode(&results)) {
if (results.overflow) {
Serial.println("IR code too long. Edit IRremoteInt.h and increase RAWBUF");
return "a12\n7.0\n.0.\n1b";
}
char * buf = reinterpret_cast<char*>(&results.value);
if (millis() - on_time > 500) {
pixels.setPixelColor(1, pixels.Color(0, 0, 0));
pixels.show();
}
if (stringCompleted) {
uint8_t checksum = buf[1];
dataCompleted = true;
if(checksum == checksumRec) {
dataVerified = true;
pixels.setPixelColor(1, pixels.Color(0, 100, 0));
} else {
pixels.setPixelColor(1, pixels.Color(100, 0, 0));
}
pixels.show();
on_time = millis();
}
else {
received += String(buf[0]) + buf[1] + buf[2] + buf[3];
checksumRec += buf[0] + buf[1] + buf[2] + buf[3];
Serial.printf("->:\n\t0: %c\n\t1: %c\n\t2: %c\n\t3: %c\n", buf[0], buf[1], buf[2], buf[3]);
if (buf[0] == '\n' || buf[1] == '\n' || buf[2] == '\n' || buf[3] == '\n') {
stringCompleted = true;
done = true;
}
}
irrecv.resume(); // Prepare for the next value
}
delay(0);
ui->dispatchInput(badge.getJoystickState()); //Allow canceling
}
badge.setGPIO(IR_EN, HIGH);
return received;
}
void joinGame() {
ui->closeCurrent();
ui->draw();
#ifdef DEBUG
Serial.printf("Join Otter\n");
#endif
String irinput = recv_ir();
#ifdef DEBUG
Serial.printf("-->\n\t%s\n<--", irinput.c_str());
#endif
String ip;
ip = irinput.substring(1, irinput.length() - 2);
Serial.printf("Received ip: %s", ip.c_str());
//client = new GameClient(ui, &badge, player);
//client->joinGame();
#ifdef DEBUG
Serial.printf("Join - Ende\n");
#endif
}
void loop() {
ui->dispatchInput(badge.getJoystickState());
ui->draw();
if (millis() - lastNotificationPull > BADGE_PULL_INTERVAL) {
if (connectBadge()) {
pixels.setPixelColor(0, pixels.Color(0, 255, 0));
} else {
lastNotificationPull = millis();
}
}
pixels.show();
if (server) {
server->update();
}
if (client) {
// client->update();
}
}
char *loadNick() {
if (SPIFFS.exists("/nick.conf")) {
UrlDecode nickUrlDecode("/nick.conf");
char *nick = nickUrlDecode.getKey("nick");
#ifdef DEBUG
Serial.printf("loaded nickname %s!\n", nick);
#endif
return nick;
}
return "otter";
}
bool connectBadge() {
if (WiFi.status() == WL_CONNECTED) {
return true;
}
WiFi.mode(WIFI_STA);
delay(30);
Serial.println("Loading config.");
File wifiConf = SPIFFS.open("/wifi.conf", "r");
String configString;
while (wifiConf.available()) {
configString += char(wifiConf.read());
}
wifiConf.close();
UrlDecode confParse(configString.c_str());
Serial.println(configString);
configString = String();
char *ssid = confParse.getKey("ssid");
char *pw = confParse.getKey("pw");
Serial.printf("Connecting to wifi '%s' with password '%s'...\n", ssid, pw);
unsigned long startTime = millis();
WiFi.begin(ssid, pw);
delete[] pw;
return WiFi.status() == WL_CONNECTED;
}
//
// Created by hoelshare on 22.05.17.
//
#include "GameClient.hpp"
void GameClient::createUIGameStart() {
if (this->playerList) {
delete this->playerList;
}
playerList = new PlayerListMenu(9);
playerList->setLeftAction([=]() { this->player->prevWeapon(); });
playerList->setRightAction([=]() { this->player->nextWeapon(); });
playerList->setDownAction([=]() { this->player->reload(); });
playerList->setEnterAction([=]() { this->player->shot(); });
LaserMenuItem *healthMI = new LaserMenuItem([]() {}, "/heart.bmp");
LaserMenuItem *armorMI = new LaserMenuItem([]() {}, "/armor.bmp");
LaserMenuItem *ammoMI = new LaserMenuItem([]() {}, "/ammo.bmp");
LaserMenuItem *nameMI = new LaserMenuItem([]() {}, nullptr);
nameMI->setText(player->getNickname());
player->setHealthMenuItem(healthMI);
player->setArmorMenuItem(armorMI);
player->setAmmoMenuItem(ammoMI);
playerList->addMenuItem(healthMI);
playerList->addMenuItem(armorMI);
playerList->addMenuItem(ammoMI);
playerList->addMenuItem(nameMI);
ui->open(playerList);
}
void GameClient::startGame() {
#ifdef DEBUG
Serial.printf("Start Game!");
#endif
this->createUIGameStart();
}
void GameClient::joinGame() {
badge->setVibrator(true);
delay(200);
badge->setVibrator(false);
playerList->addMenuItem(new MenuItem(player->getNickname(), []() {}));
ui->open(playerList);
}
void GameClient::update() {
String lastHitBy;
int lastDamage;
if (wasHit(&lastHitBy, &lastDamage)){
player->hit(lastDamage);
tft.setCursor(20, 20);
tft.print("Hit by " + lastHitBy);
}
}
bool GameClient::wasHit(String* nick, int* damage, bool enable) {
// TODO: Write 'real' code
*nick = "otter";
*damage = 10;
return enable;
}
\ No newline at end of file
//
// Created by hoelshare on 22.05.17.
//
#ifndef GPN_LASERTAG_GAMECLIENT_HPP
#define GPN_LASERTAG_GAMECLIENT_HPP
#include <GPNBadge.hpp>
#include "BadgeUI.h"
#include "PlayerListMenu.hpp"
#include "Player.hpp"
class GameClient {
public:
GameClient(WindowSystem *ui, Badge *badge, Player *player) : ui(ui), badge(badge), player(player) {}
virtual void update();
void createUIGameStart();
void startGame();
void joinGame();
bool wasHit(String* lastHitBy, int* lastDamage, bool enable = true);
private:
WindowSystem *ui;
Player *player;
Badge *badge;
char *host = "127.000.000.1"; // MaxArraySize
PlayerListMenu *playerList = new PlayerListMenu(9);
WiFiClient *client; // the client connected on client side
};
#endif //GPN_LASERTAG_GAMECLIENT_HPP
//
// Created by hoelshare on 22.05.17.
//
#define AUSGABE
#include "GameServer.hpp"
void GameServer::setTeamPlay(char isTeam) {
#ifdef AUSGABE
Serial.printf("Is team play: %s", isTeam ? "true" : "false");
#endif
//TODO set Teamplay
}
void GameServer::secureQuestion() {
#ifdef AUSGABE
Serial.printf("Sicherheitsabfrage!");
#endif
secureMenu = new PlayerListMenu(4);
secureMenu->addMenuItem(new MenuItem("Start?", [=]() { this->startGame(); }));
secureMenu->addMenuItem(new MenuItem("Cancel?", [=]() { ui->closeCurrent(); }));
secureMenu->setRightAction([=]() { ui->closeCurrent(); });
secureMenu->setLeftAction([=]() { this->startGame(); });
ui->open(secureMenu);
}
void GameServer::kick() {
if (playerList) {
// unsigned short index = playerList->getIndex();
// do something with the index
}
}
void GameServer::update() {
if (!this->gameStarted) {
if (server->hasClient()) {
Serial.println("Has Client");
serverClients[numConnections] = server->available();
Serial.printf("New connection %d\n", numConnections);
handleWelcome(&(serverClients[numConnections]), numConnections);
}
} else {
// todo Normal game functions
}
}
void GameServer::handleWelcome(WiFiClient *client, short iPlayer) {
String message;
message = client->readStringUntil('\n'); //faster the readBytesUntil()
Serial.println(message);
RemotePlayer player;
player.nickname = message;
player.pid = iPlayer;
player.tid = 0;
rPlayers[iPlayer] = player;
numConnections++;
sendAll(buildPlayerList());
}
void GameServer::sendAll(String value) {
for (short i = 0; i < numConnections; i++) {
serverClients[i].print(value);
serverClients[i].flush();
}
}
void GameServer::sendIP() {
String shareString = "a" + WiFi.localIP().toString() + "\n";
badge->setGPIO(IR_EN, HIGH);
Serial.println(shareString);
Serial.println("NEC");
uint32_t code = 0;
uint8_t checksum = 0;
for (int i = 0; i < shareString.length(); i++) {
checksum += shareString.charAt(i);
code = code | shareString.charAt(i) << (i % 4) * 8;
if (i % 4 == 3) {
irsend.sendNEC(code, 32);
Serial.println(code, HEX);
code = 0;
}
}
if (code != 0) {
irsend.sendNEC(code, 32);
Serial.println(code, HEX);
}
Serial.print("Checksum: ");
Serial.println(checksum); //224
code = 0;
code = checksum << 8 | 222;
irsend.sendNEC(code, 32);
badge->setGPIO(IR_EN, LOW);
}
void GameServer::startGame() {
this->gameStarted = true;
// todo something more
}
String GameServer::buildPlayerList() {
String ret = String(numConnections);
for (short i = 0; i < numConnections; i++) {
ret += "&" + rPlayers[i].serial();
}
ret += "\n";
return ret;
}
\ No newline at end of file
//
// Created by hoelshare on 22.05.17.
//
#ifndef GPN_LASERTAG_GAMESERVER_HPP
#define GPN_LASERTAG_GAMESERVER_HPP
#include <GPNBadge.hpp>
#include "BadgeUI.h"
#include "LaserMenuItem.hpp"
#include "PlayerListMenu.hpp"
#include "RemotePlayer.hpp"
#define SERVER_PORT 4803
#define MAX_SRV_CLIENTS 16
class GameServer {
public:
GameServer(WindowSystem *ui, Badge *badge) : ui(ui), badge(badge) {
server = new WiFiServer(SERVER_PORT);
server->begin();
#ifdef DEBUG
Serial.printf("Server - created\n");
#endif
serverClients = new WiFiClient[MAX_SRV_CLIENTS]; // initialize the array of clients
#ifdef DEBUG
Serial.printf("Client Array created\n");
#endif
sizeMenu = new Menu(2);
#ifdef DEBUG
Serial.printf("Menu Created\n");
#endif
sizeMenu->addMenuItem(new MenuItem("TEAM", [=]() {
this->setTeamPlay(1);
ui->closeCurrent();
// TODO open Menu anzTeam
}));
sizeMenu->addMenuItem(new MenuItem("SINGLE", [=]() {
#ifdef DEBUG
Serial.println("Single selected");
#endif
this->setTeamPlay(0);
ui->closeCurrent();
#ifdef DEBUG
Serial.println("UI Closed");
#endif
}));
#ifdef DEBUG
Serial.printf("Open menu\n");
#endif
// ui->open(sizeMenu);
// ui->dispatchInput(badge->getJoystickState());
// ui->draw();
#ifdef DEBUG
Serial.printf("Opened menu\n");
#endif
playerList = new PlayerListMenu(9);
LaserMenuItem* item = new LaserMenuItem([]() {}, nullptr);
item->setText("Playerlist");
playerList->addMenuItem(item);
playerList->setLeftAction([=]() { this->kick(); });
playerList->setRightAction([=]() { this->secureQuestion(); });
playerList->setEnterAction([=]() { this->sendIP(); });
ui->open(playerList);
ui->dispatchInput(badge->getJoystickState());
ui->draw();
#ifdef DEBUG
Serial.printf("Actions added\n");
#endif
// playerList->addMenuItem(new MenuItem(player->getNickname(), []() {}));
// ui->open(playerList);
#ifdef DEBUG
Serial.printf("Playerlist opend\n");
#endif
}
virtual void update();
protected:
void setTeamPlay(char isTeam);
void startGame();
virtual void secureQuestion();
virtual void handleWelcome(WiFiClient *client, short iPlayer);
virtual void kick();
void sendAll(String value);
private:
WindowSystem *ui;
Badge *badge;
Menu *sizeMenu;
PlayerListMenu *playerList;
PlayerListMenu *secureMenu;
WiFiServer *server; // the server object
WiFiClient *serverClients; // the clients connected to the server
short numConnections = 0;
bool gameStarted = false;
void sendIP();
RemotePlayer rPlayers[MAX_SRV_CLIENTS];
String buildPlayerList();
};
#endif //GPN_LASERTAG_GAMESERVER_HPP
This diff is collapsed.
//
// Created by hoelshare on 21.05.17.
//
#include "LaserMenuItem.hpp"
#include <FS.h>
#include <TFT_ILI9163C.h>
#include "BadgeUI.h"
uint32_t read32(File &f);
uint16_t read16(File &f);
void LaserMenuItem::bmpDraw(uint8_t x, uint16_t y, TFT_ILI9163C *tft) {
if (this->imagePath) {
File bmpFile;
uint16_t bmpWidth, bmpHeight; // W+H in pixels
uint8_t bmpDepth; // Bit depth (currently must be 24)
uint32_t bmpImageoffset; // Start of image data in file
uint32_t rowSize; // Not always = bmpWidth; may have padding
uint8_t sdbufferLen = BUFFPIXEL * 3;
uint8_t sdbuffer[sdbufferLen]; // pixel buffer (R+G+B per pixel)
uint8_t buffidx = sdbufferLen; // Current position in sdbuffer
boolean goodBmp = false; // Set to true on valid header parse
boolean flip = true; // BMP is stored bottom-to-top
uint16_t w, h, row, col;
uint8_t r, g, b;
uint32_t pos = 0;
if ((x >= tft->width()) || (y >= tft->height())) return;
// Open requested file on SD card
if ((bmpFile = SPIFFS.open(this->imagePath, "r")) == NULL) {
// TODO Download File
return;
}
// Parse BMP header
if (read16(bmpFile) == 0x4D42) { // BMP signature
read32(bmpFile);
(void) read32(bmpFile); // Read & ignore creator bytes
bmpImageoffset = read32(bmpFile); // Start of image data
// Read DIB header
read32(bmpFile);
bmpWidth = read32(bmpFile);
bmpHeight = read32(bmpFile);
if (read16(bmpFile) == 1) { // # planes -- must be '1'
bmpDepth = read16(bmpFile); // bits per pixel
if ((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed
goodBmp = true; // Supported BMP format -- proceed!
rowSize = (bmpWidth * 3 + 3) & ~3;// BMP rows are padded (if needed) to 4-byte boundary
if (bmpHeight < 0) {
bmpHeight = -bmpHeight;
flip = false;
}
// Crop area to be loaded
w = bmpWidth;
h = bmpHeight;
if ((x + w - 1) >= tft->width()) w = tft->width() - x;
if ((y + h - 1) >= tft->height()) h = tft->height() - y;
//tft->startPushData(x, y, x+w-1, y+h-1);
for (row = 0; row < h; row++) { // For each scanline...
uint8_t row_data[w * 2];
if (flip) { // Bitmap is stored bottom-to-top order (normal BMP)
pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
} else { // Bitmap is stored top-to-bottom
pos = bmpImageoffset + row * rowSize;
}
if (bmpFile.position() != pos) { // Need seek?
bmpFile.seek(pos, SeekSet);
buffidx = sdbufferLen; // Force buffer reload
}
for (col = 0; col < w; col++) { // For each pixel...
// Time to read more pixel data?
if (buffidx >= sdbufferLen) { // Indeed
bmpFile.read(sdbuffer, sdbufferLen);
buffidx = 0; // Set index to beginning
}
// Convert pixel from BMP to TFT format, push to display
b = sdbuffer[buffidx++];
g = sdbuffer[buffidx++];
r = sdbuffer[buffidx++];
uint16_t val = tft->Color565(r, g, b);
row_data[2 * col + 1] = (uint8_t) val;
row_data[2 * col] = *(((uint8_t * ) & val) + 1);
} // end pixel
tft->writeRow(y + row, x, w, row_data);
} // end scanline
//tft->endPushData();
} // end goodBmp
}
}
bmpFile.close();
if (!goodBmp) {
tft->setCursor(20, 20);
tft->print("file unrecognized!");
}
}
}
//
// Created by hoelshare on 21.05.17.
//
#ifndef GPN_LASERTAG_LASERMENUITEM_HPP
#define GPN_LASERTAG_LASERMENUITEM_HPP
#include <GPNBadge.hpp>
#include <FS.h>
#include "UIThemes.h"
#include "BadgeUI.h"
class LaserMenuItem : public MenuItem {
public:
LaserMenuItem(std::function<void(void)> trigger, const char *imagePath) :
MenuItem("100%", trigger), imagePath(imagePath) {
};
private:
const char *imagePath;
void bmpDraw(uint8_t, uint16_t, TFT_ILI9163C*);
};
#endif //GPN_LASERTAG_LASERMENUITEM_HPP
//
// Created by hoelshare on 21.05.17.
//
#include "Player.hpp"
#define min(a, b) ((a)<(b)?(a):(b))
#define max(a, b) ((a)>(b)?(a):(b))
void Player::heal(unsigned short healthPoints) {
unsigned short calcHP = this->currentHP + healthPoints;
this->currentHP = min(this->maxHP, calcHP);
updateHealthMenuItem();
}
void Player::hit(unsigned short hitPoints) {
float armorPercent = (this->currentArmor / (double)this->maxArmor) * this->maxArmorPercent;
unsigned short playerHitPoints = (1 - armorPercent) * hitPoints;
unsigned short armorHitPoints = armorPercent * hitPoints;
short calcHP = this->currentHP - playerHitPoints;
short calcArmor = this->currentArmor - armorHitPoints;
this->currentHP = max(short(0), calcHP);
this->currentArmor = max(short(0), calcArmor);
updateHealthMenuItem();
if (!this->currentHP) {
this->die();
}
}
void Player::updateHealthMenuItem() {
if (this->healthMenuItem) {
char buff[5];
snprintf(buff, sizeof(buff), "%d %", this->currentHP / this->maxHP * 100);
String percentage = buff;
delete[] buff;
this->healthMenuItem->setText(percentage);
}
}
void Player::updateArmorMenuItem() {
if (this->armorMenuItem) {
char buff[5];
snprintf(buff, sizeof(buff), "%d %", this->currentArmor / this->maxArmor * 100);
String percentage = buff;
delete[] buff;
this->healthMenuItem->setText(percentage);
}
}
void Player::updateAmmoMenuItem() {
if (this->ammoMenuItem) {
char buff[20];
snprintf(buff, sizeof(buff), "%d / %d",
this->currentShot[this->weaponIndex], this->shotMagazine[this->weaponIndex]);
String shotStr = buff;
delete[] buff;
this->healthMenuItem->setText(shotStr);
}
}
void Player::reload() {
// Magazine not empty
// and current not full
if (this->shotMagazine[this->weaponIndex] &&
this->currentShot[this->weaponIndex] < Player::magazineSize[this->weaponIndex]) {
// Wait some time
delay(Player::magazineReloadTime[this->weaponIndex]);
// refill
this->currentShot[weaponIndex] =
min(Player::magazineSize[this->weaponIndex], this->shotMagazine[this->weaponIndex]) -
this->currentShot[this->weaponIndex];
}
}
void Player::shot() {
if(this->currentShot[this->weaponIndex]) {
this->currentShot[this->weaponIndex]--;
delay(Player::shotTime[this->weaponIndex]);
} else {
reload();
}
}
void Player::die() {
for (unsigned char i = 0; i < 10; i++) {
pixels.setPixelColor(1, pixels.Color(255, 0, 0));
pixels.setPixelColor(2, pixels.Color(255, 0, 0));
pixels.setPixelColor(3, pixels.Color(255, 0, 0));
pixels.setPixelColor(0, pixels.Color(255, 0, 0));
badge->setVibrator(true);
delay(200);
badge->setVibrator(false);
pixels.setPixelColor(1, pixels.Color(0, 0, 0));
pixels.setPixelColor(2, pixels.Color(0, 0, 0));
pixels.setPixelColor(3, pixels.Color(0, 0, 0));
pixels.setPixelColor(0, pixels.Color(0, 0, 0));
pixels.show();
delay(200);
}
rebirth();
}
void Player::rebirth() {
this->currentHP = this->maxHP;
this->currentArmor = 0;
for(short i = 0; i < 3; i++) {
this->currentShot[i] = this->magazineSize[i];
// TODO refill magazine
}
}
\ No newline at end of file
//
// Created by hoelshare on 21.05.17.
//
#ifndef GPN_LASERTAG_PLAYER_HPP
#define GPN_LASERTAG_PLAYER_HPP
#include "LaserMenuItem.hpp"
class Player {
public:
void setNickname(char *nickname) { this->nickname = nickname; }
const char *getNickname() { return this->nickname; }
Player(Badge *badge, char *nickname = "otter") : badge(badge), nickname(nickname) {
rebirth();
};
void setMaxHealthPoints(unsigned short maxHP) { this->maxHP = maxHP; }
void setMaxArmor(unsigned short maxArmor) { this->maxArmor = maxArmor; }
void heal(unsigned short healthPoints);
void hit(unsigned short hitPoints);
void nextWeapon() { weaponIndex = (weaponIndex + 1) % 3; }
void prevWeapon() {
nextWeapon();
nextWeapon();
}
void setPlayerID(unsigned short pID) { this->pID = pID; }
void setTeamID(unsigned short tID) { this->tID = tID; }
void setHealthMenuItem(LaserMenuItem *healthMenuItem) {
if (healthMenuItem) { delete healthMenuItem; }
this->healthMenuItem = healthMenuItem;
}
void setArmorMenuItem(LaserMenuItem *armorMenuItem) {
if (armorMenuItem) { delete armorMenuItem; }
this->armorMenuItem = armorMenuItem;
}
void setAmmoMenuItem(LaserMenuItem *ammoMenuItem) {
if (ammoMenuItem) { delete ammoMenuItem; }
this->ammoMenuItem = ammoMenuItem;
}
void reload();
void shot();
void rebirth();
~Player() {
delete[] teamColor, shotMagazine, currentShot, nickname;
delete healthMenuItem, armorMenuItem, ammoMenuItem;
}
private:
unsigned short pID;
unsigned short tID;
unsigned char teamColor[3]; // 3 Colors RGB
char *nickname;
unsigned short maxHP;
unsigned short currentHP;
unsigned short shotMagazine[3]; // 3 Weapon categories [pistol, machine gun, explosive]
unsigned short currentShot[3];
unsigned char weaponIndex;
unsigned short currentArmor;
unsigned short maxArmor;
float maxArmorPercent; // max protection between 0 and 1
void die();
// TODO set constants
const char magazineSize[3] = {10, 25, 1};
const short shotTime[3] = {100, 20, 300};
const short magazineReloadTime[3] = {800, 400, 2000};
const short damage[3] = {25, 10, 100};
Badge *badge;
protected:
void updateHealthMenuItem();
void updateArmorMenuItem();
void updateAmmoMenuItem();
LaserMenuItem *healthMenuItem;
LaserMenuItem *armorMenuItem;
LaserMenuItem *ammoMenuItem;
};
#endif //GPN_LASERTAG_PLAYER_HPP
//
// Created by hoelshare on 21.05.17.
//
#include "PlayerListMenu.hpp"
//
// Created by hoelshare on 21.05.17.
//
#ifndef GPN_LASERTAG_PLAYERMENU_HPP
#define GPN_LASERTAG_PLAYERMENU_HPP
#define min(a, b) ((a)<(b)?(a):(b))
#define max(a, b) ((a)>(b)?(a):(b))
#include "BadgeUI.h"
class PlayerListMenu : public Menu {
public:
PlayerListMenu(int itemsPerPage = 3) : Menu(itemsPerPage) {
#ifdef DEBUG
Serial.println("PlayerListMenu - init");
#endif
};
void setLeftAction(std::function<void(void)> leftAction) {
#ifdef DEBUG
Serial.printf("Left Function set");
#endif
this->leftAction = leftAction;
#ifdef DEBUG
Serial.printf("Left Function setted");
#endif
}
void setRightAction(std::function<void(void)> rightAction) {
#ifdef DEBUG
Serial.printf("Right Function set");
#endif
this->rightAction = rightAction;
}
void setUpAction(std::function<void(void)> upAction) { this->upAction = upAction; }
void setDownAction(std::function<void(void)> downAction) { this->downAction = downAction; }
void setEnterAction(std::function<void(void)> enterAction) { this->enterAction = enterAction; }
unsigned short getIndex() { return index; }
void dispatchInput(JoystickState state) {
#ifdef DEBUG
Serial.println("PlayerListMenu - dispatch");
#endif
switch (state) {
case JoystickState::BTN_UP:
if (upAction) {
upAction();
} else {
index = min(index + 1, numItems);
Menu::dispatchInput(state);
}
break;
case JoystickState::BTN_DOWN:
if (downAction) {
downAction();
} else {
index = max(index - 1, 0);
Menu::dispatchInput(state);
}
case JoystickState::BTN_LEFT:
if (leftAction) {
leftAction();
} else {
Menu::dispatchInput(state);
}
break;
case JoystickState::BTN_RIGHT:
if (rightAction) {
rightAction();
} else {
Menu::dispatchInput(state);
}
break;
case JoystickState::BTN_ENTER:
if (enterAction) {
enterAction();
} else {
Menu::dispatchInput(state);
}
default:
Menu::dispatchInput(state);
break;
}
#ifdef DEBUG
Serial.println("PlayerListMenu - dispatch - Ende");
#endif
};
void addMenuItem(MenuItem *item) {
numItems++;
Menu::addMenuItem(item);
}
private:
std::function<void(void)> leftAction;
std::function<void(void)> rightAction;
std::function<void(void)> downAction;
std::function<void(void)> upAction;
std::function<void(void)> enterAction;
unsigned short index = 0;
short numItems = 0;
};
#endif //GPN_LASERTAG_PLAYERMENU_HPP
# GPN Lasertag
- Port: 4803
## Ablauf
- Rolle auswählen (Server/Client)
### Client
- Server übermittelt IP an Client via IR
- Client baut TCP connection auf
- Client übermittelt Namen
- Server übermittelt U(ser)ID
### Server
- Sobald änderung in Spielerlist:
- Allen Spielerliste
- Soblad spiel Startet:
- Sendet server
1. Start
2. Player Liste
3. T(eam)ID
4. Color
5. Ammo pro Kategorie
6. Damage pro Kategorie
7. Reloadtime pro Kategorie
8. Dauer pro Schuss pro Kategorie
9. Resistance
10. Health
## Pakete
### Spielerliste
- char(0)
- char(Anzahl Spieler)
- char(Länge des Namens)
- Name
### erweiterte Spielerliste
- char(1)
- char(Anzahl Spieler)
- char(Länge des Namens)
- Name
- char(UID)
- char(TID)
### Kick
- char(2)
### Name von Client
- char(3)
- char(Länge des Namens)
- Name
### UID von Server
- char(4)
- char(UID)
//
// Created by hoelshare on 27.05.17.
//
#ifndef GPN_LASERTAG_REMOTEPLAYER_HPP
#define GPN_LASERTAG_REMOTEPLAYER_HPP
#include "url-encode.h"
struct RemotePlayer {
String nickname;
unsigned short pid;
unsigned char tid;
String serial() {
String ret = "";
ret += "len=" + String(String(nickname).length()) + ";";
ret += "&nick=" + String(nickname) + ";";
ret += "&pid=" + String(pid) + ";";
ret += "&tid=" + String(tid) + ";";
return ret;
}
static RemotePlayer *read(String val) {
RemotePlayer *player = new RemotePlayer();
UrlDecode decode(val.c_str());
player->pid = int(decode.getKey("pid"));
player->tid = *decode.getKey("tid");
player->nickname = decode.getKey("nick");
return player;
}
};
#endif //GPN_LASERTAG_REMOTEPLAYER_HPP
#pragma once
class Theme {
public:
Theme(String name, uint16_t textColor, uint16_t backgroundColor, uint16_t selectedTextColor, uint16_t foregroundColor): name(name), textColor(textColor), backgroundColor(backgroundColor), selectedTextColor(selectedTextColor), foregroundColor(foregroundColor) {}
uint16_t textColor, backgroundColor, selectedTextColor, foregroundColor;
String name;
};
class ThemeLight: public Theme {
public:
ThemeLight():Theme("Light", 0x0000, 0xffff, 0x001f, 0x0000){}
};
class ThemeDark: public Theme {
public:
ThemeDark():Theme("Dark", 0xC800, 0x3186, 0xffff, 0xFFDF){}
};
// vim: noai:ts=2:sw=2
#include "url-encode.h"
#include <string.h>
void writeEscaped(const char * c, Stream & stream) {
while(*c) {
if(*c == '=') {
stream.write("%3d");
} else if(*c == '&') {
stream.write("%26");
} else if(*c == ' '){
stream.write("%20");
} else {
stream.write(*c);
}
c++;
}
}
void urlEncodeWriteKeyValue(const char * key, const char * value, Stream & stream){
writeEscaped(key, stream);
stream.write('=');
writeEscaped(value, stream);
stream.write('&');
}
UrlDecode::UrlDecode(const char * url_encoded) {
size_t len = strlen(url_encoded) + 1;
this->url_encoded = new char[len];
memcpy(this->url_encoded, url_encoded, len);
}
UrlDecode::~UrlDecode() {
delete[] url_encoded;
}
inline bool parse_hex_char(unsigned char * result, char c) {
if ('0' <= c && c <= '9') {
*result = c - '0';
return true;
}
if ('a' <= c && c <= 'f') {
*result = 10 + c - 'a';
return true;
}
if ('A' <= c && c <= 'F') {
*result = 10 + c - 'A';
return true;
}
}
bool hex_to_ascii(char * result, char charH, char charL) {
unsigned char digitL;
unsigned char digitH;
if (!parse_hex_char(&digitL, charL)) {
return false;
}
if (!parse_hex_char(&digitH, charH)) {
return false;
}
*result = digitH << 4 | digitL;
return true;
}
constexpr bool is_key_terminate_char(char c) {
return c == '\0' || c == '=' || c == '&';
}
bool compare_key_encoded_unencoded(const char * encoded, const char * unencoded, const char * * char_after_key) {
while (true) {
char encoded_char = encoded[0];
char unencoded_char = unencoded[0];
if (is_key_terminate_char(encoded_char) && unencoded_char == '\0') {
*char_after_key = encoded;
return true;
}
if (is_key_terminate_char(encoded_char) || unencoded_char == '\0') {
return false;
}
if (encoded_char == '%') {
encoded++;
if (is_key_terminate_char(encoded[0])) {
return false;
}
char digit1 = encoded[0];
encoded++;
if (is_key_terminate_char(encoded[0])) {
return false;
}
char digit2 = encoded[0];
if (!hex_to_ascii(&encoded_char, digit1, digit2)) {
return false;
}
}
if (encoded_char != unencoded_char) {
return false;
}
encoded++;
unencoded++;
}
}
bool decode_percent(const char * encoded, size_t encoded_len, char * decoded, size_t * decoded_len) {
size_t i = 0;
*decoded_len = 0;
while (i < encoded_len) {
if (encoded[0] == '%') {
encoded++;
i++;
if (i >= encoded_len) {
return false;
}
char digit1 = encoded[0];
encoded++;
i++;
if (i >= encoded_len) {
return false;
}
char digit2 = encoded[0];
if (!hex_to_ascii(decoded, digit1, digit2)) {
return false;
}
} else {
decoded[0] = encoded[0];
}
decoded++;
(*decoded_len)++;
encoded++;
i++;
}
return true;
}
char * UrlDecode::getKey(const char * key) {
const char * encoded = url_encoded;
while (true) {
const char * char_after_key;
if (compare_key_encoded_unencoded(encoded, key, &char_after_key)) {
// Found key
const char * end_of_val = char_after_key;
while(end_of_val[0] != '\0' && end_of_val[0] != '&') {
end_of_val++;
}
if (*char_after_key == '=') {
char_after_key++;
}
size_t len = end_of_val - char_after_key;
char * result = new char[len + 1];
size_t res_len;
decode_percent(char_after_key, len, result, &res_len);
result[res_len] = '\0';
return result;
} else {
// Skip &
if (encoded[0] != '\0') {
encoded++;
}
// Go to next key
while (encoded[0] != '\0' && encoded[0] != '&') {
encoded++;
}
if (encoded[0] == '\0') {
// Hit end of string
return nullptr;
}
encoded++;
}
}
}
// vim: noai:ts=2:sw=2
#ifndef URLENCODE_H
#define URLENCODE_H
#include <Stream.h>
class UrlDecode {
public:
UrlDecode(const char * url_encoded);
~UrlDecode();
/// returns the value to a given key
/// You have to delete[] the resulting char * yourself
char * getKey(const char * key);
private:
char * url_encoded;
};
void urlEncodeWriteKeyValue(const char * key, const char * value, Stream & stream);
#endif //URLENCODE_H
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment