├── .github └── workflows │ └── main.yml ├── .gitignore ├── README.md ├── draw_helper.h ├── gear_bmp.h ├── main.cpp ├── platformio.ini └── remote_keymap.h /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: PlatformIO CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v3 11 | 12 | - uses: actions/cache@v3 13 | with: 14 | path: | 15 | ~/.cache/pip 16 | ~/.platformio/.cache 17 | key: ${{ runner.os }}-pio 18 | 19 | - uses: actions/setup-python@v4 20 | with: 21 | python-version: '3.9' 22 | 23 | - name: Install PlatformIO Core 24 | run: pip install --upgrade platformio 25 | 26 | - name: Build PlatformIO Project 27 | run: pio run 28 | 29 | - name: Install esptool 30 | run: | 31 | pip install -U esptool 32 | 33 | - name: Create Cardputer Firmware Binary 34 | run: | 35 | esptool.py --chip esp32s3 merge_bin --output Remote.bin \ 36 | 0x00000 .pio/build/m5cardputer/bootloader.bin \ 37 | 0x08000 .pio/build/m5cardputer/partitions.bin \ 38 | 0x10000 .pio/build/m5cardputer/firmware.bin 39 | 40 | - name: Upload Build Artifact 41 | uses: actions/upload-artifact@v4.3.1 42 | with: 43 | name: CardputerRemote 44 | path: Remote.bin 45 | if-no-files-found: error 46 | 47 | release: 48 | runs-on: ubuntu-latest 49 | environment: github_release 50 | needs: [build] 51 | if: github.ref_type == 'tag' 52 | 53 | steps: 54 | - name: Download Build Artifact 55 | uses: actions/download-artifact@v4 56 | with: 57 | name: CardputerRemote 58 | 59 | - name: Show directory structure 60 | run: tree 61 | 62 | - name: Release ${{ github.ref_name }} 63 | uses: softprops/action-gh-release@v1 64 | with: 65 | name: ${{ github.ref_name }} 66 | tag_name: ${{ github.ref_name }} 67 | generate_release_notes: true 68 | files: Remote.bin 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | M5Cardputer IR remote for Sony and LG TVs, currently 2 | 3 | Inspired by https://github.com/VolosR/M5CardRemote -------------------------------------------------------------------------------- /draw_helper.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "gear_bmp.h" 4 | #include "remote_keymap.h" 5 | 6 | // RBG565 colors 7 | const unsigned short COLOR_BLACK = 0x18E3; 8 | const unsigned short COLOR_DARKGRAY = 0x0861; 9 | const unsigned short COLOR_MEDGRAY = 0x2104; 10 | const unsigned short COLOR_LIGHTGRAY = 0x4208; 11 | const unsigned short COLOR_DARKRED = 0x5800; 12 | const unsigned short COLOR_ORANGE = 0xC401; 13 | const unsigned short COLOR_TEAL = 0x07CC; 14 | const unsigned short COLOR_BLUEGRAY = 0x0B0C; 15 | const unsigned short COLOR_BLUE = 0x026E; 16 | const unsigned short COLOR_PURPLE = 0x7075; 17 | 18 | enum Symbol { Power, Input, Gear, VolUp, Mute, VolDn, Left, Up, Ok, Down, Right, Back, Display, Home }; 19 | 20 | inline void draw_title_text(M5Canvas* canvas, int x, int y) { 21 | canvas->setTextColor(TFT_SILVER, COLOR_DARKGRAY); 22 | canvas->setTextDatum(middle_center); 23 | canvas->setTextSize(1.5); 24 | 25 | // bug? drawChar doesn't respect text datum 26 | canvas->drawString("N", x - 6, y); 27 | canvas->drawString("O", x - 6, y + 14); 28 | canvas->drawString("N", x - 6, y + 28); 29 | canvas->drawString("I", x - 6, y + 42); 30 | canvas->drawString("K", x - 6, y + 56); 31 | canvas->drawString("R", x + 6, y + 20); 32 | canvas->drawString("E", x + 6, y + 34); 33 | canvas->drawString("M", x + 6, y + 48); 34 | canvas->drawString("O", x + 6, y + 62); 35 | canvas->drawString("T", x + 6, y + 76); 36 | canvas->drawString("E", x + 6, y + 90); 37 | } 38 | 39 | inline void draw_remote_type_indicators(M5Canvas* canvas, int x, int y, int m, RemoteType remoteType) { 40 | int w = 36; 41 | int h = 17; 42 | y -= h / 2; 43 | unsigned short bc = remoteType == Sony ? COLOR_ORANGE : COLOR_MEDGRAY; 44 | unsigned short tc = remoteType == Sony ? TFT_BLACK : TFT_SILVER; 45 | canvas->fillRoundRect(x, y, w, h, 3, bc); 46 | canvas->setTextColor(TFT_SILVER, COLOR_MEDGRAY); 47 | canvas->setTextSize(1.2); 48 | canvas->setTextColor(tc, bc); 49 | canvas->drawString("SONY", x + w / 2, y + h / 2); 50 | 51 | x = x + w + m; 52 | bc = remoteType == Lg ? COLOR_ORANGE : COLOR_MEDGRAY; 53 | tc = remoteType == Lg ? TFT_BLACK : TFT_SILVER; 54 | canvas->fillRoundRect(x, y, w, h, 3, bc); 55 | canvas->setTextColor(tc, bc); 56 | canvas->drawString("LG", x + w / 2, y + h / 2); 57 | 58 | x = x + w + m; 59 | bc = remoteType == Undef1 ? COLOR_ORANGE : COLOR_MEDGRAY; 60 | tc = remoteType == Undef1 ? TFT_BLACK : TFT_SILVER; 61 | canvas->fillRoundRect(x, y, w, h, 3, bc); 62 | canvas->setTextColor(tc, bc); 63 | canvas->drawString("TBD", x + w / 2, y + h / 2); 64 | 65 | x = x + w + m; 66 | bc = remoteType == Undef2 ? COLOR_ORANGE : COLOR_MEDGRAY; 67 | tc = remoteType == Undef2 ? TFT_BLACK : TFT_SILVER; 68 | canvas->fillRoundRect(x, y, w, h, 3, bc); 69 | canvas->setTextColor(tc, bc); 70 | canvas->drawString("TBD", x + w / 2, y + h / 2); 71 | } 72 | 73 | inline void draw_battery_indicator(M5Canvas* canvas, int x, int y, int batteryPct) { // draw battery indicator 74 | int battw = 24; 75 | int batth = 11; 76 | 77 | int ya = y - batth / 2; 78 | 79 | // determine battery color and charge width from charge level 80 | int chgw = (battw - 2) * batteryPct / 100; 81 | uint16_t batColor = COLOR_TEAL; 82 | if (batteryPct < 100) 83 | { 84 | int r = ((100 - batteryPct) / 100.0) * 256; 85 | int g = (batteryPct / 100.0) * 256; 86 | batColor = canvas->color565(r, g, 0); 87 | } 88 | canvas->fillRoundRect(x, ya, battw, batth, 2, TFT_SILVER); 89 | canvas->fillRect(x - 2, y - 2, 2, 4, TFT_SILVER); 90 | canvas->fillRect(x + 1, ya + 1, battw - 2 - chgw, batth - 2, 91 | COLOR_DARKGRAY); // 1px margin from outer battery 92 | canvas->fillRect(x + 1 + battw - 2 - chgw, ya + 1, chgw, batth - 2, 93 | batColor); // 1px margin from outer battery 94 | } 95 | 96 | inline void draw_power_symbol(M5Canvas* canvas, int x, int y) 97 | { 98 | canvas->fillArc(x, y, 9, 7, 0, 230, TFT_RED); 99 | canvas->fillArc(x, y, 9, 7, 310, 359, TFT_RED); 100 | canvas->fillRect(x - 1, y - 11, 3, 10, TFT_RED); 101 | } 102 | 103 | inline void draw_input_symbol(M5Canvas* canvas, int x, int y, int bw) 104 | { 105 | canvas->fillTriangle(x + 5, y, x - 3, y - 5, x - 3, y + 5, TFT_SILVER); 106 | canvas->fillRect(x - 10, y - 2, 10, 4, TFT_SILVER); 107 | for (int i = 0; i < 3; i++) 108 | { 109 | int w = bw - 6 - i; 110 | canvas->drawRoundRect(x - w / 2, y - w / 2, w, w, 3, TFT_SILVER); 111 | } 112 | } 113 | 114 | inline void draw_gear_symbol(M5Canvas* canvas, int x, int y) 115 | { 116 | canvas->pushImage(x - gearWidth / 2, y - gearWidth / 2, gearWidth, gearHeight, (uint16_t *)gearData, gearTransparency); 117 | } 118 | 119 | inline void draw_volup_symbol(M5Canvas* canvas, int x, int y) 120 | { canvas->fillTriangle(x - 8, y, x, y + 8, x, y - 8, TFT_SILVER); 121 | canvas->fillRect(x - 10, y - 3, 8, 6, TFT_SILVER); 122 | canvas->fillRect(x + 3, y - 1, 8, 2, TFT_SILVER); 123 | canvas->fillRect(x + 6, y - 4, 2, 8, TFT_SILVER); 124 | } 125 | 126 | inline void draw_mute_symbol(M5Canvas* canvas, int x, int y) 127 | { 128 | canvas->fillTriangle(x - 8, y, x, y + 8, x, y - 8, TFT_SILVER); 129 | canvas->fillRect(x - 10, y - 3, 8, 6, TFT_SILVER); 130 | } 131 | 132 | inline void draw_voldn_symbol(M5Canvas* canvas, int x, int y) 133 | { 134 | canvas->fillTriangle(x - 8, y, x, y + 8, x, y - 8, TFT_SILVER); 135 | canvas->fillRect(x - 10, y - 3, 8, 6, TFT_SILVER); 136 | canvas->fillRect(x + 3, y - 1, 8, 2, TFT_SILVER); 137 | } 138 | 139 | inline void draw_left_symbol(M5Canvas* canvas, int x, int y) 140 | { 141 | canvas->fillTriangle(x - 5, y, x + 2, y + 5, x + 2, y - 5, TFT_SILVER); 142 | } 143 | 144 | inline void draw_up_symbol(M5Canvas* canvas, int x, int y) 145 | { 146 | canvas->fillTriangle(x, y - 3, x - 5, y + 4, x + 5, y + 4, TFT_SILVER); 147 | } 148 | 149 | inline void draw_ok_symbol(M5Canvas* canvas, int x, int y) 150 | { 151 | canvas->fillArc(x, y, 7, 5, 0, 360, TFT_SILVER); 152 | } 153 | 154 | inline void draw_down_symbol(M5Canvas* canvas, int x, int y) 155 | { 156 | canvas->fillTriangle(x, y + 3, x - 5, y - 4, x + 5, y - 4, TFT_SILVER); 157 | } 158 | 159 | inline void draw_right_symbol(M5Canvas* canvas, int x, int y) 160 | { 161 | canvas->fillTriangle(x + 5, y, x - 2, y - 5, x - 2, y + 5, TFT_SILVER); 162 | } 163 | 164 | inline void draw_back_symbol(M5Canvas* canvas, int x, int y) 165 | { 166 | canvas->fillTriangle(x - 8, y - 5, x, y - 10, x, y, TFT_SILVER); 167 | canvas->fillArc(x + 1, y + 1, 8, 5, 255, 65, TFT_SILVER); 168 | } 169 | 170 | inline void draw_display_symbol(M5Canvas* canvas, int x, int y, int bw) 171 | { 172 | for (int i = 0; i < 3; i++) 173 | { 174 | int w = bw - 10 - i; 175 | canvas->drawRoundRect(x - w / 2, y - w / 2, w, w, 3, TFT_SILVER); 176 | } 177 | } 178 | 179 | inline void draw_home_symbol(M5Canvas* canvas, int x, int y) 180 | { 181 | canvas->fillTriangle(x - 9, y - 3, x + 9, y - 3, x, y - 9, TFT_SILVER); 182 | canvas->fillRect(x - 6, y - 3, 12, 12, TFT_SILVER); 183 | canvas->fillRect(x - 3, y + 1, 5, 9, COLOR_DARKRED); 184 | } 185 | 186 | inline void draw_button_symbol(M5Canvas* canvas, Symbol symbol, int x, int y, int bw) { 187 | switch (symbol) { 188 | case Power: 189 | draw_power_symbol(canvas, x, y); 190 | break; 191 | case Input: 192 | draw_input_symbol(canvas, x, y, bw); 193 | break; 194 | case Gear: 195 | draw_gear_symbol(canvas, x, y); 196 | break; 197 | case VolUp: 198 | draw_volup_symbol(canvas, x, y); 199 | break; 200 | case Mute: 201 | draw_mute_symbol(canvas, x, y); 202 | break; 203 | case VolDn: 204 | draw_voldn_symbol(canvas, x, y); 205 | break; 206 | case Left: 207 | draw_left_symbol(canvas, x, y); 208 | break; 209 | case Up: 210 | draw_up_symbol(canvas, x, y); 211 | break; 212 | case Ok: 213 | draw_ok_symbol(canvas, x, y); 214 | break; 215 | case Down: 216 | draw_down_symbol(canvas, x, y); 217 | break; 218 | case Right: 219 | draw_right_symbol(canvas, x, y); 220 | break; 221 | case Back: 222 | draw_back_symbol(canvas, x, y); 223 | break; 224 | case Display: 225 | draw_display_symbol(canvas, x, y, bw); 226 | break; 227 | case Home: 228 | draw_home_symbol(canvas, x, y); 229 | break; 230 | } 231 | } -------------------------------------------------------------------------------- /gear_bmp.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | const uint16_t gearWidth = 26; 5 | const uint16_t gearHeight = 26; 6 | const uint16_t gearTransparency = 0x0000; 7 | 8 | // RGB565 Dump(little endian) 9 | const unsigned short gearData[676] PROGMEM = { 10 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 0, 26 pixels 11 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 1, 52 pixels 12 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 2, 78 pixels 13 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 3, 104 pixels 14 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 4, 130 pixels 15 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 5, 156 pixels 16 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 6, 182 pixels 17 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 7, 208 pixels 18 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 8, 234 pixels 19 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 9, 260 pixels 20 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 10, 286 pixels 21 | 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, // row 11, 312 pixels 22 | 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, // row 12, 338 pixels 23 | 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, // row 13, 364 pixels 24 | 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, // row 14, 390 pixels 25 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 15, 416 pixels 26 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 16, 442 pixels 27 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 17, 468 pixels 28 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 18, 494 pixels 29 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 19, 520 pixels 30 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 20, 546 pixels 31 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 21, 572 pixels 32 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 22, 598 pixels 33 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x18C6, 0x18C6, 0x18C6, 0x18C6, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 23, 624 pixels 34 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 24, 650 pixels 35 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // row 25, 676 pixels 36 | }; -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "draw_helper.h" 7 | #include "remote_keymap.h" 8 | 9 | #define DISABLE_CODE_FOR_RECEIVER 10 | #define SEND_PWM_BY_TIMER 11 | #define IR_TX_PIN 44 12 | 13 | M5Canvas canvas(&M5Cardputer.Display); 14 | 15 | RemoteType remoteType = Sony; 16 | Key *activeRemote; 17 | uint activeRemoteKeyCount; 18 | std::function activeRemoteIrSend; 19 | // TODO: active button map/state for different layouts 20 | 21 | int batteryPct = M5Cardputer.Power.getBatteryLevel(); 22 | int updateDelay = 0; 23 | 24 | // define a bunch of display variables to make adjustments not a nightmare 25 | int w = 240; // width 26 | int h = 135; // height 27 | int xm = 4; // x margin 28 | int ym = 4; // y margin 29 | int rm = 4; // rect margin, used for background graphics 30 | int bw = 28; // button width 31 | int im = 2; // button margin 32 | 33 | // main background rectangle, for remote buttons 34 | int rw = 6 * rm + 6 * bw + 3 * im; 35 | int rh = 2 * rm + 3 * bw + 2 * im; 36 | int rx = w - xm - rw; 37 | int ry = h - ym - rh; 38 | // header rectangle 39 | int hx = rx; 40 | int hy = ym; 41 | int hw = rw; 42 | int hh = h - ym - rh - rm - ym; 43 | // sidebar rectangle 44 | int sx = xm; 45 | int sy = ym; 46 | int sw = rx - xm - rm; 47 | int sh = h - ym - ym; 48 | 49 | // specific to button layout 50 | int r1 = ry + rm; 51 | int r2 = r1 + bw + im; 52 | int r3 = r2 + bw + im; 53 | int c1 = rx + rm; 54 | int c2 = c1 + bw + im; 55 | int c3 = c2 + bw + 2 * rm; 56 | int c4 = c3 + bw + im; 57 | int c5 = c4 + bw + im; 58 | int c6 = c5 + bw + 2 * rm; 59 | // directional square 60 | int dw = c6 - c3; 61 | int dx = c3 - rm; 62 | int dy = ry; 63 | 64 | struct Button 65 | { 66 | char key; 67 | int x; 68 | int y; 69 | int w; 70 | int h; 71 | Symbol symbol; 72 | unsigned short color; 73 | bool pressed; 74 | }; 75 | 76 | Button buttons[] = { 77 | {'`', c1, r1, bw, bw, Power, COLOR_PURPLE, false}, // power 78 | {KEY_TAB, c1, r2, bw, bw, Input, COLOR_PURPLE, false}, // input 79 | {KEY_LEFT_CTRL, c1, r3, bw, bw, Gear, COLOR_PURPLE, false}, // settings 80 | {'s', c2, r1, bw, bw, VolUp, COLOR_PURPLE, false}, // volup 81 | {'m', c2, r2, bw, bw, Mute, COLOR_PURPLE, false}, // mute 82 | {'z', c2, r3, bw, bw, VolDn, COLOR_PURPLE, false}, // voldn 83 | {',', c3, r2, bw, bw, Left, COLOR_BLUE, false}, // left 84 | {';', c4, r1, bw, bw, Up, COLOR_BLUE, false}, // up 85 | {KEY_ENTER, c4, r2, bw, bw, Ok, COLOR_BLUE, false}, // OK 86 | {'.', c4, r3, bw, bw, Down, COLOR_BLUE, false}, // down 87 | {'/', c5, r2, bw, bw, Right, COLOR_BLUE, false}, // right 88 | {KEY_BACKSPACE, c6, r1, bw, bw, Back, COLOR_BLUEGRAY, false}, // back 89 | {'\\', c6, r2, bw, bw, Display, COLOR_BLUEGRAY, false}, // ? 90 | {' ', c6, r3, bw, bw, Home, COLOR_BLUEGRAY, false}, // home 91 | }; 92 | uint8_t buttonCount = sizeof(buttons) / sizeof(Button); 93 | 94 | void setRemoteType(RemoteType type) 95 | { 96 | switch (remoteType) 97 | { 98 | case Sony: 99 | activeRemote = SonyKeyMap; 100 | activeRemoteKeyCount = SonyKeyMapSize; 101 | activeRemoteIrSend = [](int address, int command) 102 | { 103 | IrSender.sendSony(address, command, 1, 12); 104 | }; 105 | break; 106 | case Lg: 107 | activeRemote = LgKeyMap; 108 | activeRemoteKeyCount = LgKeyMapSize; 109 | activeRemoteIrSend = [](int address, int command) 110 | { 111 | IrSender.sendNEC(address, command, 1); 112 | }; 113 | break; 114 | } 115 | } 116 | 117 | void draw() 118 | { 119 | canvas.fillSprite(BLACK); 120 | 121 | // draw background, sidebar, header graphics 122 | canvas.fillRoundRect(hx, hy, hw, hh, 8, COLOR_DARKGRAY); 123 | canvas.fillRoundRect(sx, sy, sw, sh, 8, COLOR_DARKGRAY); 124 | canvas.fillRoundRect(rx, ry, rw, rh, 8, COLOR_DARKGRAY); 125 | 126 | draw_title_text(&canvas, sx + (sw / 2), sy + 20); 127 | draw_remote_type_indicators(&canvas, hx + rm, hy + hh / 2, rm, remoteType); 128 | draw_battery_indicator(&canvas, c6 + 2, hy + (hh / 2), batteryPct); 129 | 130 | // TODO: different button layouts for different remotes 131 | 132 | // draw all layout for remote 133 | canvas.fillRoundRect(dx, dy, dw, dw, 10, COLOR_MEDGRAY); 134 | for (auto button : buttons) 135 | { 136 | unsigned short color = button.pressed ? TFT_ORANGE : button.color; 137 | canvas.fillRoundRect(button.x, button.y, button.w, button.h, 3, color); 138 | draw_button_symbol(&canvas, button.symbol, button.x + button.w / 2, 139 | button.y + button.h / 2, button.w); 140 | } 141 | 142 | canvas.pushSprite(0, 0); 143 | } 144 | 145 | void setup() 146 | { 147 | auto cfg = M5.config(); 148 | M5Cardputer.begin(cfg, true); 149 | 150 | M5Cardputer.Display.setRotation(1); 151 | M5Cardputer.Display.setBrightness(100); 152 | canvas.createSprite(w, h); 153 | 154 | IrSender.begin(DISABLE_LED_FEEDBACK); // Start with IR_SEND_PIN as send pin 155 | IrSender.setSendPin(IR_TX_PIN); 156 | 157 | setRemoteType(Sony); 158 | draw(); 159 | } 160 | 161 | void loop() 162 | { 163 | M5Cardputer.update(); 164 | if (M5Cardputer.Keyboard.isChange()) 165 | { 166 | if (M5Cardputer.Keyboard.isKeyPressed(KEY_FN)) 167 | { 168 | remoteType = (RemoteType)(remoteType + 1); 169 | if (remoteType == Undef1) 170 | remoteType = Sony; 171 | 172 | setRemoteType(remoteType); 173 | } 174 | 175 | for (int i = 0; i < activeRemoteKeyCount; i++) 176 | { 177 | Key key = activeRemote[i]; 178 | 179 | if (M5Cardputer.Keyboard.isKeyPressed(key.key)) 180 | { 181 | activeRemoteIrSend(key.address, key.command); 182 | 183 | // set button to pressed for drawing 184 | for (int i = 0; i < buttonCount; i++) 185 | { 186 | if (key.key == buttons[i].key) 187 | { 188 | buttons[i].pressed = true; 189 | } 190 | } 191 | } 192 | } 193 | 194 | draw(); 195 | delay(60); 196 | for (int i = 0; i < buttonCount; i++) 197 | buttons[i].pressed = false; 198 | draw(); 199 | } 200 | 201 | if (millis() > updateDelay) 202 | { 203 | updateDelay = millis() + 2000; 204 | int newBatteryPct = M5Cardputer.Power.getBatteryLevel(); 205 | if (newBatteryPct != batteryPct) 206 | { 207 | batteryPct = newBatteryPct; 208 | draw(); 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | [platformio] 2 | src_dir = . 3 | 4 | [env:m5cardputer] 5 | platform = espressif32 6 | board = esp32-s3-devkitc-1 7 | framework = arduino 8 | lib_deps = 9 | m5stack/M5Cardputer@^1.0.2 10 | z3t0/IRremote@^4.2.0 11 | -------------------------------------------------------------------------------- /remote_keymap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | enum RemoteType 6 | { 7 | Sony, 8 | Lg, 9 | Undef1, 10 | Undef2, 11 | End 12 | }; 13 | 14 | struct Key 15 | { 16 | char key; 17 | uint16_t address; 18 | uint8_t command; 19 | unsigned long data; 20 | // TODO: also support raw data 21 | // uint8_t dataBits; 22 | // uint8_t keyId; 23 | }; 24 | 25 | Key LgKeyMap[] = { 26 | {'`', 0x4, 0x8, 0}, // power, KEY_ESC 27 | {'s', 0x4, 0x2, 0}, // volup 28 | {'z', 0x4, 0x3, 0}, // voldn 29 | {'m', 0x4, 0x9, 0}, // mute 30 | {';', 0x4, 0x40, 0}, // up 31 | {'.', 0x4, 0x41, 0}, // down 32 | {',', 0x4, 0x7, 0}, // left 33 | {'/', 0x4, 0x6, 0}, // right 34 | {' ', 0x4, 0x7C, 0}, // home 35 | {KEY_ENTER, 0x4, 0x44, 0}, // enter 36 | {KEY_BACKSPACE, 0x4, 0x28, 0}, // back 37 | {KEY_TAB, 0x4, 0xB, 0}, // input 38 | {KEY_LEFT_CTRL, 0x4, 0x43, 0}, // settings 39 | }; 40 | const uint LgKeyMapSize = sizeof(LgKeyMap) / sizeof(Key); 41 | 42 | Key SonyKeyMap[] = { 43 | {'`', 0x1, 0x15, 0}, // power, KEY_ESC 44 | {'s', 0x1, 0x12, 0}, // volup 45 | {'z', 0x1, 0x13, 0}, // voldn 46 | {'m', 0x1, 0x14, 0}, // mute 47 | {';', 0x1, 0x74, 0}, // up 48 | {'.', 0x1, 0x75, 0}, // down 49 | {',', 0x1, 0x34, 0}, // left 50 | {'/', 0x1, 0x33, 0}, // right 51 | {' ', 0x1, 0x60, 0}, // home 52 | {KEY_ENTER, 0x1, 0x65, 0}, // enter 53 | //{KEY_BACKSPACE, 0x97, 0x23, 0}, // back 54 | {KEY_BACKSPACE, 0x1, 0x63, 0}, // back 55 | {KEY_TAB, 0x1, 0x25, 0}, // input 56 | {KEY_LEFT_CTRL, 0x1, 0x36, 0}, // settings 57 | }; 58 | const uint SonyKeyMapSize = sizeof(SonyKeyMap) / sizeof(Key); --------------------------------------------------------------------------------