Initial commit

This commit is contained in:
2018-01-24 17:51:07 +01:00
commit 1d74a399b5
341 changed files with 28906 additions and 0 deletions

34
src/Battery.cpp Normal file
View File

@@ -0,0 +1,34 @@
//
// FILE: Battery.h
// VERSION: 1.0
// PURPOSE: Reads battery voltage and returns as a percentage value
//
//
#include "Battery.h"
#include "mgos.h"
#include "mgos_adc.h"
#ifdef ESP8266
extern "C" {
#include "user_interface.h"
}
#endif
#define RefVoltage_mV 3200
#define MaxValueToBeRead 1023
Battery::Battery() {
mgos_adc_enable(0);
}
int Battery::getPercentage() {
int value = mgos_adc_read(0);
return (value * 100) / MaxValueToBeRead;
}
int Battery::getVoltage_mV() {
int value = mgos_adc_read(0);
return RefVoltage_mV * value / MaxValueToBeRead;
}

20
src/Battery.h Normal file
View File

@@ -0,0 +1,20 @@
//
// FILE: DjBattery.h
// VERSION: 1.0
// PURPOSE: Reads battery voltage
//
//
#ifndef _batteryLoaded
#define _batteryLoaded true
#include <Arduino.h>
class Battery {
public:
Battery();
int getPercentage();
int getVoltage_mV();
};
#endif

370
src/ClockClient.cpp Normal file
View File

