ESP32 Native version of Blinky, featureful controller code for WS2811/WS2812/NeoPixels
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

232 lines
5.5KB

  1. static const char *TAG = "blinky";
  2. #include <esp_log.h>
  3. #include <freertos/FreeRTOS.h>
  4. #include <freertos/task.h>
  5. #include <esp_spiffs.h>
  6. #include <nvs_flash.h>
  7. #include "wifi.hpp"
  8. #include "http_serv.hpp"
  9. #include "ota.hpp"
  10. #include "device.hpp"
  11. #include "utils.hpp"
  12. #include "leds.hpp"
  13. #include "presets.hpp"
  14. #include "utils.hpp"
  15. // mDNS / NetBIOS
  16. #include <mdns.h>
  17. #include <lwip/apps/netbiosns.h>
  18. static void start_mdns_service(const char *hostname) {
  19. // Initialize mDNS service
  20. ESP_ERROR_CHECK(mdns_init());
  21. // Set hostname
  22. mdns_hostname_set(hostname);
  23. // Set default instance
  24. mdns_instance_name_set("Blinky Lights");
  25. // NetBIOS too
  26. netbiosns_init();
  27. netbiosns_set_name(hostname);
  28. }
  29. // Dummy FS
  30. static void start_filesystem() {
  31. ESP_LOGI(TAG, "Initializing filesystem");
  32. esp_vfs_spiffs_conf_t conf = {
  33. .base_path = "/spiffs",
  34. .partition_label = NULL,
  35. .max_files = 5,
  36. .format_if_mount_failed = true
  37. };
  38. // Use settings defined above to initialize and mount SPIFFS filesystem.
  39. // Note: esp_vfs_spiffs_register is an all-in-one convenience function.
  40. ESP_ERROR_CHECK(esp_vfs_spiffs_register(&conf));
  41. }
  42. #include <string>
  43. #include <dirent.h>
  44. #include <sys/stat.h>
  45. #include <errno.h>
  46. static void log_dir(const std::string &path) {
  47. struct stat st;
  48. int err = stat(path.c_str(), &st);
  49. if (err < 0) {
  50. ESP_LOGE(TAG, "%s %d", path.c_str(), errno);
  51. } else {
  52. ESP_LOGI(TAG, "%s %d", path.c_str(), st.st_size);
  53. }
  54. DIR *dir;
  55. if ((dir = opendir(path.c_str())) == NULL) {
  56. //ESP_LOGE(TAG, "Failed to open %s", path.c_str());
  57. return;
  58. }
  59. struct dirent *de;
  60. while ((de = readdir(dir)) != NULL) {
  61. log_dir(path + "/" + de->d_name);
  62. }
  63. closedir(dir);
  64. }
  65. static std::string read_file(const char *path) {
  66. std::string data;
  67. FILE *f = fopen(path, "r");
  68. if (!f) {
  69. ESP_LOGE(TAG, "Failed to read %s", path);
  70. return data;
  71. }
  72. constexpr size_t szbuf = 4096;
  73. char *buf = (char*)malloc(szbuf);
  74. size_t nread;
  75. while ((nread = fread(buf, 1, szbuf - 1, f)) > 0) {
  76. buf[nread] = '\0';
  77. data += buf;
  78. }
  79. fclose(f);
  80. free(buf);
  81. return data;
  82. }
  83. static void write_file(const char *path, const std::string &data) {
  84. FILE *f = fopen(path, "w");
  85. if (!f) {
  86. ESP_LOGE(TAG, "Failed to open %s", path);
  87. return;
  88. }
  89. const char *ptr = data.c_str();
  90. size_t remain = data.length();
  91. size_t nwritten = fwrite(ptr, 1, remain, f);
  92. if (nwritten != remain) {
  93. ESP_LOGE(TAG, "Failed to write to %s: %d/%d", path, nwritten, remain);
  94. }
  95. fclose(f);
  96. }
  97. std::string gen_config(bool is_on, const std::string &effect) {
  98. return std::string() + "{ "
  99. "\"state\": \"" + (is_on ? "ON" : "OFF") + "\", "
  100. "\"effect\": \"" + effect + "\""
  101. " }";
  102. }
  103. constexpr char config_path[] = "/spiffs/blinky.json";
  104. // Entry Point
  105. extern "C" void app_main(void) {
  106. // Initialize NVS for WiFi Data
  107. esp_err_t ret = nvs_flash_init();
  108. if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
  109. ESP_ERROR_CHECK(nvs_flash_erase());
  110. ret = nvs_flash_init();
  111. }
  112. ESP_ERROR_CHECK(ret);
  113. // WiFi
  114. config_wifi();
  115. // mDNS
  116. start_mdns_service("blinky-jr");
  117. // HTTP Server
  118. httpd_handle_t server = start_webserver();
  119. // OTA Server
  120. start_ota_serv(server);
  121. // Dummy Filesystem
  122. start_filesystem();
  123. // TODO: Scrape this out
  124. log_dir("/spiffs");
  125. Device device("blinky-jr");
  126. cJSON *json = cJSON_Parse(read_file(config_path).c_str());
  127. if (json) {
  128. ESP_LOGI(TAG, "Setting config");
  129. device.set_json_config(json);
  130. cJSON_Delete(json);
  131. }
  132. LEDStrip *LEDs = new TerminalLEDs();
  133. while (true) {
  134. ESP_LOGI(TAG, "Configuring LEDs");
  135. device.lock();
  136. const std::string &effect = device.get_effect();
  137. bool is_on = device.is_on();
  138. int length = device.get_strip_length();
  139. cJSON *json = device.make_json_config_locked();
  140. device.unlock();
  141. if (length != LEDs->getLength()) {
  142. LEDs->setLength(length);
  143. }
  144. const Pattern *pattern = Presets::find(effect);
  145. if (pattern) {
  146. LEDs->setPattern(pattern);
  147. } else {
  148. ESP_LOGW(TAG, "Could not find pattern '%s'", effect.c_str());
  149. pattern = LEDs->getPattern();
  150. }
  151. char *config = cJSON_PrintUnformatted(json);
  152. write_file(config_path, config);
  153. ESP_LOGI(TAG, "Saved config");
  154. cJSON_free(config);
  155. cJSON_Delete(json);
  156. if (length <= 0 || !pattern || !is_on) {
  157. if (!is_on) ESP_LOGI(TAG, "Device off");
  158. if (length <= 0) ESP_LOGW(TAG, "No LEDs configured");
  159. if (!pattern) ESP_LOGW(TAG, "No LED pattern set");
  160. ESP_LOGI(TAG, "Waiting for new config");
  161. device.wait();
  162. } else {
  163. ESP_LOGI(TAG, "Starting animation");
  164. int period_us = 1000000 / 4;
  165. int64_t target_us = time_us();
  166. while (true) {
  167. int64_t delay_us = target_us - time_us();
  168. if (delay_us < 0) delay_us = 0;
  169. if (device.wait(delay_us / 1000)) {
  170. // If the semaphore is set, go back to top of outer loop
  171. break;
  172. }
  173. LEDs->step();
  174. LEDs->show();
  175. target_us += period_us;
  176. }
  177. }
  178. }
  179. // Spin
  180. }