mirror of
https://github.com/airgradienthq/arduino.git
synced 2025-12-16 18:48:26 +01:00
630 lines
16 KiB
C++
630 lines
16 KiB
C++
#include "AgOledDisplay.h"
|
|
#include "Libraries/U8g2/src/U8g2lib.h"
|
|
#include "Main/utils.h"
|
|
|
|
/** Cast U8G2 */
|
|
#define DISP() ((U8G2_SH1106_128X64_NONAME_F_HW_I2C *)(this->u8g2))
|
|
|
|
static const unsigned char WIFI_ISSUE_BITS[] = {
|
|
0xd8, 0xc6, 0xde, 0xde, 0xc7, 0xf8, 0xd1, 0xe2, 0xdc, 0xce, 0xcc,
|
|
0xcc, 0xc0, 0xc0, 0xd0, 0xc2, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0};
|
|
|
|
static const unsigned char CLOUD_ISSUE_BITS[] = {
|
|
0x70, 0xc0, 0x88, 0xc0, 0x04, 0xc1, 0x04, 0xcf, 0x02, 0xd0, 0x01,
|
|
0xe0, 0x01, 0xe0, 0x01, 0xe0, 0xa2, 0xd0, 0x4c, 0xce, 0xa0, 0xc0};
|
|
|
|
// Offline mode icon
|
|
static unsigned char OFFLINE_BITS[] = {
|
|
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x62, 0x00,
|
|
0xE6, 0x00, 0xFE, 0x1F, 0xFE, 0x1F, 0xE6, 0x00, 0x62, 0x00,
|
|
0x30, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
};
|
|
|
|
/**
|
|
* @brief Show dashboard temperature and humdity
|
|
*
|
|
* @param hasStatus
|
|
*/
|
|
void OledDisplay::showTempHum(bool hasStatus) {
|
|
char buf[10];
|
|
/** Temperature */
|
|
float temp = value.getCorrectedTempHum(Measurements::Temperature, 1);
|
|
if (utils::isValidTemperature(temp)) {
|
|
float t = 0.0f;
|
|
if (config.isTemperatureUnitInF()) {
|
|
t = utils::degreeC_To_F(temp);
|
|
} else {
|
|
t = temp;
|
|
}
|
|
|
|
if (config.isTemperatureUnitInF()) {
|
|
if (hasStatus) {
|
|
snprintf(buf, sizeof(buf), "%0.1f", t);
|
|
} else {
|
|
snprintf(buf, sizeof(buf), "%0.1f°F", t);
|
|
}
|
|
} else {
|
|
if (hasStatus) {
|
|
snprintf(buf, sizeof(buf), "%.1f", t);
|
|
} else {
|
|
snprintf(buf, sizeof(buf), "%.1f°C", t);
|
|
}
|
|
}
|
|
} else { /** Show invalid value */
|
|
if (config.isTemperatureUnitInF()) {
|
|
snprintf(buf, sizeof(buf), "-°F");
|
|
} else {
|
|
snprintf(buf, sizeof(buf), "-°C");
|
|
}
|
|
}
|
|
DISP()->drawUTF8(1, 10, buf);
|
|
|
|
/** Show humidity */
|
|
int rhum = round(value.getCorrectedTempHum(Measurements::Humidity, 1));
|
|
if (utils::isValidHumidity(rhum)) {
|
|
snprintf(buf, sizeof(buf), "%d%%", rhum);
|
|
} else {
|
|
snprintf(buf, sizeof(buf), "-%%");
|
|
}
|
|
|
|
if (rhum > 99.0) {
|
|
DISP()->drawStr(97, 10, buf);
|
|
} else {
|
|
DISP()->drawStr(105, 10, buf);
|
|
}
|
|
}
|
|
|
|
void OledDisplay::setCentralText(int y, String text) {
|
|
setCentralText(y, text.c_str());
|
|
}
|
|
|
|
void OledDisplay::setCentralText(int y, const char *text) {
|
|
int x = (DISP()->getWidth() - DISP()->getStrWidth(text)) / 2;
|
|
DISP()->drawStr(x, y, text);
|
|
}
|
|
|
|
void OledDisplay::showIcon(int x, int y, xbm_icon_t *icon) {
|
|
DISP()->drawXBM(x, y, icon->width, icon->height, icon->icon);
|
|
}
|
|
/**
|
|
* @brief Construct a new Ag Oled Display:: Ag Oled Display object
|
|
*
|
|
* @param config AgConfiguration
|
|
* @param value Measurements
|
|
* @param log Serial Stream
|
|
*/
|
|
OledDisplay::OledDisplay(Configuration &config, Measurements &value,
|
|
Stream &log)
|
|
: PrintLog(log, "OledDisplay"), config(config), value(value) {}
|
|
|
|
/**
|
|
* @brief Set AirGradient instance
|
|
*
|
|
* @param ag Point to AirGradient instance
|
|
*/
|
|
void OledDisplay::setAirGradient(AirGradient *ag) { this->ag = ag; }
|
|
|
|
OledDisplay::~OledDisplay() {}
|
|
|
|
/**
|
|
* @brief Initialize display
|
|
*
|
|
* @return true Success
|
|
* @return false Failure
|
|
*/
|
|
bool OledDisplay::begin(void) {
|
|
if (isBegin) {
|
|
logWarning("Already begin, call 'end' and try again");
|
|
return true;
|
|
}
|
|
|
|
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
|
/** Create u8g2 instance */
|
|
u8g2 = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(U8G2_R0, U8X8_PIN_NONE);
|
|
if (u8g2 == NULL) {
|
|
logError("Create 'U8G2' failed");
|
|
return false;
|
|
}
|
|
|
|
/** Init u8g2 */
|
|
if (DISP()->begin() == false) {
|
|
logError("U8G2 'begin' failed");
|
|
return false;
|
|
}
|
|
} else if (ag->isBasic()) {
|
|
logInfo("DIY_BASIC init");
|
|
ag->display.begin(Wire);
|
|
ag->display.setTextColor(1);
|
|
ag->display.clear();
|
|
ag->display.show();
|
|
}
|
|
|
|
/** Show low brightness on startup. then it's completely turn off on main
|
|
* application */
|
|
int brightness = config.getDisplayBrightness();
|
|
if (brightness == 0) {
|
|
setBrightness(1);
|
|
}
|
|
|
|
isBegin = true;
|
|
logInfo("begin");
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief De-Initialize display
|
|
*
|
|
*/
|
|
void OledDisplay::end(void) {
|
|
if (!isBegin) {
|
|
logWarning("Already end, call 'begin' and try again");
|
|
return;
|
|
}
|
|
|
|
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
|
/** Free u8g2 */
|
|
delete DISP();
|
|
u8g2 = NULL;
|
|
} else if (ag->isBasic()) {
|
|
ag->display.end();
|
|
}
|
|
|
|
isBegin = false;
|
|
logInfo("end");
|
|
}
|
|
|
|
/**
|
|
* @brief Show text on 3 line of display
|
|
*
|
|
* @param line1
|
|
* @param line2
|
|
* @param line3
|
|
*/
|
|
void OledDisplay::setText(String &line1, String &line2, String &line3) {
|
|
setText(line1.c_str(), line2.c_str(), line3.c_str());
|
|
}
|
|
|
|
/**
|
|
* @brief Show text on 3 line of display
|
|
*
|
|
* @param line1
|
|
* @param line2
|
|
* @param line3
|
|
*/
|
|
void OledDisplay::setText(const char *line1, const char *line2,
|
|
const char *line3) {
|
|
if (isDisplayOff) {
|
|
return;
|
|
}
|
|
|
|
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
|
DISP()->firstPage();
|
|
do {
|
|
DISP()->setFont(u8g2_font_t0_16_tf);
|
|
DISP()->drawStr(1, 10, line1);
|
|
DISP()->drawStr(1, 30, line2);
|
|
DISP()->drawStr(1, 50, line3);
|
|
} while (DISP()->nextPage());
|
|
} else if (ag->isBasic()) {
|
|
ag->display.clear();
|
|
|
|
ag->display.setCursor(1, 1);
|
|
ag->display.setText(line1);
|
|
ag->display.setCursor(1, 17);
|
|
ag->display.setText(line2);
|
|
ag->display.setCursor(1, 33);
|
|
ag->display.setText(line3);
|
|
|
|
ag->display.show();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Set Text on 4 line
|
|
*
|
|
* @param line1
|
|
* @param line2
|
|
* @param line3
|
|
* @param line4
|
|
*/
|
|
void OledDisplay::setText(String &line1, String &line2, String &line3,
|
|
String &line4) {
|
|
setText(line1.c_str(), line2.c_str(), line3.c_str(), line4.c_str());
|
|
}
|
|
|
|
/**
|
|
* @brief Set Text on 4 line
|
|
*
|
|
* @param line1
|
|
* @param line2
|
|
* @param line3
|
|
* @param line4
|
|
*/
|
|
void OledDisplay::setText(const char *line1, const char *line2,
|
|
const char *line3, const char *line4) {
|
|
if (isDisplayOff) {
|
|
return;
|
|
}
|
|
|
|
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
|
DISP()->firstPage();
|
|
do {
|
|
DISP()->setFont(u8g2_font_t0_16_tf);
|
|
DISP()->drawStr(1, 10, line1);
|
|
DISP()->drawStr(1, 25, line2);
|
|
DISP()->drawStr(1, 40, line3);
|
|
DISP()->drawStr(1, 55, line4);
|
|
} while (DISP()->nextPage());
|
|
} else if (ag->isBasic()) {
|
|
ag->display.clear();
|
|
ag->display.setCursor(0, 0);
|
|
ag->display.setText(line1);
|
|
ag->display.setCursor(0, 10);
|
|
ag->display.setText(line2);
|
|
ag->display.setCursor(0, 20);
|
|
ag->display.setText(line3);
|
|
ag->display.show();
|
|
}
|
|
}
|
|
|
|
void OledDisplay::showWiFiProvisioning(bool firstRun, int countdown) {
|
|
if (firstRun) {
|
|
DISP()->clearBuffer();
|
|
DISP()->setFont(u8g2_font_t0_16_tf);
|
|
DISP()->drawStr(1, 25, "to WiFi hotspot:");
|
|
DISP()->drawStr(1, 40, "\"airgradient-");
|
|
DISP()->drawStr(1, 55, (ag->deviceId() + "\"").c_str());
|
|
}
|
|
|
|
// Now just update countdown area
|
|
char buf[16];
|
|
snprintf(buf, sizeof(buf), "%ds to connect", countdown);
|
|
DISP()->setDrawColor(0); // erase previous text
|
|
DISP()->drawBox(0, 0, 128, 14); // clear top region
|
|
DISP()->setDrawColor(1); // draw new text in white
|
|
DISP()->setFont(u8g2_font_t0_16_tf);
|
|
DISP()->drawStr(1, 10, buf);
|
|
|
|
// Blink the BLE mark section
|
|
if (countdown % 2 == 0) {
|
|
DISP()->setFont(u8g2_font_t0_12b_tf);
|
|
DISP()->drawStr(108, 60, "BLE");
|
|
} else {
|
|
DISP()->setDrawColor(0);
|
|
DISP()->drawBox(108, 48, 20, 16);
|
|
DISP()->setDrawColor(1);
|
|
}
|
|
|
|
DISP()->sendBuffer();
|
|
}
|
|
|
|
/**
|
|
* @brief Update dashboard content
|
|
*
|
|
*/
|
|
void OledDisplay::showDashboard(void) { showDashboard(DashBoardStatusNone); }
|
|
|
|
/**
|
|
* @brief Update dashboard content and error status
|
|
*
|
|
*/
|
|
void OledDisplay::showDashboard(DashboardStatus status) {
|
|
if (isDisplayOff) {
|
|
return;
|
|
}
|
|
|
|
char strBuf[16];
|
|
const int icon_pos_x = 64;
|
|
xbm_icon_t xbm_icon = {
|
|
.width = 0,
|
|
.height = 0,
|
|
.icon = nullptr,
|
|
};
|
|
|
|
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
|
DISP()->firstPage();
|
|
do {
|
|
DISP()->setFont(u8g2_font_t0_16_tf);
|
|
switch (status) {
|
|
case DashBoardStatusNone: {
|
|
// Maybe show signal strength?
|
|
showTempHum(false);
|
|
break;
|
|
}
|
|
case DashBoardStatusWiFiIssue: {
|
|
DISP()->drawXBM(icon_pos_x, 0, 14, 11, WIFI_ISSUE_BITS);
|
|
showTempHum(false);
|
|
break;
|
|
}
|
|
case DashBoardStatusServerIssue: {
|
|
DISP()->drawXBM(icon_pos_x, 0, 14, 11, CLOUD_ISSUE_BITS);
|
|
showTempHum(false);
|
|
break;
|
|
}
|
|
case DashBoardStatusAddToDashboard: {
|
|
setCentralText(10, "Add To Dashboard");
|
|
break;
|
|
}
|
|
case DashBoardStatusDeviceId: {
|
|
setCentralText(10, ag->deviceId().c_str());
|
|
break;
|
|
}
|
|
case DashBoardStatusOfflineMode: {
|
|
DISP()->drawXBM(icon_pos_x, 0, 14, 14, OFFLINE_BITS);
|
|
showTempHum(false); // First true
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/** Draw horizonal line */
|
|
DISP()->drawLine(1, 13, 128, 13);
|
|
|
|
/** Show CO2 label */
|
|
DISP()->setFont(u8g2_font_t0_12_tf);
|
|
DISP()->drawUTF8(1, 27, "CO2");
|
|
|
|
DISP()->setFont(u8g2_font_t0_22b_tf);
|
|
int co2 = round(value.getAverage(Measurements::CO2));
|
|
if (utils::isValidCO2(co2)) {
|
|
sprintf(strBuf, "%d", co2);
|
|
} else {
|
|
sprintf(strBuf, "%s", "-");
|
|
}
|
|
DISP()->drawStr(1, 48, strBuf);
|
|
|
|
/** Show CO2 value index */
|
|
DISP()->setFont(u8g2_font_t0_12_tf);
|
|
DISP()->drawStr(1, 61, "ppm");
|
|
|
|
/** Draw vertical line */
|
|
DISP()->drawLine(52, 14, 52, 64);
|
|
DISP()->drawLine(97, 14, 97, 64);
|
|
|
|
/** Draw PM2.5 label */
|
|
DISP()->setFont(u8g2_font_t0_12_tf);
|
|
DISP()->drawStr(55, 27, "PM2.5");
|
|
|
|
/** Draw PM2.5 value */
|
|
int pm25 = round(value.getAverage(Measurements::PM25));
|
|
if (utils::isValidPm(pm25)) {
|
|
if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
|
|
pm25 = round(value.getCorrectedPM25(true));
|
|
}
|
|
if (config.isPmStandardInUSAQI()) {
|
|
sprintf(strBuf, "%d", ag->pms5003.convertPm25ToUsAqi(pm25));
|
|
} else {
|
|
sprintf(strBuf, "%d", pm25);
|
|
}
|
|
} else { /** Show invalid value. */
|
|
sprintf(strBuf, "%s", "-");
|
|
}
|
|
DISP()->setFont(u8g2_font_t0_22b_tf);
|
|
DISP()->drawStr(55, 48, strBuf);
|
|
|
|
/** Draw PM2.5 unit */
|
|
DISP()->setFont(u8g2_font_t0_12_tf);
|
|
if (config.isPmStandardInUSAQI()) {
|
|
DISP()->drawUTF8(55, 61, "AQI");
|
|
} else {
|
|
DISP()->drawUTF8(55, 61, "ug/m³");
|
|
}
|
|
|
|
/** Draw tvocIndexlabel */
|
|
DISP()->setFont(u8g2_font_t0_12_tf);
|
|
DISP()->drawStr(100, 27, "VOC:");
|
|
|
|
/** Draw tvocIndexvalue */
|
|
int tvoc = round(value.getAverage(Measurements::TVOC));
|
|
if (utils::isValidVOC(tvoc)) {
|
|
sprintf(strBuf, "%d", tvoc);
|
|
} else {
|
|
sprintf(strBuf, "%s", "-");
|
|
}
|
|
DISP()->drawStr(100, 39, strBuf);
|
|
|
|
/** Draw NOx label */
|
|
int nox = round(value.getAverage(Measurements::NOx));
|
|
DISP()->drawStr(100, 53, "NOx:");
|
|
if (utils::isValidNOx(nox)) {
|
|
sprintf(strBuf, "%d", nox);
|
|
} else {
|
|
sprintf(strBuf, "%s", "-");
|
|
}
|
|
DISP()->drawStr(100, 63, strBuf);
|
|
} while (DISP()->nextPage());
|
|
} else if (ag->isBasic()) {
|
|
ag->display.clear();
|
|
|
|
/** Set CO2 */
|
|
int co2 = round(value.getAverage(Measurements::CO2));
|
|
if (utils::isValidCO2(co2)) {
|
|
snprintf(strBuf, sizeof(strBuf), "CO2:%d", co2);
|
|
} else {
|
|
snprintf(strBuf, sizeof(strBuf), "CO2:-");
|
|
}
|
|
|
|
ag->display.setCursor(0, 0);
|
|
ag->display.setText(strBuf);
|
|
|
|
/** Set PM */
|
|
int pm25 = round(value.getAverage(Measurements::PM25));
|
|
if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
|
|
pm25 = round(value.getCorrectedPM25(true));
|
|
}
|
|
|
|
ag->display.setCursor(0, 12);
|
|
if (utils::isValidPm(pm25)) {
|
|
snprintf(strBuf, sizeof(strBuf), "PM2.5:%d", pm25);
|
|
} else {
|
|
snprintf(strBuf, sizeof(strBuf), "PM2.5:-");
|
|
}
|
|
ag->display.setText(strBuf);
|
|
|
|
/** Set temperature and humidity */
|
|
float temp = value.getCorrectedTempHum(Measurements::Temperature, 1);
|
|
if (utils::isValidTemperature(temp)) {
|
|
if (config.isTemperatureUnitInF()) {
|
|
snprintf(strBuf, sizeof(strBuf), "T:%0.1f F",
|
|
utils::degreeC_To_F(temp));
|
|
} else {
|
|
snprintf(strBuf, sizeof(strBuf), "T:%0.1f C", temp);
|
|
}
|
|
} else {
|
|
if (config.isTemperatureUnitInF()) {
|
|
snprintf(strBuf, sizeof(strBuf), "T:-F");
|
|
} else {
|
|
snprintf(strBuf, sizeof(strBuf), "T:-C");
|
|
}
|
|
}
|
|
|
|
ag->display.setCursor(0, 24);
|
|
ag->display.setText(strBuf);
|
|
|
|
int rhum = round(value.getCorrectedTempHum(Measurements::Humidity, 1));
|
|
if (utils::isValidHumidity(rhum)) {
|
|
snprintf(strBuf, sizeof(strBuf), "H:%d %%", rhum);
|
|
} else {
|
|
snprintf(strBuf, sizeof(strBuf), "H:- %%");
|
|
}
|
|
|
|
ag->display.setCursor(0, 36);
|
|
ag->display.setText(strBuf);
|
|
|
|
ag->display.show();
|
|
}
|
|
}
|
|
|
|
void OledDisplay::setBrightness(int percent) {
|
|
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
|
if (percent == 0) {
|
|
isDisplayOff = true;
|
|
|
|
// Clear display.
|
|
DISP()->firstPage();
|
|
do {
|
|
} while (DISP()->nextPage());
|
|
|
|
} else {
|
|
isDisplayOff = false;
|
|
DISP()->setContrast((127 * percent) / 100);
|
|
}
|
|
} else if (ag->isBasic()) {
|
|
if (percent == 0) {
|
|
isDisplayOff = true;
|
|
|
|
// Clear display.
|
|
ag->display.clear();
|
|
ag->display.show();
|
|
} else {
|
|
isDisplayOff = false;
|
|
ag->display.setContrast((255 * percent) / 100);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef ESP32
|
|
void OledDisplay::showFirmwareUpdateVersion(String version) {
|
|
if (isDisplayOff) {
|
|
return;
|
|
}
|
|
|
|
DISP()->firstPage();
|
|
do {
|
|
DISP()->setFont(u8g2_font_t0_16_tf);
|
|
setCentralText(20, "Firmware Update");
|
|
setCentralText(40, "New version");
|
|
setCentralText(60, version.c_str());
|
|
} while (DISP()->nextPage());
|
|
}
|
|
|
|
void OledDisplay::showFirmwareUpdateProgress(int percent) {
|
|
if (isDisplayOff) {
|
|
return;
|
|
}
|
|
|
|
DISP()->firstPage();
|
|
do {
|
|
DISP()->setFont(u8g2_font_t0_16_tf);
|
|
setCentralText(20, "Firmware Update");
|
|
setCentralText(50, String("Updating... ") + String(percent) + String("%"));
|
|
} while (DISP()->nextPage());
|
|
}
|
|
|
|
void OledDisplay::showFirmwareUpdateSuccess(int count) {
|
|
if (isDisplayOff) {
|
|
return;
|
|
}
|
|
|
|
DISP()->firstPage();
|
|
do {
|
|
DISP()->setFont(u8g2_font_t0_16_tf);
|
|
setCentralText(20, "Firmware Update");
|
|
setCentralText(40, "Success");
|
|
setCentralText(60, String("Rebooting... ") + String(count));
|
|
} while (DISP()->nextPage());
|
|
}
|
|
|
|
void OledDisplay::showFirmwareUpdateFailed(void) {
|
|
if (isDisplayOff) {
|
|
return;
|
|
}
|
|
|
|
DISP()->firstPage();
|
|
do {
|
|
DISP()->setFont(u8g2_font_t0_16_tf);
|
|
setCentralText(20, "Firmware Update");
|
|
setCentralText(40, "fail, will retry");
|
|
// setCentralText(60, "will retry");
|
|
} while (DISP()->nextPage());
|
|
}
|
|
|
|
void OledDisplay::showFirmwareUpdateSkipped(void) {
|
|
if (isDisplayOff) {
|
|
return;
|
|
}
|
|
|
|
DISP()->firstPage();
|
|
do {
|
|
DISP()->setFont(u8g2_font_t0_16_tf);
|
|
setCentralText(20, "Firmware Update");
|
|
setCentralText(40, "skipped");
|
|
} while (DISP()->nextPage());
|
|
}
|
|
|
|
void OledDisplay::showFirmwareUpdateUpToDate(void) {
|
|
if (isDisplayOff) {
|
|
return;
|
|
}
|
|
|
|
DISP()->firstPage();
|
|
do {
|
|
DISP()->setFont(u8g2_font_t0_16_tf);
|
|
setCentralText(20, "Firmware Update");
|
|
setCentralText(40, "up to date");
|
|
} while (DISP()->nextPage());
|
|
}
|
|
#else
|
|
|
|
#endif
|
|
|
|
void OledDisplay::showRebooting(void) {
|
|
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
|
DISP()->firstPage();
|
|
do {
|
|
DISP()->setFont(u8g2_font_t0_16_tf);
|
|
// setCentralText(20, "Firmware Update");
|
|
setCentralText(40, "Rebooting...");
|
|
// setCentralText(60, String("Retry after 24h"));
|
|
} while (DISP()->nextPage());
|
|
} else if (ag->isBasic()) {
|
|
ag->display.clear();
|
|
|
|
ag->display.setCursor(0, 20);
|
|
ag->display.setText("Rebooting...");
|
|
|
|
ag->display.show();
|
|
}
|
|
}
|