@@ -0,0 +1,370 @@
//
// FILE: ClockClient.cpp
// PURPOSE: UDP broadcast listener for fastclock (FREMO clock)
//
//
#include <stdio.h>
#include <stdlib.h>
#include <lwip/igmp.h>
#include "common/platform.h"
#include "mgos.h"
#include "mgos_app.h"
#include "mongoose/mongoose.h"
#include "mgos_wifi.h"
#include "mgos_net.h"
#include "mgos_mongoose.h"
#include "mgos_sys_config.h"
#include "ClockClient.h"
#define CLOCK_PACKET_SIZE 1024
static char packetBuffer[CLOCK_PACKET_SIZE+1]; //buffer to hold incoming and outgoing packets
const char * ClockClient::getLastMessage() { return (const char *) packetBuffer; }
int ClockClient::protocolVersion{0};
char * ClockClient::listenToName{nullptr};
char * ClockClient::name{nullptr};
char * ClockClient::text{nullptr};
char * ClockClient::clocktype{nullptr};
bool ClockClient::active{false};
bool ClockClient::isFastclock{false};
double ClockClient::speed{1.0};
char * ClockClient::clock{nullptr};
unsigned int ClockClient::weekday{0};
unsigned int ClockClient::clockHours{0};
unsigned int ClockClient::clockMinutes{0};
unsigned int ClockClient::clockSeconds{0};
int ClockClient::numClockChangeCallbacks{0};
char * ClockClient::fastclockIP{nullptr};
unsigned int ClockClient::fastclockPort{0};
unsigned int ClockClient::listenPort{0};
ClockChangeCallback ClockClient::clockChangeCallback[];
static void strFree(char **target) {
if (*target != nullptr) { free(*target); *target = nullptr; }
}
static void strReplace(char **target, const char *newValue) {
strFree(target);
*target = strdup(newValue);
}
void ClockClient::addClockChangeCallback(ClockChangeCallback _clockChangeCallback) {
if (numClockChangeCallbacks >= MAX_CLOCK_CHANGE_CALLBACKS) {
LOG(LL_ERROR, ("ERROR: Too many clock change callbacks registered!"));
return;
}
clockChangeCallback[numClockChangeCallbacks++] = _clockChangeCallback;
}
ClockClient::ClockClient() {
fastclockScanner = FastclockScanner();
}
static void multicastMessageHandler(struct mg_connection *nc, int ev, void *p) {
struct mbuf *io = &nc->recv_mbuf;
(void) p;
switch (ev) {
case MG_EV_RECV:
LOG(LL_INFO, ("Received (%d bytes): '%.*s'", (int) io->len, (int) io->len, io->buf));
ClockClient::interpretClockMessage(io->len, io->buf);
mbuf_remove(io, io->len);
nc->flags |= MG_F_SEND_AND_CLOSE;
break;
}
}
static void on_wifi_change(enum mgos_wifi_status event, void *ud) {
(void) ud;
switch (event) {
case MGOS_WIFI_IP_ACQUIRED: {
ip_addr_t host_addr;
ip_addr_t group_addr;
struct mgos_net_ip_info ip_info;
char sta_ip[16];
if (mgos_net_get_ip_info(MGOS_NET_IF_TYPE_WIFI, MGOS_NET_IF_WIFI_STA, &ip_info)) {
mgos_net_ip_to_str(&ip_info.ip, sta_ip);
}
host_addr.addr = inet_addr(sta_ip);
group_addr.addr = inet_addr(ClockClient::getMulticastIP());
LOG(LL_INFO, ("Joining multicast group %s", ClockClient::getMulticastIP()));
if (igmp_joingroup(&host_addr, &group_addr) != ERR_OK) {
LOG(LL_INFO, ("udp_join_multigroup failed!"));
};
}
default:
;
}
}
static int init_multicastListener(struct mg_mgr *mgr, char *multicast, int port) {
struct mg_bind_opts bopts;
char listener_spec[128];
(void) multicast;
snprintf(listener_spec, sizeof(listener_spec), "udp://:%d", port);
LOG(LL_INFO, ("Listening on %s", listener_spec));
memset(&bopts, 0, sizeof(bopts));
struct mg_connection *lc = mg_bind_opt(mgr, listener_spec, multicastMessageHandler, bopts);
if (lc == NULL) {
LOG(LL_ERROR, ("Failed to create listener"));
return 0;
}
return 1;
}
void ClockClient::begin() {
strReplace(&name, mgos_sys_config_get_clock_listenToName());
// WiFi.mode(WIFI_STA);
strReplace(&multicast, mgos_sys_config_get_clock_multicast());
listenPort = mgos_sys_config_get_clock_port();
logHeap();
mgos_wifi_add_on_change_cb(on_wifi_change, NULL);
if (!init_multicastListener(mgos_get_mgr(), multicast, listenPort)) {
LOG(LL_ERROR, ("Starting listener for Fastclock Multicast failed"));
}
}
void ClockClient::setDefaults() {
// do not touch listenToName, multicast, listenPort
// strFree(&listenToName);
// listenPort = 0;
// strFree(&multicast);
protocolVersion = 0;
active = false;
isFastclock = false;
speed = 0;
strReplace(&name, "DefaultClock");
strReplace(&text, "");
weekday = 0;
clockHours = 0;
clockMinutes = 0;
clockSeconds = 0;
strFree(&fastclockIP);
fastclockPort = 2000;
}
static const char *weekdayName[] = {
"---",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
};
const char* ClockClient::getClockWeekdayName() {
if (weekday > sizeof(weekdayName)) {
LOG(LL_ERROR, ("Invalid weekday %d, have no name for this", weekday));
return weekdayName[0];
}
return weekdayName[weekday];
}
static int movePointerForwardUntilEndOfLine(char **source) {
int moved = 0;
while (**source != '\r' && **source != '\n' && **source != '\0') {
(*source)++;
++moved;
}
return moved;
}
#define MAX_LINE_LENGTH 512
static int copyUntilEndOfLine(const char* parameter, const char* source, char** target) {
char buffer[MAX_LINE_LENGTH+1];
char* buffer_ptr;
int copied=0;
buffer_ptr = &buffer[0];
while (*source != '\r' && *source != '\n' && *source != '\0' && copied < MAX_LINE_LENGTH) {
*buffer_ptr++ = *source++;
copied++;
}
*buffer_ptr = '\0';
if (copied >= MAX_LINE_LENGTH) {
if (*source != '\r' && *source != '\n' && *source != '\0') {
LOG(LL_ERROR, ("String to copy is too long for parameter %s (rest: %s) -- ignored!", parameter, source));
while (*source != '\r' && *source != '\n' && *source != '\0')
++source; // skip
}
}
strReplace(target, buffer_ptr);
return copied;
}
static int copyBoolean(const char* parameter, const char* source, bool* target) {
if (strncmp(source, "yes", 3) == 0) {
*target = true;
return 3;
} else if (strncmp(source, "no", 2) == 0) {
*target = false;
return 2;
}
LOG(LL_ERROR, ("Invalid boolean value found for parameter %s (%s) -- ignored (using default=false)!", parameter, source));
*target = false;
return 0;
}
static int copyInteger(const char *source, unsigned int *result) {
char *movedSource = (char *) source;
*result = (unsigned int) strtoul(movedSource, &movedSource, 10);
movePointerForwardUntilEndOfLine(&movedSource);
return movedSource - source;
}
static int copyDouble(const char *source, double *result) {
char *movedSource = (char *) source;
*result = (double) strtod(movedSource, &movedSource);
movePointerForwardUntilEndOfLine(&movedSource);
return movedSource - source;
}
static int copyClock(const char *source, unsigned int *hours, unsigned int *minutes, unsigned int *seconds) {
char *movedSource = (char *) source;
*hours = 0;
*minutes = 0;
*seconds = 0;
*hours = strtoul(movedSource, &movedSource, 10);
if (*movedSource != ':') {
LOG(LL_ERROR, ("Invalid clock value found (%s) -- missing colon after hours!", source));
} else {
++movedSource;
*minutes = strtoul(movedSource, &movedSource, 10);
if (*movedSource != ':') {
LOG(LL_ERROR, ("Invalid clock value found (%s) -- missing colon after minutes!", source));
} else {
++movedSource;
*seconds = strtoul(movedSource, &movedSource, 10);
}
}
movePointerForwardUntilEndOfLine(&movedSource);
return movedSource - source;
}
void ClockClient::interpretClockMessage(int len, char* msg) {
int copied = 0;
setDefaults();
// ignore line 1, as this is for 1st version of clock clients only
len -= movePointerForwardUntilEndOfLine(&msg);
while (len > 0 && *msg != '\0') {
if (msg[0] == '\r' || msg[0] == '\n') {
msg++;
len--;
} else if (strncmp(msg, "version=", 8) == 0) {
if (msg[8] == '1') {
protocolVersion = 1;
} else if (msg[8] == '2') {
protocolVersion = 2;
} else {
LOG(LL_ERROR, ("Invalid version in fastclock message (%c) -- ignored!", msg[8]));
}
msg += 9;
len -= 9;
} else if (strncmp(msg, "name=", 5) == 0) {
msg += 5;
len -= 5;
copied = copyUntilEndOfLine("name", msg, &name);
fastclockScanner.addClock(name);
msg += copied;
len -= copied;
} else if (strncmp(msg, "text=", 5) == 0) {
msg += 5;
len -= 5;
copied = copyUntilEndOfLine("text", msg, &text);
msg += copied;
len -= copied;
} else if (strncmp(msg, "clock=", 6) == 0) {
msg += 6;
len -= 6;
copied = copyClock(msg, &clockHours, &clockMinutes, &clockSeconds);
msg += copied;
len -= copied;
} else if (strncmp(msg, "active=", 7) == 0) {
msg += 7;
len -= 7;
copied = copyBoolean("active", msg, &active);
msg += copied;
len -= copied;
} else if (strncmp(msg, "speed=", 6) == 0) {
msg += 6;
len -= 6;
copied = copyDouble(msg, &speed);
msg += copied;
len -= copied;
} else if (strncmp(msg, "weekday=", 8) == 0) {
msg += 8;
len -= 8;
copied = copyInteger(msg, &weekday);
msg += copied;
len -= copied;
} else if (strncmp(msg, "ip-port=", 8) == 0) {
msg += 8;
len -= 8;
copied = copyInteger(msg, &fastclockPort);
msg += copied;
len -= copied;
} else if (strncmp(msg, "ip-address=", 11) == 0) {
msg += 11;
len -= 11;
copied = copyUntilEndOfLine("ip-address", msg, &fastclockIP);
msg += copied;
len -= copied;
} else if (strncmp(msg, "clocktype=", 10) == 0) {
char * buffer = nullptr;
msg += 10;
len -= 10;
copied = copyUntilEndOfLine("clocktype", msg, &buffer);
msg += copied;
len -= copied;
if (strcmp(buffer, "fastclock") == 0) {
isFastclock = true;
} else if (strcmp(buffer, "realclock") == 0) {
isFastclock = false;
} else {
LOG(LL_ERROR, ("Invalid value for parameter clocktype in fastclock message (%s) -- using default (fastclock)!", buffer));
}
strFree(&buffer);
} else {
LOG(LL_ERROR, ("Invalid parameter in fastclock message (%s) -- ignored!", msg));
len -= movePointerForwardUntilEndOfLine(&msg);
}
}
if (strcmp(name, listenToName) == 0) {
// yes, we follow this clock, so do not ignore
// propagate new message
for (int i=0; i<numClockChangeCallbacks; ++i) {
clockChangeCallback[i](clockHours, clockMinutes, clockSeconds);
}
}
}
void ClockClient::loop() {
}

