├── 3d-design ├── enclosure-bottom v6.stl └── enclosure-top v9.stl ├── Readme.md ├── config.h ├── debug.cfg ├── debug_custom.json ├── esp32.svd ├── flipper_zero.ino └── images ├── image1.jpeg └── image2.jpeg /3d-design/enclosure-bottom v6.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmadfathan/flipper-lite/577e1bd4ab55e21c65fe168d2bd6b76ba0ec498c/3d-design/enclosure-bottom v6.stl -------------------------------------------------------------------------------- /3d-design/enclosure-top v9.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmadfathan/flipper-lite/577e1bd4ab55e21c65fe168d2bd6b76ba0ec498c/3d-design/enclosure-top v9.stl -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### FLIPPER LITE 3 | 4 | This project tries to duplicate [Flipper Zero](https://flipperzero.one/) with minimum functionality, IR cloner and RFID UID reader/writer 5 | 6 | #### MENU ROUTE 7 | | | | | | 8 | |-------------|-------------|--------------|------------| 9 | | > Main Menu | > IR Signal | > Add Signal | > Signal 1 | 10 | | | | | > Signal 2 | 11 | | | | | > Signal 3 | 12 | | | | > IR Signal | > Signal 1 | 13 | | | | | > Signal 2 | 14 | | | | | > Signal 3 | 15 | | | > RFID | > Read Tag | | 16 | | | | > Write Tag | | 17 | 18 | #### EEPROM Address Mapping 19 | 20 | ##### 1. IR Signal 21 | |Address From|Address To|Remark| 22 | |-|-|-| 23 | |0|999|IR Signal 1| 24 | |1000|1999|IR Signal 2| 25 | |2000|2999|IR Signal 3| 26 | 27 | ##### 2. RFID 28 | 29 | |Address From|Address To|Remark| 30 | |-|-|-| 31 | |3000|30009|UID 1 (currently only 1 RFID Tag)| 32 | 33 | #### Dependency 34 | 35 | 1. Display, https://github.com/olikraus/u8g2 36 | 2. IR send/receive, https://github.com/crankyoldgit/IRremoteESP8266 37 | 3. MFRC522, https://github.com/miguelbalboa/rfid 38 | 39 | #### Showcase 40 | 41 |
42 | 43 | 44 |
45 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | /** IR Signal **/ 5 | #define MAX_IR_SIGNAL_LEN 1000 // maximum of IR signal length 6 | #define MAX_IR_SIGNAL_COUNT 3 // total of IR signal saved 7 | 8 | #define ADD_IR_SIGNAL_TIMEOUT 5000 9 | 10 | #define IR_LED_PIN 4 // GPIO 4, where IR LED attached 11 | #define IR_RECEIVER_PIN 15 // GPIO 15, where IR Recevier attached 12 | #define IR_RECV_BUFFER_SIZE 1024 // multiply by 2-byte (size of uint16_t) 13 | #define IR_RECV_TIMEOUT 15 // in ms 14 | #define IR_RECV_TOLERANCE_PERCENTAGE 25 // tolerance percentage in % 15 | 16 | #define CARRIER_SIGNAL_FREQ 38 // in kHz 17 | 18 | /** Menu Button **/ 19 | #define MENU_SELECT_PIN 18 // GPIO 18 20 | #define MENU_NEXT_PIN 19 // GPIO 19 21 | #define MENU_PREV_PIN 5 // GPIO 5 22 | 23 | /** Display **/ 24 | #define DISPLAY_FONT u8g2_font_9x15_tr // font of LED display, refer to https://github.com/olikraus/u8g2/wiki/fntlistall 25 | 26 | /** EEPROM **/ 27 | #define EEPROM_SIZE 4096 // in byte 28 | #define ADDR_RFID_TAG_UID 3000 29 | 30 | /** MFRC522 Module **/ 31 | #define RST_PIN 26 32 | #define SS_PIN 27 33 | #define MOSI_PIN 13 34 | #define MISO_PIN 12 35 | #define SCK_PIN 14 36 | 37 | #define READ_TAG_UID_TIMEOUT 5000 38 | #define WRITE_TAG_UID_TIMEOUT 5000 39 | 40 | #endif -------------------------------------------------------------------------------- /debug.cfg: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-or-later 2 | # 3 | # Example OpenOCD configuration file for ESP32-WROVER-KIT board. 4 | # 5 | # For example, OpenOCD can be started for ESP32 debugging on 6 | # 7 | # openocd -f board/esp32-wrover-kit-3.3v.cfg 8 | # 9 | 10 | # Source the JTAG interface configuration file 11 | source [find interface/ftdi/esp32_devkitj_v1.cfg] 12 | set ESP32_FLASH_VOLTAGE 3.3 13 | # Source the ESP32 configuration file 14 | source [find target/esp32.cfg] 15 | -------------------------------------------------------------------------------- /debug_custom.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"Arduino on ESP32", 3 | "toolchainPrefix":"xtensa-esp32-elf", 4 | "svdFile":"esp32.svd", 5 | "request":"attach", 6 | "postAttachCommands":[ 7 | "set remote hardware-watchpoint-limit 2", 8 | "monitor reset halt", 9 | "monitor gdb_sync", 10 | "thb setup", 11 | "c" 12 | ], 13 | "overrideRestartCommands":[ 14 | "monitor reset halt", 15 | "monitor gdb_sync", 16 | "thb setup", 17 | "c" 18 | ] 19 | } -------------------------------------------------------------------------------- /flipper_zero.ino: -------------------------------------------------------------------------------- 1 | // Native Library 2 | #include 3 | #include "EEPROM.h" 4 | #include 5 | 6 | #include 7 | 8 | // https://github.com/olikraus/u8g2 9 | #include 10 | 11 | // https://github.com/crankyoldgit/IRremoteESP8266 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | #include "config.h" 20 | 21 | IRrecv irrecv(IR_RECEIVER_PIN, IR_RECV_BUFFER_SIZE, IR_RECV_TIMEOUT, true); 22 | IRsend irsend(IR_LED_PIN); // Set the GPIO to be used to sending the message. 23 | 24 | decode_results results; // Somewhere to store the results 25 | 26 | U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/SCL, /* data=*/SDA, /* reset=*/U8X8_PIN_NONE); // All Boards without Reset of the Display 27 | 28 | MFRC522 mfrc522(SS_PIN, RST_PIN); // Create MFRC522 instance 29 | 30 | 31 | 32 | struct Route { 33 | uint8_t pos; 34 | char* title; 35 | char* list; 36 | Route* children; 37 | uint8_t num_of_children; 38 | }; 39 | 40 | Route signal_route[] = { 41 | Route{ 1, "Signal 1" }, 42 | Route{ 2, "Signal 2" }, 43 | Route{ 3, "Signal 3" }, 44 | Route{ 4, "Back" }, 45 | }; 46 | 47 | Route ir_signal_route[] = { 48 | Route{ 1, "Add IR Signal", "Signal 1\n" 49 | "Signal 2\n" 50 | "Signal 3\n" 51 | "Back", 52 | &signal_route[0], 4 }, 53 | Route{ 2, "Emit IR Signal", "Signal 1\n" 54 | "Signal 2\n" 55 | "Signal 3\n" 56 | "Back", 57 | &signal_route[0], 4 }, 58 | Route{ 3, "Back" } 59 | }; 60 | 61 | Route rfid_route[] = { 62 | Route{ 1, "Read Tag" }, 63 | Route{ 2, "Write Tag" }, 64 | Route{ 3, "Back" }, 65 | }; 66 | 67 | Route main_menu_route[] = { 68 | Route{ 1, "IR Signal", "Add IR Signal\n" 69 | "Emit IR Signal\n" 70 | "Back", 71 | &ir_signal_route[0], 3 }, 72 | Route{ 2, "RFID", "Read Tag\n" 73 | "Write Tag\n" 74 | "Back", 75 | &rfid_route[0], 3 }, 76 | Route{ 3, "Back" } 77 | }; 78 | 79 | Route root = Route{ 1, "Main Menu", "IR Signal\n" 80 | "RFID", 81 | &main_menu_route[0], 2 }; 82 | 83 | Route current_route = root; 84 | Route back_stack[10]; 85 | uint8_t route_pos = 0; 86 | uint8_t last_selection = 0; 87 | 88 | void setup(void) { 89 | 90 | WiFi.disconnect(true); 91 | WiFi.mode(WIFI_OFF); 92 | 93 | Serial.begin(115200); 94 | 95 | SPI.begin(SCK_PIN, MISO_PIN, MOSI_PIN, SS_PIN); 96 | mfrc522.PCD_Init(); // Init MFRC522 card 97 | 98 | irrecv.setTolerance(IR_RECV_TOLERANCE_PERCENTAGE); // Override the default tolerance. 99 | irsend.begin(); 100 | 101 | EEPROM.begin(EEPROM_SIZE); 102 | 103 | u8g2.setBusClock(5000000); 104 | 105 | u8g2.begin(MENU_SELECT_PIN, MENU_NEXT_PIN, MENU_PREV_PIN, /* menu_up_pin= */ U8X8_PIN_NONE, /* menu_down_pin= */ U8X8_PIN_NONE, /* menu_home_pin= */ U8X8_PIN_NONE); 106 | 107 | u8g2.setFont(DISPLAY_FONT); 108 | 109 | back_stack[route_pos] = current_route; 110 | } 111 | 112 | void loop(void) { 113 | 114 | uint8_t current_selection = u8g2.userInterfaceSelectionList( 115 | current_route.title, 116 | (last_selection == 0) ? current_route.children[0].pos : last_selection, 117 | current_route.list); 118 | 119 | Route* next_route = find_next_route_by_pos(current_selection); 120 | 121 | if (next_route == NULL) return; 122 | 123 | last_selection = current_selection; 124 | 125 | if (next_route->title == "Back") { 126 | route_pos--; 127 | current_route = back_stack[route_pos]; 128 | last_selection = 0; 129 | return; 130 | } else if (next_route->title == "Read Tag") { 131 | read_tag_uid(); 132 | return; 133 | } else if (next_route->title == "Write Tag") { 134 | write_tag_uid(); 135 | return; 136 | } else if (next_route->title == "Signal 1") { 137 | if (current_route.title == "Emit IR Signal") { 138 | emit_ir_signal(1); 139 | } else if (current_route.title == "Add IR Signal") { 140 | u8g2.clearBuffer(); 141 | u8g2.drawStr(0, 10, "Waiting.."); 142 | u8g2.sendBuffer(); 143 | 144 | add_ir_signal(1); 145 | 146 | u8g2.clearBuffer(); 147 | u8g2.drawStr(0, 10, "Done"); 148 | u8g2.sendBuffer(); 149 | } 150 | return; 151 | } else if (next_route->title == "Signal 2") { 152 | if (current_route.title == "Emit IR Signal") { 153 | emit_ir_signal(2); 154 | } else if (current_route.title == "Add IR Signal") { 155 | u8g2.clearBuffer(); 156 | u8g2.drawStr(0, 10, "Waiting.."); 157 | u8g2.sendBuffer(); 158 | 159 | add_ir_signal(2); 160 | 161 | u8g2.clearBuffer(); 162 | u8g2.drawStr(0, 10, "Done"); 163 | u8g2.sendBuffer(); 164 | } 165 | return; 166 | } else if (next_route->title == "Signal 3") { 167 | if (current_route.title == "Emit IR Signal") { 168 | emit_ir_signal(3); 169 | } else if (current_route.title == "Add IR Signal") { 170 | u8g2.clearBuffer(); 171 | u8g2.drawStr(0, 10, "Recording.."); 172 | u8g2.sendBuffer(); 173 | 174 | add_ir_signal(3); 175 | 176 | u8g2.clearBuffer(); 177 | u8g2.drawStr(0, 10, "Done"); 178 | u8g2.sendBuffer(); 179 | } 180 | return; 181 | } 182 | 183 | route_pos++; 184 | current_route = *next_route; 185 | back_stack[route_pos] = current_route; 186 | last_selection = 0; 187 | } 188 | 189 | Route* find_next_route_by_pos(uint8_t pos) { 190 | for (uint8_t i = 0; i < current_route.num_of_children; i++) { 191 | if (pos == current_route.children[i].pos) { 192 | return ¤t_route.children[i]; 193 | } 194 | } 195 | return NULL; 196 | } 197 | 198 | void add_ir_signal(uint8_t signal_num) { 199 | 200 | if (signal_num > MAX_IR_SIGNAL_COUNT) return; // abort if signal num is exceeded 201 | 202 | uint16_t addr_signal_len = MAX_IR_SIGNAL_LEN * (signal_num - 1); 203 | uint16_t addr_signal = addr_signal_len + 2; 204 | 205 | irrecv.enableIRIn(); // enable IR receiver 206 | 207 | uint64_t now = millis(); 208 | while (true) { 209 | if (irrecv.decode(&results)) { 210 | 211 | uint16_t rawdata[1024]; 212 | uint16_t j = 0; 213 | for (uint16_t i = 1; i < results.rawlen; i++, j++) { 214 | uint32_t usecs = results.rawbuf[i] * kRawTick; 215 | rawdata[j] = usecs; 216 | } 217 | 218 | EEPROM.writeUShort(addr_signal_len, j); 219 | 220 | for (uint16_t i = 0; i < j; i++) { 221 | EEPROM.writeUShort(addr_signal + (i * 2), rawdata[i]); 222 | } 223 | 224 | EEPROM.commit(); 225 | 226 | break; 227 | } 228 | if (millis() - now > ADD_IR_SIGNAL_TIMEOUT) break; 229 | } 230 | 231 | irrecv.disableIRIn(); // disable IR receiver 232 | } 233 | 234 | void emit_ir_signal(uint8_t signal_num) { 235 | 236 | if (signal_num > MAX_IR_SIGNAL_COUNT) return; 237 | 238 | uint16_t addr_signal_len = MAX_IR_SIGNAL_LEN * (signal_num - 1); 239 | uint16_t signal_len = EEPROM.readUShort(addr_signal_len); 240 | 241 | if (signal_len == 0) { 242 | u8g2.clearBuffer(); 243 | u8g2.drawStr(0, 10, "Signal Not Set"); 244 | u8g2.sendBuffer(); 245 | return; 246 | } 247 | 248 | u8g2.clearBuffer(); 249 | u8g2.drawStr(0, 10, "Emiting.."); 250 | u8g2.sendBuffer(); 251 | 252 | uint16_t raw_data[signal_len]; 253 | 254 | uint16_t addr_signal = addr_signal_len + 2; 255 | 256 | for (uint16_t i = 0; i < signal_len; i++) { 257 | raw_data[i] = EEPROM.readUShort(addr_signal + (i * 2)); 258 | } 259 | 260 | irsend.sendRaw(raw_data, signal_len, CARRIER_SIGNAL_FREQ); 261 | } 262 | 263 | void write_tag_uid() { 264 | uint16_t addr_rfid_tag_uid_len = ADDR_RFID_TAG_UID; 265 | uint8_t uid_len = EEPROM.readByte(addr_rfid_tag_uid_len); 266 | 267 | uint16_t addr_rfid_tag_uid = addr_rfid_tag_uid_len + 1; 268 | 269 | String uid_str = ""; 270 | 271 | uint8_t uid[uid_len]; 272 | 273 | for (uint8_t i = 0; i < uid_len; i++, addr_rfid_tag_uid++) { 274 | uid[i] = EEPROM.readByte(addr_rfid_tag_uid); 275 | 276 | uid_str += String(uid[i], 16) + " "; 277 | } 278 | 279 | uid_str.toUpperCase(); 280 | 281 | u8g2.clearBuffer(); 282 | u8g2.drawStr(0, 10, "Writing TAG.."); 283 | u8g2.drawStr(0, 50, &uid_str[0]); 284 | u8g2.sendBuffer(); 285 | 286 | uint64_t now = millis(); 287 | bool success = false; 288 | char* err_title = "Timeout"; 289 | char* err_msg = ""; 290 | while (true) { 291 | if (millis() - now > WRITE_TAG_UID_TIMEOUT) break; 292 | if (!mfrc522.PICC_IsNewCardPresent() || !mfrc522.PICC_ReadCardSerial()) { 293 | delay(50); 294 | continue; 295 | } 296 | 297 | // Write tag UID 298 | if (!mfrc522.MIFARE_SetUid(uid, uid_len, true)) { 299 | err_title = "Failed"; 300 | err_msg = "Not supported"; 301 | success = false; 302 | break; 303 | } 304 | 305 | success = true; 306 | break; 307 | } 308 | 309 | uint8_t selection = u8g2.userInterfaceMessage( 310 | success ? "Success" : err_title, 311 | "", 312 | success ? "" : err_msg, 313 | success ? " ok " : " ok \n retry "); 314 | 315 | if (selection == 2) { // retry 316 | write_tag_uid(); 317 | } 318 | } 319 | 320 | void read_tag_uid() { 321 | 322 | u8g2.clearBuffer(); 323 | u8g2.drawStr(0, 10, "Reading TAG.."); 324 | u8g2.sendBuffer(); 325 | 326 | String uid_str = ""; 327 | uint64_t now = millis(); 328 | bool found = false; 329 | while (true) { 330 | if (millis() - now > READ_TAG_UID_TIMEOUT) break; 331 | if (!mfrc522.PICC_IsNewCardPresent() || !mfrc522.PICC_ReadCardSerial()) { 332 | delay(50); 333 | continue; 334 | } 335 | 336 | // RFID Tag found 337 | uint16_t addr_rfid_tag_uid_len = ADDR_RFID_TAG_UID; 338 | EEPROM.writeByte(addr_rfid_tag_uid_len, mfrc522.uid.size); 339 | 340 | uint16_t addr_rfid_tag_uid = addr_rfid_tag_uid_len + 1; 341 | 342 | // Dump UID 343 | for (uint8_t i = 0; i < mfrc522.uid.size; i++, addr_rfid_tag_uid++) { 344 | 345 | EEPROM.writeByte(addr_rfid_tag_uid, mfrc522.uid.uidByte[i]); 346 | 347 | uid_str += String(mfrc522.uid.uidByte[i], 16) + " "; 348 | } 349 | 350 | uid_str.toUpperCase(); 351 | 352 | EEPROM.commit(); 353 | 354 | found = true; 355 | break; 356 | } 357 | 358 | uint8_t selection = u8g2.userInterfaceMessage( 359 | found ? "Saved" : "Timeout", 360 | found ? &uid_str[0] : "", 361 | "", 362 | found ? " ok " : " ok \n retry "); 363 | 364 | if (selection == 2) { // retry 365 | read_tag_uid(); 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /images/image1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmadfathan/flipper-lite/577e1bd4ab55e21c65fe168d2bd6b76ba0ec498c/images/image1.jpeg -------------------------------------------------------------------------------- /images/image2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmadfathan/flipper-lite/577e1bd4ab55e21c65fe168d2bd6b76ba0ec498c/images/image2.jpeg --------------------------------------------------------------------------------