FabGL
ESP32 Display Controller and Graphics Library
fabutils.cpp
1 /*
2  Created by Fabrizio Di Vittorio (fdivitto2013@gmail.com) - <http://www.fabgl.com>
3  Copyright (c) 2019-2020 Fabrizio Di Vittorio.
4  All rights reserved.
5 
6  This file is part of FabGL Library.
7 
8  FabGL is free software: you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation, either version 3 of the License, or
11  (at your option) any later version.
12 
13  FabGL is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  GNU General Public License for more details.
17 
18  You should have received a copy of the GNU General Public License
19  along with FabGL. If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 
23 #include <string.h>
24 #include <stdlib.h>
25 #include <dirent.h>
26 #include <unistd.h>
27 #include <sys/stat.h>
28 #include <math.h>
29 
30 #include "diskio.h"
31 #include "ff.h"
32 #include "esp_vfs_fat.h"
33 #include "esp_task_wdt.h"
34 #include "driver/sdspi_host.h"
35 #include "sdmmc_cmd.h"
36 #include "esp_spiffs.h"
37 #include "soc/efuse_reg.h"
38 #include "soc/rtc.h"
39 
40 #include "fabutils.h"
45 
46 
47 
48 namespace fabgl {
49 
50 
51 
53 // TimeOut
54 
55 
56 TimeOut::TimeOut()
57  : m_start(esp_timer_get_time())
58 {
59 }
60 
61 
62 bool TimeOut::expired(int valueMS)
63 {
64  return valueMS > -1 && ((esp_timer_get_time() - m_start) / 1000) > valueMS;
65 }
66 
67 
68 
70 // isqrt
71 
72 // Integer square root by Halleck's method, with Legalize's speedup
73 int isqrt (int x)
74 {
75  if (x < 1)
76  return 0;
77  int squaredbit = 0x40000000;
78  int remainder = x;
79  int root = 0;
80  while (squaredbit > 0) {
81  if (remainder >= (squaredbit | root)) {
82  remainder -= (squaredbit | root);
83  root >>= 1;
84  root |= squaredbit;
85  } else
86  root >>= 1;
87  squaredbit >>= 2;
88  }
89  return root;
90 }
91 
92 
93 
95 // calcParity
96 
97 bool calcParity(uint8_t v)
98 {
99  v ^= v >> 4;
100  v &= 0xf;
101  return (0x6996 >> v) & 1;
102 }
103 
104 
106 // realloc32
107 // free32
108 
109 // size must be a multiple of uint32_t (32 bit)
110 void * realloc32(void * ptr, size_t size)
111 {
112  uint32_t * newBuffer = (uint32_t*) heap_caps_malloc(size, MALLOC_CAP_32BIT);
113  if (ptr) {
114  moveItems(newBuffer, (uint32_t*)ptr, size / sizeof(uint32_t));
115  heap_caps_free(ptr);
116  }
117  return newBuffer;
118 }
119 
120 
121 void free32(void * ptr)
122 {
123  heap_caps_free(ptr);
124 }
125 
126 
127 
129 // msToTicks
130 
131 uint32_t msToTicks(int ms)
132 {
133  return ms < 0 ? portMAX_DELAY : pdMS_TO_TICKS(ms);
134 }
135 
136 
138 // getChipPackage
139 
140 ChipPackage getChipPackage()
141 {
142  // read CHIP_VER_PKG (block0, byte 3, 105th bit % 32 = 9, 3 bits)
143  uint32_t ver_pkg = (REG_READ(EFUSE_BLK0_RDATA3_REG) >> 9) & 7;
144  switch (ver_pkg) {
145  case 0:
146  return ChipPackage::ESP32D0WDQ6;
147  case 1:
148  return ChipPackage::ESP32D0WDQ5;
149  case 2:
150  return ChipPackage::ESP32D2WDQ5;
151  case 5:
152  return ChipPackage::ESP32PICOD4;
153  default:
154  return ChipPackage::Unknown;
155  }
156 }
157 
158 
160 adc1_channel_t ADC1_GPIO2Channel(gpio_num_t gpio)
161 {
162  switch (gpio) {
163  case ADC1_CHANNEL_0_GPIO_NUM:
164  return ADC1_CHANNEL_0;
165  case ADC1_CHANNEL_1_GPIO_NUM:
166  return ADC1_CHANNEL_1;
167  case ADC1_CHANNEL_2_GPIO_NUM:
168  return ADC1_CHANNEL_2;
169  case ADC1_CHANNEL_3_GPIO_NUM:
170  return ADC1_CHANNEL_3;
171  case ADC1_CHANNEL_4_GPIO_NUM:
172  return ADC1_CHANNEL_4;
173  case ADC1_CHANNEL_5_GPIO_NUM:
174  return ADC1_CHANNEL_5;
175  case ADC1_CHANNEL_6_GPIO_NUM:
176  return ADC1_CHANNEL_6;
177  case ADC1_CHANNEL_7_GPIO_NUM:
178  return ADC1_CHANNEL_7;
179  default:
180  return ADC1_CHANNEL_0;
181  }
182 }
183 
184 
186 // configureGPIO
187 void configureGPIO(gpio_num_t gpio, gpio_mode_t mode)
188 {
189  PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpio], PIN_FUNC_GPIO);
190  gpio_set_direction(gpio, mode);
191 }
192 
193 
195 // getApbFrequency
196 uint32_t getApbFrequency()
197 {
198  rtc_cpu_freq_config_t conf;
199  rtc_clk_cpu_freq_get_config(&conf);
200  return conf.freq_mhz >= 80 ? 80000000 : (conf.source_freq_mhz * 80000000 / conf.div);
201 }
202 
203 
205 // getCPUFrequencyMHz
206 uint32_t getCPUFrequencyMHz()
207 {
208  rtc_cpu_freq_config_t conf;
209  rtc_clk_cpu_freq_get_config(&conf);
210  return conf.freq_mhz;
211 }
212 
213 
215 // esp_intr_alloc_pinnedToCore
216 
217 struct esp_intr_alloc_args {
218  int source;
219  int flags;
220  intr_handler_t handler;
221  void * arg;
222  intr_handle_t * ret_handle;
223  TaskHandle_t waitingTask;
224 };
225 
226 void esp_intr_alloc_pinnedToCore_task(void * arg)
227 {
228  auto args = (esp_intr_alloc_args*) arg;
229  esp_intr_alloc(args->source, args->flags, args->handler, args->arg, args->ret_handle);
230  xTaskNotifyGive(args->waitingTask);
231  vTaskDelete(NULL);
232 }
233 
234 void esp_intr_alloc_pinnedToCore(int source, int flags, intr_handler_t handler, void * arg, intr_handle_t * ret_handle, int core)
235 {
236  esp_intr_alloc_args args = { source, flags, handler, arg, ret_handle, xTaskGetCurrentTaskHandle() };
237  xTaskCreatePinnedToCore(esp_intr_alloc_pinnedToCore_task, "" , 1024, &args, 3, NULL, core);
238  ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
239 }
240 
241 
242 
244 // converts '\' or '/' to newSep
245 
246 void replacePathSep(char * path, char newSep)
247 {
248  for (; *path; ++path)
249  if (*path == '\\' || *path == '/')
250  *path = newSep;
251 }
252 
253 
254 
256 // Sutherland-Cohen line clipping algorithm
257 
258 static int clipLine_code(int x, int y, Rect const & clipRect)
259 {
260  int code = 0;
261  if (x < clipRect.X1)
262  code = 1;
263  else if (x > clipRect.X2)
264  code = 2;
265  if (y < clipRect.Y1)
266  code |= 4;
267  else if (y > clipRect.Y2)
268  code |= 8;
269  return code;
270 }
271 
272 // false = line is out of clipping rect
273 // true = line intersects or is inside the clipping rect (x1, y1, x2, y2 are changed if checkOnly=false)
274 bool clipLine(int & x1, int & y1, int & x2, int & y2, Rect const & clipRect, bool checkOnly)
275 {
276  int newX1 = x1;
277  int newY1 = y1;
278  int newX2 = x2;
279  int newY2 = y2;
280  int topLeftCode = clipLine_code(newX1, newY1, clipRect);
281  int bottomRightCode = clipLine_code(newX2, newY2, clipRect);
282  while (true) {
283  if ((topLeftCode == 0) && (bottomRightCode == 0)) {
284  if (!checkOnly) {
285  x1 = newX1;
286  y1 = newY1;
287  x2 = newX2;
288  y2 = newY2;
289  }
290  return true;
291  } else if (topLeftCode & bottomRightCode) {
292  break;
293  } else {
294  int x = 0, y = 0;
295  int ncode = topLeftCode != 0 ? topLeftCode : bottomRightCode;
296  if (ncode & 8) {
297  x = newX1 + (newX2 - newX1) * (clipRect.Y2 - newY1) / (newY2 - newY1);
298  y = clipRect.Y2;
299  } else if (ncode & 4) {
300  x = newX1 + (newX2 - newX1) * (clipRect.Y1 - newY1) / (newY2 - newY1);
301  y = clipRect.Y1;
302  } else if (ncode & 2) {
303  y = newY1 + (newY2 - newY1) * (clipRect.X2 - newX1) / (newX2 - newX1);
304  x = clipRect.X2;
305  } else if (ncode & 1) {
306  y = newY1 + (newY2 - newY1) * (clipRect.X1 - newX1) / (newX2 - newX1);
307  x = clipRect.X1;
308  }
309  if (ncode == topLeftCode) {
310  newX1 = x;
311  newY1 = y;
312  topLeftCode = clipLine_code(newX1, newY1, clipRect);
313  } else {
314  newX2 = x;
315  newY2 = y;
316  bottomRightCode = clipLine_code(newX2, newY2, clipRect);
317  }
318  }
319  }
320  return false;
321 }
322 
323 
325 // removeRectangle
326 // remove "rectToRemove" from "mainRect", pushing remaining rectangles to "rects" stack
327 
328 void removeRectangle(Stack<Rect> & rects, Rect const & mainRect, Rect const & rectToRemove)
329 {
330  if (!mainRect.intersects(rectToRemove) || rectToRemove.contains(mainRect))
331  return;
332 
333  // top rectangle
334  if (mainRect.Y1 < rectToRemove.Y1)
335  rects.push(Rect(mainRect.X1, mainRect.Y1, mainRect.X2, rectToRemove.Y1 - 1));
336 
337  // bottom rectangle
338  if (mainRect.Y2 > rectToRemove.Y2)
339  rects.push(Rect(mainRect.X1, rectToRemove.Y2 + 1, mainRect.X2, mainRect.Y2));
340 
341  // left rectangle
342  if (mainRect.X1 < rectToRemove.X1)
343  rects.push(Rect(mainRect.X1, tmax(rectToRemove.Y1, mainRect.Y1), rectToRemove.X1 - 1, tmin(rectToRemove.Y2, mainRect.Y2)));
344 
345  // right rectangle
346  if (mainRect.X2 > rectToRemove.X2)
347  rects.push(Rect(rectToRemove.X2 + 1, tmax(rectToRemove.Y1, mainRect.Y1), mainRect.X2, tmin(rectToRemove.Y2, mainRect.Y2)));
348 }
349 
350 
352 // Rect
353 
354 Rect IRAM_ATTR Rect::merge(Rect const & rect) const
355 {
356  return Rect(imin(rect.X1, X1), imin(rect.Y1, Y1), imax(rect.X2, X2), imax(rect.Y2, Y2));
357 }
358 
359 
360 Rect IRAM_ATTR Rect::intersection(Rect const & rect) const
361 {
362  return Rect(tmax(X1, rect.X1), tmax(Y1, rect.Y1), tmin(X2, rect.X2), tmin(Y2, rect.Y2));
363 }
364 
365 
367 // rgb222_to_hsv
368 // R, G, B in the 0..3 range
369 void rgb222_to_hsv(int R, int G, int B, double * h, double * s, double * v)
370 {
371  double r = R / 3.0;
372  double g = G / 3.0;
373  double b = B / 3.0;
374  double cmax = tmax<double>(tmax<double>(r, g), b);
375  double cmin = tmin<double>(tmin<double>(r, g), b);
376  double diff = cmax - cmin;
377  if (cmax == cmin)
378  *h = 0;
379  else if (cmax == r)
380  *h = fmod((60.0 * ((g - b) / diff) + 360.0), 360.0);
381  else if (cmax == g)
382  *h = fmod((60.0 * ((b - r) / diff) + 120.0), 360.0);
383  else if (cmax == b)
384  *h = fmod((60.0 * ((r - g) / diff) + 240.0), 360.0);
385  *s = cmax == 0 ? 0 : (diff / cmax) * 100.0;
386  *v = cmax * 100.0;
387 }
388 
389 
390 
392 // StringList
393 
394 
395 StringList::StringList()
396  : m_items(nullptr),
397  m_selMap(nullptr),
398  m_ownStrings(false),
399  m_count(0),
400  m_allocated(0)
401 {
402 }
403 
404 
405 StringList::~StringList()
406 {
407  clear();
408 }
409 
410 
411 void StringList::clear()
412 {
413  if (m_ownStrings) {
414  for (int i = 0; i < m_count; ++i)
415  free((void*) m_items[i]);
416  }
417  free32(m_items);
418  free32(m_selMap);
419  m_items = nullptr;
420  m_selMap = nullptr;
421  m_count = m_allocated = 0;
422 }
423 
424 
425 void StringList::copyFrom(StringList const & src)
426 {
427  clear();
428  m_count = src.m_count;
429  checkAllocatedSpace(m_count);
430  for (int i = 0; i < m_count; ++i) {
431  m_items[i] = nullptr;
432  set(i, src.m_items[i]);
433  }
434  deselectAll();
435 }
436 
437 
438 void StringList::checkAllocatedSpace(int requiredItems)
439 {
440  if (m_allocated < requiredItems) {
441  if (m_allocated == 0) {
442  // first time allocates exact space
443  m_allocated = requiredItems;
444  } else {
445  // next times allocate double
446  while (m_allocated < requiredItems)
447  m_allocated *= 2;
448  }
449  m_items = (char const**) realloc32(m_items, m_allocated * sizeof(char const *));
450  m_selMap = (uint32_t*) realloc32(m_selMap, (31 + m_allocated) / 32 * sizeof(uint32_t));
451  }
452 }
453 
454 
455 void StringList::insert(int index, char const * str)
456 {
457  ++m_count;
458  checkAllocatedSpace(m_count);
459  moveItems(m_items + index + 1, m_items + index, m_count - index - 1);
460  m_items[index] = nullptr;
461  set(index, str);
462  deselectAll();
463 }
464 
465 
466 int StringList::append(char const * str)
467 {
468  insert(m_count, str);
469  return m_count - 1;
470 }
471 
472 
473 int StringList::appendFmt(const char *format, ...)
474 {
475  takeStrings();
476  va_list ap;
477  va_start(ap, format);
478  int size = vsnprintf(nullptr, 0, format, ap) + 1;
479  if (size > 0) {
480  va_end(ap);
481  va_start(ap, format);
482  char buf[size + 1];
483  vsnprintf(buf, size, format, ap);
484  insert(m_count, buf);
485  }
486  va_end(ap);
487  return m_count - 1;
488 }
489 
490 
491 void StringList::append(char const * strlist[], int count)
492 {
493  for (int i = 0; i < count; ++i)
494  insert(m_count, strlist[i]);
495 }
496 
497 
498 void StringList::set(int index, char const * str)
499 {
500  if (m_ownStrings) {
501  free((void*)m_items[index]);
502  m_items[index] = (char const*) malloc(strlen(str) + 1);
503  strcpy((char*)m_items[index], str);
504  } else {
505  m_items[index] = str;
506  }
507 }
508 
509 
510 void StringList::remove(int index)
511 {
512  if (m_ownStrings)
513  free((void*)m_items[index]);
514  moveItems(m_items + index, m_items + index + 1, m_count - index - 1);
515  --m_count;
516  deselectAll();
517 }
518 
519 
520 void StringList::takeStrings()
521 {
522  if (!m_ownStrings) {
523  m_ownStrings = true;
524  // take existing strings
525  for (int i = 0; i < m_count; ++i) {
526  char const * str = m_items[i];
527  m_items[i] = nullptr;
528  set(i, str);
529  }
530  }
531 }
532 
533 
534 void StringList::deselectAll()
535 {
536  for (int i = 0; i < (31 + m_count) / 32; ++i)
537  m_selMap[i] = 0;
538 }
539 
540 
541 bool StringList::selected(int index)
542 {
543  return m_selMap[index / 32] & (1 << (index % 32));
544 }
545 
546 
547 void StringList::select(int index, bool value)
548 {
549  if (value)
550  m_selMap[index / 32] |= 1 << (index % 32);
551  else
552  m_selMap[index / 32] &= ~(1 << (index % 32));
553 }
554 
555 
556 
557 // StringList
559 
560 
561 
563 // FileBrowser
564 
565 
566 char const * FileBrowser::s_SPIFFSMountPath;
567 bool FileBrowser::s_SPIFFSMounted = false;
568 size_t FileBrowser::s_SPIFFSMaxFiles;
569 
570 char const * FileBrowser::s_SDCardMountPath;
571 bool FileBrowser::s_SDCardMounted = false;
572 size_t FileBrowser::s_SDCardMaxFiles;
573 int FileBrowser::s_SDCardAllocationUnitSize;
574 int8_t FileBrowser::s_SDCardMISO;
575 int8_t FileBrowser::s_SDCardMOSI;
576 int8_t FileBrowser::s_SDCardCLK;
577 int8_t FileBrowser::s_SDCardCS;
578 
579 
580 
581 FileBrowser::FileBrowser()
582  : m_dir(nullptr),
583  m_count(0),
584  m_items(nullptr),
585  m_sorted(true),
586  m_includeHiddenFiles(false),
587  m_namesStorage(nullptr)
588 {
589 }
590 
591 
592 FileBrowser::~FileBrowser()
593 {
594  clear();
595 
596  free(m_dir);
597  m_dir = nullptr;
598 }
599 
600 
601 void FileBrowser::clear()
602 {
603  free(m_items);
604  m_items = nullptr;
605 
606  free(m_namesStorage);
607  m_namesStorage = nullptr;
608 
609  m_count = 0;
610 }
611 
612 
613 // set absolute directory (full path must be specified)
614 bool FileBrowser::setDirectory(const char * path)
615 {
616  if (m_dir == nullptr || strcmp(path, m_dir) != 0) {
617  free(m_dir);
618  m_dir = strdup(path);
619  }
620  return reload();
621 }
622 
623 
624 // set relative directory:
625 // ".." : go to the parent directory
626 // "dirname": go inside the specified sub directory
627 void FileBrowser::changeDirectory(const char * subdir)
628 {
629  if (!m_dir || strlen(subdir) == 0)
630  return;
631  if (strcmp(subdir, "..") == 0) {
632  // go to parent directory
633  auto lastSlash = strrchr(m_dir, '/');
634  if (lastSlash) {
635  if (lastSlash != m_dir)
636  lastSlash[0] = 0;
637  else
638  lastSlash[1] = 0;
639  reload();
640  }
641  } else {
642  // go to sub directory
643  auto oldLen = strcmp(m_dir, "/") == 0 ? 0 : strlen(m_dir);
644  char * newDir = (char*) malloc(oldLen + 1 + strlen(subdir) + 1); // m_dir + '/' + subdir + 0
645  strcpy(newDir, m_dir);
646  newDir[oldLen] = '/';
647  strcpy(newDir + oldLen + 1, subdir);
648  free(m_dir);
649  m_dir = newDir;
650  reload();
651  }
652 }
653 
654 
655 int FileBrowser::countDirEntries(int * namesLength)
656 {
657  int c = 0;
658  if (strcmp(m_dir, "/") == 0) {
659 
660  // root dir
661  if (s_SPIFFSMounted)
662  ++c;
663  if (s_SDCardMounted)
664  ++c;
665 
666  } else {
667 
668  *namesLength = 0;
669  if (m_dir) {
670  auto dirp = opendir(m_dir);
671  while (dirp) {
672  auto dp = readdir(dirp);
673  if (dp == NULL)
674  break;
675  if (strcmp(".", dp->d_name) && strcmp("..", dp->d_name) && dp->d_type != DT_UNKNOWN) {
676  *namesLength += strlen(dp->d_name) + 1;
677  ++c;
678  }
679  }
680  if (dirp)
681  closedir(dirp);
682  }
683 
684  }
685 
686  return c;
687 }
688 
689 
690 bool FileBrowser::exists(char const * name, bool caseSensitive)
691 {
692  if (caseSensitive) {
693  for (int i = 0; i < m_count; ++i)
694  if (strcmp(name, m_items[i].name) == 0)
695  return true;
696  } else {
697  for (int i = 0; i < m_count; ++i)
698  if (strcasecmp(name, m_items[i].name) == 0)
699  return true;
700  }
701  return false;
702 }
703 
704 
705 size_t FileBrowser::fileSize(char const * name)
706 {
707  size_t size = 0;
708  char fullpath[strlen(m_dir) + 1 + strlen(name) + 1];
709  sprintf(fullpath, "%s/%s", m_dir, name);
710  auto fr = fopen(fullpath, "rb");
711  if (fr) {
712  fseek(fr, 0, SEEK_END);
713  size = ftell(fr);
714  fclose(fr);
715  }
716  return size;
717 }
718 
719 
720 bool FileBrowser::fileCreationDate(char const * name, int * year, int * month, int * day, int * hour, int * minutes, int * seconds)
721 {
722  char fullpath[strlen(m_dir) + 1 + strlen(name) + 1];
723  sprintf(fullpath, "%s/%s", m_dir, name);
724  struct stat s;
725  if (stat(fullpath, &s))
726  return false;
727  auto tm = *localtime((time_t*)&s.st_ctime); // I know, this is not create date, but status change. Anyway I cannot find "st_birthtimespec"
728  *year = 1900 + tm.tm_year;
729  *month = 1 + tm.tm_mon;
730  *day = tm.tm_mday;
731  *hour = tm.tm_hour;
732  *minutes = tm.tm_min;
733  *seconds = imin(tm.tm_sec, 59); // [0, 61] (until C99), [0, 60] (since C99)
734  return true;
735 }
736 
737 
738 bool FileBrowser::fileUpdateDate(char const * name, int * year, int * month, int * day, int * hour, int * minutes, int * seconds)
739 {
740  char fullpath[strlen(m_dir) + 1 + strlen(name) + 1];
741  sprintf(fullpath, "%s/%s", m_dir, name);
742  struct stat s;
743  if (stat(fullpath, &s))
744  return false;
745  auto tm = *localtime((time_t*)&s.st_mtime);
746  *year = 1900 + tm.tm_year;
747  *month = 1 + tm.tm_mon;
748  *day = tm.tm_mday;
749  *hour = tm.tm_hour;
750  *minutes = tm.tm_min;
751  *seconds = imin(tm.tm_sec, 59); // [0, 61] (until C99), [0, 60] (since C99)
752  return true;
753 }
754 
755 
756 bool FileBrowser::fileAccessDate(char const * name, int * year, int * month, int * day, int * hour, int * minutes, int * seconds)
757 {
758  char fullpath[strlen(m_dir) + 1 + strlen(name) + 1];
759  sprintf(fullpath, "%s/%s", m_dir, name);
760  struct stat s;
761  if (stat(fullpath, &s))
762  return false;
763  auto tm = *localtime((time_t*)&s.st_atime);
764  *year = 1900 + tm.tm_year;
765  *month = 1 + tm.tm_mon;
766  *day = tm.tm_mday;
767  *hour = tm.tm_hour;
768  *minutes = tm.tm_min;
769  *seconds = imin(tm.tm_sec, 59); // [0, 61] (until C99), [0, 60] (since C99)
770  return true;
771 }
772 
773 
774 
775 int DirComp(const void * i1, const void * i2)
776 {
777  DirItem * d1 = (DirItem*)i1;
778  DirItem * d2 = (DirItem*)i2;
779  if (d1->isDir != d2->isDir) // directories first
780  return d1->isDir ? -1 : +1;
781  else
782  return strcmp(d1->name, d2->name);
783 }
784 
785 
787 {
788  bool retval = true;
789 
790  clear();
791  int namesAlloc;
792  int c = countDirEntries(&namesAlloc);
793  m_items = (DirItem*) malloc(sizeof(DirItem) * (c + 1));
794  m_namesStorage = (char*) malloc(namesAlloc);
795  char * sname = m_namesStorage;
796 
797  if (strcmp(m_dir, "/") == 0) {
798 
799  // root dir
800  if (s_SPIFFSMounted) {
801  m_items[m_count].name = s_SPIFFSMountPath + 1; // +1 to bypass "/"
802  m_items[m_count].isDir = true;
803  ++m_count;
804  }
805  if (s_SDCardMounted) {
806  m_items[m_count].name = s_SDCardMountPath + 1; // +1 to bypass "/"
807  m_items[m_count].isDir = true;
808  ++m_count;
809  }
810 
811  } else {
812 
813  // first item is always ".."
814  m_items[0].name = "..";
815  m_items[0].isDir = true;
816  ++m_count;
817 
818  int hiddenFilesCount = 0;
819  auto dirp = opendir(m_dir);
820  while (dirp) {
821  auto dp = readdir(dirp);
822  if (dp == NULL)
823  break;
824  if (strcmp(".", dp->d_name) && strcmp("..", dp->d_name) && dp->d_type != DT_UNKNOWN) {
825  DirItem * di = m_items + m_count;
826  // check if this is a simulated directory (like in SPIFFS)
827  auto slashPos = strchr(dp->d_name, '/');
828  if (slashPos) {
829  // yes, this is a simulated dir. Trunc and avoid to insert it twice
830  auto len = slashPos - dp->d_name;
831  strncpy(sname, dp->d_name, len);
832  sname[len] = 0;
833  if (!exists(sname)) {
834  di->name = sname;
835  di->isDir = true;
836  sname += len + 1;
837  ++m_count;
838  }
839  } else {
840  bool isHidden = dp->d_name[0] == '.';
841  if (!isHidden || m_includeHiddenFiles) {
842  strcpy(sname, dp->d_name);
843  di->name = sname;
844  di->isDir = (dp->d_type == DT_DIR);
845  sname += strlen(sname) + 1;
846  ++m_count;
847  }
848  if (isHidden)
849  ++hiddenFilesCount;
850  }
851  }
852  }
853  if (dirp)
854  closedir(dirp);
855  else
856  retval = false;
857 
858  // for SPIFFS "opendir" returns always true, even for not existing dirs. An hidden file means there is the SPIFFS directory placeholder
859  if (m_count == 1 && hiddenFilesCount == 0 && getDriveType(m_dir) == DriveType::SPIFFS)
860  retval = false; // no hidden files, so the directory doesn't exist
861 
862  }
863 
864  if (m_sorted)
865  qsort(m_items, m_count, sizeof(DirItem), DirComp);
866 
867  return retval;
868 }
869 
870 
871 // note: for SPIFFS this creates an empty ".dirname" file. The SPIFFS path is detected when path starts with "/spiffs" or s_SPIFFSMountPath
872 // "dirname" is not a path, just a directory name created inside "m_dir"
873 void FileBrowser::makeDirectory(char const * dirname)
874 {
875  int dirnameLen = strlen(dirname);
876  if (dirnameLen > 0) {
878  // simulated directory, puts an hidden placeholder
879  char fullpath[strlen(m_dir) + 3 + 2 * dirnameLen + 1];
880  auto name = dirname;
881  while (*name) {
882  auto next = name + 1;
883  while (*next && *next != '\\' && *next != '/')
884  ++next;
885  strcpy(fullpath, m_dir);
886  if (dirname != name) {
887  strcat(fullpath, "/");
888  strncat(fullpath, dirname, name - dirname - 1);
889  }
890  strcat(fullpath, "/");
891  strncat(fullpath, name, next - name);
892  strcat(fullpath, "/.");
893  strncat(fullpath, name, next - name);
894  replacePathSep(fullpath, '/');
895  FILE * f = fopen(fullpath, "wb");
896  fclose(f);
897  if (*next == 0)
898  break;
899  name = next + 1;
900  }
901 
902  } else {
903  char fullpath[strlen(m_dir) + 1 + dirnameLen + 1];
904  sprintf(fullpath, "%s/%s", m_dir, dirname);
905  replacePathSep(fullpath, '/');
906  mkdir(fullpath, ACCESSPERMS);
907  }
908  }
909 }
910 
911 
912 // removes a file or a directory (and all files inside it)
913 // The SPIFFS path is detected when path starts with "/spiffs" or s_SPIFFSMountPath
914 // "name" is not a path, just a file or directory name inside "m_dir"
915 void FileBrowser::remove(char const * name)
916 {
917  char fullpath[strlen(m_dir) + 1 + strlen(name) + 1];
918  sprintf(fullpath, "%s/%s", m_dir, name);
919  int r = unlink(fullpath);
920 
921  if (r != 0) {
922  // failed, try to remove directory
923  r = rmdir(fullpath);
924  }
925 
926  if (r != 0) {
927  // failed, try simulated directory in SPIFFS
929  // simulated directory
930  // maybe this is a directory, remove ".dir" file
931  char hidpath[strlen(m_dir) + 3 + 2 * strlen(name) + 1];
932  sprintf(hidpath, "%s/%s/.%s", m_dir, name, name);
933  unlink(hidpath);
934  // maybe the directory contains files, remove all
935  auto dirp = opendir(fullpath);
936  while (dirp) {
937  auto dp = readdir(dirp);
938  if (dp == NULL)
939  break;
940  if (strcmp(".", dp->d_name) && strcmp("..", dp->d_name) && dp->d_type != DT_UNKNOWN) {
941  char sfullpath[strlen(fullpath) + 1 + strlen(dp->d_name) + 1];
942  sprintf(sfullpath, "%s/%s", fullpath, dp->d_name);
943  unlink(sfullpath);
944  }
945  }
946  closedir(dirp);
947  }
948  }
949 }
950 
951 
952 // works only for files
953 void FileBrowser::rename(char const * oldName, char const * newName)
954 {
955  char oldfullpath[strlen(m_dir) + 1 + strlen(oldName) + 1];
956  sprintf(oldfullpath, "%s/%s", m_dir, oldName);
957 
958  char newfullpath[strlen(m_dir) + 1 + strlen(newName) + 1];
959  sprintf(newfullpath, "%s/%s", m_dir, newName);
960 
961  ::rename(oldfullpath, newfullpath);
962 }
963 
964 
965 // return a full path
967 {
968  constexpr int FLEN = 6;
969  auto ret = (char*) malloc(strlen(m_dir) + 1 + FLEN + 4 + 1);
970  while (true) {
971  char name[FLEN + 1] = { 0 };
972  for (int i = 0; i < FLEN; ++i)
973  name[i] = 65 + (rand() % 26);
974  sprintf(ret, "%s/%s.TMP", m_dir, name);
975  if (!exists(name))
976  return ret;
977  }
978 }
979 
980 
981 bool FileBrowser::truncate(char const * name, size_t size)
982 {
983  constexpr size_t BUFLEN = 512;
984 
985  char fullpath[strlen(m_dir) + 1 + strlen(name) + 1];
986  sprintf(fullpath, "%s/%s", m_dir, name);
987 
988  // in future maybe...
989  //::truncate(name, size);
990 
991  bool retval = false;
992 
993  // for now...
994  char * tempFilename = createTempFilename();
995  if (::rename(fullpath, tempFilename) == 0) {
996  void * buf = malloc(BUFLEN);
997  if (buf) {
998  auto fr = fopen(tempFilename, "rb");
999  if (fr) {
1000  auto fw = fopen(fullpath, "wb");
1001  if (fw) {
1002 
1003  while (size > 0) {
1004  auto l = fread(buf, 1, tmin(size, BUFLEN), fr);
1005  if (l == 0)
1006  break;
1007  fwrite(buf, 1, l, fw);
1008  size -= l;
1009  }
1010 
1011  // just in case truncate is used to expand the file
1012  for (; size > 0; --size)
1013  fputc(0, fw);
1014 
1015  retval = true;
1016  fclose(fw);
1017  }
1018  fclose(fr);
1019  }
1020  }
1021  free(buf);
1022  unlink(tempFilename);
1023  }
1024  free(tempFilename);
1025  return retval;
1026 }
1027 
1028 
1029 // concatenates current directory and specified name and store result into fullpath
1030 // Specifying outPath=nullptr returns required length
1031 int FileBrowser::getFullPath(char const * name, char * outPath, int maxlen)
1032 {
1033  return (outPath ? snprintf(outPath, maxlen, "%s/%s", m_dir, name) : snprintf(nullptr, 0, "%s/%s", m_dir, name)) + 1;
1034 }
1035 
1036 
1037 FILE * FileBrowser::openFile(char const * filename, char const * mode)
1038 {
1039  char fullpath[strlen(m_dir) + 1 + strlen(filename) + 1];
1040  strcpy(fullpath, m_dir);
1041  strcat(fullpath, "/");
1042  strcat(fullpath, filename);
1043 
1044  replacePathSep(fullpath, '/');
1045 
1046  return fopen(fullpath, mode);
1047 }
1048 
1049 
1051 {
1052  return getDriveType(m_dir);
1053 }
1054 
1055 
1057 {
1058  if (strncmp(path, "/spiffs", 7) == 0 || (s_SPIFFSMounted && strncmp(path, s_SPIFFSMountPath, strlen(s_SPIFFSMountPath)) == 0)) {
1059  return DriveType::SPIFFS;
1060  } else if (s_SDCardMounted && strncmp(path, s_SDCardMountPath, strlen(s_SDCardMountPath)) == 0) {
1061  return DriveType::SDCard;
1062  } else {
1063  return DriveType::None;
1064  }
1065 }
1066 
1067 
1068 bool FileBrowser::format(DriveType driveType, int drive)
1069 {
1070  esp_task_wdt_init(45, false);
1071 
1072  if (driveType == DriveType::SDCard && s_SDCardMounted) {
1073 
1074  // unmount filesystem
1075  char drv[3] = {(char)('0' + drive), ':', 0};
1076  f_mount(0, drv, 0);
1077 
1078  void * buffer = malloc(FF_MAX_SS);
1079  if (!buffer)
1080  return false;
1081 
1082  // create partition
1083  DWORD plist[] = { 100, 0, 0, 0 };
1084  if (f_fdisk(drive, plist, buffer) != FR_OK) {
1085  free(buffer);
1086  return false;
1087  }
1088 
1089  // make filesystem
1090  if (f_mkfs(drv, FM_ANY, 16 * 1024, buffer, FF_MAX_SS) != FR_OK) {
1091  free(buffer);
1092  return false;
1093  }
1094 
1095  free(buffer);
1096 
1097  remountSDCard();
1098 
1099  return true;
1100 
1101  } else if (driveType == DriveType::SPIFFS && s_SPIFFSMounted) {
1102 
1103  // driveType == DriveType::SPIFFS
1104  bool r = (esp_spiffs_format(nullptr) == ESP_OK);
1105 
1106  remountSPIFFS();
1107 
1108  return r;
1109 
1110  } else
1111  return false;
1112 }
1113 
1114 
1115 bool FileBrowser::mountSDCard(bool formatOnFail, char const * mountPath, size_t maxFiles, int allocationUnitSize, int MISO, int MOSI, int CLK, int CS)
1116 {
1117  if (getChipPackage() == ChipPackage::ESP32PICOD4) {
1118  MISO = 2;
1119  MOSI = 12;
1120  }
1121 
1122  s_SDCardMountPath = mountPath;
1123  s_SDCardMaxFiles = maxFiles;
1124  s_SDCardAllocationUnitSize = allocationUnitSize;
1125  s_SDCardMISO = MISO;
1126  s_SDCardMOSI = MOSI;
1127  s_SDCardCLK = CLK;
1128  s_SDCardCS = CS;
1129 
1130  sdmmc_host_t host = SDSPI_HOST_DEFAULT();
1131  sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT();
1132  slot_config.gpio_miso = int2gpio(MISO);
1133  slot_config.gpio_mosi = int2gpio(MOSI);
1134  slot_config.gpio_sck = int2gpio(CLK);
1135  slot_config.gpio_cs = int2gpio(CS);
1136  esp_vfs_fat_sdmmc_mount_config_t mount_config;
1137  mount_config.format_if_mount_failed = formatOnFail;
1138  mount_config.max_files = maxFiles;
1139  mount_config.allocation_unit_size = allocationUnitSize;
1140  sdmmc_card_t* card;
1141  s_SDCardMounted = (esp_vfs_fat_sdmmc_mount(mountPath, &host, &slot_config, &mount_config, &card) == ESP_OK);
1142  return s_SDCardMounted;
1143 }
1144 
1145 
1147 {
1148  if (s_SDCardMounted) {
1149  esp_vfs_fat_sdmmc_unmount();
1150  s_SDCardMounted = false;
1151  }
1152 }
1153 
1154 
1156 {
1157  unmountSDCard();
1158  return mountSDCard(false, s_SDCardMountPath, s_SDCardMaxFiles, s_SDCardAllocationUnitSize, s_SDCardMISO, s_SDCardMOSI, s_SDCardCLK, s_SDCardCS);
1159 }
1160 
1161 
1162 bool FileBrowser::mountSPIFFS(bool formatOnFail, char const * mountPath, size_t maxFiles)
1163 {
1164  s_SPIFFSMountPath = mountPath;
1165  s_SPIFFSMaxFiles = maxFiles;
1166  esp_vfs_spiffs_conf_t conf = {
1167  .base_path = mountPath,
1168  .partition_label = nullptr,
1169  .max_files = maxFiles,
1170  .format_if_mount_failed = true
1171  };
1172  s_SPIFFSMounted = (esp_vfs_spiffs_register(&conf) == ESP_OK);
1173  return s_SPIFFSMounted;
1174 }
1175 
1176 
1178 {
1179  if (s_SPIFFSMounted) {
1180  esp_vfs_spiffs_unregister(nullptr);
1181  s_SPIFFSMounted = false;
1182  }
1183 }
1184 
1185 
1187 {
1188  unmountSPIFFS();
1189  return mountSPIFFS(false, s_SPIFFSMountPath, s_SPIFFSMaxFiles);
1190 }
1191 
1192 
1193 bool FileBrowser::getFSInfo(DriveType driveType, int drive, int64_t * total, int64_t * used)
1194 {
1195  *total = *used = 0;
1196 
1197  if (driveType == DriveType::SDCard) {
1198 
1199  FATFS * fs;
1200  DWORD free_clusters;
1201  char drv[3] = {(char)('0' + drive), ':', 0};
1202  if (f_getfree(drv, &free_clusters, &fs) != FR_OK)
1203  return false;
1204  int64_t total_sectors = (fs->n_fatent - 2) * fs->csize;
1205  int64_t free_sectors = free_clusters * fs->csize;
1206  *total = total_sectors * fs->ssize;
1207  *used = *total - free_sectors * fs->ssize;
1208 
1209  return true;
1210 
1211  } else if (driveType == DriveType::SPIFFS) {
1212 
1213  size_t stotal = 0, sused = 0;
1214  if (esp_spiffs_info(NULL, &stotal, &sused) != ESP_OK)
1215  return false;
1216  *total = stotal;
1217  *used = sused;
1218  return true;
1219 
1220  } else
1221  return false;
1222 }
1223 
1224 
1225 // FileBrowser
1227 
1228 
1229 
1230 
1232 // LightMemoryPool
1233 
1234 
1235 void LightMemoryPool::mark(int pos, int16_t size, bool allocated)
1236 {
1237  m_mem[pos] = size & 0xff;
1238  m_mem[pos + 1] = ((size >> 8) & 0x7f) | (allocated ? 0x80 : 0);
1239 }
1240 
1241 
1242 int16_t LightMemoryPool::getSize(int pos)
1243 {
1244  return m_mem[pos] | ((m_mem[pos + 1] & 0x7f) << 8);
1245 }
1246 
1247 
1248 bool LightMemoryPool::isFree(int pos)
1249 {
1250  return (m_mem[pos + 1] & 0x80) == 0;
1251 }
1252 
1253 
1254 LightMemoryPool::LightMemoryPool(int poolSize)
1255 {
1256  m_poolSize = poolSize + 2;
1257  m_mem = (uint8_t*) heap_caps_malloc(m_poolSize, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
1258  mark(0, m_poolSize - 2, false);
1259 }
1260 
1261 
1262 LightMemoryPool::~LightMemoryPool()
1263 {
1264  heap_caps_free(m_mem);
1265 }
1266 
1267 
1268 void * LightMemoryPool::alloc(int size)
1269 {
1270  for (int pos = 0; pos < m_poolSize; ) {
1271  int16_t blockSize = getSize(pos);
1272  if (isFree(pos)) {
1273  if (blockSize == size) {
1274  // found a block having the same size
1275  mark(pos, size, true);
1276  return m_mem + pos + 2;
1277  } else if (blockSize > size) {
1278  // found a block having larger size
1279  int remainingSize = blockSize - size - 2;
1280  if (remainingSize > 0)
1281  mark(pos + 2 + size, remainingSize, false); // create new free block at the end of this block
1282  else
1283  size = blockSize; // to avoid to waste last block
1284  mark(pos, size, true); // reduce size of this block and mark as allocated
1285  return m_mem + pos + 2;
1286  } else {
1287  // this block hasn't enough space
1288  // can merge with next block?
1289  int nextBlockPos = pos + 2 + blockSize;
1290  if (nextBlockPos < m_poolSize && isFree(nextBlockPos)) {
1291  // join blocks and stay at this pos
1292  mark(pos, blockSize + getSize(nextBlockPos) + 2, false);
1293  } else {
1294  // move to the next block
1295  pos += blockSize + 2;
1296  }
1297  }
1298  } else {
1299  // move to the next block
1300  pos += blockSize + 2;
1301  }
1302  }
1303  return nullptr;
1304 }
1305 
1306 
1307 bool LightMemoryPool::memCheck()
1308 {
1309  int pos = 0;
1310  while (pos < m_poolSize) {
1311  int16_t blockSize = getSize(pos);
1312  pos += blockSize + 2;
1313  }
1314  return pos == m_poolSize;
1315 }
1316 
1317 
1318 int LightMemoryPool::totFree()
1319 {
1320  int r = 0;
1321  for (int pos = 0; pos < m_poolSize; ) {
1322  int16_t blockSize = getSize(pos);
1323  if (isFree(pos))
1324  r += blockSize;
1325  pos += blockSize + 2;
1326  }
1327  return r;
1328 }
1329 
1330 
1331 int LightMemoryPool::totAllocated()
1332 {
1333  int r = 0;
1334  for (int pos = 0; pos < m_poolSize; ) {
1335  int16_t blockSize = getSize(pos);
1336  if (!isFree(pos))
1337  r += blockSize;
1338  pos += blockSize + 2;
1339  }
1340  return r;
1341 }
1342 
1343 
1344 int LightMemoryPool::largestFree()
1345 {
1346  int r = 0;
1347  for (int pos = 0; pos < m_poolSize; ) {
1348  int16_t blockSize = getSize(pos);
1349  if (isFree(pos) && blockSize > r)
1350  r = blockSize;
1351  pos += blockSize + 2;
1352  }
1353  return r;
1354 }
1355 
1356 
1357 // LightMemoryPool
1359 
1360 
1361 
1363 // CoreUsage
1364 
1365 
1366 int CoreUsage::s_busiestCore = -1;
1367 
1368 
1369 // CoreUsage
1371 
1372 
1373 }
1374 
int16_t Y1
Definition: fabutils.h:193
char const * name
Definition: fabutils.h:450
This file contains fabgl::PS2Controller definition.
This file contains fabgl::VGA16Controller definition.
bool truncate(char const *name, size_t size)
Truncates a file to the specified size.
Definition: fabutils.cpp:981
void rename(char const *oldName, char const *newName)
Renames a file.
Definition: fabutils.cpp:953
void remove(char const *name)
Removes a file or directory.
Definition: fabutils.cpp:915
static bool mountSDCard(bool formatOnFail, char const *mountPath, size_t maxFiles=4, int allocationUnitSize=16 *1024, int MISO=16, int MOSI=17, int CLK=14, int CS=13)
Mounts filesystem on SD Card.
Definition: fabutils.cpp:1115
bool setDirectory(const char *path)
Sets absolute directory path.
Definition: fabutils.cpp:614
uint8_t B
This file contains fabgl::VGAController definition.
int16_t X1
Definition: fabutils.h:192
size_t fileSize(char const *name)
Determines file size.
Definition: fabutils.cpp:705
bool fileUpdateDate(char const *name, int *year, int *month, int *day, int *hour, int *minutes, int *seconds)
Gets file update date and time.
Definition: fabutils.cpp:738
bool fileCreationDate(char const *name, int *year, int *month, int *day, int *hour, int *minutes, int *seconds)
Gets file creation date and time.
Definition: fabutils.cpp:720
This file contains fabgl::VGA2Controller definition.
static void unmountSDCard()
Unmounts filesystem on SD Card.
Definition: fabutils.cpp:1146
uint8_t G
This file contains some utility classes and functions.
Definition: canvas.cpp:31
FileBrowser item specificator.
Definition: fabutils.h:448
char * createTempFilename()
Creates a random temporary filename, with absolute path.
Definition: fabutils.cpp:966
int getFullPath(char const *name, char *outPath=nullptr, int maxlen=0)
Composes a full file path given a relative name.
Definition: fabutils.cpp:1031
int16_t Y2
Definition: fabutils.h:195
DriveType getCurrentDriveType()
Returns the drive type of current directory.
Definition: fabutils.cpp:1050
static bool format(DriveType driveType, int drive)
Formats SPIFFS or SD Card.
Definition: fabutils.cpp:1068
DriveType
This enum defines drive types (SPIFFS or SD Card)
Definition: fabutils.h:457
uint8_t R
static bool remountSDCard()
Remounts SDCard filesystem, using the same parameters.
Definition: fabutils.cpp:1155
void makeDirectory(char const *dirname)
Creates a directory.
Definition: fabutils.cpp:873
bool fileAccessDate(char const *name, int *year, int *month, int *day, int *hour, int *minutes, int *seconds)
Gets file access date and time.
Definition: fabutils.cpp:756
static DriveType getDriveType(char const *path)
Returns the drive type of specified path.
Definition: fabutils.cpp:1056
int16_t X2
Definition: fabutils.h:194
void changeDirectory(const char *subdir)
Sets relative directory path.
Definition: fabutils.cpp:627
FILE * openFile(char const *filename, char const *mode)
Opens a file from current directory.
Definition: fabutils.cpp:1037
static bool mountSPIFFS(bool formatOnFail, char const *mountPath, size_t maxFiles=4)
Mounts filesystem on SPIFFS (Flash)
Definition: fabutils.cpp:1162
bool exists(char const *name, bool caseSensitive=true)
Determines if a file or directory exists.
Definition: fabutils.cpp:690
bool reload()
Reloads directory content.
Definition: fabutils.cpp:786
static bool getFSInfo(DriveType driveType, int drive, int64_t *total, int64_t *used)
Gets total and free space on a filesystem.
Definition: fabutils.cpp:1193
static bool remountSPIFFS()
Remounts SPIFFS filesystem, using the same parameters.
Definition: fabutils.cpp:1186
static void unmountSPIFFS()
Unmounts filesystem on SPIFFS (Flash)
Definition: fabutils.cpp:1177