77
src/ClockClient.h Normal file
View File

@@ -0,0 +1,77 @@
//
// FILE: ClockClient.h
// VERSION: 0.1
// PURPOSE: FREMO Clock Client
//
//
#ifndef _clockClientLoaded
#define _clockClientLoaded
#include <appDebug.h>
#include <FastclockScanner.h>
#define MAX_CLOCK_CHANGE_CALLBACKS 5
typedef void (*ClockChangeCallback)(int h, int m, int s);
class ClockClient
{
public:
ClockClient();
static void begin();
static void loop();
static void setListenToClock(const char *_name) { listenToName = strdup(_name); }
static const char * getLastMessage();
static int getProtocolVersion() { return protocolVersion; }
static const char * getText() { return text; }
static const char * getClock() { return clock; }
static const char * getName() { return name; }
static bool isActive() { return active; }
static float getSpeed() { return speed; }
static unsigned int getClockHours() { return clockHours; }
static unsigned int getClockMinutes() { return clockMinutes; }
static unsigned int getClockSeconds() { return clockSeconds; }
static unsigned int getClockWeekday() { return weekday; }
static const char * getClockWeekdayName();
static void addClockChangeCallback(ClockChangeCallback callback);
static int getNumberOfKnownClocks();
static const char ** getKnownClocks();
static unsigned int getListenPort() { return listenPort; }
static char * getMulticastIP() { return multicast; }
static char * getFastclockServerIP() { return fastclockIP; }
static unsigned int getFastclockServerPort() { return fastclockPort; }
static char * getClockString() {
static char output[9];
snprintf(output, sizeof(output), "%02d:%02d:%02d", clockHours, clockMinutes, clockSeconds);
return output;
}
static void setDefaults();
static void interpretClockMessage(int len, char* msg);
private:
static FastclockScanner fastclockScanner;
static int numClockChangeCallbacks;
static ClockChangeCallback clockChangeCallback[MAX_CLOCK_CHANGE_CALLBACKS];
static int protocolVersion;
static char * listenToName;
static char * name;
static char * text;
static char * clocktype;
static bool active;
static bool isFastclock; // false --> real clock
static double speed;
static char * clock;
static unsigned int clockHours;
static unsigned int clockMinutes;
static unsigned int clockSeconds;
static unsigned int weekday;
static char * fastclockIP;
static unsigned int fastclockPort;
static unsigned int listenPort;
static char * multicast;
};
#endif

