diff --git a/apps/hardware_test/Makefile b/apps/hardware_test/Makefile index 57f8c7d19..ba445bc73 100644 --- a/apps/hardware_test/Makefile +++ b/apps/hardware_test/Makefile @@ -1,12 +1,15 @@ app_src += $(addprefix apps/hardware_test/,\ app.cpp \ arrow_view.cpp \ + battery_test_controller.cpp \ centered_screen_test_controller.cpp \ code_128b_view.cpp \ dead_pixels_test_controller.cpp \ keyboard_test_controller.cpp \ keyboard_view.cpp \ + lcd_data_test_controller.cpp \ led_test_controller.cpp \ pop_up_controller.cpp \ serial_number_controller.cpp \ + vblank_test_controller.cpp \ ) diff --git a/apps/hardware_test/app.cpp b/apps/hardware_test/app.cpp index d32fb0b1a..86f4dc10c 100644 --- a/apps/hardware_test/app.cpp +++ b/apps/hardware_test/app.cpp @@ -20,11 +20,14 @@ App::App(Container * container, Snapshot * snapshot) : App::WizardViewController::WizardViewController(Responder * parentResponder) : BankViewController(parentResponder), + m_batteryTestController(this), m_centeredScreenTestController(this), m_deadPixelsTestController(this), m_keyboardController(this), + m_lcdDataTestController(this), m_ledTestController(this), - m_serialNumberController(this) + m_serialNumberController(this), + m_vBlankTestController(this) { } @@ -34,10 +37,13 @@ int App::WizardViewController::numberOfChildren() { ViewController * App::WizardViewController::childAtIndex(int i) { ViewController * children[] = { + &m_vBlankTestController, + &m_lcdDataTestController, &m_centeredScreenTestController, &m_deadPixelsTestController, &m_ledTestController, &m_keyboardController, + &m_batteryTestController, &m_serialNumberController }; return children[i]; diff --git a/apps/hardware_test/app.h b/apps/hardware_test/app.h index e4d51fde4..628d75cd1 100644 --- a/apps/hardware_test/app.h +++ b/apps/hardware_test/app.h @@ -2,11 +2,14 @@ #define HARDWARE_TEST_APP_H #include +#include "battery_test_controller.h" #include "centered_screen_test_controller.h" #include "dead_pixels_test_controller.h" #include "keyboard_test_controller.h" +#include "lcd_data_test_controller.h" #include "led_test_controller.h" #include "serial_number_controller.h" +#include "vblank_test_controller.h" class AppsContainer; @@ -27,11 +30,14 @@ private: ViewController * childAtIndex(int i) override; bool handleEvent(Ion::Events::Event event) override; private: + BatteryTestController m_batteryTestController; CenteredScreenTestController m_centeredScreenTestController; DeadPixelsTestController m_deadPixelsTestController; KeyboardTestController m_keyboardController; + LCDDataTestController m_lcdDataTestController; LEDTestController m_ledTestController; SerialNumberController m_serialNumberController; + VBlankTestController m_vBlankTestController; }; App(Container * container, Snapshot * snapshot); diff --git a/apps/hardware_test/battery_test_controller.cpp b/apps/hardware_test/battery_test_controller.cpp new file mode 100644 index 000000000..51a2badaf --- /dev/null +++ b/apps/hardware_test/battery_test_controller.cpp @@ -0,0 +1,114 @@ +#include "battery_test_controller.h" +#include "../constant.h" +#include "app.h" +extern "C" { +#include +} +#include +#include + +using namespace Poincare; + +namespace HardwareTest { + +BatteryTestController::BatteryTestController(Responder * parentResponder) : + ViewController(parentResponder), + m_view() +{ +} + +View * BatteryTestController::view() { + return &m_view; +} + +bool BatteryTestController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::OK) { + if (strcmp(m_view.batteryStateTextView()->text(), k_batteryOKText) == 0) { + // Handled in WizardViewController + return false; + } + } + updateBatteryState(Ion::Battery::voltage(), Ion::Battery::isCharging()); + return true; +} + +void BatteryTestController::viewWillAppear() { + const char * text = Ion::Battery::level() != Ion::Battery::Charge::FULL ? k_batteryNeedChargingText : k_batteryOKText; + KDColor color = Ion::Battery::level() != Ion::Battery::Charge::FULL ? KDColorRed : KDColorGreen; + m_view.setColor(color); + m_view.batteryStateTextView()->setText(text); + updateBatteryState(Ion::Battery::voltage(), Ion::Battery::isCharging()); +} + +void BatteryTestController::updateBatteryState(float batteryLevel, bool batteryCharging) { + constexpr size_t bufferLevelSize = ContentView::k_maxNumberOfCharacters + PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits); + char bufferLevel[bufferLevelSize]; + const char * legend = "Battery level: "; + int legendLength = strlcpy(bufferLevel, legend, bufferLevelSize); + PrintFloat::convertFloatToText(batteryLevel, bufferLevel+legendLength, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits, Preferences::PrintFloatMode::Decimal); + m_view.batteryLevelTextView()->setText(bufferLevel); + + constexpr size_t bufferChargingSize = ContentView::k_maxNumberOfCharacters + PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits); + char bufferCharging[bufferChargingSize]; + int numberOfChars = 0; + legend = "Battery charging: "; + numberOfChars += strlcpy(bufferCharging, legend, bufferChargingSize); + legend = "no"; + if (batteryCharging) { + legend = "yes"; + } + numberOfChars += strlcpy(bufferCharging+numberOfChars, legend, bufferChargingSize); + bufferCharging[numberOfChars] = 0; + m_view.batteryChargingTextView()->setText(bufferCharging); +} + +BatteryTestController::ContentView::ContentView() : + SolidColorView(KDColorWhite), + m_batteryStateView(KDFont::LargeFont), + m_batteryLevelView(KDFont::SmallFont), + m_batteryChargingView(KDFont::SmallFont) +{ +} + +BufferTextView * BatteryTestController::ContentView::batteryStateTextView() { + return &m_batteryStateView; +} + +BufferTextView * BatteryTestController::ContentView::batteryLevelTextView() { + return &m_batteryLevelView; +} + +BufferTextView * BatteryTestController::ContentView::batteryChargingTextView() { + return &m_batteryChargingView; +} + +void BatteryTestController::ContentView::setColor(KDColor color) { + SolidColorView::setColor(color); + m_batteryStateView.setBackgroundColor(color); + m_batteryLevelView.setBackgroundColor(color); + m_batteryChargingView.setBackgroundColor(color); +} + +void BatteryTestController::ContentView::layoutSubviews() { + m_batteryStateView.setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height/2)); + KDSize textSize = KDFont::SmallFont->glyphSize(); + m_batteryLevelView.setFrame(KDRect(0, Ion::Display::Height-2*textSize.height(), Ion::Display::Width, textSize.height())); + m_batteryChargingView.setFrame(KDRect(0, Ion::Display::Height-textSize.height(), Ion::Display::Width, textSize.height())); +} + +int BatteryTestController::ContentView::numberOfSubviews() const { + return 3; +} + +View * BatteryTestController::ContentView::subviewAtIndex(int index) { + if (index == 0) { + return &m_batteryStateView; + } + if (index == 1) { + return &m_batteryLevelView; + } + return &m_batteryChargingView; +} + +} + diff --git a/apps/hardware_test/battery_test_controller.h b/apps/hardware_test/battery_test_controller.h new file mode 100644 index 000000000..723155abb --- /dev/null +++ b/apps/hardware_test/battery_test_controller.h @@ -0,0 +1,41 @@ +#ifndef HARDWARE_TEST_BATTERY_TEST_CONTROLLER_H +#define HARDWARE_TEST_BATTERY_TEST_CONTROLLER_H + +#include + +namespace HardwareTest { + +class BatteryTestController : public ViewController { +public: + BatteryTestController(Responder * parentResponder); + View * view() override; + bool handleEvent(Ion::Events::Event event) override; + void viewWillAppear() override; +private: + class ContentView : public SolidColorView { + public: + ContentView(); + BufferTextView * batteryStateTextView(); + BufferTextView * batteryLevelTextView(); + BufferTextView * batteryChargingTextView(); + constexpr static int k_maxNumberOfCharacters = 20; + void setColor(KDColor color) override; + private: + void layoutSubviews() override; + int numberOfSubviews() const override; + View * subviewAtIndex(int index) override; + constexpr static int k_margin = 4; + BufferTextView m_batteryStateView; + BufferTextView m_batteryLevelView; + BufferTextView m_batteryChargingView; + }; + constexpr static const char * k_batteryOKText = "BATTERY: OK"; + constexpr static const char * k_batteryNeedChargingText = "BATTERY: NEED RECHARGE"; + void updateBatteryState(float batteryLevel, bool batteryCharging); + ContentView m_view; +}; + +} + +#endif + diff --git a/apps/hardware_test/lcd_data_test_controller.cpp b/apps/hardware_test/lcd_data_test_controller.cpp new file mode 100644 index 000000000..2474bec79 --- /dev/null +++ b/apps/hardware_test/lcd_data_test_controller.cpp @@ -0,0 +1,129 @@ +#include "lcd_data_test_controller.h" +#include + +using namespace Poincare; + +namespace HardwareTest { + +bool LCDDataTestController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::OK && strcmp(m_view.lcdDataStateTextView()->text(), k_lcdDataOKText) == 0) { + // Handled in WizardViewController + return false; + } + return true; +} + +void LCDDataTestController::viewWillAppear() { + bool testOK = test(); + m_view.lcdDataStateTextView()->setText(testOK ? k_lcdDataOKText : k_lcdDataFailTest); + m_view.setColor(testOK ? KDColorGreen : KDColorRed); +} + +LCDDataTestController::ContentView::ContentView() : + SolidColorView(KDColorWhite), + m_lcdDataStateView(KDFont::LargeFont) +{ +} + +void LCDDataTestController::ContentView::setColor(KDColor color) { + SolidColorView::setColor(color); + m_lcdDataStateView.setBackgroundColor(color); +} + +void LCDDataTestController::ContentView::layoutSubviews() { + m_lcdDataStateView.setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height)); +} + +void colorPixelBuffer(KDColor * pixels, int numberOfPixels, KDColor c) { + for (int i = 0; i < numberOfPixels; i++) { + pixels[i] = c; + } +} + +bool LCDDataTestController::test() { + KDColor testColors[] = { + KDColorRed, KDColorGreen, KDColorBlue, + KDColor::RGB24(0xFFFF00), KDColor::RGB24(0xFF00FF), KDColor::RGB24(0x00FFFF), + KDColorWhite, KDColorBlack}; + for (KDColor c : testColors) { + if (!testDisplayColor(c)) { + return false; + } + } + return testDisplayBlackWhite(); +} + +bool LCDDataTestController::testDisplayColor(KDColor c) { + constexpr int stampHeight = 10; + constexpr int stampWidth = 10; + static_assert(Ion::Display::Width % stampWidth == 0, "Stamps must tesselate the display"); + static_assert(Ion::Display::Height % stampHeight == 0, "Stamps must tesselate the display"); + static_assert(stampHeight % 2 == 0 || stampWidth % 2 == 0, "Even number of XOR needed."); + + KDColor stamp[stampWidth*stampHeight]; + + // Tiling test with pushRect + colorPixelBuffer(stamp, stampWidth * stampHeight, c); + for (int i = 0; i < Ion::Display::Width / stampWidth; i++) { + for (int j = 0; j < Ion::Display::Height / stampHeight; j++) { + Ion::Display::pushRect(KDRect(i * stampWidth, j * stampHeight, stampWidth, stampHeight), stamp); + } + } + if (numberOfNonColoredPixels(c) > invalidPixelsLimit) { + return false; + } + + // Test with pushRectUniform + Ion::Display::pushRectUniform(KDRect(KDPointZero, Ion::Display::Width, Ion::Display::Height), c); + if (numberOfNonColoredPixels(c) > invalidPixelsLimit) { + return false; + } + return true; +} + +int LCDDataTestController::numberOfNonColoredPixels(KDColor wantedColor) { + constexpr int stampHeight = 10; + constexpr int stampWidth = 10; + static_assert(Ion::Display::Width % stampWidth == 0, "Stamps must tesselate the display"); + static_assert(Ion::Display::Height % stampHeight == 0, "Stamps must tesselate the display"); + static_assert(stampHeight % 2 == 0 || stampWidth % 2 == 0, "Even number of XOR needed."); + + KDColor stamp[stampWidth*stampHeight]; + + int numberOfInvalidPixels = 0; + for (int i = 0; i < Ion::Display::Width / stampWidth; i++) { + for (int j = 0; j < Ion::Display::Height / stampHeight; j++) { + colorPixelBuffer(stamp, stampWidth * stampHeight, wantedColor == KDColorBlack ? KDColorRed : KDColorBlack); + Ion::Display::pullRect(KDRect(i * stampWidth, j * stampHeight, stampWidth, stampHeight), stamp); + for (int k = 0; k < stampWidth * stampHeight; k++) { + if (stamp[k] != wantedColor) { + numberOfInvalidPixels++; + } + } + } + } + return numberOfInvalidPixels; +} + +bool LCDDataTestController::testDisplayBlackWhite() { + Ion::Display::POSTPushBlackWhite(); + constexpr int stampHeight = Ion::Display::Height; + constexpr int stampWidth = 2; + static_assert(Ion::Display::Width % stampWidth == 0, "Stamps must tesselate the display"); + static_assert(Ion::Display::Height % stampHeight == 0, "Stamps must tesselate the display"); + static_assert(stampHeight % 2 == 0 || stampWidth % 2 == 0, "Even number of XOR needed."); + KDColor stamp[stampWidth*stampHeight]; + int numberOfInvalidPixels = 0; + for (int i = 0; i < Ion::Display::Width/stampWidth; i++) { + colorPixelBuffer(stamp, stampWidth * stampHeight, KDColorRed); + Ion::Display::pullRect(KDRect(i*stampWidth, 0, stampWidth, stampHeight), stamp); + for (int k = 0; k < stampWidth * stampHeight; k++) { + if (stamp[k] != ((k%2 == 0) ? KDColorWhite : KDColorBlack)) { + numberOfInvalidPixels++; + } + } + } + return numberOfInvalidPixels <= invalidPixelsLimit; +} + +} diff --git a/apps/hardware_test/lcd_data_test_controller.h b/apps/hardware_test/lcd_data_test_controller.h new file mode 100644 index 000000000..4f662c021 --- /dev/null +++ b/apps/hardware_test/lcd_data_test_controller.h @@ -0,0 +1,59 @@ +#ifndef LCD_DATA_TEST_CONTROLLER_H +#define LCD_DATA_TEST_CONTROLLER_H + +#include +#include + +namespace HardwareTest { + +class LCDDataTestController : public ViewController { + +/* There are three types of tests, where a pattern is pushed to the screen and + * the number of invalid pixels then counted. + * - Test 1: Tile the screen with color patches. Tiling increases the number + * of border mistakes. + * - Test 2: Push one color to the whole screen in one step. It shows errors + * that appear on large and fast pushes. + * - Test 3: Color the screen by alterning one pixel black and one pixel + * white (maximal data difference), at maximal data writing speed. + * Tests 1 and 2 are done for a few different colors. */ + +public: + LCDDataTestController(Responder * parentResponder) : + ViewController(parentResponder), + m_view() + {} + View * view() override { return &m_view; } + bool handleEvent(Ion::Events::Event event) override; + void viewWillAppear() override; +private: + class ContentView : public SolidColorView { + public: + ContentView(); + BufferTextView * lcdDataStateTextView() { return &m_lcdDataStateView; } + void setColor(KDColor color) override; + private: + void layoutSubviews() override; + int numberOfSubviews() const override { return 1; } + View * subviewAtIndex(int index) override { + assert(index == 0); + return &m_lcdDataStateView; + } + BufferTextView m_lcdDataStateView; + }; + constexpr static const char * k_lcdDataOKText = "LCD DATA: OK"; + constexpr static const char * k_lcdDataFailTest = "LCD DATA: FAIL"; + constexpr static int invalidPixelsLimit = 2; + + bool test(); + bool testDisplayColor(KDColor c); + int numberOfNonColoredPixels(KDColor wantedColor); + bool testDisplayBlackWhite(); + + ContentView m_view; +}; + +} + +#endif + diff --git a/apps/hardware_test/vblank_test_controller.cpp b/apps/hardware_test/vblank_test_controller.cpp new file mode 100644 index 000000000..140c495b6 --- /dev/null +++ b/apps/hardware_test/vblank_test_controller.cpp @@ -0,0 +1,50 @@ +#include "vblank_test_controller.h" +#include "../apps_container.h" + +using namespace Poincare; + +namespace HardwareTest { + +bool VBlankTestController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::OK) { + if (strcmp(m_view.vBlankStateTextView()->text(), k_vBlankOKText) == 0) { + // Handled in WizardViewController + return false; + } + assert(strcmp(m_view.vBlankStateTextView()->text(), k_vBlankLaunchTest) == 0); + m_view.setColor(KDColorRed); + m_view.vBlankStateTextView()->setText(k_vBlankFailTest); + static_cast(const_cast(app()->container()))->redrawWindow(); + /* We redraw the window with "VBLANK FAIL" before lauching the test. This + * test might end up in an infinite loop, in which case "VBLANK fail" keeps + * being displayed. If the test succeeds, the screen should change very + * quickly to "VBLANK OK". */ + for (int i=0; i<6; i++) { + Ion::Display::waitForVBlank(); + } + m_view.setColor(KDColorGreen); + m_view.vBlankStateTextView()->setText(k_vBlankOKText); + } + return true; +} + +void VBlankTestController::viewWillAppear() { + m_view.vBlankStateTextView()->setText(k_vBlankLaunchTest); +} + +VBlankTestController::ContentView::ContentView() : + SolidColorView(KDColorWhite), + m_vBlankStateView(KDFont::LargeFont) +{ +} + +void VBlankTestController::ContentView::setColor(KDColor color) { + SolidColorView::setColor(color); + m_vBlankStateView.setBackgroundColor(color); +} + +void VBlankTestController::ContentView::layoutSubviews() { + m_vBlankStateView.setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height)); +} + +} diff --git a/apps/hardware_test/vblank_test_controller.h b/apps/hardware_test/vblank_test_controller.h new file mode 100644 index 000000000..b52fdc95b --- /dev/null +++ b/apps/hardware_test/vblank_test_controller.h @@ -0,0 +1,42 @@ +#ifndef HARDWARE_TEST_VBLANK_TEST_CONTROLLER_H +#define HARDWARE_TEST_VBLANK_TEST_CONTROLLER_H + +#include +#include + +namespace HardwareTest { + +class VBlankTestController : public ViewController { +public: + VBlankTestController(Responder * parentResponder) : + ViewController(parentResponder), + m_view() + {} + View * view() override { return &m_view; } + bool handleEvent(Ion::Events::Event event) override; + void viewWillAppear() override; +private: + class ContentView : public SolidColorView { + public: + ContentView(); + BufferTextView * vBlankStateTextView() { return &m_vBlankStateView; } + void setColor(KDColor color) override; + private: + void layoutSubviews() override; + int numberOfSubviews() const override { return 1; } + View * subviewAtIndex(int index) override { + assert(index == 0); + return &m_vBlankStateView; + } + BufferTextView m_vBlankStateView; + }; + constexpr static const char * k_vBlankOKText = "VBLANK: OK"; + constexpr static const char * k_vBlankFailTest = "VBLANK: FAIL"; + constexpr static const char * k_vBlankLaunchTest = "Launch VBLANK test"; + ContentView m_view; +}; + +} + +#endif +