diff --git a/.github/workflows/guppydroid.yml b/.github/workflows/guppydroid.yml new file mode 100644 index 0000000..6ad169b --- /dev/null +++ b/.github/workflows/guppydroid.yml @@ -0,0 +1,67 @@ +name: guppydroid build + +on: + push: + branches: [ "develop", "main", "android" ] + tags: + - "*" + +permissions: + contents: write + +jobs: + build: + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + with: + ref: android + + - uses: actions/checkout@v3 + with: + ref: main + submodules: true + path: ./app/src/main/cpp/guppyscreen + + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + cache: 'gradle' + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Setup signing + env: + ENCODED_KS: ${{ secrets.KEYSTORE }} + run: | + echo $ENCODED_KS > ks.txt + base64 -d ks.txt > ks.jks + + - name: Build APK + env: + KEYSTORE: ../ks.jks + KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} + KEY_ALIAS: ${{ secrets.KEY_ALIAS }} + KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} + run: ./gradlew assembleRelease + + - name: nightly release + uses: softprops/action-gh-release@v2 + with: + prerelease: true + name: nightly + tag_name: nightly + files: app/build/outputs/apk/release/app-release.apk + fail_on_unmatched_files: true + + - name: stable release + uses: softprops/action-gh-release@v2 + if: startsWith(github.ref, 'refs/tags/') + with: + files: app/build/outputs/apk/release/app-release.apk + generate_release_notes: true + fail_on_unmatched_files: true + diff --git a/src/config.cpp b/src/config.cpp index d4d05ed..760f5d7 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1,4 +1,5 @@ #include "config.h" +#include "platform.h" #include #include @@ -19,7 +20,7 @@ Config *Config::get_instance() { return instance; } -void Config::init(std::string config_path) { +void Config::init(std::string config_path, const std::string thumbdir) { path = config_path; struct stat buffer; json fans_conf = { @@ -66,62 +67,63 @@ void Config::init(std::string config_path) { if (stat(config_path.c_str(), &buffer) == 0) { data = json::parse(std::fstream(config_path)); - } else { data = { - {"default_printer", "k1"}, - {"log_path", "/usr/data/printer_data/logs/guppyscreen.log"}, - {"thumbnail_path", "/usr/data/printer_data/thumbnails"}, - {"wpa_supplicant", "/var/run/wpa_supplicant"}, - {"display_sleep_sec", 600}, - {"printers", {{ - "k1", { - {"moonraker_api_key", false}, - {"moonraker_host", "127.0.0.1"}, - {"moonraker_port", 7125}, - {"monitored_sensors", sensors_conf}, - {"fans", fans_conf}, - {"default_macros", default_macros_conf}, - } - }} - } + {"log_path", "/usr/data/printer_data/logs/guppyscreen.log"}, + {"thumbnail_path", thumbdir}, + {"wpa_supplicant", "/var/run/wpa_supplicant"}, + {"display_sleep_sec", 600} +#ifndef OS_ANDROID + , {"default_printer", "k1"}, + {"printers", {{"k1", { + {"moonraker_api_key", false}, + {"moonraker_host", "127.0.0.1"}, + {"moonraker_port", 7125}, + {"monitored_sensors", sensors_conf}, + {"fans", fans_conf}, + {"default_macros", default_macros_conf}, + }}} + } +#endif }; - } - - std::string df_name = data["/default_printer"_json_pointer]; - default_printer = "/printers/" + df_name + "/"; - auto &monitored_sensors = data[json::json_pointer(df() + "monitored_sensors")]; - if (monitored_sensors.is_null()) { - data[json::json_pointer(df() + "monitored_sensors")] = sensors_conf; - } + data["config_path"] = config_path; - auto &fans = data[json::json_pointer(df() + "fans")]; - if (fans.is_null()) { - data[json::json_pointer(df() + "fans")] = fans_conf; - } + auto df_name = data["/default_printer"_json_pointer]; + if (!df_name.is_null()) { + default_printer = "/printers/" + df_name.template get() + "/"; - auto &default_macros = data[json::json_pointer(df() + "default_macros")]; - if (default_macros.is_null()) { - default_macros_conf.merge_patch(cooldown_conf); - data[json::json_pointer(df() + "default_macros")] = default_macros_conf; - } else { - if (!default_macros.contains("cooldown")) { - default_macros.merge_patch(cooldown_conf); + auto &monitored_sensors = data[json::json_pointer(df() + "monitored_sensors")]; + if (monitored_sensors.is_null()) { + data[json::json_pointer(df() + "monitored_sensors")] = sensors_conf; } - } - auto &guppy_init = data["/guppy_init_script"_json_pointer]; - if (guppy_init.is_null()) { - data["/guppy_init_script"_json_pointer] = "/etc/init.d/S99guppyscreen"; - } - - auto &ll = data[json::json_pointer(df() + "log_level")]; - if (ll.is_null()) { - data[json::json_pointer(df() + "log_level")] = "debug"; - } + auto &fans = data[json::json_pointer(df() + "fans")]; + if (fans.is_null()) { + data[json::json_pointer(df() + "fans")] = fans_conf; + } + + auto &default_macros = data[json::json_pointer(df() + "default_macros")]; + if (default_macros.is_null()) { + default_macros_conf.merge_patch(cooldown_conf); + data[json::json_pointer(df() + "default_macros")] = default_macros_conf; + } else { + if (!default_macros.contains("cooldown")) { + default_macros.merge_patch(cooldown_conf); + } + } + auto &guppy_init = data["/guppy_init_script"_json_pointer]; + if (guppy_init.is_null()) { + data["/guppy_init_script"_json_pointer] = "/etc/init.d/S99guppyscreen"; + } + + auto &ll = data[json::json_pointer(df() + "log_level")]; + if (ll.is_null()) { + data[json::json_pointer(df() + "log_level")] = "debug"; + } + } auto &rotate = data["/display_rotate"_json_pointer]; if (rotate.is_null()) { #ifdef GUPPY_ROTATE diff --git a/src/config.h b/src/config.h index 5d45f5b..d4ba93d 100644 --- a/src/config.h +++ b/src/config.h @@ -21,7 +21,7 @@ class Config { Config(); Config(Config &o) = delete; void operator=(const Config &) = delete; - void init(std::string config_path); + void init(std::string config_path, const std::string thumbdir); template T get(const std::string &json_ptr) { return data[json::json_pointer(json_ptr)].template get(); diff --git a/src/extruder_panel.cpp b/src/extruder_panel.cpp index 6667316..999fed4 100644 --- a/src/extruder_panel.cpp +++ b/src/extruder_panel.cpp @@ -44,19 +44,22 @@ ExtruderPanel::ExtruderPanel(KWebSocketClient &websocket_client, , cooldown_macro("SET_HEATER_TEMPERATURE HEATER=extruder TARGET=0") { Config *conf = Config::get_instance(); - auto v = conf->get_json(conf->df() + "default_macros/load_filament"); - if (!v.is_null()) { - load_filament_macro = v.template get(); - } + auto df = conf->get_json("/default_printer"); + if (!df.empty()) { + auto v = conf->get_json(conf->df() + "default_macros/load_filament"); + if (!v.is_null()) { + load_filament_macro = v.template get(); + } - v = conf->get_json(conf->df() + "default_macros/unload_filament"); - if (!v.is_null()) { - unload_filament_macro = v.template get(); - } + v = conf->get_json(conf->df() + "default_macros/unload_filament"); + if (!v.is_null()) { + unload_filament_macro = v.template get(); + } - v = conf->get_json(conf->df() + "default_macros/cooldown"); - if (!v.is_null()) { - cooldown_macro = v.template get(); + v = conf->get_json(conf->df() + "default_macros/cooldown"); + if (!v.is_null()) { + cooldown_macro = v.template get(); + } } lv_obj_move_background(panel_cont); diff --git a/src/guppyscreen.cpp b/src/guppyscreen.cpp new file mode 100644 index 0000000..39c6d69 --- /dev/null +++ b/src/guppyscreen.cpp @@ -0,0 +1,257 @@ +#include "guppyscreen.h" + +#include "config.h" +#ifndef OS_ANDROID + #include "lv_drivers/display/fbdev.h" + #include "lv_drivers/indev/evdev.h" + + #include "spdlog/sinks/rotating_file_sink.h" + #include "spdlog/sinks/stdout_sinks.h" + +#else + #include "spdlog/sinks/android_sink.h" +#endif + +#include "printer_select_panel.h" +#include "spdlog/spdlog.h" +#include "state.h" + +GuppyScreen *GuppyScreen::instance = NULL; +lv_style_t GuppyScreen::style_container; +lv_style_t GuppyScreen::style_imgbtn_pressed; +lv_style_t GuppyScreen::style_imgbtn_disabled; +lv_theme_t GuppyScreen::th_new; + +#ifndef OS_ANDROID +lv_obj_t *GuppyScreen::screen_saver = NULL; +#endif + +std::mutex GuppyScreen::lv_lock; + +GuppyScreen::GuppyScreen() + : ws(NULL) + , spoolman_panel(ws, lv_lock) + , main_panel(ws, lv_lock, spoolman_panel) + , init_panel(main_panel, main_panel.get_tune_panel().get_bedmesh_panel(), lv_lock) +{ + ws.register_notify_update(State::get_instance()); + main_panel.create_panel(); +} + +GuppyScreen *GuppyScreen::get() { + if (instance == NULL) { + instance = new GuppyScreen(); + } + + return instance; +} + +GuppyScreen *GuppyScreen::init(std::function hal_init) { + hlog_disable(); + + // config + Config *conf = Config::get_instance(); + const std::string ll_path = conf->df() + "log_level"; + auto ll = spdlog::level::from_str( + conf->get_json("/printers").empty() + ? "debug" + : conf->get(ll_path)); + +#ifndef OS_ANDROID + auto console_sink = std::make_shared(); + auto file_sink = std::make_shared( + conf->get("/log_path"), 1048576 * 10, 3); + spdlog::sinks_init_list log_sinks{console_sink, file_sink}; + +#else + auto android_sink = std::make_shared(); + spdlog::sinks_init_list log_sinks{android_sink}; +#endif // OS_ANDROID + + auto klogger = std::make_shared("guppyscreen", log_sinks); + spdlog::register_logger(klogger); + + spdlog::set_level(ll); + spdlog::set_default_logger(klogger); + klogger->flush_on(ll); + +#ifdef GUPPYSCREEN_VERSION + spdlog::info("Guppy Screen Version: {}", GUPPYSCREEN_VERSION); +#endif // GUPPYSCREEN_VERSION + + spdlog::info("DPI: {}", LV_DPI_DEF); + /*LittlevGL init*/ + lv_init(); + +#if !defined(SIMULATOR) && !defined(OS_ANDROID) + /*Linux frame buffer device init*/ + fbdev_init(); + fbdev_unblank(); +#endif // OS_ANDROID + + hal_init(); + lv_png_init(); + + lv_style_init(&style_container); + lv_style_set_border_width(&style_container, 0); + lv_style_set_radius(&style_container, 0); + + lv_style_init(&style_imgbtn_pressed); + lv_style_set_img_recolor_opa(&style_imgbtn_pressed, LV_OPA_100); + lv_style_set_img_recolor(&style_imgbtn_pressed, lv_palette_main(LV_PALETTE_BLUE)); + + lv_style_init(&style_imgbtn_disabled); + lv_style_set_img_recolor_opa(&style_imgbtn_disabled, LV_OPA_100); + lv_style_set_img_recolor(&style_imgbtn_disabled, lv_palette_darken(LV_PALETTE_GREY, 1)); + + /*Initia1ize the new theme from the current theme*/ + + lv_theme_t *th_act = lv_disp_get_theme(NULL); + th_new = *th_act; + + /*Set the parent theme and the style apply callback for the new theme*/ + lv_theme_set_parent(&th_new, th_act); + lv_theme_set_apply_cb(&th_new, &GuppyScreen::new_theme_apply_cb); + + /*Assign the new theme to the current display*/ + lv_disp_set_theme(NULL, &th_new); + +#ifndef OS_ANDROID + screen_saver = lv_obj_create(lv_scr_act()); + + lv_obj_set_size(screen_saver, LV_PCT(100), LV_PCT(100)); + lv_obj_set_style_bg_opa(screen_saver, LV_OPA_100, 0); + lv_obj_move_background(screen_saver); + + lv_obj_t *main_screen = lv_disp_get_scr_act(NULL); + auto touch_calibrated = conf->get_json("/touch_calibrated"); + if (!touch_calibrated.is_null()) { + auto is_calibrated = touch_calibrated.template get(); + if (is_calibrated) { + auto calibration_coeff = conf->get_json("/touch_calibration_coeff"); + if (calibration_coeff.is_null()) { + lv_tc_register_coeff_save_cb(&GuppyScreen::save_calibration_coeff); + lv_obj_t *touch_calibrate_scr = lv_tc_screen_create(); + + lv_disp_load_scr(touch_calibrate_scr); + + lv_tc_screen_start(touch_calibrate_scr); + lv_obj_add_event_cb(touch_calibrate_scr, &GuppyScreen::handle_calibrated, LV_EVENT_READY, main_screen); + spdlog::info("running touch calibration"); + } else { + // load calibration data + auto c = calibration_coeff.template get>(); + lv_tc_coeff_t coeff = {true, c[0], c[1], c[2], c[3], c[4], c[5]}; + lv_tc_set_coeff(coeff, false); + spdlog::info("loaded calibration coefficients"); + } + } + } +#endif // OS_ANDROID + + GuppyScreen *gs = GuppyScreen::get(); + auto printers = conf->get_json("/printers"); + if (!printers.empty()) { + // start initializing all guppy components + std::string ws_url = fmt::format("ws://{}:{}/websocket", + conf->get(conf->df() + "moonraker_host"), + conf->get(conf->df() + "moonraker_port")); + + spdlog::info("connecting to printer at {}", ws_url); + gs->connect_ws(ws_url); + } + + return gs; +} + +void GuppyScreen::loop() { + /*Handle LitlevGL tasks (tickless mode)*/ +#if !defined(SIMULATOR) && !defined(OS_ANDROID) + std::atomic_bool is_sleeping(false); + Config *conf = Config::get_instance(); + int32_t display_sleep = conf->get("/display_sleep_sec") * 1000; +#endif + + while (1) { + lv_lock.lock(); + lv_timer_handler(); + lv_lock.unlock(); + +#if !defined(SIMULATOR) && !defined(OS_ANDROID) + if (display_sleep != -1) { + if (lv_disp_get_inactive_time(NULL) > display_sleep) { + if (!is_sleeping.load()) { + spdlog::debug("putting display to sleeping"); + fbdev_blank(); + lv_obj_move_foreground(screen_saver); + // spdlog::debug("screen saver foreground"); + is_sleeping = true; + } + } else { + if (is_sleeping.load()) { + spdlog::debug("waking up display"); + fbdev_unblank(); + lv_obj_move_background(screen_saver); + is_sleeping = false; + } + } + } +#endif // SIMULATOR/OS_ANDROID + + usleep(5000); + } +} + +std::mutex &GuppyScreen::get_lock() { + return lv_lock; +} + +void GuppyScreen::connect_ws(const std::string &url) { + ws.connect(url.c_str(), + [this]() { init_panel.connected(ws); }, + [this]() { init_panel.disconnected(ws); }); +} + +void GuppyScreen::new_theme_apply_cb(lv_theme_t *th, lv_obj_t *obj) { + LV_UNUSED(th); + + if (lv_obj_check_type(obj, &lv_obj_class)) { + lv_obj_add_style(obj, &style_container, 0); + } + + if (lv_obj_check_type(obj, &lv_imgbtn_class)) { + lv_obj_add_style(obj, &style_imgbtn_pressed, LV_STATE_PRESSED); + lv_obj_add_style(obj, &style_imgbtn_disabled, LV_STATE_DISABLED); + } +} + +void GuppyScreen::handle_calibrated(lv_event_t *event) { + spdlog::info("finished calibration"); + lv_obj_t *main_screen = (lv_obj_t *)event->user_data; + lv_disp_load_scr(main_screen); +} + +void GuppyScreen::save_calibration_coeff(lv_tc_coeff_t coeff) { + Config *conf = Config::get_instance(); + conf->set>("/touch_calibration_coeff", + {coeff.a, coeff.b, coeff.c, coeff.d, coeff.e, coeff.f}); + conf->save(); +} + +/*Set in lv_conf.h as `LV_TICK_CUSTOM_SYS_TIME_EXPR`*/ +uint32_t custom_tick_get(void) { + static uint64_t start_ms = 0; + if (start_ms == 0) { + struct timeval tv_start; + gettimeofday(&tv_start, NULL); + start_ms = (tv_start.tv_sec * 1000000 + tv_start.tv_usec) / 1000; + } + + struct timeval tv_now; + gettimeofday(&tv_now, NULL); + uint64_t now_ms; + now_ms = (tv_now.tv_sec * 1000000 + tv_now.tv_usec) / 1000; + + uint32_t time_ms = now_ms - start_ms; + return time_ms; +} diff --git a/src/guppyscreen.h b/src/guppyscreen.h new file mode 100644 index 0000000..d8f2034 --- /dev/null +++ b/src/guppyscreen.h @@ -0,0 +1,50 @@ +#ifndef __GUPPY_SCREEN_H__ +#define __GUPPY_SCREEN_H__ + +#include +#include + +#include "lv_tc.h" +#include "lv_tc_screen.h" +#include "lvgl/lvgl.h" + +#include "platform.h" +#include "init_panel.h" +#include "main_panel.h" +#include "spoolman_panel.h" +#include "websocket_client.h" + +class GuppyScreen { + private: + static GuppyScreen *instance; + static lv_style_t style_container; + static lv_style_t style_imgbtn_pressed; + static lv_style_t style_imgbtn_disabled; + static lv_theme_t th_new; +#ifndef OS_ANDROID + static lv_obj_t *screen_saver; +#endif + static std::mutex lv_lock; + + KWebSocketClient ws; + SpoolmanPanel spoolman_panel; + MainPanel main_panel; + InitPanel init_panel; + + public: + GuppyScreen(); + GuppyScreen(GuppyScreen &o) = delete; + void operator=(const GuppyScreen &) = delete; + + std::mutex &get_lock(); + + void connect_ws(const std::string &url); + static GuppyScreen *get(); + static GuppyScreen *init(std::function hal_init); + static void loop(); + static void new_theme_apply_cb(lv_theme_t *th, lv_obj_t *obj); + static void handle_calibrated(lv_event_t *event); + static void save_calibration_coeff(lv_tc_coeff_t coeff); +}; + +#endif // __GUPPY_SCREEN_H__ diff --git a/src/image_label.cpp b/src/image_label.cpp index 059cc15..bcca246 100644 --- a/src/image_label.cpp +++ b/src/image_label.cpp @@ -29,13 +29,13 @@ ImageLabel::ImageLabel(lv_obj_t *parent, lv_obj_set_size(cont, LV_PCT(width_pct), LV_PCT(height_pct)); lv_obj_set_style_border_width(cont, 2, 0); lv_obj_set_style_radius(cont, 4, 0); + lv_obj_set_style_pad_left(cont, 5, 0); + lv_obj_set_style_pad_right(cont, 5, 0); lv_img_set_src(image, img); lv_label_set_text(label, value); - auto scale = (double)lv_disp_get_physical_hor_res(NULL) / 800.0; - - lv_obj_align(image, LV_ALIGN_LEFT_MID, -30 * scale, 0); + lv_obj_align(image, LV_ALIGN_LEFT_MID, 0, 0); lv_obj_align(label, LV_ALIGN_RIGHT_MID, 0, 0); } @@ -47,7 +47,9 @@ ImageLabel::ImageLabel(lv_obj_t *parent, const char *v) : ImageLabel(parent, img, width_pct, height_pct, v) { - lv_img_set_zoom(image, img_scale); + auto wscale = lv_disp_get_physical_hor_res(NULL) / 800.0; + lv_img_set_size_mode(image, LV_IMG_SIZE_MODE_REAL); + lv_img_set_zoom(image, img_scale * wscale); } ImageLabel::ImageLabel(lv_obj_t *parent, diff --git a/src/init_panel.cpp b/src/init_panel.cpp index c5fcf15..f155c88 100644 --- a/src/init_panel.cpp +++ b/src/init_panel.cpp @@ -9,25 +9,26 @@ InitPanel::InitPanel(MainPanel &mp, BedMeshPanel &bmp, std::mutex& l) : cont(lv_obj_create(lv_scr_act())) - , label_cont(lv_obj_create(cont)) - , label(lv_label_create(label_cont)) + , label(lv_label_create(cont)) , main_panel(mp) , bedmesh_panel(bmp) , lv_lock(l) { - lv_obj_set_size(cont, LV_PCT(55), LV_PCT(30)); - lv_obj_align(cont, LV_ALIGN_TOP_MID, 0, 0); + lv_obj_set_size(cont, LV_PCT(55), LV_SIZE_CONTENT); + lv_obj_align(cont, LV_ALIGN_TOP_MID, 0, 15); lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLLABLE); - // lv_obj_set_style_bg_opa(cont, LV_OPA_70, 0); - - lv_obj_set_size(label_cont, LV_PCT(100), LV_PCT(100)); - lv_obj_set_style_border_width(label_cont, 2, 0); - lv_obj_set_style_bg_color(label_cont, lv_palette_darken(LV_PALETTE_GREY, 1), 0); + lv_obj_set_style_bg_color(cont, lv_palette_darken(LV_PALETTE_GREY, 1), 0); - lv_obj_align(label_cont, LV_ALIGN_CENTER, 0, 0); + lv_obj_set_size(label, LV_PCT(100), LV_SIZE_CONTENT); - lv_label_set_text(label, LV_SYMBOL_WARNING " Waiting for printer to initialize..."); + Config *conf = Config::get_instance(); + if (!conf->get_json("/default_printer").is_null()) { + lv_label_set_text(label, LV_SYMBOL_WARNING " Waiting for printer to initialize..."); + } else { + lv_label_set_text(label, "Welcome to Guppy Screen. Use the Setting Panel to add your printers."); + } + lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0); lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); } diff --git a/src/init_panel.h b/src/init_panel.h index 8b3c7e4..190b469 100644 --- a/src/init_panel.h +++ b/src/init_panel.h @@ -19,7 +19,6 @@ class InitPanel { private: lv_obj_t *cont; - lv_obj_t *label_cont; lv_obj_t *label; MainPanel &main_panel; BedMeshPanel &bedmesh_panel; diff --git a/src/main.cpp b/src/main.cpp index 2010917..4a3a6e3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,246 +24,30 @@ static int tick_thread(void *data); static void hal_init(void); -#include "websocket_client.h" -#include "notify_consumer.h" -#include "main_panel.h" -#include "spoolman_panel.h" -#include "init_panel.h" -#include "state.h" +#include "guppyscreen.h" #include "hv/hlog.h" -#include "hv/json.hpp" #include "config.h" -#include "bedmesh_panel.h" -#include "spdlog/spdlog.h" -#include "spdlog/sinks/rotating_file_sink.h" -#include "spdlog/sinks/stdout_sinks.h" - -#include -#include -#include -#include -#define DISP_BUF_SIZE (128 * 1024) +#include using namespace hv; -using namespace std; - - -static lv_style_t style_container; -static lv_style_t style_imgbtn_pressed; -static lv_style_t style_imgbtn_disabled; -static lv_obj_t *screen_saver; - -/*Will be called when the styles of the base theme are already added - to add new styles*/ -static void new_theme_apply_cb(lv_theme_t * th, lv_obj_t * obj) -{ - LV_UNUSED(th); - - if(lv_obj_check_type(obj, &lv_obj_class)) { - lv_obj_add_style(obj, &style_container, 0); - } - if (lv_obj_check_type(obj, &lv_imgbtn_class)) { - lv_obj_add_style(obj, &style_imgbtn_pressed, LV_STATE_PRESSED); - lv_obj_add_style(obj, &style_imgbtn_disabled, LV_STATE_DISABLED); - } -} - -static void handle_calibrated(lv_event_t *event) { - spdlog::info("finished calibration"); - lv_obj_t *main_screen = (lv_obj_t*)event->user_data; - lv_disp_load_scr(main_screen); -} - -static void save_calibration_coeff(lv_tc_coeff_t coeff) { - Config *conf = Config::get_instance(); - conf->set>("/touch_calibration_coeff", - {coeff.a, coeff.b, coeff.c, coeff.d, coeff.e, coeff.f}); - conf->save(); -} - -#ifndef SIMULATOR -std::atomic_bool is_sleeping(false); - -#endif // SIMULATOR +#define DISP_BUF_SIZE (128 * 1024) int main(void) { - hlog_disable(); // config - Config *conf = Config::get_instance(); spdlog::debug("current path {}", std::string(fs::canonical("/proc/self/exe").parent_path())); + Config *conf = Config::get_instance(); auto config_path = fs::canonical("/proc/self/exe").parent_path() / "guppyconfig.json"; - conf->init(config_path.string()); - - const std::string ll_path = conf->df() + "log_level"; - auto ll = spdlog::level::from_str(conf->get(ll_path)); - - auto console_sink = std::make_shared(); - // console_sink->set_level(spdlog::level::debug); - auto file_sink = std::make_shared( - conf->get("/log_path"), 1048576 * 10, 3); - // file_sink->set_level(spdlog::level::debug); - - spdlog::sinks_init_list log_sinks{console_sink, file_sink}; - auto klogger = std::make_shared("guppyscreen", log_sinks); - spdlog::register_logger(klogger); - - spdlog::set_level(ll); - spdlog::set_default_logger(klogger); - klogger->flush_on(ll); - -#ifdef GUPPYSCREEN_VERSION - spdlog::info("Guppy Screen Version: {}", GUPPYSCREEN_VERSION); -#endif // GUPPYSCREEN_VERSION - - spdlog::info("DPI: {}", LV_DPI_DEF); - /*LittlevGL init*/ - lv_init(); - -#ifndef SIMULATOR - /*Linux frame buffer device init*/ - fbdev_init(); - fbdev_unblank(); -#endif - - hal_init(); - lv_png_init(); - - lv_style_init(&style_container); - lv_style_set_border_width(&style_container, 0); - lv_style_set_radius(&style_container, 0); - - lv_style_init(&style_imgbtn_pressed); - lv_style_set_img_recolor_opa(&style_imgbtn_pressed, LV_OPA_100); - lv_style_set_img_recolor(&style_imgbtn_pressed, lv_palette_main(LV_PALETTE_BLUE)); - - lv_style_init(&style_imgbtn_disabled); - lv_style_set_img_recolor_opa(&style_imgbtn_disabled, LV_OPA_100); - lv_style_set_img_recolor(&style_imgbtn_disabled, lv_palette_darken(LV_PALETTE_GREY, 1)); - /*Initialize the new theme from the current theme*/ - - lv_theme_t * th_act = lv_disp_get_theme(NULL); - static lv_theme_t th_new; - th_new = *th_act; - - /*Set the parent theme and the style apply callback for the new theme*/ - lv_theme_set_parent(&th_new, th_act); - lv_theme_set_apply_cb(&th_new, new_theme_apply_cb); - - /*Assign the new theme to the current display*/ - lv_disp_set_theme(NULL, &th_new); - - mutex lv_lock; - KWebSocketClient ws(NULL); - /// preregister state to consume subscriptions - ws.register_notify_update(State::get_instance()); - - SpoolmanPanel spoolman_panel(ws, lv_lock); - MainPanel main_panel(ws, lv_lock, spoolman_panel); - main_panel.create_panel(); - - InitPanel init_panel(main_panel, - main_panel.get_tune_panel().get_bedmesh_panel(), - lv_lock); - - std::string ws_url = fmt::format("ws://{}:{}/websocket", - conf->get(conf->df() + "moonraker_host"), - conf->get(conf->df() + "moonraker_port")); - - spdlog::info("connecting to printer at {}", ws_url); - int32_t display_sleep = conf->get("/display_sleep_sec") * 1000; - - ws.connect(ws_url.c_str(), - [&init_panel, &ws]() { init_panel.connected(ws); }, - [&init_panel, &ws]() { init_panel.disconnected(ws); }); - - screen_saver = lv_obj_create(lv_scr_act()); - lv_obj_set_size(screen_saver, LV_PCT(100), LV_PCT(100)); - lv_obj_set_style_bg_opa(screen_saver, LV_OPA_100, 0); - lv_obj_move_background(screen_saver); - - lv_obj_t *main_screen = lv_disp_get_scr_act(NULL); - auto touch_calibrated = conf->get_json("/touch_calibrated"); - if (!touch_calibrated.is_null()) { - auto is_calibrated = touch_calibrated.template get(); - if (is_calibrated) { - auto calibration_coeff = conf->get_json("/touch_calibration_coeff"); - if (calibration_coeff.is_null()) { - lv_tc_register_coeff_save_cb(save_calibration_coeff); - lv_obj_t *touch_calibrate_scr = lv_tc_screen_create(); - - lv_disp_load_scr(touch_calibrate_scr); - - lv_tc_screen_start(touch_calibrate_scr); - lv_obj_add_event_cb(touch_calibrate_scr, handle_calibrated, LV_EVENT_READY, main_screen); - spdlog::info("running touch calibration"); - } else { - // load calibration data - auto c = calibration_coeff.template get>(); - lv_tc_coeff_t coeff = { true, c[0], c[1], c[2], c[3], c[4], c[5] }; - lv_tc_set_coeff(coeff, false); - spdlog::info("loaded calibration coefficients"); - } - } - } - - /*Handle LitlevGL tasks (tickless mode)*/ - while(1) { - lv_lock.lock(); - lv_timer_handler(); - lv_lock.unlock(); - -#ifndef SIMULATOR - if (display_sleep != -1) { - if (lv_disp_get_inactive_time(NULL) > display_sleep) { - if (!is_sleeping.load()) { - spdlog::debug("putting display to sleeping"); - fbdev_blank(); - lv_obj_move_foreground(screen_saver); - // spdlog::debug("screen saver foreground"); - is_sleeping = true; - } - } else { - if (is_sleeping.load()) { - spdlog::debug("waking up display"); - fbdev_unblank(); - lv_obj_move_background(screen_saver); - is_sleeping = false; - } - } - } -#endif // SIMULATOR - - usleep(5000); - } + conf->init(config_path.string(), "/usr/data/printer_data/thumbnails"); + GuppyScreen::init(hal_init); + GuppyScreen::loop(); return 0; } -/*Set in lv_conf.h as `LV_TICK_CUSTOM_SYS_TIME_EXPR`*/ -uint32_t custom_tick_get(void) -{ - static uint64_t start_ms = 0; - if(start_ms == 0) { - struct timeval tv_start; - gettimeofday(&tv_start, NULL); - start_ms = (tv_start.tv_sec * 1000000 + tv_start.tv_usec) / 1000; - } - - struct timeval tv_now; - gettimeofday(&tv_now, NULL); - uint64_t now_ms; - now_ms = (tv_now.tv_sec * 1000000 + tv_now.tv_usec) / 1000; - - uint32_t time_ms = now_ms - start_ms; - return time_ms; -} - - - #ifndef SIMULATOR static void hal_init(void) { @@ -307,6 +91,7 @@ static void hal_init(void) { evdev_init(); static lv_indev_drv_t indev_drv_1; + lv_indev_drv_init(&indev_drv_1); indev_drv_1.read_cb = evdev_read; // no calibration indev_drv_1.type = LV_INDEV_TYPE_POINTER; diff --git a/src/main_panel.cpp b/src/main_panel.cpp index 2ac503c..e24258d 100644 --- a/src/main_panel.cpp +++ b/src/main_panel.cpp @@ -212,7 +212,8 @@ void MainPanel::create_main(lv_obj_t * parent) lv_obj_clear_flag(temp_cont, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_size(temp_cont, LV_PCT(50), LV_PCT(50)); - + lv_obj_set_style_pad_all(temp_cont, 0, 0); + lv_obj_set_flex_flow(temp_cont, LV_FLEX_FLOW_ROW_WRAP); lv_obj_set_grid_cell(temp_cont, LV_GRID_ALIGN_START, 0, 2, LV_GRID_ALIGN_CENTER, 0, 2); diff --git a/src/platform.h b/src/platform.h new file mode 100644 index 0000000..f117c7b --- /dev/null +++ b/src/platform.h @@ -0,0 +1,8 @@ +#ifndef __GUPPY_PLATFORM_H__ +#define __GUPPY_PLATFORM_H__ + +#if defined(ANDROID) || defined(__ANDROID__) + #define OS_ANDROID +#endif + +#endif // __GUPPY_PLATFORM_H__ diff --git a/src/print_status_panel.cpp b/src/print_status_panel.cpp index 91c970e..fe8c508 100644 --- a/src/print_status_panel.cpp +++ b/src/print_status_panel.cpp @@ -22,7 +22,7 @@ LV_IMG_DECLARE(cancel); LV_IMG_DECLARE(emergency); LV_IMG_DECLARE(back); -constexpr double pi() { return std::atan(1)*4; } +double pi() { return std::atan(1)*4; } PrintStatusPanel::PrintStatusPanel(KWebSocketClient &websocket_client, std::mutex &lock, diff --git a/src/printer_select_panel.cpp b/src/printer_select_panel.cpp index 9689f23..2a9c13c 100644 --- a/src/printer_select_panel.cpp +++ b/src/printer_select_panel.cpp @@ -1,4 +1,5 @@ #include "printer_select_panel.h" +#include "guppyscreen.h" #include "config.h" #include "hv/json.hpp" #include "subprocess.hpp" @@ -122,7 +123,6 @@ lv_obj_t *PrinterSelectContainer::prompt(const std::string &prompt_text) { return mbox1; } - PrinterSelectPanel::PrinterSelectPanel() : cont(lv_obj_create(lv_scr_act())) , top(lv_obj_create(cont)) @@ -178,6 +178,7 @@ PrinterSelectPanel::PrinterSelectPanel() p->handle_input(e); }, LV_EVENT_ALL, this); + lv_textarea_set_text(moonraker_port, "7125"); lv_textarea_set_placeholder_text(moonraker_port, "Moonraker Port"); lv_textarea_set_one_line(moonraker_port, true); lv_textarea_set_max_length(moonraker_port, 5); @@ -227,6 +228,15 @@ PrinterSelectPanel::PrinterSelectPanel() p->add_printer(pname, ip, port); lv_obj_add_flag(p->kb, LV_OBJ_FLAG_HIDDEN); + + if (conf->get_json("/default_printer").is_null()) { + // connect to the one and only added printer + conf->set("/default_printer", pname); + conf->save(); + conf->init(conf->get("/config_path"), conf->get("/thumbnail_path")); + std::string ws_url = fmt::format("ws://{}:{}/websocket", ip, port); + GuppyScreen::get()->connect_ws(ws_url); + } } }, LV_EVENT_CLICKED, this); @@ -251,7 +261,6 @@ PrinterSelectPanel::PrinterSelectPanel() lv_obj_add_flag(back_btn.get_container(), LV_OBJ_FLAG_FLOATING); lv_obj_align(back_btn.get_container(), LV_ALIGN_BOTTOM_RIGHT, 0, -20); - } PrinterSelectPanel::~PrinterSelectPanel() { diff --git a/src/sensor_container.cpp b/src/sensor_container.cpp index 89f61bb..c43d0c2 100644 --- a/src/sensor_container.cpp +++ b/src/sensor_container.cpp @@ -40,9 +40,12 @@ SensorContainer::SensorContainer(KWebSocketClient &c, // cont_height = cont_height > 60 ? 60 : cont_height; auto width_scale = (double)lv_disp_get_physical_hor_res(NULL) / 800.0; - lv_obj_set_size(sensor_cont, 330 * width_scale, 60 * width_scale); + auto height_scale = (double)lv_disp_get_physical_ver_res(NULL) / 480.0; + lv_obj_set_size(sensor_cont, 330 * width_scale, 60 * height_scale); + lv_obj_set_style_pad_all(sensor_cont, 0, 0); + lv_img_set_src(sensor_img, img); - lv_obj_align(sensor_img, LV_ALIGN_LEFT_MID, -25 * width_scale, 0); + lv_obj_align(sensor_img, LV_ALIGN_LEFT_MID, 0, 0); lv_label_set_text(sensor_label, text); lv_obj_align_to(sensor_label, sensor_img, LV_ALIGN_OUT_RIGHT_MID, -7 * width_scale, 0); diff --git a/src/setting_panel.cpp b/src/setting_panel.cpp index 8a6384d..da53c49 100644 --- a/src/setting_panel.cpp +++ b/src/setting_panel.cpp @@ -24,7 +24,9 @@ LV_IMG_DECLARE(print); SettingPanel::SettingPanel(KWebSocketClient &c, std::mutex &l, lv_obj_t *parent, SpoolmanPanel &sm) : ws(c) , cont(lv_obj_create(parent)) +#ifndef OS_ANDROID , wifi_panel(l) +#endif , sysinfo_panel() , spoolman_panel(sm) , wifi_btn(cont, &network_img, "WIFI", &SettingPanel::_handle_callback, this) @@ -44,6 +46,9 @@ SettingPanel::SettingPanel(KWebSocketClient &c, std::mutex &l, lv_obj_t *parent, lv_obj_set_size(cont, LV_PCT(100), LV_PCT(100)); spoolman_btn.disable(); +#ifdef OS_ANDROID + wifi_btn.disable(); +#endif static lv_coord_t grid_main_row_dsc[] = {LV_GRID_FR(2), LV_GRID_FR(5), LV_GRID_FR(5), LV_GRID_TEMPLATE_LAST}; static lv_coord_t grid_main_col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1), @@ -82,7 +87,9 @@ void SettingPanel::handle_callback(lv_event_t *event) { if (btn == wifi_btn.get_container()) { spdlog::trace("wifi pressed"); +#ifndef OS_ANDROID wifi_panel.foreground(); +#endif } else if (btn == sysinfo_btn.get_container()) { spdlog::trace("setting system info pressed"); sysinfo_panel.foreground(); diff --git a/src/setting_panel.h b/src/setting_panel.h index f31e189..ed58186 100644 --- a/src/setting_panel.h +++ b/src/setting_panel.h @@ -1,7 +1,12 @@ #ifndef __SETTING_PANEL_H__ #define __SETTING_PANEL_H__ +#include "platform.h" + +#ifndef OS_ANDROID #include "wifi_panel.h" +#endif + #include "sysinfo_panel.h" #include "spoolman_panel.h" #include "printer_select_panel.h" @@ -29,7 +34,11 @@ class SettingPanel { private: KWebSocketClient &ws; lv_obj_t *cont; + +#ifndef OS_ANDROID WifiPanel wifi_panel; +#endif + SysInfoPanel sysinfo_panel; SpoolmanPanel &spoolman_panel; PrinterSelectPanel printer_select_panel; diff --git a/src/sysinfo_panel.cpp b/src/sysinfo_panel.cpp index ca4b820..f4f8774 100644 --- a/src/sysinfo_panel.cpp +++ b/src/sysinfo_panel.cpp @@ -114,7 +114,9 @@ SysInfoPanel::SysInfoPanel() lv_dropdown_set_options(loglevel_dd, fmt::format("{}", fmt::join(log_levels, "\n")).c_str()); - v = conf->get_json(conf->df() + "log_level"); + auto df = conf->get_json("/default_printer"); + json j_null; + v = !df.empty() ? conf->get_json(conf->df() + "log_level") : j_null; if (!v.is_null()) { auto it = std::find(log_levels.begin(), log_levels.end(), v.template get()); if (it != std::end(log_levels)) { diff --git a/src/utils.cpp b/src/utils.cpp index 2d336f2..4dffb7f 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -3,6 +3,7 @@ #include "config.h" #include "state.h" #include "spdlog/spdlog.h" +#include "platform.h" #include #include @@ -144,10 +145,10 @@ namespace KUtils { } std::vector get_interfaces() { + std::vector ifaces; +#ifndef OS_ANDROID struct ifaddrs *addrs; getifaddrs(&addrs); - - std::vector ifaces; for (struct ifaddrs *addr = addrs; addr != nullptr; addr = addr->ifa_next) { if (addr->ifa_addr && addr->ifa_addr->sa_family == AF_PACKET) { ifaces.push_back(addr->ifa_name); @@ -155,6 +156,7 @@ namespace KUtils { } freeifaddrs(addrs); +#endif // OS_ANDROID return ifaces; } @@ -244,50 +246,47 @@ namespace KUtils { } std::map> parse_macros(json &m) { - std::map> macros; - + std::regex param_regex(R"(params\.(\w+)(.*))", std::regex_constants::icase); - std::regex default_value_regex(R"(\|\s*default\s*\(\s*((["'])(?:\\.|(?!\2)[^])*\2|-?[0-9][^,)]*))", - std::regex_constants::icase); + std::regex default_value_regex(R"(\|\s*default\s*\(\s*((["'])(?:\\.|[^\x02])*\2|-?[0-9][^,)]*))", + std::regex_constants::icase); for (auto &el : m.items()) { std::string key = el.key(); if (key.rfind("gcode_macro ", 0) == 0) { - auto &gcode = el.value()["/gcode"_json_pointer]; - if (!gcode.is_null()) { - - auto macro_split = split(el.key(), ' '); - if (macro_split.size() > 1 && macro_split[1].rfind("_", 0) != 0) { - std::string macro_name = macro_split[1]; - - const auto &gcode_str = gcode.template get(); - auto param_begin = - std::sregex_iterator(gcode_str.begin(), gcode_str.end(), param_regex); - auto param_end = std::sregex_iterator(); - - std::map macro_params; - for (std::sregex_iterator i = param_begin; i != param_end; ++i) { - std::smatch match = *i; - std::string param_name = match.str(1); - std::string rest = match.str(2); - std::smatch matches; - std::string default_value = ""; - - spdlog::trace("macro: {}, param; {}, rest: {}", macro_name, param_name, rest); - - if (std::regex_search(rest, matches, default_value_regex)) { - default_value = matches.str(1); - } - - macro_params.insert({param_name, default_value}); - } - macros.insert({macro_name, macro_params}); - } - } + auto &gcode = el.value()["/gcode"_json_pointer]; + if (!gcode.is_null()) { + auto macro_split = split(el.key(), ' '); + if (macro_split.size() > 1 && macro_split[1].rfind("_", 0) != 0) { + std::string macro_name = macro_split[1]; + + const auto &gcode_str = gcode.template get(); + auto param_begin = + std::sregex_iterator(gcode_str.begin(), gcode_str.end(), param_regex); + auto param_end = std::sregex_iterator(); + + std::map macro_params; + for (std::sregex_iterator i = param_begin; i != param_end; ++i) { + std::smatch match = *i; + std::string param_name = match.str(1); + std::string rest = match.str(2); + std::smatch matches; + std::string default_value = ""; + + spdlog::trace("macro: {}, param; {}, rest: {}", macro_name, param_name, rest); + + if (std::regex_search(rest, matches, default_value_regex)) { + default_value = matches.str(1); + } + + macro_params.insert({param_name, default_value}); + } + macros.insert({macro_name, macro_params}); + } + } } } return macros; - } -} + } // namespace KUtils diff --git a/src/websocket_client.cpp b/src/websocket_client.cpp index a9a9379..8e3585b 100644 --- a/src/websocket_client.cpp +++ b/src/websocket_client.cpp @@ -18,7 +18,7 @@ using namespace hv; using json = nlohmann::json; -KWebSocketClient::KWebSocketClient(EventLoopPtr loop = NULL) +KWebSocketClient::KWebSocketClient(EventLoopPtr loop) : WebSocketClient(loop) , id(0) {