30
src/FastclockScanner.cpp Normal file
View File

@@ -0,0 +1,30 @@
//
// FILE: FastclockScanner.h
// VERSION: 1.0
// PURPOSE: Scans the broadcasts for clocks and returns the clocknames found
//
//
#include <string.h>
#include "mgos.h"
#include "FastclockScanner.h"
#include "appDebug.h"
const char *FastclockScanner::knownClocks[MAX_CLOCKS];
int FastclockScanner::numberOfKnownClocks = 0;
void FastclockScanner::addClock(const char *clockName) {
for (int i=0; i<numberOfKnownClocks; ++i) {
if (strcmp(clockName, knownClocks[i]) == 0) return;
}
if (numberOfKnownClocks < MAX_CLOCKS) {
knownClocks[numberOfKnownClocks] = strdup(clockName);
++numberOfKnownClocks;
LOG(LL_DEBUG, ("Added new clock with name=%s", clockName));
}
logHeap();
}

22
src/FastclockScanner.h Normal file
View File

@@ -0,0 +1,22 @@
/* FastclockScanner -- collect fastclock server names
*/
#ifndef _FastclockScannerIncluded
#define _FastclockScannerIncluded
#include "appDebug.h"
#define MAX_CLOCKS 10
class FastclockScanner {
public:
FastclockScanner() {};
void addClock(const char * clockName);
const char **getKnownClocks() { return knownClocks; }
int getNumberOfKnownClocks() { return numberOfKnownClocks; }
private:
static const char *knownClocks[];
static int numberOfKnownClocks;
};
#endif

