415 lines
14 KiB
C++
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
|
|
}
|
|
}
|
|
|
|
|