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