414
src/UI.cpp Normal file
View File

@@ -0,0 +1,414 @@
/*********************************************************************
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
}
}

30
src/UI.h Normal file
View File

@@ -0,0 +1,30 @@
//
// FILE: Ui.h
// VERSION: 0.1
// PURPOSE: User interface / OLED/LCD driver
//
//
#ifndef uiControllerLoaded
#define uiControllerLoaded true
#include "appDebug.h"
#include "ClockClient.h"
class Ui {
public:
Ui() {};
static void begin();
static void loop();
static void showSplashImage(const uint8_t *image, int width, int height);
static void showSplashImage_p(const uint8_t *image, int width, int height);
static void displayImage(int x, int y, const uint8_t *image, int width, int height);
static void displayImage_p(int x, int y, const uint8_t *image, int width, int height);
static void displayImageInverted(int x, int y, const uint8_t *image, int width, int height);
static void displayImageInverted_p(int x, int y, const uint8_t *image, int width, int height);
static void setTime(int _hour, int _minute, int _second);
private:
};
#endif

8
src/appDebug.h Normal file
View File

@@ -0,0 +1,8 @@
#if !defined _appDEBUG_H_INCLUDED_
#define _appDEBUG_H_INCLUDED_
#include "mgos.h"
#include "mgos_system.h"
#define logHeap() LOG(LL_DEBUG,("> Heap: %d - %s: line %d in %s", mgos_get_free_heap_size(), __FILE__,__LINE__,__func__))
#endif

137
src/main.cpp Normal file
View File

@@ -0,0 +1,137 @@
#include <stdio.h>
#include <stdlib.h>
#include "mgos.h"
#include "common/cs_dbg.h"
#include "common/json_utils.h"
#include "common/platform.h"
#include "frozen/frozen.h"
#include "mgos_rpc.h"
#include "common/cs_file.h"
#include "mjs.h"
#include <Arduino.h>
#include "Adafruit_SSD1306dj.h"
#include "Battery.h"
#include "Ui.h"
#include "ClockClient.h"
#include "appDebug.h"
static Battery battery = Battery();
static ClockClient clockClient = ClockClient();
static Ui ui = Ui();
#if CS_PLATFORM == CS_P_ESP8266
/* On ESP-12E there is a blue LED connected to GPIO2 (aka U1TX). */
#define LED_GPIO 2
#define BUTTON_GPIO 0 /* Usually a "Flash" button. */
#define BUTTON_PULL MGOS_GPIO_PULL_UP
#define BUTTON_EDGE MGOS_GPIO_INT_EDGE_POS
#elif CS_PLATFORM == CS_P_ESP32
/* Unfortunately, there is no LED on DevKitC, so this is random GPIO. */
#define LED_GPIO 17
#define BUTTON_GPIO 0 /* Usually a "Flash" button. */
#define BUTTON_PULL MGOS_GPIO_PULL_UP
#define BUTTON_EDGE MGOS_GPIO_INT_EDGE_POS
#endif
static void blink_timer_cb(void *arg) {
bool current_level = mgos_gpio_toggle(LED_GPIO);
// LOG(LL_INFO, ("%s", (current_level ? "Tick" : "Tock")));
(void) arg;
(void) current_level;
}
static void button_cb(int pin, void *arg) {
LOG(LL_INFO, ("Click!"));
(void) pin;
(void) arg;
}
static void getBattery_handler(struct mg_rpc_request_info *ri, void *cb_arg,
struct mg_rpc_frame_info *fi, struct mg_str args) {
struct mbuf fb;
struct json_out out = JSON_OUT_MBUF(&fb);
mbuf_init(&fb, 20);
json_printf(&out, "{load: %d, voltage_mV: %d}", batteryGetPercentage(), batteryGetVoltage_mV());
mgos_gpio_toggle(LED_GPIO);
mg_rpc_send_responsef(ri, "%.*s", fb.len, fb.buf);
ri = NULL;
mbuf_free(&fb);
(void) cb_arg;
(void) fi;
(void) args;
}
int get_led_gpio_pin(void) {
return LED_GPIO;
}
enum mgos_app_init_result mgos_app_init(void) {
LOG(LL_DEBUG, ("Starting app %s", mgos_sys_config_get_app_title()));
// if (!init_fastclock_client(mgos_get_mgr())) return MGOS_APP_INIT_ERROR;
/* Set up the blinky timer. */
mgos_gpio_set_mode(LED_GPIO, MGOS_GPIO_MODE_OUTPUT);
mgos_set_timer(1000 /* ms */, true /* repeat */, blink_timer_cb, NULL);
/* Set up a button handler */
mgos_gpio_set_button_handler(BUTTON_GPIO, BUTTON_PULL, BUTTON_EDGE,
50 /* debounce_ms */, button_cb, NULL);
/* Set up RPC */
struct mg_rpc *c = mgos_rpc_get_global();
mg_rpc_add_handler(c, "ClockClient.GetBattery", "{num: %d}", getBattery_handler, NULL);
/* Initialize JavaScript engine */
int mem1, mem2, mem3;
mem1 = mgos_get_free_heap_size();
struct mjs *mjs = mjs_create();
mem2 = mgos_get_free_heap_size();
mjs_set_ffi_resolver(mjs, mgos_dlsym);
mjs_err_t err = mjs_exec_file(mjs, "init.js", 1, NULL);
if (err != MJS_OK) {
mjs_print_error(mjs, stdout, NULL, 1 /* print_stack_trace */);
}
mem3 = mgos_get_free_heap_size();
LOG(LL_INFO, ("mJS memory stat: before init: %d after init: %d after init.js: %d", mem1, mem2, mem3));
/* Startup app components */
ui.begin();
clockClient.begin();
clockClient.addClockChangeCallback(ui.setTime);
// not necessary to call the loop, as this is done by the Arduino lib
// mgos_timer_callback(1000, MGOS_TIMER_REPEAT, loop, NULL);
return MGOS_APP_INIT_SUCCESS;
}
void checkBattery() {
static int lastBatteryReadOn = 0;
if (millis() - lastBatteryReadOn > 30000) {
lastBatteryReadOn = millis();
// debug.out("Battery="); debug.out(battery.getPercentage()); debug.outln(" %");
LOG(LL_DEBUG, ("Battery=%d%%, %dmV", battery.getPercentage(), battery.getVoltage_mV()));
logHeap();
}
}
static void loop(void *args) {
checkBattery();
clockClient.loop();
ui.loop();
}