├── M5BT.ino ├── M5Menu.ino ├── M5Watch.ino ├── README.md ├── library ├── M5WatchFonts.h ├── M5WatchIcos.h ├── M5WatchLcd.cpp ├── M5WatchLcd.h ├── Menu.cpp └── Menu.h └── mobile_app ├── M5Watch.aia └── M5Watch.apk /M5BT.ino: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | BLEServer *pServer = NULL; 5 | BLECharacteristic * pTxCharacteristic; 6 | bool deviceConnected = false; 7 | bool oldDeviceConnected = false; 8 | uint8_t txValue = 0; 9 | 10 | #define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID 11 | #define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" 12 | #define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" // 0x2A3D 13 | 14 | class MyServerCallbacks: public BLEServerCallbacks { 15 | void onConnect(BLEServer* pServer) { 16 | Serial.println("BT connected"); 17 | deviceConnected = true; 18 | watch.setBTstate(true); 19 | 20 | watch.sendBTstate(); 21 | }; 22 | 23 | void onDisconnect(BLEServer* pServer) { 24 | Serial.println("BT disconnected"); 25 | deviceConnected = false; 26 | watch.setBTstate(false); 27 | } 28 | }; 29 | 30 | class MyCallbacks: public BLECharacteristicCallbacks { 31 | void onWrite(BLECharacteristic *pCharacteristic) { 32 | std::string rxValue = pCharacteristic->getValue(); 33 | char *cstr = new char[rxValue.length() + 1]; 34 | strcpy(cstr, rxValue.c_str()); 35 | 36 | watch.checkBTdata(cstr); 37 | } 38 | }; 39 | 40 | void M5BLEinit() { 41 | BLEDevice::init("HappyWatch"); 42 | 43 | pServer = BLEDevice::createServer(); 44 | pServer->setCallbacks(new MyServerCallbacks()); 45 | 46 | // Create the BLE Service 47 | BLEService *pService = pServer->createService(SERVICE_UUID); 48 | 49 | 50 | pTxCharacteristic = pService->createCharacteristic( 51 | CHARACTERISTIC_UUID_TX, 52 | BLECharacteristic::PROPERTY_NOTIFY | 53 | BLECharacteristic::PROPERTY_READ 54 | ); 55 | 56 | pTxCharacteristic->addDescriptor(new BLE2902()); 57 | 58 | BLECharacteristic *pRxCharacteristic = pService->createCharacteristic( 59 | CHARACTERISTIC_UUID_RX, 60 | BLECharacteristic::PROPERTY_WRITE 61 | ); 62 | 63 | pRxCharacteristic->setCallbacks(new MyCallbacks()); 64 | 65 | pService->start(); 66 | pServer->getAdvertising()->start(); 67 | } 68 | 69 | void M5BLEloop() { 70 | if (deviceConnected && (watch.BLEdata != "")) { 71 | pTxCharacteristic->setValue(watch.BLEdata); 72 | pTxCharacteristic->notify(); 73 | 74 | delay(50); // bluetooth stack will go into congestion, if too many packets are sent 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /M5Menu.ino: -------------------------------------------------------------------------------- 1 | Menu::ITEM menu_main_0; // brightness 2 | Menu::ITEM menu_main_1; // alarms 3 | Menu::ITEM menu_br_0; // brightness + 4 | Menu::ITEM menu_br_1; // brightness - 5 | Menu::ITEM menu_al_0; // alarm 1 6 | Menu::ITEM menu_al_1; // alarm 2 7 | Menu::ITEM menu_al_2; // alarm 3 8 | 9 | int menuRow = 0; 10 | int maxMenuRow = 3; 11 | int brightness = 8; 12 | 13 | 14 | bool changeBrightnessDown() { 15 | if (brightness > 7) { 16 | brightness--; 17 | } 18 | watch.setBrightness(brightness); 19 | } 20 | 21 | bool changeBrightnessUp() { 22 | if (brightness < 16) { 23 | brightness++; 24 | } 25 | watch.setBrightness(brightness); 26 | } 27 | 28 | bool setAlarm_0() { 29 | watch.setAlarmState(0); 30 | watch.setAlarmMenu(alarm_0, alarm_1, alarm_2); 31 | } 32 | bool setAlarm_1() { 33 | watch.setAlarmState(1); 34 | watch.setAlarmMenu(alarm_0, alarm_1, alarm_2); 35 | } 36 | bool setAlarm_2() { 37 | watch.setAlarmState(2); 38 | watch.setAlarmMenu(alarm_0, alarm_1, alarm_2); 39 | } 40 | 41 | void menuPreRender() { 42 | M5.Lcd.fillRect(0, 20, 160, 60, BLACK); 43 | } 44 | 45 | void renderMenuTitle(Menu::ITEM * item) { 46 | menuRow = 0; // first row 47 | } 48 | 49 | void renderMenuItem(Menu::ITEM * item, int activeUid, bool markActive, bool inlineValue) { 50 | int yMenu = 14 * menuRow + 22; // 14 pixels on 1 row, 22 pixels for the top menu 51 | String radek = ""; 52 | if (markActive) { 53 | if (item->uid == activeUid) { 54 | radek += "> "; 55 | } else { 56 | radek += " "; 57 | } 58 | } 59 | 60 | // show item title 61 | radek += item->text; 62 | 63 | if (item->disabled == true) { 64 | radek += " (disabled) "; 65 | } 66 | 67 | // show item expand symbol 68 | if (item->child != NULL ) { 69 | // radek += " + "; 70 | } 71 | 72 | if (inlineValue && item->value_cb != NULL) { 73 | (item->value_cb)(true); 74 | } 75 | 76 | M5.Lcd.setTextSize(1); 77 | M5.Lcd.setCursor(2, yMenu); 78 | M5.Lcd.print(radek); 79 | menuRow++; 80 | } 81 | 82 | void menuExit() { 83 | showSettings = false; 84 | watch.menu(false); 85 | watch.setBluetooth(false); 86 | watch.redraw(); 87 | } 88 | 89 | 90 | void M5MenuInit() { 91 | m.renderTitle = &renderMenuTitle; 92 | m.renderItem = &renderMenuItem; 93 | m.menuExit = &menuExit; 94 | m.preRender = &menuPreRender; 95 | 96 | m.addItem(&menu_main_0, NULL, "Brightness", NULL, NULL, NULL); 97 | m.addItem(&menu_main_1, NULL, "Alarms", NULL, NULL, NULL); 98 | m.addItem(&menu_br_0, NULL, "+", &menu_main_0, &changeBrightnessUp, NULL); 99 | m.addItem(&menu_br_1, NULL, "-", &menu_main_0, &changeBrightnessDown, NULL); 100 | m.addItem(&menu_al_0, NULL, alarm_0, &menu_main_1, &setAlarm_0, NULL); 101 | m.addItem(&menu_al_1, NULL, alarm_1, &menu_main_1, &setAlarm_1, NULL); 102 | m.addItem(&menu_al_2, NULL, alarm_2, &menu_main_1, &setAlarm_2, NULL); 103 | 104 | m.init(&menu_main_0); 105 | } 106 | -------------------------------------------------------------------------------- /M5Watch.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | char * alarm_0;// = "00:00"; 11 | char * alarm_1;// = "00:00"; 12 | char * alarm_2;// = "00:00"; 13 | 14 | M5WatchLcd watch; 15 | unsigned long powerUpTime = millis(); 16 | Menu m = Menu(); 17 | 18 | bool showSettings = false; 19 | bool showMenuAfterWakeup = false; 20 | bool BUTTON_HOME_CLICK = false; 21 | 22 | bool shaking = false; 23 | bool stopShaking = false; 24 | 25 | bool alarmDisabled = false; 26 | 27 | float accX = 0; 28 | float accY = 0; 29 | float accZ = 0; 30 | 31 | 32 | 33 | void setup() { 34 | Serial.begin(115200); 35 | bool buttonWakeUp = false; 36 | alarm_0 = (char*)malloc(7); 37 | alarm_1 = (char*)malloc(7); 38 | alarm_2 = (char*)malloc(7); 39 | 40 | esp_sleep_wakeup_cause_t wakeup_reason; 41 | wakeup_reason = esp_sleep_get_wakeup_cause(); 42 | switch(wakeup_reason){ 43 | case ESP_SLEEP_WAKEUP_EXT0 : showMenuAfterWakeup = true; buttonWakeUp = true; 44 | case ESP_SLEEP_WAKEUP_EXT1 : 45 | case ESP_SLEEP_WAKEUP_TIMER : 46 | case ESP_SLEEP_WAKEUP_TOUCHPAD : 47 | case ESP_SLEEP_WAKEUP_ULP : 48 | case ESP_SLEEP_WAKEUP_GPIO : 49 | case ESP_SLEEP_WAKEUP_UART : M5.begin(true, false, true); break; 50 | default : buttonWakeUp = true; M5.begin(); break; 51 | } 52 | 53 | M5.Axp.EnableCoulombcounter(); 54 | 55 | pinMode(M5_BUTTON_HOME,INPUT_PULLUP); 56 | pinMode(M5_BUTTON_RST, INPUT); 57 | pinMode(M5_LED, OUTPUT); 58 | digitalWrite(M5_LED, HIGH); 59 | M5.MPU6886.Init(); // for the 'third button' 60 | Serial.println("START"); 61 | watch.init(powerUpTime, buttonWakeUp); 62 | watch.setAlarmMenu(alarm_0, alarm_1, alarm_2); 63 | M5BLEinit(); 64 | M5MenuInit(); 65 | } 66 | 67 | void loop() { 68 | if(digitalRead(M5_BUTTON_HOME) == LOW){ 69 | while(digitalRead(M5_BUTTON_HOME) == LOW); 70 | BUTTON_HOME_CLICK = true; 71 | } 72 | if (showMenuAfterWakeup || BUTTON_HOME_CLICK) { 73 | watch.setAlarm(false); 74 | alarmDisabled = true; 75 | if (!showSettings) { 76 | watch.menu(true); 77 | showSettings = true; 78 | m.reset(); 79 | m.show(); 80 | showMenuAfterWakeup = false; 81 | watch.setBluetooth(true); 82 | } else { 83 | m.action(m.KEY_RIGHT_ENTER); 84 | } 85 | } 86 | if (showSettings) { 87 | M5.MPU6886.getAccelData(&accX,&accY,&accZ); 88 | if (accX > 1 || accY > 1 ) { // shaking 89 | shaking = true; 90 | stopShaking = false; 91 | } else { 92 | stopShaking = true; 93 | } 94 | 95 | if (shaking && stopShaking) { 96 | m.action(m.KEY_LEFT); // go back 97 | shaking = false; 98 | } 99 | 100 | if(digitalRead(M5_BUTTON_RST) == LOW){ 101 | while(digitalRead(M5_BUTTON_RST) == LOW); 102 | m.action(m.KEY_DOWN); 103 | } 104 | } 105 | BUTTON_HOME_CLICK = false; 106 | 107 | M5BLEloop(); 108 | 109 | if ((M5.Axp.GetIchargeData() == 0) && (M5.Axp.GetIdischargeData() == 0)) { 110 | watch.setBattery(watch.BATTERY_FULL); 111 | } else if ((M5.Axp.GetIchargeData() > 0) && (M5.Axp.GetIdischargeData() == 0)) { 112 | watch.setBattery(watch.BATTERY_CHARGING); 113 | } else { 114 | watch.setBattery(M5.Axp.GetVbatData() * 1.1); 115 | } 116 | 117 | watch.setTime(false); // check if is any change in time 118 | if (!alarmDisabled) { 119 | watch.checkAlarms(); 120 | } 121 | 122 | if (watch.displayOn && !showSettings && !watch.alarmNow) { 123 | watch.setDisplayOn(false); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # M5Watch 2 | 3 | Super cool watch with M5StickC. 4 | 5 | Settings over Bluetooth. 6 | 7 | Alarms which can be set directly in the watch. 8 | 9 | Perfect menu! 10 | 11 | Up to 8 hours with display always on. 12 | 13 | Very big potential to upgrade in the future. 14 | 15 | ## Using 16 | 17 | Copy directory 'library' to your libraries directory ( e.g. c:\Users\~username~\Documents\Arduino\libraries\ ) 18 | 19 | ## Mobile app 20 | **M5Watch.aia** is a file for a classic version of **Thunkable**. (You have to add BluetoothLE addon) 21 | 22 | **M5Watch.apk** is binary file for Android generated from **Thunkable**. (it requires allow access to location (bluetooth)) -------------------------------------------------------------------------------- /library/M5WatchFonts.h: -------------------------------------------------------------------------------- 1 | #define num0_width 30 2 | #define num0_height 40 3 | static unsigned char num0_bits[] = { 4 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 5 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 6 | 0x00, 0xe0, 0x1f, 0x00, 0x00, 0xfc, 0xff, 0x00, 0x00, 0xbe, 0xff, 0x01, 7 | 0x80, 0x03, 0xf0, 0x03, 0xc0, 0x00, 0xe0, 0x07, 0x60, 0x00, 0x80, 0x07, 8 | 0x30, 0x00, 0x80, 0x0f, 0x38, 0x00, 0x00, 0x0e, 0x18, 0x00, 0x00, 0x1e, 9 | 0x0c, 0x00, 0x00, 0x1e, 0x0c, 0x00, 0x00, 0x1e, 0x06, 0x00, 0x00, 0x1e, 10 | 0x06, 0x00, 0x00, 0x1e, 0x06, 0x00, 0x00, 0x1c, 0x04, 0x00, 0x00, 0x1e, 11 | 0x06, 0x00, 0x00, 0x1e, 0x06, 0x00, 0x00, 0x1c, 0x06, 0x00, 0x00, 0x04, 12 | 0x0e, 0x00, 0x00, 0x0e, 0x0e, 0x00, 0x00, 0x0e, 0x1c, 0x00, 0x00, 0x06, 13 | 0x3c, 0x00, 0x00, 0x07, 0x7c, 0x00, 0x80, 0x07, 0x78, 0x01, 0xc0, 0x03, 14 | 0xe0, 0x03, 0xe0, 0x01, 0xe0, 0x10, 0x78, 0x00, 0xc0, 0xff, 0x3f, 0x00, 15 | 0x00, 0xff, 0x0f, 0x00, 0x00, 0xf0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 16 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 17 | 0x00, 0x00, 0x00, 0x00 }; 18 | 19 | #define num1_width 30 20 | #define num1_height 40 21 | static unsigned char num1_bits[] = { 22 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 23 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 24 | 0x00, 0xe0, 0x01, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00, 0x3c, 0x00, 0x00, 25 | 0x00, 0x20, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 26 | 0x00, 0xe0, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0xe0, 0x01, 0x00, 27 | 0x00, 0xe0, 0x01, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0xe0, 0x01, 0x00, 28 | 0x00, 0xe0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 29 | 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 30 | 0x00, 0xe0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 31 | 0x00, 0xe0, 0x01, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 32 | 0x00, 0xe0, 0x01, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0xe0, 0x01, 0x00, 33 | 0x00, 0xe0, 0x01, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 34 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 35 | 0x00, 0x00, 0x00, 0x00 }; 36 | 37 | #define num2_width 30 38 | #define num2_height 40 39 | static unsigned char num2_bits[] = { 40 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 41 | 0x00, 0x38, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x80, 0xf0, 0x07, 0x00, 42 | 0x60, 0xc0, 0x0f, 0x00, 0x60, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3f, 0x00, 43 | 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x3c, 0x00, 44 | 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1c, 0x00, 45 | 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 46 | 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 47 | 0x00, 0xc0, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 48 | 0x00, 0x30, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 49 | 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 50 | 0x80, 0x03, 0x00, 0x00, 0x80, 0xff, 0x1f, 0x0c, 0xe0, 0xff, 0xff, 0x07, 51 | 0xf0, 0xff, 0xff, 0x07, 0x78, 0x40, 0xfe, 0x01, 0x00, 0x00, 0x00, 0x00, 52 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 53 | 0x00, 0x00, 0x00, 0x00 }; 54 | 55 | #define num3_width 30 56 | #define num3_height 40 57 | static unsigned char num3_bits[] = { 58 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x0f, 0x00, 0xc0, 0xff, 0x0f, 0x00, 59 | 0xe0, 0xff, 0x0f, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x80, 0x07, 0x00, 60 | 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x01, 0x00, 61 | 0x00, 0xe0, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xf0, 0x07, 0x00, 62 | 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xfc, 0xff, 0x00, 0x00, 0xfe, 0xed, 0x01, 63 | 0x00, 0x07, 0xec, 0x03, 0x80, 0x01, 0xe0, 0x03, 0x00, 0x00, 0x80, 0x03, 64 | 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x03, 65 | 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x01, 66 | 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0xe0, 0x00, 67 | 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x38, 0x00, 68 | 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 69 | 0x00, 0x00, 0x07, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0xe0, 0x00, 0x00, 70 | 0x00, 0x78, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x80, 0x0f, 0x00, 0x00, 71 | 0x00, 0x03, 0x00, 0x00 }; 72 | 73 | #define num4_width 30 74 | #define num4_height 40 75 | static unsigned char num4_bits[] = { 76 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 77 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x18, 0x00, 78 | 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 79 | 0x00, 0x80, 0x1f, 0x00, 0x00, 0x80, 0x19, 0x00, 0x00, 0xc0, 0x18, 0x00, 80 | 0x00, 0x60, 0x18, 0x00, 0x00, 0x30, 0x18, 0x00, 0x00, 0x1c, 0x18, 0x00, 81 | 0x00, 0x0f, 0x18, 0x00, 0x80, 0x07, 0x18, 0x00, 0xe0, 0x03, 0x08, 0x00, 82 | 0xf0, 0x01, 0x18, 0x38, 0xfc, 0x3e, 0xfe, 0x3f, 0xfe, 0xff, 0xff, 0x38, 83 | 0x3e, 0x4b, 0x1c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1c, 0x00, 84 | 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 85 | 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 86 | 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1c, 0x00, 87 | 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x0e, 0x00, 88 | 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 89 | 0x00, 0x00, 0x00, 0x00 }; 90 | 91 | #define num5_width 30 92 | #define num5_height 40 93 | static unsigned char num5_bits[] = { 94 | 0x00, 0x00, 0x00, 0x03, 0x00, 0xc0, 0xe4, 0x01, 0x00, 0xe0, 0xf6, 0x01, 95 | 0x00, 0x30, 0xf9, 0x00, 0x00, 0x38, 0x78, 0x00, 0x00, 0x18, 0x00, 0x00, 96 | 0x00, 0x08, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 97 | 0x00, 0x1e, 0x00, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x1f, 0x00, 98 | 0x80, 0xf7, 0x7f, 0x00, 0xc0, 0x80, 0xff, 0x00, 0x60, 0x00, 0xfc, 0x01, 99 | 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00, 0xd0, 0x01, 0x00, 0x00, 0xe0, 0x01, 100 | 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0xe0, 0x00, 101 | 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x70, 0x00, 102 | 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x18, 0x00, 103 | 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 104 | 0x00, 0x00, 0x03, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 105 | 0x00, 0x30, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 106 | 0x00, 0x07, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xf0, 0x01, 0x00, 0x00, 107 | 0x60, 0x00, 0x00, 0x00 }; 108 | 109 | #define num6_width 30 110 | #define num6_height 40 111 | static unsigned char num6_bits[] = { 112 | 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x07, 113 | 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x70, 0x00, 114 | 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x0c, 0x00, 115 | 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x80, 0x01, 0x00, 116 | 0x00, 0x80, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 117 | 0x00, 0x20, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 118 | 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x1f, 0x00, 119 | 0x00, 0xc3, 0xff, 0x00, 0x00, 0xf3, 0xff, 0x00, 0x00, 0x33, 0xb0, 0x03, 120 | 0x80, 0x03, 0xa0, 0x03, 0xc0, 0x01, 0xa0, 0x03, 0xc0, 0x01, 0x80, 0x03, 121 | 0xe0, 0x01, 0x80, 0x07, 0xe0, 0x01, 0x00, 0x03, 0xe0, 0x01, 0x00, 0x03, 122 | 0xc0, 0x01, 0x00, 0x03, 0xe0, 0x01, 0x80, 0x01, 0xe0, 0x03, 0x80, 0x00, 123 | 0xe0, 0x03, 0xc0, 0x00, 0xe0, 0x07, 0x20, 0x00, 0xc0, 0x0f, 0x30, 0x00, 124 | 0x80, 0x3f, 0x0c, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xfe, 0x01, 0x00, 125 | 0x00, 0x10, 0x00, 0x00 }; 126 | 127 | #define num7_width 30 128 | #define num7_height 40 129 | static unsigned char num7_bits[] = { 130 | 0x00, 0x00, 0x00, 0x10, 0x00, 0x70, 0x00, 0x1c, 0x00, 0xfc, 0xff, 0x0f, 131 | 0x00, 0xfe, 0xff, 0x07, 0x80, 0xf7, 0xfe, 0x07, 0x80, 0x01, 0x80, 0x03, 132 | 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x00, 133 | 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x30, 0x00, 134 | 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x18, 0x00, 135 | 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 136 | 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 137 | 0x00, 0x80, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 138 | 0x00, 0x60, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 139 | 0x00, 0x18, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 140 | 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 141 | 0x00, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 142 | 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 143 | 0x70, 0x00, 0x00, 0x00 }; 144 | 145 | #define num8_width 30 146 | #define num8_height 40 147 | static unsigned char num8_bits[] = { 148 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 149 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 150 | 0x00, 0x80, 0xff, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, 0xe0, 0xf8, 0x01, 151 | 0x00, 0x30, 0xe0, 0x01, 0x00, 0x18, 0xc0, 0x01, 0x00, 0x1c, 0xc0, 0x01, 152 | 0x00, 0x0c, 0xc0, 0x01, 0x00, 0x1c, 0xc0, 0x01, 0x00, 0x1e, 0xc0, 0x00, 153 | 0x00, 0x04, 0x70, 0x00, 0x00, 0xfc, 0x31, 0x00, 0x00, 0xf8, 0x1b, 0x00, 154 | 0x00, 0xf0, 0x3b, 0x00, 0x00, 0xf0, 0x7f, 0x00, 0x00, 0x38, 0xfe, 0x00, 155 | 0x00, 0x0e, 0xf0, 0x01, 0x00, 0x03, 0xf0, 0x01, 0x80, 0x01, 0xe0, 0x01, 156 | 0x80, 0x01, 0xc0, 0x03, 0xc0, 0x00, 0xc0, 0x03, 0xe0, 0x00, 0xc0, 0x03, 157 | 0xe0, 0x00, 0xc0, 0x03, 0xe0, 0x00, 0x80, 0x01, 0xf0, 0x00, 0xc0, 0x00, 158 | 0xe0, 0x00, 0x60, 0x00, 0xe0, 0x01, 0x30, 0x00, 0xe0, 0x07, 0x1c, 0x00, 159 | 0xe0, 0x9f, 0x0e, 0x00, 0xc0, 0xff, 0x03, 0x00, 0x80, 0xff, 0x01, 0x00, 160 | 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 161 | 0x00, 0x00, 0x00, 0x00 }; 162 | 163 | #define num9_width 30 164 | #define num9_height 40 165 | static unsigned char num9_bits[] = { 166 | 0x00, 0x00, 0x1e, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 0xc0, 0xff, 0x01, 167 | 0x00, 0x30, 0xfe, 0x03, 0x00, 0x18, 0xf0, 0x03, 0x00, 0x04, 0xe0, 0x07, 168 | 0x00, 0x02, 0xc0, 0x07, 0x00, 0x03, 0xc0, 0x07, 0x80, 0x01, 0x80, 0x07, 169 | 0xc0, 0x00, 0x80, 0x07, 0xc0, 0x01, 0x80, 0x07, 0xc0, 0x01, 0x80, 0x07, 170 | 0xc0, 0x01, 0x80, 0x07, 0xc0, 0x03, 0x80, 0x03, 0xc0, 0x07, 0x80, 0x03, 171 | 0xc0, 0x0f, 0xc0, 0x01, 0x80, 0x1f, 0xc4, 0x00, 0x80, 0xff, 0xcf, 0x00, 172 | 0x00, 0xff, 0x63, 0x00, 0x00, 0xf8, 0x60, 0x00, 0x00, 0x00, 0x20, 0x00, 173 | 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x18, 0x00, 174 | 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 175 | 0x00, 0x00, 0x03, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0xc0, 0x00, 0x00, 176 | 0x00, 0x60, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 177 | 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 178 | 0x80, 0x03, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 179 | 0x30, 0x00, 0x00, 0x00 }; 180 | 181 | #define colon_width 6 182 | #define colon_height 17 183 | static unsigned char colon_bits[] = { 184 | 0x20, 185 | 0x78, 186 | 0xfc, 187 | 0x7c, 188 | 0x38, 189 | 0x10, 190 | 0x00, 191 | 0x00, 192 | 0x00, 193 | 0x00, 194 | 0x00, 195 | 0x20, 196 | 0x78, 197 | 0xfc, 198 | 0x7c, 199 | 0x38, 200 | 0x10 201 | }; -------------------------------------------------------------------------------- /library/M5WatchIcos.h: -------------------------------------------------------------------------------- 1 | #define ble_width 10 2 | #define ble_height 16 3 | PROGMEM const uint16_t ble[] = { 4 | 0x0000, 0x0000, 0x0000, 0x02bf, 0x02bf, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 5 | 0x0000, 0x0000, 0x0000, 0x02bf, 0x02bf, 0x02bf, 0x0000, 0x0000, 0x0000, 0x0000, 6 | 0x0000, 0x0000, 0x0000, 0x001f, 0x0000, 0x02bf, 0x02bf, 0x0000, 0x0000, 0x0000, 7 | 0x02bf, 0x0000, 0x0000, 0x001f, 0x0000, 0x0000, 0x02bf, 0x02bf, 0x0000, 0x0000, 8 | 0x02bf, 0x02bf, 0x0000, 0x001f, 0x0000, 0x0000, 0x0000, 0x02bf, 0x02bf, 0x0000, 9 | 0x0000, 0x02bf, 0x02bf, 0x0000, 0x0000, 0x0000, 0x02bf, 0x02bf, 0x0000, 0x0000, 10 | 0x0000, 0x0000, 0x02bf, 0x02bf, 0x0000, 0x02bf, 0x02bf, 0x0000, 0x0000, 0x0000, 11 | 0x0000, 0x0000, 0x0000, 0x02bf, 0x02bf, 0x02bf, 0x0000, 0x0000, 0x0000, 0x0000, 12 | 0x0000, 0x0000, 0x0000, 0x02bf, 0x02bf, 0x001f, 0x0000, 0x0000, 0x0000, 0x0000, 13 | 0x0000, 0x0000, 0x02bf, 0x02bf, 0x0000, 0x001f, 0x001f, 0x0000, 0x0000, 0x0000, 14 | 0x0000, 0x02bf, 0x02bf, 0x001f, 0x0000, 0x0000, 0x001f, 0x001f, 0x0000, 0x0000, 15 | 0x02bf, 0x02bf, 0x0000, 0x001f, 0x0000, 0x0000, 0x0000, 0x02bf, 0x02bf, 0x0000, 16 | 0x02bf, 0x0000, 0x0000, 0x001f, 0x0000, 0x0000, 0x02bf, 0x02bf, 0x0000, 0x0000, 17 | 0x0000, 0x0000, 0x0000, 0x001f, 0x0000, 0x02bf, 0x02bf, 0x0000, 0x0000, 0x0000, 18 | 0x0000, 0x0000, 0x0000, 0x0000, 0x02bf, 0x02bf, 0x0000, 0x0000, 0x0000, 0x0000, 19 | 0x0000, 0x0000, 0x0000, 0x02bf, 0x02bf, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 20 | }; 21 | 22 | #define ble_off_width 10 23 | #define ble_off_height 16 24 | PROGMEM const uint16_t ble_off[] = { 25 | 0x0000, 0x0000, 0x0000, 0xf800, 0xf800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 26 | 0x0000, 0x0000, 0x0000, 0xf800, 0xf800, 0xf800, 0x0000, 0x0000, 0x0000, 0x0000, 27 | 0x0000, 0x0000, 0x0000, 0xa800, 0x0000, 0xf800, 0xf800, 0x0000, 0x0000, 0x0000, 28 | 0xf800, 0x0000, 0x0000, 0xa800, 0x0000, 0x0000, 0xf800, 0xf800, 0x0000, 0x0000, 29 | 0xf800, 0xf800, 0x0000, 0xa800, 0x0000, 0x0000, 0x0000, 0xf800, 0xf800, 0x0000, 30 | 0x0000, 0xf800, 0xf800, 0x0000, 0x0000, 0x0000, 0xf800, 0xf800, 0x0000, 0x0000, 31 | 0x0000, 0x0000, 0xf800, 0xf800, 0x0000, 0xf800, 0xf800, 0x0000, 0x0000, 0x0000, 32 | 0x0000, 0x0000, 0x0000, 0xf800, 0xf800, 0xf800, 0x0000, 0x0000, 0x0000, 0x0000, 33 | 0x0000, 0x0000, 0x0000, 0xf800, 0xf800, 0xa800, 0x0000, 0x0000, 0x0000, 0x0000, 34 | 0x0000, 0x0000, 0xf800, 0xf800, 0x0000, 0xa800, 0xa800, 0x0000, 0x0000, 0x0000, 35 | 0x0000, 0xf800, 0xf800, 0xa800, 0x0000, 0x0000, 0xa800, 0xa800, 0x0000, 0x0000, 36 | 0xf800, 0xf800, 0x0000, 0xa800, 0x0000, 0x0000, 0x0000, 0xf800, 0xf800, 0x0000, 37 | 0xf800, 0x0000, 0x0000, 0xa800, 0x0000, 0x0000, 0xf800, 0xf800, 0x0000, 0x0000, 38 | 0x0000, 0x0000, 0x0000, 0xa800, 0x0000, 0xf800, 0xf800, 0x0000, 0x0000, 0x0000, 39 | 0x0000, 0x0000, 0x0000, 0x0000, 0xf800, 0xf800, 0x0000, 0x0000, 0x0000, 0x0000, 40 | 0x0000, 0x0000, 0x0000, 0xf800, 0xf800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 41 | }; 42 | 43 | #define bell_width 16 44 | #define bell_height 16 45 | PROGMEM const uint16_t bell[] = { 46 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 47 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 48 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffe0, 0xffe0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 49 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 50 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffef, 0xfff5, 0xffe0, 0xffe0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 51 | 0x0000, 0x0000, 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffef, 0xfff5, 0xfffb, 0xffe0, 0xffe0, 0x0000, 0x0000, 0x0000, 0x0000, 52 | 0x0000, 0x0000, 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffef, 0xfff5, 0xfffb, 0xffe0, 0x0000, 0x0000, 0x0000, 0x0000, 53 | 0x0000, 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffef, 0xfff5, 0xfffb, 0xffe0, 0x0000, 0x0000, 0x0000, 54 | 0x0000, 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffef, 0xfff5, 0xfffb, 0xffe0, 0x0000, 0x0000, 0x0000, 55 | 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffef, 0xfff5, 0xfffb, 0xffe0, 0x0000, 0x0000, 56 | 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffef, 0xfff5, 0xfffb, 0xffe0, 0x0000, 0x0000, 57 | 0x0000, 0x0000, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffe0, 0xffef, 0xfff5, 0xfff5, 0xfffb, 0x0000, 0x0000, 58 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffe0, 0xffef, 0xfff5, 0xfffb, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 59 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffef, 0xfffb, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 60 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 61 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 62 | }; -------------------------------------------------------------------------------- /library/M5WatchLcd.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // Public 10 | 11 | M5WatchLcd::M5WatchLcd() { 12 | }; 13 | 14 | void M5WatchLcd::init(unsigned long powerUpTime, bool wakeup) { 15 | this->powerUpTime = powerUpTime; 16 | EEPROM.begin(this->EEPROMlength); 17 | if (wakeup) { // wake by button or first init 18 | this->loadBrightness(); 19 | } 20 | this->loadAlarms(); 21 | M5.Lcd.setRotation(3); // rotate to 270 degrees 22 | M5.Lcd.fillScreen(BLACK); 23 | M5.Lcd.setTextColor(WHITE, BLACK); 24 | M5.Rtc.GetBm8563Time(); 25 | 26 | this->useBluetooth = false; // for the first setBTstate 27 | 28 | this->redraw(); 29 | }; 30 | 31 | void M5WatchLcd::redraw() { 32 | this->drawStatusLine(); 33 | this->setTime(true); 34 | this->drawDate(); 35 | this->drawAlarm(); 36 | }; 37 | 38 | void M5WatchLcd::terminal(char* text) { 39 | M5.Lcd.setTextSize(1); 40 | M5.Lcd.setCursor(1, 20); 41 | M5.Lcd.printf(text); 42 | }; 43 | 44 | void M5WatchLcd::setTime(bool redrawAll) { 45 | M5.Rtc.GetTime(&this->TimeStruct); 46 | int hour_high = (int)(this->TimeStruct.Hours / 10); 47 | int hour_low = (int)(this->TimeStruct.Hours % 10); 48 | int minute_high = (int)(this->TimeStruct.Minutes / 10); 49 | int minute_low = (int)(this->TimeStruct.Minutes % 10); 50 | bool change = false; 51 | 52 | if (redrawAll) { 53 | M5.Lcd.fillRect(0, 19, 160, 61, BLACK); 54 | } 55 | 56 | if (redrawAll || (hour_high != this->hour_high)) { 57 | this->hour_high = hour_high; 58 | if (!this->showMenu) { 59 | this->drawTime(10, 30, this->hour_high); 60 | this->drawDate(); 61 | } 62 | change = true; 63 | } 64 | if (redrawAll || (hour_low != this->hour_low)) { 65 | this->hour_low = hour_low; 66 | if (!this->showMenu) { 67 | this->drawTime(40, 30, this->hour_low); 68 | } 69 | change = true; 70 | } 71 | if (redrawAll || (minute_high != this->minute_high)) { 72 | this->minute_high = minute_high; 73 | if (!this->showMenu) { 74 | this->drawTime(90, 30, this->minute_high); 75 | } 76 | change = true; 77 | } 78 | if (redrawAll || (minute_low != this->minute_low)) { 79 | this->minute_low = minute_low; 80 | if (!this->showMenu) { 81 | this->drawTime(120, 30, this->minute_low); 82 | } 83 | change = true; 84 | } 85 | 86 | if (change) { 87 | if (!this->showMenu) { 88 | M5.Lcd.fillRect(76, 41, 6, 17, BLACK); 89 | M5.Lcd.drawXBitmap(76, 41, colon_bits, 6, 17, WHITE); 90 | } else { 91 | M5.Lcd.setTextSize(1); 92 | M5.Lcd.setCursor(65, 6); 93 | M5.Lcd.printf("%02d:%02d", this->TimeStruct.Hours, this->TimeStruct.Minutes); 94 | } 95 | 96 | M5.Rtc.GetData(&this->DateStruct); 97 | } 98 | } 99 | 100 | void M5WatchLcd::setBattery(int bat) { 101 | int batPercent = 0; 102 | if (bat == this->BATTERY_FULL || bat == this->BATTERY_CHARGING) { 103 | batPercent = bat; 104 | } else { 105 | batPercent = map(bat, 3300, 4125, 0, 100); 106 | } 107 | if (batPercent != this->battery) { 108 | this->battery = batPercent; 109 | this->drawBattery(); 110 | } 111 | }; 112 | 113 | void M5WatchLcd::setBluetooth(bool state) { 114 | this->useBluetooth = state; 115 | 116 | if (state) { 117 | btStart(); 118 | } else { 119 | btStop(); 120 | } 121 | this->drawBT(); 122 | } 123 | 124 | void M5WatchLcd::setBTstate(bool state) { 125 | this->BTstate = state; 126 | this->drawBT(); 127 | } 128 | 129 | void M5WatchLcd::setDisplayOn(bool state) { 130 | this->displayOn = state; 131 | if (!state) { 132 | M5.Axp.ScreenBreath(7); // for the lowest power consumption but time visibility 133 | // pinMode(GPIO_NUM_37, INPUT); 134 | esp_sleep_enable_ext0_wakeup(GPIO_NUM_37, LOW); 135 | long time_to_wake = 60000000 - (millis() - this->powerUpTime) * 1000; 136 | if (time_to_wake < 0) { 137 | time_to_wake = 60000000; 138 | } 139 | esp_sleep_enable_timer_wakeup(time_to_wake); // wake up every 60 seconds 140 | Serial.print("Sleep for "); 141 | Serial.println(time_to_wake); 142 | esp_deep_sleep_start(); 143 | } 144 | } 145 | 146 | void M5WatchLcd::setBrightness(int state) { 147 | M5.Axp.ScreenBreath(state); 148 | this->memorySave(state, this->brightnessAddr); 149 | } 150 | 151 | void M5WatchLcd::menu(bool state) { 152 | this->showMenu = state; 153 | if (state) { 154 | M5.Lcd.fillRect(0, 19, 160, 61, BLACK); 155 | this->setTime(true); // change date on the top to time 156 | } 157 | } 158 | 159 | void M5WatchLcd::checkBTdata(char* chrs) { 160 | String text = chrs; 161 | text.trim(); 162 | if (text.length() == 15) { // alarm settings 163 | this->addAlarm(0, (text.substring(4, 5) == "1" ? true : false), text.substring(0, 2).toInt(), text.substring(2, 4).toInt()); 164 | this->addAlarm(1, (text.substring(9, 10) == "1" ? true : false), text.substring(5, 7).toInt(), text.substring(7, 9).toInt()); 165 | this->addAlarm(2, (text.substring(14, 15) == "1" ? true : false), text.substring(10, 12).toInt(), text.substring(12, 14).toInt()); 166 | this->sendBTstate(); 167 | this->resetAlarmMenu(); 168 | } else if (text.length() == 14) { // datetime settings 169 | RTC_TimeTypeDef TimeStruct; 170 | RTC_DateTypeDef DateStruct; 171 | DateStruct.Year = text.substring(0, 4).toInt(); 172 | DateStruct.Month = text.substring(4, 6).toInt(); 173 | DateStruct.Date = text.substring(6, 8).toInt(); 174 | TimeStruct.Hours = text.substring(8, 10).toInt(); 175 | TimeStruct.Minutes = text.substring(10, 12).toInt(); 176 | TimeStruct.Seconds = text.substring(12, 14).toInt(); 177 | M5.Rtc.SetData(&DateStruct); 178 | M5.Rtc.SetTime(&TimeStruct); 179 | } 180 | // JSONVar json = JSON.parse(text); 181 | // if (JSON.typeof(json) == "undefined") { 182 | // Serial.println("Parsing input failed!"); 183 | // return; 184 | // } 185 | 186 | // if (json.hasOwnProperty("type")) { 187 | // String type = String((const char*)json["type"]); 188 | 189 | // if (type == "terminal") { 190 | // String jsonString = JSON.stringify(json); 191 | // char *cstr = new char[jsonString.length() + 1]; 192 | // strcpy(cstr, jsonString.c_str()); 193 | // Serial.println(cstr); 194 | // this->terminal(cstr); 195 | // } else if (type == "clock") { 196 | // RTC_TimeTypeDef TimeStruct; 197 | // TimeStruct.Hours = (int)json["h"]; 198 | // TimeStruct.Minutes = (int)json["m"]; 199 | // TimeStruct.Seconds = (int)json["s"]; 200 | // Serial.println(TimeStruct.Hours); 201 | // M5.Rtc.SetTime(&TimeStruct); 202 | // } else if (type == "date") { 203 | // RTC_DateTypeDef DateStruct; 204 | // DateStruct.Month = (int)json["m"]; 205 | // DateStruct.Date = (int)json["d"]; 206 | // DateStruct.Year = (int)json["y"]; 207 | // M5.Rtc.SetData(&DateStruct); 208 | // } else if (type == "alarmA") { 209 | // this->addAlarm((int)json["i"], (bool)json["s"], (int)json["h"], (int)json["m"]); 210 | // } else if (type == "alarmR") { 211 | // this->removeAlarm((int)json["i"]); 212 | // } else if (type == "alarmS") { 213 | // this->setAlarm((int)json["i"], (bool)json["s"]); 214 | // } 215 | // } 216 | } 217 | 218 | void M5WatchLcd::setAlarm(bool state) { 219 | this->alarmNow = state; 220 | digitalWrite(M5_LED, !state); 221 | } 222 | 223 | void M5WatchLcd::setAlarmState(int index) { 224 | this->alarms[index].set = !this->alarms[index].set; 225 | this->saveAlarms(index); 226 | this->checkAlarmSet(); 227 | } 228 | 229 | void M5WatchLcd::checkAlarms() { 230 | for (int i = 0; i < 3; i++) { 231 | M5WatchLcd::Alarm a = this->alarms[i]; 232 | if (a.set && a.hour == this->TimeStruct.Hours && a.minute == this->TimeStruct.Minutes) { 233 | this->setAlarm(true); 234 | } 235 | } 236 | } 237 | 238 | void M5WatchLcd::setAlarmMenu (char* alarm_0, char* alarm_1, char* alarm_2) { 239 | this->alarm_0 = alarm_0; 240 | this->alarm_1 = alarm_1; 241 | this->alarm_2 = alarm_2; 242 | this->resetAlarmMenu(); 243 | } 244 | 245 | void M5WatchLcd::resetAlarmMenu() { 246 | sprintf(this->alarm_0, "%02d:%02d", this->alarms[0].hour, this->alarms[0].minute); 247 | sprintf(this->alarm_1, "%02d:%02d", this->alarms[1].hour, this->alarms[1].minute); 248 | sprintf(this->alarm_2, "%02d:%02d", this->alarms[2].hour, this->alarms[2].minute); 249 | if (this->alarms[0].set) { 250 | strcat(this->alarm_0, " *"); 251 | } 252 | if (this->alarms[1].set) { 253 | strcat(this->alarm_1, " *"); 254 | } 255 | if (this->alarms[2].set) { 256 | strcat(this->alarm_2, " *"); 257 | } 258 | } 259 | 260 | void M5WatchLcd::sendBTstate() { 261 | Serial.println("sendBTstate"); 262 | char x0[5]; 263 | sprintf(x0, "%02d%02d%1d", this->alarms[0].hour, this->alarms[0].minute, this->alarms[0].set ? 1 : 0); 264 | strcpy(this->BLEdata, x0); 265 | char x1[5]; 266 | sprintf(x1, "%02d%02d%1d", this->alarms[1].hour, this->alarms[1].minute, this->alarms[1].set ? 1 : 0); 267 | strcat(this->BLEdata, x1); 268 | char x2[5]; 269 | sprintf(x2, "%02d%02d%1d", this->alarms[2].hour, this->alarms[2].minute, this->alarms[2].set ? 1 : 0); 270 | strcat(this->BLEdata, x2); 271 | } 272 | 273 | 274 | 275 | 276 | 277 | // Private 278 | 279 | void M5WatchLcd::drawStatusLine() { 280 | M5.Lcd.drawLine(0, 18, 160, 18, WHITE); 281 | M5.Lcd.setCursor(1, 1); 282 | }; 283 | 284 | void M5WatchLcd::drawBattery() { 285 | M5.Lcd.drawRect(135, 3, 20, 12, WHITE); 286 | M5.Lcd.drawRect(155, 7, 2, 6, WHITE); 287 | uint16_t color = BLACK; 288 | if (this->battery == this->BATTERY_FULL) { 289 | color = GREEN; 290 | } else if (this->battery == this->BATTERY_CHARGING) { 291 | color = BLUE; 292 | } 293 | M5.Lcd.fillRect(136, 4, 18, 10, color); 294 | 295 | if (this->battery < 100) { 296 | M5.Lcd.setTextSize(1); 297 | M5.Lcd.setCursor(140, 6); 298 | M5.Lcd.printf("%2d", this->battery); 299 | } 300 | } 301 | 302 | void M5WatchLcd::drawTime(int x, int y, int value) { 303 | M5.Lcd.fillRect(x, y, 30, 40, BLACK); 304 | M5.Lcd.drawXBitmap(x, y, this->getFontNumber(value), 30, 40, WHITE); 305 | } 306 | 307 | void M5WatchLcd::drawDate() { 308 | M5.Lcd.setTextSize(1); 309 | M5.Lcd.setCursor(65, 6); 310 | M5.Lcd.printf("%02d/%02d", this->DateStruct.Month, this->DateStruct.Date); 311 | } 312 | 313 | void M5WatchLcd::drawBT() { 314 | if (this->useBluetooth) { 315 | if (this->BTstate) { 316 | M5.Lcd.drawBitmap(1, 1, ble_width, ble_height, ble, BLACK); 317 | } else { 318 | M5.Lcd.drawBitmap(1, 1, ble_off_width, ble_off_height, ble_off, BLACK); 319 | } 320 | } else { 321 | M5.Lcd.fillRect(1, 1, 10, 16, BLACK); 322 | } 323 | } 324 | 325 | void M5WatchLcd::drawAlarm() { 326 | if (this->alarmSet) { 327 | M5.Lcd.drawBitmap(16, 1, bell_width, bell_height, bell, BLACK); 328 | } else { 329 | M5.Lcd.fillRect(16, 1, 16, 16, BLACK); 330 | } 331 | } 332 | 333 | unsigned char* M5WatchLcd::getFontNumber(int x) { 334 | switch(x) { 335 | case 0: return num0_bits; 336 | case 1: return num1_bits; 337 | case 2: return num2_bits; 338 | case 3: return num3_bits; 339 | case 4: return num4_bits; 340 | case 5: return num5_bits; 341 | case 6: return num6_bits; 342 | case 7: return num7_bits; 343 | case 8: return num8_bits; 344 | case 9: return num9_bits; 345 | default: return num0_bits; 346 | } 347 | } 348 | 349 | void M5WatchLcd::loadBrightness() { 350 | Serial.println("load brightness"); 351 | this->brightness = this->memoryLoad(this->brightnessAddr); 352 | if (this->brightness < 7 || this->brightness > 15) { 353 | this->brightness = 8; // default 354 | } 355 | M5.Axp.ScreenBreath(this->brightness); 356 | } 357 | 358 | void M5WatchLcd::memorySave(int num, int MemPos) { 359 | EEPROM.write(MemPos, num); 360 | EEPROM.commit(); 361 | } 362 | 363 | int M5WatchLcd::memoryLoad(int MemPos) { 364 | return EEPROM.read(MemPos); 365 | } 366 | 367 | void M5WatchLcd::checkAlarmSet() { 368 | bool anyAlarm = false; 369 | for (int i = 0; i < 3; i++) { 370 | if (this->alarms[i].set) { 371 | anyAlarm = true; 372 | break; 373 | } 374 | } 375 | this->alarmSet = anyAlarm; 376 | this->drawAlarm(); 377 | } 378 | 379 | void M5WatchLcd::addAlarm(int index, bool state, int hour, int minute) { 380 | this->alarms[index] = (M5WatchLcd::Alarm) { hour, minute, state }; 381 | this->saveAlarms(index); 382 | this->checkAlarmSet(); 383 | } 384 | 385 | void M5WatchLcd::removeAlarm(int index) { 386 | Serial.println("removeAlarm"); 387 | this->alarms[index] = (M5WatchLcd::Alarm) { 0, 0, false }; 388 | this->saveAlarms(index); 389 | this->checkAlarmSet(); 390 | } 391 | 392 | void M5WatchLcd::setAlarm(int index, bool state) { 393 | Serial.println("setAlarm"); 394 | this->alarms[index].set = state; 395 | this->saveAlarms(index); 396 | this->checkAlarmSet(); 397 | } 398 | 399 | void M5WatchLcd::saveAlarms(int index) { 400 | Serial.println("Save alarms"); 401 | int h = this->alarms[index].hour; 402 | byte par = 100 - this->alarms[index].hour - this->alarms[index].minute; 403 | if (this->alarms[index].set) { 404 | h |= 0x80; 405 | } 406 | this->memorySave(h, this->alarmsAddr + index * 3); 407 | this->memorySave(this->alarms[index].minute, this->alarmsAddr + index * 3 + 1); 408 | this->memorySave(par, this->alarmsAddr + index * 3 + 2); 409 | } 410 | 411 | void M5WatchLcd::loadAlarms() { 412 | Serial.println("loadAlarms"); 413 | for (int i = 0; i < 3; i++) { 414 | bool set = false; 415 | int h = this->memoryLoad(this->alarmsAddr + i * 3); 416 | if ((h & 0x80) == 0x80) { 417 | set = true; 418 | h = h ^ 0x80; 419 | } 420 | int m = this->memoryLoad(this->alarmsAddr + i * 3 + 1); 421 | int par = this->memoryLoad(this->alarmsAddr + i * 3 + 2); 422 | if ((par + m + h != 100) || (h < 0 || h > 23) || (m < 0 || m > 59)) { 423 | h = 0; 424 | m = 0; 425 | set = false; 426 | } 427 | this->alarms[i] = (M5WatchLcd::Alarm) { h, m, set }; 428 | } 429 | this->checkAlarmSet(); 430 | } 431 | -------------------------------------------------------------------------------- /library/M5WatchLcd.h: -------------------------------------------------------------------------------- 1 | #ifndef M5WatchLcd_h 2 | #define M5WatchLcd_h 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class M5WatchLcd { 12 | public: 13 | const int BATTERY_FULL = 5000; 14 | const int BATTERY_CHARGING = 4999; 15 | bool displayOn = true; // is display on? 16 | bool useBluetooth = true; // use bluetooth? 17 | bool alarmNow = false; // alarm is going right now 18 | char BLEdata[20]; 19 | 20 | unsigned long powerUpTime = 0; 21 | 22 | typedef struct Alarm { 23 | int hour; 24 | int minute; 25 | bool set; 26 | }; 27 | 28 | Alarm alarms[3]; // setted alarms, max 3 29 | 30 | M5WatchLcd(); 31 | void init(unsigned long powerUpTime, bool wakeup); 32 | void redraw(); 33 | void terminal(char* text); 34 | void setTime(bool redrawAll); 35 | void setBattery(int bat); 36 | void setBluetooth(bool state); 37 | void setBTstate(bool state); 38 | void setBrightness(int state); 39 | void setDisplayOn(bool state); 40 | void menu(bool state); 41 | void checkBTdata(char* text); 42 | void setAlarm(bool state); 43 | void setAlarmState(int index); 44 | void checkAlarms(); 45 | void setAlarmMenu(char* alarm_0, char* alarm_1, char* alarm_2); 46 | void resetAlarmMenu(); 47 | void sendBTstate(); 48 | private: 49 | RTC_TimeTypeDef TimeStruct; 50 | RTC_DateTypeDef DateStruct; 51 | bool change; // Has to be redrawn? 52 | int battery; // Battery in percent 53 | bool BTstate = false;// Bluetooth state (connected / disconnected) 54 | 55 | int EEPROMlength = 10; // brightness, 3 alarms(3 bytes each of them) 56 | int brightnessAddr = 0; 57 | int brightness = 8; 58 | int alarmsAddr = 1; 59 | 60 | int hour_low = -1; 61 | int hour_high = -1; 62 | int minute_low = -1; 63 | int minute_high = -1; 64 | 65 | bool showMenu = false; 66 | bool alarmSet = false; 67 | 68 | char* alarm_0; 69 | char* alarm_1; 70 | char* alarm_2; 71 | 72 | void drawStatusLine(); 73 | void drawTime(int x, int y, int value); 74 | void drawDate(); 75 | void drawBT(); 76 | void drawAlarm(); 77 | void drawBattery(); 78 | unsigned char* getFontNumber(int x); 79 | 80 | void loadBrightness(); 81 | void memorySave(int num, int memPos); 82 | int memoryLoad(int memPos); 83 | 84 | void checkAlarmSet(); 85 | void addAlarm(int index, bool state, int hour, int minute); 86 | void removeAlarm(int index); 87 | void setAlarm(int index, bool state); 88 | void saveAlarms(int index); 89 | void loadAlarms(); 90 | }; 91 | 92 | #endif -------------------------------------------------------------------------------- /library/Menu.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Menu.cpp - Library for universal menu. 3 | Created by Petr Vavrin, 2019 4 | Released into the public domain. 5 | */ 6 | 7 | #include "Arduino.h" 8 | #include "Menu.h" 9 | 10 | Menu::Menu() 11 | { 12 | } 13 | 14 | void Menu::init(Menu::ITEM * item) { 15 | this->mainItem = item; 16 | this->activeItem = item; 17 | } 18 | 19 | void Menu::reset() { 20 | this->activeItem = this->mainItem; 21 | } 22 | 23 | void Menu::addItem (Menu::ITEM * item, char hotkey, char * text, Menu::ITEM * parent, bool (* cb)(), bool (* value_cb)(bool x)) { 24 | Menu::ITEM * childItem; 25 | if (parent == NULL) { 26 | parent = &this->rootItem; 27 | } 28 | item->parent = parent; 29 | item->prev = NULL; 30 | item->next = NULL; 31 | item->child = NULL; 32 | item->text = text; 33 | item->cb = cb; 34 | item->value_cb = value_cb; 35 | item->uid = this->itemCounter; 36 | item->hotkey = hotkey; 37 | item->disabled = false; 38 | this->itemCounter += 1; 39 | if (parent->child == NULL) { 40 | parent->child = item; 41 | } else { 42 | childItem = parent->child; 43 | while (childItem->next) { 44 | childItem = childItem->next; 45 | } 46 | childItem->next = item; 47 | item->prev = childItem; 48 | } 49 | } 50 | 51 | void Menu::action(byte key) { 52 | if (this->circular && key == this->KEY_UP && this->activeItem->prev == NULL) { 53 | while (this->activeItem->next) { 54 | this->activeItem = this->activeItem->next; 55 | } 56 | 57 | } else if (this->circular && key == this->KEY_DOWN && this->activeItem->next == NULL) { 58 | while (this->activeItem->prev) { 59 | this->activeItem = this->activeItem->prev; 60 | } 61 | 62 | } else if (key == this->KEY_UP && this->activeItem->prev != NULL) { 63 | this->activeItem = this->activeItem->prev; 64 | 65 | } else if (key == this->KEY_DOWN && this->activeItem->next != NULL) { 66 | this->activeItem = this->activeItem->next; 67 | 68 | } else if (key == this->KEY_LEFT && this->activeItem->parent != NULL && this->activeItem->parent->text != NULL) { 69 | 70 | this->activeItem = this->activeItem->parent; 71 | 72 | } else if (key == this->KEY_LEFT && (this->activeItem->parent == NULL || this->activeItem->parent->text == NULL) && this->menuExit != NULL) { 73 | (this->menuExit)(); 74 | return; 75 | } else if (this->activeItem->disabled == false) { 76 | if ((key == this->KEY_RIGHT || key == this->KEY_RIGHT_ENTER) && this->activeItem->child != NULL) { 77 | this->activeItem = this->activeItem->child; 78 | 79 | } else if ((key == this->KEY_ENTER || key == this->KEY_RIGHT_ENTER) && this->activeItem->cb != NULL) { 80 | (this->activeItem->cb)(); 81 | 82 | } 83 | } 84 | 85 | this->show(); 86 | } 87 | 88 | void Menu::actionHotkey (char hotkey) { 89 | Menu::ITEM * item = this->activeItem; 90 | while (item->prev) { 91 | item = item->prev; 92 | } 93 | do { 94 | if (item->hotkey == hotkey) { 95 | this->activeItem = item; 96 | this->action(this->KEY_RIGHT_ENTER); 97 | break; 98 | } else if (item->next != NULL){ 99 | item = item->next; 100 | } else { 101 | break; 102 | } 103 | } while (true); 104 | } 105 | 106 | void Menu::show() { 107 | if (this->preRender != NULL) { 108 | (this->preRender)(); 109 | } 110 | Menu::ITEM * showItem = this->activeItem; 111 | int itemsCounter = this->itemsLimit; 112 | bool iterate = true; 113 | if (this->showTitle) { 114 | if (this->renderTitle != NULL) { 115 | (this->renderTitle)(this->activeItem->parent); 116 | } 117 | itemsCounter -= 1; 118 | } 119 | if (this->showPreviousitems) { 120 | while (showItem->prev) { 121 | showItem = showItem->prev; 122 | } 123 | } 124 | do { 125 | if (this->renderItem != NULL) { 126 | (this->renderItem)(showItem, this->activeItem->uid, this->markActive, this->inlineValue); 127 | } 128 | 129 | itemsCounter -= 1; 130 | if (itemsCounter > 0 && showItem->next != NULL){ 131 | showItem = showItem->next; 132 | } else { 133 | iterate = false; 134 | } 135 | } while (iterate); 136 | if (this->showValue && this->activeItem->parent != NULL) { 137 | showItem = this->activeItem->parent; 138 | if (showItem->value_cb != NULL) { 139 | (showItem->value_cb)(false); 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /library/Menu.h: -------------------------------------------------------------------------------- 1 | /* 2 | Menu.h - Library for universal menu. 3 | Created by Petr Vavrin, 2019 4 | Released into the public domain. 5 | */ 6 | 7 | #ifndef Menu_h 8 | #define Menu_h 9 | 10 | #include "Arduino.h" 11 | 12 | class Menu 13 | { 14 | public: 15 | struct ITEM { 16 | Menu::ITEM * parent; 17 | Menu::ITEM * prev; 18 | Menu::ITEM * next; 19 | Menu::ITEM * child; 20 | int uid; 21 | char * text; 22 | bool disabled; 23 | bool (* cb)(); 24 | bool (* value_cb)(bool isItemLine); 25 | char hotkey; 26 | }; 27 | Menu::ITEM * mainItem; 28 | Menu::ITEM * activeItem; 29 | Menu::ITEM rootItem; 30 | int itemCounter = 0; 31 | 32 | bool showTitle = true; 33 | bool showValue = true; 34 | bool showPreviousitems = true; 35 | bool circular = true; 36 | bool markActive = true; 37 | bool inlineValue = true; 38 | byte itemsLimit = 99; 39 | 40 | byte KEY_UP = 1; 41 | byte KEY_DOWN = 2; 42 | byte KEY_LEFT = 3; 43 | byte KEY_RIGHT = 4; 44 | byte KEY_ENTER = 5; 45 | byte KEY_RIGHT_ENTER = 6; 46 | 47 | byte BUTTON_CLICK = Menu::KEY_DOWN; 48 | byte BUTTON_DOUBLE_CLICK = Menu::KEY_LEFT; 49 | byte BUTTON_LONG_CLICK = Menu::KEY_RIGHT_ENTER; 50 | 51 | void (* renderTitle)(Menu::ITEM * item) = NULL; 52 | void (* renderItem)(Menu::ITEM * item, int activeUid, bool markActive, bool inlineValue) = NULL; 53 | void (* menuExit)() = NULL; 54 | void (* preRender)() = NULL; 55 | 56 | Menu(); 57 | void init(Menu::ITEM * item); 58 | void reset(); 59 | void addItem (Menu::ITEM * item, char hotkey, char * text, Menu::ITEM * parent, bool (* cb)(), bool (* value_cb)(bool x)); 60 | void action(byte key); 61 | void actionHotkey (char hotkey); 62 | void show(); 63 | 64 | private: 65 | 66 | }; 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /mobile_app/M5Watch.aia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Happy83/M5Watch/a257502a033054cceab474cc02df70ae2f747438/mobile_app/M5Watch.aia -------------------------------------------------------------------------------- /mobile_app/M5Watch.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Happy83/M5Watch/a257502a033054cceab474cc02df70ae2f747438/mobile_app/M5Watch.apk --------------------------------------------------------------------------------