diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e2529002..d7621d51 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -70,15 +70,24 @@ jobs: #run: echo "Testing... ${{ github.ref_name }}, ${{ steps.vars.outputs.sha_short }}" > ./sd-card/html/version.txt; mkdir -p ./code/.pio/build/esp32cam/; cd ./code/.pio/build/esp32cam/; echo "${{ steps.vars.outputs.sha_short }}" > firmware.bin; cp firmware.bin partitions.bin; cp firmware.bin bootloader.bin # Testing run: cd code; platformio run --environment esp32cam - - name: Prepare Web UI (copy data from repo and update hashes in all files) + - name: Prepare Web UI (copy data from repo, generate tooltip pages and update hashes in all files) run: | rm -rf ./html mkdir html - cp ./sd-card/html/* ./html/ + cp -r ./sd-card/html/* ./html/ + + python -m pip install markdown + mkdir html/param-tooltips + cd tools/parameter-tooltip-generator + bash generate-param-doc-tooltips.sh + cd ../.. + + cp -r ./sd-card/html/* ./html/ + + echo "Replacing variables..." cd html; find . -type f -exec sed -i 's/$COMMIT_HASH/${{ steps.vars.outputs.sha_short }}/g' {} \; - ######################################################################################### ## Pack for Update ######################################################################################### @@ -86,7 +95,7 @@ jobs: # New OTA concept # update__version.zip file with following content: # - /firmware.bin - # - (optional) /html/* + # - (optional) /html/* (inkl. subfolders) # - (optional) /config/*.tfl runs-on: ubuntu-latest needs: build @@ -149,7 +158,7 @@ jobs: # New Remote Setup concept # remote_setup__version.zip file with following content: # - /firmware.bin - # - /html/* + # - /html/* (inkl. subfolders) # - /config/* runs-on: ubuntu-latest needs: build diff --git a/.gitignore b/.gitignore index 089c9ea4..2e3ae032 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ CTestTestfile.cmake _deps code/edgeAI.code-workspace .DS_Store +tools/parameter-tooltip-generator/html +tools/parameter-tooltip-generator/AI-on-the-edge-device-docs diff --git a/Changelog.md b/Changelog.md index 6d3fd03f..1fed5b1e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,44 @@ +## [Unreleased] + +xxx + +### Update Procedure + +Update Procedure see [online documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/#update-ota-over-the-air) + +:bangbang: Afterwards you should force-reload the Web Interface (usually Ctrl-F5 will do it). + +### Changes + +This release only migrates some parameters, see #2023 for details and a list of all parameter changes. +The parameter migration happens automatically on the next startup. No user interaction is required. +A backup of the config is stored on the SD-card as `config.bak`. + +Beside of the parameter change and the bugfix listed below, no changes are contained in this release! + +If you want to revert back to `v14` or earlier, you will have to revert the migration changes in `config.ini` manually! + +#### Added + +- Additional interface to InfluxDB Version 2 upwards +- Updated the Hybrid CNN network to `dig-cont_0610_s3` +- :bangbang: Update Camera driver: contrast, brightness and saturation now working + + :bangbang: **Attention**: this can effect old version as well, because there not all settings were effective! + +#### Changed + +- [#2023](https://github.com/jomjol/AI-on-the-edge-device/pull/2023) Migrated Parameters +- Removed old `Topic` parameter, it is not used anymore + +#### Fixed +- n.a. + +#### Removed + +- n.a. + + ## [15.0.3] - 2023-02-28 **Parameter Migration** @@ -37,7 +78,8 @@ If you want to revert back to `v14` or earlier, you will have to revert the migr - n.a. -## [14.0.3] - 2023-02-05 + +## [14.0.3] -2023-02-05 **Stabilization and Improved User Experience** @@ -841,7 +883,7 @@ External Illumination - Initial Version -[15.0.1]: https://github.com/jomjol/AI-on-the-edge-device/compare/v14.0.3...v15.0.1 +[15.0.3]: https://github.com/jomjol/AI-on-the-edge-device/compare/v14.0.3...v15.0.3 [14.0.3]: https://github.com/jomjol/AI-on-the-edge-device/compare/v13.0.8...v14.0.3 [13.0.8]: https://github.com/jomjol/AI-on-the-edge-device/compare/v12.0.1...v13.0.8 [13.0.7]: https://github.com/jomjol/AI-on-the-edge-device/compare/v12.0.1...v13.0.7 diff --git a/code/components/jomjol_controlcamera/ClassControllCamera.cpp b/code/components/jomjol_controlcamera/ClassControllCamera.cpp index e1f04091..13dd7ab3 100644 --- a/code/components/jomjol_controlcamera/ClassControllCamera.cpp +++ b/code/components/jomjol_controlcamera/ClassControllCamera.cpp @@ -7,6 +7,7 @@ #include "esp_log.h" #include "Helper.h" +#include "statusled.h" #include "CImageBasis.h" #include "server_ota.h" @@ -557,15 +558,17 @@ void CCamera::LightOnOff(bool status) void CCamera::LEDOnOff(bool status) { - // Init the GPIO - gpio_pad_select_gpio(BLINK_GPIO); - /* Set the GPIO as a push/pull output */ - gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT); + if (xHandle_task_StatusLED == NULL) { + // Init the GPIO + gpio_pad_select_gpio(BLINK_GPIO); + /* Set the GPIO as a push/pull output */ + gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT); - if (!status) - gpio_set_level(BLINK_GPIO, 1); - else - gpio_set_level(BLINK_GPIO, 0); + if (!status) + gpio_set_level(BLINK_GPIO, 1); + else + gpio_set_level(BLINK_GPIO, 0); + } } diff --git a/code/components/jomjol_fileserver_ota/server_file.cpp b/code/components/jomjol_fileserver_ota/server_file.cpp index 3c795f24..07a2c7c0 100644 --- a/code/components/jomjol_fileserver_ota/server_file.cpp +++ b/code/components/jomjol_fileserver_ota/server_file.cpp @@ -228,7 +228,7 @@ static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath, const if (chunksize > 0){ if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) { fclose(fd); - ESP_LOGE(TAG, "File sending failed!"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File sending failed!"); return ESP_FAIL; } } @@ -267,7 +267,7 @@ static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath, const strlcpy(entrypath + dirpath_len, entry->d_name, sizeof(entrypath) - dirpath_len); ESP_LOGD(TAG, "Entrypath: %s", entrypath); if (stat(entrypath, &entry_stat) == -1) { - ESP_LOGE(TAG, "Failed to stat %s: %s", entrytype, entry->d_name); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to stat " + string(entrytype) + ": " + string(entry->d_name)); continue; } @@ -357,7 +357,7 @@ static esp_err_t send_datafile(httpd_req_t *req, bool send_full_file) fd = fopen(currentfilename.c_str(), "r"); if (!fd) { - LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to read file: " + std::string(currentfilename) +"!"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to read file: " + currentfilename + "!"); /* Respond with 404 Error */ httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, get404()); return ESP_FAIL; @@ -373,7 +373,7 @@ static esp_err_t send_datafile(httpd_req_t *req, bool send_full_file) /* Adapted from https://www.geeksforgeeks.org/implement-your-own-tail-read-last-n-lines-of-a-huge-file/ */ if (fseek(fd, 0, SEEK_END)) { - ESP_LOGE(TAG, "Failed to get to end of file!"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to get to end of file!"); return ESP_FAIL; } else { @@ -381,7 +381,7 @@ static esp_err_t send_datafile(httpd_req_t *req, bool send_full_file) ESP_LOGI(TAG, "File contains %ld bytes", pos); if (fseek(fd, pos - std::min((long)LOGFILE_LAST_PART_BYTES, pos), SEEK_SET)) { // Go LOGFILE_LAST_PART_BYTES bytes back from EOF - ESP_LOGE(TAG, "Failed to go back %ld bytes within the file!", std::min((long)LOGFILE_LAST_PART_BYTES, pos)); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to go back " + to_string(std::min((long)LOGFILE_LAST_PART_BYTES, pos)) + " bytes within the file!"); return ESP_FAIL; } } @@ -404,7 +404,7 @@ static esp_err_t send_datafile(httpd_req_t *req, bool send_full_file) /* Send the buffer contents as HTTP response chunk */ if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) { fclose(fd); - ESP_LOGE(TAG, "File sending failed!"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File sending failed!"); /* Abort sending file */ httpd_resp_sendstr_chunk(req, NULL); /* Respond with 500 Internal Server Error */ @@ -443,7 +443,7 @@ static esp_err_t send_logfile(httpd_req_t *req, bool send_full_file) fd = fopen(currentfilename.c_str(), "r"); if (!fd) { - LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to read file: " + std::string(currentfilename.c_str()) +"!"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to read file: " + currentfilename + "!"); /* Respond with 404 Error */ httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, get404()); return ESP_FAIL; @@ -459,7 +459,7 @@ static esp_err_t send_logfile(httpd_req_t *req, bool send_full_file) /* Adapted from https://www.geeksforgeeks.org/implement-your-own-tail-read-last-n-lines-of-a-huge-file/ */ if (fseek(fd, 0, SEEK_END)) { - ESP_LOGE(TAG, "Failed to get to end of file!"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to get to end of file!"); return ESP_FAIL; } else { @@ -467,7 +467,7 @@ static esp_err_t send_logfile(httpd_req_t *req, bool send_full_file) ESP_LOGI(TAG, "File contains %ld bytes", pos); if (fseek(fd, pos - std::min((long)LOGFILE_LAST_PART_BYTES, pos), SEEK_SET)) { // Go LOGFILE_LAST_PART_BYTES bytes back from EOF - ESP_LOGE(TAG, "Failed to go back %ld bytes within the file!", std::min((long)LOGFILE_LAST_PART_BYTES, pos)); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to go back " + to_string(std::min((long)LOGFILE_LAST_PART_BYTES, pos)) + " bytes within the file!"); return ESP_FAIL; } } @@ -490,7 +490,7 @@ static esp_err_t send_logfile(httpd_req_t *req, bool send_full_file) /* Send the buffer contents as HTTP response chunk */ if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) { fclose(fd); - ESP_LOGE(TAG, "File sending failed!"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File sending failed!"); /* Abort sending file */ httpd_resp_sendstr_chunk(req, NULL); /* Respond with 500 Internal Server Error */ @@ -530,7 +530,7 @@ static esp_err_t download_get_handler(httpd_req_t *req) if (!filename) { - ESP_LOGE(TAG, "Filename is too long"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Filename is too long"); /* Respond with 414 Error */ httpd_resp_send_err(req, HTTPD_414_URI_TOO_LONG, "Filename too long"); return ESP_FAIL; @@ -571,7 +571,7 @@ static esp_err_t download_get_handler(httpd_req_t *req) fd = fopen(filepath, "r"); if (!fd) { - LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to read file: " + std::string(filepath) +"!"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to read file: " + std::string(filepath) + "!"); /* Respond with 404 Error */ httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, get404()); return ESP_FAIL; @@ -592,7 +592,7 @@ static esp_err_t download_get_handler(httpd_req_t *req) /* Send the buffer contents as HTTP response chunk */ if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) { fclose(fd); - ESP_LOGE(TAG, "File sending failed!"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File sending failed!"); /* Abort sending file */ httpd_resp_sendstr_chunk(req, NULL); /* Respond with 500 Internal Server Error */ @@ -634,14 +634,14 @@ static esp_err_t upload_post_handler(httpd_req_t *req) /* Filename cannot have a trailing '/' */ if (filename[strlen(filename) - 1] == '/') { - ESP_LOGE(TAG, "Invalid filename: %s", filename); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Invalid filename: " + string(filename)); /* Respond with 400 Bad Request */ httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid filename"); return ESP_FAIL; } if (stat(filepath, &file_stat) == 0) { - ESP_LOGE(TAG, "File already exists: %s", filepath); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File already exists: " + string(filepath)); /* Respond with 400 Bad Request */ httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "File already exists"); return ESP_FAIL; @@ -649,7 +649,7 @@ static esp_err_t upload_post_handler(httpd_req_t *req) /* File cannot be larger than a limit */ if (req->content_len > MAX_FILE_SIZE) { - ESP_LOGE(TAG, "File too large: %d bytes", req->content_len); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File too large: " + to_string(req->content_len) + " bytes"); /* Respond with 400 Bad Request */ httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "File size must be less than " @@ -661,7 +661,7 @@ static esp_err_t upload_post_handler(httpd_req_t *req) fd = fopen(filepath, "w"); if (!fd) { - ESP_LOGE(TAG, "Failed to create file: %s", filepath); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to create file: " + string(filepath)); /* Respond with 500 Internal Server Error */ httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to create file"); return ESP_FAIL; @@ -692,7 +692,7 @@ static esp_err_t upload_post_handler(httpd_req_t *req) fclose(fd); unlink(filepath); - ESP_LOGE(TAG, "File reception failed!"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File reception failed!"); /* Respond with 500 Internal Server Error */ httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive file"); return ESP_FAIL; @@ -705,7 +705,7 @@ static esp_err_t upload_post_handler(httpd_req_t *req) fclose(fd); unlink(filepath); - ESP_LOGE(TAG, "File write failed!"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File write failed!"); /* Respond with 500 Internal Server Error */ httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to write file to storage"); return ESP_FAIL; @@ -780,7 +780,7 @@ static esp_err_t delete_post_handler(httpd_req_t *req) if (httpd_query_key_value(_query, "task", _valuechar, 30) == ESP_OK) { - ESP_LOGD(TAG, "task is found: %s", _valuechar); + LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "task is found: " + string(_valuechar)); _task = std::string(_valuechar); } } @@ -826,26 +826,26 @@ static esp_err_t delete_post_handler(httpd_req_t *req) /* Filename cannot have a trailing '/' */ if (filename[strlen(filename) - 1] == '/') { - ESP_LOGE(TAG, "Invalid filename: %s", filename); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Invalid filename: " + string(filename)); /* Respond with 400 Bad Request */ httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid filename"); return ESP_FAIL; } if (strcmp(filename, "wlan.ini") == 0) { - ESP_LOGE(TAG, "Trying to delete protected file : %s", filename); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to delete protected file : " + string(filename)); httpd_resp_send_err(req, HTTPD_403_FORBIDDEN, "Not allowed to delete wlan.ini"); return ESP_FAIL; } if (stat(filepath, &file_stat) == -1) { - ESP_LOGE(TAG, "File does not exist: %s", filename); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File does not exist: " + string(filename)); /* Respond with 400 Bad Request */ httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "File does not exist"); return ESP_FAIL; } - ESP_LOGI(TAG, "Deleting file: %s", filename); + LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Deleting file: " + string(filename)); /* Delete file */ unlink(filepath); @@ -887,7 +887,7 @@ void delete_all_in_directory(std::string _directory) std::string filename; if (!dir) { - ESP_LOGE(TAG, "Failed to stat dir: %s", _directory.c_str()); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to stat dir: " + _directory); return; } @@ -896,7 +896,7 @@ void delete_all_in_directory(std::string _directory) if (!(entry->d_type == DT_DIR)){ if (strcmp("wlan.ini", entry->d_name) != 0){ // auf wlan.ini soll nicht zugegriffen werden !!! filename = _directory + "/" + std::string(entry->d_name); - ESP_LOGI(TAG, "Deleting file: %s", filename.c_str()); + LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Deleting file: " + filename); /* Delete file */ unlink(filename.c_str()); } @@ -930,7 +930,7 @@ std::string unzip_new(std::string _in_zip_file, std::string _target_zip, std::st // Get and print information about each file in the archive. int numberoffiles = (int)mz_zip_reader_get_num_files(&zip_archive); - ESP_LOGI(TAG, "Numbers of files to be extracted: %d", numberoffiles); + LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Numbers of files to be extracted: " + to_string(numberoffiles)); sort_iter = 0; { @@ -953,7 +953,7 @@ std::string unzip_new(std::string _in_zip_file, std::string _target_zip, std::st p = mz_zip_reader_extract_file_to_heap(&zip_archive, archive_filename, &uncomp_size, 0); if (!p) { - ESP_LOGE(TAG, "mz_zip_reader_extract_file_to_heap() failed on file %s", archive_filename); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "mz_zip_reader_extract_file_to_heap() failed on file " + string(archive_filename)); mz_zip_reader_end(&zip_archive); return ret; } @@ -1015,21 +1015,22 @@ std::string unzip_new(std::string _in_zip_file, std::string _target_zip, std::st else { isokay = false; - ESP_LOGD(TAG, "ERROR in writting extracted file (function fwrite) extracted file \"%s\", size %u", archive_filename, (uint)uncomp_size); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ERROR in writting extracted file (function fwrite) extracted file \"" + + string(archive_filename) + "\", size " + to_string(uncomp_size)); } DeleteFile(zw); if (!isokay) - ESP_LOGE(TAG, "ERROR in fwrite \"%s\", size %u", archive_filename, (uint)uncomp_size); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ERROR in fwrite \"" + string(archive_filename) + "\", size " + to_string(uncomp_size)); isokay = isokay && RenameFile(filename_zw, zw); if (!isokay) - ESP_LOGE(TAG, "ERROR in Rename \"%s\" to \"%s\"", filename_zw.c_str(), zw.c_str()); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ERROR in Rename \"" + filename_zw + "\" to \"" + zw); if (isokay) - ESP_LOGI(TAG, "Successfully extracted file \"%s\", size %u", archive_filename, (uint)uncomp_size); + LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Successfully extracted file \"" + string(archive_filename) + "\", size " + to_string(uncomp_size)); else { - ESP_LOGE(TAG, "ERROR in extracting file \"%s\", size %u", archive_filename, (uint)uncomp_size); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ERROR in extracting file \"" + string(archive_filename) + "\", size " + to_string(uncomp_size)); ret = "ERROR"; } mz_free(p); @@ -1063,7 +1064,7 @@ void unzip(std::string _in_zip_file, std::string _target_directory){ status = mz_zip_reader_init_file(&zip_archive, _in_zip_file.c_str(), 0); if (!status) { - ESP_LOGD(TAG, "mz_zip_reader_init_file() failed!"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "mz_zip_reader_init_file() failed!"); return; } @@ -1075,7 +1076,7 @@ void unzip(std::string _in_zip_file, std::string _target_directory){ status = mz_zip_reader_init_file(&zip_archive, _in_zip_file.c_str(), sort_iter ? MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY : 0); if (!status) { - ESP_LOGD(TAG, "mz_zip_reader_init_file() failed!"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "mz_zip_reader_init_file() failed!"); return; } @@ -1089,7 +1090,7 @@ void unzip(std::string _in_zip_file, std::string _target_directory){ p = mz_zip_reader_extract_file_to_heap(&zip_archive, archive_filename, &uncomp_size, 0); if (!p) { - ESP_LOGD(TAG, "mz_zip_reader_extract_file_to_heap() failed!"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "mz_zip_reader_extract_file_to_heap() failed!"); mz_zip_reader_end(&zip_archive); return; } @@ -1125,19 +1126,19 @@ void register_server_file_uri(httpd_handle_t server, const char *base_path) /* Validate file storage base path */ if (!base_path) { // if (!base_path || strcmp(base_path, "/spiffs") != 0) { - ESP_LOGE(TAG, "File server base_path not set"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File server base_path not set"); // return ESP_ERR_INVALID_ARG; } if (server_data) { - ESP_LOGE(TAG, "File server already started"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File server already started"); // return ESP_ERR_INVALID_STATE; } /* Allocate memory for server data */ server_data = (file_server_data *) calloc(1, sizeof(struct file_server_data)); if (!server_data) { - ESP_LOGE(TAG, "Failed to allocate memory for server data"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to allocate memory for server data"); // return ESP_ERR_NO_MEM; } strlcpy(server_data->base_path, base_path, diff --git a/code/components/jomjol_fileserver_ota/server_ota.cpp b/code/components/jomjol_fileserver_ota/server_ota.cpp index bea0a4ad..14390925 100644 --- a/code/components/jomjol_fileserver_ota/server_ota.cpp +++ b/code/components/jomjol_fileserver_ota/server_ota.cpp @@ -39,6 +39,7 @@ #include "ClassLogFile.h" #include "Helper.h" +#include "statusled.h" #include "../../include/defines.h" /*an ota data write buffer ready to write to the flash*/ @@ -56,9 +57,9 @@ bool initial_setup = false; static void infinite_loop(void) { int i = 0; - ESP_LOGI(TAG, "When a new firmware is available on the server, press the reset button to download it"); + LogFile.WriteToFile(ESP_LOG_INFO, TAG, "When a new firmware is available on the server, press the reset button to download it"); while(1) { - ESP_LOGI(TAG, "Waiting for a new firmware... %d", ++i); + LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Waiting for a new firmware... (" + to_string(++i) + ")"); vTaskDelay(1000 / portTICK_PERIOD_MS); } } @@ -66,6 +67,8 @@ static void infinite_loop(void) void task_do_Update_ZIP(void *pvParameter) { + StatusLED(AP_OR_OTA, 1, true); // Signaling an OTA update + std::string filetype = toUpper(getFileType(_file_name_update)); LogFile.WriteToFile(ESP_LOG_INFO, TAG, "File: " + _file_name_update + " Filetype: " + filetype); @@ -87,13 +90,13 @@ void task_do_Update_ZIP(void *pvParameter) ota_update_task(retfirmware); } - LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Trigger reboot due to firmware update."); + LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Trigger reboot due to firmware update"); doRebootOTA(); } else if (filetype == "BIN") { LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Do firmware update - file: " + _file_name_update); ota_update_task(_file_name_update); - LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Trigger reboot due to firmware update."); + LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Trigger reboot due to firmware update"); doRebootOTA(); } else @@ -108,7 +111,7 @@ void CheckUpdate() FILE *pfile; if ((pfile = fopen("/sdcard/update.txt", "r")) == NULL) { - LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "No update triggered."); + LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "No pending update"); return; } @@ -120,13 +123,13 @@ void CheckUpdate() std::string _szw = std::string(zw); if (_szw == "init") { - LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Inital Setup triggered."); - initial_setup = true; } + LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Inital Setup triggered"); + } } fclose(pfile); DeleteFile("/sdcard/update.txt"); // Prevent Boot Loop!!! - LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Update during boot triggered - Update File: " + _file_name_update); + LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Start update process (" + _file_name_update + ")"); xTaskCreate(&task_do_Update_ZIP, "task_do_Update_ZIP", configMINIMAL_STACK_SIZE * 35, NULL, tskIDLE_PRIORITY+1, NULL); @@ -148,10 +151,10 @@ static bool ota_update_task(std::string fn) const esp_partition_t *configured = esp_ota_get_boot_partition(); const esp_partition_t *running = esp_ota_get_running_partition(); - if (configured != running) { - ESP_LOGW(TAG, "Configured OTA boot partition at offset 0x%08x, but running from offset 0x%08x", - configured->address, running->address); - ESP_LOGW(TAG, "(This can happen if either the OTA boot data or preferred boot image become somehow corrupted.)"); + if (configured != running) { + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Configured OTA boot partition at offset " + to_string(configured->address) + + ", but running from offset " + to_string(running->address)); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "(This can happen if either the OTA boot data or preferred boot image become somehow corrupted.)"); } ESP_LOGI(TAG, "Running partition type %d subtype %d (offset 0x%08x)", running->type, running->subtype, running->address); @@ -179,7 +182,7 @@ static bool ota_update_task(std::string fn) while (data_read > 0) { if (data_read < 0) { - ESP_LOGE(TAG, "Error: SSL data read error"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Error: SSL data read error"); return false; } else if (data_read > 0) { if (image_header_was_checked == false) { @@ -203,16 +206,17 @@ static bool ota_update_task(std::string fn) // check current version with last invalid partition if (last_invalid_app != NULL) { if (memcmp(invalid_app_info.version, new_app_info.version, sizeof(new_app_info.version)) == 0) { - ESP_LOGW(TAG, "New version is the same as invalid version."); - ESP_LOGW(TAG, "Previously, there was an attempt to launch the firmware with %s version, but it failed.", invalid_app_info.version); - ESP_LOGW(TAG, "The firmware has been rolled back to the previous version."); + LogFile.WriteToFile(ESP_LOG_WARN, TAG, "New version is the same as invalid version"); + LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Previously, there was an attempt to launch the firmware with " + + string(invalid_app_info.version) + " version, but it failed"); + LogFile.WriteToFile(ESP_LOG_WARN, TAG, "The firmware has been rolled back to the previous version"); infinite_loop(); } } /* if (memcmp(new_app_info.version, running_app_info.version, sizeof(new_app_info.version)) == 0) { - ESP_LOGW(TAG, "Current running version is the same as a new. We will not continue the update."); + LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Current running version is the same as a new. We will not continue the update"); infinite_loop(); } */ @@ -220,12 +224,12 @@ static bool ota_update_task(std::string fn) err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle); if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err)); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "esp_ota_begin failed (" + string(esp_err_to_name(err)) + ")"); return false; } ESP_LOGI(TAG, "esp_ota_begin succeeded"); } else { - ESP_LOGE(TAG, "received package is not fit len"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "received package is not fit len"); return false; } } @@ -241,7 +245,7 @@ static bool ota_update_task(std::string fn) // * `errno` to check for underlying transport connectivity closure if any // if (errno == ECONNRESET || errno == ENOTCONN) { - ESP_LOGE(TAG, "Connection closed, errno = %d", errno); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Connection closed, errno = " + to_string(errno)); break; } } @@ -254,15 +258,15 @@ static bool ota_update_task(std::string fn) err = esp_ota_end(update_handle); if (err != ESP_OK) { if (err == ESP_ERR_OTA_VALIDATE_FAILED) { - ESP_LOGE(TAG, "Image validation failed, image is corrupted"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Image validation failed, image is corrupted"); } - ESP_LOGE(TAG, "esp_ota_end failed (%s)!", esp_err_to_name(err)); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "esp_ota_end failed (" + string(esp_err_to_name(err)) + ")!"); return false; } err = esp_ota_set_boot_partition(update_partition); if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(err)); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "esp_ota_set_boot_partition failed (" + string(esp_err_to_name(err)) + ")!"); } // ESP_LOGI(TAG, "Prepare to restart system!"); @@ -329,7 +333,7 @@ void CheckOTAUpdate(void) ESP_LOGI(TAG, "Diagnostics completed successfully! Continuing execution..."); esp_ota_mark_app_valid_cancel_rollback(); } else { - ESP_LOGE(TAG, "Diagnostics failed! Start rollback to the previous version..."); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Diagnostics failed! Start rollback to the previous version..."); esp_ota_mark_app_invalid_rollback_and_reboot(); } } @@ -353,7 +357,7 @@ void CheckOTAUpdate(void) ESP_LOGI(TAG, "Diagnostics completed successfully! Continuing execution..."); esp_ota_mark_app_valid_cancel_rollback(); } else { - ESP_LOGE(TAG, "Diagnostics failed! Start rollback to the previous version..."); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Diagnostics failed! Start rollback to the previous version..."); esp_ota_mark_app_invalid_rollback_and_reboot(); } } @@ -445,7 +449,7 @@ esp_err_t handler_ota_update(httpd_req_t *req) if ((filetype == "ZIP") || (filetype == "BIN")) { FILE *pfile; - LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Update for reboot."); + LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Update for reboot"); pfile = fopen("/sdcard/update.txt", "w"); fwrite(fn.c_str(), fn.length(), 1, pfile); fclose(pfile); @@ -522,7 +526,7 @@ esp_err_t handler_ota_update(httpd_req_t *req) } else { - ESP_LOGD(TAG, "File does not exist: %s", fn.c_str()); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File does not exist: " + fn); } /* Respond with an empty chunk to signal HTTP response completion */ std::string zw = "file deleted\n"; @@ -537,7 +541,7 @@ esp_err_t handler_ota_update(httpd_req_t *req) httpd_resp_send(req, zw.c_str(), strlen(zw.c_str())); httpd_resp_send_chunk(req, NULL, 0); - ESP_LOGE(TAG, "ota without parameter - should not be the case!"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "ota without parameter - should not be the case!"); /* const char* resp_str; @@ -586,6 +590,9 @@ void task_reboot(void *KillAutoFlow) KillTFliteTasks(); // Kill autoflow task if executed in extra task, if not don't kill parent task } + Camera.LightOnOff(false); + StatusLEDOff(); + /* Stop service tasks */ #ifdef ENABLE_MQTT MQTTdestroy_client(true); @@ -600,20 +607,20 @@ void task_reboot(void *KillAutoFlow) vTaskDelay(5000 / portTICK_PERIOD_MS); hard_restart(); // Reset type: System reset (Triggered by watchdog), if esp_restart stalls (WDT needs to be activated) - ESP_LOGE(TAG, "Reboot failed!"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Reboot failed!"); vTaskDelete(NULL); //Delete this task if it comes to this point } void doReboot() { - LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Reboot triggered by Software (5s)."); + LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Reboot triggered by Software (5s)"); LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Reboot in 5sec"); BaseType_t xReturned = xTaskCreate(&task_reboot, "task_reboot", configMINIMAL_STACK_SIZE * 3, (void*) true, 10, NULL); if( xReturned != pdPASS ) { - ESP_LOGE(TAG, "task_reboot not created -> force reboot without killing flow"); + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "task_reboot not created -> force reboot without killing flow"); task_reboot((void*) false); } vTaskDelay(10000 / portTICK_PERIOD_MS); // Prevent serving web client fetch response until system is shuting down @@ -624,6 +631,8 @@ void doRebootOTA() { LogFile.WriteToFile(ESP_LOG_WARN, TAG, "Reboot in 5sec"); + Camera.LightOnOff(false); + StatusLEDOff(); esp_camera_deinit(); vTaskDelay(5000 / portTICK_PERIOD_MS); @@ -641,7 +650,7 @@ esp_err_t handler_reboot(httpd_req_t *req) #endif LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "handler_reboot"); - ESP_LOGI(TAG, "!!! System will restart within 5 sec!!!"); + LogFile.WriteToFile(ESP_LOG_INFO, TAG, "!!! System will restart within 5 sec!!!"); std::string response = "
"; httpd_resp_send_chunk(req, message.c_str(), strlen(message.c_str())); return; @@ -164,8 +167,6 @@ void SendHTTPResponse(httpd_req_t *req) } - - esp_err_t test_handler(httpd_req_t *req) { SendHTTPResponse(req); @@ -182,7 +183,7 @@ esp_err_t reboot_handlerAP(httpd_req_t *req) LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Trigger reboot due to firmware update."); doRebootOTA(); return ESP_OK; -}; +} esp_err_t config_ini_handler(httpd_req_t *req) @@ -201,9 +202,10 @@ esp_err_t config_ini_handler(httpd_req_t *req) std::string hn = ""; // hostname std::string ip = ""; std::string gw = ""; // gateway - std::string nm = ""; // nm + std::string nm = ""; // netmask std::string dns = ""; - std::string rssi = ""; + std::string rssithreshold = ""; //rssi threshold for WIFI roaming + std::string text = ""; if (httpd_req_get_url_query_str(req, _query, 400) == ESP_OK) @@ -258,77 +260,106 @@ esp_err_t config_ini_handler(httpd_req_t *req) dns = UrlDecode(std::string(_valuechar)); } - if (httpd_query_key_value(_query, "rssi", _valuechar, 30) == ESP_OK) + if (httpd_query_key_value(_query, "rssithreshold", _valuechar, 30) == ESP_OK) { - ESP_LOGD(TAG, "rssi is found: %s", _valuechar); - rssi = UrlDecode(std::string(_valuechar)); + ESP_LOGD(TAG, "rssithreshold is found: %s", _valuechar); + rssithreshold = UrlDecode(std::string(_valuechar)); } - }; + } FILE* configfilehandle = fopen(WLAN_CONFIG_FILE, "w"); + + text = ";++++++++++++++++++++++++++++++++++\n"; + text += "; AI on the edge - WLAN configuration\n"; + text += "; ssid: Name of WLAN network (mandatory), e.g. \"WLAN-SSID\"\n"; + text += "; password: Password of WLAN network (mandatory), e.g. \"PASSWORD\"\n\n"; + fputs(text.c_str(), configfilehandle); if (ssid.length()) ssid = "ssid = \"" + ssid + "\"\n"; else - ssid = ";ssid = \"\"\n"; - + ssid = "ssid = \"\"\n"; fputs(ssid.c_str(), configfilehandle); if (pwd.length()) pwd = "password = \"" + pwd + "\"\n"; else - pwd = ";password = \"\"\n"; + pwd = "password = \"\"\n"; fputs(pwd.c_str(), configfilehandle); + text = "\n;++++++++++++++++++++++++++++++++++\n"; + text += "; Hostname: Name of device in network\n"; + text += "; This parameter can be configured via WebUI configuration\n"; + text += "; Default: \"watermeter\", if nothing is configured\n\n"; + fputs(text.c_str(), configfilehandle); + if (hn.length()) hn = "hostname = \"" + hn + "\"\n"; else - hn = ";hostname = \"\"\n"; + hn = ";hostname = \"watermeter\"\n"; fputs(hn.c_str(), configfilehandle); + text = "\n;++++++++++++++++++++++++++++++++++\n"; + text += "; Fixed IP: If you like to use fixed IP instead of DHCP (default), the following\n"; + text += "; parameters needs to be configured: ip, gateway, netmask are mandatory, dns optional\n\n"; + fputs(text.c_str(), configfilehandle); + if (ip.length()) ip = "ip = \"" + ip + "\"\n"; else - ip = ";ip = \"\"\n"; + ip = ";ip = \"xxx.xxx.xxx.xxx\"\n"; fputs(ip.c_str(), configfilehandle); if (gw.length()) gw = "gateway = \"" + gw + "\"\n"; else - gw = ";gateway = \"\"\n"; + gw = ";gateway = \"xxx.xxx.xxx.xxx\"\n"; fputs(gw.c_str(), configfilehandle); if (nm.length()) nm = "netmask = \"" + nm + "\"\n"; else - nm = ";netmask = \"\"\n"; + nm = ";netmask = \"xxx.xxx.xxx.xxx\"\n"; fputs(nm.c_str(), configfilehandle); + text = "\n;++++++++++++++++++++++++++++++++++\n"; + text += "; DNS server (optional, if no DNS is configured, gateway address will be used)\n\n"; + fputs(text.c_str(), configfilehandle); + if (dns.length()) dns = "dns = \"" + dns + "\"\n"; else - dns = ";dns = \"\"\n"; + dns = ";dns = \"xxx.xxx.xxx.xxx\"\n"; fputs(dns.c_str(), configfilehandle); - if (rssi.length()) - rssi = "RSSIThreshold = \"" + rssi + "\"\n"; + text = "\n;++++++++++++++++++++++++++++++++++\n"; + text += "; WIFI Roaming:\n"; + text += "; Network assisted roaming protocol is activated by default\n"; + text += "; AP / mesh system needs to support roaming protocol 802.11k/v\n"; + text += ";\n"; + text += "; Optional feature (usually not neccessary):\n"; + text += "; RSSI Threshold for client requested roaming query (RSSI < RSSIThreshold)\n"; + text += "; Note: This parameter can be configured via WebUI configuration\n"; + text += "; Default: 0 = Disable client requested roaming query\n\n"; + fputs(text.c_str(), configfilehandle); + + if (rssithreshold.length()) + rssithreshold = "RSSIThreshold = " + rssithreshold + "\n"; else - rssi = ";rssi = \"\"\n"; - fputs(rssi.c_str(), configfilehandle); + rssithreshold = "RSSIThreshold = 0\n"; + fputs(rssithreshold.c_str(), configfilehandle); fflush(configfilehandle); fclose(configfilehandle); std::string zw = "ota without parameter - should not be the case!"; httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); - httpd_resp_send(req, zw.c_str(), strlen(zw.c_str())); - httpd_resp_send_chunk(req, NULL, 0); - - ESP_LOGE(TAG, "end config.ini"); + httpd_resp_send(req, zw.c_str(), zw.length()); + ESP_LOGD(TAG, "end config.ini"); return ESP_OK; -}; +} esp_err_t upload_post_handlerAP(httpd_req_t *req) @@ -470,21 +501,28 @@ httpd_handle_t start_webserverAP(void) return NULL; } + void CheckStartAPMode() { isConfigINI = FileExists(CONFIG_FILE); isWlanINI = FileExists(WLAN_CONFIG_FILE); - if (!isConfigINI or !isWlanINI) + if (!isConfigINI) + ESP_LOGW(TAG, "config.ini not found!"); + + if (!isWlanINI) + ESP_LOGW(TAG, "wlan.ini not found!"); + + if (!isConfigINI || !isWlanINI) { + ESP_LOGI(TAG, "Starting access point for remote configuration"); + StatusLED(AP_OR_OTA, 2, true); wifi_init_softAP(); start_webserverAP(); while(1) { // wait until reboot within task_do_Update_ZIP vTaskDelay(1000 / portTICK_PERIOD_MS); } } - - } #endif //#ifdef ENABLE_SOFTAP diff --git a/code/sdkconfig.defaults b/code/sdkconfig.defaults index a524df65..978a4b48 100644 --- a/code/sdkconfig.defaults +++ b/code/sdkconfig.defaults @@ -133,6 +133,8 @@ CONFIG_GC032A_SUPPORT=n CONFIG_GC0308_SUPPORT=n CONFIG_BF3005_SUPPORT=n +CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=4864 + #only necessary for task analysis (include/defines -> TASK_ANALYSIS_ON) #set in [env:esp32cam-dev-task-analysis] #CONFIG_FREERTOS_USE_TRACE_FACILITY=1 diff --git a/sd-card/config/ana-class100_0154_s1_q.tflite b/sd-card/config/ana-class100_0154_s1_q.tflite deleted file mode 100644 index 2d4945d9..00000000 Binary files a/sd-card/config/ana-class100_0154_s1_q.tflite and /dev/null differ diff --git a/sd-card/config/ana-class100_0157_s1_q.tflite b/sd-card/config/ana-class100_0157_s1_q.tflite new file mode 100644 index 00000000..0b184b20 Binary files /dev/null and b/sd-card/config/ana-class100_0157_s1_q.tflite differ diff --git a/sd-card/config/ana-cont_11.3.1_s2.tflite b/sd-card/config/ana-cont_11.3.1_s2.tflite deleted file mode 100644 index d43ea51b..00000000 Binary files a/sd-card/config/ana-cont_11.3.1_s2.tflite and /dev/null differ diff --git a/sd-card/config/ana-cont_1105_s2_q.tflite b/sd-card/config/ana-cont_1105_s2_q.tflite new file mode 100644 index 00000000..e9c2368a Binary files /dev/null and b/sd-card/config/ana-cont_1105_s2_q.tflite differ diff --git a/sd-card/config/dig-class100-0150_s2_q.tflite b/sd-card/config/dig-class100-0150_s2_q.tflite deleted file mode 100644 index 9bf8025c..00000000 Binary files a/sd-card/config/dig-class100-0150_s2_q.tflite and /dev/null differ diff --git a/sd-card/config/dig-class100_0160_s2_q.tflite b/sd-card/config/dig-class100_0160_s2_q.tflite new file mode 100644 index 00000000..a1769322 Binary files /dev/null and b/sd-card/config/dig-class100_0160_s2_q.tflite differ diff --git a/sd-card/html/data.html b/sd-card/html/data.html index c14a0a53..a592241c 100644 --- a/sd-card/html/data.html +++ b/sd-card/html/data.html @@ -5,6 +5,7 @@ body { height: 100%; margin: 2px; + font-family: Arial, Helvetica, sans-serif; } .box { @@ -33,19 +34,22 @@ +The documentation of each parameter can be shown with hovering over the
icon.
+ The latest version of the documentation is available on jomjol.github.io/AI-on-the-edge-device-docs/Parameters.
| + | Parameter | Value | - Information + Documentation | ||
|---|---|---|---|---|---|
| - + | - | - Location to store raw camera images - | +$TOOLTIP_TakeImage_RawImagesLocation | ||
| - + | - - | -- Time to keep the raw image (in days, resp. "0" = forever) + Days | +$TOOLTIP_TakeImage_RawImagesRetention | ||
| - + | - |
- Enable to use demo images instead of the real camera images. - Make sure to have a demo folder on your SD-Card! - Check the documentation for details. - |
+ $TOOLTIP_TakeImage_Demo | ||
|
- |
- - | -- Wait time between switching illumination on and taking the picture (in seconds) + Seconds | +$TOOLTIP_TakeImage_WaitBeforeTakingPicture | ||
|
- |
- |
- Image quality index (default = 12) - Input range: 8 [highest quality] ... 63 [lowest quality] - Remark: Value < 12 could result in system instabilities! - |
+ $TOOLTIP_TakeImage_ImageQuality | ||
|
- |
- | - Size of camera picture (default = "VGA") - | +$TOOLTIP_TakeImage_ImageSize | ||
|
- |
- |
- Internal LED Flash Intensity (PWM from 0% - 100%). - Remark: as the camera autoillumination settings are used, this is rather for energy saving, than reducing reflections. - |
+ $TOOLTIP_TakeImage_LEDIntensity | ||
| - |
- Image brightness (default = 0) - Input range: -2 ... 2 - |
+ $TOOLTIP_TakeImage_Brightness | |||
| - |
- Image contrast (default = 0) - Input range: -2 ... 2 - |
+ $TOOLTIP_TakeImage_Contrast | |||
| - |
- Image saturation (default = 0) - Input range: -2 ... 2 - |
+ $TOOLTIP_TakeImage_Saturation | |||
|
- |
- | - Fixes the illumination setting of camera at the startup and uses this later --> individual round is faster - | +$TOOLTIP_TakeImage_FixedExposure | ||
|
- |
- - | -- x size (width) in which the reference is searched (default = "20") + Pixel | +$TOOLTIP_Alignment_SearchFieldX | ||
|
- |
- - | -- y size (height) in which the reference is searched (default = "20") + Pixel | +$TOOLTIP_Alignment_SearchFieldY | ||
| - + | - |
- "Default" = use only R-Channel, "HighAccuracy" = use all Channels (RGB, 3x slower), "Fast" (First time RGB, then only check if image is shifted) - |
+ $TOOLTIP_Alignment_AlignmentAlgo | ||
|
- |
- | - Rotate the viewport together with the alignment rotation. - | +$TOOLTIP_Alignment_FlipImageSize | ||
|
- |
- | - Rotate the viewport together with the alignment rotation. - | +$TOOLTIP_Alignment_InitialMirror | ||
-+ |
|||||
|
- Path to CNN model file for image recognition. - Check the documentation for details. - |
+ $TOOLTIP_Digits_Model | ||||
| - + | - | - EXPERIMENTAL - NOT WORKING FOR ALL CNNs! - Threshold above which the classification should be to accept the value (only for digits meaningfull) - | +$TOOLTIP_Digits_CNNGoodThreshold | ||
| - + | - | - Location to store separated digits for logging - | +$TOOLTIP_Digits_ROIImagesLocation | ||
| - + | - - | -- Time to keep the separated digit images (in days, resp. "0" = forever) + Days | +$TOOLTIP_Digits_ROIImagesRetention | ||
-+ |
|||||
| Path to CNN model file for image recognition. - Check the documentation for details. |
+ $TOOLTIP_Analog_Model | ||||
| - | - | Location to store separated digits for logging | +$TOOLTIP_Analog_ROIImagesLocation | ||
|
- |
- - | Time to keep the separated digit images (in days, resp. "0" = forever) | +Days | +$TOOLTIP_Analog_ROIImagesRetention | |
|
- |
- | - Enable to use the previous read value for consistency checks - also on reboots - | +$TOOLTIP_PostProcessing_PreValueUse | ||
|
- |
- - | -- Time (in minutes), how long a previous read value is valid after reboot (default = 720 min) + Seconds | +$TOOLTIP_PostProcessing_PreValueAgeStartup | ||
|
- |
- | - Do not show error message in return value - in error case, the last valid number will be send out - | +$TOOLTIP_PostProcessing_ErrorMessage | ||
|
- |
- | - Enable additional consistency check - especially zero crossing check between digits - | +$TOOLTIP_PostProcessing_CheckDigitIncreaseConsistency | ||
|
- |
- | - Set to "false" to ensure, that only positive changes are accepted (typically for counter) - | +$TOOLTIP_PostProcessing_NUMBER.AllowNegativeRates | ||
|
- |
- |
- Shift the decimal separator (positiv or negativ). - Eg. to move from 'm³' to 'l' (Multiply by 1000), you need to set it to +3. - |
+ $TOOLTIP_PostProcessing_NUMBER.DecimalShift | ||
|
- |
- | - If you have false Values, but the recognition is correct. Look for the start of changing of the first digit and note the analog pointer value behind. Set it here. - Only used on combination of digits and analog pointers. Default=9.2 - | +$TOOLTIP_PostProcessing_NUMBER.AnalogDigitalTransitionStart | ||
|
- |
- | - Maximum change of a reading - if treated as absolute or relative change see next parameter. - | +$TOOLTIP_PostProcessing_NUMBER.MaxRateValue | ||
|
- |
- | - Defines if the change rate compared to the previous value is calculated as absolute change (AbsoluteChange) or as rate normalized to the interval (RateChange = change/minute). - | +$TOOLTIP_PostProcessing_NUMBER.MaxRateType | ||
|
- |
- | - Enable to use the decimal place of the last analog counter - | +$TOOLTIP_PostProcessing_NUMBER.ExtendedResolution | ||
|
- |
- | - Leading "N"'s will be deleted before further processing - | +$TOOLTIP_PostProcessing_NUMBER.IgnoreLeadingNaN | ||
|
- |
- | - URI to the MQTT broker including port e.g.: mqtt://IP-Address:Port - | +$TOOLTIP_MQTT_Uri | ||
|
- |
- |
- MQTT main topic, under which the counters are published. - The single value will be published with the following key: MAINTOPIC/VALUE_NAME/PARAMETER where -
|
+ $TOOLTIP_MQTT_MainTopic | ||
|
- |
- | - ClientID to connect to the MQTT broker - | +$TOOLTIP_MQTT_ClientID | ||
|
- |
- | - User for MQTT authentication - | +$TOOLTIP_MQTT_user | ||
|
- |
- | - Password for MQTT authentication - | +$TOOLTIP_MQTT_password | ||
|
- |
- | - Enable or disable the retain flag for all MQTT entries - | +$TOOLTIP_MQTT_RetainMessages | ||
| @@ -680,7 +627,7 @@ textarea { | |||||
|
- |
- | - Enable or disable the Homeassistant Discovery - | +$TOOLTIP_MQTT_HomeassistantDiscovery | ||
| @@ -711,12 +656,7 @@ textarea { | -
- Select the meter type so the sensors have the right units in Homeassistant. - Note: For 'Watermeter' you need to have Homeassistant 2022.11 or never! - Please also make sure that it matches the dimention of the value provided by the meter! Eg. if your meter provides m3, you need to also set it to m3. - Alternatively you could set 'DecimalShift' to '3' so the value is converted to 'l'! - |
+ $TOOLTIP_MQTT_MeterType |
- |
||
|
- |
- | - URI of the HTTP interface to InfluxDB, without traililing slash, e.g. http://IP-Address:Port - | +$TOOLTIP_InfluxDB_Uri | ||
| @@ -747,9 +685,7 @@ textarea { | - | - Database name in which to publish the read value. - | +$TOOLTIP_InfluxDB_Database | ||
| @@ -759,33 +695,27 @@ textarea { | - | - Measurement name to use to publish the read value. - | +$TOOLTIP_InfluxDB_Measurement | ||
|
- |
- | - User for InfluxDB authentication - | +$TOOLTIP_InfluxDB_user | ||
|
- |
- | - Password for InfluxDB authentication - | +$TOOLTIP_InfluxDB_password |
- |
|
|
- |
- | - URI of the HTTP interface to InfluxDB (Version2), without traililing slash, e.g. http://IP-Address:Port - | +$TOOLTIP_InfluxDBv2_Uri | ||
| @@ -818,9 +746,7 @@ textarea { | - | - Database name in which to publish the read value. - | +$TOOLTIP_InfluxDBv2_Database | ||
| @@ -830,21 +756,17 @@ textarea { | - | - Measurement name to use to publish the read value. - | +$TOOLTIP_InfluxDBv2_Measurement | ||
|
- |
- | - Organisation (Org) for InfluxDBv2 authentication - | +$TOOLTIP_InfluxDBv2_Org | ||
| @@ -854,18 +776,13 @@ textarea { | - | - Token for InfluxDB authentication - | +$TOOLTIP_InfluxDBv2_Token | ||
|
Postprocessing Individual Parameters: |
- | - Fieldname to use for saving. - | +$TOOLTIP_InfluxDBv2_NUMBER.Fieldname | ||
-
- |
|||||
|
- |
- |
- GPIO 0 Usable with restrictions. - Must be disabled when camera is used. - Pin is used to activate flash mode and must therefore be HIGH when booting. - |
+ $TOOLTIP_GPIO_IO0 | ||
| - GPIO 0 use interrupt + GPIO 0 Use Interrupt | - | - GPIO 0 enable interrupt trigger - | +|||
| - GPIO 0 PWM duty resolution - | -- | - GPIO 0 LEDC PWM duty resolution in bit + GPIO 0 PWM Duty Cycle Resolution | +Bits | +||
| - GPIO 0 enable MQTT + GPIO 0 Enable MQTT | - | - GPIO 0 enable MQTT publishing/subscribing - | +|||
| - GPIO 0 enable HTTP + GPIO 0 Enable HTTP | - | - GPIO 0 enable HTTP write/read - | +|||
| - GPIO 0 name + GPIO 0 Name | - | - GPIO 0 MQTT topic name (empty = GPIO0). Allowed characters (a-z, A-Z, 0-9, _, -) - | +|||
|
- |
- |
- GPIO 1 Used by default for serial communication as TX pin. Required for seriales monitor. - |
+ $TOOLTIP_GPIO_IO1 | ||
| - GPIO 1 use interrupt + GPIO 1 Use Interrupt | - | - GPIO 1 enable interrupt trigger - | +|||
| - GPIO 1 PWM duty resolution - | -- | - GPIO 1 LEDC PWM duty resolution in bit + GPIO 1 PWM Duty Cycle Resolution | +Bits | +||
| - GPIO 1 enable MQTT + GPIO 1 Enable MQTT | - | - GPIO 1 enable MQTT publishing/subscribing - | +|||
| - GPIO 1 enable HTTP + GPIO 1 Enable HTTP | - | - GPIO 1 enable HTTP write/read - | +|||
| - GPIO 1 name + GPIO 1 Name | - | - GPIO 1 MQTT topic name (empty = GPIO1). Allowed characters (a-z, A-Z, 0-9, _, -) - | +|||
|
- |
- | - GPIO 3 Used by default for serial communication as RX pin. - | +$TOOLTIP_GPIO_IO3 | ||
| - GPIO 3 use interrupt + GPIO 3 Use Interrupt | - | - GPIO 3 Used by default for serial communication as RX pin. - | +|||
| - GPIO 3 PWM duty resolution - | -- | - GPIO 3 LEDC PWM duty resolution in bit + GPIO 3 PWM Duty Cycle Resolution | +Bits | +||
| - GPIO 3 enable MQTT + GPIO 3 Enable MQTT | - | - GPIO 3 enable MQTT publishing/subscribing - | +|||
| - GPIO 3 enable HTTP + GPIO 3 Enable HTTP | - | - GPIO 3 enable HTTP write/read - | +|||
| - GPIO 3 name + GPIO 3 Name | - | - GPIO 3 MQTT topic name (empty = GPIO3). Allowed characters (a-z, A-Z, 0-9, _, -) - | +|||
|
- |
- |
- GPIO 4 Usable with restrictions. - Pin is used for build-in flash light. - |
+ $TOOLTIP_GPIO_IO4 | ||
| - GPIO 4 use interrupt + GPIO 4 Use Interrupt | - | - GPIO 4 enable interrupt trigger - | +|||
| - GPIO 4 PWM duty resolution - | -- | - GPIO 4 LEDC PWM duty resolution in bit + GPIO 4 PWM Duty Cycle Resolution | +Bits | +||
| - GPIO 4 enable MQTT + GPIO 4 Enable MQTT | - | - GPIO 4 enable MQTT publishing/subscribing - | +|||
| - GPIO 4 enable HTTP + GPIO 4 Enable HTTP | - | - GPIO 4 enable HTTP write/read - | +|||
| - GPIO 4 name + GPIO 4 Name | - | - GPIO 4 MQTT topic name (empty = GPIO4). Allowed characters (a-z, A-Z, 0-9, _, -) - | +|||
|
- |
- | - GPIO 12 is usable without restrictions - | +$TOOLTIP_GPIO_IO12 | ||
| - GPIO 12 use interrupt + GPIO 12 Use Interrupt | - | - GPIO 12 enable interrupt trigger - | +|||
| - GPIO 12 PWM duty resolution - | -- | - GPIO 12 LEDC PWM duty resolution in bit + GPIO 12 PWM Duty Cycle Resolution | +Bits | +||
| - GPIO 12 enable MQTT + GPIO 12 Enable MQTT | - | - GPIO 12 enable MQTT publishing/subscribing - | +|||
| - GPIO 12 enable HTTP + GPIO 12 Enable HTTP | - | - GPIO 12 enable HTTP write/read - | +|||
| - GPIO 12 name + GPIO 12 Name | - | - GPIO 12 MQTT topic name (empty = GPIO12). Allowed characters (a-z, A-Z, 0-9, _, -) - | +|||
| - LED-Type + LED Type (NeoPixel) | -- | ||||
| - Numbers of LEDs + Numbers of LEDs | - | - Number of LEDs on the external LED-stripe - | +$TOOLTIP_GPIO_LEDNumbers | ||
| - LED Color + LED Color | -+ | R G B | -- Color of LEDs in (R)ed, (G)reen (B)lue from 0...255 - | +$TOOLTIP_GPIO_LEDColor | |
|
- |
|
- - GPIO 13 is usable without restrictions - | +$TOOLTIP_GPIO_IO13 | ||
| - GPIO 13 use interrupt + GPIO 13 Use Interrupt |
|
- - GPIO 13 enable interrupt trigger - | +|||
| - GPIO 13 PWM duty resolution - | -- | - GPIO 13 LEDC PWM duty resolution in bit + GPIO 13 PWM Duty Cycle Resolution | +Bits | +||
| - GPIO 13 enable MQTT + GPIO 13 Enable MQTT | - | - GPIO 13 enable MQTT publishing/subscribing - | +|||
| - GPIO 13 enable HTTP + GPIO 13 Enable HTTP | - | - GPIO 13 enable HTTP write/read - | +|||
| - GPIO 13 name + GPIO 13 Name | - | - GPIO 13 MQTT topic name (empty = GPIO13). Allowed characters (a-z, A-Z, 0-9, _, -) - | +|||
|
- |
|
- - Start the image recognition immediatly after power up. false is basically for debugging. - | +$TOOLTIP_AutoTimer_AutoStart | ||
|
- |
- - | -- Interval in which the number(s) are read (in minutes). If a digitalization round takes longer than this interval, the next run gets postponed until the current run completes. + Minutes | +$TOOLTIP_AutoTimer_Interval | ||
DataLogging |
+ Data Logging |
||||
|
- |
|
- - Activate data log to SD card - | +$TOOLTIP_DataLogging_DataLogActive | ||
|
- |
- - | -- Time to keep the data files (in days - "0" = forever) + Days | +$TOOLTIP_DataLogging_DataFilesRetention | ||
|
- Set the level of the logging to the SD-Card. Warning: DEBUG or INFO might damage the SD-Card if enabled longterm due to excessive writes to the SD-Card! - |
+ $TOOLTIP_Debug_LogLevel | ||||
|
|
- - | -- Time to keep the log files (in days - "0" = forever) + Days | +$TOOLTIP_Debug_LogfilesRetention | ||
|
- |
- |
- Time zone in POSIX syntax (Europe/Berlin = "CET-1CEST,M3.5.0,M10.5.0/3" - incl. daylight saving) - Use this table to find the settings for your region. - |
+ $TOOLTIP_System_TimeZone | ||
|
- |
- |
- Time server to synchronize system time. If it is disabled or "undefined", "pool.ntp.org" will be used. - You can also set it to the IP of your router. Many routers like - Fritzboxes can act as a local NTP server. - To disable NTP, you need to activate it but set the TimeServer config to be empty. In such case the time always starts at 01.01.1970 after each power cycle! - |
+ $TOOLTIP_System_TimeServer | ||
| @@ -1514,21 +1328,20 @@ textarea { | - | - Hostname for server - will be transfered to wlan.ini at next startup) | +$TOOLTIP_System_Hostname | ||
|
- |
- - | - WLAN Mesh Parameter: Threshold for RSSI value to check for start switching access point in a mesh system. - Possible values: -100 to 0, 0 = disabled - Value will be transfered to wlan.ini at next startup) + dBm | +$TOOLTIP_System_RSSIThreshold | ||
Loading...
=i.length)return e;var n=[],o=a[r++];return e.forEach((function(e,i){n.push({key:e,values:t(i,r)})})),o?n.sort((function(t,e){return o(t.key,e.key)})):n}(o(t.map,e,0),0)},n.key=function(t){return i.push(t),n},n.sortKeys=function(t){return a[i.length-1]=t,n},n.sortValues=function(t){return e=t,n},n.rollup=function(t){return r=t,n},n},t.set=function(t){var e=new L;if(t)for(var r=0,n=t.length;r 360?t-=360:t<0&&(t+=360),t<60?n+(i-n)*t/60:t<180?i:t<240?n+(i-n)*(240-t)/60:n}(t))}return t=isNaN(t)?0:(t%=360)<0?t+360:t,e=isNaN(e)||e<0?0:e>1?1:e,n=2*(r=r<0?0:r>1?1:r)-(i=r<=.5?r*(1+e):r+e-r*e),new Qt(a(t+120),a(t),a(t-120))}function Ut(e,r,n){return this instanceof Ut?(this.h=+e,this.c=+r,void(this.l=+n)):arguments.length<2?e instanceof Ut?new Ut(e.h,e.c,e.l):Xt(e instanceof qt?e.l:(e=ae((e=t.rgb(e)).r,e.g,e.b)).l,e.a,e.b):new Ut(e,r,n)}Nt.brighter=function(t){return t=Math.pow(.7,arguments.length?t:1),new Bt(this.h,this.s,this.l/t)},Nt.darker=function(t){return t=Math.pow(.7,arguments.length?t:1),new Bt(this.h,this.s,t*this.l)},Nt.rgb=function(){return jt(this.h,this.s,this.l)},t.hcl=Ut;var Vt=Ut.prototype=new Ft;function Ht(t,e,r){return isNaN(t)&&(t=0),isNaN(e)&&(e=0),new qt(r,Math.cos(t*=Lt)*e,Math.sin(t)*e)}function qt(t,e,r){return this instanceof qt?(this.l=+t,this.a=+e,void(this.b=+r)):arguments.length<2?t instanceof qt?new qt(t.l,t.a,t.b):t instanceof Ut?Ht(t.h,t.c,t.l):ae((t=Qt(t)).r,t.g,t.b):new qt(t,e,r)}Vt.brighter=function(t){return new Ut(this.h,this.c,Math.min(100,this.l+Gt*(arguments.length?t:1)))},Vt.darker=function(t){return new Ut(this.h,this.c,Math.max(0,this.l-Gt*(arguments.length?t:1)))},Vt.rgb=function(){return Ht(this.h,this.c,this.l).rgb()},t.lab=qt;var Gt=18,Yt=qt.prototype=new Ft;function Wt(t,e,r){var n=(t+16)/116,i=n+e/500,a=n-r/200;return new Qt(Kt(3.2404542*(i=.95047*Zt(i))-1.5371385*(n=1*Zt(n))-.4985314*(a=1.08883*Zt(a))),Kt(-.969266*i+1.8760108*n+.041556*a),Kt(.0556434*i-.2040259*n+1.0572252*a))}function Xt(t,e,r){return t>0?new Ut(Math.atan2(r,e)*Ct,Math.sqrt(e*e+r*r),t):new Ut(NaN,NaN,t)}function Zt(t){return t>.206893034?t*t*t:(t-4/29)/7.787037}function Jt(t){return t>.008856?Math.pow(t,1/3):7.787037*t+4/29}function Kt(t){return Math.round(255*(t<=.00304?12.92*t:1.055*Math.pow(t,1/2.4)-.055))}function Qt(t,e,r){return this instanceof Qt?(this.r=~~t,this.g=~~e,void(this.b=~~r)):arguments.length<2?t instanceof Qt?new Qt(t.r,t.g,t.b):ne(""+t,Qt,jt):new Qt(t,e,r)}function $t(t){return new Qt(t>>16,t>>8&255,255&t)}function te(t){return $t(t)+""}Yt.brighter=function(t){return new qt(Math.min(100,this.l+Gt*(arguments.length?t:1)),this.a,this.b)},Yt.darker=function(t){return new qt(Math.max(0,this.l-Gt*(arguments.length?t:1)),this.a,this.b)},Yt.rgb=function(){return Wt(this.l,this.a,this.b)},t.rgb=Qt;var ee=Qt.prototype=new Ft;function re(t){return t<16?"0"+Math.max(0,t).toString(16):Math.min(255,t).toString(16)}function ne(t,e,r){var n,i,a,o=0,s=0,l=0;if(n=/([a-z]+)\((.*)\)/.exec(t=t.toLowerCase()))switch(i=n[2].split(","),n[1]){case"hsl":return r(parseFloat(i[0]),parseFloat(i[1])/100,parseFloat(i[2])/100);case"rgb":return e(se(i[0]),se(i[1]),se(i[2]))}return(a=le.get(t))?e(a.r,a.g,a.b):(null==t||"#"!==t.charAt(0)||isNaN(a=parseInt(t.slice(1),16))||(4===t.length?(o=(3840&a)>>4,o|=o>>4,s=240&a,s|=s>>4,l=15&a,l|=l<<4):7===t.length&&(o=(16711680&a)>>16,s=(65280&a)>>8,l=255&a)),e(o,s,l))}function ie(t,e,r){var n,i,a=Math.min(t/=255,e/=255,r/=255),o=Math.max(t,e,r),s=o-a,l=(o+a)/2;return s?(i=l<.5?s/(o+a):s/(2-o-a),n=t==o?(e-r)/s+(e =f[0]&&l<=f[1]&&((s=c[t.bisect(h,l,1,d)-1]).y+=m,s.push(a[o]));return c}return a.value=function(t){return arguments.length?(r=t,a):r},a.range=function(t){return arguments.length?(n=ce(t),a):n},a.bins=function(t){return arguments.length?(i="number"==typeof t?function(e){return vn(e,t)}:ce(t),a):i},a.frequency=function(t){return arguments.length?(e=!!t,a):e},a},t.layout.pack=function(){var e,r=t.layout.hierarchy().sort(xn),n=0,i=[1,1];function a(t,a){var o=r.call(this,t,a),s=o[0],l=i[0],c=i[1],u=null==e?Math.sqrt:"function"==typeof e?e:function(){return e};if(s.x=s.y=0,$r(s,(function(t){t.r=+u(t.value)})),$r(s,Tn),n){var f=n*(e?1:Math.max(2*s.r/l,2*s.r/c))/2;$r(s,(function(t){t.r+=f})),$r(s,Tn),$r(s,(function(t){t.r-=f}))}return function t(e,r,n,i){var a=e.children;if(e.x=r+=i*e.x,e.y=n+=i*e.y,e.r*=i,a)for(var o=-1,s=a.length;++o =r)for(n=r;++an&&(n=r)}else for(;++a=r)for(n=r;++an&&(n=r);return n},t.mean=function(t,e){var r,n=t.length,i=n,a=-1,o=0;if(null==e)for(;++a l.length)return r;var i,a=c[n-1];return null!=e&&n>=l.length?i=r.entries():(i=[],r.each((function(e,r){i.push({key:r,values:t(e,n)})}))),null!=a?i.sort((function(t,e){return a(t.key,e.key)})):i}(u(t,0,a,o),0)},key:function(t){return l.push(t),s},sortKeys:function(t){return c[l.length-1]=t,s},sortValues:function(e){return t=e,s},rollup:function(t){return e=t,s}}},t.set=c,t.map=r,t.keys=function(t){var e=[];for(var r in t)e.push(r);return e},t.values=function(t){var e=[];for(var r in t)e.push(t[r]);return e},t.entries=function(t){var e=[];for(var r in t)e.push({key:r,value:t[r]});return e},Object.defineProperty(t,"__esModule",{value:!0})}))},{}],109:[function(t,e,r){!function(t,n){"object"==typeof r&&void 0!==e?n(r):n((t=t||self).d3=t.d3||{})}(this,(function(t){"use strict";function e(t,e,r){t.prototype=e.prototype=r,r.constructor=t}function r(t,e){var r=Object.create(t.prototype);for(var n in e)r[n]=e[n];return r}function n(){}var i="\\s*([+-]?\\d+)\\s*",a="\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\s*",o="\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)%\\s*",s=/^#([0-9a-f]{3,8})$/,l=new RegExp("^rgb\\("+[i,i,i]+"\\)$"),c=new RegExp("^rgb\\("+[o,o,o]+"\\)$"),u=new RegExp("^rgba\\("+[i,i,i,a]+"\\)$"),f=new RegExp("^rgba\\("+[o,o,o,a]+"\\)$"),h=new RegExp("^hsl\\("+[a,o,o]+"\\)$"),p=new RegExp("^hsla\\("+[a,o,o,a]+"\\)$"),d={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074};function m(){return this.rgb().formatHex()}function g(){return this.rgb().formatRgb()}function v(t){var e,r;return t=(t+"").trim().toLowerCase(),(e=s.exec(t))?(r=e[1].length,e=parseInt(e[1],16),6===r?y(e):3===r?new w(e>>8&15|e>>4&240,e>>4&15|240&e,(15&e)<<4|15&e,1):8===r?x(e>>24&255,e>>16&255,e>>8&255,(255&e)/255):4===r?x(e>>12&15|e>>8&240,e>>8&15|e>>4&240,e>>4&15|240&e,((15&e)<<4|15&e)/255):null):(e=l.exec(t))?new w(e[1],e[2],e[3],1):(e=c.exec(t))?new w(255*e[1]/100,255*e[2]/100,255*e[3]/100,1):(e=u.exec(t))?x(e[1],e[2],e[3],e[4]):(e=f.exec(t))?x(255*e[1]/100,255*e[2]/100,255*e[3]/100,e[4]):(e=h.exec(t))?M(e[1],e[2]/100,e[3]/100,1):(e=p.exec(t))?M(e[1],e[2]/100,e[3]/100,e[4]):d.hasOwnProperty(t)?y(d[t]):"transparent"===t?new w(NaN,NaN,NaN,0):null}function y(t){return new w(t>>16&255,t>>8&255,255&t,1)}function x(t,e,r,n){return n<=0&&(t=e=r=NaN),new w(t,e,r,n)}function b(t){return t instanceof n||(t=v(t)),t?new w((t=t.rgb()).r,t.g,t.b,t.opacity):new w}function _(t,e,r,n){return 1===arguments.length?b(t):new w(t,e,r,null==n?1:n)}function w(t,e,r,n){this.r=+t,this.g=+e,this.b=+r,this.opacity=+n}function T(){return"#"+A(this.r)+A(this.g)+A(this.b)}function k(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===t?")":", "+t+")")}function A(t){return((t=Math.max(0,Math.min(255,Math.round(t)||0)))<16?"0":"")+t.toString(16)}function M(t,e,r,n){return n<=0?t=e=r=NaN:r<=0||r>=1?t=e=NaN:e<=0&&(t=NaN),new L(t,e,r,n)}function S(t){if(t instanceof L)return new L(t.h,t.s,t.l,t.opacity);if(t instanceof n||(t=v(t)),!t)return new L;if(t instanceof L)return t;var e=(t=t.rgb()).r/255,r=t.g/255,i=t.b/255,a=Math.min(e,r,i),o=Math.max(e,r,i),s=NaN,l=o-a,c=(o+a)/2;return l?(s=e===o?(r-i)/l+6*(r0&&c<1?0:s,new L(s,l,c,t.opacity)}function E(t,e,r,n){return 1===arguments.length?S(t):new L(t,e,r,null==n?1:n)}function L(t,e,r,n){this.h=+t,this.s=+e,this.l=+r,this.opacity=+n}function C(t,e,r){return 255*(t<60?e+(r-e)*t/60:t<180?r:t<240?e+(r-e)*(240-t)/60:e)}e(n,v,{copy:function(t){return Object.assign(new this.constructor,this,t)},displayable:function(){return this.rgb().displayable()},hex:m,formatHex:m,formatHsl:function(){return S(this).formatHsl()},formatRgb:g,toString:g}),e(w,_,r(n,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new w(this.r*t,this.g*t,this.b*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new w(this.r*t,this.g*t,this.b*t,this.opacity)},rgb:function(){return this},displayable:function(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:T,formatHex:T,formatRgb:k,toString:k})),e(L,E,r(n,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new L(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new L(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=this.h%360+360*(this.h<0),e=isNaN(t)||isNaN(this.s)?0:this.s,r=this.l,n=r+(r<.5?r:1-r)*e,i=2*r-n;return new w(C(t>=240?t-240:t+120,i,n),C(t,i,n),C(t<120?t+240:t-120,i,n),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl:function(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"hsl(":"hsla(")+(this.h||0)+", "+100*(this.s||0)+"%, "+100*(this.l||0)+"%"+(1===t?")":", "+t+")")}}));var P=Math.PI/180,I=180/Math.PI,O=6/29,z=3*O*O;function D(t){if(t instanceof F)return new F(t.l,t.a,t.b,t.opacity);if(t instanceof q)return G(t);t instanceof w||(t=b(t));var e,r,n=U(t.r),i=U(t.g),a=U(t.b),o=B((.2225045*n+.7168786*i+.0606169*a)/1);return n===i&&i===a?e=r=o:(e=B((.4360747*n+.3850649*i+.1430804*a)/.96422),r=B((.0139322*n+.0971045*i+.7141733*a)/.82521)),new F(116*o-16,500*(e-o),200*(o-r),t.opacity)}function R(t,e,r,n){return 1===arguments.length?D(t):new F(t,e,r,null==n?1:n)}function F(t,e,r,n){this.l=+t,this.a=+e,this.b=+r,this.opacity=+n}function B(t){return t>.008856451679035631?Math.pow(t,1/3):t/z+4/29}function N(t){return t>O?t*t*t:z*(t-4/29)}function j(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function U(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function V(t){if(t instanceof q)return new q(t.h,t.c,t.l,t.opacity);if(t instanceof F||(t=D(t)),0===t.a&&0===t.b)return new q(NaN,0=0&&"xmlns"!==(r=t.slice(0,e))&&(t=t.slice(e+1)),J.hasOwnProperty(r)?{space:J[r],local:t}:t}},Y.attr=function(e,r){if(arguments.length<2){if("string"==typeof e){var n=this.node();return(e=t.ns.qualify(e)).local?n.getAttributeNS(e.space,e.local):n.getAttribute(e)}for(r in e)this.each(K(r,e[r]));return this}return this.each(K(e,r))},Y.classed=function(t,e){if(arguments.length<2){if("string"==typeof t){var r=this.node(),n=(t=tt(t)).length,i=-1;if(e=r.classList){for(;++i=1?1:t(e)}}function kr(t){return function(e){return 1-t(1-e)}}function Ar(t){return function(e){return.5*(e<.5?t(2*e):2-t(2-2*e))}}function Mr(t){return t*t}function Sr(t){return t*t*t}function Er(t){if(t<=0)return 0;if(t>=1)return 1;var e=t*t,r=e*t;return 4*(t<.5?r:3*(t-e)+r-.75)}function Lr(t){return 1-Math.cos(t*Et)}function Cr(t){return Math.pow(2,10*(t-1))}function Pr(t){return 1-Math.sqrt(1-t*t)}function Ir(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375}function Or(t,e){return e-=t,function(r){return Math.round(t+e*r)}}function zr(t){var e,r,n,i=[t.a,t.b],a=[t.c,t.d],o=Rr(i),s=Dr(i,a),l=Rr(((e=a)[0]+=(n=-s)*(r=i)[0],e[1]+=n*r[1],e))||0;i[0]*a[1]=0?t.slice(0,e):t,i=e>=0?t.slice(e+1):"in";return n=_r.get(n)||br,Tr((i=wr.get(i)||C)(n.apply(null,r.call(arguments,1))))},t.interpolateHcl=function(e,r){e=t.hcl(e),r=t.hcl(r);var n=e.h,i=e.c,a=e.l,o=r.h-n,s=r.c-i,l=r.l-a;isNaN(s)&&(s=0,i=isNaN(i)?r.c:i);isNaN(o)?(o=0,n=isNaN(n)?r.h:n):o>180?o-=360:o<-180&&(o+=360);return function(t){return Ht(n+o*t,i+s*t,a+l*t)+""}},t.interpolateHsl=function(e,r){e=t.hsl(e),r=t.hsl(r);var n=e.h,i=e.s,a=e.l,o=r.h-n,s=r.s-i,l=r.l-a;isNaN(s)&&(s=0,i=isNaN(i)?r.s:i);isNaN(o)?(o=0,n=isNaN(n)?r.h:n):o>180?o-=360:o<-180&&(o+=360);return function(t){return jt(n+o*t,i+s*t,a+l*t)+""}},t.interpolateLab=function(e,r){e=t.lab(e),r=t.lab(r);var n=e.l,i=e.a,a=e.b,o=r.l-n,s=r.a-i,l=r.b-a;return function(t){return Wt(n+o*t,i+s*t,a+l*t)+""}},t.interpolateRound=Or,t.transform=function(e){var r=i.createElementNS(t.ns.prefix.svg,"g");return(t.transform=function(t){if(null!=t){r.setAttribute("transform",t);var e=r.transform.baseVal.consolidate()}return new zr(e?e.matrix:Fr)})(e)},zr.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var Fr={a:1,b:0,c:0,d:1,e:0,f:0};function Br(t){return t.length?t.pop()+",":""}function Nr(e,r){var n=[],i=[];return e=t.transform(e),r=t.transform(r),function(t,e,r,n){if(t[0]!==e[0]||t[1]!==e[1]){var i=r.push("translate(",null,",",null,")");n.push({i:i-4,x:dr(t[0],e[0])},{i:i-2,x:dr(t[1],e[1])})}else(e[0]||e[1])&&r.push("translate("+e+")")}(e.translate,r.translate,n,i),function(t,e,r,n){t!==e?(t-e>180?e+=360:e-t>180&&(t+=360),n.push({i:r.push(Br(r)+"rotate(",null,")")-2,x:dr(t,e)})):e&&r.push(Br(r)+"rotate("+e+")")}(e.rotate,r.rotate,n,i),function(t,e,r,n){t!==e?n.push({i:r.push(Br(r)+"skewX(",null,")")-2,x:dr(t,e)}):e&&r.push(Br(r)+"skewX("+e+")")}(e.skew,r.skew,n,i),function(t,e,r,n){if(t[0]!==e[0]||t[1]!==e[1]){var i=r.push(Br(r)+"scale(",null,",",null,")");n.push({i:i-4,x:dr(t[0],e[0])},{i:i-2,x:dr(t[1],e[1])})}else 1===e[0]&&1===e[1]||r.push(Br(r)+"scale("+e+")")}(e.scale,r.scale,n,i),e=r=null,function(t){for(var e,r=-1,a=i.length;++r0?n=t:(e.c=null,e.t=NaN,e=null,l.end({type:"end",alpha:n=0})):t>0&&(l.start({type:"start",alpha:n=t}),e=ve(s.tick)),s):n},s.start=function(){var t,e,r,n=v.length,l=y.length,u=c[0],d=c[1];for(t=0;tp.x&&(p=t),t.depth>d.depth&&(d=t)}));var m=r(h,p)/2-h.x,g=n[0]/(p.x+r(p,h)/2+m),v=n[1]/(d.depth||1);Qr(u,(function(t){t.x=(t.x+m)*g,t.y=t.depth*v}))}return c}function o(t){var e=t.children,n=t.parent.children,i=t.i?n[t.i-1]:null;if(e.length){!function(t){var e,r=0,n=0,i=t.children,a=i.length;for(;--a>=0;)(e=i[a]).z+=r,e.m+=r,r+=e.s+(n+=e.c)}(t);var a=(e[0].z+e[e.length-1].z)/2;i?(t.z=i.z+r(t._,i._),t.m=t.z-a):t.z=a}else i&&(t.z=i.z+r(t._,i._));t.parent.A=function(t,e,n){if(e){for(var i,a=t,o=t,s=e,l=a.parent.children[0],c=a.m,u=o.m,f=s.m,h=l.m;s=Ln(s),a=En(a),s&&a;)l=En(l),(o=Ln(o)).a=t,(i=s.z+f-a.z-c+r(s._,a._))>0&&(Cn(Pn(s,t,n),t,i),c+=i,u+=i),f+=s.m,c+=a.m,h+=l.m,u+=o.m;s&&!Ln(o)&&(o.t=s,o.m+=f-u),a&&!En(l)&&(l.t=a,l.m+=c-h,n=t)}return n}(t,i,t.parent.A||n[0])}function s(t){t._.x=t.z+t.parent.m,t.m+=t.parent.m}function l(t){t.x*=n[0],t.y=t.depth*n[1]}return a.separation=function(t){return arguments.length?(r=t,a):r},a.size=function(t){return arguments.length?(i=null==(n=t)?l:null,a):i?null:n},a.nodeSize=function(t){return arguments.length?(i=null==(n=t)?null:l,a):i?n:null},Kr(a,e)},t.layout.cluster=function(){var e=t.layout.hierarchy().sort(null).value(null),r=Sn,n=[1,1],i=!1;function a(a,o){var s,l=e.call(this,a,o),c=l[0],u=0;$r(c,(function(e){var n=e.children;n&&n.length?(e.x=function(t){return t.reduce((function(t,e){return t+e.x}),0)/t.length}(n),e.y=function(e){return 1+t.max(e,(function(t){return t.y}))}(n)):(e.x=s?u+=r(e,s):0,e.y=0,s=e)}));var f=function t(e){var r=e.children;return r&&r.length?t(r[0]):e}(c),h=function t(e){var r,n=e.children;return n&&(r=n.length)?t(n[r-1]):e}(c),p=f.x-r(f,h)/2,d=h.x+r(h,f)/2;return $r(c,i?function(t){t.x=(t.x-c.x)*n[0],t.y=(c.y-t.y)*n[1]}:function(t){t.x=(t.x-p)/(d-p)*n[0],t.y=(1-(c.y?t.y/c.y:1))*n[1]}),l}return a.separation=function(t){return arguments.length?(r=t,a):r},a.size=function(t){return arguments.length?(i=null==(n=t),a):i?null:n},a.nodeSize=function(t){return arguments.length?(i=null!=(n=t),a):i?n:null},Kr(a,e)},t.layout.treemap=function(){var e,r=t.layout.hierarchy(),n=Math.round,i=[1,1],a=null,o=In,s=!1,l="squarify",c=.5*(1+Math.sqrt(5));function u(t,e){for(var r,n,i=-1,a=t.length;++i0;)s.push(r=c[i-1]),s.area+=r.area,"squarify"!==l||(n=p(s,m))<=h?(c.pop(),h=n):(s.area-=s.pop().area,d(s,m,a,!1),m=Math.min(a.dx,a.dy),s.length=s.area=0,h=1/0);s.length&&(d(s,m,a,!0),s.length=s.area=0),e.forEach(f)}}function h(t){var e=t.children;if(e&&e.length){var r,n=o(t),i=e.slice(),a=[];for(u(i,n.dx*n.dy/t.value),a.area=0;r=i.pop();)a.push(r),a.area+=r.area,null!=r.z&&(d(a,r.z?n.dx:n.dy,n,!i.length),a.length=a.area=0);e.forEach(h)}}function p(t,e){for(var r,n=t.area,i=0,a=1/0,o=-1,s=t.length;++oi&&(i=r));return e*=e,(n*=n)?Math.max(e*i*c/n,n/(e*a*c)):1/0}function d(t,e,r,i){var a,o=-1,s=t.length,l=r.x,c=r.y,u=e?n(t.area/e):0;if(e==r.dx){for((i||u>r.dy)&&(u=r.dy);++or.dx)&&(u=r.dx);++o1);return t+e*r*Math.sqrt(-2*Math.log(i)/i)}},logNormal:function(){var e=t.random.normal.apply(t,arguments);return function(){return Math.exp(e())}},bates:function(e){var r=t.random.irwinHall(e);return function(){return r()/e}},irwinHall:function(t){return function(){for(var e=0,r=0;rl;u--);e=e.slice(c,u)}return e},s.copy=function(){return t(e.copy(),r,n,i)},Un(s,e)}(t.scale.linear().domain([0,1]),10,!0,[1,10])};var Gn={floor:function(t){return-Math.ceil(-t)},ceil:function(t){return-Math.floor(-t)}};function Yn(t){return function(e){return e<0?-Math.pow(-e,t):Math.pow(e,t)}}t.scale.pow=function(){return function t(e,r,n){var i=Yn(r),a=Yn(1/r);function o(t){return e(i(t))}return o.invert=function(t){return a(e.invert(t))},o.domain=function(t){return arguments.length?(e.domain((n=t.map(Number)).map(i)),o):n},o.ticks=function(t){return qn(n,t)},o.tickFormat=function(t,e){return d3_scale_linearTickFormat(n,t,e)},o.nice=function(t){return o.domain(Vn(n,t))},o.exponent=function(t){return arguments.length?(i=Yn(r=t),a=Yn(1/r),e.domain(n.map(i)),o):r},o.copy=function(){return t(e.copy(),r,n)},Un(o,e)}(t.scale.linear(),1,[0,1])},t.scale.sqrt=function(){return t.scale.pow().exponent(.5)},t.scale.ordinal=function(){return function e(r,n){var i,a,o;function s(t){return a[((i.get(t)||("range"===n.t?i.set(t,r.push(t)):NaN))-1)%a.length]}function l(e,n){return t.range(r.length).map((function(t){return e+n*t}))}return s.domain=function(t){if(!arguments.length)return r;r=[],i=new _;for(var e,a=-1,o=t.length;++a=St)return l(c,p)+(s?l(s,1-p):"")+"Z";var d,m,g,v,y,x,b,_,w,T,k,A,M=0,S=0,E=[];if((v=(+o.apply(this,arguments)||0)/2)&&(g=n===Qn?Math.sqrt(s*s+c*c):+n.apply(this,arguments),p||(S*=-1),c&&(S=Pt(g/c*Math.sin(v))),s&&(M=Pt(g/s*Math.sin(v)))),c){y=c*Math.cos(u+S),x=c*Math.sin(u+S),b=c*Math.cos(f-S),_=c*Math.sin(f-S);var L=Math.abs(f-u-2*S)<=At?0:1;if(S&&ii(y,x,b,_)===p^L){var C=(u+f)/2;y=c*Math.cos(C),x=c*Math.sin(C),b=_=null}}else y=x=0;if(s){w=s*Math.cos(f-M),T=s*Math.sin(f-M),k=s*Math.cos(u+M),A=s*Math.sin(u+M);var P=Math.abs(u-f+2*M)<=At?0:1;if(M&&ii(w,T,k,A)===1-p^P){var I=(u+f)/2;w=s*Math.cos(I),T=s*Math.sin(I),k=A=null}}else w=T=0;if(h>kt&&(d=Math.min(Math.abs(c-s)/2,+r.apply(this,arguments)))>.001){m=s=0&&0,t+e.replace(/\u001b\[\d\d?m/g,"").length+1}),0)>60)return r[0]+(""===e?"":e+"\n ")+" "+t.join(",\n ")+" "+r[1];return r[0]+e+" "+t.join(", ")+" "+r[1]}(c,b,A)):A[0]+b+A[1]}function f(t){return"["+Error.prototype.toString.call(t)+"]"}function h(t,e,r,n,i,a){var o,s,l;if((l=Object.getOwnPropertyDescriptor(e,i)||{value:e[i]}).get?s=l.set?t.stylize("[Getter/Setter]","special"):t.stylize("[Getter]","special"):l.set&&(s=t.stylize("[Setter]","special")),E(n,i)||(o="["+i+"]"),s||(t.seen.indexOf(l.value)<0?(s=m(r)?u(t,l.value,null):u(t,l.value,r-1)).indexOf("\n")>-1&&(s=a?s.split("\n").map((function(t){return" "+t})).join("\n").substr(2):"\n"+s.split("\n").map((function(t){return" "+t})).join("\n")):s=t.stylize("[Circular]","special")),y(o)){if(a&&i.match(/^\d+$/))return s;(o=JSON.stringify(""+i)).match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(o=o.substr(1,o.length-2),o=t.stylize(o,"name")):(o=o.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),o=t.stylize(o,"string"))}return o+": "+s}function p(t){return Array.isArray(t)}function d(t){return"boolean"==typeof t}function m(t){return null===t}function g(t){return"number"==typeof t}function v(t){return"string"==typeof t}function y(t){return void 0===t}function x(t){return b(t)&&"[object RegExp]"===k(t)}function b(t){return"object"==typeof t&&null!==t}function _(t){return b(t)&&"[object Date]"===k(t)}function w(t){return b(t)&&("[object Error]"===k(t)||t instanceof Error)}function T(t){return"function"==typeof t}function k(t){return Object.prototype.toString.call(t)}function A(t){return t<10?"0"+t.toString(10):t.toString(10)}r.debuglog=function(t){if(y(a)&&(a=e.env.NODE_DEBUG||""),t=t.toUpperCase(),!o[t])if(new RegExp("\\b"+t+"\\b","i").test(a)){var n=e.pid;o[t]=function(){var e=r.format.apply(r,arguments);console.error("%s %d: %s",t,n,e)}}else o[t]=function(){};return o[t]},r.inspect=s,s.colors={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]},s.styles={special:"cyan",number:"yellow",boolean:"yellow",undefined:"grey",null:"bold",string:"green",date:"magenta",regexp:"red"},r.isArray=p,r.isBoolean=d,r.isNull=m,r.isNullOrUndefined=function(t){return null==t},r.isNumber=g,r.isString=v,r.isSymbol=function(t){return"symbol"==typeof t},r.isUndefined=y,r.isRegExp=x,r.isObject=b,r.isDate=_,r.isError=w,r.isFunction=T,r.isPrimitive=function(t){return null===t||"boolean"==typeof t||"number"==typeof t||"string"==typeof t||"symbol"==typeof t||void 0===t},r.isBuffer=t("./support/isBuffer");var M=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function S(){var t=new Date,e=[A(t.getHours()),A(t.getMinutes()),A(t.getSeconds())].join(":");return[t.getDate(),M[t.getMonth()],e].join(" ")}function E(t,e){return Object.prototype.hasOwnProperty.call(t,e)}r.log=function(){console.log("%s - %s",S(),r.format.apply(r,arguments))},r.inherits=t("inherits"),r._extend=function(t,e){if(!e||!b(e))return t;for(var r=Object.keys(e),n=r.length;n--;)t[r[n]]=e[r[n]];return t}}).call(this)}).call(this,t("_process"),"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./support/isBuffer":77,_process:278,inherits:76}],79:[function(t,e,r){"use strict";r.byteLength=function(t){var e=c(t),r=e[0],n=e[1];return 3*(r+n)/4-n},r.toByteArray=function(t){var e,r,n=c(t),o=n[0],s=n[1],l=new a(function(t,e,r){return 3*(e+r)/4-r}(0,o,s)),u=0,f=s>0?o-4:o;for(r=0;rs?s:o+16383));1===i?(e=t[r-1],a.push(n[e>>2]+n[e<<4&63]+"==")):2===i&&(e=(t[r-2]<<8)+t[r-1],a.push(n[e>>10]+n[e>>4&63]+n[e<<2&63]+"="));return a.join("")};for(var n=[],i=[],a="undefined"!=typeof Uint8Array?Uint8Array:Array,o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s=0,l=o.length;ss&&(r=s-l),a=r;a>=0;a--){for(var f=!0,h=0;hf;)h.pop(),--p;var d,m=new Array(p+1);for(a=0;a<=p;++a)(d=m[a]=[]).x0=a>0?h[a-1]:u,d.x1=a(l=t.charCodeAt(a))||l>57){u=(46===l?p+t.slice(a+1):t.slice(a))+u,t=t.slice(0,a);break}}x&&!f&&(t=o(t,1/0));var S=c.length+t.length+u.length,E=Sa[o][2][0];++o);var l=t(e-a[o][1][0],n);return l[0]+=t(a[o][1][0],i*n>i*a[o][0][1]?a[o][0][1]:n)[0],l}n?o.invert=n(o):t.invert&&(o.invert=function(e,n){for(var i=a[+(n<0)],s=r[+(n<0)],l=0,c=i.length;l1.790857183?e=1.790857183:e<-1.790857183&&(e=-1.790857183);var r,i=e;do{var a=i*i;i-=r=(i*(1.0148+a*a*(.23185+a*(.02406*a-.14499)))-e)/(1.0148+a*a*(5*.23185+a*(.21654*a-1.01493)))}while(n(r)>v);return[t,i]},Fe.invert=function(t,e){if(n(e)pr&&p0?y-l:l)*A],u=e.geoProjection(t(s)).rotate(c),f=e.geoRotation(c),h=u.center;return delete u.rotate,u.center=function(t){return arguments.length?h(f(t)):f.invert(h())},u.clipAngle(90)}function Mr(t){var r=o(t);function n(t,n){var i=e.geoGnomonicRaw(t,n);return i[0]*=r,i}return n.invert=function(t,n){return e.geoGnomonicRaw.invert(t/r,n)},n}function Sr(t,e){return Ar(Mr,t,e)}function Er(t){if(!(t*=2))return e.geoAzimuthalEquidistantRaw;var r=-t/2,n=-r,i=t*t,s=g(n),l=.5/m(n);function c(e,a){var s=E(o(a)*o(e-r)),l=E(o(a)*o(e-n));return[((s*=s)-(l*=l))/(2*t),(a<0?-1:1)*L(4*i*l-(i-s+l)*(i-s+l))/(2*t)]}return c.invert=function(t,e){var i,c,u=e*e,f=o(L(u+(i=t+r)*i)),h=o(L(u+(i=t+n)*i));return[a(c=f-h,i=(f+h)*s),(e<0?-1:1)*E(L(i*i+c*c)*l)]},c}function Lr(t,e){return Ar(Er,t,e)}function Cr(t,e){if(n(e)nt&&(nt=e)),c?trr&&(rr=t);e