aboutsummaryrefslogtreecommitdiffstats
path: root/hal/api.cpp
blob: 961cdd5d86f3a997a4fb13da2cd9bdc51297cc27 (plain)
1
2
3
4
5
6
7
8
9
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
build/**
.vscode/build/**
.vscode/.cortex-debug.*.state.json
id='n184' href='#n184'>184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
#include <stdio.h>
#ifndef SDL2_BUILD
#include "pico/stdlib.h"
extern "C" {
#include "hardware/rtc.h"
}
#else
#include "sdl2/display.hpp"
#endif

#include "api.hpp"
#include "../buttons.hpp"
#include "../init.hpp"
#include "../globals.hpp"

void Api::init() {
    if (!m_init_done) {
        init_display();
        m_init_done = 1;
    }
}

void Api::init_display() {
    sleep_ms(500); // Wait for the OLED to settle
    oledInit(&m_oled, OLED_128x64, 0x3d, 0, 0, 1, SDA_PIN, SCL_PIN, RESET_PIN, 1000000L);
    oledFill(&m_oled, 0,1);
    oledSetContrast(&m_oled, g_user.oled_contrast);
    oledSetBackBuffer(&m_oled, m_ucBuffer); // Seems to be required to draw lines, rectangles…
    //oledSetTextWrap(&oled, true);
}

void Api::display_power(bool mode) {
    oledPower(&m_oled, mode);
}

void Api::display_set_contrast(unsigned char contrast) {
    oledSetContrast(&m_oled, contrast);
}

int Api::display_write_string(int iScrollX, int x, int y, const char *szMsg, int iSize, int bInvert, int bRender) {
    oledWriteString(&m_oled, iScrollX, x, y, szMsg, iSize, bInvert, 0);
    m_writebb_needed = true;
    return 0;
}

void Api::display_fill(unsigned char ucData, int bRender) {
    oledFill(&m_oled, ucData, bRender);
}

void Api::display_draw_line(int x1, int y1, int x2, int y2, int bRender) {
    oledDrawLine(&m_oled, x1, y1, x2, y2, 0);
    m_writebb_needed = true;
}

void Api::display_draw_rectange(int x1, int y1, int x2, int y2, uint8_t ucColor, uint8_t bFilled) { // FIXME: Fix typo
    oledRectangle(&m_oled, x1, y1, x2, y2, ucColor, bFilled);
    m_writebb_needed = true; // Write the back buffer, after experimentation, seems to be required when drawing this shape
}

void Api::display_draw_ellipse(int iCenterX, int iCenterY, int32_t iRadiusX, int32_t iRadiusY, uint8_t ucColor, uint8_t bFilled) {
    oledEllipse(&m_oled, iCenterX, iCenterY, iRadiusX, iRadiusY, ucColor, bFilled);
    m_writebb_needed = true;
}

void Api::display_write_buffer(uint8_t *pBuffer) {
    oledDumpBuffer(&m_oled, pBuffer);
}

bool Api::display_write_backbuffer() {
#ifndef SDL2_BUILD
    if (m_writebb_needed)
        oledDumpBuffer(&m_oled, m_ucBuffer);
    m_writebb_needed = false;
    return false;
#else
    m_sdl_display.process_input();
    return m_sdl_display.update_display(m_ucBuffer);
#endif
}

int Api::display_write_pixel(int x, int y, unsigned char ucColor, int bRender) {
    return oledSetPixel(&m_oled, x, y, ucColor, bRender);
}

void Api::gui_popup_generic(std::string &title, std::string &body, int max_title_length, int max_body_length) {
    oledRectangle(&m_oled, 9,7, 119,63, 0, 1); // Background
    oledRectangle(&m_oled, 9,7, 119,63, 1, 0); // Popup border
    oledRectangle(&m_oled, 9,7, 119,16, 1, 1); // Title background, FIXME pixel bleeding
    m_writebb_needed = true; this->display_write_backbuffer(); // Display rectangle and anything else behind it (drawn before), could be moved after writing strings (but not done for debugging)

    // Truncate longer strings to avoid wasting time in for loop and drawing on OLED
    if (max_title_length > 13) max_title_length = 13;
    if (max_body_length > 78) max_body_length = 78;
    if (title.size() > max_title_length)
        title.resize(13);
    if (body.size() > max_body_length)
        body.resize(78);

    // Make body fit by adding '\n' at a regular interval
    #define CHARS_PER_LINE 13
    int since_nl = 0; // Characters since newline
    for (std::string::size_type i = 0; i < body.size(); ++i) {
        if (body[i] == '\n')
            since_nl = 0;
        else if (since_nl++ == CHARS_PER_LINE) {
            body.insert(i, 1, '\n');
            since_nl = 0; // Probably unneeded
        }
    }
    // See https://stackoverflow.com/questions/1986966/does-s0-point-to-contiguous-characters-in-a-stdstring
    oledWriteString(&m_oled, 0, 15,1, title.c_str(), FONT_8x8, 1, 1); // Draw title
    oledWriteString(&m_oled, 0, 13,2, body.c_str(), FONT_8x8, 0, 1); // Draw body
}

bool Api::gui_popup_text(std::string title, std::string body){
    m_button_last_pressed = BUTTON_NONE;
    m_interpret_button_press = false;

    gui_popup_generic(title, body);

    while (m_button_last_pressed != BUTTON_SELECT)
        sleep_ms(50); // TODO: use _wfi()
    // Give back control to running app
    oledFill(&m_oled, 0, 1);
    m_interpret_button_press = true;
    return true;
}

bool Api::gui_popup_booleanchoice(std::string title, std::string body){
    m_button_last_pressed = BUTTON_NONE;
    m_interpret_button_press = false;

    title.insert(0, "Choice|"); // TODO: Could be made nicer with a custom char that uses the whole height, this would give a visible separation, with two "text blocks" composing the title
    gui_popup_generic(title, body);

    while (m_button_last_pressed != BUTTON_SELECT and m_button_last_pressed != BUTTON_MODE)
        sleep_ms(50); // TODO: use _wfi()

    bool choice;
    switch (m_button_last_pressed) {
        case BUTTON_SELECT:
            choice = true;
            break;
        case BUTTON_MODE:
            choice = false;
            break;
        default:
            __breakpoint(); // Impossible to attain (but you never know…)
    }
    // Give back control to running app
    oledFill(&m_oled, 0, 1);
    m_interpret_button_press = true;
    return choice;
}

void Api::gui_popup_intchoice_footer(int current_num, int min_num, int max_num) {
    char buf[30];
    snprintf(&buf[0], sizeof(buf),
            "Select: %d (%d/%d)",
            current_num,
            min_num,
            max_num);
    oledRectangle(&m_oled, 9,55, 119,63, 1, 1); // Footer background, FIXME pixel bleeding
    oledWriteString(&m_oled, 0,10,7, buf, FONT_6x8, 1, 0);
    m_writebb_needed = true; display_write_backbuffer();
}

int Api::gui_popup_intchoice(std::string title, std::string body, int min_num, int max_num, int default_num, int step){
    m_button_last_pressed = BUTTON_NONE;
    m_interpret_button_press = false;

    int current_num = default_num;

    title.insert(0, "Number|"); // TODO: Could be made nicer with a custom char instead of pipe char
    gui_popup_generic(title, body, 13, 39); // 39: 3 lines of body text, to leave space for number
    gui_popup_intchoice_footer(current_num, min_num, max_num);

    do {
        m_button_last_pressed = BUTTON_NONE;
        sleep_ms(50); // TODO: use _wfi()
        switch (m_button_last_pressed) {
            case BUTTON_UP:
                current_num += step;
                if (current_num > max_num)
                    current_num = max_num;
                break;
            case BUTTON_DOWN:
                current_num -= step;
                if (current_num < min_num)
                    current_num = min_num;
                break;
            case BUTTON_MODE:
                current_num = default_num;
                break;
        }
        if (m_button_last_pressed)
            gui_popup_intchoice_footer(current_num, min_num, max_num);
    } while (m_button_last_pressed != BUTTON_SELECT);    

    // Give back control to running app
    oledFill(&m_oled, 0, 1);
    m_interpret_button_press = true;
    return current_num;
}

void Api::gui_popup_strchoice_footer(const char selection[]) {
    std::string buf{selection};
    buf.insert(0, "Select: ");

    if (buf.size() > 36) // 2 lines of 18 chars
        buf.resize(36);

    // Choose most adapted font size
    int font;
    int chars_per_line;
    if (buf.size() > 26) {
        font = FONT_6x8;
        chars_per_line = 18;
    } else {
        font = FONT_8x8;
        chars_per_line = 13;
    }

    // Make selection text fit by adding '\n' at a regular interval
    // TODO: Make this a private function
    int since_nl = 0; // Characters since newline
    for (std::string::size_type i = 0; i < buf.size(); ++i) {
        if (buf[i] == '\n')
            since_nl = 0;
        else if (since_nl++ == chars_per_line) {
            buf.insert(i, 1, '\n');
            since_nl = 0; // Probably unneeded
        }
    }

    oledRectangle(&m_oled, 9,47, 119,63, 1, 1); // Footer background, FIXME pixel bleeding
    oledWriteString(&m_oled, 0,10,6, buf.c_str(), font, 1, 0);
    m_writebb_needed = true; display_write_backbuffer();
}

int Api::gui_popup_strchoice(std::string title, std::string body, const char *choices[27], int choices_size, int min_index, int max_index, int default_index){
    m_button_last_pressed = BUTTON_NONE;
    m_interpret_button_press = false;
    if (max_index == -1)
        max_index = choices_size-1;

    int current_index = default_index;

    title.insert(0, "Choose|"); // TODO: Could be made nicer with a custom char instead of pipe char
    gui_popup_generic(title, body, 13, 39); // 39: 3 lines of body text, to leave space for selection string
    gui_popup_strchoice_footer(choices[current_index]);

    do {
        m_button_last_pressed = BUTTON_NONE;
        sleep_ms(50); // TODO: use _wfi()
        switch (m_button_last_pressed) {
            case BUTTON_UP:
                current_index += 1;
                if (current_index > max_index)
                    current_index = max_index;
                break;
            case BUTTON_DOWN:
                current_index -= 1;
                if (current_index < min_index)
                    current_index = min_index;
                break;
            case BUTTON_MODE:
                current_index = default_index;
                break;
        }
        if (m_button_last_pressed)
            gui_popup_strchoice_footer(choices[current_index]);
    } while (m_button_last_pressed != BUTTON_SELECT);    

    // Give back control to running app
    oledFill(&m_oled, 0, 1);
    m_interpret_button_press = true;
    return current_index;
}

bool Api::gui_footer_text(std::string text, int offset_x, int offset_row, bool invert, bool no_bg) {
    // Max chars per line for FONT_8x8 is 16 chars
    // Max chars per line for FONT_6x8 is 21 chars
    // Truncate longer text
    if (text.size() > 21)
        text.resize(21);
    
    // Choose most adapted font size
    int font;
    if (text.size() > 16)
        font = FONT_6x8;
    else
        font = FONT_8x8;
    
    if (!no_bg) {
        oledRectangle(&m_oled, 0,56-offset_row*8, 127,64-offset_row*8, invert, 1);
        m_writebb_needed = true;
    }
    return oledWriteString(&m_oled, 0,offset_x,7-offset_row, text.c_str(), font, invert, 0);
}

bool Api::gui_header_text(std::string text, int offset_x, int offset_row, bool invert, bool no_bg) {
    // Max chars per line for FONT_8x8 is 16 chars
    // Max chars per line for FONT_6x8 is 21 chars
    // Truncate longer text
    if (text.size() > 21)
        text.resize(21);
    
    // Choose most adapted font size
    int font;
    if (text.size() > 16)
        font = FONT_6x8;
    else
        font = FONT_8x8;
    
    if (!no_bg) {
        oledRectangle(&m_oled, 0,0+offset_row*8, 127,8+offset_row*8, invert, 1);
        m_writebb_needed = true;
    }
    return oledWriteString(&m_oled, 0,offset_x,0+offset_row, text.c_str(), font, invert, 0);
}

bool Api::performance_set(int perf) {
    return false;
}

bool Api::datetime_get(datetime_t *t) {
#ifndef SDL2_BUILD
    return rtc_get_datetime(t);
#else
    return m_sdl_display.get_datetime(t);
#endif
}

bool Api::datetime_set(datetime_t *t) {
#ifndef SDL2_BUILD
    return rtc_set_datetime(t);
#else
    return false;
#endif
}

uint Api::button_last_get() {
    return m_button_last_pressed;
}

void Api::button_last_set(uint gpio) {
    m_button_last_pressed = gpio;
}