├── 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
--------------------------------------------------------------------------------