fastclockClient/src/UI.cpp

415 lines
14 KiB
C++

/*********************************************************************
UI.cpp
Fastclock Client Display
*********************************************************************/
#define USE_I2C true
#define USE_SPI false
#define SSD1306_64_48
#include <stdio.h>
#include "mgos.h"
#include "common/platform.h"
#include "common/cs_file.h"
#include "mgos_app.h"
#include "mgos_gpio.h"
#include "mgos_sys_config.h"
#include "mgos_timers.h"
#include "mgos_hal.h"
#include "mgos_dlsym.h"
#include "mjs.h"
#include <Arduino.h>
#include "Adafruit_SSD1306dj.h"
#include "mgos_rpc.h"
#include "common/cs_dbg.h"
#include "common/json_utils.h"
#include "frozen/frozen.h"
#include "mgos_net.h"
#include "Ui.h"
#include "fremo_logo.xbm"
// OLED display object
Adafruit_SSD1306 display;
// Use these variables to set the initial time
int hours = 11;
int minutes = 50;
int seconds = 30;
// How fast do you want the clock to spin? Set this to 1 for fun.
// Set this to 1000 to get _about_ 1 second timing.
// CLOCK_SPEED = # of milliseconds per model minute
const int CLOCK_SPEED = 1000;
// Global variables to help draw the clock face:
boolean showSecondsArm = true;
int MIDDLE_Y, MIDDLE_X;
int CLOCK_RADIUS;
int POS_12_X, POS_12_Y;
int POS_3_X, POS_3_Y;
int POS_6_X, POS_6_Y;
int POS_9_X, POS_9_Y;
int S_LENGTH;
int M_LENGTH;
int H_LENGTH;
int TEXT_WIDTH = 6;
int TEXT_HEIGHT = 8;
unsigned long lastDraw = 0;
void initClockVariables()
{
// Calculate constants for clock face component positions:
display.setFontType(1);
MIDDLE_Y = display.height() / 2;
MIDDLE_X = display.width() / 2;
CLOCK_RADIUS = min(MIDDLE_X, MIDDLE_Y) - 1;
POS_12_X = MIDDLE_X - TEXT_WIDTH;
POS_12_Y = MIDDLE_Y - CLOCK_RADIUS + 2;
POS_3_X = MIDDLE_X + CLOCK_RADIUS - TEXT_WIDTH - 1;
POS_3_Y = MIDDLE_Y - TEXT_HEIGHT/2;
POS_6_X = MIDDLE_X - TEXT_WIDTH/2;
POS_6_Y = MIDDLE_Y + CLOCK_RADIUS - TEXT_HEIGHT - 1;
POS_9_X = MIDDLE_X - CLOCK_RADIUS + TEXT_WIDTH - 2;
POS_9_Y = MIDDLE_Y - TEXT_HEIGHT/2;
// Calculate clock arm lengths
S_LENGTH = CLOCK_RADIUS * 0.1; // CLOCK_RADIUS - 2;
M_LENGTH = CLOCK_RADIUS * 0.8;
H_LENGTH = CLOCK_RADIUS * 0.5;
}
// Simple function to increment seconds and then increment minutes
// and hours if necessary.
void updateTime()
{
seconds++; // Increment seconds
if (seconds >= 60) // If seconds overflows (>=60)
{
seconds = 0; // Set seconds back to 0
minutes++; // Increment minutes
if (minutes >= 60) // If minutes overflows (>=60)
{
minutes = 0; // Set minutes back to 0
hours++; // Increment hours
if (hours >= 12) // If hours overflows (>=12)
{
hours = 0; // Set hours back to 0
}
}
}
}
// Helper function to map value from one range to another
long map(long x, long in_min, long in_max, long out_min, long out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
// Draw the clock's three arms: seconds, minutes, hours.
void drawArms(int h, int m, int s)
{
double midHours; // this will be used to slightly adjust the hour hand
static int hx, hy, mx, my, sx, sy;
// Adjust time to shift display 90 degrees ccw
// this will turn the clock the same direction as text:
h -= 3;
m -= 15;
s -= 15;
if (h <= 0)
h += 12;
if (m < 0)
m += 60;
if (s < 0)
s += 60;
if (showSecondsArm) {
// Calculate and draw new lines:
s = map(s, 0, 60, 0, 360); // map the 0-60, to "360 degrees"
sx = S_LENGTH * cos(PI * ((float)s) / 180); // woo trig!
sy = S_LENGTH * sin(PI * ((float)s) / 180); // woo trig!
// draw the second hand:
display.drawLine(MIDDLE_X, MIDDLE_Y, MIDDLE_X + sx, MIDDLE_Y + sy, WHITE);
}
m = map(m, 0, 60, 0, 360); // map the 0-60, to "360 degrees"
mx = M_LENGTH * cos(PI * ((float)m) / 180); // woo trig!
my = M_LENGTH * sin(PI * ((float)m) / 180); // woo trig!
// draw the minute hand
display.drawLine(MIDDLE_X, MIDDLE_Y, MIDDLE_X + mx, MIDDLE_Y + my, WHITE);
midHours = minutes/12; // midHours is used to set the hours hand to middling levels between whole hours
h *= 5; // Get hours and midhours to the same scale
h += midHours; // add hours and midhours
h = map(h, 0, 60, 0, 360); // map the 0-60, to "360 degrees"
hx = H_LENGTH * cos(PI * ((float)h) / 180); // woo trig!
hy = H_LENGTH * sin(PI * ((float)h) / 180); // woo trig!
// draw the hour hand:
display.drawLine(MIDDLE_X, MIDDLE_Y, MIDDLE_X + hx, MIDDLE_Y + hy, WHITE);
}
void drawHourTick(int hour) {
if (hour == 0 || hour == 3 || hour == 6 || hour == 9) return;
float t = map(hour, 0, 12, 0, 360); // map the 0-60, to "360 degrees"
float dx = cos(PI * t / 180.0);
float dy = sin(PI * t / 180.0);
float radius = (float) CLOCK_RADIUS;
int ex = radius * dx + 0.5;
int ax = 0.9 * ex;
int ey = radius * dy + 0.5;
int ay = 0.9 * ey;
display.drawLine(MIDDLE_X + ax, MIDDLE_Y + ay, MIDDLE_X + ex, MIDDLE_Y + ey, WHITE);
}
// Draw an analog clock face
void drawFace()
{
// Draw the clock border
display.drawCircle(MIDDLE_X, MIDDLE_Y, CLOCK_RADIUS, WHITE);
for (int i=0; i<12; ++i) {
// display hour ticks
if (i!=0 && i!=3 && i!=6 && i!=9) {
int t = map(i*5, 0, 60, 0, 360); // map the 0-60, to "360 degrees"
float dx = cos(PI * ((float) t) / 180);
float dy = sin(PI * ((float) t) / 180);
int ex = CLOCK_RADIUS * dx;
int ax = 0.9 * ex;
int ey = CLOCK_RADIUS * dy;
int ay = 0.9 * ey;
display.drawLine(MIDDLE_X + ax, MIDDLE_Y + ay, MIDDLE_X + ex, MIDDLE_Y + ey, WHITE);
}
}
// Draw the clock numbers
display.setFontType(0); // set font type 0, please see declaration in SFE_MicroOLED.cpp
display.setCursor(POS_12_X, POS_12_Y); // points cursor to x=27 y=0
display.print(12);
display.setCursor(POS_6_X, POS_6_Y);
display.print(6);
display.setCursor(POS_9_X, POS_9_Y);
display.print(9);
display.setCursor(POS_3_X, POS_3_Y);
display.print(3);
}
//------------------------------------------------------------------------------
// File generated by LCD Assistant
// http://en.radzio.dxp.pl/bitmap_converter/
//------------------------------------------------------------------------------
//This is the array that holds the Bitmap image. The easiest way to convert a bmp
//to an array is to use the LCD Assistant linked above.
const uint8_t bender [] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xBF, 0xDF, 0x5F, 0x5F, 0x5F, 0x5F,
0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F,
0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F,
0x5F, 0xDF, 0xBF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0xF9, 0xFE, 0x07, 0x01, 0x00, 0x00, 0xF8, 0xFE, 0xFF,
0xFF, 0xFF, 0x1F, 0x1F, 0x1F, 0xFF, 0xFF, 0xFE, 0xFC, 0xF8, 0xF0, 0xE0, 0x00, 0x00, 0x00, 0x00,
0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF, 0xFF, 0x1F, 0x1F, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xF8,
0x00, 0x00, 0x01, 0x07, 0xFE, 0xF9, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xF9, 0xE7, 0xDC, 0xB0, 0xA0, 0x40, 0x41, 0x47, 0x4F,
0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x4F, 0x47, 0x43, 0x40, 0x40, 0x40, 0x40,
0x43, 0x47, 0x4F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x4F, 0x47, 0x43, 0x40,
0x40, 0xA0, 0xB0, 0xDE, 0xE7, 0xF9, 0xFE, 0x1F, 0x0F, 0x07, 0x73, 0x79, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F,
0xBF, 0x5F, 0xEF, 0x0F, 0xEF, 0xEF, 0xDF, 0xDF, 0x1F, 0xDF, 0xDF, 0xDF, 0xDF, 0x1F, 0xDF, 0xDF,
0xDF, 0xDF, 0xDF, 0x1F, 0xDF, 0xDF, 0xDF, 0xEF, 0x0F, 0xEF, 0xDF, 0xBF, 0x7F, 0xFF, 0xFF, 0xFF,
0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0xBE, 0x9C, 0xC0, 0xE0, 0xF0, 0xF9, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0,
0xB7, 0x6F, 0xEE, 0x00, 0xDE, 0xDE, 0xDE, 0xDD, 0x00, 0xDD, 0xDD, 0xDD, 0xDD, 0x00, 0xDD, 0xDD,
0xDD, 0xC5, 0xC1, 0x00, 0xC9, 0xC5, 0xC1, 0x01, 0xC8, 0xC4, 0x42, 0x80, 0xC0, 0xE8, 0xE4, 0xE2,
0xE0, 0xE0, 0xEF, 0xEF, 0xE6, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFE, 0xFE, 0xFD, 0xFD, 0xFD, 0xFB, 0xF8, 0xFB, 0xFB, 0xFB, 0xFB, 0xF8, 0xFB, 0xFB,
0xFB, 0xFB, 0xFB, 0xF8, 0xFB, 0xFD, 0xFD, 0xFC, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
const uint8_t auge_16x16[] = {
0B11111110, 0B11111111,
0B11111101, 0B11110111,
0B11111111, 0B11101111,
0B11110111, 0B11111111,
0B11100111, 0B11111111,
0B11011101, 0B11111110,
0B11011110, 0B11110001,
0B11110111, 0B11011111,
0B11111110, 0B01011111,
0B11110111, 0B11101111,
0B11111111, 0B11111111,
0B11111111, 0B11111111,
0B00000000, 0B00000000,
0B11111111, 0B11111111,
0B00000000, 0B00000000,
0B11111111, 0B11111111
};
void Ui::showSplashImage(const uint8_t *image, int width, int height) {
display.clearDisplay();
displayImage((64-width)/2, (48-height)/2, image, width, height);
display.setFontType(0);
display.setCursor(0, 48-display.getFontHeight());
display.print("FREMOClock");
display.display();
}
void Ui::showSplashImage_p(const uint8_t *image, int width, int height) {
display.clearDisplay();
displayImage_p((64-width)/2, (48-height)/2, image, width, height);
display.setFontType(0);
display.setCursor(0, 48-display.getFontHeight());
display.print("FREMOClock");
display.display();
}
void Ui::displayImage(int x, int y, const uint8_t *image, int width, int height) {
#if 0
for (int row=0; row<height; row++) {
for (int col=0; col<width; ++col) {
int posx = col + x;
int posy = row + y;
if (posx < 64 && posy < 48) {
uint8_t checkByte = image[row * (width/8) + col/8];
uint8_t checkBit = col % 8;
if (checkByte & _BV(checkBit)) {
display.pixel(posx, posy, BLACK, NORM);
} else {
display.pixel(posx, posy, WHITE, NORM);
}
}
}
}
#else
display.drawBitmap(x, y, image, width, height, WHITE, BLACK);
#endif
}
void Ui::displayImage_p(int x, int y, const uint8_t *image, int width, int height) {
#if 0
for (int row=0; row<height; row++) {
for (int col=0; col<width; ++col) {
int posx = col + x;
int posy = row + y;
if (posx < 64 && posy < 48) {
uint8_t checkByte = pgm_read_byte_near(image + row * (width/8) + col/8); //image[row * (width/8) + col/8];
uint8_t checkBit = col % 8;
if (checkByte & _BV(checkBit)) {
display.pixel(posx, posy, BLACK, NORM);
} else {
display.pixel(posx, posy, WHITE, NORM);
}
}
}
}
#else
display.drawBitmap(x, y, image, width, height, BLACK, WHITE);
#endif
}
void Ui::displayImageInverted(int x, int y, const uint8_t *image, int width, int height) {
#if 0
for (int row=0; row<height; row++) {
for (int col=0; col<width; ++col) {
int posx = col + x;
int posy = row + y;
if (posx < 64 && posy < 48) {
uint8_t checkByte = image[row * (width/8) + col/8];
uint8_t checkBit = col % 8;
if (checkByte & _BV(checkBit)) {
display.pixel(posx, posy, WHITE, NORM);
} else {
display.pixel(posx, posy, BLACK, NORM);
}
}
}
}
#else
display.drawBitmap(x, y, image, width, height, BLACK, WHITE);
#endif
}
void Ui::displayImageInverted_p(int x, int y, const uint8_t *image, int width, int height) {
#if 0
for (int row=0; row<height; row++) {
for (int col=0; col<width; ++col) {
int posx = col + x;
int posy = row + y;
if (posx < 64 && posy < 48) {
uint8_t checkByte = pgm_read_byte_near(image + row * (width/8) + col/8); // image[row * (width/8) + col/8];
uint8_t checkBit = col % 8;
if (checkByte & _BV(checkBit)) {
display.pixel(posx, posy, WHITE, NORM);
} else {
display.pixel(posx, posy, BLACK, NORM);
}
}
}
}
#else
display.drawBitmap(x, y, image, width, height, BLACK, WHITE);
#endif
}
void Ui::begin()
{
logHeap();
display = new Adafruit_SSD1306(4 /* RST GPIO */, Adafruit_SSD1306::RES_64_48);
display.begin(SSD1306_SWITCHCAPVCC, 0x3c, true /* reset */);
display.display();
// set codepage correctly / apply bug fix
display.cp437(true);
display.setTextWrap(false);
display.setTextSize(2);
display.setTextColor(WHITE);
initClockVariables();
display.clearDisplay();
showSplashImage_p(auge_16x16, 16, 16);
delay(1000);
displayImageInverted_p(0, 0, FREMO_LOGO_bits, FREMO_LOGO_width, FREMO_LOGO_height);
// drawFace();
// drawArms(hours, minutes, seconds);
display.display(); // display the memory buffer drawn
logHeap();
}
void Ui::setTime(int _hour, int _minute, int _second) {
// logHeap();
// Debug::out(F("setTime(")); Debug::out(_hour); Debug::out(","); Debug::out(_minute); Debug::out(","); Debug::out(_second); Debug::outln(")");
hours = _hour;
minutes = _minute;
seconds = _second;
}
void Ui::loop()
{
if (millis() < 2000) return;
// Check if we need to update seconds, minutes, hours:
if (lastDraw + CLOCK_SPEED < millis())
{
lastDraw = millis();
// Add a second, update minutes/hours if necessary:
// updateTime();
// Draw the clock:
display.clearDisplay(); // Clear the buffer
drawFace(); // Draw the face to the buffer
drawArms(hours, minutes, seconds); // Draw arms to the buffer
display.display(); // Draw the memory buffer
}
}