diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..96bff3b --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,63 @@ +name: Build Release + +on: + release: + types: [published] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + target: + - "ttgo-lora32-v21" + - "heltec-lora32-v2" + - "heltec_wifi_lora_32_V3" + - "ESP32_DIY_LoRa" + - "ESP32_DIY_1W_LoRa" + - "ttgo-t-beam-v1_2" + - "ttgo-t-beam-v1" + - "ttgo-t-beam-v1_SX1268" + - "ttgo-t-beam-v1_2_SX1262" + # - "heltec_wireless_stick_lite" # NOT FULLY TESTED + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: "3.9" + + - name: Install PlatformIO Core + run: pip install --upgrade platformio + + - name: Build target + run: pio run -e ${{ matrix.target }} + + - name: Build FS + run: pio run --target buildfs -e ${{ matrix.target }} + + - name: Move Files + run: | + mkdir -p installer/firmware + cp .pio/build/${{ matrix.target }}/firmware.bin installer/firmware/ + cp .pio/build/${{ matrix.target }}/bootloader.bin installer/firmware/ + cp .pio/build/${{ matrix.target }}/partitions.bin installer/firmware/ + cp .pio/build/${{ matrix.target }}/spiffs.bin installer/firmware/ + cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin installer/firmware/ + + - name: Install Zip + run: sudo apt-get install zip + + - name: Archive Files + run: zip -r installer.zip installer/ + + - name: Upload Release Asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: ./installer.zip + asset_name: ${{ matrix.target }}.zip + asset_content_type: application/zip diff --git a/.gitignore b/.gitignore index 4de822f..3d683e2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,7 @@ .vscode/launch.json .vscode/ipch .DS_Store +/data_embed/*.gz +installer/firmware +installer/*.bin +**/__pycache__/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f179091 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "editor.tabSize": 4, + "editor.formatOnSave": true +} diff --git a/data/igate_conf.json b/data/igate_conf.json index 15ad1fc..9c6974f 100644 --- a/data/igate_conf.json +++ b/data/igate_conf.json @@ -3,23 +3,29 @@ "stationMode": 2, "iGateComment": "LoRa_APRS_iGate", "wifi": { + "autoAP": { + "password": "1234567890", + "powerOff": 10 + }, "AP": [ - { "ssid": "WIFI_1", + { + "ssid": "WIFI_1", "password": "WIFI_1_password", - "latitude": 0.0000000, - "longitude": 0.0000000 + "latitude": 0.0, + "longitude": 0.0 }, - { "ssid": "WIFI_2", + { + "ssid": "WIFI_2", "password": "WIFI_2_password", - "latitude": 0.0000000, - "longitude": 0.0000000 + "latitude": 0.0, + "longitude": 0.0 } ] }, "digi": { "comment": "LoRa_APRS_Digirepeater", - "latitude": 0.0000000, - "longitude": 0.0000000 + "latitude": 0.0, + "longitude": 0.0 }, "aprs_is": { "passcode": "XYZVW", @@ -39,7 +45,7 @@ "display": { "alwaysOn": true, "timeout": 4, - "turn180" : false + "turn180": false }, "syslog": { "active": false, @@ -50,7 +56,7 @@ "active": false }, "ota": { - "username":"", + "username": "", "password": "" }, "other": { @@ -59,7 +65,7 @@ "igateRepeatsLoRaPackets": false, "rememberStationTime": 30, "sendBatteryVoltage": false, - "externalVoltageMeasurement" : false, + "externalVoltageMeasurement": false, "externalVoltagePin": 34 - } -} \ No newline at end of file + } +} diff --git a/data/process_form.php b/data/process_form.php deleted file mode 100644 index 4351854..0000000 --- a/data/process_form.php +++ /dev/null @@ -1,19 +0,0 @@ - $_POST['name'], - 'email' => $_POST['email'] - ]; - - // Convert the data array to JSON format - $json_data = json_encode($data); - - // Specify the path where you want to save the JSON file - $file_path = 'userdata.json'; - - // Save the JSON data to the file - file_put_contents($file_path, $json_data); - - echo "Data saved as JSON successfully."; -} -?> \ No newline at end of file diff --git a/data/script.js b/data/script.js deleted file mode 100644 index f0914d0..0000000 --- a/data/script.js +++ /dev/null @@ -1,34 +0,0 @@ -// Retrieve JSON data from the JSON file in SPIFFS -fetch('igate_conf.json') - .then(response => response.json()) - .then(data => { - // Process the JSON data - //displayJSONData(data); - - const jsonDataDiv = document.getElementById('json-container'); - jsonDataDiv.innerHTML = ''; // Clear the previous content - - if (Array.isArray(data)) { - // If data is an array, iterate through it - data.forEach(item => { - const itemDiv = document.createElement('div'); - itemDiv.textContent = JSON.stringify(item, null, 2); - jsonDataDiv.appendChild(itemDiv); - }); - } else { - // If data is not an array, display it directly - const itemDiv = document.createElement('div'); - itemDiv.textContent = JSON.stringify(data, null, 2); - jsonDataDiv.appendChild(itemDiv); - } - }) - .catch(error => { - console.error('There was a problem fetching the JSON data:', error); - }); - -// Display JSON data in the HTML -function displayJSONData(data) { - //const jsonContainer = document.getElementById('json-container'); - //jsonContainer.innerHTML = JSON.stringify(data, null, 2); - -} diff --git a/data/test1.html b/data/test1.html deleted file mode 100644 index 13db39d..0000000 --- a/data/test1.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - Save Form Data to JSON - - -

Save Form Data to JSON

- -
- -
- - -
- - -
- - \ No newline at end of file diff --git a/data/test_info_1.html b/data/test_info_1.html deleted file mode 100644 index 42411f8..0000000 --- a/data/test_info_1.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - JSON Data Example - - -
- - - \ No newline at end of file diff --git a/data_embed/bootstrap.css b/data_embed/bootstrap.css new file mode 100644 index 0000000..f38d232 --- /dev/null +++ b/data_embed/bootstrap.css @@ -0,0 +1,12200 @@ +@charset "UTF-8"; +/*! +* Bootstrap v5.3.2 (https://getbootstrap.com/) +* Copyright 2011-2023 The Bootstrap Authors +* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) +*/ +:root, +[data-bs-theme="light"] { + --bs-blue: #0d6efd; + --bs-indigo: #6610f2; + --bs-purple: #6f42c1; + --bs-pink: #d63384; + --bs-red: #dc3545; + --bs-orange: #fd7e14; + --bs-yellow: #ffc107; + --bs-green: #198754; + --bs-teal: #20c997; + --bs-cyan: #0dcaf0; + --bs-black: #000; + --bs-white: #fff; + --bs-gray: #6c757d; + --bs-gray-dark: #343a40; + --bs-gray-100: #f8f9fa; + --bs-gray-200: #e9ecef; + --bs-gray-300: #dee2e6; + --bs-gray-400: #ced4da; + --bs-gray-500: #adb5bd; + --bs-gray-600: #6c757d; + --bs-gray-700: #495057; + --bs-gray-800: #343a40; + --bs-gray-900: #212529; + --bs-primary: #0d6efd; + --bs-secondary: #6c757d; + --bs-success: #198754; + --bs-info: #0dcaf0; + --bs-warning: #ffc107; + --bs-danger: #dc3545; + --bs-light: #f8f9fa; + --bs-dark: #212529; + --bs-primary-rgb: 13, 110, 253; + --bs-secondary-rgb: 108, 117, 125; + --bs-success-rgb: 25, 135, 84; + --bs-info-rgb: 13, 202, 240; + --bs-warning-rgb: 255, 193, 7; + --bs-danger-rgb: 220, 53, 69; + --bs-light-rgb: 248, 249, 250; + --bs-dark-rgb: 33, 37, 41; + --bs-primary-text-emphasis: #052c65; + --bs-secondary-text-emphasis: #2b2f32; + --bs-success-text-emphasis: #0a3622; + --bs-info-text-emphasis: #055160; + --bs-warning-text-emphasis: #664d03; + --bs-danger-text-emphasis: #58151c; + --bs-light-text-emphasis: #495057; + --bs-dark-text-emphasis: #495057; + --bs-primary-bg-subtle: #cfe2ff; + --bs-secondary-bg-subtle: #e2e3e5; + --bs-success-bg-subtle: #d1e7dd; + --bs-info-bg-subtle: #cff4fc; + --bs-warning-bg-subtle: #fff3cd; + --bs-danger-bg-subtle: #f8d7da; + --bs-light-bg-subtle: #fcfcfd; + --bs-dark-bg-subtle: #ced4da; + --bs-primary-border-subtle: #9ec5fe; + --bs-secondary-border-subtle: #c4c8cb; + --bs-success-border-subtle: #a3cfbb; + --bs-info-border-subtle: #9eeaf9; + --bs-warning-border-subtle: #ffe69c; + --bs-danger-border-subtle: #f1aeb5; + --bs-light-border-subtle: #e9ecef; + --bs-dark-border-subtle: #adb5bd; + --bs-white-rgb: 255, 255, 255; + --bs-black-rgb: 0, 0, 0; + --bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", + Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", + Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", + "Segoe UI Symbol", "Noto Color Emoji"; + --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; + --bs-gradient: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.15), + rgba(255, 255, 255, 0) + ); + --bs-body-font-family: var(--bs-font-sans-serif); + --bs-body-font-size: 1rem; + --bs-body-font-weight: 400; + --bs-body-line-height: 1.5; + --bs-body-color: #212529; + --bs-body-color-rgb: 33, 37, 41; + --bs-body-bg: #fff; + --bs-body-bg-rgb: 255, 255, 255; + --bs-emphasis-color: #000; + --bs-emphasis-color-rgb: 0, 0, 0; + --bs-secondary-color: rgba(33, 37, 41, 0.75); + --bs-secondary-color-rgb: 33, 37, 41; + --bs-secondary-bg: #e9ecef; + --bs-secondary-bg-rgb: 233, 236, 239; + --bs-tertiary-color: rgba(33, 37, 41, 0.5); + --bs-tertiary-color-rgb: 33, 37, 41; + --bs-tertiary-bg: #f8f9fa; + --bs-tertiary-bg-rgb: 248, 249, 250; + --bs-heading-color: inherit; + --bs-link-color: #0d6efd; + --bs-link-color-rgb: 13, 110, 253; + --bs-link-decoration: underline; + --bs-link-hover-color: #0a58ca; + --bs-link-hover-color-rgb: 10, 88, 202; + --bs-code-color: #d63384; + --bs-highlight-color: #212529; + --bs-highlight-bg: #fff3cd; + --bs-border-width: 1px; + --bs-border-style: solid; + --bs-border-color: #dee2e6; + --bs-border-color-translucent: rgba(0, 0, 0, 0.175); + --bs-border-radius: 0.375rem; + --bs-border-radius-sm: 0.25rem; + --bs-border-radius-lg: 0.5rem; + --bs-border-radius-xl: 1rem; + --bs-border-radius-xxl: 2rem; + --bs-border-radius-2xl: var(--bs-border-radius-xxl); + --bs-border-radius-pill: 50rem; + --bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); + --bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); + --bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175); + --bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075); + --bs-focus-ring-width: 0.25rem; + --bs-focus-ring-opacity: 0.25; + --bs-focus-ring-color: rgba(13, 110, 253, 0.25); + --bs-form-valid-color: #198754; + --bs-form-valid-border-color: #198754; + --bs-form-invalid-color: #dc3545; + --bs-form-invalid-border-color: #dc3545; +} +[data-bs-theme="dark"] { + color-scheme: dark; + --bs-body-color: #dee2e6; + --bs-body-color-rgb: 222, 226, 230; + --bs-body-bg: #212529; + --bs-body-bg-rgb: 33, 37, 41; + --bs-emphasis-color: #fff; + --bs-emphasis-color-rgb: 255, 255, 255; + --bs-secondary-color: rgba(222, 226, 230, 0.75); + --bs-secondary-color-rgb: 222, 226, 230; + --bs-secondary-bg: #343a40; + --bs-secondary-bg-rgb: 52, 58, 64; + --bs-tertiary-color: rgba(222, 226, 230, 0.5); + --bs-tertiary-color-rgb: 222, 226, 230; + --bs-tertiary-bg: #2b3035; + --bs-tertiary-bg-rgb: 43, 48, 53; + --bs-primary-text-emphasis: #6ea8fe; + --bs-secondary-text-emphasis: #a7acb1; + --bs-success-text-emphasis: #75b798; + --bs-info-text-emphasis: #6edff6; + --bs-warning-text-emphasis: #ffda6a; + --bs-danger-text-emphasis: #ea868f; + --bs-light-text-emphasis: #f8f9fa; + --bs-dark-text-emphasis: #dee2e6; + --bs-primary-bg-subtle: #031633; + --bs-secondary-bg-subtle: #161719; + --bs-success-bg-subtle: #051b11; + --bs-info-bg-subtle: #032830; + --bs-warning-bg-subtle: #332701; + --bs-danger-bg-subtle: #2c0b0e; + --bs-light-bg-subtle: #343a40; + --bs-dark-bg-subtle: #1a1d20; + --bs-primary-border-subtle: #084298; + --bs-secondary-border-subtle: #41464b; + --bs-success-border-subtle: #0f5132; + --bs-info-border-subtle: #087990; + --bs-warning-border-subtle: #997404; + --bs-danger-border-subtle: #842029; + --bs-light-border-subtle: #495057; + --bs-dark-border-subtle: #343a40; + --bs-heading-color: inherit; + --bs-link-color: #6ea8fe; + --bs-link-hover-color: #8bb9fe; + --bs-link-color-rgb: 110, 168, 254; + --bs-link-hover-color-rgb: 139, 185, 254; + --bs-code-color: #e685b5; + --bs-highlight-color: #dee2e6; + --bs-highlight-bg: #664d03; + --bs-border-color: #495057; + --bs-border-color-translucent: rgba(255, 255, 255, 0.15); + --bs-form-valid-color: #75b798; + --bs-form-valid-border-color: #75b798; + --bs-form-invalid-color: #ea868f; + --bs-form-invalid-border-color: #ea868f; +} +*, +::after, +::before { + box-sizing: border-box; +} +@media (prefers-reduced-motion: no-preference) { + :root { + scroll-behavior: smooth; + } +} +body { + margin: 0; + font-family: var(--bs-body-font-family); + font-size: var(--bs-body-font-size); + font-weight: var(--bs-body-font-weight); + line-height: var(--bs-body-line-height); + color: var(--bs-body-color); + text-align: var(--bs-body-text-align); + background-color: var(--bs-body-bg); + -webkit-text-size-adjust: 100%; + -webkit-tap-highlight-color: transparent; +} +hr { + margin: 1rem 0; + color: inherit; + border: 0; + border-top: var(--bs-border-width) solid; + opacity: 0.25; +} +.h1, +.h2, +.h3, +.h4, +.h5, +.h6, +h1, +h2, +h3, +h4, +h5, +h6 { + margin-top: 0; + margin-bottom: 0.5rem; + font-weight: 500; + line-height: 1.2; + color: var(--bs-heading-color); +} +.h1, +h1 { + font-size: calc(1.375rem + 1.5vw); +} +@media (min-width: 1200px) { + .h1, + h1 { + font-size: 2.5rem; + } +} +.h2, +h2 { + font-size: calc(1.325rem + 0.9vw); +} +@media (min-width: 1200px) { + .h2, + h2 { + font-size: 2rem; + } +} +.h3, +h3 { + font-size: calc(1.3rem + 0.6vw); +} +@media (min-width: 1200px) { + .h3, + h3 { + font-size: 1.75rem; + } +} +.h4, +h4 { + font-size: calc(1.275rem + 0.3vw); +} +@media (min-width: 1200px) { + .h4, + h4 { + font-size: 1.5rem; + } +} +.h5, +h5 { + font-size: 1.25rem; +} +.h6, +h6 { + font-size: 1rem; +} +p { + margin-top: 0; + margin-bottom: 1rem; +} +abbr[title] { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + cursor: help; + -webkit-text-decoration-skip-ink: none; + text-decoration-skip-ink: none; +} +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; +} +ol, +ul { + padding-left: 2rem; +} +dl, +ol, +ul { + margin-top: 0; + margin-bottom: 1rem; +} +ol ol, +ol ul, +ul ol, +ul ul { + margin-bottom: 0; +} +dt { + font-weight: 700; +} +dd { + margin-bottom: 0.5rem; + margin-left: 0; +} +blockquote { + margin: 0 0 1rem; +} +b, +strong { + font-weight: bolder; +} +.small, +small { + font-size: 0.875em; +} +.mark, +mark { + padding: 0.1875em; + color: var(--bs-highlight-color); + background-color: var(--bs-highlight-bg); +} +sub, +sup { + position: relative; + font-size: 0.75em; + line-height: 0; + vertical-align: baseline; +} +sub { + bottom: -0.25em; +} +sup { + top: -0.5em; +} +a { + color: rgba( + var(--bs-link-color-rgb), + var(--bs-link-opacity, 1) + ); + text-decoration: underline; +} +a:hover { + --bs-link-color-rgb: var(--bs-link-hover-color-rgb); +} +a:not([href]):not([class]), +a:not([href]):not([class]):hover { + color: inherit; + text-decoration: none; +} +code, +kbd, +pre, +samp { + font-family: var(--bs-font-monospace); + font-size: 1em; +} +pre { + display: block; + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; + font-size: 0.875em; +} +pre code { + font-size: inherit; + color: inherit; + word-break: normal; +} +code { + font-size: 0.875em; + color: var(--bs-code-color); + word-wrap: break-word; +} +a > code { + color: inherit; +} +kbd { + padding: 0.1875rem 0.375rem; + font-size: 0.875em; + color: var(--bs-body-bg); + background-color: var(--bs-body-color); + border-radius: 0.25rem; +} +kbd kbd { + padding: 0; + font-size: 1em; +} +figure { + margin: 0 0 1rem; +} +img, +svg { + vertical-align: middle; +} +table { + caption-side: bottom; + border-collapse: collapse; +} +caption { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + color: var(--bs-secondary-color); + text-align: left; +} +th { + text-align: inherit; + text-align: -webkit-match-parent; +} +tbody, +td, +tfoot, +th, +thead, +tr { + border-color: inherit; + border-style: solid; + border-width: 0; +} +label { + display: inline-block; +} +button { + border-radius: 0; +} +button:focus:not(:focus-visible) { + outline: 0; +} +button, +input, +optgroup, +select, +textarea { + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +button, +select { + text-transform: none; +} +[role="button"] { + cursor: pointer; +} +select { + word-wrap: normal; +} +select:disabled { + opacity: 1; +} +[list]:not([type="date"]):not([type="datetime-local"]):not( + [type="month"] + ):not([type="week"]):not( + [type="time"] + )::-webkit-calendar-picker-indicator { + display: none !important; +} +[type="button"], +[type="reset"], +[type="submit"], +button { + -webkit-appearance: button; +} +[type="button"]:not(:disabled), +[type="reset"]:not(:disabled), +[type="submit"]:not(:disabled), +button:not(:disabled) { + cursor: pointer; +} +::-moz-focus-inner { + padding: 0; + border-style: none; +} +textarea { + resize: vertical; +} +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} +legend { + float: left; + width: 100%; + padding: 0; + margin-bottom: 0.5rem; + font-size: calc(1.275rem + 0.3vw); + line-height: inherit; +} +@media (min-width: 1200px) { + legend { + font-size: 1.5rem; + } +} +legend + * { + clear: left; +} +::-webkit-datetime-edit-day-field, +::-webkit-datetime-edit-fields-wrapper, +::-webkit-datetime-edit-hour-field, +::-webkit-datetime-edit-minute, +::-webkit-datetime-edit-month-field, +::-webkit-datetime-edit-text, +::-webkit-datetime-edit-year-field { + padding: 0; +} +::-webkit-inner-spin-button { + height: auto; +} +[type="search"] { + -webkit-appearance: textfield; + outline-offset: -2px; +} +::-webkit-search-decoration { + -webkit-appearance: none; +} +::-webkit-color-swatch-wrapper { + padding: 0; +} +::-webkit-file-upload-button { + font: inherit; + -webkit-appearance: button; +} +::file-selector-button { + font: inherit; + -webkit-appearance: button; +} +output { + display: inline-block; +} +iframe { + border: 0; +} +summary { + display: list-item; + cursor: pointer; +} +progress { + vertical-align: baseline; +} +[hidden] { + display: none !important; +} +.lead { + font-size: 1.25rem; + font-weight: 300; +} +.display-1 { + font-size: calc(1.625rem + 4.5vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-1 { + font-size: 5rem; + } +} +.display-2 { + font-size: calc(1.575rem + 3.9vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-2 { + font-size: 4.5rem; + } +} +.display-3 { + font-size: calc(1.525rem + 3.3vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-3 { + font-size: 4rem; + } +} +.display-4 { + font-size: calc(1.475rem + 2.7vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-4 { + font-size: 3.5rem; + } +} +.display-5 { + font-size: calc(1.425rem + 2.1vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-5 { + font-size: 3rem; + } +} +.display-6 { + font-size: calc(1.375rem + 1.5vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-6 { + font-size: 2.5rem; + } +} +.list-unstyled { + padding-left: 0; + list-style: none; +} +.list-inline { + padding-left: 0; + list-style: none; +} +.list-inline-item { + display: inline-block; +} +.list-inline-item:not(:last-child) { + margin-right: 0.5rem; +} +.initialism { + font-size: 0.875em; + text-transform: uppercase; +} +.blockquote { + margin-bottom: 1rem; + font-size: 1.25rem; +} +.blockquote > :last-child { + margin-bottom: 0; +} +.blockquote-footer { + margin-top: -1rem; + margin-bottom: 1rem; + font-size: 0.875em; + color: #6c757d; +} +.blockquote-footer::before { + content: "— "; +} +.img-fluid { + max-width: 100%; + height: auto; +} +.img-thumbnail { + padding: 0.25rem; + background-color: var(--bs-body-bg); + border: var(--bs-border-width) solid var(--bs-border-color); + border-radius: var(--bs-border-radius); + max-width: 100%; + height: auto; +} +.figure { + display: inline-block; +} +.figure-img { + margin-bottom: 0.5rem; + line-height: 1; +} +.figure-caption { + font-size: 0.875em; + color: var(--bs-secondary-color); +} +.container, +.container-fluid, +.container-lg, +.container-md, +.container-sm, +.container-xl, +.container-xxl { + --bs-gutter-x: 1.5rem; + --bs-gutter-y: 0; + width: 100%; + padding-right: calc(var(--bs-gutter-x) * 0.5); + padding-left: calc(var(--bs-gutter-x) * 0.5); + margin-right: auto; + margin-left: auto; +} +@media (min-width: 576px) { + .container, + .container-sm { + max-width: 540px; + } +} +@media (min-width: 768px) { + .container, + .container-md, + .container-sm { + max-width: 720px; + } +} +@media (min-width: 992px) { + .container, + .container-lg, + .container-md, + .container-sm { + max-width: 960px; + } +} +@media (min-width: 1200px) { + .container, + .container-lg, + .container-md, + .container-sm, + .container-xl { + max-width: 1140px; + } +} +@media (min-width: 1400px) { + .container, + .container-lg, + .container-md, + .container-sm, + .container-xl, + .container-xxl { + max-width: 1320px; + } +} +:root { + --bs-breakpoint-xs: 0; + --bs-breakpoint-sm: 576px; + --bs-breakpoint-md: 768px; + --bs-breakpoint-lg: 992px; + --bs-breakpoint-xl: 1200px; + --bs-breakpoint-xxl: 1400px; +} +.row { + --bs-gutter-x: 1.5rem; + --bs-gutter-y: 0; + display: flex; + flex-wrap: wrap; + margin-top: calc(-1 * var(--bs-gutter-y)); + margin-right: calc(-0.5 * var(--bs-gutter-x)); + margin-left: calc(-0.5 * var(--bs-gutter-x)); +} +.row > * { + flex-shrink: 0; + width: 100%; + max-width: 100%; + padding-right: calc(var(--bs-gutter-x) * 0.5); + padding-left: calc(var(--bs-gutter-x) * 0.5); + margin-top: var(--bs-gutter-y); +} +.col { + flex: 1 0 0%; +} +.row-cols-auto > * { + flex: 0 0 auto; + width: auto; +} +.row-cols-1 > * { + flex: 0 0 auto; + width: 100%; +} +.row-cols-2 > * { + flex: 0 0 auto; + width: 50%; +} +.row-cols-3 > * { + flex: 0 0 auto; + width: 33.33333333%; +} +.row-cols-4 > * { + flex: 0 0 auto; + width: 25%; +} +.row-cols-5 > * { + flex: 0 0 auto; + width: 20%; +} +.row-cols-6 > * { + flex: 0 0 auto; + width: 16.66666667%; +} +.col-auto { + flex: 0 0 auto; + width: auto; +} +.col-1 { + flex: 0 0 auto; + width: 8.33333333%; +} +.col-2 { + flex: 0 0 auto; + width: 16.66666667%; +} +.col-3 { + flex: 0 0 auto; + width: 25%; +} +.col-4 { + flex: 0 0 auto; + width: 33.33333333%; +} +.col-5 { + flex: 0 0 auto; + width: 41.66666667%; +} +.col-6 { + flex: 0 0 auto; + width: 50%; +} +.col-7 { + flex: 0 0 auto; + width: 58.33333333%; +} +.col-8 { + flex: 0 0 auto; + width: 66.66666667%; +} +.col-9 { + flex: 0 0 auto; + width: 75%; +} +.col-10 { + flex: 0 0 auto; + width: 83.33333333%; +} +.col-11 { + flex: 0 0 auto; + width: 91.66666667%; +} +.col-12 { + flex: 0 0 auto; + width: 100%; +} +.offset-1 { + margin-left: 8.33333333%; +} +.offset-2 { + margin-left: 16.66666667%; +} +.offset-3 { + margin-left: 25%; +} +.offset-4 { + margin-left: 33.33333333%; +} +.offset-5 { + margin-left: 41.66666667%; +} +.offset-6 { + margin-left: 50%; +} +.offset-7 { + margin-left: 58.33333333%; +} +.offset-8 { + margin-left: 66.66666667%; +} +.offset-9 { + margin-left: 75%; +} +.offset-10 { + margin-left: 83.33333333%; +} +.offset-11 { + margin-left: 91.66666667%; +} +.g-0, +.gx-0 { + --bs-gutter-x: 0; +} +.g-0, +.gy-0 { + --bs-gutter-y: 0; +} +.g-1, +.gx-1 { + --bs-gutter-x: 0.25rem; +} +.g-1, +.gy-1 { + --bs-gutter-y: 0.25rem; +} +.g-2, +.gx-2 { + --bs-gutter-x: 0.5rem; +} +.g-2, +.gy-2 { + --bs-gutter-y: 0.5rem; +} +.g-3, +.gx-3 { + --bs-gutter-x: 1rem; +} +.g-3, +.gy-3 { + --bs-gutter-y: 1rem; +} +.g-4, +.gx-4 { + --bs-gutter-x: 1.5rem; +} +.g-4, +.gy-4 { + --bs-gutter-y: 1.5rem; +} +.g-5, +.gx-5 { + --bs-gutter-x: 3rem; +} +.g-5, +.gy-5 { + --bs-gutter-y: 3rem; +} +@media (min-width: 576px) { + .col-sm { + flex: 1 0 0%; + } + .row-cols-sm-auto > * { + flex: 0 0 auto; + width: auto; + } + .row-cols-sm-1 > * { + flex: 0 0 auto; + width: 100%; + } + .row-cols-sm-2 > * { + flex: 0 0 auto; + width: 50%; + } + .row-cols-sm-3 > * { + flex: 0 0 auto; + width: 33.33333333%; + } + .row-cols-sm-4 > * { + flex: 0 0 auto; + width: 25%; + } + .row-cols-sm-5 > * { + flex: 0 0 auto; + width: 20%; + } + .row-cols-sm-6 > * { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-sm-auto { + flex: 0 0 auto; + width: auto; + } + .col-sm-1 { + flex: 0 0 auto; + width: 8.33333333%; + } + .col-sm-2 { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-sm-3 { + flex: 0 0 auto; + width: 25%; + } + .col-sm-4 { + flex: 0 0 auto; + width: 33.33333333%; + } + .col-sm-5 { + flex: 0 0 auto; + width: 41.66666667%; + } + .col-sm-6 { + flex: 0 0 auto; + width: 50%; + } + .col-sm-7 { + flex: 0 0 auto; + width: 58.33333333%; + } + .col-sm-8 { + flex: 0 0 auto; + width: 66.66666667%; + } + .col-sm-9 { + flex: 0 0 auto; + width: 75%; + } + .col-sm-10 { + flex: 0 0 auto; + width: 83.33333333%; + } + .col-sm-11 { + flex: 0 0 auto; + width: 91.66666667%; + } + .col-sm-12 { + flex: 0 0 auto; + width: 100%; + } + .offset-sm-0 { + margin-left: 0; + } + .offset-sm-1 { + margin-left: 8.33333333%; + } + .offset-sm-2 { + margin-left: 16.66666667%; + } + .offset-sm-3 { + margin-left: 25%; + } + .offset-sm-4 { + margin-left: 33.33333333%; + } + .offset-sm-5 { + margin-left: 41.66666667%; + } + .offset-sm-6 { + margin-left: 50%; + } + .offset-sm-7 { + margin-left: 58.33333333%; + } + .offset-sm-8 { + margin-left: 66.66666667%; + } + .offset-sm-9 { + margin-left: 75%; + } + .offset-sm-10 { + margin-left: 83.33333333%; + } + .offset-sm-11 { + margin-left: 91.66666667%; + } + .g-sm-0, + .gx-sm-0 { + --bs-gutter-x: 0; + } + .g-sm-0, + .gy-sm-0 { + --bs-gutter-y: 0; + } + .g-sm-1, + .gx-sm-1 { + --bs-gutter-x: 0.25rem; + } + .g-sm-1, + .gy-sm-1 { + --bs-gutter-y: 0.25rem; + } + .g-sm-2, + .gx-sm-2 { + --bs-gutter-x: 0.5rem; + } + .g-sm-2, + .gy-sm-2 { + --bs-gutter-y: 0.5rem; + } + .g-sm-3, + .gx-sm-3 { + --bs-gutter-x: 1rem; + } + .g-sm-3, + .gy-sm-3 { + --bs-gutter-y: 1rem; + } + .g-sm-4, + .gx-sm-4 { + --bs-gutter-x: 1.5rem; + } + .g-sm-4, + .gy-sm-4 { + --bs-gutter-y: 1.5rem; + } + .g-sm-5, + .gx-sm-5 { + --bs-gutter-x: 3rem; + } + .g-sm-5, + .gy-sm-5 { + --bs-gutter-y: 3rem; + } +} +@media (min-width: 768px) { + .col-md { + flex: 1 0 0%; + } + .row-cols-md-auto > * { + flex: 0 0 auto; + width: auto; + } + .row-cols-md-1 > * { + flex: 0 0 auto; + width: 100%; + } + .row-cols-md-2 > * { + flex: 0 0 auto; + width: 50%; + } + .row-cols-md-3 > * { + flex: 0 0 auto; + width: 33.33333333%; + } + .row-cols-md-4 > * { + flex: 0 0 auto; + width: 25%; + } + .row-cols-md-5 > * { + flex: 0 0 auto; + width: 20%; + } + .row-cols-md-6 > * { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-md-auto { + flex: 0 0 auto; + width: auto; + } + .col-md-1 { + flex: 0 0 auto; + width: 8.33333333%; + } + .col-md-2 { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-md-3 { + flex: 0 0 auto; + width: 25%; + } + .col-md-4 { + flex: 0 0 auto; + width: 33.33333333%; + } + .col-md-5 { + flex: 0 0 auto; + width: 41.66666667%; + } + .col-md-6 { + flex: 0 0 auto; + width: 50%; + } + .col-md-7 { + flex: 0 0 auto; + width: 58.33333333%; + } + .col-md-8 { + flex: 0 0 auto; + width: 66.66666667%; + } + .col-md-9 { + flex: 0 0 auto; + width: 75%; + } + .col-md-10 { + flex: 0 0 auto; + width: 83.33333333%; + } + .col-md-11 { + flex: 0 0 auto; + width: 91.66666667%; + } + .col-md-12 { + flex: 0 0 auto; + width: 100%; + } + .offset-md-0 { + margin-left: 0; + } + .offset-md-1 { + margin-left: 8.33333333%; + } + .offset-md-2 { + margin-left: 16.66666667%; + } + .offset-md-3 { + margin-left: 25%; + } + .offset-md-4 { + margin-left: 33.33333333%; + } + .offset-md-5 { + margin-left: 41.66666667%; + } + .offset-md-6 { + margin-left: 50%; + } + .offset-md-7 { + margin-left: 58.33333333%; + } + .offset-md-8 { + margin-left: 66.66666667%; + } + .offset-md-9 { + margin-left: 75%; + } + .offset-md-10 { + margin-left: 83.33333333%; + } + .offset-md-11 { + margin-left: 91.66666667%; + } + .g-md-0, + .gx-md-0 { + --bs-gutter-x: 0; + } + .g-md-0, + .gy-md-0 { + --bs-gutter-y: 0; + } + .g-md-1, + .gx-md-1 { + --bs-gutter-x: 0.25rem; + } + .g-md-1, + .gy-md-1 { + --bs-gutter-y: 0.25rem; + } + .g-md-2, + .gx-md-2 { + --bs-gutter-x: 0.5rem; + } + .g-md-2, + .gy-md-2 { + --bs-gutter-y: 0.5rem; + } + .g-md-3, + .gx-md-3 { + --bs-gutter-x: 1rem; + } + .g-md-3, + .gy-md-3 { + --bs-gutter-y: 1rem; + } + .g-md-4, + .gx-md-4 { + --bs-gutter-x: 1.5rem; + } + .g-md-4, + .gy-md-4 { + --bs-gutter-y: 1.5rem; + } + .g-md-5, + .gx-md-5 { + --bs-gutter-x: 3rem; + } + .g-md-5, + .gy-md-5 { + --bs-gutter-y: 3rem; + } +} +@media (min-width: 992px) { + .col-lg { + flex: 1 0 0%; + } + .row-cols-lg-auto > * { + flex: 0 0 auto; + width: auto; + } + .row-cols-lg-1 > * { + flex: 0 0 auto; + width: 100%; + } + .row-cols-lg-2 > * { + flex: 0 0 auto; + width: 50%; + } + .row-cols-lg-3 > * { + flex: 0 0 auto; + width: 33.33333333%; + } + .row-cols-lg-4 > * { + flex: 0 0 auto; + width: 25%; + } + .row-cols-lg-5 > * { + flex: 0 0 auto; + width: 20%; + } + .row-cols-lg-6 > * { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-lg-auto { + flex: 0 0 auto; + width: auto; + } + .col-lg-1 { + flex: 0 0 auto; + width: 8.33333333%; + } + .col-lg-2 { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-lg-3 { + flex: 0 0 auto; + width: 25%; + } + .col-lg-4 { + flex: 0 0 auto; + width: 33.33333333%; + } + .col-lg-5 { + flex: 0 0 auto; + width: 41.66666667%; + } + .col-lg-6 { + flex: 0 0 auto; + width: 50%; + } + .col-lg-7 { + flex: 0 0 auto; + width: 58.33333333%; + } + .col-lg-8 { + flex: 0 0 auto; + width: 66.66666667%; + } + .col-lg-9 { + flex: 0 0 auto; + width: 75%; + } + .col-lg-10 { + flex: 0 0 auto; + width: 83.33333333%; + } + .col-lg-11 { + flex: 0 0 auto; + width: 91.66666667%; + } + .col-lg-12 { + flex: 0 0 auto; + width: 100%; + } + .offset-lg-0 { + margin-left: 0; + } + .offset-lg-1 { + margin-left: 8.33333333%; + } + .offset-lg-2 { + margin-left: 16.66666667%; + } + .offset-lg-3 { + margin-left: 25%; + } + .offset-lg-4 { + margin-left: 33.33333333%; + } + .offset-lg-5 { + margin-left: 41.66666667%; + } + .offset-lg-6 { + margin-left: 50%; + } + .offset-lg-7 { + margin-left: 58.33333333%; + } + .offset-lg-8 { + margin-left: 66.66666667%; + } + .offset-lg-9 { + margin-left: 75%; + } + .offset-lg-10 { + margin-left: 83.33333333%; + } + .offset-lg-11 { + margin-left: 91.66666667%; + } + .g-lg-0, + .gx-lg-0 { + --bs-gutter-x: 0; + } + .g-lg-0, + .gy-lg-0 { + --bs-gutter-y: 0; + } + .g-lg-1, + .gx-lg-1 { + --bs-gutter-x: 0.25rem; + } + .g-lg-1, + .gy-lg-1 { + --bs-gutter-y: 0.25rem; + } + .g-lg-2, + .gx-lg-2 { + --bs-gutter-x: 0.5rem; + } + .g-lg-2, + .gy-lg-2 { + --bs-gutter-y: 0.5rem; + } + .g-lg-3, + .gx-lg-3 { + --bs-gutter-x: 1rem; + } + .g-lg-3, + .gy-lg-3 { + --bs-gutter-y: 1rem; + } + .g-lg-4, + .gx-lg-4 { + --bs-gutter-x: 1.5rem; + } + .g-lg-4, + .gy-lg-4 { + --bs-gutter-y: 1.5rem; + } + .g-lg-5, + .gx-lg-5 { + --bs-gutter-x: 3rem; + } + .g-lg-5, + .gy-lg-5 { + --bs-gutter-y: 3rem; + } +} +@media (min-width: 1200px) { + .col-xl { + flex: 1 0 0%; + } + .row-cols-xl-auto > * { + flex: 0 0 auto; + width: auto; + } + .row-cols-xl-1 > * { + flex: 0 0 auto; + width: 100%; + } + .row-cols-xl-2 > * { + flex: 0 0 auto; + width: 50%; + } + .row-cols-xl-3 > * { + flex: 0 0 auto; + width: 33.33333333%; + } + .row-cols-xl-4 > * { + flex: 0 0 auto; + width: 25%; + } + .row-cols-xl-5 > * { + flex: 0 0 auto; + width: 20%; + } + .row-cols-xl-6 > * { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-xl-auto { + flex: 0 0 auto; + width: auto; + } + .col-xl-1 { + flex: 0 0 auto; + width: 8.33333333%; + } + .col-xl-2 { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-xl-3 { + flex: 0 0 auto; + width: 25%; + } + .col-xl-4 { + flex: 0 0 auto; + width: 33.33333333%; + } + .col-xl-5 { + flex: 0 0 auto; + width: 41.66666667%; + } + .col-xl-6 { + flex: 0 0 auto; + width: 50%; + } + .col-xl-7 { + flex: 0 0 auto; + width: 58.33333333%; + } + .col-xl-8 { + flex: 0 0 auto; + width: 66.66666667%; + } + .col-xl-9 { + flex: 0 0 auto; + width: 75%; + } + .col-xl-10 { + flex: 0 0 auto; + width: 83.33333333%; + } + .col-xl-11 { + flex: 0 0 auto; + width: 91.66666667%; + } + .col-xl-12 { + flex: 0 0 auto; + width: 100%; + } + .offset-xl-0 { + margin-left: 0; + } + .offset-xl-1 { + margin-left: 8.33333333%; + } + .offset-xl-2 { + margin-left: 16.66666667%; + } + .offset-xl-3 { + margin-left: 25%; + } + .offset-xl-4 { + margin-left: 33.33333333%; + } + .offset-xl-5 { + margin-left: 41.66666667%; + } + .offset-xl-6 { + margin-left: 50%; + } + .offset-xl-7 { + margin-left: 58.33333333%; + } + .offset-xl-8 { + margin-left: 66.66666667%; + } + .offset-xl-9 { + margin-left: 75%; + } + .offset-xl-10 { + margin-left: 83.33333333%; + } + .offset-xl-11 { + margin-left: 91.66666667%; + } + .g-xl-0, + .gx-xl-0 { + --bs-gutter-x: 0; + } + .g-xl-0, + .gy-xl-0 { + --bs-gutter-y: 0; + } + .g-xl-1, + .gx-xl-1 { + --bs-gutter-x: 0.25rem; + } + .g-xl-1, + .gy-xl-1 { + --bs-gutter-y: 0.25rem; + } + .g-xl-2, + .gx-xl-2 { + --bs-gutter-x: 0.5rem; + } + .g-xl-2, + .gy-xl-2 { + --bs-gutter-y: 0.5rem; + } + .g-xl-3, + .gx-xl-3 { + --bs-gutter-x: 1rem; + } + .g-xl-3, + .gy-xl-3 { + --bs-gutter-y: 1rem; + } + .g-xl-4, + .gx-xl-4 { + --bs-gutter-x: 1.5rem; + } + .g-xl-4, + .gy-xl-4 { + --bs-gutter-y: 1.5rem; + } + .g-xl-5, + .gx-xl-5 { + --bs-gutter-x: 3rem; + } + .g-xl-5, + .gy-xl-5 { + --bs-gutter-y: 3rem; + } +} +@media (min-width: 1400px) { + .col-xxl { + flex: 1 0 0%; + } + .row-cols-xxl-auto > * { + flex: 0 0 auto; + width: auto; + } + .row-cols-xxl-1 > * { + flex: 0 0 auto; + width: 100%; + } + .row-cols-xxl-2 > * { + flex: 0 0 auto; + width: 50%; + } + .row-cols-xxl-3 > * { + flex: 0 0 auto; + width: 33.33333333%; + } + .row-cols-xxl-4 > * { + flex: 0 0 auto; + width: 25%; + } + .row-cols-xxl-5 > * { + flex: 0 0 auto; + width: 20%; + } + .row-cols-xxl-6 > * { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-xxl-auto { + flex: 0 0 auto; + width: auto; + } + .col-xxl-1 { + flex: 0 0 auto; + width: 8.33333333%; + } + .col-xxl-2 { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-xxl-3 { + flex: 0 0 auto; + width: 25%; + } + .col-xxl-4 { + flex: 0 0 auto; + width: 33.33333333%; + } + .col-xxl-5 { + flex: 0 0 auto; + width: 41.66666667%; + } + .col-xxl-6 { + flex: 0 0 auto; + width: 50%; + } + .col-xxl-7 { + flex: 0 0 auto; + width: 58.33333333%; + } + .col-xxl-8 { + flex: 0 0 auto; + width: 66.66666667%; + } + .col-xxl-9 { + flex: 0 0 auto; + width: 75%; + } + .col-xxl-10 { + flex: 0 0 auto; + width: 83.33333333%; + } + .col-xxl-11 { + flex: 0 0 auto; + width: 91.66666667%; + } + .col-xxl-12 { + flex: 0 0 auto; + width: 100%; + } + .offset-xxl-0 { + margin-left: 0; + } + .offset-xxl-1 { + margin-left: 8.33333333%; + } + .offset-xxl-2 { + margin-left: 16.66666667%; + } + .offset-xxl-3 { + margin-left: 25%; + } + .offset-xxl-4 { + margin-left: 33.33333333%; + } + .offset-xxl-5 { + margin-left: 41.66666667%; + } + .offset-xxl-6 { + margin-left: 50%; + } + .offset-xxl-7 { + margin-left: 58.33333333%; + } + .offset-xxl-8 { + margin-left: 66.66666667%; + } + .offset-xxl-9 { + margin-left: 75%; + } + .offset-xxl-10 { + margin-left: 83.33333333%; + } + .offset-xxl-11 { + margin-left: 91.66666667%; + } + .g-xxl-0, + .gx-xxl-0 { + --bs-gutter-x: 0; + } + .g-xxl-0, + .gy-xxl-0 { + --bs-gutter-y: 0; + } + .g-xxl-1, + .gx-xxl-1 { + --bs-gutter-x: 0.25rem; + } + .g-xxl-1, + .gy-xxl-1 { + --bs-gutter-y: 0.25rem; + } + .g-xxl-2, + .gx-xxl-2 { + --bs-gutter-x: 0.5rem; + } + .g-xxl-2, + .gy-xxl-2 { + --bs-gutter-y: 0.5rem; + } + .g-xxl-3, + .gx-xxl-3 { + --bs-gutter-x: 1rem; + } + .g-xxl-3, + .gy-xxl-3 { + --bs-gutter-y: 1rem; + } + .g-xxl-4, + .gx-xxl-4 { + --bs-gutter-x: 1.5rem; + } + .g-xxl-4, + .gy-xxl-4 { + --bs-gutter-y: 1.5rem; + } + .g-xxl-5, + .gx-xxl-5 { + --bs-gutter-x: 3rem; + } + .g-xxl-5, + .gy-xxl-5 { + --bs-gutter-y: 3rem; + } +} +.table { + --bs-table-color-type: initial; + --bs-table-bg-type: initial; + --bs-table-color-state: initial; + --bs-table-bg-state: initial; + --bs-table-color: var(--bs-emphasis-color); + --bs-table-bg: var(--bs-body-bg); + --bs-table-border-color: var(--bs-border-color); + --bs-table-accent-bg: transparent; + --bs-table-striped-color: var(--bs-emphasis-color); + --bs-table-striped-bg: rgba(var(--bs-emphasis-color-rgb), 0.05); + --bs-table-active-color: var(--bs-emphasis-color); + --bs-table-active-bg: rgba(var(--bs-emphasis-color-rgb), 0.1); + --bs-table-hover-color: var(--bs-emphasis-color); + --bs-table-hover-bg: rgba(var(--bs-emphasis-color-rgb), 0.075); + width: 100%; + margin-bottom: 1rem; + vertical-align: top; + border-color: var(--bs-table-border-color); +} +.table > :not(caption) > * > * { + padding: 0.5rem 0.5rem; + color: var( + --bs-table-color-state, + var(--bs-table-color-type, var(--bs-table-color)) + ); + background-color: var(--bs-table-bg); + border-bottom-width: var(--bs-border-width); + box-shadow: inset 0 0 0 9999px + var( + --bs-table-bg-state, + var(--bs-table-bg-type, var(--bs-table-accent-bg)) + ); +} +.table > tbody { + vertical-align: inherit; +} +.table > thead { + vertical-align: bottom; +} +.table-group-divider { + border-top: calc(var(--bs-border-width) * 2) solid currentcolor; +} +.caption-top { + caption-side: top; +} +.table-sm > :not(caption) > * > * { + padding: 0.25rem 0.25rem; +} +.table-bordered > :not(caption) > * { + border-width: var(--bs-border-width) 0; +} +.table-bordered > :not(caption) > * > * { + border-width: 0 var(--bs-border-width); +} +.table-borderless > :not(caption) > * > * { + border-bottom-width: 0; +} +.table-borderless > :not(:first-child) { + border-top-width: 0; +} +.table-striped > tbody > tr:nth-of-type(odd) > * { + --bs-table-color-type: var(--bs-table-striped-color); + --bs-table-bg-type: var(--bs-table-striped-bg); +} +.table-striped-columns > :not(caption) > tr > :nth-child(2n) { + --bs-table-color-type: var(--bs-table-striped-color); + --bs-table-bg-type: var(--bs-table-striped-bg); +} +.table-active { + --bs-table-color-state: var(--bs-table-active-color); + --bs-table-bg-state: var(--bs-table-active-bg); +} +.table-hover > tbody > tr:hover > * { + --bs-table-color-state: var(--bs-table-hover-color); + --bs-table-bg-state: var(--bs-table-hover-bg); +} +.table-primary { + --bs-table-color: #000; + --bs-table-bg: #cfe2ff; + --bs-table-border-color: #a6b5cc; + --bs-table-striped-bg: #c5d7f2; + --bs-table-striped-color: #000; + --bs-table-active-bg: #bacbe6; + --bs-table-active-color: #000; + --bs-table-hover-bg: #bfd1ec; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} +.table-secondary { + --bs-table-color: #000; + --bs-table-bg: #e2e3e5; + --bs-table-border-color: #b5b6b7; + --bs-table-striped-bg: #d7d8da; + --bs-table-striped-color: #000; + --bs-table-active-bg: #cbccce; + --bs-table-active-color: #000; + --bs-table-hover-bg: #d1d2d4; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} +.table-success { + --bs-table-color: #000; + --bs-table-bg: #d1e7dd; + --bs-table-border-color: #a7b9b1; + --bs-table-striped-bg: #c7dbd2; + --bs-table-striped-color: #000; + --bs-table-active-bg: #bcd0c7; + --bs-table-active-color: #000; + --bs-table-hover-bg: #c1d6cc; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} +.table-info { + --bs-table-color: #000; + --bs-table-bg: #cff4fc; + --bs-table-border-color: #a6c3ca; + --bs-table-striped-bg: #c5e8ef; + --bs-table-striped-color: #000; + --bs-table-active-bg: #badce3; + --bs-table-active-color: #000; + --bs-table-hover-bg: #bfe2e9; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} +.table-warning { + --bs-table-color: #000; + --bs-table-bg: #fff3cd; + --bs-table-border-color: #ccc2a4; + --bs-table-striped-bg: #f2e7c3; + --bs-table-striped-color: #000; + --bs-table-active-bg: #e6dbb9; + --bs-table-active-color: #000; + --bs-table-hover-bg: #ece1be; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} +.table-danger { + --bs-table-color: #000; + --bs-table-bg: #f8d7da; + --bs-table-border-color: #c6acae; + --bs-table-striped-bg: #eccccf; + --bs-table-striped-color: #000; + --bs-table-active-bg: #dfc2c4; + --bs-table-active-color: #000; + --bs-table-hover-bg: #e5c7ca; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} +.table-light { + --bs-table-color: #000; + --bs-table-bg: #f8f9fa; + --bs-table-border-color: #c6c7c8; + --bs-table-striped-bg: #ecedee; + --bs-table-striped-color: #000; + --bs-table-active-bg: #dfe0e1; + --bs-table-active-color: #000; + --bs-table-hover-bg: #e5e6e7; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} +.table-dark { + --bs-table-color: #fff; + --bs-table-bg: #212529; + --bs-table-border-color: #4d5154; + --bs-table-striped-bg: #2c3034; + --bs-table-striped-color: #fff; + --bs-table-active-bg: #373b3e; + --bs-table-active-color: #fff; + --bs-table-hover-bg: #323539; + --bs-table-hover-color: #fff; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} +.table-responsive { + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} +@media (max-width: 575.98px) { + .table-responsive-sm { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} +@media (max-width: 767.98px) { + .table-responsive-md { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} +@media (max-width: 991.98px) { + .table-responsive-lg { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} +@media (max-width: 1199.98px) { + .table-responsive-xl { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} +@media (max-width: 1399.98px) { + .table-responsive-xxl { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} +.form-label { + margin-bottom: 0.5rem; +} +.col-form-label { + padding-top: calc(0.375rem + var(--bs-border-width)); + padding-bottom: calc(0.375rem + var(--bs-border-width)); + margin-bottom: 0; + font-size: inherit; + line-height: 1.5; +} +.col-form-label-lg { + padding-top: calc(0.5rem + var(--bs-border-width)); + padding-bottom: calc(0.5rem + var(--bs-border-width)); + font-size: 1.25rem; +} +.col-form-label-sm { + padding-top: calc(0.25rem + var(--bs-border-width)); + padding-bottom: calc(0.25rem + var(--bs-border-width)); + font-size: 0.875rem; +} +.form-text { + margin-top: 0.25rem; + font-size: 0.875em; + color: var(--bs-secondary-color); +} +.form-control { + display: block; + width: 100%; + padding: 0.375rem 0.75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: var(--bs-body-color); + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: var(--bs-body-bg); + background-clip: padding-box; + border: var(--bs-border-width) solid var(--bs-border-color); + border-radius: var(--bs-border-radius); + transition: border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-control { + transition: none; + } +} +.form-control[type="file"] { + overflow: hidden; +} +.form-control[type="file"]:not(:disabled):not([readonly]) { + cursor: pointer; +} +.form-control:focus { + color: var(--bs-body-color); + background-color: var(--bs-body-bg); + border-color: #86b7fe; + outline: 0; + box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); +} +.form-control::-webkit-date-and-time-value { + min-width: 85px; + height: 1.5em; + margin: 0; +} +.form-control::-webkit-datetime-edit { + display: block; + padding: 0; +} +.form-control::-moz-placeholder { + color: var(--bs-secondary-color); + opacity: 1; +} +.form-control::placeholder { + color: var(--bs-secondary-color); + opacity: 1; +} +.form-control:disabled { + background-color: var(--bs-secondary-bg); + opacity: 1; +} +.form-control::-webkit-file-upload-button { + padding: 0.375rem 0.75rem; + margin: -0.375rem -0.75rem; + -webkit-margin-end: 0.75rem; + margin-inline-end: 0.75rem; + color: var(--bs-body-color); + background-color: var(--bs-tertiary-bg); + pointer-events: none; + border-color: inherit; + border-style: solid; + border-width: 0; + border-inline-end-width: var(--bs-border-width); + border-radius: 0; + -webkit-transition: color 0.15s ease-in-out, + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: color 0.15s ease-in-out, + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +.form-control::file-selector-button { + padding: 0.375rem 0.75rem; + margin: -0.375rem -0.75rem; + -webkit-margin-end: 0.75rem; + margin-inline-end: 0.75rem; + color: var(--bs-body-color); + background-color: var(--bs-tertiary-bg); + pointer-events: none; + border-color: inherit; + border-style: solid; + border-width: 0; + border-inline-end-width: var(--bs-border-width); + border-radius: 0; + transition: color 0.15s ease-in-out, + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-control::-webkit-file-upload-button { + -webkit-transition: none; + transition: none; + } + .form-control::file-selector-button { + transition: none; + } +} +.form-control:hover:not(:disabled):not( + [readonly] + )::-webkit-file-upload-button { + background-color: var(--bs-secondary-bg); +} +.form-control:hover:not(:disabled):not( + [readonly] + )::file-selector-button { + background-color: var(--bs-secondary-bg); +} +.form-control-plaintext { + display: block; + width: 100%; + padding: 0.375rem 0; + margin-bottom: 0; + line-height: 1.5; + color: var(--bs-body-color); + background-color: transparent; + border: solid transparent; + border-width: var(--bs-border-width) 0; +} +.form-control-plaintext:focus { + outline: 0; +} +.form-control-plaintext.form-control-lg, +.form-control-plaintext.form-control-sm { + padding-right: 0; + padding-left: 0; +} +.form-control-sm { + min-height: calc( + 1.5em + 0.5rem + calc(var(--bs-border-width) * 2) + ); + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + border-radius: var(--bs-border-radius-sm); +} +.form-control-sm::-webkit-file-upload-button { + padding: 0.25rem 0.5rem; + margin: -0.25rem -0.5rem; + -webkit-margin-end: 0.5rem; + margin-inline-end: 0.5rem; +} +.form-control-sm::file-selector-button { + padding: 0.25rem 0.5rem; + margin: -0.25rem -0.5rem; + -webkit-margin-end: 0.5rem; + margin-inline-end: 0.5rem; +} +.form-control-lg { + min-height: calc( + 1.5em + 1rem + calc(var(--bs-border-width) * 2) + ); + padding: 0.5rem 1rem; + font-size: 1.25rem; + border-radius: var(--bs-border-radius-lg); +} +.form-control-lg::-webkit-file-upload-button { + padding: 0.5rem 1rem; + margin: -0.5rem -1rem; + -webkit-margin-end: 1rem; + margin-inline-end: 1rem; +} +.form-control-lg::file-selector-button { + padding: 0.5rem 1rem; + margin: -0.5rem -1rem; + -webkit-margin-end: 1rem; + margin-inline-end: 1rem; +} +textarea.form-control { + min-height: calc( + 1.5em + 0.75rem + calc(var(--bs-border-width) * 2) + ); +} +textarea.form-control-sm { + min-height: calc( + 1.5em + 0.5rem + calc(var(--bs-border-width) * 2) + ); +} +textarea.form-control-lg { + min-height: calc( + 1.5em + 1rem + calc(var(--bs-border-width) * 2) + ); +} +.form-control-color { + width: 3rem; + height: calc( + 1.5em + 0.75rem + calc(var(--bs-border-width) * 2) + ); + padding: 0.375rem; +} +.form-control-color:not(:disabled):not([readonly]) { + cursor: pointer; +} +.form-control-color::-moz-color-swatch { + border: 0 !important; + border-radius: var(--bs-border-radius); +} +.form-control-color::-webkit-color-swatch { + border: 0 !important; + border-radius: var(--bs-border-radius); +} +.form-control-color.form-control-sm { + height: calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2)); +} +.form-control-color.form-control-lg { + height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2)); +} +.form-select { + --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e"); + display: block; + width: 100%; + padding: 0.375rem 2.25rem 0.375rem 0.75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: var(--bs-body-color); + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: var(--bs-body-bg); + background-image: var(--bs-form-select-bg-img), + var(--bs-form-select-bg-icon, none); + background-repeat: no-repeat; + background-position: right 0.75rem center; + background-size: 16px 12px; + border: var(--bs-border-width) solid var(--bs-border-color); + border-radius: var(--bs-border-radius); + transition: border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-select { + transition: none; + } +} +.form-select:focus { + border-color: #86b7fe; + outline: 0; + box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); +} +.form-select[multiple], +.form-select[size]:not([size="1"]) { + padding-right: 0.75rem; + background-image: none; +} +.form-select:disabled { + background-color: var(--bs-secondary-bg); +} +.form-select:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 var(--bs-body-color); +} +.form-select-sm { + padding-top: 0.25rem; + padding-bottom: 0.25rem; + padding-left: 0.5rem; + font-size: 0.875rem; + border-radius: var(--bs-border-radius-sm); +} +.form-select-lg { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + padding-left: 1rem; + font-size: 1.25rem; + border-radius: var(--bs-border-radius-lg); +} +[data-bs-theme="dark"] .form-select { + --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e"); +} +.form-check { + display: block; + min-height: 1.5rem; + padding-left: 1.5em; + margin-bottom: 0.125rem; +} +.form-check .form-check-input { + float: left; + margin-left: -1.5em; +} +.form-check-reverse { + padding-right: 1.5em; + padding-left: 0; + text-align: right; +} +.form-check-reverse .form-check-input { + float: right; + margin-right: -1.5em; + margin-left: 0; +} +.form-check-input { + --bs-form-check-bg: var(--bs-body-bg); + flex-shrink: 0; + width: 1em; + height: 1em; + margin-top: 0.25em; + vertical-align: top; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: var(--bs-form-check-bg); + background-image: var(--bs-form-check-bg-image); + background-repeat: no-repeat; + background-position: center; + background-size: contain; + border: var(--bs-border-width) solid var(--bs-border-color); + -webkit-print-color-adjust: exact; + color-adjust: exact; + print-color-adjust: exact; +} +.form-check-input[type="checkbox"] { + border-radius: 0.25em; +} +.form-check-input[type="radio"] { + border-radius: 50%; +} +.form-check-input:active { + filter: brightness(90%); +} +.form-check-input:focus { + border-color: #86b7fe; + outline: 0; + box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); +} +.form-check-input:checked { + background-color: #0d6efd; + border-color: #0d6efd; +} +.form-check-input:checked[type="checkbox"] { + --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e"); +} +.form-check-input:checked[type="radio"] { + --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e"); +} +.form-check-input[type="checkbox"]:indeterminate { + background-color: #0d6efd; + border-color: #0d6efd; + --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e"); +} +.form-check-input:disabled { + pointer-events: none; + filter: none; + opacity: 0.5; +} +.form-check-input:disabled ~ .form-check-label, +.form-check-input[disabled] ~ .form-check-label { + cursor: default; + opacity: 0.5; +} +.form-switch { + padding-left: 2.5em; +} +.form-switch .form-check-input { + --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e"); + width: 2em; + margin-left: -2.5em; + background-image: var(--bs-form-switch-bg); + background-position: left center; + border-radius: 2em; + transition: background-position 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-switch .form-check-input { + transition: none; + } +} +.form-switch .form-check-input:focus { + --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e"); +} +.form-switch .form-check-input:checked { + background-position: right center; + --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e"); +} +.form-switch.form-check-reverse { + padding-right: 2.5em; + padding-left: 0; +} +.form-switch.form-check-reverse .form-check-input { + margin-right: -2.5em; + margin-left: 0; +} +.form-check-inline { + display: inline-block; + margin-right: 1rem; +} +.btn-check { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} +.btn-check:disabled + .btn, +.btn-check[disabled] + .btn { + pointer-events: none; + filter: none; + opacity: 0.65; +} +[data-bs-theme="dark"] + .form-switch + .form-check-input:not(:checked):not(:focus) { + --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e"); +} +.form-range { + width: 100%; + height: 1.5rem; + padding: 0; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: transparent; +} +.form-range:focus { + outline: 0; +} +.form-range:focus::-webkit-slider-thumb { + box-shadow: 0 0 0 1px #fff, + 0 0 0 0.25rem rgba(13, 110, 253, 0.25); +} +.form-range:focus::-moz-range-thumb { + box-shadow: 0 0 0 1px #fff, + 0 0 0 0.25rem rgba(13, 110, 253, 0.25); +} +.form-range::-moz-focus-outer { + border: 0; +} +.form-range::-webkit-slider-thumb { + width: 1rem; + height: 1rem; + margin-top: -0.25rem; + -webkit-appearance: none; + appearance: none; + background-color: #0d6efd; + border: 0; + border-radius: 1rem; + -webkit-transition: background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-range::-webkit-slider-thumb { + -webkit-transition: none; + transition: none; + } +} +.form-range::-webkit-slider-thumb:active { + background-color: #b6d4fe; +} +.form-range::-webkit-slider-runnable-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: var(--bs-secondary-bg); + border-color: transparent; + border-radius: 1rem; +} +.form-range::-moz-range-thumb { + width: 1rem; + height: 1rem; + -moz-appearance: none; + appearance: none; + background-color: #0d6efd; + border: 0; + border-radius: 1rem; + -moz-transition: background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-range::-moz-range-thumb { + -moz-transition: none; + transition: none; + } +} +.form-range::-moz-range-thumb:active { + background-color: #b6d4fe; +} +.form-range::-moz-range-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: var(--bs-secondary-bg); + border-color: transparent; + border-radius: 1rem; +} +.form-range:disabled { + pointer-events: none; +} +.form-range:disabled::-webkit-slider-thumb { + background-color: var(--bs-secondary-color); +} +.form-range:disabled::-moz-range-thumb { + background-color: var(--bs-secondary-color); +} +.form-floating { + position: relative; +} +.form-floating > .form-control, +.form-floating > .form-control-plaintext, +.form-floating > .form-select { + height: calc(3.5rem + calc(var(--bs-border-width) * 2)); + min-height: calc(3.5rem + calc(var(--bs-border-width) * 2)); + line-height: 1.25; +} +.form-floating > label { + position: absolute; + top: 0; + left: 0; + z-index: 2; + height: 100%; + padding: 1rem 0.75rem; + overflow: hidden; + text-align: start; + text-overflow: ellipsis; + white-space: nowrap; + pointer-events: none; + border: var(--bs-border-width) solid transparent; + transform-origin: 0 0; + transition: opacity 0.1s ease-in-out, transform 0.1s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-floating > label { + transition: none; + } +} +.form-floating > .form-control, +.form-floating > .form-control-plaintext { + padding: 1rem 0.75rem; +} +.form-floating > .form-control-plaintext::-moz-placeholder, +.form-floating > .form-control::-moz-placeholder { + color: transparent; +} +.form-floating > .form-control-plaintext::placeholder, +.form-floating > .form-control::placeholder { + color: transparent; +} +.form-floating + > .form-control-plaintext:not(:-moz-placeholder-shown), +.form-floating > .form-control:not(:-moz-placeholder-shown) { + padding-top: 1.625rem; + padding-bottom: 0.625rem; +} +.form-floating > .form-control-plaintext:focus, +.form-floating > .form-control-plaintext:not(:placeholder-shown), +.form-floating > .form-control:focus, +.form-floating > .form-control:not(:placeholder-shown) { + padding-top: 1.625rem; + padding-bottom: 0.625rem; +} +.form-floating > .form-control-plaintext:-webkit-autofill, +.form-floating > .form-control:-webkit-autofill { + padding-top: 1.625rem; + padding-bottom: 0.625rem; +} +.form-floating > .form-select { + padding-top: 1.625rem; + padding-bottom: 0.625rem; +} +.form-floating + > .form-control:not(:-moz-placeholder-shown) + ~ label { + color: rgba(var(--bs-body-color-rgb), 0.65); + transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); +} +.form-floating > .form-control-plaintext ~ label, +.form-floating > .form-control:focus ~ label, +.form-floating > .form-control:not(:placeholder-shown) ~ label, +.form-floating > .form-select ~ label { + color: rgba(var(--bs-body-color-rgb), 0.65); + transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); +} +.form-floating + > .form-control:not(:-moz-placeholder-shown) + ~ label::after { + position: absolute; + inset: 1rem 0.375rem; + z-index: -1; + height: 1.5em; + content: ""; + background-color: var(--bs-body-bg); + border-radius: var(--bs-border-radius); +} +.form-floating > .form-control-plaintext ~ label::after, +.form-floating > .form-control:focus ~ label::after, +.form-floating + > .form-control:not(:placeholder-shown) + ~ label::after, +.form-floating > .form-select ~ label::after { + position: absolute; + inset: 1rem 0.375rem; + z-index: -1; + height: 1.5em; + content: ""; + background-color: var(--bs-body-bg); + border-radius: var(--bs-border-radius); +} +.form-floating > .form-control:-webkit-autofill ~ label { + color: rgba(var(--bs-body-color-rgb), 0.65); + transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); +} +.form-floating > .form-control-plaintext ~ label { + border-width: var(--bs-border-width) 0; +} +.form-floating > .form-control:disabled ~ label, +.form-floating > :disabled ~ label { + color: #6c757d; +} +.form-floating > .form-control:disabled ~ label::after, +.form-floating > :disabled ~ label::after { + background-color: var(--bs-secondary-bg); +} +.input-group { + position: relative; + display: flex; + flex-wrap: wrap; + align-items: stretch; + width: 100%; +} +.input-group > .form-control, +.input-group > .form-floating, +.input-group > .form-select { + position: relative; + flex: 1 1 auto; + width: 1%; + min-width: 0; +} +.input-group > .form-control:focus, +.input-group > .form-floating:focus-within, +.input-group > .form-select:focus { + z-index: 5; +} +.input-group .btn { + position: relative; + z-index: 2; +} +.input-group .btn:focus { + z-index: 5; +} +.input-group-text { + display: flex; + align-items: center; + padding: 0.375rem 0.75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: var(--bs-body-color); + text-align: center; + white-space: nowrap; + background-color: var(--bs-tertiary-bg); + border: var(--bs-border-width) solid var(--bs-border-color); + border-radius: var(--bs-border-radius); +} +.input-group-lg > .btn, +.input-group-lg > .form-control, +.input-group-lg > .form-select, +.input-group-lg > .input-group-text { + padding: 0.5rem 1rem; + font-size: 1.25rem; + border-radius: var(--bs-border-radius-lg); +} +.input-group-sm > .btn, +.input-group-sm > .form-control, +.input-group-sm > .form-select, +.input-group-sm > .input-group-text { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + border-radius: var(--bs-border-radius-sm); +} +.input-group-lg > .form-select, +.input-group-sm > .form-select { + padding-right: 3rem; +} +.input-group:not(.has-validation) + > .dropdown-toggle:nth-last-child(n + 3), +.input-group:not(.has-validation) + > .form-floating:not(:last-child) + > .form-control, +.input-group:not(.has-validation) + > .form-floating:not(:last-child) + > .form-select, +.input-group:not(.has-validation) + > :not(:last-child):not(.dropdown-toggle):not( + .dropdown-menu + ):not(.form-floating) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group.has-validation + > .dropdown-toggle:nth-last-child(n + 4), +.input-group.has-validation + > .form-floating:nth-last-child(n + 3) + > .form-control, +.input-group.has-validation + > .form-floating:nth-last-child(n + 3) + > .form-select, +.input-group.has-validation + > :nth-last-child(n + 3):not(.dropdown-toggle):not( + .dropdown-menu + ):not(.form-floating) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group + > :not(:first-child):not(.dropdown-menu):not( + .valid-tooltip + ):not(.valid-feedback):not(.invalid-tooltip):not( + .invalid-feedback + ) { + margin-left: calc(var(--bs-border-width) * -1); + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.input-group > .form-floating:not(:first-child) > .form-control, +.input-group > .form-floating:not(:first-child) > .form-select { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.valid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 0.875em; + color: var(--bs-form-valid-color); +} +.valid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + max-width: 100%; + padding: 0.25rem 0.5rem; + margin-top: 0.1rem; + font-size: 0.875rem; + color: #fff; + background-color: var(--bs-success); + border-radius: var(--bs-border-radius); +} +.is-valid ~ .valid-feedback, +.is-valid ~ .valid-tooltip, +.was-validated :valid ~ .valid-feedback, +.was-validated :valid ~ .valid-tooltip { + display: block; +} +.form-control.is-valid, +.was-validated .form-control:valid { + border-color: var(--bs-form-valid-border-color); + padding-right: calc(1.5em + 0.75rem); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(0.375em + 0.1875rem) center; + background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); +} +.form-control.is-valid:focus, +.was-validated .form-control:valid:focus { + border-color: var(--bs-form-valid-border-color); + box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25); +} +.was-validated textarea.form-control:valid, +textarea.form-control.is-valid { + padding-right: calc(1.5em + 0.75rem); + background-position: top calc(0.375em + 0.1875rem) right + calc(0.375em + 0.1875rem); +} +.form-select.is-valid, +.was-validated .form-select:valid { + border-color: var(--bs-form-valid-border-color); +} +.form-select.is-valid:not([multiple]):not([size]), +.form-select.is-valid:not([multiple])[size="1"], +.was-validated .form-select:valid:not([multiple]):not([size]), +.was-validated .form-select:valid:not([multiple])[size="1"] { + --bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); + padding-right: 4.125rem; + background-position: right 0.75rem center, center right 2.25rem; + background-size: 16px 12px, + calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); +} +.form-select.is-valid:focus, +.was-validated .form-select:valid:focus { + border-color: var(--bs-form-valid-border-color); + box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25); +} +.form-control-color.is-valid, +.was-validated .form-control-color:valid { + width: calc(3rem + calc(1.5em + 0.75rem)); +} +.form-check-input.is-valid, +.was-validated .form-check-input:valid { + border-color: var(--bs-form-valid-border-color); +} +.form-check-input.is-valid:checked, +.was-validated .form-check-input:valid:checked { + background-color: var(--bs-form-valid-color); +} +.form-check-input.is-valid:focus, +.was-validated .form-check-input:valid:focus { + box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25); +} +.form-check-input.is-valid ~ .form-check-label, +.was-validated .form-check-input:valid ~ .form-check-label { + color: var(--bs-form-valid-color); +} +.form-check-inline .form-check-input ~ .valid-feedback { + margin-left: 0.5em; +} +.input-group > .form-control:not(:focus).is-valid, +.input-group > .form-floating:not(:focus-within).is-valid, +.input-group > .form-select:not(:focus).is-valid, +.was-validated .input-group > .form-control:not(:focus):valid, +.was-validated + .input-group + > .form-floating:not(:focus-within):valid, +.was-validated .input-group > .form-select:not(:focus):valid { + z-index: 3; +} +.invalid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 0.875em; + color: var(--bs-form-invalid-color); +} +.invalid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + max-width: 100%; + padding: 0.25rem 0.5rem; + margin-top: 0.1rem; + font-size: 0.875rem; + color: #fff; + background-color: var(--bs-danger); + border-radius: var(--bs-border-radius); +} +.is-invalid ~ .invalid-feedback, +.is-invalid ~ .invalid-tooltip, +.was-validated :invalid ~ .invalid-feedback, +.was-validated :invalid ~ .invalid-tooltip { + display: block; +} +.form-control.is-invalid, +.was-validated .form-control:invalid { + border-color: var(--bs-form-invalid-border-color); + padding-right: calc(1.5em + 0.75rem); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(0.375em + 0.1875rem) center; + background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); +} +.form-control.is-invalid:focus, +.was-validated .form-control:invalid:focus { + border-color: var(--bs-form-invalid-border-color); + box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25); +} +.was-validated textarea.form-control:invalid, +textarea.form-control.is-invalid { + padding-right: calc(1.5em + 0.75rem); + background-position: top calc(0.375em + 0.1875rem) right + calc(0.375em + 0.1875rem); +} +.form-select.is-invalid, +.was-validated .form-select:invalid { + border-color: var(--bs-form-invalid-border-color); +} +.form-select.is-invalid:not([multiple]):not([size]), +.form-select.is-invalid:not([multiple])[size="1"], +.was-validated .form-select:invalid:not([multiple]):not([size]), +.was-validated .form-select:invalid:not([multiple])[size="1"] { + --bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e"); + padding-right: 4.125rem; + background-position: right 0.75rem center, center right 2.25rem; + background-size: 16px 12px, + calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); +} +.form-select.is-invalid:focus, +.was-validated .form-select:invalid:focus { + border-color: var(--bs-form-invalid-border-color); + box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25); +} +.form-control-color.is-invalid, +.was-validated .form-control-color:invalid { + width: calc(3rem + calc(1.5em + 0.75rem)); +} +.form-check-input.is-invalid, +.was-validated .form-check-input:invalid { + border-color: var(--bs-form-invalid-border-color); +} +.form-check-input.is-invalid:checked, +.was-validated .form-check-input:invalid:checked { + background-color: var(--bs-form-invalid-color); +} +.form-check-input.is-invalid:focus, +.was-validated .form-check-input:invalid:focus { + box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25); +} +.form-check-input.is-invalid ~ .form-check-label, +.was-validated .form-check-input:invalid ~ .form-check-label { + color: var(--bs-form-invalid-color); +} +.form-check-inline .form-check-input ~ .invalid-feedback { + margin-left: 0.5em; +} +.input-group > .form-control:not(:focus).is-invalid, +.input-group > .form-floating:not(:focus-within).is-invalid, +.input-group > .form-select:not(:focus).is-invalid, +.was-validated .input-group > .form-control:not(:focus):invalid, +.was-validated + .input-group + > .form-floating:not(:focus-within):invalid, +.was-validated .input-group > .form-select:not(:focus):invalid { + z-index: 4; +} +.btn { + --bs-btn-padding-x: 0.75rem; + --bs-btn-padding-y: 0.375rem; + --bs-btn-font-family: ; + --bs-btn-font-size: 1rem; + --bs-btn-font-weight: 400; + --bs-btn-line-height: 1.5; + --bs-btn-color: var(--bs-body-color); + --bs-btn-bg: transparent; + --bs-btn-border-width: var(--bs-border-width); + --bs-btn-border-color: transparent; + --bs-btn-border-radius: var(--bs-border-radius); + --bs-btn-hover-border-color: transparent; + --bs-btn-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), + 0 1px 1px rgba(0, 0, 0, 0.075); + --bs-btn-disabled-opacity: 0.65; + --bs-btn-focus-box-shadow: 0 0 0 0.25rem + rgba(var(--bs-btn-focus-shadow-rgb), 0.5); + display: inline-block; + padding: var(--bs-btn-padding-y) var(--bs-btn-padding-x); + font-family: var(--bs-btn-font-family); + font-size: var(--bs-btn-font-size); + font-weight: var(--bs-btn-font-weight); + line-height: var(--bs-btn-line-height); + color: var(--bs-btn-color); + text-align: center; + text-decoration: none; + vertical-align: middle; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + border: var(--bs-btn-border-width) solid + var(--bs-btn-border-color); + border-radius: var(--bs-btn-border-radius); + background-color: var(--bs-btn-bg); + transition: color 0.15s ease-in-out, + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .btn { + transition: none; + } +} +.btn:hover { + color: var(--bs-btn-hover-color); + background-color: var(--bs-btn-hover-bg); + border-color: var(--bs-btn-hover-border-color); +} +.btn-check + .btn:hover { + color: var(--bs-btn-color); + background-color: var(--bs-btn-bg); + border-color: var(--bs-btn-border-color); +} +.btn:focus-visible { + color: var(--bs-btn-hover-color); + background-color: var(--bs-btn-hover-bg); + border-color: var(--bs-btn-hover-border-color); + outline: 0; + box-shadow: var(--bs-btn-focus-box-shadow); +} +.btn-check:focus-visible + .btn { + border-color: var(--bs-btn-hover-border-color); + outline: 0; + box-shadow: var(--bs-btn-focus-box-shadow); +} +.btn-check:checked + .btn, +.btn.active, +.btn.show, +.btn:first-child:active, +:not(.btn-check) + .btn:active { + color: var(--bs-btn-active-color); + background-color: var(--bs-btn-active-bg); + border-color: var(--bs-btn-active-border-color); +} +.btn-check:checked + .btn:focus-visible, +.btn.active:focus-visible, +.btn.show:focus-visible, +.btn:first-child:active:focus-visible, +:not(.btn-check) + .btn:active:focus-visible { + box-shadow: var(--bs-btn-focus-box-shadow); +} +.btn.disabled, +.btn:disabled, +fieldset:disabled .btn { + color: var(--bs-btn-disabled-color); + pointer-events: none; + background-color: var(--bs-btn-disabled-bg); + border-color: var(--bs-btn-disabled-border-color); + opacity: var(--bs-btn-disabled-opacity); +} +.btn-primary { + --bs-btn-color: #fff; + --bs-btn-bg: #0d6efd; + --bs-btn-border-color: #0d6efd; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #0b5ed7; + --bs-btn-hover-border-color: #0a58ca; + --bs-btn-focus-shadow-rgb: 49, 132, 253; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #0a58ca; + --bs-btn-active-border-color: #0a53be; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #0d6efd; + --bs-btn-disabled-border-color: #0d6efd; +} +.btn-secondary { + --bs-btn-color: #fff; + --bs-btn-bg: #6c757d; + --bs-btn-border-color: #6c757d; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #5c636a; + --bs-btn-hover-border-color: #565e64; + --bs-btn-focus-shadow-rgb: 130, 138, 145; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #565e64; + --bs-btn-active-border-color: #51585e; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #6c757d; + --bs-btn-disabled-border-color: #6c757d; +} +.btn-success { + --bs-btn-color: #fff; + --bs-btn-bg: #198754; + --bs-btn-border-color: #198754; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #157347; + --bs-btn-hover-border-color: #146c43; + --bs-btn-focus-shadow-rgb: 60, 153, 110; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #146c43; + --bs-btn-active-border-color: #13653f; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #198754; + --bs-btn-disabled-border-color: #198754; +} +.btn-info { + --bs-btn-color: #000; + --bs-btn-bg: #0dcaf0; + --bs-btn-border-color: #0dcaf0; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #31d2f2; + --bs-btn-hover-border-color: #25cff2; + --bs-btn-focus-shadow-rgb: 11, 172, 204; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #3dd5f3; + --bs-btn-active-border-color: #25cff2; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #000; + --bs-btn-disabled-bg: #0dcaf0; + --bs-btn-disabled-border-color: #0dcaf0; +} +.btn-warning { + --bs-btn-color: #000; + --bs-btn-bg: #ffc107; + --bs-btn-border-color: #ffc107; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #ffca2c; + --bs-btn-hover-border-color: #ffc720; + --bs-btn-focus-shadow-rgb: 217, 164, 6; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #ffcd39; + --bs-btn-active-border-color: #ffc720; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #000; + --bs-btn-disabled-bg: #ffc107; + --bs-btn-disabled-border-color: #ffc107; +} +.btn-danger { + --bs-btn-color: #fff; + --bs-btn-bg: #dc3545; + --bs-btn-border-color: #dc3545; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #bb2d3b; + --bs-btn-hover-border-color: #b02a37; + --bs-btn-focus-shadow-rgb: 225, 83, 97; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #b02a37; + --bs-btn-active-border-color: #a52834; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #dc3545; + --bs-btn-disabled-border-color: #dc3545; +} +.btn-light { + --bs-btn-color: #000; + --bs-btn-bg: #f8f9fa; + --bs-btn-border-color: #f8f9fa; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #d3d4d5; + --bs-btn-hover-border-color: #c6c7c8; + --bs-btn-focus-shadow-rgb: 211, 212, 213; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #c6c7c8; + --bs-btn-active-border-color: #babbbc; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #000; + --bs-btn-disabled-bg: #f8f9fa; + --bs-btn-disabled-border-color: #f8f9fa; +} +.btn-dark { + --bs-btn-color: #fff; + --bs-btn-bg: #212529; + --bs-btn-border-color: #212529; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #424649; + --bs-btn-hover-border-color: #373b3e; + --bs-btn-focus-shadow-rgb: 66, 70, 73; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #4d5154; + --bs-btn-active-border-color: #373b3e; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #212529; + --bs-btn-disabled-border-color: #212529; +} +.btn-outline-primary { + --bs-btn-color: #0d6efd; + --bs-btn-border-color: #0d6efd; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #0d6efd; + --bs-btn-hover-border-color: #0d6efd; + --bs-btn-focus-shadow-rgb: 13, 110, 253; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #0d6efd; + --bs-btn-active-border-color: #0d6efd; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #0d6efd; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #0d6efd; + --bs-gradient: none; +} +.btn-outline-secondary { + --bs-btn-color: #6c757d; + --bs-btn-border-color: #6c757d; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #6c757d; + --bs-btn-hover-border-color: #6c757d; + --bs-btn-focus-shadow-rgb: 108, 117, 125; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #6c757d; + --bs-btn-active-border-color: #6c757d; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #6c757d; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #6c757d; + --bs-gradient: none; +} +.btn-outline-success { + --bs-btn-color: #198754; + --bs-btn-border-color: #198754; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #198754; + --bs-btn-hover-border-color: #198754; + --bs-btn-focus-shadow-rgb: 25, 135, 84; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #198754; + --bs-btn-active-border-color: #198754; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #198754; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #198754; + --bs-gradient: none; +} +.btn-outline-info { + --bs-btn-color: #0dcaf0; + --bs-btn-border-color: #0dcaf0; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #0dcaf0; + --bs-btn-hover-border-color: #0dcaf0; + --bs-btn-focus-shadow-rgb: 13, 202, 240; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #0dcaf0; + --bs-btn-active-border-color: #0dcaf0; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #0dcaf0; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #0dcaf0; + --bs-gradient: none; +} +.btn-outline-warning { + --bs-btn-color: #ffc107; + --bs-btn-border-color: #ffc107; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #ffc107; + --bs-btn-hover-border-color: #ffc107; + --bs-btn-focus-shadow-rgb: 255, 193, 7; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #ffc107; + --bs-btn-active-border-color: #ffc107; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #ffc107; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #ffc107; + --bs-gradient: none; +} +.btn-outline-danger { + --bs-btn-color: #dc3545; + --bs-btn-border-color: #dc3545; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #dc3545; + --bs-btn-hover-border-color: #dc3545; + --bs-btn-focus-shadow-rgb: 220, 53, 69; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #dc3545; + --bs-btn-active-border-color: #dc3545; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #dc3545; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #dc3545; + --bs-gradient: none; +} +.btn-outline-light { + --bs-btn-color: #f8f9fa; + --bs-btn-border-color: #f8f9fa; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #f8f9fa; + --bs-btn-hover-border-color: #f8f9fa; + --bs-btn-focus-shadow-rgb: 248, 249, 250; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #f8f9fa; + --bs-btn-active-border-color: #f8f9fa; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #f8f9fa; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #f8f9fa; + --bs-gradient: none; +} +.btn-outline-dark { + --bs-btn-color: #212529; + --bs-btn-border-color: #212529; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #212529; + --bs-btn-hover-border-color: #212529; + --bs-btn-focus-shadow-rgb: 33, 37, 41; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #212529; + --bs-btn-active-border-color: #212529; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #212529; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #212529; + --bs-gradient: none; +} +.btn-link { + --bs-btn-font-weight: 400; + --bs-btn-color: var(--bs-link-color); + --bs-btn-bg: transparent; + --bs-btn-border-color: transparent; + --bs-btn-hover-color: var(--bs-link-hover-color); + --bs-btn-hover-border-color: transparent; + --bs-btn-active-color: var(--bs-link-hover-color); + --bs-btn-active-border-color: transparent; + --bs-btn-disabled-color: #6c757d; + --bs-btn-disabled-border-color: transparent; + --bs-btn-box-shadow: 0 0 0 #000; + --bs-btn-focus-shadow-rgb: 49, 132, 253; + text-decoration: underline; +} +.btn-link:focus-visible { + color: var(--bs-btn-color); +} +.btn-link:hover { + color: var(--bs-btn-hover-color); +} +.btn-group-lg > .btn, +.btn-lg { + --bs-btn-padding-y: 0.5rem; + --bs-btn-padding-x: 1rem; + --bs-btn-font-size: 1.25rem; + --bs-btn-border-radius: var(--bs-border-radius-lg); +} +.btn-group-sm > .btn, +.btn-sm { + --bs-btn-padding-y: 0.25rem; + --bs-btn-padding-x: 0.5rem; + --bs-btn-font-size: 0.875rem; + --bs-btn-border-radius: var(--bs-border-radius-sm); +} +.fade { + transition: opacity 0.15s linear; +} +@media (prefers-reduced-motion: reduce) { + .fade { + transition: none; + } +} +.fade:not(.show) { + opacity: 0; +} +.collapse:not(.show) { + display: none; +} +.collapsing { + height: 0; + overflow: hidden; + transition: height 0.35s ease; +} +@media (prefers-reduced-motion: reduce) { + .collapsing { + transition: none; + } +} +.collapsing.collapse-horizontal { + width: 0; + height: auto; + transition: width 0.35s ease; +} +@media (prefers-reduced-motion: reduce) { + .collapsing.collapse-horizontal { + transition: none; + } +} +.dropdown, +.dropdown-center, +.dropend, +.dropstart, +.dropup, +.dropup-center { + position: relative; +} +.dropdown-toggle { + white-space: nowrap; +} +.dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid; + border-right: 0.3em solid transparent; + border-bottom: 0; + border-left: 0.3em solid transparent; +} +.dropdown-toggle:empty::after { + margin-left: 0; +} +.dropdown-menu { + --bs-dropdown-zindex: 1000; + --bs-dropdown-min-width: 10rem; + --bs-dropdown-padding-x: 0; + --bs-dropdown-padding-y: 0.5rem; + --bs-dropdown-spacer: 0.125rem; + --bs-dropdown-font-size: 1rem; + --bs-dropdown-color: var(--bs-body-color); + --bs-dropdown-bg: var(--bs-body-bg); + --bs-dropdown-border-color: var(--bs-border-color-translucent); + --bs-dropdown-border-radius: var(--bs-border-radius); + --bs-dropdown-border-width: var(--bs-border-width); + --bs-dropdown-inner-border-radius: calc( + var(--bs-border-radius) - var(--bs-border-width) + ); + --bs-dropdown-divider-bg: var(--bs-border-color-translucent); + --bs-dropdown-divider-margin-y: 0.5rem; + --bs-dropdown-box-shadow: var(--bs-box-shadow); + --bs-dropdown-link-color: var(--bs-body-color); + --bs-dropdown-link-hover-color: var(--bs-body-color); + --bs-dropdown-link-hover-bg: var(--bs-tertiary-bg); + --bs-dropdown-link-active-color: #fff; + --bs-dropdown-link-active-bg: #0d6efd; + --bs-dropdown-link-disabled-color: var(--bs-tertiary-color); + --bs-dropdown-item-padding-x: 1rem; + --bs-dropdown-item-padding-y: 0.25rem; + --bs-dropdown-header-color: #6c757d; + --bs-dropdown-header-padding-x: 1rem; + --bs-dropdown-header-padding-y: 0.5rem; + position: absolute; + z-index: var(--bs-dropdown-zindex); + display: none; + min-width: var(--bs-dropdown-min-width); + padding: var(--bs-dropdown-padding-y) + var(--bs-dropdown-padding-x); + margin: 0; + font-size: var(--bs-dropdown-font-size); + color: var(--bs-dropdown-color); + text-align: left; + list-style: none; + background-color: var(--bs-dropdown-bg); + background-clip: padding-box; + border: var(--bs-dropdown-border-width) solid + var(--bs-dropdown-border-color); + border-radius: var(--bs-dropdown-border-radius); +} +.dropdown-menu[data-bs-popper] { + top: 100%; + left: 0; + margin-top: var(--bs-dropdown-spacer); +} +.dropdown-menu-start { + --bs-position: start; +} +.dropdown-menu-start[data-bs-popper] { + right: auto; + left: 0; +} +.dropdown-menu-end { + --bs-position: end; +} +.dropdown-menu-end[data-bs-popper] { + right: 0; + left: auto; +} +@media (min-width: 576px) { + .dropdown-menu-sm-start { + --bs-position: start; + } + .dropdown-menu-sm-start[data-bs-popper] { + right: auto; + left: 0; + } + .dropdown-menu-sm-end { + --bs-position: end; + } + .dropdown-menu-sm-end[data-bs-popper] { + right: 0; + left: auto; + } +} +@media (min-width: 768px) { + .dropdown-menu-md-start { + --bs-position: start; + } + .dropdown-menu-md-start[data-bs-popper] { + right: auto; + left: 0; + } + .dropdown-menu-md-end { + --bs-position: end; + } + .dropdown-menu-md-end[data-bs-popper] { + right: 0; + left: auto; + } +} +@media (min-width: 992px) { + .dropdown-menu-lg-start { + --bs-position: start; + } + .dropdown-menu-lg-start[data-bs-popper] { + right: auto; + left: 0; + } + .dropdown-menu-lg-end { + --bs-position: end; + } + .dropdown-menu-lg-end[data-bs-popper] { + right: 0; + left: auto; + } +} +@media (min-width: 1200px) { + .dropdown-menu-xl-start { + --bs-position: start; + } + .dropdown-menu-xl-start[data-bs-popper] { + right: auto; + left: 0; + } + .dropdown-menu-xl-end { + --bs-position: end; + } + .dropdown-menu-xl-end[data-bs-popper] { + right: 0; + left: auto; + } +} +@media (min-width: 1400px) { + .dropdown-menu-xxl-start { + --bs-position: start; + } + .dropdown-menu-xxl-start[data-bs-popper] { + right: auto; + left: 0; + } + .dropdown-menu-xxl-end { + --bs-position: end; + } + .dropdown-menu-xxl-end[data-bs-popper] { + right: 0; + left: auto; + } +} +.dropup .dropdown-menu[data-bs-popper] { + top: auto; + bottom: 100%; + margin-top: 0; + margin-bottom: var(--bs-dropdown-spacer); +} +.dropup .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0; + border-right: 0.3em solid transparent; + border-bottom: 0.3em solid; + border-left: 0.3em solid transparent; +} +.dropup .dropdown-toggle:empty::after { + margin-left: 0; +} +.dropend .dropdown-menu[data-bs-popper] { + top: 0; + right: auto; + left: 100%; + margin-top: 0; + margin-left: var(--bs-dropdown-spacer); +} +.dropend .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid transparent; + border-right: 0; + border-bottom: 0.3em solid transparent; + border-left: 0.3em solid; +} +.dropend .dropdown-toggle:empty::after { + margin-left: 0; +} +.dropend .dropdown-toggle::after { + vertical-align: 0; +} +.dropstart .dropdown-menu[data-bs-popper] { + top: 0; + right: 100%; + left: auto; + margin-top: 0; + margin-right: var(--bs-dropdown-spacer); +} +.dropstart .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; +} +.dropstart .dropdown-toggle::after { + display: none; +} +.dropstart .dropdown-toggle::before { + display: inline-block; + margin-right: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid transparent; + border-right: 0.3em solid; + border-bottom: 0.3em solid transparent; +} +.dropstart .dropdown-toggle:empty::after { + margin-left: 0; +} +.dropstart .dropdown-toggle::before { + vertical-align: 0; +} +.dropdown-divider { + height: 0; + margin: var(--bs-dropdown-divider-margin-y) 0; + overflow: hidden; + border-top: 1px solid var(--bs-dropdown-divider-bg); + opacity: 1; +} +.dropdown-item { + display: block; + width: 100%; + padding: var(--bs-dropdown-item-padding-y) + var(--bs-dropdown-item-padding-x); + clear: both; + font-weight: 400; + color: var(--bs-dropdown-link-color); + text-align: inherit; + text-decoration: none; + white-space: nowrap; + background-color: transparent; + border: 0; + border-radius: var(--bs-dropdown-item-border-radius, 0); +} +.dropdown-item:focus, +.dropdown-item:hover { + color: var(--bs-dropdown-link-hover-color); + background-color: var(--bs-dropdown-link-hover-bg); +} +.dropdown-item.active, +.dropdown-item:active { + color: var(--bs-dropdown-link-active-color); + text-decoration: none; + background-color: var(--bs-dropdown-link-active-bg); +} +.dropdown-item.disabled, +.dropdown-item:disabled { + color: var(--bs-dropdown-link-disabled-color); + pointer-events: none; + background-color: transparent; +} +.dropdown-menu.show { + display: block; +} +.dropdown-header { + display: block; + padding: var(--bs-dropdown-header-padding-y) + var(--bs-dropdown-header-padding-x); + margin-bottom: 0; + font-size: 0.875rem; + color: var(--bs-dropdown-header-color); + white-space: nowrap; +} +.dropdown-item-text { + display: block; + padding: var(--bs-dropdown-item-padding-y) + var(--bs-dropdown-item-padding-x); + color: var(--bs-dropdown-link-color); +} +.dropdown-menu-dark { + --bs-dropdown-color: #dee2e6; + --bs-dropdown-bg: #343a40; + --bs-dropdown-border-color: var(--bs-border-color-translucent); + --bs-dropdown-box-shadow: ; + --bs-dropdown-link-color: #dee2e6; + --bs-dropdown-link-hover-color: #fff; + --bs-dropdown-divider-bg: var(--bs-border-color-translucent); + --bs-dropdown-link-hover-bg: rgba(255, 255, 255, 0.15); + --bs-dropdown-link-active-color: #fff; + --bs-dropdown-link-active-bg: #0d6efd; + --bs-dropdown-link-disabled-color: #adb5bd; + --bs-dropdown-header-color: #adb5bd; +} +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-flex; + vertical-align: middle; +} +.btn-group-vertical > .btn, +.btn-group > .btn { + position: relative; + flex: 1 1 auto; +} +.btn-group-vertical > .btn-check:checked + .btn, +.btn-group-vertical > .btn-check:focus + .btn, +.btn-group-vertical > .btn.active, +.btn-group-vertical > .btn:active, +.btn-group-vertical > .btn:focus, +.btn-group-vertical > .btn:hover, +.btn-group > .btn-check:checked + .btn, +.btn-group > .btn-check:focus + .btn, +.btn-group > .btn.active, +.btn-group > .btn:active, +.btn-group > .btn:focus, +.btn-group > .btn:hover { + z-index: 1; +} +.btn-toolbar { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; +} +.btn-toolbar .input-group { + width: auto; +} +.btn-group { + border-radius: var(--bs-border-radius); +} +.btn-group > .btn-group:not(:first-child), +.btn-group > :not(.btn-check:first-child) + .btn { + margin-left: calc(var(--bs-border-width) * -1); +} +.btn-group > .btn-group:not(:last-child) > .btn, +.btn-group > .btn.dropdown-toggle-split:first-child, +.btn-group > .btn:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn-group:not(:first-child) > .btn, +.btn-group > .btn:nth-child(n + 3), +.btn-group > :not(.btn-check) + .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.dropdown-toggle-split { + padding-right: 0.5625rem; + padding-left: 0.5625rem; +} +.dropdown-toggle-split::after, +.dropend .dropdown-toggle-split::after, +.dropup .dropdown-toggle-split::after { + margin-left: 0; +} +.dropstart .dropdown-toggle-split::before { + margin-right: 0; +} +.btn-group-sm > .btn + .dropdown-toggle-split, +.btn-sm + .dropdown-toggle-split { + padding-right: 0.375rem; + padding-left: 0.375rem; +} +.btn-group-lg > .btn + .dropdown-toggle-split, +.btn-lg + .dropdown-toggle-split { + padding-right: 0.75rem; + padding-left: 0.75rem; +} +.btn-group-vertical { + flex-direction: column; + align-items: flex-start; + justify-content: center; +} +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group { + width: 100%; +} +.btn-group-vertical > .btn-group:not(:first-child), +.btn-group-vertical > .btn:not(:first-child) { + margin-top: calc(var(--bs-border-width) * -1); +} +.btn-group-vertical > .btn-group:not(:last-child) > .btn, +.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle) { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn-group:not(:first-child) > .btn, +.btn-group-vertical > .btn ~ .btn { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.nav { + --bs-nav-link-padding-x: 1rem; + --bs-nav-link-padding-y: 0.5rem; + --bs-nav-link-font-weight: ; + --bs-nav-link-color: var(--bs-link-color); + --bs-nav-link-hover-color: var(--bs-link-hover-color); + --bs-nav-link-disabled-color: var(--bs-secondary-color); + display: flex; + flex-wrap: wrap; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} +.nav-link { + display: block; + padding: var(--bs-nav-link-padding-y) + var(--bs-nav-link-padding-x); + font-size: var(--bs-nav-link-font-size); + font-weight: var(--bs-nav-link-font-weight); + color: var(--bs-nav-link-color); + text-decoration: none; + background: 0 0; + border: 0; + transition: color 0.15s ease-in-out, + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .nav-link { + transition: none; + } +} +.nav-link:focus, +.nav-link:hover { + color: var(--bs-nav-link-hover-color); +} +.nav-link:focus-visible { + outline: 0; + box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); +} +.nav-link.disabled, +.nav-link:disabled { + color: var(--bs-nav-link-disabled-color); + pointer-events: none; + cursor: default; +} +.nav-tabs { + --bs-nav-tabs-border-width: var(--bs-border-width); + --bs-nav-tabs-border-color: var(--bs-border-color); + --bs-nav-tabs-border-radius: var(--bs-border-radius); + --bs-nav-tabs-link-hover-border-color: var(--bs-secondary-bg) + var(--bs-secondary-bg) var(--bs-border-color); + --bs-nav-tabs-link-active-color: var(--bs-emphasis-color); + --bs-nav-tabs-link-active-bg: var(--bs-body-bg); + --bs-nav-tabs-link-active-border-color: var(--bs-border-color) + var(--bs-border-color) var(--bs-body-bg); + border-bottom: var(--bs-nav-tabs-border-width) solid + var(--bs-nav-tabs-border-color); +} +.nav-tabs .nav-link { + margin-bottom: calc(-1 * var(--bs-nav-tabs-border-width)); + border: var(--bs-nav-tabs-border-width) solid transparent; + border-top-left-radius: var(--bs-nav-tabs-border-radius); + border-top-right-radius: var(--bs-nav-tabs-border-radius); +} +.nav-tabs .nav-link:focus, +.nav-tabs .nav-link:hover { + isolation: isolate; + border-color: var(--bs-nav-tabs-link-hover-border-color); +} +.nav-tabs .nav-item.show .nav-link, +.nav-tabs .nav-link.active { + color: var(--bs-nav-tabs-link-active-color); + background-color: var(--bs-nav-tabs-link-active-bg); + border-color: var(--bs-nav-tabs-link-active-border-color); +} +.nav-tabs .dropdown-menu { + margin-top: calc(-1 * var(--bs-nav-tabs-border-width)); + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.nav-pills { + --bs-nav-pills-border-radius: var(--bs-border-radius); + --bs-nav-pills-link-active-color: #fff; + --bs-nav-pills-link-active-bg: #0d6efd; +} +.nav-pills .nav-link { + border-radius: var(--bs-nav-pills-border-radius); +} +.nav-pills .nav-link.active, +.nav-pills .show > .nav-link { + color: var(--bs-nav-pills-link-active-color); + background-color: var(--bs-nav-pills-link-active-bg); +} +.nav-underline { + --bs-nav-underline-gap: 1rem; + --bs-nav-underline-border-width: 0.125rem; + --bs-nav-underline-link-active-color: var(--bs-emphasis-color); + gap: var(--bs-nav-underline-gap); +} +.nav-underline .nav-link { + padding-right: 0; + padding-left: 0; + border-bottom: var(--bs-nav-underline-border-width) solid + transparent; +} +.nav-underline .nav-link:focus, +.nav-underline .nav-link:hover { + border-bottom-color: currentcolor; +} +.nav-underline .nav-link.active, +.nav-underline .show > .nav-link { + font-weight: 700; + color: var(--bs-nav-underline-link-active-color); + border-bottom-color: currentcolor; +} +.nav-fill .nav-item, +.nav-fill > .nav-link { + flex: 1 1 auto; + text-align: center; +} +.nav-justified .nav-item, +.nav-justified > .nav-link { + flex-basis: 0; + flex-grow: 1; + text-align: center; +} +.nav-fill .nav-item .nav-link, +.nav-justified .nav-item .nav-link { + width: 100%; +} +.tab-content > .tab-pane { + display: none; +} +.tab-content > .active { + display: block; +} +.navbar { + --bs-navbar-padding-x: 0; + --bs-navbar-padding-y: 0.5rem; + --bs-navbar-color: rgba(var(--bs-emphasis-color-rgb), 0.65); + --bs-navbar-hover-color: rgba( + var(--bs-emphasis-color-rgb), + 0.8 + ); + --bs-navbar-disabled-color: rgba( + var(--bs-emphasis-color-rgb), + 0.3 + ); + --bs-navbar-active-color: rgba(var(--bs-emphasis-color-rgb), 1); + --bs-navbar-brand-padding-y: 0.3125rem; + --bs-navbar-brand-margin-end: 1rem; + --bs-navbar-brand-font-size: 1.25rem; + --bs-navbar-brand-color: rgba(var(--bs-emphasis-color-rgb), 1); + --bs-navbar-brand-hover-color: rgba( + var(--bs-emphasis-color-rgb), + 1 + ); + --bs-navbar-nav-link-padding-x: 0.5rem; + --bs-navbar-toggler-padding-y: 0.25rem; + --bs-navbar-toggler-padding-x: 0.75rem; + --bs-navbar-toggler-font-size: 1.25rem; + --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); + --bs-navbar-toggler-border-color: rgba( + var(--bs-emphasis-color-rgb), + 0.15 + ); + --bs-navbar-toggler-border-radius: var(--bs-border-radius); + --bs-navbar-toggler-focus-width: 0.25rem; + --bs-navbar-toggler-transition: box-shadow 0.15s ease-in-out; + position: relative; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + padding: var(--bs-navbar-padding-y) var(--bs-navbar-padding-x); +} +.navbar > .container, +.navbar > .container-fluid, +.navbar > .container-lg, +.navbar > .container-md, +.navbar > .container-sm, +.navbar > .container-xl, +.navbar > .container-xxl { + display: flex; + flex-wrap: inherit; + align-items: center; + justify-content: space-between; +} +.navbar-brand { + padding-top: var(--bs-navbar-brand-padding-y); + padding-bottom: var(--bs-navbar-brand-padding-y); + margin-right: var(--bs-navbar-brand-margin-end); + font-size: var(--bs-navbar-brand-font-size); + color: var(--bs-navbar-brand-color); + text-decoration: none; + white-space: nowrap; +} +.navbar-brand:focus, +.navbar-brand:hover { + color: var(--bs-navbar-brand-hover-color); +} +.navbar-nav { + --bs-nav-link-padding-x: 0; + --bs-nav-link-padding-y: 0.5rem; + --bs-nav-link-font-weight: ; + --bs-nav-link-color: var(--bs-navbar-color); + --bs-nav-link-hover-color: var(--bs-navbar-hover-color); + --bs-nav-link-disabled-color: var(--bs-navbar-disabled-color); + display: flex; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} +.navbar-nav .nav-link.active, +.navbar-nav .nav-link.show { + color: var(--bs-navbar-active-color); +} +.navbar-nav .dropdown-menu { + position: static; +} +.navbar-text { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + color: var(--bs-navbar-color); +} +.navbar-text a, +.navbar-text a:focus, +.navbar-text a:hover { + color: var(--bs-navbar-active-color); +} +.navbar-collapse { + flex-basis: 100%; + flex-grow: 1; + align-items: center; +} +.navbar-toggler { + padding: var(--bs-navbar-toggler-padding-y) + var(--bs-navbar-toggler-padding-x); + font-size: var(--bs-navbar-toggler-font-size); + line-height: 1; + color: var(--bs-navbar-color); + background-color: transparent; + border: var(--bs-border-width) solid + var(--bs-navbar-toggler-border-color); + border-radius: var(--bs-navbar-toggler-border-radius); + transition: var(--bs-navbar-toggler-transition); +} +@media (prefers-reduced-motion: reduce) { + .navbar-toggler { + transition: none; + } +} +.navbar-toggler:hover { + text-decoration: none; +} +.navbar-toggler:focus { + text-decoration: none; + outline: 0; + box-shadow: 0 0 0 var(--bs-navbar-toggler-focus-width); +} +.navbar-toggler-icon { + display: inline-block; + width: 1.5em; + height: 1.5em; + vertical-align: middle; + background-image: var(--bs-navbar-toggler-icon-bg); + background-repeat: no-repeat; + background-position: center; + background-size: 100%; +} +.navbar-nav-scroll { + max-height: var(--bs-scroll-height, 75vh); + overflow-y: auto; +} +@media (min-width: 576px) { + .navbar-expand-sm { + flex-wrap: nowrap; + justify-content: flex-start; + } + .navbar-expand-sm .navbar-nav { + flex-direction: row; + } + .navbar-expand-sm .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-sm .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); + } + .navbar-expand-sm .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-sm .navbar-collapse { + display: flex !important; + flex-basis: auto; + } + .navbar-expand-sm .navbar-toggler { + display: none; + } + .navbar-expand-sm .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + transition: none; + } + .navbar-expand-sm .offcanvas .offcanvas-header { + display: none; + } + .navbar-expand-sm .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } +} +@media (min-width: 768px) { + .navbar-expand-md { + flex-wrap: nowrap; + justify-content: flex-start; + } + .navbar-expand-md .navbar-nav { + flex-direction: row; + } + .navbar-expand-md .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-md .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); + } + .navbar-expand-md .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-md .navbar-collapse { + display: flex !important; + flex-basis: auto; + } + .navbar-expand-md .navbar-toggler { + display: none; + } + .navbar-expand-md .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + transition: none; + } + .navbar-expand-md .offcanvas .offcanvas-header { + display: none; + } + .navbar-expand-md .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } +} +@media (min-width: 992px) { + .navbar-expand-lg { + flex-wrap: nowrap; + justify-content: flex-start; + } + .navbar-expand-lg .navbar-nav { + flex-direction: row; + } + .navbar-expand-lg .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-lg .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); + } + .navbar-expand-lg .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-lg .navbar-collapse { + display: flex !important; + flex-basis: auto; + } + .navbar-expand-lg .navbar-toggler { + display: none; + } + .navbar-expand-lg .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + transition: none; + } + .navbar-expand-lg .offcanvas .offcanvas-header { + display: none; + } + .navbar-expand-lg .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } +} +@media (min-width: 1200px) { + .navbar-expand-xl { + flex-wrap: nowrap; + justify-content: flex-start; + } + .navbar-expand-xl .navbar-nav { + flex-direction: row; + } + .navbar-expand-xl .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-xl .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); + } + .navbar-expand-xl .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-xl .navbar-collapse { + display: flex !important; + flex-basis: auto; + } + .navbar-expand-xl .navbar-toggler { + display: none; + } + .navbar-expand-xl .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + transition: none; + } + .navbar-expand-xl .offcanvas .offcanvas-header { + display: none; + } + .navbar-expand-xl .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } +} +@media (min-width: 1400px) { + .navbar-expand-xxl { + flex-wrap: nowrap; + justify-content: flex-start; + } + .navbar-expand-xxl .navbar-nav { + flex-direction: row; + } + .navbar-expand-xxl .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-xxl .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); + } + .navbar-expand-xxl .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-xxl .navbar-collapse { + display: flex !important; + flex-basis: auto; + } + .navbar-expand-xxl .navbar-toggler { + display: none; + } + .navbar-expand-xxl .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + transition: none; + } + .navbar-expand-xxl .offcanvas .offcanvas-header { + display: none; + } + .navbar-expand-xxl .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } +} +.navbar-expand { + flex-wrap: nowrap; + justify-content: flex-start; +} +.navbar-expand .navbar-nav { + flex-direction: row; +} +.navbar-expand .navbar-nav .dropdown-menu { + position: absolute; +} +.navbar-expand .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); +} +.navbar-expand .navbar-nav-scroll { + overflow: visible; +} +.navbar-expand .navbar-collapse { + display: flex !important; + flex-basis: auto; +} +.navbar-expand .navbar-toggler { + display: none; +} +.navbar-expand .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + transition: none; +} +.navbar-expand .offcanvas .offcanvas-header { + display: none; +} +.navbar-expand .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; +} +.navbar-dark, +.navbar[data-bs-theme="dark"] { + --bs-navbar-color: rgba(255, 255, 255, 0.55); + --bs-navbar-hover-color: rgba(255, 255, 255, 0.75); + --bs-navbar-disabled-color: rgba(255, 255, 255, 0.25); + --bs-navbar-active-color: #fff; + --bs-navbar-brand-color: #fff; + --bs-navbar-brand-hover-color: #fff; + --bs-navbar-toggler-border-color: rgba(255, 255, 255, 0.1); + --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); +} +[data-bs-theme="dark"] .navbar-toggler-icon { + --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); +} +.card { + --bs-card-spacer-y: 1rem; + --bs-card-spacer-x: 1rem; + --bs-card-title-spacer-y: 0.5rem; + --bs-card-title-color: ; + --bs-card-subtitle-color: ; + --bs-card-border-width: var(--bs-border-width); + --bs-card-border-color: var(--bs-border-color-translucent); + --bs-card-border-radius: var(--bs-border-radius); + --bs-card-box-shadow: ; + --bs-card-inner-border-radius: calc( + var(--bs-border-radius) - (var(--bs-border-width)) + ); + --bs-card-cap-padding-y: 0.5rem; + --bs-card-cap-padding-x: 1rem; + --bs-card-cap-bg: rgba(var(--bs-body-color-rgb), 0.03); + --bs-card-cap-color: ; + --bs-card-height: ; + --bs-card-color: ; + --bs-card-bg: var(--bs-body-bg); + --bs-card-img-overlay-padding: 1rem; + --bs-card-group-margin: 0.75rem; + position: relative; + display: flex; + flex-direction: column; + min-width: 0; + height: var(--bs-card-height); + color: var(--bs-body-color); + word-wrap: break-word; + background-color: var(--bs-card-bg); + background-clip: border-box; + border: var(--bs-card-border-width) solid + var(--bs-card-border-color); + border-radius: var(--bs-card-border-radius); +} +.card > hr { + margin-right: 0; + margin-left: 0; +} +.card > .list-group { + border-top: inherit; + border-bottom: inherit; +} +.card > .list-group:first-child { + border-top-width: 0; + border-top-left-radius: var(--bs-card-inner-border-radius); + border-top-right-radius: var(--bs-card-inner-border-radius); +} +.card > .list-group:last-child { + border-bottom-width: 0; + border-bottom-right-radius: var(--bs-card-inner-border-radius); + border-bottom-left-radius: var(--bs-card-inner-border-radius); +} +.card > .card-header + .list-group, +.card > .list-group + .card-footer { + border-top: 0; +} +.card-body { + flex: 1 1 auto; + padding: var(--bs-card-spacer-y) var(--bs-card-spacer-x); + color: var(--bs-card-color); +} +.card-title { + margin-bottom: var(--bs-card-title-spacer-y); + color: var(--bs-card-title-color); +} +.card-subtitle { + margin-top: calc(-0.5 * var(--bs-card-title-spacer-y)); + margin-bottom: 0; + color: var(--bs-card-subtitle-color); +} +.card-text:last-child { + margin-bottom: 0; +} +.card-link + .card-link { + margin-left: var(--bs-card-spacer-x); +} +.card-header { + padding: var(--bs-card-cap-padding-y) + var(--bs-card-cap-padding-x); + margin-bottom: 0; + color: var(--bs-card-cap-color); + background-color: var(--bs-card-cap-bg); + border-bottom: var(--bs-card-border-width) solid + var(--bs-card-border-color); +} +.card-header:first-child { + border-radius: var(--bs-card-inner-border-radius) + var(--bs-card-inner-border-radius) 0 0; +} +.card-footer { + padding: var(--bs-card-cap-padding-y) + var(--bs-card-cap-padding-x); + color: var(--bs-card-cap-color); + background-color: var(--bs-card-cap-bg); + border-top: var(--bs-card-border-width) solid + var(--bs-card-border-color); +} +.card-footer:last-child { + border-radius: 0 0 var(--bs-card-inner-border-radius) + var(--bs-card-inner-border-radius); +} +.card-header-tabs { + margin-right: calc(-0.5 * var(--bs-card-cap-padding-x)); + margin-bottom: calc(-1 * var(--bs-card-cap-padding-y)); + margin-left: calc(-0.5 * var(--bs-card-cap-padding-x)); + border-bottom: 0; +} +.card-header-tabs .nav-link.active { + background-color: var(--bs-card-bg); + border-bottom-color: var(--bs-card-bg); +} +.card-header-pills { + margin-right: calc(-0.5 * var(--bs-card-cap-padding-x)); + margin-left: calc(-0.5 * var(--bs-card-cap-padding-x)); +} +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: var(--bs-card-img-overlay-padding); + border-radius: var(--bs-card-inner-border-radius); +} +.card-img, +.card-img-bottom, +.card-img-top { + width: 100%; +} +.card-img, +.card-img-top { + border-top-left-radius: var(--bs-card-inner-border-radius); + border-top-right-radius: var(--bs-card-inner-border-radius); +} +.card-img, +.card-img-bottom { + border-bottom-right-radius: var(--bs-card-inner-border-radius); + border-bottom-left-radius: var(--bs-card-inner-border-radius); +} +.card-group > .card { + margin-bottom: var(--bs-card-group-margin); +} +@media (min-width: 576px) { + .card-group { + display: flex; + flex-flow: row wrap; + } + .card-group > .card { + flex: 1 0 0%; + margin-bottom: 0; + } + .card-group > .card + .card { + margin-left: 0; + border-left: 0; + } + .card-group > .card:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + .card-group > .card:not(:last-child) .card-header, + .card-group > .card:not(:last-child) .card-img-top { + border-top-right-radius: 0; + } + .card-group > .card:not(:last-child) .card-footer, + .card-group > .card:not(:last-child) .card-img-bottom { + border-bottom-right-radius: 0; + } + .card-group > .card:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + .card-group > .card:not(:first-child) .card-header, + .card-group > .card:not(:first-child) .card-img-top { + border-top-left-radius: 0; + } + .card-group > .card:not(:first-child) .card-footer, + .card-group > .card:not(:first-child) .card-img-bottom { + border-bottom-left-radius: 0; + } +} +.accordion { + --bs-accordion-color: var(--bs-body-color); + --bs-accordion-bg: var(--bs-body-bg); + --bs-accordion-transition: color 0.15s ease-in-out, + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, + border-radius 0.15s ease; + --bs-accordion-border-color: var(--bs-border-color); + --bs-accordion-border-width: var(--bs-border-width); + --bs-accordion-border-radius: var(--bs-border-radius); + --bs-accordion-inner-border-radius: calc( + var(--bs-border-radius) - (var(--bs-border-width)) + ); + --bs-accordion-btn-padding-x: 1.25rem; + --bs-accordion-btn-padding-y: 1rem; + --bs-accordion-btn-color: var(--bs-body-color); + --bs-accordion-btn-bg: var(--bs-accordion-bg); + --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); + --bs-accordion-btn-icon-width: 1.25rem; + --bs-accordion-btn-icon-transform: rotate(-180deg); + --bs-accordion-btn-icon-transition: transform 0.2s ease-in-out; + --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23052c65'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); + --bs-accordion-btn-focus-border-color: #86b7fe; + --bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem + rgba(13, 110, 253, 0.25); + --bs-accordion-body-padding-x: 1.25rem; + --bs-accordion-body-padding-y: 1rem; + --bs-accordion-active-color: var(--bs-primary-text-emphasis); + --bs-accordion-active-bg: var(--bs-primary-bg-subtle); +} +.accordion-button { + position: relative; + display: flex; + align-items: center; + width: 100%; + padding: var(--bs-accordion-btn-padding-y) + var(--bs-accordion-btn-padding-x); + font-size: 1rem; + color: var(--bs-accordion-btn-color); + text-align: left; + background-color: var(--bs-accordion-btn-bg); + border: 0; + border-radius: 0; + overflow-anchor: none; + transition: var(--bs-accordion-transition); +} +@media (prefers-reduced-motion: reduce) { + .accordion-button { + transition: none; + } +} +.accordion-button:not(.collapsed) { + color: var(--bs-accordion-active-color); + background-color: var(--bs-accordion-active-bg); + box-shadow: inset 0 calc(-1 * var(--bs-accordion-border-width)) + 0 var(--bs-accordion-border-color); +} +.accordion-button:not(.collapsed)::after { + background-image: var(--bs-accordion-btn-active-icon); + transform: var(--bs-accordion-btn-icon-transform); +} +.accordion-button::after { + flex-shrink: 0; + width: var(--bs-accordion-btn-icon-width); + height: var(--bs-accordion-btn-icon-width); + margin-left: auto; + content: ""; + background-image: var(--bs-accordion-btn-icon); + background-repeat: no-repeat; + background-size: var(--bs-accordion-btn-icon-width); + transition: var(--bs-accordion-btn-icon-transition); +} +@media (prefers-reduced-motion: reduce) { + .accordion-button::after { + transition: none; + } +} +.accordion-button:hover { + z-index: 2; +} +.accordion-button:focus { + z-index: 3; + border-color: var(--bs-accordion-btn-focus-border-color); + outline: 0; + box-shadow: var(--bs-accordion-btn-focus-box-shadow); +} +.accordion-header { + margin-bottom: 0; +} +.accordion-item { + color: var(--bs-accordion-color); + background-color: var(--bs-accordion-bg); + border: var(--bs-accordion-border-width) solid + var(--bs-accordion-border-color); +} +.accordion-item:first-of-type { + border-top-left-radius: var(--bs-accordion-border-radius); + border-top-right-radius: var(--bs-accordion-border-radius); +} +.accordion-item:first-of-type .accordion-button { + border-top-left-radius: var(--bs-accordion-inner-border-radius); + border-top-right-radius: var( + --bs-accordion-inner-border-radius + ); +} +.accordion-item:not(:first-of-type) { + border-top: 0; +} +.accordion-item:last-of-type { + border-bottom-right-radius: var(--bs-accordion-border-radius); + border-bottom-left-radius: var(--bs-accordion-border-radius); +} +.accordion-item:last-of-type .accordion-button.collapsed { + border-bottom-right-radius: var( + --bs-accordion-inner-border-radius + ); + border-bottom-left-radius: var( + --bs-accordion-inner-border-radius + ); +} +.accordion-item:last-of-type .accordion-collapse { + border-bottom-right-radius: var(--bs-accordion-border-radius); + border-bottom-left-radius: var(--bs-accordion-border-radius); +} +.accordion-body { + padding: var(--bs-accordion-body-padding-y) + var(--bs-accordion-body-padding-x); +} +.accordion-flush .accordion-collapse { + border-width: 0; +} +.accordion-flush .accordion-item { + border-right: 0; + border-left: 0; + border-radius: 0; +} +.accordion-flush .accordion-item:first-child { + border-top: 0; +} +.accordion-flush .accordion-item:last-child { + border-bottom: 0; +} +.accordion-flush .accordion-item .accordion-button, +.accordion-flush .accordion-item .accordion-button.collapsed { + border-radius: 0; +} +[data-bs-theme="dark"] .accordion-button::after { + --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); + --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); +} +.breadcrumb { + --bs-breadcrumb-padding-x: 0; + --bs-breadcrumb-padding-y: 0; + --bs-breadcrumb-margin-bottom: 1rem; + --bs-breadcrumb-bg: ; + --bs-breadcrumb-border-radius: ; + --bs-breadcrumb-divider-color: var(--bs-secondary-color); + --bs-breadcrumb-item-padding-x: 0.5rem; + --bs-breadcrumb-item-active-color: var(--bs-secondary-color); + display: flex; + flex-wrap: wrap; + padding: var(--bs-breadcrumb-padding-y) + var(--bs-breadcrumb-padding-x); + margin-bottom: var(--bs-breadcrumb-margin-bottom); + font-size: var(--bs-breadcrumb-font-size); + list-style: none; + background-color: var(--bs-breadcrumb-bg); + border-radius: var(--bs-breadcrumb-border-radius); +} +.breadcrumb-item + .breadcrumb-item { + padding-left: var(--bs-breadcrumb-item-padding-x); +} +.breadcrumb-item + .breadcrumb-item::before { + float: left; + padding-right: var(--bs-breadcrumb-item-padding-x); + color: var(--bs-breadcrumb-divider-color); + content: var(--bs-breadcrumb-divider, "/"); +} +.breadcrumb-item.active { + color: var(--bs-breadcrumb-item-active-color); +} +.pagination { + --bs-pagination-padding-x: 0.75rem; + --bs-pagination-padding-y: 0.375rem; + --bs-pagination-font-size: 1rem; + --bs-pagination-color: var(--bs-link-color); + --bs-pagination-bg: var(--bs-body-bg); + --bs-pagination-border-width: var(--bs-border-width); + --bs-pagination-border-color: var(--bs-border-color); + --bs-pagination-border-radius: var(--bs-border-radius); + --bs-pagination-hover-color: var(--bs-link-hover-color); + --bs-pagination-hover-bg: var(--bs-tertiary-bg); + --bs-pagination-hover-border-color: var(--bs-border-color); + --bs-pagination-focus-color: var(--bs-link-hover-color); + --bs-pagination-focus-bg: var(--bs-secondary-bg); + --bs-pagination-focus-box-shadow: 0 0 0 0.25rem + rgba(13, 110, 253, 0.25); + --bs-pagination-active-color: #fff; + --bs-pagination-active-bg: #0d6efd; + --bs-pagination-active-border-color: #0d6efd; + --bs-pagination-disabled-color: var(--bs-secondary-color); + --bs-pagination-disabled-bg: var(--bs-secondary-bg); + --bs-pagination-disabled-border-color: var(--bs-border-color); + display: flex; + padding-left: 0; + list-style: none; +} +.page-link { + position: relative; + display: block; + padding: var(--bs-pagination-padding-y) + var(--bs-pagination-padding-x); + font-size: var(--bs-pagination-font-size); + color: var(--bs-pagination-color); + text-decoration: none; + background-color: var(--bs-pagination-bg); + border: var(--bs-pagination-border-width) solid + var(--bs-pagination-border-color); + transition: color 0.15s ease-in-out, + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .page-link { + transition: none; + } +} +.page-link:hover { + z-index: 2; + color: var(--bs-pagination-hover-color); + background-color: var(--bs-pagination-hover-bg); + border-color: var(--bs-pagination-hover-border-color); +} +.page-link:focus { + z-index: 3; + color: var(--bs-pagination-focus-color); + background-color: var(--bs-pagination-focus-bg); + outline: 0; + box-shadow: var(--bs-pagination-focus-box-shadow); +} +.active > .page-link, +.page-link.active { + z-index: 3; + color: var(--bs-pagination-active-color); + background-color: var(--bs-pagination-active-bg); + border-color: var(--bs-pagination-active-border-color); +} +.disabled > .page-link, +.page-link.disabled { + color: var(--bs-pagination-disabled-color); + pointer-events: none; + background-color: var(--bs-pagination-disabled-bg); + border-color: var(--bs-pagination-disabled-border-color); +} +.page-item:not(:first-child) .page-link { + margin-left: calc(var(--bs-border-width) * -1); +} +.page-item:first-child .page-link { + border-top-left-radius: var(--bs-pagination-border-radius); + border-bottom-left-radius: var(--bs-pagination-border-radius); +} +.page-item:last-child .page-link { + border-top-right-radius: var(--bs-pagination-border-radius); + border-bottom-right-radius: var(--bs-pagination-border-radius); +} +.pagination-lg { + --bs-pagination-padding-x: 1.5rem; + --bs-pagination-padding-y: 0.75rem; + --bs-pagination-font-size: 1.25rem; + --bs-pagination-border-radius: var(--bs-border-radius-lg); +} +.pagination-sm { + --bs-pagination-padding-x: 0.5rem; + --bs-pagination-padding-y: 0.25rem; + --bs-pagination-font-size: 0.875rem; + --bs-pagination-border-radius: var(--bs-border-radius-sm); +} +.badge { + --bs-badge-padding-x: 0.65em; + --bs-badge-padding-y: 0.35em; + --bs-badge-font-size: 0.75em; + --bs-badge-font-weight: 700; + --bs-badge-color: #fff; + --bs-badge-border-radius: var(--bs-border-radius); + display: inline-block; + padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x); + font-size: var(--bs-badge-font-size); + font-weight: var(--bs-badge-font-weight); + line-height: 1; + color: var(--bs-badge-color); + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: var(--bs-badge-border-radius); +} +.badge:empty { + display: none; +} +.btn .badge { + position: relative; + top: -1px; +} +.alert { + --bs-alert-bg: transparent; + --bs-alert-padding-x: 1rem; + --bs-alert-padding-y: 1rem; + --bs-alert-margin-bottom: 1rem; + --bs-alert-color: inherit; + --bs-alert-border-color: transparent; + --bs-alert-border: var(--bs-border-width) solid + var(--bs-alert-border-color); + --bs-alert-border-radius: var(--bs-border-radius); + --bs-alert-link-color: inherit; + position: relative; + padding: var(--bs-alert-padding-y) var(--bs-alert-padding-x); + margin-bottom: var(--bs-alert-margin-bottom); + color: var(--bs-alert-color); + background-color: var(--bs-alert-bg); + border: var(--bs-alert-border); + border-radius: var(--bs-alert-border-radius); +} +.alert-heading { + color: inherit; +} +.alert-link { + font-weight: 700; + color: var(--bs-alert-link-color); +} +.alert-dismissible { + padding-right: 3rem; +} +.alert-dismissible .btn-close { + position: absolute; + top: 0; + right: 0; + z-index: 2; + padding: 1.25rem 1rem; +} +.alert-primary { + --bs-alert-color: var(--bs-primary-text-emphasis); + --bs-alert-bg: var(--bs-primary-bg-subtle); + --bs-alert-border-color: var(--bs-primary-border-subtle); + --bs-alert-link-color: var(--bs-primary-text-emphasis); +} +.alert-secondary { + --bs-alert-color: var(--bs-secondary-text-emphasis); + --bs-alert-bg: var(--bs-secondary-bg-subtle); + --bs-alert-border-color: var(--bs-secondary-border-subtle); + --bs-alert-link-color: var(--bs-secondary-text-emphasis); +} +.alert-success { + --bs-alert-color: var(--bs-success-text-emphasis); + --bs-alert-bg: var(--bs-success-bg-subtle); + --bs-alert-border-color: var(--bs-success-border-subtle); + --bs-alert-link-color: var(--bs-success-text-emphasis); +} +.alert-info { + --bs-alert-color: var(--bs-info-text-emphasis); + --bs-alert-bg: var(--bs-info-bg-subtle); + --bs-alert-border-color: var(--bs-info-border-subtle); + --bs-alert-link-color: var(--bs-info-text-emphasis); +} +.alert-warning { + --bs-alert-color: var(--bs-warning-text-emphasis); + --bs-alert-bg: var(--bs-warning-bg-subtle); + --bs-alert-border-color: var(--bs-warning-border-subtle); + --bs-alert-link-color: var(--bs-warning-text-emphasis); +} +.alert-danger { + --bs-alert-color: var(--bs-danger-text-emphasis); + --bs-alert-bg: var(--bs-danger-bg-subtle); + --bs-alert-border-color: var(--bs-danger-border-subtle); + --bs-alert-link-color: var(--bs-danger-text-emphasis); +} +.alert-light { + --bs-alert-color: var(--bs-light-text-emphasis); + --bs-alert-bg: var(--bs-light-bg-subtle); + --bs-alert-border-color: var(--bs-light-border-subtle); + --bs-alert-link-color: var(--bs-light-text-emphasis); +} +.alert-dark { + --bs-alert-color: var(--bs-dark-text-emphasis); + --bs-alert-bg: var(--bs-dark-bg-subtle); + --bs-alert-border-color: var(--bs-dark-border-subtle); + --bs-alert-link-color: var(--bs-dark-text-emphasis); +} +@keyframes progress-bar-stripes { + 0% { + background-position-x: 1rem; + } +} +.progress, +.progress-stacked { + --bs-progress-height: 1rem; + --bs-progress-font-size: 0.75rem; + --bs-progress-bg: var(--bs-secondary-bg); + --bs-progress-border-radius: var(--bs-border-radius); + --bs-progress-box-shadow: var(--bs-box-shadow-inset); + --bs-progress-bar-color: #fff; + --bs-progress-bar-bg: #0d6efd; + --bs-progress-bar-transition: width 0.6s ease; + display: flex; + height: var(--bs-progress-height); + overflow: hidden; + font-size: var(--bs-progress-font-size); + background-color: var(--bs-progress-bg); + border-radius: var(--bs-progress-border-radius); +} +.progress-bar { + display: flex; + flex-direction: column; + justify-content: center; + overflow: hidden; + color: var(--bs-progress-bar-color); + text-align: center; + white-space: nowrap; + background-color: var(--bs-progress-bar-bg); + transition: var(--bs-progress-bar-transition); +} +@media (prefers-reduced-motion: reduce) { + .progress-bar { + transition: none; + } +} +.progress-bar-striped { + background-image: linear-gradient( + 45deg, + rgba(255, 255, 255, 0.15) 25%, + transparent 25%, + transparent 50%, + rgba(255, 255, 255, 0.15) 50%, + rgba(255, 255, 255, 0.15) 75%, + transparent 75%, + transparent + ); + background-size: var(--bs-progress-height) + var(--bs-progress-height); +} +.progress-stacked > .progress { + overflow: visible; +} +.progress-stacked > .progress > .progress-bar { + width: 100%; +} +.progress-bar-animated { + animation: 1s linear infinite progress-bar-stripes; +} +@media (prefers-reduced-motion: reduce) { + .progress-bar-animated { + animation: none; + } +} +.list-group { + --bs-list-group-color: var(--bs-body-color); + --bs-list-group-bg: var(--bs-body-bg); + --bs-list-group-border-color: var(--bs-border-color); + --bs-list-group-border-width: var(--bs-border-width); + --bs-list-group-border-radius: var(--bs-border-radius); + --bs-list-group-item-padding-x: 1rem; + --bs-list-group-item-padding-y: 0.5rem; + --bs-list-group-action-color: var(--bs-secondary-color); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-tertiary-bg); + --bs-list-group-action-active-color: var(--bs-body-color); + --bs-list-group-action-active-bg: var(--bs-secondary-bg); + --bs-list-group-disabled-color: var(--bs-secondary-color); + --bs-list-group-disabled-bg: var(--bs-body-bg); + --bs-list-group-active-color: #fff; + --bs-list-group-active-bg: #0d6efd; + --bs-list-group-active-border-color: #0d6efd; + display: flex; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + border-radius: var(--bs-list-group-border-radius); +} +.list-group-numbered { + list-style-type: none; + counter-reset: section; +} +.list-group-numbered > .list-group-item::before { + content: counters(section, ".") ". "; + counter-increment: section; +} +.list-group-item-action { + width: 100%; + color: var(--bs-list-group-action-color); + text-align: inherit; +} +.list-group-item-action:focus, +.list-group-item-action:hover { + z-index: 1; + color: var(--bs-list-group-action-hover-color); + text-decoration: none; + background-color: var(--bs-list-group-action-hover-bg); +} +.list-group-item-action:active { + color: var(--bs-list-group-action-active-color); + background-color: var(--bs-list-group-action-active-bg); +} +.list-group-item { + position: relative; + display: block; + padding: var(--bs-list-group-item-padding-y) + var(--bs-list-group-item-padding-x); + color: var(--bs-list-group-color); + text-decoration: none; + background-color: var(--bs-list-group-bg); + border: var(--bs-list-group-border-width) solid + var(--bs-list-group-border-color); +} +.list-group-item:first-child { + border-top-left-radius: inherit; + border-top-right-radius: inherit; +} +.list-group-item:last-child { + border-bottom-right-radius: inherit; + border-bottom-left-radius: inherit; +} +.list-group-item.disabled, +.list-group-item:disabled { + color: var(--bs-list-group-disabled-color); + pointer-events: none; + background-color: var(--bs-list-group-disabled-bg); +} +.list-group-item.active { + z-index: 2; + color: var(--bs-list-group-active-color); + background-color: var(--bs-list-group-active-bg); + border-color: var(--bs-list-group-active-border-color); +} +.list-group-item + .list-group-item { + border-top-width: 0; +} +.list-group-item + .list-group-item.active { + margin-top: calc(-1 * var(--bs-list-group-border-width)); + border-top-width: var(--bs-list-group-border-width); +} +.list-group-horizontal { + flex-direction: row; +} +.list-group-horizontal + > .list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var(--bs-list-group-border-radius); + border-top-right-radius: 0; +} +.list-group-horizontal + > .list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0; +} +.list-group-horizontal > .list-group-item.active { + margin-top: 0; +} +.list-group-horizontal > .list-group-item + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; +} +.list-group-horizontal + > .list-group-item + + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); +} +@media (min-width: 576px) { + .list-group-horizontal-sm { + flex-direction: row; + } + .list-group-horizontal-sm + > .list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var( + --bs-list-group-border-radius + ); + border-top-right-radius: 0; + } + .list-group-horizontal-sm + > .list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0; + } + .list-group-horizontal-sm > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-sm + > .list-group-item + + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; + } + .list-group-horizontal-sm + > .list-group-item + + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); + } +} +@media (min-width: 768px) { + .list-group-horizontal-md { + flex-direction: row; + } + .list-group-horizontal-md + > .list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var( + --bs-list-group-border-radius + ); + border-top-right-radius: 0; + } + .list-group-horizontal-md + > .list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0; + } + .list-group-horizontal-md > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-md + > .list-group-item + + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; + } + .list-group-horizontal-md + > .list-group-item + + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); + } +} +@media (min-width: 992px) { + .list-group-horizontal-lg { + flex-direction: row; + } + .list-group-horizontal-lg + > .list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var( + --bs-list-group-border-radius + ); + border-top-right-radius: 0; + } + .list-group-horizontal-lg + > .list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0; + } + .list-group-horizontal-lg > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-lg + > .list-group-item + + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; + } + .list-group-horizontal-lg + > .list-group-item + + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); + } +} +@media (min-width: 1200px) { + .list-group-horizontal-xl { + flex-direction: row; + } + .list-group-horizontal-xl + > .list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var( + --bs-list-group-border-radius + ); + border-top-right-radius: 0; + } + .list-group-horizontal-xl + > .list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0; + } + .list-group-horizontal-xl > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-xl + > .list-group-item + + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; + } + .list-group-horizontal-xl + > .list-group-item + + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); + } +} +@media (min-width: 1400px) { + .list-group-horizontal-xxl { + flex-direction: row; + } + .list-group-horizontal-xxl + > .list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var( + --bs-list-group-border-radius + ); + border-top-right-radius: 0; + } + .list-group-horizontal-xxl + > .list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0; + } + .list-group-horizontal-xxl > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-xxl + > .list-group-item + + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; + } + .list-group-horizontal-xxl + > .list-group-item + + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); + } +} +.list-group-flush { + border-radius: 0; +} +.list-group-flush > .list-group-item { + border-width: 0 0 var(--bs-list-group-border-width); +} +.list-group-flush > .list-group-item:last-child { + border-bottom-width: 0; +} +.list-group-item-primary { + --bs-list-group-color: var(--bs-primary-text-emphasis); + --bs-list-group-bg: var(--bs-primary-bg-subtle); + --bs-list-group-border-color: var(--bs-primary-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var( + --bs-primary-border-subtle + ); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var( + --bs-primary-border-subtle + ); + --bs-list-group-active-color: var(--bs-primary-bg-subtle); + --bs-list-group-active-bg: var(--bs-primary-text-emphasis); + --bs-list-group-active-border-color: var( + --bs-primary-text-emphasis + ); +} +.list-group-item-secondary { + --bs-list-group-color: var(--bs-secondary-text-emphasis); + --bs-list-group-bg: var(--bs-secondary-bg-subtle); + --bs-list-group-border-color: var(--bs-secondary-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var( + --bs-secondary-border-subtle + ); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var( + --bs-secondary-border-subtle + ); + --bs-list-group-active-color: var(--bs-secondary-bg-subtle); + --bs-list-group-active-bg: var(--bs-secondary-text-emphasis); + --bs-list-group-active-border-color: var( + --bs-secondary-text-emphasis + ); +} +.list-group-item-success { + --bs-list-group-color: var(--bs-success-text-emphasis); + --bs-list-group-bg: var(--bs-success-bg-subtle); + --bs-list-group-border-color: var(--bs-success-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var( + --bs-success-border-subtle + ); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var( + --bs-success-border-subtle + ); + --bs-list-group-active-color: var(--bs-success-bg-subtle); + --bs-list-group-active-bg: var(--bs-success-text-emphasis); + --bs-list-group-active-border-color: var( + --bs-success-text-emphasis + ); +} +.list-group-item-info { + --bs-list-group-color: var(--bs-info-text-emphasis); + --bs-list-group-bg: var(--bs-info-bg-subtle); + --bs-list-group-border-color: var(--bs-info-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-info-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-info-border-subtle); + --bs-list-group-active-color: var(--bs-info-bg-subtle); + --bs-list-group-active-bg: var(--bs-info-text-emphasis); + --bs-list-group-active-border-color: var( + --bs-info-text-emphasis + ); +} +.list-group-item-warning { + --bs-list-group-color: var(--bs-warning-text-emphasis); + --bs-list-group-bg: var(--bs-warning-bg-subtle); + --bs-list-group-border-color: var(--bs-warning-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var( + --bs-warning-border-subtle + ); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var( + --bs-warning-border-subtle + ); + --bs-list-group-active-color: var(--bs-warning-bg-subtle); + --bs-list-group-active-bg: var(--bs-warning-text-emphasis); + --bs-list-group-active-border-color: var( + --bs-warning-text-emphasis + ); +} +.list-group-item-danger { + --bs-list-group-color: var(--bs-danger-text-emphasis); + --bs-list-group-bg: var(--bs-danger-bg-subtle); + --bs-list-group-border-color: var(--bs-danger-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-danger-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var( + --bs-danger-border-subtle + ); + --bs-list-group-active-color: var(--bs-danger-bg-subtle); + --bs-list-group-active-bg: var(--bs-danger-text-emphasis); + --bs-list-group-active-border-color: var( + --bs-danger-text-emphasis + ); +} +.list-group-item-light { + --bs-list-group-color: var(--bs-light-text-emphasis); + --bs-list-group-bg: var(--bs-light-bg-subtle); + --bs-list-group-border-color: var(--bs-light-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-light-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-light-border-subtle); + --bs-list-group-active-color: var(--bs-light-bg-subtle); + --bs-list-group-active-bg: var(--bs-light-text-emphasis); + --bs-list-group-active-border-color: var( + --bs-light-text-emphasis + ); +} +.list-group-item-dark { + --bs-list-group-color: var(--bs-dark-text-emphasis); + --bs-list-group-bg: var(--bs-dark-bg-subtle); + --bs-list-group-border-color: var(--bs-dark-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-dark-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-dark-border-subtle); + --bs-list-group-active-color: var(--bs-dark-bg-subtle); + --bs-list-group-active-bg: var(--bs-dark-text-emphasis); + --bs-list-group-active-border-color: var( + --bs-dark-text-emphasis + ); +} +.btn-close { + --bs-btn-close-color: #000; + --bs-btn-close-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e"); + --bs-btn-close-opacity: 0.5; + --bs-btn-close-hover-opacity: 0.75; + --bs-btn-close-focus-shadow: 0 0 0 0.25rem + rgba(13, 110, 253, 0.25); + --bs-btn-close-focus-opacity: 1; + --bs-btn-close-disabled-opacity: 0.25; + --bs-btn-close-white-filter: invert(1) grayscale(100%) + brightness(200%); + box-sizing: content-box; + width: 1em; + height: 1em; + padding: 0.25em 0.25em; + color: var(--bs-btn-close-color); + background: transparent var(--bs-btn-close-bg) center/1em auto + no-repeat; + border: 0; + border-radius: 0.375rem; + opacity: var(--bs-btn-close-opacity); +} +.btn-close:hover { + color: var(--bs-btn-close-color); + text-decoration: none; + opacity: var(--bs-btn-close-hover-opacity); +} +.btn-close:focus { + outline: 0; + box-shadow: var(--bs-btn-close-focus-shadow); + opacity: var(--bs-btn-close-focus-opacity); +} +.btn-close.disabled, +.btn-close:disabled { + pointer-events: none; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + opacity: var(--bs-btn-close-disabled-opacity); +} +.btn-close-white { + filter: var(--bs-btn-close-white-filter); +} +[data-bs-theme="dark"] .btn-close { + filter: var(--bs-btn-close-white-filter); +} +.toast { + --bs-toast-zindex: 1090; + --bs-toast-padding-x: 0.75rem; + --bs-toast-padding-y: 0.5rem; + --bs-toast-spacing: 1.5rem; + --bs-toast-max-width: 350px; + --bs-toast-font-size: 0.875rem; + --bs-toast-color: ; + --bs-toast-bg: rgba(var(--bs-body-bg-rgb), 0.85); + --bs-toast-border-width: var(--bs-border-width); + --bs-toast-border-color: var(--bs-border-color-translucent); + --bs-toast-border-radius: var(--bs-border-radius); + --bs-toast-box-shadow: var(--bs-box-shadow); + --bs-toast-header-color: var(--bs-secondary-color); + --bs-toast-header-bg: rgba(var(--bs-body-bg-rgb), 0.85); + --bs-toast-header-border-color: var( + --bs-border-color-translucent + ); + width: var(--bs-toast-max-width); + max-width: 100%; + font-size: var(--bs-toast-font-size); + color: var(--bs-toast-color); + pointer-events: auto; + background-color: var(--bs-toast-bg); + background-clip: padding-box; + border: var(--bs-toast-border-width) solid + var(--bs-toast-border-color); + box-shadow: var(--bs-toast-box-shadow); + border-radius: var(--bs-toast-border-radius); +} +.toast.showing { + opacity: 0; +} +.toast:not(.show) { + display: none; +} +.toast-container { + --bs-toast-zindex: 1090; + position: absolute; + z-index: var(--bs-toast-zindex); + width: -webkit-max-content; + width: -moz-max-content; + width: max-content; + max-width: 100%; + pointer-events: none; +} +.toast-container > :not(:last-child) { + margin-bottom: var(--bs-toast-spacing); +} +.toast-header { + display: flex; + align-items: center; + padding: var(--bs-toast-padding-y) var(--bs-toast-padding-x); + color: var(--bs-toast-header-color); + background-color: var(--bs-toast-header-bg); + background-clip: padding-box; + border-bottom: var(--bs-toast-border-width) solid + var(--bs-toast-header-border-color); + border-top-left-radius: calc( + var(--bs-toast-border-radius) - var(--bs-toast-border-width) + ); + border-top-right-radius: calc( + var(--bs-toast-border-radius) - var(--bs-toast-border-width) + ); +} +.toast-header .btn-close { + margin-right: calc(-0.5 * var(--bs-toast-padding-x)); + margin-left: var(--bs-toast-padding-x); +} +.toast-body { + padding: var(--bs-toast-padding-x); + word-wrap: break-word; +} +.modal { + --bs-modal-zindex: 1055; + --bs-modal-width: 500px; + --bs-modal-padding: 1rem; + --bs-modal-margin: 0.5rem; + --bs-modal-color: ; + --bs-modal-bg: var(--bs-body-bg); + --bs-modal-border-color: var(--bs-border-color-translucent); + --bs-modal-border-width: var(--bs-border-width); + --bs-modal-border-radius: var(--bs-border-radius-lg); + --bs-modal-box-shadow: var(--bs-box-shadow-sm); + --bs-modal-inner-border-radius: calc( + var(--bs-border-radius-lg) - (var(--bs-border-width)) + ); + --bs-modal-header-padding-x: 1rem; + --bs-modal-header-padding-y: 1rem; + --bs-modal-header-padding: 1rem 1rem; + --bs-modal-header-border-color: var(--bs-border-color); + --bs-modal-header-border-width: var(--bs-border-width); + --bs-modal-title-line-height: 1.5; + --bs-modal-footer-gap: 0.5rem; + --bs-modal-footer-bg: ; + --bs-modal-footer-border-color: var(--bs-border-color); + --bs-modal-footer-border-width: var(--bs-border-width); + position: fixed; + top: 0; + left: 0; + z-index: var(--bs-modal-zindex); + display: none; + width: 100%; + height: 100%; + overflow-x: hidden; + overflow-y: auto; + outline: 0; +} +.modal-dialog { + position: relative; + width: auto; + margin: var(--bs-modal-margin); + pointer-events: none; +} +.modal.fade .modal-dialog { + transition: transform 0.3s ease-out; + transform: translate(0, -50px); +} +@media (prefers-reduced-motion: reduce) { + .modal.fade .modal-dialog { + transition: none; + } +} +.modal.show .modal-dialog { + transform: none; +} +.modal.modal-static .modal-dialog { + transform: scale(1.02); +} +.modal-dialog-scrollable { + height: calc(100% - var(--bs-modal-margin) * 2); +} +.modal-dialog-scrollable .modal-content { + max-height: 100%; + overflow: hidden; +} +.modal-dialog-scrollable .modal-body { + overflow-y: auto; +} +.modal-dialog-centered { + display: flex; + align-items: center; + min-height: calc(100% - var(--bs-modal-margin) * 2); +} +.modal-content { + position: relative; + display: flex; + flex-direction: column; + width: 100%; + color: var(--bs-modal-color); + pointer-events: auto; + background-color: var(--bs-modal-bg); + background-clip: padding-box; + border: var(--bs-modal-border-width) solid + var(--bs-modal-border-color); + border-radius: var(--bs-modal-border-radius); + outline: 0; +} +.modal-backdrop { + --bs-backdrop-zindex: 1050; + --bs-backdrop-bg: #000; + --bs-backdrop-opacity: 0.5; + position: fixed; + top: 0; + left: 0; + z-index: var(--bs-backdrop-zindex); + width: 100vw; + height: 100vh; + background-color: var(--bs-backdrop-bg); +} +.modal-backdrop.fade { + opacity: 0; +} +.modal-backdrop.show { + opacity: var(--bs-backdrop-opacity); +} +.modal-header { + display: flex; + flex-shrink: 0; + align-items: center; + justify-content: space-between; + padding: var(--bs-modal-header-padding); + border-bottom: var(--bs-modal-header-border-width) solid + var(--bs-modal-header-border-color); + border-top-left-radius: var(--bs-modal-inner-border-radius); + border-top-right-radius: var(--bs-modal-inner-border-radius); +} +.modal-header .btn-close { + padding: calc(var(--bs-modal-header-padding-y) * 0.5) + calc(var(--bs-modal-header-padding-x) * 0.5); + margin: calc(-0.5 * var(--bs-modal-header-padding-y)) + calc(-0.5 * var(--bs-modal-header-padding-x)) + calc(-0.5 * var(--bs-modal-header-padding-y)) auto; +} +.modal-title { + margin-bottom: 0; + line-height: var(--bs-modal-title-line-height); +} +.modal-body { + position: relative; + flex: 1 1 auto; + padding: var(--bs-modal-padding); +} +.modal-footer { + display: flex; + flex-shrink: 0; + flex-wrap: wrap; + align-items: center; + justify-content: flex-end; + padding: calc( + var(--bs-modal-padding) - var(--bs-modal-footer-gap) * 0.5 + ); + background-color: var(--bs-modal-footer-bg); + border-top: var(--bs-modal-footer-border-width) solid + var(--bs-modal-footer-border-color); + border-bottom-right-radius: var(--bs-modal-inner-border-radius); + border-bottom-left-radius: var(--bs-modal-inner-border-radius); +} +.modal-footer > * { + margin: calc(var(--bs-modal-footer-gap) * 0.5); +} +@media (min-width: 576px) { + .modal { + --bs-modal-margin: 1.75rem; + --bs-modal-box-shadow: var(--bs-box-shadow); + } + .modal-dialog { + max-width: var(--bs-modal-width); + margin-right: auto; + margin-left: auto; + } + .modal-sm { + --bs-modal-width: 300px; + } +} +@media (min-width: 992px) { + .modal-lg, + .modal-xl { + --bs-modal-width: 800px; + } +} +@media (min-width: 1200px) { + .modal-xl { + --bs-modal-width: 1140px; + } +} +.modal-fullscreen { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; +} +.modal-fullscreen .modal-content { + height: 100%; + border: 0; + border-radius: 0; +} +.modal-fullscreen .modal-footer, +.modal-fullscreen .modal-header { + border-radius: 0; +} +.modal-fullscreen .modal-body { + overflow-y: auto; +} +@media (max-width: 575.98px) { + .modal-fullscreen-sm-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + .modal-fullscreen-sm-down .modal-content { + height: 100%; + border: 0; + border-radius: 0; + } + .modal-fullscreen-sm-down .modal-footer, + .modal-fullscreen-sm-down .modal-header { + border-radius: 0; + } + .modal-fullscreen-sm-down .modal-body { + overflow-y: auto; + } +} +@media (max-width: 767.98px) { + .modal-fullscreen-md-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + .modal-fullscreen-md-down .modal-content { + height: 100%; + border: 0; + border-radius: 0; + } + .modal-fullscreen-md-down .modal-footer, + .modal-fullscreen-md-down .modal-header { + border-radius: 0; + } + .modal-fullscreen-md-down .modal-body { + overflow-y: auto; + } +} +@media (max-width: 991.98px) { + .modal-fullscreen-lg-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + .modal-fullscreen-lg-down .modal-content { + height: 100%; + border: 0; + border-radius: 0; + } + .modal-fullscreen-lg-down .modal-footer, + .modal-fullscreen-lg-down .modal-header { + border-radius: 0; + } + .modal-fullscreen-lg-down .modal-body { + overflow-y: auto; + } +} +@media (max-width: 1199.98px) { + .modal-fullscreen-xl-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + .modal-fullscreen-xl-down .modal-content { + height: 100%; + border: 0; + border-radius: 0; + } + .modal-fullscreen-xl-down .modal-footer, + .modal-fullscreen-xl-down .modal-header { + border-radius: 0; + } + .modal-fullscreen-xl-down .modal-body { + overflow-y: auto; + } +} +@media (max-width: 1399.98px) { + .modal-fullscreen-xxl-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + .modal-fullscreen-xxl-down .modal-content { + height: 100%; + border: 0; + border-radius: 0; + } + .modal-fullscreen-xxl-down .modal-footer, + .modal-fullscreen-xxl-down .modal-header { + border-radius: 0; + } + .modal-fullscreen-xxl-down .modal-body { + overflow-y: auto; + } +} +.tooltip { + --bs-tooltip-zindex: 1080; + --bs-tooltip-max-width: 200px; + --bs-tooltip-padding-x: 0.5rem; + --bs-tooltip-padding-y: 0.25rem; + --bs-tooltip-margin: ; + --bs-tooltip-font-size: 0.875rem; + --bs-tooltip-color: var(--bs-body-bg); + --bs-tooltip-bg: var(--bs-emphasis-color); + --bs-tooltip-border-radius: var(--bs-border-radius); + --bs-tooltip-opacity: 0.9; + --bs-tooltip-arrow-width: 0.8rem; + --bs-tooltip-arrow-height: 0.4rem; + z-index: var(--bs-tooltip-zindex); + display: block; + margin: var(--bs-tooltip-margin); + font-family: var(--bs-font-sans-serif); + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + white-space: normal; + word-spacing: normal; + line-break: auto; + font-size: var(--bs-tooltip-font-size); + word-wrap: break-word; + opacity: 0; +} +.tooltip.show { + opacity: var(--bs-tooltip-opacity); +} +.tooltip .tooltip-arrow { + display: block; + width: var(--bs-tooltip-arrow-width); + height: var(--bs-tooltip-arrow-height); +} +.tooltip .tooltip-arrow::before { + position: absolute; + content: ""; + border-color: transparent; + border-style: solid; +} +.bs-tooltip-auto[data-popper-placement^="top"] .tooltip-arrow, +.bs-tooltip-top .tooltip-arrow { + bottom: calc(-1 * var(--bs-tooltip-arrow-height)); +} +.bs-tooltip-auto[data-popper-placement^="top"] + .tooltip-arrow::before, +.bs-tooltip-top .tooltip-arrow::before { + top: -1px; + border-width: var(--bs-tooltip-arrow-height) + calc(var(--bs-tooltip-arrow-width) * 0.5) 0; + border-top-color: var(--bs-tooltip-bg); +} +.bs-tooltip-auto[data-popper-placement^="right"] .tooltip-arrow, +.bs-tooltip-end .tooltip-arrow { + left: calc(-1 * var(--bs-tooltip-arrow-height)); + width: var(--bs-tooltip-arrow-height); + height: var(--bs-tooltip-arrow-width); +} +.bs-tooltip-auto[data-popper-placement^="right"] + .tooltip-arrow::before, +.bs-tooltip-end .tooltip-arrow::before { + right: -1px; + border-width: calc(var(--bs-tooltip-arrow-width) * 0.5) + var(--bs-tooltip-arrow-height) + calc(var(--bs-tooltip-arrow-width) * 0.5) 0; + border-right-color: var(--bs-tooltip-bg); +} +.bs-tooltip-auto[data-popper-placement^="bottom"] .tooltip-arrow, +.bs-tooltip-bottom .tooltip-arrow { + top: calc(-1 * var(--bs-tooltip-arrow-height)); +} +.bs-tooltip-auto[data-popper-placement^="bottom"] + .tooltip-arrow::before, +.bs-tooltip-bottom .tooltip-arrow::before { + bottom: -1px; + border-width: 0 calc(var(--bs-tooltip-arrow-width) * 0.5) + var(--bs-tooltip-arrow-height); + border-bottom-color: var(--bs-tooltip-bg); +} +.bs-tooltip-auto[data-popper-placement^="left"] .tooltip-arrow, +.bs-tooltip-start .tooltip-arrow { + right: calc(-1 * var(--bs-tooltip-arrow-height)); + width: var(--bs-tooltip-arrow-height); + height: var(--bs-tooltip-arrow-width); +} +.bs-tooltip-auto[data-popper-placement^="left"] + .tooltip-arrow::before, +.bs-tooltip-start .tooltip-arrow::before { + left: -1px; + border-width: calc(var(--bs-tooltip-arrow-width) * 0.5) 0 + calc(var(--bs-tooltip-arrow-width) * 0.5) + var(--bs-tooltip-arrow-height); + border-left-color: var(--bs-tooltip-bg); +} +.tooltip-inner { + max-width: var(--bs-tooltip-max-width); + padding: var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x); + color: var(--bs-tooltip-color); + text-align: center; + background-color: var(--bs-tooltip-bg); + border-radius: var(--bs-tooltip-border-radius); +} +.popover { + --bs-popover-zindex: 1070; + --bs-popover-max-width: 276px; + --bs-popover-font-size: 0.875rem; + --bs-popover-bg: var(--bs-body-bg); + --bs-popover-border-width: var(--bs-border-width); + --bs-popover-border-color: var(--bs-border-color-translucent); + --bs-popover-border-radius: var(--bs-border-radius-lg); + --bs-popover-inner-border-radius: calc( + var(--bs-border-radius-lg) - var(--bs-border-width) + ); + --bs-popover-box-shadow: var(--bs-box-shadow); + --bs-popover-header-padding-x: 1rem; + --bs-popover-header-padding-y: 0.5rem; + --bs-popover-header-font-size: 1rem; + --bs-popover-header-color: inherit; + --bs-popover-header-bg: var(--bs-secondary-bg); + --bs-popover-body-padding-x: 1rem; + --bs-popover-body-padding-y: 1rem; + --bs-popover-body-color: var(--bs-body-color); + --bs-popover-arrow-width: 1rem; + --bs-popover-arrow-height: 0.5rem; + --bs-popover-arrow-border: var(--bs-popover-border-color); + z-index: var(--bs-popover-zindex); + display: block; + max-width: var(--bs-popover-max-width); + font-family: var(--bs-font-sans-serif); + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + white-space: normal; + word-spacing: normal; + line-break: auto; + font-size: var(--bs-popover-font-size); + word-wrap: break-word; + background-color: var(--bs-popover-bg); + background-clip: padding-box; + border: var(--bs-popover-border-width) solid + var(--bs-popover-border-color); + border-radius: var(--bs-popover-border-radius); +} +.popover .popover-arrow { + display: block; + width: var(--bs-popover-arrow-width); + height: var(--bs-popover-arrow-height); +} +.popover .popover-arrow::after, +.popover .popover-arrow::before { + position: absolute; + display: block; + content: ""; + border-color: transparent; + border-style: solid; + border-width: 0; +} +.bs-popover-auto[data-popper-placement^="top"] > .popover-arrow, +.bs-popover-top > .popover-arrow { + bottom: calc( + -1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width) + ); +} +.bs-popover-auto[data-popper-placement^="top"] + > .popover-arrow::after, +.bs-popover-auto[data-popper-placement^="top"] + > .popover-arrow::before, +.bs-popover-top > .popover-arrow::after, +.bs-popover-top > .popover-arrow::before { + border-width: var(--bs-popover-arrow-height) + calc(var(--bs-popover-arrow-width) * 0.5) 0; +} +.bs-popover-auto[data-popper-placement^="top"] + > .popover-arrow::before, +.bs-popover-top > .popover-arrow::before { + bottom: 0; + border-top-color: var(--bs-popover-arrow-border); +} +.bs-popover-auto[data-popper-placement^="top"] + > .popover-arrow::after, +.bs-popover-top > .popover-arrow::after { + bottom: var(--bs-popover-border-width); + border-top-color: var(--bs-popover-bg); +} +.bs-popover-auto[data-popper-placement^="right"] > .popover-arrow, +.bs-popover-end > .popover-arrow { + left: calc( + -1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width) + ); + width: var(--bs-popover-arrow-height); + height: var(--bs-popover-arrow-width); +} +.bs-popover-auto[data-popper-placement^="right"] + > .popover-arrow::after, +.bs-popover-auto[data-popper-placement^="right"] + > .popover-arrow::before, +.bs-popover-end > .popover-arrow::after, +.bs-popover-end > .popover-arrow::before { + border-width: calc(var(--bs-popover-arrow-width) * 0.5) + var(--bs-popover-arrow-height) + calc(var(--bs-popover-arrow-width) * 0.5) 0; +} +.bs-popover-auto[data-popper-placement^="right"] + > .popover-arrow::before, +.bs-popover-end > .popover-arrow::before { + left: 0; + border-right-color: var(--bs-popover-arrow-border); +} +.bs-popover-auto[data-popper-placement^="right"] + > .popover-arrow::after, +.bs-popover-end > .popover-arrow::after { + left: var(--bs-popover-border-width); + border-right-color: var(--bs-popover-bg); +} +.bs-popover-auto[data-popper-placement^="bottom"] > .popover-arrow, +.bs-popover-bottom > .popover-arrow { + top: calc( + -1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width) + ); +} +.bs-popover-auto[data-popper-placement^="bottom"] + > .popover-arrow::after, +.bs-popover-auto[data-popper-placement^="bottom"] + > .popover-arrow::before, +.bs-popover-bottom > .popover-arrow::after, +.bs-popover-bottom > .popover-arrow::before { + border-width: 0 calc(var(--bs-popover-arrow-width) * 0.5) + var(--bs-popover-arrow-height); +} +.bs-popover-auto[data-popper-placement^="bottom"] + > .popover-arrow::before, +.bs-popover-bottom > .popover-arrow::before { + top: 0; + border-bottom-color: var(--bs-popover-arrow-border); +} +.bs-popover-auto[data-popper-placement^="bottom"] + > .popover-arrow::after, +.bs-popover-bottom > .popover-arrow::after { + top: var(--bs-popover-border-width); + border-bottom-color: var(--bs-popover-bg); +} +.bs-popover-auto[data-popper-placement^="bottom"] + .popover-header::before, +.bs-popover-bottom .popover-header::before { + position: absolute; + top: 0; + left: 50%; + display: block; + width: var(--bs-popover-arrow-width); + margin-left: calc(-0.5 * var(--bs-popover-arrow-width)); + content: ""; + border-bottom: var(--bs-popover-border-width) solid + var(--bs-popover-header-bg); +} +.bs-popover-auto[data-popper-placement^="left"] > .popover-arrow, +.bs-popover-start > .popover-arrow { + right: calc( + -1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width) + ); + width: var(--bs-popover-arrow-height); + height: var(--bs-popover-arrow-width); +} +.bs-popover-auto[data-popper-placement^="left"] + > .popover-arrow::after, +.bs-popover-auto[data-popper-placement^="left"] + > .popover-arrow::before, +.bs-popover-start > .popover-arrow::after, +.bs-popover-start > .popover-arrow::before { + border-width: calc(var(--bs-popover-arrow-width) * 0.5) 0 + calc(var(--bs-popover-arrow-width) * 0.5) + var(--bs-popover-arrow-height); +} +.bs-popover-auto[data-popper-placement^="left"] + > .popover-arrow::before, +.bs-popover-start > .popover-arrow::before { + right: 0; + border-left-color: var(--bs-popover-arrow-border); +} +.bs-popover-auto[data-popper-placement^="left"] + > .popover-arrow::after, +.bs-popover-start > .popover-arrow::after { + right: var(--bs-popover-border-width); + border-left-color: var(--bs-popover-bg); +} +.popover-header { + padding: var(--bs-popover-header-padding-y) + var(--bs-popover-header-padding-x); + margin-bottom: 0; + font-size: var(--bs-popover-header-font-size); + color: var(--bs-popover-header-color); + background-color: var(--bs-popover-header-bg); + border-bottom: var(--bs-popover-border-width) solid + var(--bs-popover-border-color); + border-top-left-radius: var(--bs-popover-inner-border-radius); + border-top-right-radius: var(--bs-popover-inner-border-radius); +} +.popover-header:empty { + display: none; +} +.popover-body { + padding: var(--bs-popover-body-padding-y) + var(--bs-popover-body-padding-x); + color: var(--bs-popover-body-color); +} +.carousel { + position: relative; +} +.carousel.pointer-event { + touch-action: pan-y; +} +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} +.carousel-inner::after { + display: block; + clear: both; + content: ""; +} +.carousel-item { + position: relative; + display: none; + float: left; + width: 100%; + margin-right: -100%; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + transition: transform 0.6s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .carousel-item { + transition: none; + } +} +.carousel-item-next, +.carousel-item-prev, +.carousel-item.active { + display: block; +} +.active.carousel-item-end, +.carousel-item-next:not(.carousel-item-start) { + transform: translateX(100%); +} +.active.carousel-item-start, +.carousel-item-prev:not(.carousel-item-end) { + transform: translateX(-100%); +} +.carousel-fade .carousel-item { + opacity: 0; + transition-property: opacity; + transform: none; +} +.carousel-fade .carousel-item-next.carousel-item-start, +.carousel-fade .carousel-item-prev.carousel-item-end, +.carousel-fade .carousel-item.active { + z-index: 1; + opacity: 1; +} +.carousel-fade .active.carousel-item-end, +.carousel-fade .active.carousel-item-start { + z-index: 0; + opacity: 0; + transition: opacity 0s 0.6s; +} +@media (prefers-reduced-motion: reduce) { + .carousel-fade .active.carousel-item-end, + .carousel-fade .active.carousel-item-start { + transition: none; + } +} +.carousel-control-next, +.carousel-control-prev { + position: absolute; + top: 0; + bottom: 0; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + width: 15%; + padding: 0; + color: #fff; + text-align: center; + background: 0 0; + border: 0; + opacity: 0.5; + transition: opacity 0.15s ease; +} +@media (prefers-reduced-motion: reduce) { + .carousel-control-next, + .carousel-control-prev { + transition: none; + } +} +.carousel-control-next:focus, +.carousel-control-next:hover, +.carousel-control-prev:focus, +.carousel-control-prev:hover { + color: #fff; + text-decoration: none; + outline: 0; + opacity: 0.9; +} +.carousel-control-prev { + left: 0; +} +.carousel-control-next { + right: 0; +} +.carousel-control-next-icon, +.carousel-control-prev-icon { + display: inline-block; + width: 2rem; + height: 2rem; + background-repeat: no-repeat; + background-position: 50%; + background-size: 100% 100%; +} +.carousel-control-prev-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e"); +} +.carousel-control-next-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); +} +.carousel-indicators { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 2; + display: flex; + justify-content: center; + padding: 0; + margin-right: 15%; + margin-bottom: 1rem; + margin-left: 15%; +} +.carousel-indicators [data-bs-target] { + box-sizing: content-box; + flex: 0 1 auto; + width: 30px; + height: 3px; + padding: 0; + margin-right: 3px; + margin-left: 3px; + text-indent: -999px; + cursor: pointer; + background-color: #fff; + background-clip: padding-box; + border: 0; + border-top: 10px solid transparent; + border-bottom: 10px solid transparent; + opacity: 0.5; + transition: opacity 0.6s ease; +} +@media (prefers-reduced-motion: reduce) { + .carousel-indicators [data-bs-target] { + transition: none; + } +} +.carousel-indicators .active { + opacity: 1; +} +.carousel-caption { + position: absolute; + right: 15%; + bottom: 1.25rem; + left: 15%; + padding-top: 1.25rem; + padding-bottom: 1.25rem; + color: #fff; + text-align: center; +} +.carousel-dark .carousel-control-next-icon, +.carousel-dark .carousel-control-prev-icon { + filter: invert(1) grayscale(100); +} +.carousel-dark .carousel-indicators [data-bs-target] { + background-color: #000; +} +.carousel-dark .carousel-caption { + color: #000; +} +[data-bs-theme="dark"] .carousel .carousel-control-next-icon, +[data-bs-theme="dark"] .carousel .carousel-control-prev-icon, +[data-bs-theme="dark"].carousel .carousel-control-next-icon, +[data-bs-theme="dark"].carousel .carousel-control-prev-icon { + filter: invert(1) grayscale(100); +} +[data-bs-theme="dark"] + .carousel + .carousel-indicators + [data-bs-target], +[data-bs-theme="dark"].carousel + .carousel-indicators + [data-bs-target] { + background-color: #000; +} +[data-bs-theme="dark"] .carousel .carousel-caption, +[data-bs-theme="dark"].carousel .carousel-caption { + color: #000; +} +.spinner-border, +.spinner-grow { + display: inline-block; + width: var(--bs-spinner-width); + height: var(--bs-spinner-height); + vertical-align: var(--bs-spinner-vertical-align); + border-radius: 50%; + animation: var(--bs-spinner-animation-speed) linear infinite + var(--bs-spinner-animation-name); +} +@keyframes spinner-border { + to { + transform: rotate(360deg); + } +} +.spinner-border { + --bs-spinner-width: 2rem; + --bs-spinner-height: 2rem; + --bs-spinner-vertical-align: -0.125em; + --bs-spinner-border-width: 0.25em; + --bs-spinner-animation-speed: 0.75s; + --bs-spinner-animation-name: spinner-border; + border: var(--bs-spinner-border-width) solid currentcolor; + border-right-color: transparent; +} +.spinner-border-sm { + --bs-spinner-width: 1rem; + --bs-spinner-height: 1rem; + --bs-spinner-border-width: 0.2em; +} +@keyframes spinner-grow { + 0% { + transform: scale(0); + } + 50% { + opacity: 1; + transform: none; + } +} +.spinner-grow { + --bs-spinner-width: 2rem; + --bs-spinner-height: 2rem; + --bs-spinner-vertical-align: -0.125em; + --bs-spinner-animation-speed: 0.75s; + --bs-spinner-animation-name: spinner-grow; + background-color: currentcolor; + opacity: 0; +} +.spinner-grow-sm { + --bs-spinner-width: 1rem; + --bs-spinner-height: 1rem; +} +@media (prefers-reduced-motion: reduce) { + .spinner-border, + .spinner-grow { + --bs-spinner-animation-speed: 1.5s; + } +} +.offcanvas, +.offcanvas-lg, +.offcanvas-md, +.offcanvas-sm, +.offcanvas-xl, +.offcanvas-xxl { + --bs-offcanvas-zindex: 1045; + --bs-offcanvas-width: 400px; + --bs-offcanvas-height: 30vh; + --bs-offcanvas-padding-x: 1rem; + --bs-offcanvas-padding-y: 1rem; + --bs-offcanvas-color: var(--bs-body-color); + --bs-offcanvas-bg: var(--bs-body-bg); + --bs-offcanvas-border-width: var(--bs-border-width); + --bs-offcanvas-border-color: var(--bs-border-color-translucent); + --bs-offcanvas-box-shadow: var(--bs-box-shadow-sm); + --bs-offcanvas-transition: transform 0.3s ease-in-out; + --bs-offcanvas-title-line-height: 1.5; +} +@media (max-width: 575.98px) { + .offcanvas-sm { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: var(--bs-offcanvas-transition); + } +} +@media (max-width: 575.98px) and (prefers-reduced-motion: reduce) { + .offcanvas-sm { + transition: none; + } +} +@media (max-width: 575.98px) { + .offcanvas-sm.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } + .offcanvas-sm.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(100%); + } + .offcanvas-sm.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } + .offcanvas-sm.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(100%); + } + .offcanvas-sm.show:not(.hiding), + .offcanvas-sm.showing { + transform: none; + } + .offcanvas-sm.hiding, + .offcanvas-sm.show, + .offcanvas-sm.showing { + visibility: visible; + } +} +@media (min-width: 576px) { + .offcanvas-sm { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-sm .offcanvas-header { + display: none; + } + .offcanvas-sm .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} +@media (max-width: 767.98px) { + .offcanvas-md { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: var(--bs-offcanvas-transition); + } +} +@media (max-width: 767.98px) and (prefers-reduced-motion: reduce) { + .offcanvas-md { + transition: none; + } +} +@media (max-width: 767.98px) { + .offcanvas-md.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } + .offcanvas-md.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(100%); + } + .offcanvas-md.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } + .offcanvas-md.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(100%); + } + .offcanvas-md.show:not(.hiding), + .offcanvas-md.showing { + transform: none; + } + .offcanvas-md.hiding, + .offcanvas-md.show, + .offcanvas-md.showing { + visibility: visible; + } +} +@media (min-width: 768px) { + .offcanvas-md { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-md .offcanvas-header { + display: none; + } + .offcanvas-md .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} +@media (max-width: 991.98px) { + .offcanvas-lg { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: var(--bs-offcanvas-transition); + } +} +@media (max-width: 991.98px) and (prefers-reduced-motion: reduce) { + .offcanvas-lg { + transition: none; + } +} +@media (max-width: 991.98px) { + .offcanvas-lg.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } + .offcanvas-lg.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(100%); + } + .offcanvas-lg.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } + .offcanvas-lg.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(100%); + } + .offcanvas-lg.show:not(.hiding), + .offcanvas-lg.showing { + transform: none; + } + .offcanvas-lg.hiding, + .offcanvas-lg.show, + .offcanvas-lg.showing { + visibility: visible; + } +} +@media (min-width: 992px) { + .offcanvas-lg { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-lg .offcanvas-header { + display: none; + } + .offcanvas-lg .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} +@media (max-width: 1199.98px) { + .offcanvas-xl { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: var(--bs-offcanvas-transition); + } +} +@media (max-width: 1199.98px) and (prefers-reduced-motion: reduce) { + .offcanvas-xl { + transition: none; + } +} +@media (max-width: 1199.98px) { + .offcanvas-xl.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } + .offcanvas-xl.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(100%); + } + .offcanvas-xl.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } + .offcanvas-xl.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(100%); + } + .offcanvas-xl.show:not(.hiding), + .offcanvas-xl.showing { + transform: none; + } + .offcanvas-xl.hiding, + .offcanvas-xl.show, + .offcanvas-xl.showing { + visibility: visible; + } +} +@media (min-width: 1200px) { + .offcanvas-xl { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-xl .offcanvas-header { + display: none; + } + .offcanvas-xl .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} +@media (max-width: 1399.98px) { + .offcanvas-xxl { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: var(--bs-offcanvas-transition); + } +} +@media (max-width: 1399.98px) and (prefers-reduced-motion: reduce) { + .offcanvas-xxl { + transition: none; + } +} +@media (max-width: 1399.98px) { + .offcanvas-xxl.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } + .offcanvas-xxl.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(100%); + } + .offcanvas-xxl.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } + .offcanvas-xxl.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(100%); + } + .offcanvas-xxl.show:not(.hiding), + .offcanvas-xxl.showing { + transform: none; + } + .offcanvas-xxl.hiding, + .offcanvas-xxl.show, + .offcanvas-xxl.showing { + visibility: visible; + } +} +@media (min-width: 1400px) { + .offcanvas-xxl { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-xxl .offcanvas-header { + display: none; + } + .offcanvas-xxl .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} +.offcanvas { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + transition: var(--bs-offcanvas-transition); +} +@media (prefers-reduced-motion: reduce) { + .offcanvas { + transition: none; + } +} +.offcanvas.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(-100%); +} +.offcanvas.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateX(100%); +} +.offcanvas.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(-100%); +} +.offcanvas.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid + var(--bs-offcanvas-border-color); + transform: translateY(100%); +} +.offcanvas.show:not(.hiding), +.offcanvas.showing { + transform: none; +} +.offcanvas.hiding, +.offcanvas.show, +.offcanvas.showing { + visibility: visible; +} +.offcanvas-backdrop { + position: fixed; + top: 0; + left: 0; + z-index: 1040; + width: 100vw; + height: 100vh; + background-color: #000; +} +.offcanvas-backdrop.fade { + opacity: 0; +} +.offcanvas-backdrop.show { + opacity: 0.5; +} +.offcanvas-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--bs-offcanvas-padding-y) + var(--bs-offcanvas-padding-x); +} +.offcanvas-header .btn-close { + padding: calc(var(--bs-offcanvas-padding-y) * 0.5) + calc(var(--bs-offcanvas-padding-x) * 0.5); + margin-top: calc(-0.5 * var(--bs-offcanvas-padding-y)); + margin-right: calc(-0.5 * var(--bs-offcanvas-padding-x)); + margin-bottom: calc(-0.5 * var(--bs-offcanvas-padding-y)); +} +.offcanvas-title { + margin-bottom: 0; + line-height: var(--bs-offcanvas-title-line-height); +} +.offcanvas-body { + flex-grow: 1; + padding: var(--bs-offcanvas-padding-y) + var(--bs-offcanvas-padding-x); + overflow-y: auto; +} +.placeholder { + display: inline-block; + min-height: 1em; + vertical-align: middle; + cursor: wait; + background-color: currentcolor; + opacity: 0.5; +} +.placeholder.btn::before { + display: inline-block; + content: ""; +} +.placeholder-xs { + min-height: 0.6em; +} +.placeholder-sm { + min-height: 0.8em; +} +.placeholder-lg { + min-height: 1.2em; +} +.placeholder-glow .placeholder { + animation: placeholder-glow 2s ease-in-out infinite; +} +@keyframes placeholder-glow { + 50% { + opacity: 0.2; + } +} +.placeholder-wave { + -webkit-mask-image: linear-gradient( + 130deg, + #000 55%, + rgba(0, 0, 0, 0.8) 75%, + #000 95% + ); + mask-image: linear-gradient( + 130deg, + #000 55%, + rgba(0, 0, 0, 0.8) 75%, + #000 95% + ); + -webkit-mask-size: 200% 100%; + mask-size: 200% 100%; + animation: placeholder-wave 2s linear infinite; +} +@keyframes placeholder-wave { + 100% { + -webkit-mask-position: -200% 0%; + mask-position: -200% 0%; + } +} +.clearfix::after { + display: block; + clear: both; + content: ""; +} +.text-bg-primary { + color: #fff !important; + background-color: RGBA( + var(--bs-primary-rgb), + var(--bs-bg-opacity, 1) + ) !important; +} +.text-bg-secondary { + color: #fff !important; + background-color: RGBA( + var(--bs-secondary-rgb), + var(--bs-bg-opacity, 1) + ) !important; +} +.text-bg-success { + color: #fff !important; + background-color: RGBA( + var(--bs-success-rgb), + var(--bs-bg-opacity, 1) + ) !important; +} +.text-bg-info { + color: #000 !important; + background-color: RGBA( + var(--bs-info-rgb), + var(--bs-bg-opacity, 1) + ) !important; +} +.text-bg-warning { + color: #000 !important; + background-color: RGBA( + var(--bs-warning-rgb), + var(--bs-bg-opacity, 1) + ) !important; +} +.text-bg-danger { + color: #fff !important; + background-color: RGBA( + var(--bs-danger-rgb), + var(--bs-bg-opacity, 1) + ) !important; +} +.text-bg-light { + color: #000 !important; + background-color: RGBA( + var(--bs-light-rgb), + var(--bs-bg-opacity, 1) + ) !important; +} +.text-bg-dark { + color: #fff !important; + background-color: RGBA( + var(--bs-dark-rgb), + var(--bs-bg-opacity, 1) + ) !important; +} +.link-primary { + color: RGBA( + var(--bs-primary-rgb), + var(--bs-link-opacity, 1) + ) !important; + -webkit-text-decoration-color: RGBA( + var(--bs-primary-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + var(--bs-primary-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-primary:focus, +.link-primary:hover { + color: RGBA(10, 88, 202, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + 10, + 88, + 202, + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + 10, + 88, + 202, + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-secondary { + color: RGBA( + var(--bs-secondary-rgb), + var(--bs-link-opacity, 1) + ) !important; + -webkit-text-decoration-color: RGBA( + var(--bs-secondary-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + var(--bs-secondary-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-secondary:focus, +.link-secondary:hover { + color: RGBA(86, 94, 100, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + 86, + 94, + 100, + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + 86, + 94, + 100, + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-success { + color: RGBA( + var(--bs-success-rgb), + var(--bs-link-opacity, 1) + ) !important; + -webkit-text-decoration-color: RGBA( + var(--bs-success-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + var(--bs-success-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-success:focus, +.link-success:hover { + color: RGBA(20, 108, 67, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + 20, + 108, + 67, + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + 20, + 108, + 67, + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-info { + color: RGBA( + var(--bs-info-rgb), + var(--bs-link-opacity, 1) + ) !important; + -webkit-text-decoration-color: RGBA( + var(--bs-info-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + var(--bs-info-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-info:focus, +.link-info:hover { + color: RGBA(61, 213, 243, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + 61, + 213, + 243, + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + 61, + 213, + 243, + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-warning { + color: RGBA( + var(--bs-warning-rgb), + var(--bs-link-opacity, 1) + ) !important; + -webkit-text-decoration-color: RGBA( + var(--bs-warning-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + var(--bs-warning-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-warning:focus, +.link-warning:hover { + color: RGBA(255, 205, 57, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + 255, + 205, + 57, + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + 255, + 205, + 57, + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-danger { + color: RGBA( + var(--bs-danger-rgb), + var(--bs-link-opacity, 1) + ) !important; + -webkit-text-decoration-color: RGBA( + var(--bs-danger-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + var(--bs-danger-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-danger:focus, +.link-danger:hover { + color: RGBA(176, 42, 55, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + 176, + 42, + 55, + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + 176, + 42, + 55, + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-light { + color: RGBA( + var(--bs-light-rgb), + var(--bs-link-opacity, 1) + ) !important; + -webkit-text-decoration-color: RGBA( + var(--bs-light-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + var(--bs-light-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-light:focus, +.link-light:hover { + color: RGBA( + 249, + 250, + 251, + var(--bs-link-opacity, 1) + ) !important; + -webkit-text-decoration-color: RGBA( + 249, + 250, + 251, + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + 249, + 250, + 251, + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-dark { + color: RGBA( + var(--bs-dark-rgb), + var(--bs-link-opacity, 1) + ) !important; + -webkit-text-decoration-color: RGBA( + var(--bs-dark-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + var(--bs-dark-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-dark:focus, +.link-dark:hover { + color: RGBA(26, 30, 33, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA( + 26, + 30, + 33, + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + 26, + 30, + 33, + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-body-emphasis { + color: RGBA( + var(--bs-emphasis-color-rgb), + var(--bs-link-opacity, 1) + ) !important; + -webkit-text-decoration-color: RGBA( + var(--bs-emphasis-color-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: RGBA( + var(--bs-emphasis-color-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-body-emphasis:focus, +.link-body-emphasis:hover { + color: RGBA( + var(--bs-emphasis-color-rgb), + var(--bs-link-opacity, 0.75) + ) !important; + -webkit-text-decoration-color: RGBA( + var(--bs-emphasis-color-rgb), + var(--bs-link-underline-opacity, 0.75) + ) !important; + text-decoration-color: RGBA( + var(--bs-emphasis-color-rgb), + var(--bs-link-underline-opacity, 0.75) + ) !important; +} +.focus-ring:focus { + outline: 0; + box-shadow: var(--bs-focus-ring-x, 0) var(--bs-focus-ring-y, 0) + var(--bs-focus-ring-blur, 0) var(--bs-focus-ring-width) + var(--bs-focus-ring-color); +} +.icon-link { + display: inline-flex; + gap: 0.375rem; + align-items: center; + -webkit-text-decoration-color: rgba( + var(--bs-link-color-rgb), + var(--bs-link-opacity, 0.5) + ); + text-decoration-color: rgba( + var(--bs-link-color-rgb), + var(--bs-link-opacity, 0.5) + ); + text-underline-offset: 0.25em; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; +} +.icon-link > .bi { + flex-shrink: 0; + width: 1em; + height: 1em; + fill: currentcolor; + transition: 0.2s ease-in-out transform; +} +@media (prefers-reduced-motion: reduce) { + .icon-link > .bi { + transition: none; + } +} +.icon-link-hover:focus-visible > .bi, +.icon-link-hover:hover > .bi { + transform: var( + --bs-icon-link-transform, + translate3d(0.25em, 0, 0) + ); +} +.ratio { + position: relative; + width: 100%; +} +.ratio::before { + display: block; + padding-top: var(--bs-aspect-ratio); + content: ""; +} +.ratio > * { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} +.ratio-1x1 { + --bs-aspect-ratio: 100%; +} +.ratio-4x3 { + --bs-aspect-ratio: 75%; +} +.ratio-16x9 { + --bs-aspect-ratio: 56.25%; +} +.ratio-21x9 { + --bs-aspect-ratio: 42.8571428571%; +} +.fixed-top { + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: 1030; +} +.fixed-bottom { + position: fixed; + right: 0; + bottom: 0; + left: 0; + z-index: 1030; +} +.sticky-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; +} +.sticky-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; +} +@media (min-width: 576px) { + .sticky-sm-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } + .sticky-sm-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; + } +} +@media (min-width: 768px) { + .sticky-md-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } + .sticky-md-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; + } +} +@media (min-width: 992px) { + .sticky-lg-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } + .sticky-lg-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; + } +} +@media (min-width: 1200px) { + .sticky-xl-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } + .sticky-xl-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; + } +} +@media (min-width: 1400px) { + .sticky-xxl-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } + .sticky-xxl-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; + } +} +.hstack { + display: flex; + flex-direction: row; + align-items: center; + align-self: stretch; +} +.vstack { + display: flex; + flex: 1 1 auto; + flex-direction: column; + align-self: stretch; +} +.visually-hidden, +.visually-hidden-focusable:not(:focus):not(:focus-within) { + width: 1px !important; + height: 1px !important; + padding: 0 !important; + margin: -1px !important; + overflow: hidden !important; + clip: rect(0, 0, 0, 0) !important; + white-space: nowrap !important; + border: 0 !important; +} +.visually-hidden-focusable:not(:focus):not(:focus-within):not( + caption + ), +.visually-hidden:not(caption) { + position: absolute !important; +} +.stretched-link::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1; + content: ""; +} +.text-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.vr { + display: inline-block; + align-self: stretch; + width: var(--bs-border-width); + min-height: 1em; + background-color: currentcolor; + opacity: 0.25; +} +.align-baseline { + vertical-align: baseline !important; +} +.align-top { + vertical-align: top !important; +} +.align-middle { + vertical-align: middle !important; +} +.align-bottom { + vertical-align: bottom !important; +} +.align-text-bottom { + vertical-align: text-bottom !important; +} +.align-text-top { + vertical-align: text-top !important; +} +.float-start { + float: left !important; +} +.float-end { + float: right !important; +} +.float-none { + float: none !important; +} +.object-fit-contain { + -o-object-fit: contain !important; + object-fit: contain !important; +} +.object-fit-cover { + -o-object-fit: cover !important; + object-fit: cover !important; +} +.object-fit-fill { + -o-object-fit: fill !important; + object-fit: fill !important; +} +.object-fit-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; +} +.object-fit-none { + -o-object-fit: none !important; + object-fit: none !important; +} +.opacity-0 { + opacity: 0 !important; +} +.opacity-25 { + opacity: 0.25 !important; +} +.opacity-50 { + opacity: 0.5 !important; +} +.opacity-75 { + opacity: 0.75 !important; +} +.opacity-100 { + opacity: 1 !important; +} +.overflow-auto { + overflow: auto !important; +} +.overflow-hidden { + overflow: hidden !important; +} +.overflow-visible { + overflow: visible !important; +} +.overflow-scroll { + overflow: scroll !important; +} +.overflow-x-auto { + overflow-x: auto !important; +} +.overflow-x-hidden { + overflow-x: hidden !important; +} +.overflow-x-visible { + overflow-x: visible !important; +} +.overflow-x-scroll { + overflow-x: scroll !important; +} +.overflow-y-auto { + overflow-y: auto !important; +} +.overflow-y-hidden { + overflow-y: hidden !important; +} +.overflow-y-visible { + overflow-y: visible !important; +} +.overflow-y-scroll { + overflow-y: scroll !important; +} +.d-inline { + display: inline !important; +} +.d-inline-block { + display: inline-block !important; +} +.d-block { + display: block !important; +} +.d-grid { + display: grid !important; +} +.d-inline-grid { + display: inline-grid !important; +} +.d-table { + display: table !important; +} +.d-table-row { + display: table-row !important; +} +.d-table-cell { + display: table-cell !important; +} +.d-flex { + display: flex !important; +} +.d-inline-flex { + display: inline-flex !important; +} +.d-none { + display: none !important; +} +.shadow { + box-shadow: var(--bs-box-shadow) !important; +} +.shadow-sm { + box-shadow: var(--bs-box-shadow-sm) !important; +} +.shadow-lg { + box-shadow: var(--bs-box-shadow-lg) !important; +} +.shadow-none { + box-shadow: none !important; +} +.focus-ring-primary { + --bs-focus-ring-color: rgba( + var(--bs-primary-rgb), + var(--bs-focus-ring-opacity) + ); +} +.focus-ring-secondary { + --bs-focus-ring-color: rgba( + var(--bs-secondary-rgb), + var(--bs-focus-ring-opacity) + ); +} +.focus-ring-success { + --bs-focus-ring-color: rgba( + var(--bs-success-rgb), + var(--bs-focus-ring-opacity) + ); +} +.focus-ring-info { + --bs-focus-ring-color: rgba( + var(--bs-info-rgb), + var(--bs-focus-ring-opacity) + ); +} +.focus-ring-warning { + --bs-focus-ring-color: rgba( + var(--bs-warning-rgb), + var(--bs-focus-ring-opacity) + ); +} +.focus-ring-danger { + --bs-focus-ring-color: rgba( + var(--bs-danger-rgb), + var(--bs-focus-ring-opacity) + ); +} +.focus-ring-light { + --bs-focus-ring-color: rgba( + var(--bs-light-rgb), + var(--bs-focus-ring-opacity) + ); +} +.focus-ring-dark { + --bs-focus-ring-color: rgba( + var(--bs-dark-rgb), + var(--bs-focus-ring-opacity) + ); +} +.position-static { + position: static !important; +} +.position-relative { + position: relative !important; +} +.position-absolute { + position: absolute !important; +} +.position-fixed { + position: fixed !important; +} +.position-sticky { + position: -webkit-sticky !important; + position: sticky !important; +} +.top-0 { + top: 0 !important; +} +.top-50 { + top: 50% !important; +} +.top-100 { + top: 100% !important; +} +.bottom-0 { + bottom: 0 !important; +} +.bottom-50 { + bottom: 50% !important; +} +.bottom-100 { + bottom: 100% !important; +} +.start-0 { + left: 0 !important; +} +.start-50 { + left: 50% !important; +} +.start-100 { + left: 100% !important; +} +.end-0 { + right: 0 !important; +} +.end-50 { + right: 50% !important; +} +.end-100 { + right: 100% !important; +} +.translate-middle { + transform: translate(-50%, -50%) !important; +} +.translate-middle-x { + transform: translateX(-50%) !important; +} +.translate-middle-y { + transform: translateY(-50%) !important; +} +.border { + border: var(--bs-border-width) var(--bs-border-style) + var(--bs-border-color) !important; +} +.border-0 { + border: 0 !important; +} +.border-top { + border-top: var(--bs-border-width) var(--bs-border-style) + var(--bs-border-color) !important; +} +.border-top-0 { + border-top: 0 !important; +} +.border-end { + border-right: var(--bs-border-width) var(--bs-border-style) + var(--bs-border-color) !important; +} +.border-end-0 { + border-right: 0 !important; +} +.border-bottom { + border-bottom: var(--bs-border-width) var(--bs-border-style) + var(--bs-border-color) !important; +} +.border-bottom-0 { + border-bottom: 0 !important; +} +.border-start { + border-left: var(--bs-border-width) var(--bs-border-style) + var(--bs-border-color) !important; +} +.border-start-0 { + border-left: 0 !important; +} +.border-primary { + --bs-border-opacity: 1; + border-color: rgba( + var(--bs-primary-rgb), + var(--bs-border-opacity) + ) !important; +} +.border-secondary { + --bs-border-opacity: 1; + border-color: rgba( + var(--bs-secondary-rgb), + var(--bs-border-opacity) + ) !important; +} +.border-success { + --bs-border-opacity: 1; + border-color: rgba( + var(--bs-success-rgb), + var(--bs-border-opacity) + ) !important; +} +.border-info { + --bs-border-opacity: 1; + border-color: rgba( + var(--bs-info-rgb), + var(--bs-border-opacity) + ) !important; +} +.border-warning { + --bs-border-opacity: 1; + border-color: rgba( + var(--bs-warning-rgb), + var(--bs-border-opacity) + ) !important; +} +.border-danger { + --bs-border-opacity: 1; + border-color: rgba( + var(--bs-danger-rgb), + var(--bs-border-opacity) + ) !important; +} +.border-light { + --bs-border-opacity: 1; + border-color: rgba( + var(--bs-light-rgb), + var(--bs-border-opacity) + ) !important; +} +.border-dark { + --bs-border-opacity: 1; + border-color: rgba( + var(--bs-dark-rgb), + var(--bs-border-opacity) + ) !important; +} +.border-black { + --bs-border-opacity: 1; + border-color: rgba( + var(--bs-black-rgb), + var(--bs-border-opacity) + ) !important; +} +.border-white { + --bs-border-opacity: 1; + border-color: rgba( + var(--bs-white-rgb), + var(--bs-border-opacity) + ) !important; +} +.border-primary-subtle { + border-color: var(--bs-primary-border-subtle) !important; +} +.border-secondary-subtle { + border-color: var(--bs-secondary-border-subtle) !important; +} +.border-success-subtle { + border-color: var(--bs-success-border-subtle) !important; +} +.border-info-subtle { + border-color: var(--bs-info-border-subtle) !important; +} +.border-warning-subtle { + border-color: var(--bs-warning-border-subtle) !important; +} +.border-danger-subtle { + border-color: var(--bs-danger-border-subtle) !important; +} +.border-light-subtle { + border-color: var(--bs-light-border-subtle) !important; +} +.border-dark-subtle { + border-color: var(--bs-dark-border-subtle) !important; +} +.border-1 { + border-width: 1px !important; +} +.border-2 { + border-width: 2px !important; +} +.border-3 { + border-width: 3px !important; +} +.border-4 { + border-width: 4px !important; +} +.border-5 { + border-width: 5px !important; +} +.border-opacity-10 { + --bs-border-opacity: 0.1; +} +.border-opacity-25 { + --bs-border-opacity: 0.25; +} +.border-opacity-50 { + --bs-border-opacity: 0.5; +} +.border-opacity-75 { + --bs-border-opacity: 0.75; +} +.border-opacity-100 { + --bs-border-opacity: 1; +} +.w-25 { + width: 25% !important; +} +.w-50 { + width: 50% !important; +} +.w-75 { + width: 75% !important; +} +.w-100 { + width: 100% !important; +} +.w-auto { + width: auto !important; +} +.mw-100 { + max-width: 100% !important; +} +.vw-100 { + width: 100vw !important; +} +.min-vw-100 { + min-width: 100vw !important; +} +.h-25 { + height: 25% !important; +} +.h-50 { + height: 50% !important; +} +.h-75 { + height: 75% !important; +} +.h-100 { + height: 100% !important; +} +.h-auto { + height: auto !important; +} +.mh-100 { + max-height: 100% !important; +} +.vh-100 { + height: 100vh !important; +} +.min-vh-100 { + min-height: 100vh !important; +} +.flex-fill { + flex: 1 1 auto !important; +} +.flex-row { + flex-direction: row !important; +} +.flex-column { + flex-direction: column !important; +} +.flex-row-reverse { + flex-direction: row-reverse !important; +} +.flex-column-reverse { + flex-direction: column-reverse !important; +} +.flex-grow-0 { + flex-grow: 0 !important; +} +.flex-grow-1 { + flex-grow: 1 !important; +} +.flex-shrink-0 { + flex-shrink: 0 !important; +} +.flex-shrink-1 { + flex-shrink: 1 !important; +} +.flex-wrap { + flex-wrap: wrap !important; +} +.flex-nowrap { + flex-wrap: nowrap !important; +} +.flex-wrap-reverse { + flex-wrap: wrap-reverse !important; +} +.justify-content-start { + justify-content: flex-start !important; +} +.justify-content-end { + justify-content: flex-end !important; +} +.justify-content-center { + justify-content: center !important; +} +.justify-content-between { + justify-content: space-between !important; +} +.justify-content-around { + justify-content: space-around !important; +} +.justify-content-evenly { + justify-content: space-evenly !important; +} +.align-items-start { + align-items: flex-start !important; +} +.align-items-end { + align-items: flex-end !important; +} +.align-items-center { + align-items: center !important; +} +.align-items-baseline { + align-items: baseline !important; +} +.align-items-stretch { + align-items: stretch !important; +} +.align-content-start { + align-content: flex-start !important; +} +.align-content-end { + align-content: flex-end !important; +} +.align-content-center { + align-content: center !important; +} +.align-content-between { + align-content: space-between !important; +} +.align-content-around { + align-content: space-around !important; +} +.align-content-stretch { + align-content: stretch !important; +} +.align-self-auto { + align-self: auto !important; +} +.align-self-start { + align-self: flex-start !important; +} +.align-self-end { + align-self: flex-end !important; +} +.align-self-center { + align-self: center !important; +} +.align-self-baseline { + align-self: baseline !important; +} +.align-self-stretch { + align-self: stretch !important; +} +.order-first { + order: -1 !important; +} +.order-0 { + order: 0 !important; +} +.order-1 { + order: 1 !important; +} +.order-2 { + order: 2 !important; +} +.order-3 { + order: 3 !important; +} +.order-4 { + order: 4 !important; +} +.order-5 { + order: 5 !important; +} +.order-last { + order: 6 !important; +} +.m-0 { + margin: 0 !important; +} +.m-1 { + margin: 0.25rem !important; +} +.m-2 { + margin: 0.5rem !important; +} +.m-3 { + margin: 1rem !important; +} +.m-4 { + margin: 1.5rem !important; +} +.m-5 { + margin: 3rem !important; +} +.m-auto { + margin: auto !important; +} +.mx-0 { + margin-right: 0 !important; + margin-left: 0 !important; +} +.mx-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; +} +.mx-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; +} +.mx-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; +} +.mx-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; +} +.mx-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; +} +.mx-auto { + margin-right: auto !important; + margin-left: auto !important; +} +.my-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; +} +.my-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; +} +.my-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; +} +.my-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; +} +.my-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; +} +.my-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; +} +.my-auto { + margin-top: auto !important; + margin-bottom: auto !important; +} +.mt-0 { + margin-top: 0 !important; +} +.mt-1 { + margin-top: 0.25rem !important; +} +.mt-2 { + margin-top: 0.5rem !important; +} +.mt-3 { + margin-top: 1rem !important; +} +.mt-4 { + margin-top: 1.5rem !important; +} +.mt-5 { + margin-top: 3rem !important; +} +.mt-auto { + margin-top: auto !important; +} +.me-0 { + margin-right: 0 !important; +} +.me-1 { + margin-right: 0.25rem !important; +} +.me-2 { + margin-right: 0.5rem !important; +} +.me-3 { + margin-right: 1rem !important; +} +.me-4 { + margin-right: 1.5rem !important; +} +.me-5 { + margin-right: 3rem !important; +} +.me-auto { + margin-right: auto !important; +} +.mb-0 { + margin-bottom: 0 !important; +} +.mb-1 { + margin-bottom: 0.25rem !important; +} +.mb-2 { + margin-bottom: 0.5rem !important; +} +.mb-3 { + margin-bottom: 1rem !important; +} +.mb-4 { + margin-bottom: 1.5rem !important; +} +.mb-5 { + margin-bottom: 3rem !important; +} +.mb-auto { + margin-bottom: auto !important; +} +.ms-0 { + margin-left: 0 !important; +} +.ms-1 { + margin-left: 0.25rem !important; +} +.ms-2 { + margin-left: 0.5rem !important; +} +.ms-3 { + margin-left: 1rem !important; +} +.ms-4 { + margin-left: 1.5rem !important; +} +.ms-5 { + margin-left: 3rem !important; +} +.ms-auto { + margin-left: auto !important; +} +.p-0 { + padding: 0 !important; +} +.p-1 { + padding: 0.25rem !important; +} +.p-2 { + padding: 0.5rem !important; +} +.p-3 { + padding: 1rem !important; +} +.p-4 { + padding: 1.5rem !important; +} +.p-5 { + padding: 3rem !important; +} +.px-0 { + padding-right: 0 !important; + padding-left: 0 !important; +} +.px-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; +} +.px-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; +} +.px-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; +} +.px-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; +} +.px-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; +} +.py-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; +} +.py-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; +} +.py-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; +} +.py-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; +} +.py-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; +} +.py-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; +} +.pt-0 { + padding-top: 0 !important; +} +.pt-1 { + padding-top: 0.25rem !important; +} +.pt-2 { + padding-top: 0.5rem !important; +} +.pt-3 { + padding-top: 1rem !important; +} +.pt-4 { + padding-top: 1.5rem !important; +} +.pt-5 { + padding-top: 3rem !important; +} +.pe-0 { + padding-right: 0 !important; +} +.pe-1 { + padding-right: 0.25rem !important; +} +.pe-2 { + padding-right: 0.5rem !important; +} +.pe-3 { + padding-right: 1rem !important; +} +.pe-4 { + padding-right: 1.5rem !important; +} +.pe-5 { + padding-right: 3rem !important; +} +.pb-0 { + padding-bottom: 0 !important; +} +.pb-1 { + padding-bottom: 0.25rem !important; +} +.pb-2 { + padding-bottom: 0.5rem !important; +} +.pb-3 { + padding-bottom: 1rem !important; +} +.pb-4 { + padding-bottom: 1.5rem !important; +} +.pb-5 { + padding-bottom: 3rem !important; +} +.ps-0 { + padding-left: 0 !important; +} +.ps-1 { + padding-left: 0.25rem !important; +} +.ps-2 { + padding-left: 0.5rem !important; +} +.ps-3 { + padding-left: 1rem !important; +} +.ps-4 { + padding-left: 1.5rem !important; +} +.ps-5 { + padding-left: 3rem !important; +} +.gap-0 { + gap: 0 !important; +} +.gap-1 { + gap: 0.25rem !important; +} +.gap-2 { + gap: 0.5rem !important; +} +.gap-3 { + gap: 1rem !important; +} +.gap-4 { + gap: 1.5rem !important; +} +.gap-5 { + gap: 3rem !important; +} +.row-gap-0 { + row-gap: 0 !important; +} +.row-gap-1 { + row-gap: 0.25rem !important; +} +.row-gap-2 { + row-gap: 0.5rem !important; +} +.row-gap-3 { + row-gap: 1rem !important; +} +.row-gap-4 { + row-gap: 1.5rem !important; +} +.row-gap-5 { + row-gap: 3rem !important; +} +.column-gap-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; +} +.column-gap-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; +} +.column-gap-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; +} +.column-gap-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; +} +.column-gap-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; +} +.column-gap-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; +} +.font-monospace { + font-family: var(--bs-font-monospace) !important; +} +.fs-1 { + font-size: calc(1.375rem + 1.5vw) !important; +} +.fs-2 { + font-size: calc(1.325rem + 0.9vw) !important; +} +.fs-3 { + font-size: calc(1.3rem + 0.6vw) !important; +} +.fs-4 { + font-size: calc(1.275rem + 0.3vw) !important; +} +.fs-5 { + font-size: 1.25rem !important; +} +.fs-6 { + font-size: 1rem !important; +} +.fst-italic { + font-style: italic !important; +} +.fst-normal { + font-style: normal !important; +} +.fw-lighter { + font-weight: lighter !important; +} +.fw-light { + font-weight: 300 !important; +} +.fw-normal { + font-weight: 400 !important; +} +.fw-medium { + font-weight: 500 !important; +} +.fw-semibold { + font-weight: 600 !important; +} +.fw-bold { + font-weight: 700 !important; +} +.fw-bolder { + font-weight: bolder !important; +} +.lh-1 { + line-height: 1 !important; +} +.lh-sm { + line-height: 1.25 !important; +} +.lh-base { + line-height: 1.5 !important; +} +.lh-lg { + line-height: 2 !important; +} +.text-start { + text-align: left !important; +} +.text-end { + text-align: right !important; +} +.text-center { + text-align: center !important; +} +.text-decoration-none { + text-decoration: none !important; +} +.text-decoration-underline { + text-decoration: underline !important; +} +.text-decoration-line-through { + text-decoration: line-through !important; +} +.text-lowercase { + text-transform: lowercase !important; +} +.text-uppercase { + text-transform: uppercase !important; +} +.text-capitalize { + text-transform: capitalize !important; +} +.text-wrap { + white-space: normal !important; +} +.text-nowrap { + white-space: nowrap !important; +} +.text-break { + word-wrap: break-word !important; + word-break: break-word !important; +} +.text-primary { + --bs-text-opacity: 1; + color: rgba( + var(--bs-primary-rgb), + var(--bs-text-opacity) + ) !important; +} +.text-secondary { + --bs-text-opacity: 1; + color: rgba( + var(--bs-secondary-rgb), + var(--bs-text-opacity) + ) !important; +} +.text-success { + --bs-text-opacity: 1; + color: rgba( + var(--bs-success-rgb), + var(--bs-text-opacity) + ) !important; +} +.text-info { + --bs-text-opacity: 1; + color: rgba( + var(--bs-info-rgb), + var(--bs-text-opacity) + ) !important; +} +.text-warning { + --bs-text-opacity: 1; + color: rgba( + var(--bs-warning-rgb), + var(--bs-text-opacity) + ) !important; +} +.text-danger { + --bs-text-opacity: 1; + color: rgba( + var(--bs-danger-rgb), + var(--bs-text-opacity) + ) !important; +} +.text-light { + --bs-text-opacity: 1; + color: rgba( + var(--bs-light-rgb), + var(--bs-text-opacity) + ) !important; +} +.text-dark { + --bs-text-opacity: 1; + color: rgba( + var(--bs-dark-rgb), + var(--bs-text-opacity) + ) !important; +} +.text-black { + --bs-text-opacity: 1; + color: rgba( + var(--bs-black-rgb), + var(--bs-text-opacity) + ) !important; +} +.text-white { + --bs-text-opacity: 1; + color: rgba( + var(--bs-white-rgb), + var(--bs-text-opacity) + ) !important; +} +.text-body { + --bs-text-opacity: 1; + color: rgba( + var(--bs-body-color-rgb), + var(--bs-text-opacity) + ) !important; +} +.text-muted { + --bs-text-opacity: 1; + color: var(--bs-secondary-color) !important; +} +.text-black-50 { + --bs-text-opacity: 1; + color: rgba(0, 0, 0, 0.5) !important; +} +.text-white-50 { + --bs-text-opacity: 1; + color: rgba(255, 255, 255, 0.5) !important; +} +.text-body-secondary { + --bs-text-opacity: 1; + color: var(--bs-secondary-color) !important; +} +.text-body-tertiary { + --bs-text-opacity: 1; + color: var(--bs-tertiary-color) !important; +} +.text-body-emphasis { + --bs-text-opacity: 1; + color: var(--bs-emphasis-color) !important; +} +.text-reset { + --bs-text-opacity: 1; + color: inherit !important; +} +.text-opacity-25 { + --bs-text-opacity: 0.25; +} +.text-opacity-50 { + --bs-text-opacity: 0.5; +} +.text-opacity-75 { + --bs-text-opacity: 0.75; +} +.text-opacity-100 { + --bs-text-opacity: 1; +} +.text-primary-emphasis { + color: var(--bs-primary-text-emphasis) !important; +} +.text-secondary-emphasis { + color: var(--bs-secondary-text-emphasis) !important; +} +.text-success-emphasis { + color: var(--bs-success-text-emphasis) !important; +} +.text-info-emphasis { + color: var(--bs-info-text-emphasis) !important; +} +.text-warning-emphasis { + color: var(--bs-warning-text-emphasis) !important; +} +.text-danger-emphasis { + color: var(--bs-danger-text-emphasis) !important; +} +.text-light-emphasis { + color: var(--bs-light-text-emphasis) !important; +} +.text-dark-emphasis { + color: var(--bs-dark-text-emphasis) !important; +} +.link-opacity-10 { + --bs-link-opacity: 0.1; +} +.link-opacity-10-hover:hover { + --bs-link-opacity: 0.1; +} +.link-opacity-25 { + --bs-link-opacity: 0.25; +} +.link-opacity-25-hover:hover { + --bs-link-opacity: 0.25; +} +.link-opacity-50 { + --bs-link-opacity: 0.5; +} +.link-opacity-50-hover:hover { + --bs-link-opacity: 0.5; +} +.link-opacity-75 { + --bs-link-opacity: 0.75; +} +.link-opacity-75-hover:hover { + --bs-link-opacity: 0.75; +} +.link-opacity-100 { + --bs-link-opacity: 1; +} +.link-opacity-100-hover:hover { + --bs-link-opacity: 1; +} +.link-offset-1 { + text-underline-offset: 0.125em !important; +} +.link-offset-1-hover:hover { + text-underline-offset: 0.125em !important; +} +.link-offset-2 { + text-underline-offset: 0.25em !important; +} +.link-offset-2-hover:hover { + text-underline-offset: 0.25em !important; +} +.link-offset-3 { + text-underline-offset: 0.375em !important; +} +.link-offset-3-hover:hover { + text-underline-offset: 0.375em !important; +} +.link-underline-primary { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba( + var(--bs-primary-rgb), + var(--bs-link-underline-opacity) + ) !important; + text-decoration-color: rgba( + var(--bs-primary-rgb), + var(--bs-link-underline-opacity) + ) !important; +} +.link-underline-secondary { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba( + var(--bs-secondary-rgb), + var(--bs-link-underline-opacity) + ) !important; + text-decoration-color: rgba( + var(--bs-secondary-rgb), + var(--bs-link-underline-opacity) + ) !important; +} +.link-underline-success { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba( + var(--bs-success-rgb), + var(--bs-link-underline-opacity) + ) !important; + text-decoration-color: rgba( + var(--bs-success-rgb), + var(--bs-link-underline-opacity) + ) !important; +} +.link-underline-info { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba( + var(--bs-info-rgb), + var(--bs-link-underline-opacity) + ) !important; + text-decoration-color: rgba( + var(--bs-info-rgb), + var(--bs-link-underline-opacity) + ) !important; +} +.link-underline-warning { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba( + var(--bs-warning-rgb), + var(--bs-link-underline-opacity) + ) !important; + text-decoration-color: rgba( + var(--bs-warning-rgb), + var(--bs-link-underline-opacity) + ) !important; +} +.link-underline-danger { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba( + var(--bs-danger-rgb), + var(--bs-link-underline-opacity) + ) !important; + text-decoration-color: rgba( + var(--bs-danger-rgb), + var(--bs-link-underline-opacity) + ) !important; +} +.link-underline-light { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba( + var(--bs-light-rgb), + var(--bs-link-underline-opacity) + ) !important; + text-decoration-color: rgba( + var(--bs-light-rgb), + var(--bs-link-underline-opacity) + ) !important; +} +.link-underline-dark { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba( + var(--bs-dark-rgb), + var(--bs-link-underline-opacity) + ) !important; + text-decoration-color: rgba( + var(--bs-dark-rgb), + var(--bs-link-underline-opacity) + ) !important; +} +.link-underline { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba( + var(--bs-link-color-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; + text-decoration-color: rgba( + var(--bs-link-color-rgb), + var(--bs-link-underline-opacity, 1) + ) !important; +} +.link-underline-opacity-0 { + --bs-link-underline-opacity: 0; +} +.link-underline-opacity-0-hover:hover { + --bs-link-underline-opacity: 0; +} +.link-underline-opacity-10 { + --bs-link-underline-opacity: 0.1; +} +.link-underline-opacity-10-hover:hover { + --bs-link-underline-opacity: 0.1; +} +.link-underline-opacity-25 { + --bs-link-underline-opacity: 0.25; +} +.link-underline-opacity-25-hover:hover { + --bs-link-underline-opacity: 0.25; +} +.link-underline-opacity-50 { + --bs-link-underline-opacity: 0.5; +} +.link-underline-opacity-50-hover:hover { + --bs-link-underline-opacity: 0.5; +} +.link-underline-opacity-75 { + --bs-link-underline-opacity: 0.75; +} +.link-underline-opacity-75-hover:hover { + --bs-link-underline-opacity: 0.75; +} +.link-underline-opacity-100 { + --bs-link-underline-opacity: 1; +} +.link-underline-opacity-100-hover:hover { + --bs-link-underline-opacity: 1; +} +.bg-primary { + --bs-bg-opacity: 1; + background-color: rgba( + var(--bs-primary-rgb), + var(--bs-bg-opacity) + ) !important; +} +.bg-secondary { + --bs-bg-opacity: 1; + background-color: rgba( + var(--bs-secondary-rgb), + var(--bs-bg-opacity) + ) !important; +} +.bg-success { + --bs-bg-opacity: 1; + background-color: rgba( + var(--bs-success-rgb), + var(--bs-bg-opacity) + ) !important; +} +.bg-info { + --bs-bg-opacity: 1; + background-color: rgba( + var(--bs-info-rgb), + var(--bs-bg-opacity) + ) !important; +} +.bg-warning { + --bs-bg-opacity: 1; + background-color: rgba( + var(--bs-warning-rgb), + var(--bs-bg-opacity) + ) !important; +} +.bg-danger { + --bs-bg-opacity: 1; + background-color: rgba( + var(--bs-danger-rgb), + var(--bs-bg-opacity) + ) !important; +} +.bg-light { + --bs-bg-opacity: 1; + background-color: rgba( + var(--bs-light-rgb), + var(--bs-bg-opacity) + ) !important; +} +.bg-dark { + --bs-bg-opacity: 1; + background-color: rgba( + var(--bs-dark-rgb), + var(--bs-bg-opacity) + ) !important; +} +.bg-black { + --bs-bg-opacity: 1; + background-color: rgba( + var(--bs-black-rgb), + var(--bs-bg-opacity) + ) !important; +} +.bg-white { + --bs-bg-opacity: 1; + background-color: rgba( + var(--bs-white-rgb), + var(--bs-bg-opacity) + ) !important; +} +.bg-body { + --bs-bg-opacity: 1; + background-color: rgba( + var(--bs-body-bg-rgb), + var(--bs-bg-opacity) + ) !important; +} +.bg-transparent { + --bs-bg-opacity: 1; + background-color: transparent !important; +} +.bg-body-secondary { + --bs-bg-opacity: 1; + background-color: rgba( + var(--bs-secondary-bg-rgb), + var(--bs-bg-opacity) + ) !important; +} +.bg-body-tertiary { + --bs-bg-opacity: 1; + background-color: rgba( + var(--bs-tertiary-bg-rgb), + var(--bs-bg-opacity) + ) !important; +} +.bg-opacity-10 { + --bs-bg-opacity: 0.1; +} +.bg-opacity-25 { + --bs-bg-opacity: 0.25; +} +.bg-opacity-50 { + --bs-bg-opacity: 0.5; +} +.bg-opacity-75 { + --bs-bg-opacity: 0.75; +} +.bg-opacity-100 { + --bs-bg-opacity: 1; +} +.bg-primary-subtle { + background-color: var(--bs-primary-bg-subtle) !important; +} +.bg-secondary-subtle { + background-color: var(--bs-secondary-bg-subtle) !important; +} +.bg-success-subtle { + background-color: var(--bs-success-bg-subtle) !important; +} +.bg-info-subtle { + background-color: var(--bs-info-bg-subtle) !important; +} +.bg-warning-subtle { + background-color: var(--bs-warning-bg-subtle) !important; +} +.bg-danger-subtle { + background-color: var(--bs-danger-bg-subtle) !important; +} +.bg-light-subtle { + background-color: var(--bs-light-bg-subtle) !important; +} +.bg-dark-subtle { + background-color: var(--bs-dark-bg-subtle) !important; +} +.bg-gradient { + background-image: var(--bs-gradient) !important; +} +.user-select-all { + -webkit-user-select: all !important; + -moz-user-select: all !important; + user-select: all !important; +} +.user-select-auto { + -webkit-user-select: auto !important; + -moz-user-select: auto !important; + user-select: auto !important; +} +.user-select-none { + -webkit-user-select: none !important; + -moz-user-select: none !important; + user-select: none !important; +} +.pe-none { + pointer-events: none !important; +} +.pe-auto { + pointer-events: auto !important; +} +.rounded { + border-radius: var(--bs-border-radius) !important; +} +.rounded-0 { + border-radius: 0 !important; +} +.rounded-1 { + border-radius: var(--bs-border-radius-sm) !important; +} +.rounded-2 { + border-radius: var(--bs-border-radius) !important; +} +.rounded-3 { + border-radius: var(--bs-border-radius-lg) !important; +} +.rounded-4 { + border-radius: var(--bs-border-radius-xl) !important; +} +.rounded-5 { + border-radius: var(--bs-border-radius-xxl) !important; +} +.rounded-circle { + border-radius: 50% !important; +} +.rounded-pill { + border-radius: var(--bs-border-radius-pill) !important; +} +.rounded-top { + border-top-left-radius: var(--bs-border-radius) !important; + border-top-right-radius: var(--bs-border-radius) !important; +} +.rounded-top-0 { + border-top-left-radius: 0 !important; + border-top-right-radius: 0 !important; +} +.rounded-top-1 { + border-top-left-radius: var(--bs-border-radius-sm) !important; + border-top-right-radius: var(--bs-border-radius-sm) !important; +} +.rounded-top-2 { + border-top-left-radius: var(--bs-border-radius) !important; + border-top-right-radius: var(--bs-border-radius) !important; +} +.rounded-top-3 { + border-top-left-radius: var(--bs-border-radius-lg) !important; + border-top-right-radius: var(--bs-border-radius-lg) !important; +} +.rounded-top-4 { + border-top-left-radius: var(--bs-border-radius-xl) !important; + border-top-right-radius: var(--bs-border-radius-xl) !important; +} +.rounded-top-5 { + border-top-left-radius: var(--bs-border-radius-xxl) !important; + border-top-right-radius: var(--bs-border-radius-xxl) !important; +} +.rounded-top-circle { + border-top-left-radius: 50% !important; + border-top-right-radius: 50% !important; +} +.rounded-top-pill { + border-top-left-radius: var(--bs-border-radius-pill) !important; + border-top-right-radius: var( + --bs-border-radius-pill + ) !important; +} +.rounded-end { + border-top-right-radius: var(--bs-border-radius) !important; + border-bottom-right-radius: var(--bs-border-radius) !important; +} +.rounded-end-0 { + border-top-right-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} +.rounded-end-1 { + border-top-right-radius: var(--bs-border-radius-sm) !important; + border-bottom-right-radius: var( + --bs-border-radius-sm + ) !important; +} +.rounded-end-2 { + border-top-right-radius: var(--bs-border-radius) !important; + border-bottom-right-radius: var(--bs-border-radius) !important; +} +.rounded-end-3 { + border-top-right-radius: var(--bs-border-radius-lg) !important; + border-bottom-right-radius: var( + --bs-border-radius-lg + ) !important; +} +.rounded-end-4 { + border-top-right-radius: var(--bs-border-radius-xl) !important; + border-bottom-right-radius: var( + --bs-border-radius-xl + ) !important; +} +.rounded-end-5 { + border-top-right-radius: var(--bs-border-radius-xxl) !important; + border-bottom-right-radius: var( + --bs-border-radius-xxl + ) !important; +} +.rounded-end-circle { + border-top-right-radius: 50% !important; + border-bottom-right-radius: 50% !important; +} +.rounded-end-pill { + border-top-right-radius: var( + --bs-border-radius-pill + ) !important; + border-bottom-right-radius: var( + --bs-border-radius-pill + ) !important; +} +.rounded-bottom { + border-bottom-right-radius: var(--bs-border-radius) !important; + border-bottom-left-radius: var(--bs-border-radius) !important; +} +.rounded-bottom-0 { + border-bottom-right-radius: 0 !important; + border-bottom-left-radius: 0 !important; +} +.rounded-bottom-1 { + border-bottom-right-radius: var( + --bs-border-radius-sm + ) !important; + border-bottom-left-radius: var( + --bs-border-radius-sm + ) !important; +} +.rounded-bottom-2 { + border-bottom-right-radius: var(--bs-border-radius) !important; + border-bottom-left-radius: var(--bs-border-radius) !important; +} +.rounded-bottom-3 { + border-bottom-right-radius: var( + --bs-border-radius-lg + ) !important; + border-bottom-left-radius: var( + --bs-border-radius-lg + ) !important; +} +.rounded-bottom-4 { + border-bottom-right-radius: var( + --bs-border-radius-xl + ) !important; + border-bottom-left-radius: var( + --bs-border-radius-xl + ) !important; +} +.rounded-bottom-5 { + border-bottom-right-radius: var( + --bs-border-radius-xxl + ) !important; + border-bottom-left-radius: var( + --bs-border-radius-xxl + ) !important; +} +.rounded-bottom-circle { + border-bottom-right-radius: 50% !important; + border-bottom-left-radius: 50% !important; +} +.rounded-bottom-pill { + border-bottom-right-radius: var( + --bs-border-radius-pill + ) !important; + border-bottom-left-radius: var( + --bs-border-radius-pill + ) !important; +} +.rounded-start { + border-bottom-left-radius: var(--bs-border-radius) !important; + border-top-left-radius: var(--bs-border-radius) !important; +} +.rounded-start-0 { + border-bottom-left-radius: 0 !important; + border-top-left-radius: 0 !important; +} +.rounded-start-1 { + border-bottom-left-radius: var( + --bs-border-radius-sm + ) !important; + border-top-left-radius: var(--bs-border-radius-sm) !important; +} +.rounded-start-2 { + border-bottom-left-radius: var(--bs-border-radius) !important; + border-top-left-radius: var(--bs-border-radius) !important; +} +.rounded-start-3 { + border-bottom-left-radius: var( + --bs-border-radius-lg + ) !important; + border-top-left-radius: var(--bs-border-radius-lg) !important; +} +.rounded-start-4 { + border-bottom-left-radius: var( + --bs-border-radius-xl + ) !important; + border-top-left-radius: var(--bs-border-radius-xl) !important; +} +.rounded-start-5 { + border-bottom-left-radius: var( + --bs-border-radius-xxl + ) !important; + border-top-left-radius: var(--bs-border-radius-xxl) !important; +} +.rounded-start-circle { + border-bottom-left-radius: 50% !important; + border-top-left-radius: 50% !important; +} +.rounded-start-pill { + border-bottom-left-radius: var( + --bs-border-radius-pill + ) !important; + border-top-left-radius: var(--bs-border-radius-pill) !important; +} +.visible { + visibility: visible !important; +} +.invisible { + visibility: hidden !important; +} +.z-n1 { + z-index: -1 !important; +} +.z-0 { + z-index: 0 !important; +} +.z-1 { + z-index: 1 !important; +} +.z-2 { + z-index: 2 !important; +} +.z-3 { + z-index: 3 !important; +} +@media (min-width: 576px) { + .float-sm-start { + float: left !important; + } + .float-sm-end { + float: right !important; + } + .float-sm-none { + float: none !important; + } + .object-fit-sm-contain { + -o-object-fit: contain !important; + object-fit: contain !important; + } + .object-fit-sm-cover { + -o-object-fit: cover !important; + object-fit: cover !important; + } + .object-fit-sm-fill { + -o-object-fit: fill !important; + object-fit: fill !important; + } + .object-fit-sm-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; + } + .object-fit-sm-none { + -o-object-fit: none !important; + object-fit: none !important; + } + .d-sm-inline { + display: inline !important; + } + .d-sm-inline-block { + display: inline-block !important; + } + .d-sm-block { + display: block !important; + } + .d-sm-grid { + display: grid !important; + } + .d-sm-inline-grid { + display: inline-grid !important; + } + .d-sm-table { + display: table !important; + } + .d-sm-table-row { + display: table-row !important; + } + .d-sm-table-cell { + display: table-cell !important; + } + .d-sm-flex { + display: flex !important; + } + .d-sm-inline-flex { + display: inline-flex !important; + } + .d-sm-none { + display: none !important; + } + .flex-sm-fill { + flex: 1 1 auto !important; + } + .flex-sm-row { + flex-direction: row !important; + } + .flex-sm-column { + flex-direction: column !important; + } + .flex-sm-row-reverse { + flex-direction: row-reverse !important; + } + .flex-sm-column-reverse { + flex-direction: column-reverse !important; + } + .flex-sm-grow-0 { + flex-grow: 0 !important; + } + .flex-sm-grow-1 { + flex-grow: 1 !important; + } + .flex-sm-shrink-0 { + flex-shrink: 0 !important; + } + .flex-sm-shrink-1 { + flex-shrink: 1 !important; + } + .flex-sm-wrap { + flex-wrap: wrap !important; + } + .flex-sm-nowrap { + flex-wrap: nowrap !important; + } + .flex-sm-wrap-reverse { + flex-wrap: wrap-reverse !important; + } + .justify-content-sm-start { + justify-content: flex-start !important; + } + .justify-content-sm-end { + justify-content: flex-end !important; + } + .justify-content-sm-center { + justify-content: center !important; + } + .justify-content-sm-between { + justify-content: space-between !important; + } + .justify-content-sm-around { + justify-content: space-around !important; + } + .justify-content-sm-evenly { + justify-content: space-evenly !important; + } + .align-items-sm-start { + align-items: flex-start !important; + } + .align-items-sm-end { + align-items: flex-end !important; + } + .align-items-sm-center { + align-items: center !important; + } + .align-items-sm-baseline { + align-items: baseline !important; + } + .align-items-sm-stretch { + align-items: stretch !important; + } + .align-content-sm-start { + align-content: flex-start !important; + } + .align-content-sm-end { + align-content: flex-end !important; + } + .align-content-sm-center { + align-content: center !important; + } + .align-content-sm-between { + align-content: space-between !important; + } + .align-content-sm-around { + align-content: space-around !important; + } + .align-content-sm-stretch { + align-content: stretch !important; + } + .align-self-sm-auto { + align-self: auto !important; + } + .align-self-sm-start { + align-self: flex-start !important; + } + .align-self-sm-end { + align-self: flex-end !important; + } + .align-self-sm-center { + align-self: center !important; + } + .align-self-sm-baseline { + align-self: baseline !important; + } + .align-self-sm-stretch { + align-self: stretch !important; + } + .order-sm-first { + order: -1 !important; + } + .order-sm-0 { + order: 0 !important; + } + .order-sm-1 { + order: 1 !important; + } + .order-sm-2 { + order: 2 !important; + } + .order-sm-3 { + order: 3 !important; + } + .order-sm-4 { + order: 4 !important; + } + .order-sm-5 { + order: 5 !important; + } + .order-sm-last { + order: 6 !important; + } + .m-sm-0 { + margin: 0 !important; + } + .m-sm-1 { + margin: 0.25rem !important; + } + .m-sm-2 { + margin: 0.5rem !important; + } + .m-sm-3 { + margin: 1rem !important; + } + .m-sm-4 { + margin: 1.5rem !important; + } + .m-sm-5 { + margin: 3rem !important; + } + .m-sm-auto { + margin: auto !important; + } + .mx-sm-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .mx-sm-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .mx-sm-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .mx-sm-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .mx-sm-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .mx-sm-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .mx-sm-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-sm-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .my-sm-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .my-sm-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .my-sm-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .my-sm-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .my-sm-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .my-sm-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } + .mt-sm-0 { + margin-top: 0 !important; + } + .mt-sm-1 { + margin-top: 0.25rem !important; + } + .mt-sm-2 { + margin-top: 0.5rem !important; + } + .mt-sm-3 { + margin-top: 1rem !important; + } + .mt-sm-4 { + margin-top: 1.5rem !important; + } + .mt-sm-5 { + margin-top: 3rem !important; + } + .mt-sm-auto { + margin-top: auto !important; + } + .me-sm-0 { + margin-right: 0 !important; + } + .me-sm-1 { + margin-right: 0.25rem !important; + } + .me-sm-2 { + margin-right: 0.5rem !important; + } + .me-sm-3 { + margin-right: 1rem !important; + } + .me-sm-4 { + margin-right: 1.5rem !important; + } + .me-sm-5 { + margin-right: 3rem !important; + } + .me-sm-auto { + margin-right: auto !important; + } + .mb-sm-0 { + margin-bottom: 0 !important; + } + .mb-sm-1 { + margin-bottom: 0.25rem !important; + } + .mb-sm-2 { + margin-bottom: 0.5rem !important; + } + .mb-sm-3 { + margin-bottom: 1rem !important; + } + .mb-sm-4 { + margin-bottom: 1.5rem !important; + } + .mb-sm-5 { + margin-bottom: 3rem !important; + } + .mb-sm-auto { + margin-bottom: auto !important; + } + .ms-sm-0 { + margin-left: 0 !important; + } + .ms-sm-1 { + margin-left: 0.25rem !important; + } + .ms-sm-2 { + margin-left: 0.5rem !important; + } + .ms-sm-3 { + margin-left: 1rem !important; + } + .ms-sm-4 { + margin-left: 1.5rem !important; + } + .ms-sm-5 { + margin-left: 3rem !important; + } + .ms-sm-auto { + margin-left: auto !important; + } + .p-sm-0 { + padding: 0 !important; + } + .p-sm-1 { + padding: 0.25rem !important; + } + .p-sm-2 { + padding: 0.5rem !important; + } + .p-sm-3 { + padding: 1rem !important; + } + .p-sm-4 { + padding: 1.5rem !important; + } + .p-sm-5 { + padding: 3rem !important; + } + .px-sm-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .px-sm-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .px-sm-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .px-sm-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .px-sm-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .px-sm-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-sm-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .py-sm-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .py-sm-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .py-sm-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .py-sm-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .py-sm-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .pt-sm-0 { + padding-top: 0 !important; + } + .pt-sm-1 { + padding-top: 0.25rem !important; + } + .pt-sm-2 { + padding-top: 0.5rem !important; + } + .pt-sm-3 { + padding-top: 1rem !important; + } + .pt-sm-4 { + padding-top: 1.5rem !important; + } + .pt-sm-5 { + padding-top: 3rem !important; + } + .pe-sm-0 { + padding-right: 0 !important; + } + .pe-sm-1 { + padding-right: 0.25rem !important; + } + .pe-sm-2 { + padding-right: 0.5rem !important; + } + .pe-sm-3 { + padding-right: 1rem !important; + } + .pe-sm-4 { + padding-right: 1.5rem !important; + } + .pe-sm-5 { + padding-right: 3rem !important; + } + .pb-sm-0 { + padding-bottom: 0 !important; + } + .pb-sm-1 { + padding-bottom: 0.25rem !important; + } + .pb-sm-2 { + padding-bottom: 0.5rem !important; + } + .pb-sm-3 { + padding-bottom: 1rem !important; + } + .pb-sm-4 { + padding-bottom: 1.5rem !important; + } + .pb-sm-5 { + padding-bottom: 3rem !important; + } + .ps-sm-0 { + padding-left: 0 !important; + } + .ps-sm-1 { + padding-left: 0.25rem !important; + } + .ps-sm-2 { + padding-left: 0.5rem !important; + } + .ps-sm-3 { + padding-left: 1rem !important; + } + .ps-sm-4 { + padding-left: 1.5rem !important; + } + .ps-sm-5 { + padding-left: 3rem !important; + } + .gap-sm-0 { + gap: 0 !important; + } + .gap-sm-1 { + gap: 0.25rem !important; + } + .gap-sm-2 { + gap: 0.5rem !important; + } + .gap-sm-3 { + gap: 1rem !important; + } + .gap-sm-4 { + gap: 1.5rem !important; + } + .gap-sm-5 { + gap: 3rem !important; + } + .row-gap-sm-0 { + row-gap: 0 !important; + } + .row-gap-sm-1 { + row-gap: 0.25rem !important; + } + .row-gap-sm-2 { + row-gap: 0.5rem !important; + } + .row-gap-sm-3 { + row-gap: 1rem !important; + } + .row-gap-sm-4 { + row-gap: 1.5rem !important; + } + .row-gap-sm-5 { + row-gap: 3rem !important; + } + .column-gap-sm-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; + } + .column-gap-sm-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; + } + .column-gap-sm-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; + } + .column-gap-sm-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; + } + .column-gap-sm-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; + } + .column-gap-sm-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; + } + .text-sm-start { + text-align: left !important; + } + .text-sm-end { + text-align: right !important; + } + .text-sm-center { + text-align: center !important; + } +} +@media (min-width: 768px) { + .float-md-start { + float: left !important; + } + .float-md-end { + float: right !important; + } + .float-md-none { + float: none !important; + } + .object-fit-md-contain { + -o-object-fit: contain !important; + object-fit: contain !important; + } + .object-fit-md-cover { + -o-object-fit: cover !important; + object-fit: cover !important; + } + .object-fit-md-fill { + -o-object-fit: fill !important; + object-fit: fill !important; + } + .object-fit-md-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; + } + .object-fit-md-none { + -o-object-fit: none !important; + object-fit: none !important; + } + .d-md-inline { + display: inline !important; + } + .d-md-inline-block { + display: inline-block !important; + } + .d-md-block { + display: block !important; + } + .d-md-grid { + display: grid !important; + } + .d-md-inline-grid { + display: inline-grid !important; + } + .d-md-table { + display: table !important; + } + .d-md-table-row { + display: table-row !important; + } + .d-md-table-cell { + display: table-cell !important; + } + .d-md-flex { + display: flex !important; + } + .d-md-inline-flex { + display: inline-flex !important; + } + .d-md-none { + display: none !important; + } + .flex-md-fill { + flex: 1 1 auto !important; + } + .flex-md-row { + flex-direction: row !important; + } + .flex-md-column { + flex-direction: column !important; + } + .flex-md-row-reverse { + flex-direction: row-reverse !important; + } + .flex-md-column-reverse { + flex-direction: column-reverse !important; + } + .flex-md-grow-0 { + flex-grow: 0 !important; + } + .flex-md-grow-1 { + flex-grow: 1 !important; + } + .flex-md-shrink-0 { + flex-shrink: 0 !important; + } + .flex-md-shrink-1 { + flex-shrink: 1 !important; + } + .flex-md-wrap { + flex-wrap: wrap !important; + } + .flex-md-nowrap { + flex-wrap: nowrap !important; + } + .flex-md-wrap-reverse { + flex-wrap: wrap-reverse !important; + } + .justify-content-md-start { + justify-content: flex-start !important; + } + .justify-content-md-end { + justify-content: flex-end !important; + } + .justify-content-md-center { + justify-content: center !important; + } + .justify-content-md-between { + justify-content: space-between !important; + } + .justify-content-md-around { + justify-content: space-around !important; + } + .justify-content-md-evenly { + justify-content: space-evenly !important; + } + .align-items-md-start { + align-items: flex-start !important; + } + .align-items-md-end { + align-items: flex-end !important; + } + .align-items-md-center { + align-items: center !important; + } + .align-items-md-baseline { + align-items: baseline !important; + } + .align-items-md-stretch { + align-items: stretch !important; + } + .align-content-md-start { + align-content: flex-start !important; + } + .align-content-md-end { + align-content: flex-end !important; + } + .align-content-md-center { + align-content: center !important; + } + .align-content-md-between { + align-content: space-between !important; + } + .align-content-md-around { + align-content: space-around !important; + } + .align-content-md-stretch { + align-content: stretch !important; + } + .align-self-md-auto { + align-self: auto !important; + } + .align-self-md-start { + align-self: flex-start !important; + } + .align-self-md-end { + align-self: flex-end !important; + } + .align-self-md-center { + align-self: center !important; + } + .align-self-md-baseline { + align-self: baseline !important; + } + .align-self-md-stretch { + align-self: stretch !important; + } + .order-md-first { + order: -1 !important; + } + .order-md-0 { + order: 0 !important; + } + .order-md-1 { + order: 1 !important; + } + .order-md-2 { + order: 2 !important; + } + .order-md-3 { + order: 3 !important; + } + .order-md-4 { + order: 4 !important; + } + .order-md-5 { + order: 5 !important; + } + .order-md-last { + order: 6 !important; + } + .m-md-0 { + margin: 0 !important; + } + .m-md-1 { + margin: 0.25rem !important; + } + .m-md-2 { + margin: 0.5rem !important; + } + .m-md-3 { + margin: 1rem !important; + } + .m-md-4 { + margin: 1.5rem !important; + } + .m-md-5 { + margin: 3rem !important; + } + .m-md-auto { + margin: auto !important; + } + .mx-md-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .mx-md-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .mx-md-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .mx-md-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .mx-md-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .mx-md-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .mx-md-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-md-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .my-md-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .my-md-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .my-md-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .my-md-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .my-md-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .my-md-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } + .mt-md-0 { + margin-top: 0 !important; + } + .mt-md-1 { + margin-top: 0.25rem !important; + } + .mt-md-2 { + margin-top: 0.5rem !important; + } + .mt-md-3 { + margin-top: 1rem !important; + } + .mt-md-4 { + margin-top: 1.5rem !important; + } + .mt-md-5 { + margin-top: 3rem !important; + } + .mt-md-auto { + margin-top: auto !important; + } + .me-md-0 { + margin-right: 0 !important; + } + .me-md-1 { + margin-right: 0.25rem !important; + } + .me-md-2 { + margin-right: 0.5rem !important; + } + .me-md-3 { + margin-right: 1rem !important; + } + .me-md-4 { + margin-right: 1.5rem !important; + } + .me-md-5 { + margin-right: 3rem !important; + } + .me-md-auto { + margin-right: auto !important; + } + .mb-md-0 { + margin-bottom: 0 !important; + } + .mb-md-1 { + margin-bottom: 0.25rem !important; + } + .mb-md-2 { + margin-bottom: 0.5rem !important; + } + .mb-md-3 { + margin-bottom: 1rem !important; + } + .mb-md-4 { + margin-bottom: 1.5rem !important; + } + .mb-md-5 { + margin-bottom: 3rem !important; + } + .mb-md-auto { + margin-bottom: auto !important; + } + .ms-md-0 { + margin-left: 0 !important; + } + .ms-md-1 { + margin-left: 0.25rem !important; + } + .ms-md-2 { + margin-left: 0.5rem !important; + } + .ms-md-3 { + margin-left: 1rem !important; + } + .ms-md-4 { + margin-left: 1.5rem !important; + } + .ms-md-5 { + margin-left: 3rem !important; + } + .ms-md-auto { + margin-left: auto !important; + } + .p-md-0 { + padding: 0 !important; + } + .p-md-1 { + padding: 0.25rem !important; + } + .p-md-2 { + padding: 0.5rem !important; + } + .p-md-3 { + padding: 1rem !important; + } + .p-md-4 { + padding: 1.5rem !important; + } + .p-md-5 { + padding: 3rem !important; + } + .px-md-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .px-md-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .px-md-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .px-md-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .px-md-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .px-md-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-md-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .py-md-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .py-md-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .py-md-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .py-md-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .py-md-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .pt-md-0 { + padding-top: 0 !important; + } + .pt-md-1 { + padding-top: 0.25rem !important; + } + .pt-md-2 { + padding-top: 0.5rem !important; + } + .pt-md-3 { + padding-top: 1rem !important; + } + .pt-md-4 { + padding-top: 1.5rem !important; + } + .pt-md-5 { + padding-top: 3rem !important; + } + .pe-md-0 { + padding-right: 0 !important; + } + .pe-md-1 { + padding-right: 0.25rem !important; + } + .pe-md-2 { + padding-right: 0.5rem !important; + } + .pe-md-3 { + padding-right: 1rem !important; + } + .pe-md-4 { + padding-right: 1.5rem !important; + } + .pe-md-5 { + padding-right: 3rem !important; + } + .pb-md-0 { + padding-bottom: 0 !important; + } + .pb-md-1 { + padding-bottom: 0.25rem !important; + } + .pb-md-2 { + padding-bottom: 0.5rem !important; + } + .pb-md-3 { + padding-bottom: 1rem !important; + } + .pb-md-4 { + padding-bottom: 1.5rem !important; + } + .pb-md-5 { + padding-bottom: 3rem !important; + } + .ps-md-0 { + padding-left: 0 !important; + } + .ps-md-1 { + padding-left: 0.25rem !important; + } + .ps-md-2 { + padding-left: 0.5rem !important; + } + .ps-md-3 { + padding-left: 1rem !important; + } + .ps-md-4 { + padding-left: 1.5rem !important; + } + .ps-md-5 { + padding-left: 3rem !important; + } + .gap-md-0 { + gap: 0 !important; + } + .gap-md-1 { + gap: 0.25rem !important; + } + .gap-md-2 { + gap: 0.5rem !important; + } + .gap-md-3 { + gap: 1rem !important; + } + .gap-md-4 { + gap: 1.5rem !important; + } + .gap-md-5 { + gap: 3rem !important; + } + .row-gap-md-0 { + row-gap: 0 !important; + } + .row-gap-md-1 { + row-gap: 0.25rem !important; + } + .row-gap-md-2 { + row-gap: 0.5rem !important; + } + .row-gap-md-3 { + row-gap: 1rem !important; + } + .row-gap-md-4 { + row-gap: 1.5rem !important; + } + .row-gap-md-5 { + row-gap: 3rem !important; + } + .column-gap-md-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; + } + .column-gap-md-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; + } + .column-gap-md-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; + } + .column-gap-md-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; + } + .column-gap-md-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; + } + .column-gap-md-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; + } + .text-md-start { + text-align: left !important; + } + .text-md-end { + text-align: right !important; + } + .text-md-center { + text-align: center !important; + } +} +@media (min-width: 992px) { + .float-lg-start { + float: left !important; + } + .float-lg-end { + float: right !important; + } + .float-lg-none { + float: none !important; + } + .object-fit-lg-contain { + -o-object-fit: contain !important; + object-fit: contain !important; + } + .object-fit-lg-cover { + -o-object-fit: cover !important; + object-fit: cover !important; + } + .object-fit-lg-fill { + -o-object-fit: fill !important; + object-fit: fill !important; + } + .object-fit-lg-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; + } + .object-fit-lg-none { + -o-object-fit: none !important; + object-fit: none !important; + } + .d-lg-inline { + display: inline !important; + } + .d-lg-inline-block { + display: inline-block !important; + } + .d-lg-block { + display: block !important; + } + .d-lg-grid { + display: grid !important; + } + .d-lg-inline-grid { + display: inline-grid !important; + } + .d-lg-table { + display: table !important; + } + .d-lg-table-row { + display: table-row !important; + } + .d-lg-table-cell { + display: table-cell !important; + } + .d-lg-flex { + display: flex !important; + } + .d-lg-inline-flex { + display: inline-flex !important; + } + .d-lg-none { + display: none !important; + } + .flex-lg-fill { + flex: 1 1 auto !important; + } + .flex-lg-row { + flex-direction: row !important; + } + .flex-lg-column { + flex-direction: column !important; + } + .flex-lg-row-reverse { + flex-direction: row-reverse !important; + } + .flex-lg-column-reverse { + flex-direction: column-reverse !important; + } + .flex-lg-grow-0 { + flex-grow: 0 !important; + } + .flex-lg-grow-1 { + flex-grow: 1 !important; + } + .flex-lg-shrink-0 { + flex-shrink: 0 !important; + } + .flex-lg-shrink-1 { + flex-shrink: 1 !important; + } + .flex-lg-wrap { + flex-wrap: wrap !important; + } + .flex-lg-nowrap { + flex-wrap: nowrap !important; + } + .flex-lg-wrap-reverse { + flex-wrap: wrap-reverse !important; + } + .justify-content-lg-start { + justify-content: flex-start !important; + } + .justify-content-lg-end { + justify-content: flex-end !important; + } + .justify-content-lg-center { + justify-content: center !important; + } + .justify-content-lg-between { + justify-content: space-between !important; + } + .justify-content-lg-around { + justify-content: space-around !important; + } + .justify-content-lg-evenly { + justify-content: space-evenly !important; + } + .align-items-lg-start { + align-items: flex-start !important; + } + .align-items-lg-end { + align-items: flex-end !important; + } + .align-items-lg-center { + align-items: center !important; + } + .align-items-lg-baseline { + align-items: baseline !important; + } + .align-items-lg-stretch { + align-items: stretch !important; + } + .align-content-lg-start { + align-content: flex-start !important; + } + .align-content-lg-end { + align-content: flex-end !important; + } + .align-content-lg-center { + align-content: center !important; + } + .align-content-lg-between { + align-content: space-between !important; + } + .align-content-lg-around { + align-content: space-around !important; + } + .align-content-lg-stretch { + align-content: stretch !important; + } + .align-self-lg-auto { + align-self: auto !important; + } + .align-self-lg-start { + align-self: flex-start !important; + } + .align-self-lg-end { + align-self: flex-end !important; + } + .align-self-lg-center { + align-self: center !important; + } + .align-self-lg-baseline { + align-self: baseline !important; + } + .align-self-lg-stretch { + align-self: stretch !important; + } + .order-lg-first { + order: -1 !important; + } + .order-lg-0 { + order: 0 !important; + } + .order-lg-1 { + order: 1 !important; + } + .order-lg-2 { + order: 2 !important; + } + .order-lg-3 { + order: 3 !important; + } + .order-lg-4 { + order: 4 !important; + } + .order-lg-5 { + order: 5 !important; + } + .order-lg-last { + order: 6 !important; + } + .m-lg-0 { + margin: 0 !important; + } + .m-lg-1 { + margin: 0.25rem !important; + } + .m-lg-2 { + margin: 0.5rem !important; + } + .m-lg-3 { + margin: 1rem !important; + } + .m-lg-4 { + margin: 1.5rem !important; + } + .m-lg-5 { + margin: 3rem !important; + } + .m-lg-auto { + margin: auto !important; + } + .mx-lg-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .mx-lg-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .mx-lg-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .mx-lg-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .mx-lg-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .mx-lg-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .mx-lg-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-lg-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .my-lg-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .my-lg-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .my-lg-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .my-lg-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .my-lg-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .my-lg-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } + .mt-lg-0 { + margin-top: 0 !important; + } + .mt-lg-1 { + margin-top: 0.25rem !important; + } + .mt-lg-2 { + margin-top: 0.5rem !important; + } + .mt-lg-3 { + margin-top: 1rem !important; + } + .mt-lg-4 { + margin-top: 1.5rem !important; + } + .mt-lg-5 { + margin-top: 3rem !important; + } + .mt-lg-auto { + margin-top: auto !important; + } + .me-lg-0 { + margin-right: 0 !important; + } + .me-lg-1 { + margin-right: 0.25rem !important; + } + .me-lg-2 { + margin-right: 0.5rem !important; + } + .me-lg-3 { + margin-right: 1rem !important; + } + .me-lg-4 { + margin-right: 1.5rem !important; + } + .me-lg-5 { + margin-right: 3rem !important; + } + .me-lg-auto { + margin-right: auto !important; + } + .mb-lg-0 { + margin-bottom: 0 !important; + } + .mb-lg-1 { + margin-bottom: 0.25rem !important; + } + .mb-lg-2 { + margin-bottom: 0.5rem !important; + } + .mb-lg-3 { + margin-bottom: 1rem !important; + } + .mb-lg-4 { + margin-bottom: 1.5rem !important; + } + .mb-lg-5 { + margin-bottom: 3rem !important; + } + .mb-lg-auto { + margin-bottom: auto !important; + } + .ms-lg-0 { + margin-left: 0 !important; + } + .ms-lg-1 { + margin-left: 0.25rem !important; + } + .ms-lg-2 { + margin-left: 0.5rem !important; + } + .ms-lg-3 { + margin-left: 1rem !important; + } + .ms-lg-4 { + margin-left: 1.5rem !important; + } + .ms-lg-5 { + margin-left: 3rem !important; + } + .ms-lg-auto { + margin-left: auto !important; + } + .p-lg-0 { + padding: 0 !important; + } + .p-lg-1 { + padding: 0.25rem !important; + } + .p-lg-2 { + padding: 0.5rem !important; + } + .p-lg-3 { + padding: 1rem !important; + } + .p-lg-4 { + padding: 1.5rem !important; + } + .p-lg-5 { + padding: 3rem !important; + } + .px-lg-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .px-lg-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .px-lg-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .px-lg-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .px-lg-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .px-lg-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-lg-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .py-lg-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .py-lg-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .py-lg-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .py-lg-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .py-lg-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .pt-lg-0 { + padding-top: 0 !important; + } + .pt-lg-1 { + padding-top: 0.25rem !important; + } + .pt-lg-2 { + padding-top: 0.5rem !important; + } + .pt-lg-3 { + padding-top: 1rem !important; + } + .pt-lg-4 { + padding-top: 1.5rem !important; + } + .pt-lg-5 { + padding-top: 3rem !important; + } + .pe-lg-0 { + padding-right: 0 !important; + } + .pe-lg-1 { + padding-right: 0.25rem !important; + } + .pe-lg-2 { + padding-right: 0.5rem !important; + } + .pe-lg-3 { + padding-right: 1rem !important; + } + .pe-lg-4 { + padding-right: 1.5rem !important; + } + .pe-lg-5 { + padding-right: 3rem !important; + } + .pb-lg-0 { + padding-bottom: 0 !important; + } + .pb-lg-1 { + padding-bottom: 0.25rem !important; + } + .pb-lg-2 { + padding-bottom: 0.5rem !important; + } + .pb-lg-3 { + padding-bottom: 1rem !important; + } + .pb-lg-4 { + padding-bottom: 1.5rem !important; + } + .pb-lg-5 { + padding-bottom: 3rem !important; + } + .ps-lg-0 { + padding-left: 0 !important; + } + .ps-lg-1 { + padding-left: 0.25rem !important; + } + .ps-lg-2 { + padding-left: 0.5rem !important; + } + .ps-lg-3 { + padding-left: 1rem !important; + } + .ps-lg-4 { + padding-left: 1.5rem !important; + } + .ps-lg-5 { + padding-left: 3rem !important; + } + .gap-lg-0 { + gap: 0 !important; + } + .gap-lg-1 { + gap: 0.25rem !important; + } + .gap-lg-2 { + gap: 0.5rem !important; + } + .gap-lg-3 { + gap: 1rem !important; + } + .gap-lg-4 { + gap: 1.5rem !important; + } + .gap-lg-5 { + gap: 3rem !important; + } + .row-gap-lg-0 { + row-gap: 0 !important; + } + .row-gap-lg-1 { + row-gap: 0.25rem !important; + } + .row-gap-lg-2 { + row-gap: 0.5rem !important; + } + .row-gap-lg-3 { + row-gap: 1rem !important; + } + .row-gap-lg-4 { + row-gap: 1.5rem !important; + } + .row-gap-lg-5 { + row-gap: 3rem !important; + } + .column-gap-lg-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; + } + .column-gap-lg-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; + } + .column-gap-lg-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; + } + .column-gap-lg-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; + } + .column-gap-lg-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; + } + .column-gap-lg-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; + } + .text-lg-start { + text-align: left !important; + } + .text-lg-end { + text-align: right !important; + } + .text-lg-center { + text-align: center !important; + } +} +@media (min-width: 1200px) { + .float-xl-start { + float: left !important; + } + .float-xl-end { + float: right !important; + } + .float-xl-none { + float: none !important; + } + .object-fit-xl-contain { + -o-object-fit: contain !important; + object-fit: contain !important; + } + .object-fit-xl-cover { + -o-object-fit: cover !important; + object-fit: cover !important; + } + .object-fit-xl-fill { + -o-object-fit: fill !important; + object-fit: fill !important; + } + .object-fit-xl-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; + } + .object-fit-xl-none { + -o-object-fit: none !important; + object-fit: none !important; + } + .d-xl-inline { + display: inline !important; + } + .d-xl-inline-block { + display: inline-block !important; + } + .d-xl-block { + display: block !important; + } + .d-xl-grid { + display: grid !important; + } + .d-xl-inline-grid { + display: inline-grid !important; + } + .d-xl-table { + display: table !important; + } + .d-xl-table-row { + display: table-row !important; + } + .d-xl-table-cell { + display: table-cell !important; + } + .d-xl-flex { + display: flex !important; + } + .d-xl-inline-flex { + display: inline-flex !important; + } + .d-xl-none { + display: none !important; + } + .flex-xl-fill { + flex: 1 1 auto !important; + } + .flex-xl-row { + flex-direction: row !important; + } + .flex-xl-column { + flex-direction: column !important; + } + .flex-xl-row-reverse { + flex-direction: row-reverse !important; + } + .flex-xl-column-reverse { + flex-direction: column-reverse !important; + } + .flex-xl-grow-0 { + flex-grow: 0 !important; + } + .flex-xl-grow-1 { + flex-grow: 1 !important; + } + .flex-xl-shrink-0 { + flex-shrink: 0 !important; + } + .flex-xl-shrink-1 { + flex-shrink: 1 !important; + } + .flex-xl-wrap { + flex-wrap: wrap !important; + } + .flex-xl-nowrap { + flex-wrap: nowrap !important; + } + .flex-xl-wrap-reverse { + flex-wrap: wrap-reverse !important; + } + .justify-content-xl-start { + justify-content: flex-start !important; + } + .justify-content-xl-end { + justify-content: flex-end !important; + } + .justify-content-xl-center { + justify-content: center !important; + } + .justify-content-xl-between { + justify-content: space-between !important; + } + .justify-content-xl-around { + justify-content: space-around !important; + } + .justify-content-xl-evenly { + justify-content: space-evenly !important; + } + .align-items-xl-start { + align-items: flex-start !important; + } + .align-items-xl-end { + align-items: flex-end !important; + } + .align-items-xl-center { + align-items: center !important; + } + .align-items-xl-baseline { + align-items: baseline !important; + } + .align-items-xl-stretch { + align-items: stretch !important; + } + .align-content-xl-start { + align-content: flex-start !important; + } + .align-content-xl-end { + align-content: flex-end !important; + } + .align-content-xl-center { + align-content: center !important; + } + .align-content-xl-between { + align-content: space-between !important; + } + .align-content-xl-around { + align-content: space-around !important; + } + .align-content-xl-stretch { + align-content: stretch !important; + } + .align-self-xl-auto { + align-self: auto !important; + } + .align-self-xl-start { + align-self: flex-start !important; + } + .align-self-xl-end { + align-self: flex-end !important; + } + .align-self-xl-center { + align-self: center !important; + } + .align-self-xl-baseline { + align-self: baseline !important; + } + .align-self-xl-stretch { + align-self: stretch !important; + } + .order-xl-first { + order: -1 !important; + } + .order-xl-0 { + order: 0 !important; + } + .order-xl-1 { + order: 1 !important; + } + .order-xl-2 { + order: 2 !important; + } + .order-xl-3 { + order: 3 !important; + } + .order-xl-4 { + order: 4 !important; + } + .order-xl-5 { + order: 5 !important; + } + .order-xl-last { + order: 6 !important; + } + .m-xl-0 { + margin: 0 !important; + } + .m-xl-1 { + margin: 0.25rem !important; + } + .m-xl-2 { + margin: 0.5rem !important; + } + .m-xl-3 { + margin: 1rem !important; + } + .m-xl-4 { + margin: 1.5rem !important; + } + .m-xl-5 { + margin: 3rem !important; + } + .m-xl-auto { + margin: auto !important; + } + .mx-xl-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .mx-xl-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .mx-xl-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .mx-xl-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .mx-xl-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .mx-xl-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .mx-xl-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-xl-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .my-xl-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .my-xl-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .my-xl-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .my-xl-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .my-xl-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .my-xl-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } + .mt-xl-0 { + margin-top: 0 !important; + } + .mt-xl-1 { + margin-top: 0.25rem !important; + } + .mt-xl-2 { + margin-top: 0.5rem !important; + } + .mt-xl-3 { + margin-top: 1rem !important; + } + .mt-xl-4 { + margin-top: 1.5rem !important; + } + .mt-xl-5 { + margin-top: 3rem !important; + } + .mt-xl-auto { + margin-top: auto !important; + } + .me-xl-0 { + margin-right: 0 !important; + } + .me-xl-1 { + margin-right: 0.25rem !important; + } + .me-xl-2 { + margin-right: 0.5rem !important; + } + .me-xl-3 { + margin-right: 1rem !important; + } + .me-xl-4 { + margin-right: 1.5rem !important; + } + .me-xl-5 { + margin-right: 3rem !important; + } + .me-xl-auto { + margin-right: auto !important; + } + .mb-xl-0 { + margin-bottom: 0 !important; + } + .mb-xl-1 { + margin-bottom: 0.25rem !important; + } + .mb-xl-2 { + margin-bottom: 0.5rem !important; + } + .mb-xl-3 { + margin-bottom: 1rem !important; + } + .mb-xl-4 { + margin-bottom: 1.5rem !important; + } + .mb-xl-5 { + margin-bottom: 3rem !important; + } + .mb-xl-auto { + margin-bottom: auto !important; + } + .ms-xl-0 { + margin-left: 0 !important; + } + .ms-xl-1 { + margin-left: 0.25rem !important; + } + .ms-xl-2 { + margin-left: 0.5rem !important; + } + .ms-xl-3 { + margin-left: 1rem !important; + } + .ms-xl-4 { + margin-left: 1.5rem !important; + } + .ms-xl-5 { + margin-left: 3rem !important; + } + .ms-xl-auto { + margin-left: auto !important; + } + .p-xl-0 { + padding: 0 !important; + } + .p-xl-1 { + padding: 0.25rem !important; + } + .p-xl-2 { + padding: 0.5rem !important; + } + .p-xl-3 { + padding: 1rem !important; + } + .p-xl-4 { + padding: 1.5rem !important; + } + .p-xl-5 { + padding: 3rem !important; + } + .px-xl-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .px-xl-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .px-xl-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .px-xl-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .px-xl-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .px-xl-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-xl-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .py-xl-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .py-xl-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .py-xl-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .py-xl-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .py-xl-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .pt-xl-0 { + padding-top: 0 !important; + } + .pt-xl-1 { + padding-top: 0.25rem !important; + } + .pt-xl-2 { + padding-top: 0.5rem !important; + } + .pt-xl-3 { + padding-top: 1rem !important; + } + .pt-xl-4 { + padding-top: 1.5rem !important; + } + .pt-xl-5 { + padding-top: 3rem !important; + } + .pe-xl-0 { + padding-right: 0 !important; + } + .pe-xl-1 { + padding-right: 0.25rem !important; + } + .pe-xl-2 { + padding-right: 0.5rem !important; + } + .pe-xl-3 { + padding-right: 1rem !important; + } + .pe-xl-4 { + padding-right: 1.5rem !important; + } + .pe-xl-5 { + padding-right: 3rem !important; + } + .pb-xl-0 { + padding-bottom: 0 !important; + } + .pb-xl-1 { + padding-bottom: 0.25rem !important; + } + .pb-xl-2 { + padding-bottom: 0.5rem !important; + } + .pb-xl-3 { + padding-bottom: 1rem !important; + } + .pb-xl-4 { + padding-bottom: 1.5rem !important; + } + .pb-xl-5 { + padding-bottom: 3rem !important; + } + .ps-xl-0 { + padding-left: 0 !important; + } + .ps-xl-1 { + padding-left: 0.25rem !important; + } + .ps-xl-2 { + padding-left: 0.5rem !important; + } + .ps-xl-3 { + padding-left: 1rem !important; + } + .ps-xl-4 { + padding-left: 1.5rem !important; + } + .ps-xl-5 { + padding-left: 3rem !important; + } + .gap-xl-0 { + gap: 0 !important; + } + .gap-xl-1 { + gap: 0.25rem !important; + } + .gap-xl-2 { + gap: 0.5rem !important; + } + .gap-xl-3 { + gap: 1rem !important; + } + .gap-xl-4 { + gap: 1.5rem !important; + } + .gap-xl-5 { + gap: 3rem !important; + } + .row-gap-xl-0 { + row-gap: 0 !important; + } + .row-gap-xl-1 { + row-gap: 0.25rem !important; + } + .row-gap-xl-2 { + row-gap: 0.5rem !important; + } + .row-gap-xl-3 { + row-gap: 1rem !important; + } + .row-gap-xl-4 { + row-gap: 1.5rem !important; + } + .row-gap-xl-5 { + row-gap: 3rem !important; + } + .column-gap-xl-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; + } + .column-gap-xl-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; + } + .column-gap-xl-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; + } + .column-gap-xl-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; + } + .column-gap-xl-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; + } + .column-gap-xl-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; + } + .text-xl-start { + text-align: left !important; + } + .text-xl-end { + text-align: right !important; + } + .text-xl-center { + text-align: center !important; + } +} +@media (min-width: 1400px) { + .float-xxl-start { + float: left !important; + } + .float-xxl-end { + float: right !important; + } + .float-xxl-none { + float: none !important; + } + .object-fit-xxl-contain { + -o-object-fit: contain !important; + object-fit: contain !important; + } + .object-fit-xxl-cover { + -o-object-fit: cover !important; + object-fit: cover !important; + } + .object-fit-xxl-fill { + -o-object-fit: fill !important; + object-fit: fill !important; + } + .object-fit-xxl-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; + } + .object-fit-xxl-none { + -o-object-fit: none !important; + object-fit: none !important; + } + .d-xxl-inline { + display: inline !important; + } + .d-xxl-inline-block { + display: inline-block !important; + } + .d-xxl-block { + display: block !important; + } + .d-xxl-grid { + display: grid !important; + } + .d-xxl-inline-grid { + display: inline-grid !important; + } + .d-xxl-table { + display: table !important; + } + .d-xxl-table-row { + display: table-row !important; + } + .d-xxl-table-cell { + display: table-cell !important; + } + .d-xxl-flex { + display: flex !important; + } + .d-xxl-inline-flex { + display: inline-flex !important; + } + .d-xxl-none { + display: none !important; + } + .flex-xxl-fill { + flex: 1 1 auto !important; + } + .flex-xxl-row { + flex-direction: row !important; + } + .flex-xxl-column { + flex-direction: column !important; + } + .flex-xxl-row-reverse { + flex-direction: row-reverse !important; + } + .flex-xxl-column-reverse { + flex-direction: column-reverse !important; + } + .flex-xxl-grow-0 { + flex-grow: 0 !important; + } + .flex-xxl-grow-1 { + flex-grow: 1 !important; + } + .flex-xxl-shrink-0 { + flex-shrink: 0 !important; + } + .flex-xxl-shrink-1 { + flex-shrink: 1 !important; + } + .flex-xxl-wrap { + flex-wrap: wrap !important; + } + .flex-xxl-nowrap { + flex-wrap: nowrap !important; + } + .flex-xxl-wrap-reverse { + flex-wrap: wrap-reverse !important; + } + .justify-content-xxl-start { + justify-content: flex-start !important; + } + .justify-content-xxl-end { + justify-content: flex-end !important; + } + .justify-content-xxl-center { + justify-content: center !important; + } + .justify-content-xxl-between { + justify-content: space-between !important; + } + .justify-content-xxl-around { + justify-content: space-around !important; + } + .justify-content-xxl-evenly { + justify-content: space-evenly !important; + } + .align-items-xxl-start { + align-items: flex-start !important; + } + .align-items-xxl-end { + align-items: flex-end !important; + } + .align-items-xxl-center { + align-items: center !important; + } + .align-items-xxl-baseline { + align-items: baseline !important; + } + .align-items-xxl-stretch { + align-items: stretch !important; + } + .align-content-xxl-start { + align-content: flex-start !important; + } + .align-content-xxl-end { + align-content: flex-end !important; + } + .align-content-xxl-center { + align-content: center !important; + } + .align-content-xxl-between { + align-content: space-between !important; + } + .align-content-xxl-around { + align-content: space-around !important; + } + .align-content-xxl-stretch { + align-content: stretch !important; + } + .align-self-xxl-auto { + align-self: auto !important; + } + .align-self-xxl-start { + align-self: flex-start !important; + } + .align-self-xxl-end { + align-self: flex-end !important; + } + .align-self-xxl-center { + align-self: center !important; + } + .align-self-xxl-baseline { + align-self: baseline !important; + } + .align-self-xxl-stretch { + align-self: stretch !important; + } + .order-xxl-first { + order: -1 !important; + } + .order-xxl-0 { + order: 0 !important; + } + .order-xxl-1 { + order: 1 !important; + } + .order-xxl-2 { + order: 2 !important; + } + .order-xxl-3 { + order: 3 !important; + } + .order-xxl-4 { + order: 4 !important; + } + .order-xxl-5 { + order: 5 !important; + } + .order-xxl-last { + order: 6 !important; + } + .m-xxl-0 { + margin: 0 !important; + } + .m-xxl-1 { + margin: 0.25rem !important; + } + .m-xxl-2 { + margin: 0.5rem !important; + } + .m-xxl-3 { + margin: 1rem !important; + } + .m-xxl-4 { + margin: 1.5rem !important; + } + .m-xxl-5 { + margin: 3rem !important; + } + .m-xxl-auto { + margin: auto !important; + } + .mx-xxl-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .mx-xxl-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .mx-xxl-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .mx-xxl-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .mx-xxl-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .mx-xxl-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .mx-xxl-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-xxl-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .my-xxl-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .my-xxl-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .my-xxl-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .my-xxl-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .my-xxl-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .my-xxl-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } + .mt-xxl-0 { + margin-top: 0 !important; + } + .mt-xxl-1 { + margin-top: 0.25rem !important; + } + .mt-xxl-2 { + margin-top: 0.5rem !important; + } + .mt-xxl-3 { + margin-top: 1rem !important; + } + .mt-xxl-4 { + margin-top: 1.5rem !important; + } + .mt-xxl-5 { + margin-top: 3rem !important; + } + .mt-xxl-auto { + margin-top: auto !important; + } + .me-xxl-0 { + margin-right: 0 !important; + } + .me-xxl-1 { + margin-right: 0.25rem !important; + } + .me-xxl-2 { + margin-right: 0.5rem !important; + } + .me-xxl-3 { + margin-right: 1rem !important; + } + .me-xxl-4 { + margin-right: 1.5rem !important; + } + .me-xxl-5 { + margin-right: 3rem !important; + } + .me-xxl-auto { + margin-right: auto !important; + } + .mb-xxl-0 { + margin-bottom: 0 !important; + } + .mb-xxl-1 { + margin-bottom: 0.25rem !important; + } + .mb-xxl-2 { + margin-bottom: 0.5rem !important; + } + .mb-xxl-3 { + margin-bottom: 1rem !important; + } + .mb-xxl-4 { + margin-bottom: 1.5rem !important; + } + .mb-xxl-5 { + margin-bottom: 3rem !important; + } + .mb-xxl-auto { + margin-bottom: auto !important; + } + .ms-xxl-0 { + margin-left: 0 !important; + } + .ms-xxl-1 { + margin-left: 0.25rem !important; + } + .ms-xxl-2 { + margin-left: 0.5rem !important; + } + .ms-xxl-3 { + margin-left: 1rem !important; + } + .ms-xxl-4 { + margin-left: 1.5rem !important; + } + .ms-xxl-5 { + margin-left: 3rem !important; + } + .ms-xxl-auto { + margin-left: auto !important; + } + .p-xxl-0 { + padding: 0 !important; + } + .p-xxl-1 { + padding: 0.25rem !important; + } + .p-xxl-2 { + padding: 0.5rem !important; + } + .p-xxl-3 { + padding: 1rem !important; + } + .p-xxl-4 { + padding: 1.5rem !important; + } + .p-xxl-5 { + padding: 3rem !important; + } + .px-xxl-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .px-xxl-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .px-xxl-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .px-xxl-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .px-xxl-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .px-xxl-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-xxl-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .py-xxl-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .py-xxl-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .py-xxl-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .py-xxl-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .py-xxl-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .pt-xxl-0 { + padding-top: 0 !important; + } + .pt-xxl-1 { + padding-top: 0.25rem !important; + } + .pt-xxl-2 { + padding-top: 0.5rem !important; + } + .pt-xxl-3 { + padding-top: 1rem !important; + } + .pt-xxl-4 { + padding-top: 1.5rem !important; + } + .pt-xxl-5 { + padding-top: 3rem !important; + } + .pe-xxl-0 { + padding-right: 0 !important; + } + .pe-xxl-1 { + padding-right: 0.25rem !important; + } + .pe-xxl-2 { + padding-right: 0.5rem !important; + } + .pe-xxl-3 { + padding-right: 1rem !important; + } + .pe-xxl-4 { + padding-right: 1.5rem !important; + } + .pe-xxl-5 { + padding-right: 3rem !important; + } + .pb-xxl-0 { + padding-bottom: 0 !important; + } + .pb-xxl-1 { + padding-bottom: 0.25rem !important; + } + .pb-xxl-2 { + padding-bottom: 0.5rem !important; + } + .pb-xxl-3 { + padding-bottom: 1rem !important; + } + .pb-xxl-4 { + padding-bottom: 1.5rem !important; + } + .pb-xxl-5 { + padding-bottom: 3rem !important; + } + .ps-xxl-0 { + padding-left: 0 !important; + } + .ps-xxl-1 { + padding-left: 0.25rem !important; + } + .ps-xxl-2 { + padding-left: 0.5rem !important; + } + .ps-xxl-3 { + padding-left: 1rem !important; + } + .ps-xxl-4 { + padding-left: 1.5rem !important; + } + .ps-xxl-5 { + padding-left: 3rem !important; + } + .gap-xxl-0 { + gap: 0 !important; + } + .gap-xxl-1 { + gap: 0.25rem !important; + } + .gap-xxl-2 { + gap: 0.5rem !important; + } + .gap-xxl-3 { + gap: 1rem !important; + } + .gap-xxl-4 { + gap: 1.5rem !important; + } + .gap-xxl-5 { + gap: 3rem !important; + } + .row-gap-xxl-0 { + row-gap: 0 !important; + } + .row-gap-xxl-1 { + row-gap: 0.25rem !important; + } + .row-gap-xxl-2 { + row-gap: 0.5rem !important; + } + .row-gap-xxl-3 { + row-gap: 1rem !important; + } + .row-gap-xxl-4 { + row-gap: 1.5rem !important; + } + .row-gap-xxl-5 { + row-gap: 3rem !important; + } + .column-gap-xxl-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; + } + .column-gap-xxl-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; + } + .column-gap-xxl-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; + } + .column-gap-xxl-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; + } + .column-gap-xxl-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; + } + .column-gap-xxl-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; + } + .text-xxl-start { + text-align: left !important; + } + .text-xxl-end { + text-align: right !important; + } + .text-xxl-center { + text-align: center !important; + } +} +@media (min-width: 1200px) { + .fs-1 { + font-size: 2.5rem !important; + } + .fs-2 { + font-size: 2rem !important; + } + .fs-3 { + font-size: 1.75rem !important; + } + .fs-4 { + font-size: 1.5rem !important; + } +} +@media print { + .d-print-inline { + display: inline !important; + } + .d-print-inline-block { + display: inline-block !important; + } + .d-print-block { + display: block !important; + } + .d-print-grid { + display: grid !important; + } + .d-print-inline-grid { + display: inline-grid !important; + } + .d-print-table { + display: table !important; + } + .d-print-table-row { + display: table-row !important; + } + .d-print-table-cell { + display: table-cell !important; + } + .d-print-flex { + display: flex !important; + } + .d-print-inline-flex { + display: inline-flex !important; + } + .d-print-none { + display: none !important; + } +} + +.container { + max-width: 960px; +} +.bd-placeholder-img { + font-size: 1.125rem; + text-anchor: middle; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +@media (min-width: 768px) { + .bd-placeholder-img-lg { + font-size: 3.5rem; + } +} + +.b-example-divider { + width: 100%; + height: 3rem; + background-color: rgba(0, 0, 0, 0.1); + border: solid rgba(0, 0, 0, 0.15); + border-width: 1px 0; + box-shadow: inset 0 0.5em 1.5em rgba(0, 0, 0, 0.1), + inset 0 0.125em 0.5em rgba(0, 0, 0, 0.15); +} + +.b-example-vr { + flex-shrink: 0; + width: 1.5rem; + height: 100vh; +} + +.bi { + vertical-align: -0.125em; + fill: currentColor; +} + +.nav-scroller { + position: relative; + z-index: 2; + height: 2.75rem; + overflow-y: hidden; +} + +.nav-scroller .nav { + display: flex; + flex-wrap: nowrap; + padding-bottom: 1rem; + margin-top: -1px; + overflow-x: auto; + text-align: center; + white-space: nowrap; + -webkit-overflow-scrolling: touch; +} + +.btn-bd-primary { + --bd-violet-bg: #712cf9; + --bd-violet-rgb: 112.520718, 44.062154, 249.437846; + + --bs-btn-font-weight: 600; + --bs-btn-color: var(--bs-white); + --bs-btn-bg: var(--bd-violet-bg); + --bs-btn-border-color: var(--bd-violet-bg); + --bs-btn-hover-color: var(--bs-white); + --bs-btn-hover-bg: #6528e0; + --bs-btn-hover-border-color: #6528e0; + --bs-btn-focus-shadow-rgb: var(--bd-violet-rgb); + --bs-btn-active-color: var(--bs-btn-hover-color); + --bs-btn-active-bg: #5a23c8; + --bs-btn-active-border-color: #5a23c8; +} + +.bd-mode-toggle { + z-index: 1500; +} + +.bd-mode-toggle .dropdown-menu .active .bi { + display: block !important; +} \ No newline at end of file diff --git a/data_embed/bootstrap.js b/data_embed/bootstrap.js new file mode 100644 index 0000000..ded4eb2 --- /dev/null +++ b/data_embed/bootstrap.js @@ -0,0 +1,4467 @@ +// Bootstrap base +/*! + * Bootstrap v5.3.2 (https://getbootstrap.com/) + * Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +!(function (t, e) { + "object" == typeof exports && "undefined" != typeof module + ? (module.exports = e()) + : "function" == typeof define && define.amd + ? define(e) + : ((t = + "undefined" != typeof globalThis + ? globalThis + : t || self).bootstrap = e()); +})(this, function () { + "use strict"; + const t = new Map(), + e = { + set(e, i, n) { + t.has(e) || t.set(e, new Map()); + const s = t.get(e); + s.has(i) || 0 === s.size + ? s.set(i, n) + : console.error( + `Bootstrap doesn't allow more than one instance per element. Bound instance: ${ + Array.from(s.keys())[0] + }.` + ); + }, + get: (e, i) => (t.has(e) && t.get(e).get(i)) || null, + remove(e, i) { + if (!t.has(e)) return; + const n = t.get(e); + n.delete(i), 0 === n.size && t.delete(e); + }, + }, + i = "transitionend", + n = (t) => ( + t && + window.CSS && + window.CSS.escape && + (t = t.replace(/#([^\s"#']+)/g, (t, e) => `#${CSS.escape(e)}`)), + t + ), + s = (t) => { + t.dispatchEvent(new Event(i)); + }, + o = (t) => + !(!t || "object" != typeof t) && + (void 0 !== t.jquery && (t = t[0]), void 0 !== t.nodeType), + r = (t) => + o(t) + ? t.jquery + ? t[0] + : t + : "string" == typeof t && t.length > 0 + ? document.querySelector(n(t)) + : null, + a = (t) => { + if (!o(t) || 0 === t.getClientRects().length) return !1; + const e = + "visible" === + getComputedStyle(t).getPropertyValue("visibility"), + i = t.closest("details:not([open])"); + if (!i) return e; + if (i !== t) { + const e = t.closest("summary"); + if (e && e.parentNode !== i) return !1; + if (null === e) return !1; + } + return e; + }, + l = (t) => + !t || + t.nodeType !== Node.ELEMENT_NODE || + !!t.classList.contains("disabled") || + (void 0 !== t.disabled + ? t.disabled + : t.hasAttribute("disabled") && + "false" !== t.getAttribute("disabled")), + c = (t) => { + if (!document.documentElement.attachShadow) return null; + if ("function" == typeof t.getRootNode) { + const e = t.getRootNode(); + return e instanceof ShadowRoot ? e : null; + } + return t instanceof ShadowRoot + ? t + : t.parentNode + ? c(t.parentNode) + : null; + }, + h = () => {}, + d = (t) => { + t.offsetHeight; + }, + u = () => + window.jQuery && !document.body.hasAttribute("data-bs-no-jquery") + ? window.jQuery + : null, + f = [], + p = () => "rtl" === document.documentElement.dir, + m = (t) => { + var e; + (e = () => { + const e = u(); + if (e) { + const i = t.NAME, + n = e.fn[i]; + (e.fn[i] = t.jQueryInterface), + (e.fn[i].Constructor = t), + (e.fn[i].noConflict = () => ( + (e.fn[i] = n), t.jQueryInterface + )); + } + }), + "loading" === document.readyState + ? (f.length || + document.addEventListener("DOMContentLoaded", () => { + for (const t of f) t(); + }), + f.push(e)) + : e(); + }, + g = (t, e = [], i = t) => ("function" == typeof t ? t(...e) : i), + _ = (t, e, n = !0) => { + if (!n) return void g(t); + const o = + ((t) => { + if (!t) return 0; + let { transitionDuration: e, transitionDelay: i } = + window.getComputedStyle(t); + const n = Number.parseFloat(e), + s = Number.parseFloat(i); + return n || s + ? ((e = e.split(",")[0]), + (i = i.split(",")[0]), + 1e3 * (Number.parseFloat(e) + Number.parseFloat(i))) + : 0; + })(e) + 5; + let r = !1; + const a = ({ target: n }) => { + n === e && ((r = !0), e.removeEventListener(i, a), g(t)); + }; + e.addEventListener(i, a), + setTimeout(() => { + r || s(e); + }, o); + }, + b = (t, e, i, n) => { + const s = t.length; + let o = t.indexOf(e); + return -1 === o + ? !i && n + ? t[s - 1] + : t[0] + : ((o += i ? 1 : -1), + n && (o = (o + s) % s), + t[Math.max(0, Math.min(o, s - 1))]); + }, + v = /[^.]*(?=\..*)\.|.*/, + y = /\..*/, + w = /::\d+$/, + A = {}; + let E = 1; + const T = { mouseenter: "mouseover", mouseleave: "mouseout" }, + C = new Set([ + "click", + "dblclick", + "mouseup", + "mousedown", + "contextmenu", + "mousewheel", + "DOMMouseScroll", + "mouseover", + "mouseout", + "mousemove", + "selectstart", + "selectend", + "keydown", + "keypress", + "keyup", + "orientationchange", + "touchstart", + "touchmove", + "touchend", + "touchcancel", + "pointerdown", + "pointermove", + "pointerup", + "pointerleave", + "pointercancel", + "gesturestart", + "gesturechange", + "gestureend", + "focus", + "blur", + "change", + "reset", + "select", + "submit", + "focusin", + "focusout", + "load", + "unload", + "beforeunload", + "resize", + "move", + "DOMContentLoaded", + "readystatechange", + "error", + "abort", + "scroll", + ]); + function O(t, e) { + return (e && `${e}::${E++}`) || t.uidEvent || E++; + } + function x(t) { + const e = O(t); + return (t.uidEvent = e), (A[e] = A[e] || {}), A[e]; + } + function k(t, e, i = null) { + return Object.values(t).find( + (t) => t.callable === e && t.delegationSelector === i + ); + } + function L(t, e, i) { + const n = "string" == typeof e, + s = n ? i : e || i; + let o = I(t); + return C.has(o) || (o = t), [n, s, o]; + } + function S(t, e, i, n, s) { + if ("string" != typeof e || !t) return; + let [o, r, a] = L(e, i, n); + if (e in T) { + const t = (t) => + function (e) { + if ( + !e.relatedTarget || + (e.relatedTarget !== e.delegateTarget && + !e.delegateTarget.contains(e.relatedTarget)) + ) + return t.call(this, e); + }; + r = t(r); + } + const l = x(t), + c = l[a] || (l[a] = {}), + h = k(c, r, o ? i : null); + if (h) return void (h.oneOff = h.oneOff && s); + const d = O(r, e.replace(v, "")), + u = o + ? (function (t, e, i) { + return function n(s) { + const o = t.querySelectorAll(e); + for ( + let { target: r } = s; + r && r !== this; + r = r.parentNode + ) + for (const a of o) + if (a === r) + return ( + P(s, { delegateTarget: r }), + n.oneOff && N.off(t, s.type, e, i), + i.apply(r, [s]) + ); + }; + })(t, i, r) + : (function (t, e) { + return function i(n) { + return ( + P(n, { delegateTarget: t }), + i.oneOff && N.off(t, n.type, e), + e.apply(t, [n]) + ); + }; + })(t, r); + (u.delegationSelector = o ? i : null), + (u.callable = r), + (u.oneOff = s), + (u.uidEvent = d), + (c[d] = u), + t.addEventListener(a, u, o); + } + function D(t, e, i, n, s) { + const o = k(e[i], n, s); + o && (t.removeEventListener(i, o, Boolean(s)), delete e[i][o.uidEvent]); + } + function $(t, e, i, n) { + const s = e[i] || {}; + for (const [o, r] of Object.entries(s)) + o.includes(n) && D(t, e, i, r.callable, r.delegationSelector); + } + function I(t) { + return (t = t.replace(y, "")), T[t] || t; + } + const N = { + on(t, e, i, n) { + S(t, e, i, n, !1); + }, + one(t, e, i, n) { + S(t, e, i, n, !0); + }, + off(t, e, i, n) { + if ("string" != typeof e || !t) return; + const [s, o, r] = L(e, i, n), + a = r !== e, + l = x(t), + c = l[r] || {}, + h = e.startsWith("."); + if (void 0 === o) { + if (h) for (const i of Object.keys(l)) $(t, l, i, e.slice(1)); + for (const [i, n] of Object.entries(c)) { + const s = i.replace(w, ""); + (a && !e.includes(s)) || + D(t, l, r, n.callable, n.delegationSelector); + } + } else { + if (!Object.keys(c).length) return; + D(t, l, r, o, s ? i : null); + } + }, + trigger(t, e, i) { + if ("string" != typeof e || !t) return null; + const n = u(); + let s = null, + o = !0, + r = !0, + a = !1; + e !== I(e) && + n && + ((s = n.Event(e, i)), + n(t).trigger(s), + (o = !s.isPropagationStopped()), + (r = !s.isImmediatePropagationStopped()), + (a = s.isDefaultPrevented())); + const l = P(new Event(e, { bubbles: o, cancelable: !0 }), i); + return ( + a && l.preventDefault(), + r && t.dispatchEvent(l), + l.defaultPrevented && s && s.preventDefault(), + l + ); + }, + }; + function P(t, e = {}) { + for (const [i, n] of Object.entries(e)) + try { + t[i] = n; + } catch (e) { + Object.defineProperty(t, i, { configurable: !0, get: () => n }); + } + return t; + } + function M(t) { + if ("true" === t) return !0; + if ("false" === t) return !1; + if (t === Number(t).toString()) return Number(t); + if ("" === t || "null" === t) return null; + if ("string" != typeof t) return t; + try { + return JSON.parse(decodeURIComponent(t)); + } catch (e) { + return t; + } + } + function j(t) { + return t.replace(/[A-Z]/g, (t) => `-${t.toLowerCase()}`); + } + const F = { + setDataAttribute(t, e, i) { + t.setAttribute(`data-bs-${j(e)}`, i); + }, + removeDataAttribute(t, e) { + t.removeAttribute(`data-bs-${j(e)}`); + }, + getDataAttributes(t) { + if (!t) return {}; + const e = {}, + i = Object.keys(t.dataset).filter( + (t) => t.startsWith("bs") && !t.startsWith("bsConfig") + ); + for (const n of i) { + let i = n.replace(/^bs/, ""); + (i = i.charAt(0).toLowerCase() + i.slice(1, i.length)), + (e[i] = M(t.dataset[n])); + } + return e; + }, + getDataAttribute: (t, e) => M(t.getAttribute(`data-bs-${j(e)}`)), + }; + class H { + static get Default() { + return {}; + } + static get DefaultType() { + return {}; + } + static get NAME() { + throw new Error( + 'You have to implement the static method "NAME", for each component!' + ); + } + _getConfig(t) { + return ( + (t = this._mergeConfigObj(t)), + (t = this._configAfterMerge(t)), + this._typeCheckConfig(t), + t + ); + } + _configAfterMerge(t) { + return t; + } + _mergeConfigObj(t, e) { + const i = o(e) ? F.getDataAttribute(e, "config") : {}; + return { + ...this.constructor.Default, + ...("object" == typeof i ? i : {}), + ...(o(e) ? F.getDataAttributes(e) : {}), + ...("object" == typeof t ? t : {}), + }; + } + _typeCheckConfig(t, e = this.constructor.DefaultType) { + for (const [n, s] of Object.entries(e)) { + const e = t[n], + r = o(e) + ? "element" + : null == (i = e) + ? `${i}` + : Object.prototype.toString + .call(i) + .match(/\s([a-z]+)/i)[1] + .toLowerCase(); + if (!new RegExp(s).test(r)) + throw new TypeError( + `${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${r}" but expected type "${s}".` + ); + } + var i; + } + } + class W extends H { + constructor(t, i) { + super(), + (t = r(t)) && + ((this._element = t), + (this._config = this._getConfig(i)), + e.set(this._element, this.constructor.DATA_KEY, this)); + } + dispose() { + e.remove(this._element, this.constructor.DATA_KEY), + N.off(this._element, this.constructor.EVENT_KEY); + for (const t of Object.getOwnPropertyNames(this)) this[t] = null; + } + _queueCallback(t, e, i = !0) { + _(t, e, i); + } + _getConfig(t) { + return ( + (t = this._mergeConfigObj(t, this._element)), + (t = this._configAfterMerge(t)), + this._typeCheckConfig(t), + t + ); + } + static getInstance(t) { + return e.get(r(t), this.DATA_KEY); + } + static getOrCreateInstance(t, e = {}) { + return ( + this.getInstance(t) || + new this(t, "object" == typeof e ? e : null) + ); + } + static get VERSION() { + return "5.3.2"; + } + static get DATA_KEY() { + return `bs.${this.NAME}`; + } + static get EVENT_KEY() { + return `.${this.DATA_KEY}`; + } + static eventName(t) { + return `${t}${this.EVENT_KEY}`; + } + } + const B = (t) => { + let e = t.getAttribute("data-bs-target"); + if (!e || "#" === e) { + let i = t.getAttribute("href"); + if (!i || (!i.includes("#") && !i.startsWith("."))) return null; + i.includes("#") && + !i.startsWith("#") && + (i = `#${i.split("#")[1]}`), + (e = i && "#" !== i ? n(i.trim()) : null); + } + return e; + }, + z = { + find: (t, e = document.documentElement) => + [].concat(...Element.prototype.querySelectorAll.call(e, t)), + findOne: (t, e = document.documentElement) => + Element.prototype.querySelector.call(e, t), + children: (t, e) => + [].concat(...t.children).filter((t) => t.matches(e)), + parents(t, e) { + const i = []; + let n = t.parentNode.closest(e); + for (; n; ) i.push(n), (n = n.parentNode.closest(e)); + return i; + }, + prev(t, e) { + let i = t.previousElementSibling; + for (; i; ) { + if (i.matches(e)) return [i]; + i = i.previousElementSibling; + } + return []; + }, + next(t, e) { + let i = t.nextElementSibling; + for (; i; ) { + if (i.matches(e)) return [i]; + i = i.nextElementSibling; + } + return []; + }, + focusableChildren(t) { + const e = [ + "a", + "button", + "input", + "textarea", + "select", + "details", + "[tabindex]", + '[contenteditable="true"]', + ] + .map((t) => `${t}:not([tabindex^="-"])`) + .join(","); + return this.find(e, t).filter((t) => !l(t) && a(t)); + }, + getSelectorFromElement(t) { + const e = B(t); + return e && z.findOne(e) ? e : null; + }, + getElementFromSelector(t) { + const e = B(t); + return e ? z.findOne(e) : null; + }, + getMultipleElementsFromSelector(t) { + const e = B(t); + return e ? z.find(e) : []; + }, + }, + R = (t, e = "hide") => { + const i = `click.dismiss${t.EVENT_KEY}`, + n = t.NAME; + N.on(document, i, `[data-bs-dismiss="${n}"]`, function (i) { + if ( + (["A", "AREA"].includes(this.tagName) && i.preventDefault(), + l(this)) + ) + return; + const s = + z.getElementFromSelector(this) || this.closest(`.${n}`); + t.getOrCreateInstance(s)[e](); + }); + }, + q = ".bs.alert", + V = `close${q}`, + K = `closed${q}`; + class Q extends W { + static get NAME() { + return "alert"; + } + close() { + if (N.trigger(this._element, V).defaultPrevented) return; + this._element.classList.remove("show"); + const t = this._element.classList.contains("fade"); + this._queueCallback(() => this._destroyElement(), this._element, t); + } + _destroyElement() { + this._element.remove(), N.trigger(this._element, K), this.dispose(); + } + static jQueryInterface(t) { + return this.each(function () { + const e = Q.getOrCreateInstance(this); + if ("string" == typeof t) { + if ( + void 0 === e[t] || + t.startsWith("_") || + "constructor" === t + ) + throw new TypeError(`No method named "${t}"`); + e[t](this); + } + }); + } + } + R(Q, "close"), m(Q); + const X = '[data-bs-toggle="button"]'; + class Y extends W { + static get NAME() { + return "button"; + } + toggle() { + this._element.setAttribute( + "aria-pressed", + this._element.classList.toggle("active") + ); + } + static jQueryInterface(t) { + return this.each(function () { + const e = Y.getOrCreateInstance(this); + "toggle" === t && e[t](); + }); + } + } + N.on(document, "click.bs.button.data-api", X, (t) => { + t.preventDefault(); + const e = t.target.closest(X); + Y.getOrCreateInstance(e).toggle(); + }), + m(Y); + const U = ".bs.swipe", + G = `touchstart${U}`, + J = `touchmove${U}`, + Z = `touchend${U}`, + tt = `pointerdown${U}`, + et = `pointerup${U}`, + it = { endCallback: null, leftCallback: null, rightCallback: null }, + nt = { + endCallback: "(function|null)", + leftCallback: "(function|null)", + rightCallback: "(function|null)", + }; + class st extends H { + constructor(t, e) { + super(), + (this._element = t), + t && + st.isSupported() && + ((this._config = this._getConfig(e)), + (this._deltaX = 0), + (this._supportPointerEvents = Boolean(window.PointerEvent)), + this._initEvents()); + } + static get Default() { + return it; + } + static get DefaultType() { + return nt; + } + static get NAME() { + return "swipe"; + } + dispose() { + N.off(this._element, U); + } + _start(t) { + this._supportPointerEvents + ? this._eventIsPointerPenTouch(t) && (this._deltaX = t.clientX) + : (this._deltaX = t.touches[0].clientX); + } + _end(t) { + this._eventIsPointerPenTouch(t) && + (this._deltaX = t.clientX - this._deltaX), + this._handleSwipe(), + g(this._config.endCallback); + } + _move(t) { + this._deltaX = + t.touches && t.touches.length > 1 + ? 0 + : t.touches[0].clientX - this._deltaX; + } + _handleSwipe() { + const t = Math.abs(this._deltaX); + if (t <= 40) return; + const e = t / this._deltaX; + (this._deltaX = 0), + e && + g( + e > 0 + ? this._config.rightCallback + : this._config.leftCallback + ); + } + _initEvents() { + this._supportPointerEvents + ? (N.on(this._element, tt, (t) => this._start(t)), + N.on(this._element, et, (t) => this._end(t)), + this._element.classList.add("pointer-event")) + : (N.on(this._element, G, (t) => this._start(t)), + N.on(this._element, J, (t) => this._move(t)), + N.on(this._element, Z, (t) => this._end(t))); + } + _eventIsPointerPenTouch(t) { + return ( + this._supportPointerEvents && + ("pen" === t.pointerType || "touch" === t.pointerType) + ); + } + static isSupported() { + return ( + "ontouchstart" in document.documentElement || + navigator.maxTouchPoints > 0 + ); + } + } + const ot = ".bs.carousel", + rt = ".data-api", + at = "next", + lt = "prev", + ct = "left", + ht = "right", + dt = `slide${ot}`, + ut = `slid${ot}`, + ft = `keydown${ot}`, + pt = `mouseenter${ot}`, + mt = `mouseleave${ot}`, + gt = `dragstart${ot}`, + _t = `load${ot}${rt}`, + bt = `click${ot}${rt}`, + vt = "carousel", + yt = "active", + wt = ".active", + At = ".carousel-item", + Et = wt + At, + Tt = { ArrowLeft: ht, ArrowRight: ct }, + Ct = { + interval: 5e3, + keyboard: !0, + pause: "hover", + ride: !1, + touch: !0, + wrap: !0, + }, + Ot = { + interval: "(number|boolean)", + keyboard: "boolean", + pause: "(string|boolean)", + ride: "(boolean|string)", + touch: "boolean", + wrap: "boolean", + }; + class xt extends W { + constructor(t, e) { + super(t, e), + (this._interval = null), + (this._activeElement = null), + (this._isSliding = !1), + (this.touchTimeout = null), + (this._swipeHelper = null), + (this._indicatorsElement = z.findOne( + ".carousel-indicators", + this._element + )), + this._addEventListeners(), + this._config.ride === vt && this.cycle(); + } + static get Default() { + return Ct; + } + static get DefaultType() { + return Ot; + } + static get NAME() { + return "carousel"; + } + next() { + this._slide(at); + } + nextWhenVisible() { + !document.hidden && a(this._element) && this.next(); + } + prev() { + this._slide(lt); + } + pause() { + this._isSliding && s(this._element), this._clearInterval(); + } + cycle() { + this._clearInterval(), + this._updateInterval(), + (this._interval = setInterval( + () => this.nextWhenVisible(), + this._config.interval + )); + } + _maybeEnableCycle() { + this._config.ride && + (this._isSliding + ? N.one(this._element, ut, () => this.cycle()) + : this.cycle()); + } + to(t) { + const e = this._getItems(); + if (t > e.length - 1 || t < 0) return; + if (this._isSliding) + return void N.one(this._element, ut, () => this.to(t)); + const i = this._getItemIndex(this._getActive()); + if (i === t) return; + const n = t > i ? at : lt; + this._slide(n, e[t]); + } + dispose() { + this._swipeHelper && this._swipeHelper.dispose(), super.dispose(); + } + _configAfterMerge(t) { + return (t.defaultInterval = t.interval), t; + } + _addEventListeners() { + this._config.keyboard && + N.on(this._element, ft, (t) => this._keydown(t)), + "hover" === this._config.pause && + (N.on(this._element, pt, () => this.pause()), + N.on(this._element, mt, () => this._maybeEnableCycle())), + this._config.touch && + st.isSupported() && + this._addTouchEventListeners(); + } + _addTouchEventListeners() { + for (const t of z.find(".carousel-item img", this._element)) + N.on(t, gt, (t) => t.preventDefault()); + const t = { + leftCallback: () => this._slide(this._directionToOrder(ct)), + rightCallback: () => this._slide(this._directionToOrder(ht)), + endCallback: () => { + "hover" === this._config.pause && + (this.pause(), + this.touchTimeout && clearTimeout(this.touchTimeout), + (this.touchTimeout = setTimeout( + () => this._maybeEnableCycle(), + 500 + this._config.interval + ))); + }, + }; + this._swipeHelper = new st(this._element, t); + } + _keydown(t) { + if (/input|textarea/i.test(t.target.tagName)) return; + const e = Tt[t.key]; + e && (t.preventDefault(), this._slide(this._directionToOrder(e))); + } + _getItemIndex(t) { + return this._getItems().indexOf(t); + } + _setActiveIndicatorElement(t) { + if (!this._indicatorsElement) return; + const e = z.findOne(wt, this._indicatorsElement); + e.classList.remove(yt), e.removeAttribute("aria-current"); + const i = z.findOne( + `[data-bs-slide-to="${t}"]`, + this._indicatorsElement + ); + i && (i.classList.add(yt), i.setAttribute("aria-current", "true")); + } + _updateInterval() { + const t = this._activeElement || this._getActive(); + if (!t) return; + const e = Number.parseInt(t.getAttribute("data-bs-interval"), 10); + this._config.interval = e || this._config.defaultInterval; + } + _slide(t, e = null) { + if (this._isSliding) return; + const i = this._getActive(), + n = t === at, + s = e || b(this._getItems(), i, n, this._config.wrap); + if (s === i) return; + const o = this._getItemIndex(s), + r = (e) => + N.trigger(this._element, e, { + relatedTarget: s, + direction: this._orderToDirection(t), + from: this._getItemIndex(i), + to: o, + }); + if (r(dt).defaultPrevented) return; + if (!i || !s) return; + const a = Boolean(this._interval); + this.pause(), + (this._isSliding = !0), + this._setActiveIndicatorElement(o), + (this._activeElement = s); + const l = n ? "carousel-item-start" : "carousel-item-end", + c = n ? "carousel-item-next" : "carousel-item-prev"; + s.classList.add(c), + d(s), + i.classList.add(l), + s.classList.add(l), + this._queueCallback( + () => { + s.classList.remove(l, c), + s.classList.add(yt), + i.classList.remove(yt, c, l), + (this._isSliding = !1), + r(ut); + }, + i, + this._isAnimated() + ), + a && this.cycle(); + } + _isAnimated() { + return this._element.classList.contains("slide"); + } + _getActive() { + return z.findOne(Et, this._element); + } + _getItems() { + return z.find(At, this._element); + } + _clearInterval() { + this._interval && + (clearInterval(this._interval), (this._interval = null)); + } + _directionToOrder(t) { + return p() ? (t === ct ? lt : at) : t === ct ? at : lt; + } + _orderToDirection(t) { + return p() ? (t === lt ? ct : ht) : t === lt ? ht : ct; + } + static jQueryInterface(t) { + return this.each(function () { + const e = xt.getOrCreateInstance(this, t); + if ("number" != typeof t) { + if ("string" == typeof t) { + if ( + void 0 === e[t] || + t.startsWith("_") || + "constructor" === t + ) + throw new TypeError(`No method named "${t}"`); + e[t](); + } + } else e.to(t); + }); + } + } + N.on(document, bt, "[data-bs-slide], [data-bs-slide-to]", function (t) { + const e = z.getElementFromSelector(this); + if (!e || !e.classList.contains(vt)) return; + t.preventDefault(); + const i = xt.getOrCreateInstance(e), + n = this.getAttribute("data-bs-slide-to"); + return n + ? (i.to(n), void i._maybeEnableCycle()) + : "next" === F.getDataAttribute(this, "slide") + ? (i.next(), void i._maybeEnableCycle()) + : (i.prev(), void i._maybeEnableCycle()); + }), + N.on(window, _t, () => { + const t = z.find('[data-bs-ride="carousel"]'); + for (const e of t) xt.getOrCreateInstance(e); + }), + m(xt); + const kt = ".bs.collapse", + Lt = `show${kt}`, + St = `shown${kt}`, + Dt = `hide${kt}`, + $t = `hidden${kt}`, + It = `click${kt}.data-api`, + Nt = "show", + Pt = "collapse", + Mt = "collapsing", + jt = `:scope .${Pt} .${Pt}`, + Ft = '[data-bs-toggle="collapse"]', + Ht = { parent: null, toggle: !0 }, + Wt = { parent: "(null|element)", toggle: "boolean" }; + class Bt extends W { + constructor(t, e) { + super(t, e), + (this._isTransitioning = !1), + (this._triggerArray = []); + const i = z.find(Ft); + for (const t of i) { + const e = z.getSelectorFromElement(t), + i = z.find(e).filter((t) => t === this._element); + null !== e && i.length && this._triggerArray.push(t); + } + this._initializeChildren(), + this._config.parent || + this._addAriaAndCollapsedClass( + this._triggerArray, + this._isShown() + ), + this._config.toggle && this.toggle(); + } + static get Default() { + return Ht; + } + static get DefaultType() { + return Wt; + } + static get NAME() { + return "collapse"; + } + toggle() { + this._isShown() ? this.hide() : this.show(); + } + show() { + if (this._isTransitioning || this._isShown()) return; + let t = []; + if ( + (this._config.parent && + (t = this._getFirstLevelChildren( + ".collapse.show, .collapse.collapsing" + ) + .filter((t) => t !== this._element) + .map((t) => Bt.getOrCreateInstance(t, { toggle: !1 }))), + t.length && t[0]._isTransitioning) + ) + return; + if (N.trigger(this._element, Lt).defaultPrevented) return; + for (const e of t) e.hide(); + const e = this._getDimension(); + this._element.classList.remove(Pt), + this._element.classList.add(Mt), + (this._element.style[e] = 0), + this._addAriaAndCollapsedClass(this._triggerArray, !0), + (this._isTransitioning = !0); + const i = `scroll${e[0].toUpperCase() + e.slice(1)}`; + this._queueCallback( + () => { + (this._isTransitioning = !1), + this._element.classList.remove(Mt), + this._element.classList.add(Pt, Nt), + (this._element.style[e] = ""), + N.trigger(this._element, St); + }, + this._element, + !0 + ), + (this._element.style[e] = `${this._element[i]}px`); + } + hide() { + if (this._isTransitioning || !this._isShown()) return; + if (N.trigger(this._element, Dt).defaultPrevented) return; + const t = this._getDimension(); + (this._element.style[t] = `${ + this._element.getBoundingClientRect()[t] + }px`), + d(this._element), + this._element.classList.add(Mt), + this._element.classList.remove(Pt, Nt); + for (const t of this._triggerArray) { + const e = z.getElementFromSelector(t); + e && + !this._isShown(e) && + this._addAriaAndCollapsedClass([t], !1); + } + (this._isTransitioning = !0), + (this._element.style[t] = ""), + this._queueCallback( + () => { + (this._isTransitioning = !1), + this._element.classList.remove(Mt), + this._element.classList.add(Pt), + N.trigger(this._element, $t); + }, + this._element, + !0 + ); + } + _isShown(t = this._element) { + return t.classList.contains(Nt); + } + _configAfterMerge(t) { + return (t.toggle = Boolean(t.toggle)), (t.parent = r(t.parent)), t; + } + _getDimension() { + return this._element.classList.contains("collapse-horizontal") + ? "width" + : "height"; + } + _initializeChildren() { + if (!this._config.parent) return; + const t = this._getFirstLevelChildren(Ft); + for (const e of t) { + const t = z.getElementFromSelector(e); + t && this._addAriaAndCollapsedClass([e], this._isShown(t)); + } + } + _getFirstLevelChildren(t) { + const e = z.find(jt, this._config.parent); + return z.find(t, this._config.parent).filter((t) => !e.includes(t)); + } + _addAriaAndCollapsedClass(t, e) { + if (t.length) + for (const i of t) + i.classList.toggle("collapsed", !e), + i.setAttribute("aria-expanded", e); + } + static jQueryInterface(t) { + const e = {}; + return ( + "string" == typeof t && /show|hide/.test(t) && (e.toggle = !1), + this.each(function () { + const i = Bt.getOrCreateInstance(this, e); + if ("string" == typeof t) { + if (void 0 === i[t]) + throw new TypeError(`No method named "${t}"`); + i[t](); + } + }) + ); + } + } + N.on(document, It, Ft, function (t) { + ("A" === t.target.tagName || + (t.delegateTarget && "A" === t.delegateTarget.tagName)) && + t.preventDefault(); + for (const t of z.getMultipleElementsFromSelector(this)) + Bt.getOrCreateInstance(t, { toggle: !1 }).toggle(); + }), + m(Bt); + var zt = "top", + Rt = "bottom", + qt = "right", + Vt = "left", + Kt = "auto", + Qt = [zt, Rt, qt, Vt], + Xt = "start", + Yt = "end", + Ut = "clippingParents", + Gt = "viewport", + Jt = "popper", + Zt = "reference", + te = Qt.reduce(function (t, e) { + return t.concat([e + "-" + Xt, e + "-" + Yt]); + }, []), + ee = [].concat(Qt, [Kt]).reduce(function (t, e) { + return t.concat([e, e + "-" + Xt, e + "-" + Yt]); + }, []), + ie = "beforeRead", + ne = "read", + se = "afterRead", + oe = "beforeMain", + re = "main", + ae = "afterMain", + le = "beforeWrite", + ce = "write", + he = "afterWrite", + de = [ie, ne, se, oe, re, ae, le, ce, he]; + function ue(t) { + return t ? (t.nodeName || "").toLowerCase() : null; + } + function fe(t) { + if (null == t) return window; + if ("[object Window]" !== t.toString()) { + var e = t.ownerDocument; + return (e && e.defaultView) || window; + } + return t; + } + function pe(t) { + return t instanceof fe(t).Element || t instanceof Element; + } + function me(t) { + return t instanceof fe(t).HTMLElement || t instanceof HTMLElement; + } + function ge(t) { + return ( + "undefined" != typeof ShadowRoot && + (t instanceof fe(t).ShadowRoot || t instanceof ShadowRoot) + ); + } + const _e = { + name: "applyStyles", + enabled: !0, + phase: "write", + fn: function (t) { + var e = t.state; + Object.keys(e.elements).forEach(function (t) { + var i = e.styles[t] || {}, + n = e.attributes[t] || {}, + s = e.elements[t]; + me(s) && + ue(s) && + (Object.assign(s.style, i), + Object.keys(n).forEach(function (t) { + var e = n[t]; + !1 === e + ? s.removeAttribute(t) + : s.setAttribute(t, !0 === e ? "" : e); + })); + }); + }, + effect: function (t) { + var e = t.state, + i = { + popper: { + position: e.options.strategy, + left: "0", + top: "0", + margin: "0", + }, + arrow: { position: "absolute" }, + reference: {}, + }; + return ( + Object.assign(e.elements.popper.style, i.popper), + (e.styles = i), + e.elements.arrow && + Object.assign(e.elements.arrow.style, i.arrow), + function () { + Object.keys(e.elements).forEach(function (t) { + var n = e.elements[t], + s = e.attributes[t] || {}, + o = Object.keys( + e.styles.hasOwnProperty(t) ? e.styles[t] : i[t] + ).reduce(function (t, e) { + return (t[e] = ""), t; + }, {}); + me(n) && + ue(n) && + (Object.assign(n.style, o), + Object.keys(s).forEach(function (t) { + n.removeAttribute(t); + })); + }); + } + ); + }, + requires: ["computeStyles"], + }; + function be(t) { + return t.split("-")[0]; + } + var ve = Math.max, + ye = Math.min, + we = Math.round; + function Ae() { + var t = navigator.userAgentData; + return null != t && t.brands && Array.isArray(t.brands) + ? t.brands + .map(function (t) { + return t.brand + "/" + t.version; + }) + .join(" ") + : navigator.userAgent; + } + function Ee() { + return !/^((?!chrome|android).)*safari/i.test(Ae()); + } + function Te(t, e, i) { + void 0 === e && (e = !1), void 0 === i && (i = !1); + var n = t.getBoundingClientRect(), + s = 1, + o = 1; + e && + me(t) && + ((s = (t.offsetWidth > 0 && we(n.width) / t.offsetWidth) || 1), + (o = (t.offsetHeight > 0 && we(n.height) / t.offsetHeight) || 1)); + var r = (pe(t) ? fe(t) : window).visualViewport, + a = !Ee() && i, + l = (n.left + (a && r ? r.offsetLeft : 0)) / s, + c = (n.top + (a && r ? r.offsetTop : 0)) / o, + h = n.width / s, + d = n.height / o; + return { + width: h, + height: d, + top: c, + right: l + h, + bottom: c + d, + left: l, + x: l, + y: c, + }; + } + function Ce(t) { + var e = Te(t), + i = t.offsetWidth, + n = t.offsetHeight; + return ( + Math.abs(e.width - i) <= 1 && (i = e.width), + Math.abs(e.height - n) <= 1 && (n = e.height), + { x: t.offsetLeft, y: t.offsetTop, width: i, height: n } + ); + } + function Oe(t, e) { + var i = e.getRootNode && e.getRootNode(); + if (t.contains(e)) return !0; + if (i && ge(i)) { + var n = e; + do { + if (n && t.isSameNode(n)) return !0; + n = n.parentNode || n.host; + } while (n); + } + return !1; + } + function xe(t) { + return fe(t).getComputedStyle(t); + } + function ke(t) { + return ["table", "td", "th"].indexOf(ue(t)) >= 0; + } + function Le(t) { + return ( + (pe(t) ? t.ownerDocument : t.document) || window.document + ).documentElement; + } + function Se(t) { + return "html" === ue(t) + ? t + : t.assignedSlot || + t.parentNode || + (ge(t) ? t.host : null) || + Le(t); + } + function De(t) { + return me(t) && "fixed" !== xe(t).position ? t.offsetParent : null; + } + function $e(t) { + for ( + var e = fe(t), i = De(t); + i && ke(i) && "static" === xe(i).position; + + ) + i = De(i); + return i && + ("html" === ue(i) || + ("body" === ue(i) && "static" === xe(i).position)) + ? e + : i || + (function (t) { + var e = /firefox/i.test(Ae()); + if ( + /Trident/i.test(Ae()) && + me(t) && + "fixed" === xe(t).position + ) + return null; + var i = Se(t); + for ( + ge(i) && (i = i.host); + me(i) && ["html", "body"].indexOf(ue(i)) < 0; + + ) { + var n = xe(i); + if ( + "none" !== n.transform || + "none" !== n.perspective || + "paint" === n.contain || + -1 !== + ["transform", "perspective"].indexOf( + n.willChange + ) || + (e && "filter" === n.willChange) || + (e && n.filter && "none" !== n.filter) + ) + return i; + i = i.parentNode; + } + return null; + })(t) || + e; + } + function Ie(t) { + return ["top", "bottom"].indexOf(t) >= 0 ? "x" : "y"; + } + function Ne(t, e, i) { + return ve(t, ye(e, i)); + } + function Pe(t) { + return Object.assign({}, { top: 0, right: 0, bottom: 0, left: 0 }, t); + } + function Me(t, e) { + return e.reduce(function (e, i) { + return (e[i] = t), e; + }, {}); + } + const je = { + name: "arrow", + enabled: !0, + phase: "main", + fn: function (t) { + var e, + i = t.state, + n = t.name, + s = t.options, + o = i.elements.arrow, + r = i.modifiersData.popperOffsets, + a = be(i.placement), + l = Ie(a), + c = [Vt, qt].indexOf(a) >= 0 ? "height" : "width"; + if (o && r) { + var h = (function (t, e) { + return Pe( + "number" != + typeof (t = + "function" == typeof t + ? t( + Object.assign({}, e.rects, { + placement: e.placement, + }) + ) + : t) + ? t + : Me(t, Qt) + ); + })(s.padding, i), + d = Ce(o), + u = "y" === l ? zt : Vt, + f = "y" === l ? Rt : qt, + p = + i.rects.reference[c] + + i.rects.reference[l] - + r[l] - + i.rects.popper[c], + m = r[l] - i.rects.reference[l], + g = $e(o), + _ = g + ? "y" === l + ? g.clientHeight || 0 + : g.clientWidth || 0 + : 0, + b = p / 2 - m / 2, + v = h[u], + y = _ - d[c] - h[f], + w = _ / 2 - d[c] / 2 + b, + A = Ne(v, w, y), + E = l; + i.modifiersData[n] = + (((e = {})[E] = A), (e.centerOffset = A - w), e); + } + }, + effect: function (t) { + var e = t.state, + i = t.options.element, + n = void 0 === i ? "[data-popper-arrow]" : i; + null != n && + ("string" != typeof n || + (n = e.elements.popper.querySelector(n))) && + Oe(e.elements.popper, n) && + (e.elements.arrow = n); + }, + requires: ["popperOffsets"], + requiresIfExists: ["preventOverflow"], + }; + function Fe(t) { + return t.split("-")[1]; + } + var He = { top: "auto", right: "auto", bottom: "auto", left: "auto" }; + function We(t) { + var e, + i = t.popper, + n = t.popperRect, + s = t.placement, + o = t.variation, + r = t.offsets, + a = t.position, + l = t.gpuAcceleration, + c = t.adaptive, + h = t.roundOffsets, + d = t.isFixed, + u = r.x, + f = void 0 === u ? 0 : u, + p = r.y, + m = void 0 === p ? 0 : p, + g = "function" == typeof h ? h({ x: f, y: m }) : { x: f, y: m }; + (f = g.x), (m = g.y); + var _ = r.hasOwnProperty("x"), + b = r.hasOwnProperty("y"), + v = Vt, + y = zt, + w = window; + if (c) { + var A = $e(i), + E = "clientHeight", + T = "clientWidth"; + A === fe(i) && + "static" !== xe((A = Le(i))).position && + "absolute" === a && + ((E = "scrollHeight"), (T = "scrollWidth")), + (s === zt || ((s === Vt || s === qt) && o === Yt)) && + ((y = Rt), + (m -= + (d && A === w && w.visualViewport + ? w.visualViewport.height + : A[E]) - n.height), + (m *= l ? 1 : -1)), + (s !== Vt && ((s !== zt && s !== Rt) || o !== Yt)) || + ((v = qt), + (f -= + (d && A === w && w.visualViewport + ? w.visualViewport.width + : A[T]) - n.width), + (f *= l ? 1 : -1)); + } + var C, + O = Object.assign({ position: a }, c && He), + x = + !0 === h + ? (function (t, e) { + var i = t.x, + n = t.y, + s = e.devicePixelRatio || 1; + return { + x: we(i * s) / s || 0, + y: we(n * s) / s || 0, + }; + })({ x: f, y: m }, fe(i)) + : { x: f, y: m }; + return ( + (f = x.x), + (m = x.y), + l + ? Object.assign( + {}, + O, + (((C = {})[y] = b ? "0" : ""), + (C[v] = _ ? "0" : ""), + (C.transform = + (w.devicePixelRatio || 1) <= 1 + ? "translate(" + f + "px, " + m + "px)" + : "translate3d(" + f + "px, " + m + "px, 0)"), + C) + ) + : Object.assign( + {}, + O, + (((e = {})[y] = b ? m + "px" : ""), + (e[v] = _ ? f + "px" : ""), + (e.transform = ""), + e) + ) + ); + } + const Be = { + name: "computeStyles", + enabled: !0, + phase: "beforeWrite", + fn: function (t) { + var e = t.state, + i = t.options, + n = i.gpuAcceleration, + s = void 0 === n || n, + o = i.adaptive, + r = void 0 === o || o, + a = i.roundOffsets, + l = void 0 === a || a, + c = { + placement: be(e.placement), + variation: Fe(e.placement), + popper: e.elements.popper, + popperRect: e.rects.popper, + gpuAcceleration: s, + isFixed: "fixed" === e.options.strategy, + }; + null != e.modifiersData.popperOffsets && + (e.styles.popper = Object.assign( + {}, + e.styles.popper, + We( + Object.assign({}, c, { + offsets: e.modifiersData.popperOffsets, + position: e.options.strategy, + adaptive: r, + roundOffsets: l, + }) + ) + )), + null != e.modifiersData.arrow && + (e.styles.arrow = Object.assign( + {}, + e.styles.arrow, + We( + Object.assign({}, c, { + offsets: e.modifiersData.arrow, + position: "absolute", + adaptive: !1, + roundOffsets: l, + }) + ) + )), + (e.attributes.popper = Object.assign({}, e.attributes.popper, { + "data-popper-placement": e.placement, + })); + }, + data: {}, + }; + var ze = { passive: !0 }; + const Re = { + name: "eventListeners", + enabled: !0, + phase: "write", + fn: function () {}, + effect: function (t) { + var e = t.state, + i = t.instance, + n = t.options, + s = n.scroll, + o = void 0 === s || s, + r = n.resize, + a = void 0 === r || r, + l = fe(e.elements.popper), + c = [].concat( + e.scrollParents.reference, + e.scrollParents.popper + ); + return ( + o && + c.forEach(function (t) { + t.addEventListener("scroll", i.update, ze); + }), + a && l.addEventListener("resize", i.update, ze), + function () { + o && + c.forEach(function (t) { + t.removeEventListener("scroll", i.update, ze); + }), + a && l.removeEventListener("resize", i.update, ze); + } + ); + }, + data: {}, + }; + var qe = { left: "right", right: "left", bottom: "top", top: "bottom" }; + function Ve(t) { + return t.replace(/left|right|bottom|top/g, function (t) { + return qe[t]; + }); + } + var Ke = { start: "end", end: "start" }; + function Qe(t) { + return t.replace(/start|end/g, function (t) { + return Ke[t]; + }); + } + function Xe(t) { + var e = fe(t); + return { scrollLeft: e.pageXOffset, scrollTop: e.pageYOffset }; + } + function Ye(t) { + return Te(Le(t)).left + Xe(t).scrollLeft; + } + function Ue(t) { + var e = xe(t), + i = e.overflow, + n = e.overflowX, + s = e.overflowY; + return /auto|scroll|overlay|hidden/.test(i + s + n); + } + function Ge(t) { + return ["html", "body", "#document"].indexOf(ue(t)) >= 0 + ? t.ownerDocument.body + : me(t) && Ue(t) + ? t + : Ge(Se(t)); + } + function Je(t, e) { + var i; + void 0 === e && (e = []); + var n = Ge(t), + s = n === (null == (i = t.ownerDocument) ? void 0 : i.body), + o = fe(n), + r = s ? [o].concat(o.visualViewport || [], Ue(n) ? n : []) : n, + a = e.concat(r); + return s ? a : a.concat(Je(Se(r))); + } + function Ze(t) { + return Object.assign({}, t, { + left: t.x, + top: t.y, + right: t.x + t.width, + bottom: t.y + t.height, + }); + } + function ti(t, e, i) { + return e === Gt + ? Ze( + (function (t, e) { + var i = fe(t), + n = Le(t), + s = i.visualViewport, + o = n.clientWidth, + r = n.clientHeight, + a = 0, + l = 0; + if (s) { + (o = s.width), (r = s.height); + var c = Ee(); + (c || (!c && "fixed" === e)) && + ((a = s.offsetLeft), (l = s.offsetTop)); + } + return { width: o, height: r, x: a + Ye(t), y: l }; + })(t, i) + ) + : pe(e) + ? (function (t, e) { + var i = Te(t, !1, "fixed" === e); + return ( + (i.top = i.top + t.clientTop), + (i.left = i.left + t.clientLeft), + (i.bottom = i.top + t.clientHeight), + (i.right = i.left + t.clientWidth), + (i.width = t.clientWidth), + (i.height = t.clientHeight), + (i.x = i.left), + (i.y = i.top), + i + ); + })(e, i) + : Ze( + (function (t) { + var e, + i = Le(t), + n = Xe(t), + s = null == (e = t.ownerDocument) ? void 0 : e.body, + o = ve( + i.scrollWidth, + i.clientWidth, + s ? s.scrollWidth : 0, + s ? s.clientWidth : 0 + ), + r = ve( + i.scrollHeight, + i.clientHeight, + s ? s.scrollHeight : 0, + s ? s.clientHeight : 0 + ), + a = -n.scrollLeft + Ye(t), + l = -n.scrollTop; + return ( + "rtl" === xe(s || i).direction && + (a += + ve(i.clientWidth, s ? s.clientWidth : 0) - o), + { width: o, height: r, x: a, y: l } + ); + })(Le(t)) + ); + } + function ei(t) { + var e, + i = t.reference, + n = t.element, + s = t.placement, + o = s ? be(s) : null, + r = s ? Fe(s) : null, + a = i.x + i.width / 2 - n.width / 2, + l = i.y + i.height / 2 - n.height / 2; + switch (o) { + case zt: + e = { x: a, y: i.y - n.height }; + break; + case Rt: + e = { x: a, y: i.y + i.height }; + break; + case qt: + e = { x: i.x + i.width, y: l }; + break; + case Vt: + e = { x: i.x - n.width, y: l }; + break; + default: + e = { x: i.x, y: i.y }; + } + var c = o ? Ie(o) : null; + if (null != c) { + var h = "y" === c ? "height" : "width"; + switch (r) { + case Xt: + e[c] = e[c] - (i[h] / 2 - n[h] / 2); + break; + case Yt: + e[c] = e[c] + (i[h] / 2 - n[h] / 2); + } + } + return e; + } + function ii(t, e) { + void 0 === e && (e = {}); + var i = e, + n = i.placement, + s = void 0 === n ? t.placement : n, + o = i.strategy, + r = void 0 === o ? t.strategy : o, + a = i.boundary, + l = void 0 === a ? Ut : a, + c = i.rootBoundary, + h = void 0 === c ? Gt : c, + d = i.elementContext, + u = void 0 === d ? Jt : d, + f = i.altBoundary, + p = void 0 !== f && f, + m = i.padding, + g = void 0 === m ? 0 : m, + _ = Pe("number" != typeof g ? g : Me(g, Qt)), + b = u === Jt ? Zt : Jt, + v = t.rects.popper, + y = t.elements[p ? b : u], + w = (function (t, e, i, n) { + var s = + "clippingParents" === e + ? (function (t) { + var e = Je(Se(t)), + i = + ["absolute", "fixed"].indexOf( + xe(t).position + ) >= 0 && me(t) + ? $e(t) + : t; + return pe(i) + ? e.filter(function (t) { + return ( + pe(t) && + Oe(t, i) && + "body" !== ue(t) + ); + }) + : []; + })(t) + : [].concat(e), + o = [].concat(s, [i]), + r = o[0], + a = o.reduce(function (e, i) { + var s = ti(t, i, n); + return ( + (e.top = ve(s.top, e.top)), + (e.right = ye(s.right, e.right)), + (e.bottom = ye(s.bottom, e.bottom)), + (e.left = ve(s.left, e.left)), + e + ); + }, ti(t, r, n)); + return ( + (a.width = a.right - a.left), + (a.height = a.bottom - a.top), + (a.x = a.left), + (a.y = a.top), + a + ); + })(pe(y) ? y : y.contextElement || Le(t.elements.popper), l, h, r), + A = Te(t.elements.reference), + E = ei({ + reference: A, + element: v, + strategy: "absolute", + placement: s, + }), + T = Ze(Object.assign({}, v, E)), + C = u === Jt ? T : A, + O = { + top: w.top - C.top + _.top, + bottom: C.bottom - w.bottom + _.bottom, + left: w.left - C.left + _.left, + right: C.right - w.right + _.right, + }, + x = t.modifiersData.offset; + if (u === Jt && x) { + var k = x[s]; + Object.keys(O).forEach(function (t) { + var e = [qt, Rt].indexOf(t) >= 0 ? 1 : -1, + i = [zt, Rt].indexOf(t) >= 0 ? "y" : "x"; + O[t] += k[i] * e; + }); + } + return O; + } + function ni(t, e) { + void 0 === e && (e = {}); + var i = e, + n = i.placement, + s = i.boundary, + o = i.rootBoundary, + r = i.padding, + a = i.flipVariations, + l = i.allowedAutoPlacements, + c = void 0 === l ? ee : l, + h = Fe(n), + d = h + ? a + ? te + : te.filter(function (t) { + return Fe(t) === h; + }) + : Qt, + u = d.filter(function (t) { + return c.indexOf(t) >= 0; + }); + 0 === u.length && (u = d); + var f = u.reduce(function (e, i) { + return ( + (e[i] = ii(t, { + placement: i, + boundary: s, + rootBoundary: o, + padding: r, + })[be(i)]), + e + ); + }, {}); + return Object.keys(f).sort(function (t, e) { + return f[t] - f[e]; + }); + } + const si = { + name: "flip", + enabled: !0, + phase: "main", + fn: function (t) { + var e = t.state, + i = t.options, + n = t.name; + if (!e.modifiersData[n]._skip) { + for ( + var s = i.mainAxis, + o = void 0 === s || s, + r = i.altAxis, + a = void 0 === r || r, + l = i.fallbackPlacements, + c = i.padding, + h = i.boundary, + d = i.rootBoundary, + u = i.altBoundary, + f = i.flipVariations, + p = void 0 === f || f, + m = i.allowedAutoPlacements, + g = e.options.placement, + _ = be(g), + b = + l || + (_ !== g && p + ? (function (t) { + if (be(t) === Kt) return []; + var e = Ve(t); + return [Qe(t), e, Qe(e)]; + })(g) + : [Ve(g)]), + v = [g].concat(b).reduce(function (t, i) { + return t.concat( + be(i) === Kt + ? ni(e, { + placement: i, + boundary: h, + rootBoundary: d, + padding: c, + flipVariations: p, + allowedAutoPlacements: m, + }) + : i + ); + }, []), + y = e.rects.reference, + w = e.rects.popper, + A = new Map(), + E = !0, + T = v[0], + C = 0; + C < v.length; + C++ + ) { + var O = v[C], + x = be(O), + k = Fe(O) === Xt, + L = [zt, Rt].indexOf(x) >= 0, + S = L ? "width" : "height", + D = ii(e, { + placement: O, + boundary: h, + rootBoundary: d, + altBoundary: u, + padding: c, + }), + $ = L ? (k ? qt : Vt) : k ? Rt : zt; + y[S] > w[S] && ($ = Ve($)); + var I = Ve($), + N = []; + if ( + (o && N.push(D[x] <= 0), + a && N.push(D[$] <= 0, D[I] <= 0), + N.every(function (t) { + return t; + })) + ) { + (T = O), (E = !1); + break; + } + A.set(O, N); + } + if (E) + for ( + var P = function (t) { + var e = v.find(function (e) { + var i = A.get(e); + if (i) + return i + .slice(0, t) + .every(function (t) { + return t; + }); + }); + if (e) return (T = e), "break"; + }, + M = p ? 3 : 1; + M > 0 && "break" !== P(M); + M-- + ); + e.placement !== T && + ((e.modifiersData[n]._skip = !0), + (e.placement = T), + (e.reset = !0)); + } + }, + requiresIfExists: ["offset"], + data: { _skip: !1 }, + }; + function oi(t, e, i) { + return ( + void 0 === i && (i = { x: 0, y: 0 }), + { + top: t.top - e.height - i.y, + right: t.right - e.width + i.x, + bottom: t.bottom - e.height + i.y, + left: t.left - e.width - i.x, + } + ); + } + function ri(t) { + return [zt, qt, Rt, Vt].some(function (e) { + return t[e] >= 0; + }); + } + const ai = { + name: "hide", + enabled: !0, + phase: "main", + requiresIfExists: ["preventOverflow"], + fn: function (t) { + var e = t.state, + i = t.name, + n = e.rects.reference, + s = e.rects.popper, + o = e.modifiersData.preventOverflow, + r = ii(e, { elementContext: "reference" }), + a = ii(e, { altBoundary: !0 }), + l = oi(r, n), + c = oi(a, s, o), + h = ri(l), + d = ri(c); + (e.modifiersData[i] = { + referenceClippingOffsets: l, + popperEscapeOffsets: c, + isReferenceHidden: h, + hasPopperEscaped: d, + }), + (e.attributes.popper = Object.assign( + {}, + e.attributes.popper, + { + "data-popper-reference-hidden": h, + "data-popper-escaped": d, + } + )); + }, + }, + li = { + name: "offset", + enabled: !0, + phase: "main", + requires: ["popperOffsets"], + fn: function (t) { + var e = t.state, + i = t.options, + n = t.name, + s = i.offset, + o = void 0 === s ? [0, 0] : s, + r = ee.reduce(function (t, i) { + return ( + (t[i] = (function (t, e, i) { + var n = be(t), + s = [Vt, zt].indexOf(n) >= 0 ? -1 : 1, + o = + "function" == typeof i + ? i( + Object.assign({}, e, { + placement: t, + }) + ) + : i, + r = o[0], + a = o[1]; + return ( + (r = r || 0), + (a = (a || 0) * s), + [Vt, qt].indexOf(n) >= 0 + ? { x: a, y: r } + : { x: r, y: a } + ); + })(i, e.rects, o)), + t + ); + }, {}), + a = r[e.placement], + l = a.x, + c = a.y; + null != e.modifiersData.popperOffsets && + ((e.modifiersData.popperOffsets.x += l), + (e.modifiersData.popperOffsets.y += c)), + (e.modifiersData[n] = r); + }, + }, + ci = { + name: "popperOffsets", + enabled: !0, + phase: "read", + fn: function (t) { + var e = t.state, + i = t.name; + e.modifiersData[i] = ei({ + reference: e.rects.reference, + element: e.rects.popper, + strategy: "absolute", + placement: e.placement, + }); + }, + data: {}, + }, + hi = { + name: "preventOverflow", + enabled: !0, + phase: "main", + fn: function (t) { + var e = t.state, + i = t.options, + n = t.name, + s = i.mainAxis, + o = void 0 === s || s, + r = i.altAxis, + a = void 0 !== r && r, + l = i.boundary, + c = i.rootBoundary, + h = i.altBoundary, + d = i.padding, + u = i.tether, + f = void 0 === u || u, + p = i.tetherOffset, + m = void 0 === p ? 0 : p, + g = ii(e, { + boundary: l, + rootBoundary: c, + padding: d, + altBoundary: h, + }), + _ = be(e.placement), + b = Fe(e.placement), + v = !b, + y = Ie(_), + w = "x" === y ? "y" : "x", + A = e.modifiersData.popperOffsets, + E = e.rects.reference, + T = e.rects.popper, + C = + "function" == typeof m + ? m( + Object.assign({}, e.rects, { + placement: e.placement, + }) + ) + : m, + O = + "number" == typeof C + ? { mainAxis: C, altAxis: C } + : Object.assign({ mainAxis: 0, altAxis: 0 }, C), + x = e.modifiersData.offset + ? e.modifiersData.offset[e.placement] + : null, + k = { x: 0, y: 0 }; + if (A) { + if (o) { + var L, + S = "y" === y ? zt : Vt, + D = "y" === y ? Rt : qt, + $ = "y" === y ? "height" : "width", + I = A[y], + N = I + g[S], + P = I - g[D], + M = f ? -T[$] / 2 : 0, + j = b === Xt ? E[$] : T[$], + F = b === Xt ? -T[$] : -E[$], + H = e.elements.arrow, + W = f && H ? Ce(H) : { width: 0, height: 0 }, + B = e.modifiersData["arrow#persistent"] + ? e.modifiersData["arrow#persistent"].padding + : { top: 0, right: 0, bottom: 0, left: 0 }, + z = B[S], + R = B[D], + q = Ne(0, E[$], W[$]), + V = v + ? E[$] / 2 - M - q - z - O.mainAxis + : j - q - z - O.mainAxis, + K = v + ? -E[$] / 2 + M + q + R + O.mainAxis + : F + q + R + O.mainAxis, + Q = e.elements.arrow && $e(e.elements.arrow), + X = Q + ? "y" === y + ? Q.clientTop || 0 + : Q.clientLeft || 0 + : 0, + Y = null != (L = null == x ? void 0 : x[y]) ? L : 0, + U = I + K - Y, + G = Ne( + f ? ye(N, I + V - Y - X) : N, + I, + f ? ve(P, U) : P + ); + (A[y] = G), (k[y] = G - I); + } + if (a) { + var J, + Z = "x" === y ? zt : Vt, + tt = "x" === y ? Rt : qt, + et = A[w], + it = "y" === w ? "height" : "width", + nt = et + g[Z], + st = et - g[tt], + ot = -1 !== [zt, Vt].indexOf(_), + rt = + null != (J = null == x ? void 0 : x[w]) ? J : 0, + at = ot ? nt : et - E[it] - T[it] - rt + O.altAxis, + lt = ot ? et + E[it] + T[it] - rt - O.altAxis : st, + ct = + f && ot + ? (function (t, e, i) { + var n = Ne(t, e, i); + return n > i ? i : n; + })(at, et, lt) + : Ne(f ? at : nt, et, f ? lt : st); + (A[w] = ct), (k[w] = ct - et); + } + e.modifiersData[n] = k; + } + }, + requiresIfExists: ["offset"], + }; + function di(t, e, i) { + void 0 === i && (i = !1); + var n, + s, + o = me(e), + r = + me(e) && + (function (t) { + var e = t.getBoundingClientRect(), + i = we(e.width) / t.offsetWidth || 1, + n = we(e.height) / t.offsetHeight || 1; + return 1 !== i || 1 !== n; + })(e), + a = Le(e), + l = Te(t, r, i), + c = { scrollLeft: 0, scrollTop: 0 }, + h = { x: 0, y: 0 }; + return ( + (o || (!o && !i)) && + (("body" !== ue(e) || Ue(a)) && + (c = + (n = e) !== fe(n) && me(n) + ? { + scrollLeft: (s = n).scrollLeft, + scrollTop: s.scrollTop, + } + : Xe(n)), + me(e) + ? (((h = Te(e, !0)).x += e.clientLeft), + (h.y += e.clientTop)) + : a && (h.x = Ye(a))), + { + x: l.left + c.scrollLeft - h.x, + y: l.top + c.scrollTop - h.y, + width: l.width, + height: l.height, + } + ); + } + function ui(t) { + var e = new Map(), + i = new Set(), + n = []; + function s(t) { + i.add(t.name), + [] + .concat(t.requires || [], t.requiresIfExists || []) + .forEach(function (t) { + if (!i.has(t)) { + var n = e.get(t); + n && s(n); + } + }), + n.push(t); + } + return ( + t.forEach(function (t) { + e.set(t.name, t); + }), + t.forEach(function (t) { + i.has(t.name) || s(t); + }), + n + ); + } + var fi = { placement: "bottom", modifiers: [], strategy: "absolute" }; + function pi() { + for (var t = arguments.length, e = new Array(t), i = 0; i < t; i++) + e[i] = arguments[i]; + return !e.some(function (t) { + return !(t && "function" == typeof t.getBoundingClientRect); + }); + } + function mi(t) { + void 0 === t && (t = {}); + var e = t, + i = e.defaultModifiers, + n = void 0 === i ? [] : i, + s = e.defaultOptions, + o = void 0 === s ? fi : s; + return function (t, e, i) { + void 0 === i && (i = o); + var s, + r, + a = { + placement: "bottom", + orderedModifiers: [], + options: Object.assign({}, fi, o), + modifiersData: {}, + elements: { reference: t, popper: e }, + attributes: {}, + styles: {}, + }, + l = [], + c = !1, + h = { + state: a, + setOptions: function (i) { + var s = "function" == typeof i ? i(a.options) : i; + d(), + (a.options = Object.assign({}, o, a.options, s)), + (a.scrollParents = { + reference: pe(t) + ? Je(t) + : t.contextElement + ? Je(t.contextElement) + : [], + popper: Je(e), + }); + var r, + c, + u = (function (t) { + var e = ui(t); + return de.reduce(function (t, i) { + return t.concat( + e.filter(function (t) { + return t.phase === i; + }) + ); + }, []); + })( + ((r = [].concat(n, a.options.modifiers)), + (c = r.reduce(function (t, e) { + var i = t[e.name]; + return ( + (t[e.name] = i + ? Object.assign({}, i, e, { + options: Object.assign( + {}, + i.options, + e.options + ), + data: Object.assign( + {}, + i.data, + e.data + ), + }) + : e), + t + ); + }, {})), + Object.keys(c).map(function (t) { + return c[t]; + })) + ); + return ( + (a.orderedModifiers = u.filter(function (t) { + return t.enabled; + })), + a.orderedModifiers.forEach(function (t) { + var e = t.name, + i = t.options, + n = void 0 === i ? {} : i, + s = t.effect; + if ("function" == typeof s) { + var o = s({ + state: a, + name: e, + instance: h, + options: n, + }); + l.push(o || function () {}); + } + }), + h.update() + ); + }, + forceUpdate: function () { + if (!c) { + var t = a.elements, + e = t.reference, + i = t.popper; + if (pi(e, i)) { + (a.rects = { + reference: di( + e, + $e(i), + "fixed" === a.options.strategy + ), + popper: Ce(i), + }), + (a.reset = !1), + (a.placement = a.options.placement), + a.orderedModifiers.forEach(function (t) { + return (a.modifiersData[t.name] = + Object.assign({}, t.data)); + }); + for ( + var n = 0; + n < a.orderedModifiers.length; + n++ + ) + if (!0 !== a.reset) { + var s = a.orderedModifiers[n], + o = s.fn, + r = s.options, + l = void 0 === r ? {} : r, + d = s.name; + "function" == typeof o && + (a = + o({ + state: a, + options: l, + name: d, + instance: h, + }) || a); + } else (a.reset = !1), (n = -1); + } + } + }, + update: + ((s = function () { + return new Promise(function (t) { + h.forceUpdate(), t(a); + }); + }), + function () { + return ( + r || + (r = new Promise(function (t) { + Promise.resolve().then(function () { + (r = void 0), t(s()); + }); + })), + r + ); + }), + destroy: function () { + d(), (c = !0); + }, + }; + if (!pi(t, e)) return h; + function d() { + l.forEach(function (t) { + return t(); + }), + (l = []); + } + return ( + h.setOptions(i).then(function (t) { + !c && i.onFirstUpdate && i.onFirstUpdate(t); + }), + h + ); + }; + } + var gi = mi(), + _i = mi({ defaultModifiers: [Re, ci, Be, _e] }), + bi = mi({ defaultModifiers: [Re, ci, Be, _e, li, si, hi, je, ai] }); + const vi = Object.freeze( + Object.defineProperty( + { + __proto__: null, + afterMain: ae, + afterRead: se, + afterWrite: he, + applyStyles: _e, + arrow: je, + auto: Kt, + basePlacements: Qt, + beforeMain: oe, + beforeRead: ie, + beforeWrite: le, + bottom: Rt, + clippingParents: Ut, + computeStyles: Be, + createPopper: bi, + createPopperBase: gi, + createPopperLite: _i, + detectOverflow: ii, + end: Yt, + eventListeners: Re, + flip: si, + hide: ai, + left: Vt, + main: re, + modifierPhases: de, + offset: li, + placements: ee, + popper: Jt, + popperGenerator: mi, + popperOffsets: ci, + preventOverflow: hi, + read: ne, + reference: Zt, + right: qt, + start: Xt, + top: zt, + variationPlacements: te, + viewport: Gt, + write: ce, + }, + Symbol.toStringTag, + { value: "Module" } + ) + ), + yi = "dropdown", + wi = ".bs.dropdown", + Ai = ".data-api", + Ei = "ArrowUp", + Ti = "ArrowDown", + Ci = `hide${wi}`, + Oi = `hidden${wi}`, + xi = `show${wi}`, + ki = `shown${wi}`, + Li = `click${wi}${Ai}`, + Si = `keydown${wi}${Ai}`, + Di = `keyup${wi}${Ai}`, + $i = "show", + Ii = '[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)', + Ni = `${Ii}.${$i}`, + Pi = ".dropdown-menu", + Mi = p() ? "top-end" : "top-start", + ji = p() ? "top-start" : "top-end", + Fi = p() ? "bottom-end" : "bottom-start", + Hi = p() ? "bottom-start" : "bottom-end", + Wi = p() ? "left-start" : "right-start", + Bi = p() ? "right-start" : "left-start", + zi = { + autoClose: !0, + boundary: "clippingParents", + display: "dynamic", + offset: [0, 2], + popperConfig: null, + reference: "toggle", + }, + Ri = { + autoClose: "(boolean|string)", + boundary: "(string|element)", + display: "string", + offset: "(array|string|function)", + popperConfig: "(null|object|function)", + reference: "(string|element|object)", + }; + class qi extends W { + constructor(t, e) { + super(t, e), + (this._popper = null), + (this._parent = this._element.parentNode), + (this._menu = + z.next(this._element, Pi)[0] || + z.prev(this._element, Pi)[0] || + z.findOne(Pi, this._parent)), + (this._inNavbar = this._detectNavbar()); + } + static get Default() { + return zi; + } + static get DefaultType() { + return Ri; + } + static get NAME() { + return yi; + } + toggle() { + return this._isShown() ? this.hide() : this.show(); + } + show() { + if (l(this._element) || this._isShown()) return; + const t = { relatedTarget: this._element }; + if (!N.trigger(this._element, xi, t).defaultPrevented) { + if ( + (this._createPopper(), + "ontouchstart" in document.documentElement && + !this._parent.closest(".navbar-nav")) + ) + for (const t of [].concat(...document.body.children)) + N.on(t, "mouseover", h); + this._element.focus(), + this._element.setAttribute("aria-expanded", !0), + this._menu.classList.add($i), + this._element.classList.add($i), + N.trigger(this._element, ki, t); + } + } + hide() { + if (l(this._element) || !this._isShown()) return; + const t = { relatedTarget: this._element }; + this._completeHide(t); + } + dispose() { + this._popper && this._popper.destroy(), super.dispose(); + } + update() { + (this._inNavbar = this._detectNavbar()), + this._popper && this._popper.update(); + } + _completeHide(t) { + if (!N.trigger(this._element, Ci, t).defaultPrevented) { + if ("ontouchstart" in document.documentElement) + for (const t of [].concat(...document.body.children)) + N.off(t, "mouseover", h); + this._popper && this._popper.destroy(), + this._menu.classList.remove($i), + this._element.classList.remove($i), + this._element.setAttribute("aria-expanded", "false"), + F.removeDataAttribute(this._menu, "popper"), + N.trigger(this._element, Oi, t); + } + } + _getConfig(t) { + if ( + "object" == typeof (t = super._getConfig(t)).reference && + !o(t.reference) && + "function" != typeof t.reference.getBoundingClientRect + ) + throw new TypeError( + `${yi.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.` + ); + return t; + } + _createPopper() { + if (void 0 === vi) + throw new TypeError( + "Bootstrap's dropdowns require Popper (https://popper.js.org)" + ); + let t = this._element; + "parent" === this._config.reference + ? (t = this._parent) + : o(this._config.reference) + ? (t = r(this._config.reference)) + : "object" == typeof this._config.reference && + (t = this._config.reference); + const e = this._getPopperConfig(); + this._popper = bi(t, this._menu, e); + } + _isShown() { + return this._menu.classList.contains($i); + } + _getPlacement() { + const t = this._parent; + if (t.classList.contains("dropend")) return Wi; + if (t.classList.contains("dropstart")) return Bi; + if (t.classList.contains("dropup-center")) return "top"; + if (t.classList.contains("dropdown-center")) return "bottom"; + const e = + "end" === + getComputedStyle(this._menu) + .getPropertyValue("--bs-position") + .trim(); + return t.classList.contains("dropup") ? (e ? ji : Mi) : e ? Hi : Fi; + } + _detectNavbar() { + return null !== this._element.closest(".navbar"); + } + _getOffset() { + const { offset: t } = this._config; + return "string" == typeof t + ? t.split(",").map((t) => Number.parseInt(t, 10)) + : "function" == typeof t + ? (e) => t(e, this._element) + : t; + } + _getPopperConfig() { + const t = { + placement: this._getPlacement(), + modifiers: [ + { + name: "preventOverflow", + options: { boundary: this._config.boundary }, + }, + { name: "offset", options: { offset: this._getOffset() } }, + ], + }; + return ( + (this._inNavbar || "static" === this._config.display) && + (F.setDataAttribute(this._menu, "popper", "static"), + (t.modifiers = [{ name: "applyStyles", enabled: !1 }])), + { ...t, ...g(this._config.popperConfig, [t]) } + ); + } + _selectMenuItem({ key: t, target: e }) { + const i = z + .find( + ".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)", + this._menu + ) + .filter((t) => a(t)); + i.length && b(i, e, t === Ti, !i.includes(e)).focus(); + } + static jQueryInterface(t) { + return this.each(function () { + const e = qi.getOrCreateInstance(this, t); + if ("string" == typeof t) { + if (void 0 === e[t]) + throw new TypeError(`No method named "${t}"`); + e[t](); + } + }); + } + static clearMenus(t) { + if (2 === t.button || ("keyup" === t.type && "Tab" !== t.key)) + return; + const e = z.find(Ni); + for (const i of e) { + const e = qi.getInstance(i); + if (!e || !1 === e._config.autoClose) continue; + const n = t.composedPath(), + s = n.includes(e._menu); + if ( + n.includes(e._element) || + ("inside" === e._config.autoClose && !s) || + ("outside" === e._config.autoClose && s) + ) + continue; + if ( + e._menu.contains(t.target) && + (("keyup" === t.type && "Tab" === t.key) || + /input|select|option|textarea|form/i.test( + t.target.tagName + )) + ) + continue; + const o = { relatedTarget: e._element }; + "click" === t.type && (o.clickEvent = t), e._completeHide(o); + } + } + static dataApiKeydownHandler(t) { + const e = /input|textarea/i.test(t.target.tagName), + i = "Escape" === t.key, + n = [Ei, Ti].includes(t.key); + if (!n && !i) return; + if (e && !i) return; + t.preventDefault(); + const s = this.matches(Ii) + ? this + : z.prev(this, Ii)[0] || + z.next(this, Ii)[0] || + z.findOne(Ii, t.delegateTarget.parentNode), + o = qi.getOrCreateInstance(s); + if (n) + return t.stopPropagation(), o.show(), void o._selectMenuItem(t); + o._isShown() && (t.stopPropagation(), o.hide(), s.focus()); + } + } + N.on(document, Si, Ii, qi.dataApiKeydownHandler), + N.on(document, Si, Pi, qi.dataApiKeydownHandler), + N.on(document, Li, qi.clearMenus), + N.on(document, Di, qi.clearMenus), + N.on(document, Li, Ii, function (t) { + t.preventDefault(), qi.getOrCreateInstance(this).toggle(); + }), + m(qi); + const Vi = "backdrop", + Ki = "show", + Qi = `mousedown.bs.${Vi}`, + Xi = { + className: "modal-backdrop", + clickCallback: null, + isAnimated: !1, + isVisible: !0, + rootElement: "body", + }, + Yi = { + className: "string", + clickCallback: "(function|null)", + isAnimated: "boolean", + isVisible: "boolean", + rootElement: "(element|string)", + }; + class Ui extends H { + constructor(t) { + super(), + (this._config = this._getConfig(t)), + (this._isAppended = !1), + (this._element = null); + } + static get Default() { + return Xi; + } + static get DefaultType() { + return Yi; + } + static get NAME() { + return Vi; + } + show(t) { + if (!this._config.isVisible) return void g(t); + this._append(); + const e = this._getElement(); + this._config.isAnimated && d(e), + e.classList.add(Ki), + this._emulateAnimation(() => { + g(t); + }); + } + hide(t) { + this._config.isVisible + ? (this._getElement().classList.remove(Ki), + this._emulateAnimation(() => { + this.dispose(), g(t); + })) + : g(t); + } + dispose() { + this._isAppended && + (N.off(this._element, Qi), + this._element.remove(), + (this._isAppended = !1)); + } + _getElement() { + if (!this._element) { + const t = document.createElement("div"); + (t.className = this._config.className), + this._config.isAnimated && t.classList.add("fade"), + (this._element = t); + } + return this._element; + } + _configAfterMerge(t) { + return (t.rootElement = r(t.rootElement)), t; + } + _append() { + if (this._isAppended) return; + const t = this._getElement(); + this._config.rootElement.append(t), + N.on(t, Qi, () => { + g(this._config.clickCallback); + }), + (this._isAppended = !0); + } + _emulateAnimation(t) { + _(t, this._getElement(), this._config.isAnimated); + } + } + const Gi = ".bs.focustrap", + Ji = `focusin${Gi}`, + Zi = `keydown.tab${Gi}`, + tn = "backward", + en = { autofocus: !0, trapElement: null }, + nn = { autofocus: "boolean", trapElement: "element" }; + class sn extends H { + constructor(t) { + super(), + (this._config = this._getConfig(t)), + (this._isActive = !1), + (this._lastTabNavDirection = null); + } + static get Default() { + return en; + } + static get DefaultType() { + return nn; + } + static get NAME() { + return "focustrap"; + } + activate() { + this._isActive || + (this._config.autofocus && this._config.trapElement.focus(), + N.off(document, Gi), + N.on(document, Ji, (t) => this._handleFocusin(t)), + N.on(document, Zi, (t) => this._handleKeydown(t)), + (this._isActive = !0)); + } + deactivate() { + this._isActive && ((this._isActive = !1), N.off(document, Gi)); + } + _handleFocusin(t) { + const { trapElement: e } = this._config; + if (t.target === document || t.target === e || e.contains(t.target)) + return; + const i = z.focusableChildren(e); + 0 === i.length + ? e.focus() + : this._lastTabNavDirection === tn + ? i[i.length - 1].focus() + : i[0].focus(); + } + _handleKeydown(t) { + "Tab" === t.key && + (this._lastTabNavDirection = t.shiftKey ? tn : "forward"); + } + } + const on = ".fixed-top, .fixed-bottom, .is-fixed, .sticky-top", + rn = ".sticky-top", + an = "padding-right", + ln = "margin-right"; + class cn { + constructor() { + this._element = document.body; + } + getWidth() { + const t = document.documentElement.clientWidth; + return Math.abs(window.innerWidth - t); + } + hide() { + const t = this.getWidth(); + this._disableOverFlow(), + this._setElementAttributes(this._element, an, (e) => e + t), + this._setElementAttributes(on, an, (e) => e + t), + this._setElementAttributes(rn, ln, (e) => e - t); + } + reset() { + this._resetElementAttributes(this._element, "overflow"), + this._resetElementAttributes(this._element, an), + this._resetElementAttributes(on, an), + this._resetElementAttributes(rn, ln); + } + isOverflowing() { + return this.getWidth() > 0; + } + _disableOverFlow() { + this._saveInitialAttribute(this._element, "overflow"), + (this._element.style.overflow = "hidden"); + } + _setElementAttributes(t, e, i) { + const n = this.getWidth(); + this._applyManipulationCallback(t, (t) => { + if ( + t !== this._element && + window.innerWidth > t.clientWidth + n + ) + return; + this._saveInitialAttribute(t, e); + const s = window.getComputedStyle(t).getPropertyValue(e); + t.style.setProperty(e, `${i(Number.parseFloat(s))}px`); + }); + } + _saveInitialAttribute(t, e) { + const i = t.style.getPropertyValue(e); + i && F.setDataAttribute(t, e, i); + } + _resetElementAttributes(t, e) { + this._applyManipulationCallback(t, (t) => { + const i = F.getDataAttribute(t, e); + null !== i + ? (F.removeDataAttribute(t, e), t.style.setProperty(e, i)) + : t.style.removeProperty(e); + }); + } + _applyManipulationCallback(t, e) { + if (o(t)) e(t); + else for (const i of z.find(t, this._element)) e(i); + } + } + const hn = ".bs.modal", + dn = `hide${hn}`, + un = `hidePrevented${hn}`, + fn = `hidden${hn}`, + pn = `show${hn}`, + mn = `shown${hn}`, + gn = `resize${hn}`, + _n = `click.dismiss${hn}`, + bn = `mousedown.dismiss${hn}`, + vn = `keydown.dismiss${hn}`, + yn = `click${hn}.data-api`, + wn = "modal-open", + An = "show", + En = "modal-static", + Tn = { backdrop: !0, focus: !0, keyboard: !0 }, + Cn = { + backdrop: "(boolean|string)", + focus: "boolean", + keyboard: "boolean", + }; + class On extends W { + constructor(t, e) { + super(t, e), + (this._dialog = z.findOne(".modal-dialog", this._element)), + (this._backdrop = this._initializeBackDrop()), + (this._focustrap = this._initializeFocusTrap()), + (this._isShown = !1), + (this._isTransitioning = !1), + (this._scrollBar = new cn()), + this._addEventListeners(); + } + static get Default() { + return Tn; + } + static get DefaultType() { + return Cn; + } + static get NAME() { + return "modal"; + } + toggle(t) { + return this._isShown ? this.hide() : this.show(t); + } + show(t) { + this._isShown || + this._isTransitioning || + N.trigger(this._element, pn, { relatedTarget: t }) + .defaultPrevented || + ((this._isShown = !0), + (this._isTransitioning = !0), + this._scrollBar.hide(), + document.body.classList.add(wn), + this._adjustDialog(), + this._backdrop.show(() => this._showElement(t))); + } + hide() { + this._isShown && + !this._isTransitioning && + (N.trigger(this._element, dn).defaultPrevented || + ((this._isShown = !1), + (this._isTransitioning = !0), + this._focustrap.deactivate(), + this._element.classList.remove(An), + this._queueCallback( + () => this._hideModal(), + this._element, + this._isAnimated() + ))); + } + dispose() { + N.off(window, hn), + N.off(this._dialog, hn), + this._backdrop.dispose(), + this._focustrap.deactivate(), + super.dispose(); + } + handleUpdate() { + this._adjustDialog(); + } + _initializeBackDrop() { + return new Ui({ + isVisible: Boolean(this._config.backdrop), + isAnimated: this._isAnimated(), + }); + } + _initializeFocusTrap() { + return new sn({ trapElement: this._element }); + } + _showElement(t) { + document.body.contains(this._element) || + document.body.append(this._element), + (this._element.style.display = "block"), + this._element.removeAttribute("aria-hidden"), + this._element.setAttribute("aria-modal", !0), + this._element.setAttribute("role", "dialog"), + (this._element.scrollTop = 0); + const e = z.findOne(".modal-body", this._dialog); + e && (e.scrollTop = 0), + d(this._element), + this._element.classList.add(An), + this._queueCallback( + () => { + this._config.focus && this._focustrap.activate(), + (this._isTransitioning = !1), + N.trigger(this._element, mn, { relatedTarget: t }); + }, + this._dialog, + this._isAnimated() + ); + } + _addEventListeners() { + N.on(this._element, vn, (t) => { + "Escape" === t.key && + (this._config.keyboard + ? this.hide() + : this._triggerBackdropTransition()); + }), + N.on(window, gn, () => { + this._isShown && + !this._isTransitioning && + this._adjustDialog(); + }), + N.on(this._element, bn, (t) => { + N.one(this._element, _n, (e) => { + this._element === t.target && + this._element === e.target && + ("static" !== this._config.backdrop + ? this._config.backdrop && this.hide() + : this._triggerBackdropTransition()); + }); + }); + } + _hideModal() { + (this._element.style.display = "none"), + this._element.setAttribute("aria-hidden", !0), + this._element.removeAttribute("aria-modal"), + this._element.removeAttribute("role"), + (this._isTransitioning = !1), + this._backdrop.hide(() => { + document.body.classList.remove(wn), + this._resetAdjustments(), + this._scrollBar.reset(), + N.trigger(this._element, fn); + }); + } + _isAnimated() { + return this._element.classList.contains("fade"); + } + _triggerBackdropTransition() { + if (N.trigger(this._element, un).defaultPrevented) return; + const t = + this._element.scrollHeight > + document.documentElement.clientHeight, + e = this._element.style.overflowY; + "hidden" === e || + this._element.classList.contains(En) || + (t || (this._element.style.overflowY = "hidden"), + this._element.classList.add(En), + this._queueCallback(() => { + this._element.classList.remove(En), + this._queueCallback(() => { + this._element.style.overflowY = e; + }, this._dialog); + }, this._dialog), + this._element.focus()); + } + _adjustDialog() { + const t = + this._element.scrollHeight > + document.documentElement.clientHeight, + e = this._scrollBar.getWidth(), + i = e > 0; + if (i && !t) { + const t = p() ? "paddingLeft" : "paddingRight"; + this._element.style[t] = `${e}px`; + } + if (!i && t) { + const t = p() ? "paddingRight" : "paddingLeft"; + this._element.style[t] = `${e}px`; + } + } + _resetAdjustments() { + (this._element.style.paddingLeft = ""), + (this._element.style.paddingRight = ""); + } + static jQueryInterface(t, e) { + return this.each(function () { + const i = On.getOrCreateInstance(this, t); + if ("string" == typeof t) { + if (void 0 === i[t]) + throw new TypeError(`No method named "${t}"`); + i[t](e); + } + }); + } + } + N.on(document, yn, '[data-bs-toggle="modal"]', function (t) { + const e = z.getElementFromSelector(this); + ["A", "AREA"].includes(this.tagName) && t.preventDefault(), + N.one(e, pn, (t) => { + t.defaultPrevented || + N.one(e, fn, () => { + a(this) && this.focus(); + }); + }); + const i = z.findOne(".modal.show"); + i && On.getInstance(i).hide(), On.getOrCreateInstance(e).toggle(this); + }), + R(On), + m(On); + const xn = ".bs.offcanvas", + kn = ".data-api", + Ln = `load${xn}${kn}`, + Sn = "show", + Dn = "showing", + $n = "hiding", + In = ".offcanvas.show", + Nn = `show${xn}`, + Pn = `shown${xn}`, + Mn = `hide${xn}`, + jn = `hidePrevented${xn}`, + Fn = `hidden${xn}`, + Hn = `resize${xn}`, + Wn = `click${xn}${kn}`, + Bn = `keydown.dismiss${xn}`, + zn = { backdrop: !0, keyboard: !0, scroll: !1 }, + Rn = { + backdrop: "(boolean|string)", + keyboard: "boolean", + scroll: "boolean", + }; + class qn extends W { + constructor(t, e) { + super(t, e), + (this._isShown = !1), + (this._backdrop = this._initializeBackDrop()), + (this._focustrap = this._initializeFocusTrap()), + this._addEventListeners(); + } + static get Default() { + return zn; + } + static get DefaultType() { + return Rn; + } + static get NAME() { + return "offcanvas"; + } + toggle(t) { + return this._isShown ? this.hide() : this.show(t); + } + show(t) { + this._isShown || + N.trigger(this._element, Nn, { relatedTarget: t }) + .defaultPrevented || + ((this._isShown = !0), + this._backdrop.show(), + this._config.scroll || new cn().hide(), + this._element.setAttribute("aria-modal", !0), + this._element.setAttribute("role", "dialog"), + this._element.classList.add(Dn), + this._queueCallback( + () => { + (this._config.scroll && !this._config.backdrop) || + this._focustrap.activate(), + this._element.classList.add(Sn), + this._element.classList.remove(Dn), + N.trigger(this._element, Pn, { relatedTarget: t }); + }, + this._element, + !0 + )); + } + hide() { + this._isShown && + (N.trigger(this._element, Mn).defaultPrevented || + (this._focustrap.deactivate(), + this._element.blur(), + (this._isShown = !1), + this._element.classList.add($n), + this._backdrop.hide(), + this._queueCallback( + () => { + this._element.classList.remove(Sn, $n), + this._element.removeAttribute("aria-modal"), + this._element.removeAttribute("role"), + this._config.scroll || new cn().reset(), + N.trigger(this._element, Fn); + }, + this._element, + !0 + ))); + } + dispose() { + this._backdrop.dispose(), + this._focustrap.deactivate(), + super.dispose(); + } + _initializeBackDrop() { + const t = Boolean(this._config.backdrop); + return new Ui({ + className: "offcanvas-backdrop", + isVisible: t, + isAnimated: !0, + rootElement: this._element.parentNode, + clickCallback: t + ? () => { + "static" !== this._config.backdrop + ? this.hide() + : N.trigger(this._element, jn); + } + : null, + }); + } + _initializeFocusTrap() { + return new sn({ trapElement: this._element }); + } + _addEventListeners() { + N.on(this._element, Bn, (t) => { + "Escape" === t.key && + (this._config.keyboard + ? this.hide() + : N.trigger(this._element, jn)); + }); + } + static jQueryInterface(t) { + return this.each(function () { + const e = qn.getOrCreateInstance(this, t); + if ("string" == typeof t) { + if ( + void 0 === e[t] || + t.startsWith("_") || + "constructor" === t + ) + throw new TypeError(`No method named "${t}"`); + e[t](this); + } + }); + } + } + N.on(document, Wn, '[data-bs-toggle="offcanvas"]', function (t) { + const e = z.getElementFromSelector(this); + if ( + (["A", "AREA"].includes(this.tagName) && t.preventDefault(), + l(this)) + ) + return; + N.one(e, Fn, () => { + a(this) && this.focus(); + }); + const i = z.findOne(In); + i && i !== e && qn.getInstance(i).hide(), + qn.getOrCreateInstance(e).toggle(this); + }), + N.on(window, Ln, () => { + for (const t of z.find(In)) qn.getOrCreateInstance(t).show(); + }), + N.on(window, Hn, () => { + for (const t of z.find( + "[aria-modal][class*=show][class*=offcanvas-]" + )) + "fixed" !== getComputedStyle(t).position && + qn.getOrCreateInstance(t).hide(); + }), + R(qn), + m(qn); + const Vn = { + "*": ["class", "dir", "id", "lang", "role", /^aria-[\w-]*$/i], + a: ["target", "href", "title", "rel"], + area: [], + b: [], + br: [], + col: [], + code: [], + div: [], + em: [], + hr: [], + h1: [], + h2: [], + h3: [], + h4: [], + h5: [], + h6: [], + i: [], + img: ["src", "srcset", "alt", "title", "width", "height"], + li: [], + ol: [], + p: [], + pre: [], + s: [], + small: [], + span: [], + sub: [], + sup: [], + strong: [], + u: [], + ul: [], + }, + Kn = new Set([ + "background", + "cite", + "href", + "itemtype", + "longdesc", + "poster", + "src", + "xlink:href", + ]), + Qn = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i, + Xn = (t, e) => { + const i = t.nodeName.toLowerCase(); + return e.includes(i) + ? !Kn.has(i) || Boolean(Qn.test(t.nodeValue)) + : e.filter((t) => t instanceof RegExp).some((t) => t.test(i)); + }, + Yn = { + allowList: Vn, + content: {}, + extraClass: "", + html: !1, + sanitize: !0, + sanitizeFn: null, + template: "
", + }, + Un = { + allowList: "object", + content: "object", + extraClass: "(string|function)", + html: "boolean", + sanitize: "boolean", + sanitizeFn: "(null|function)", + template: "string", + }, + Gn = { + entry: "(string|element|function|null)", + selector: "(string|element)", + }; + class Jn extends H { + constructor(t) { + super(), (this._config = this._getConfig(t)); + } + static get Default() { + return Yn; + } + static get DefaultType() { + return Un; + } + static get NAME() { + return "TemplateFactory"; + } + getContent() { + return Object.values(this._config.content) + .map((t) => this._resolvePossibleFunction(t)) + .filter(Boolean); + } + hasContent() { + return this.getContent().length > 0; + } + changeContent(t) { + return ( + this._checkContent(t), + (this._config.content = { ...this._config.content, ...t }), + this + ); + } + toHtml() { + const t = document.createElement("div"); + t.innerHTML = this._maybeSanitize(this._config.template); + for (const [e, i] of Object.entries(this._config.content)) + this._setContent(t, i, e); + const e = t.children[0], + i = this._resolvePossibleFunction(this._config.extraClass); + return i && e.classList.add(...i.split(" ")), e; + } + _typeCheckConfig(t) { + super._typeCheckConfig(t), this._checkContent(t.content); + } + _checkContent(t) { + for (const [e, i] of Object.entries(t)) + super._typeCheckConfig({ selector: e, entry: i }, Gn); + } + _setContent(t, e, i) { + const n = z.findOne(i, t); + n && + ((e = this._resolvePossibleFunction(e)) + ? o(e) + ? this._putElementInTemplate(r(e), n) + : this._config.html + ? (n.innerHTML = this._maybeSanitize(e)) + : (n.textContent = e) + : n.remove()); + } + _maybeSanitize(t) { + return this._config.sanitize + ? (function (t, e, i) { + if (!t.length) return t; + if (i && "function" == typeof i) return i(t); + const n = new window.DOMParser().parseFromString( + t, + "text/html" + ), + s = [].concat(...n.body.querySelectorAll("*")); + for (const t of s) { + const i = t.nodeName.toLowerCase(); + if (!Object.keys(e).includes(i)) { + t.remove(); + continue; + } + const n = [].concat(...t.attributes), + s = [].concat(e["*"] || [], e[i] || []); + for (const e of n) + Xn(e, s) || t.removeAttribute(e.nodeName); + } + return n.body.innerHTML; + })(t, this._config.allowList, this._config.sanitizeFn) + : t; + } + _resolvePossibleFunction(t) { + return g(t, [this]); + } + _putElementInTemplate(t, e) { + if (this._config.html) return (e.innerHTML = ""), void e.append(t); + e.textContent = t.textContent; + } + } + const Zn = new Set(["sanitize", "allowList", "sanitizeFn"]), + ts = "fade", + es = "show", + is = ".modal", + ns = "hide.bs.modal", + ss = "hover", + os = "focus", + rs = { + AUTO: "auto", + TOP: "top", + RIGHT: p() ? "left" : "right", + BOTTOM: "bottom", + LEFT: p() ? "right" : "left", + }, + as = { + allowList: Vn, + animation: !0, + boundary: "clippingParents", + container: !1, + customClass: "", + delay: 0, + fallbackPlacements: ["top", "right", "bottom", "left"], + html: !1, + offset: [0, 6], + placement: "top", + popperConfig: null, + sanitize: !0, + sanitizeFn: null, + selector: !1, + template: + '', + title: "", + trigger: "hover focus", + }, + ls = { + allowList: "object", + animation: "boolean", + boundary: "(string|element)", + container: "(string|element|boolean)", + customClass: "(string|function)", + delay: "(number|object)", + fallbackPlacements: "array", + html: "boolean", + offset: "(array|string|function)", + placement: "(string|function)", + popperConfig: "(null|object|function)", + sanitize: "boolean", + sanitizeFn: "(null|function)", + selector: "(string|boolean)", + template: "string", + title: "(string|element|function)", + trigger: "string", + }; + class cs extends W { + constructor(t, e) { + if (void 0 === vi) + throw new TypeError( + "Bootstrap's tooltips require Popper (https://popper.js.org)" + ); + super(t, e), + (this._isEnabled = !0), + (this._timeout = 0), + (this._isHovered = null), + (this._activeTrigger = {}), + (this._popper = null), + (this._templateFactory = null), + (this._newContent = null), + (this.tip = null), + this._setListeners(), + this._config.selector || this._fixTitle(); + } + static get Default() { + return as; + } + static get DefaultType() { + return ls; + } + static get NAME() { + return "tooltip"; + } + enable() { + this._isEnabled = !0; + } + disable() { + this._isEnabled = !1; + } + toggleEnabled() { + this._isEnabled = !this._isEnabled; + } + toggle() { + this._isEnabled && + ((this._activeTrigger.click = !this._activeTrigger.click), + this._isShown() ? this._leave() : this._enter()); + } + dispose() { + clearTimeout(this._timeout), + N.off(this._element.closest(is), ns, this._hideModalHandler), + this._element.getAttribute("data-bs-original-title") && + this._element.setAttribute( + "title", + this._element.getAttribute("data-bs-original-title") + ), + this._disposePopper(), + super.dispose(); + } + show() { + if ("none" === this._element.style.display) + throw new Error("Please use show on visible elements"); + if (!this._isWithContent() || !this._isEnabled) return; + const t = N.trigger( + this._element, + this.constructor.eventName("show") + ), + e = ( + c(this._element) || + this._element.ownerDocument.documentElement + ).contains(this._element); + if (t.defaultPrevented || !e) return; + this._disposePopper(); + const i = this._getTipElement(); + this._element.setAttribute( + "aria-describedby", + i.getAttribute("id") + ); + const { container: n } = this._config; + if ( + (this._element.ownerDocument.documentElement.contains( + this.tip + ) || + (n.append(i), + N.trigger( + this._element, + this.constructor.eventName("inserted") + )), + (this._popper = this._createPopper(i)), + i.classList.add(es), + "ontouchstart" in document.documentElement) + ) + for (const t of [].concat(...document.body.children)) + N.on(t, "mouseover", h); + this._queueCallback( + () => { + N.trigger( + this._element, + this.constructor.eventName("shown") + ), + !1 === this._isHovered && this._leave(), + (this._isHovered = !1); + }, + this.tip, + this._isAnimated() + ); + } + hide() { + if ( + this._isShown() && + !N.trigger(this._element, this.constructor.eventName("hide")) + .defaultPrevented + ) { + if ( + (this._getTipElement().classList.remove(es), + "ontouchstart" in document.documentElement) + ) + for (const t of [].concat(...document.body.children)) + N.off(t, "mouseover", h); + (this._activeTrigger.click = !1), + (this._activeTrigger[os] = !1), + (this._activeTrigger[ss] = !1), + (this._isHovered = null), + this._queueCallback( + () => { + this._isWithActiveTrigger() || + (this._isHovered || this._disposePopper(), + this._element.removeAttribute( + "aria-describedby" + ), + N.trigger( + this._element, + this.constructor.eventName("hidden") + )); + }, + this.tip, + this._isAnimated() + ); + } + } + update() { + this._popper && this._popper.update(); + } + _isWithContent() { + return Boolean(this._getTitle()); + } + _getTipElement() { + return ( + this.tip || + (this.tip = this._createTipElement( + this._newContent || this._getContentForTemplate() + )), + this.tip + ); + } + _createTipElement(t) { + const e = this._getTemplateFactory(t).toHtml(); + if (!e) return null; + e.classList.remove(ts, es), + e.classList.add(`bs-${this.constructor.NAME}-auto`); + const i = ((t) => { + do { + t += Math.floor(1e6 * Math.random()); + } while (document.getElementById(t)); + return t; + })(this.constructor.NAME).toString(); + return ( + e.setAttribute("id", i), + this._isAnimated() && e.classList.add(ts), + e + ); + } + setContent(t) { + (this._newContent = t), + this._isShown() && (this._disposePopper(), this.show()); + } + _getTemplateFactory(t) { + return ( + this._templateFactory + ? this._templateFactory.changeContent(t) + : (this._templateFactory = new Jn({ + ...this._config, + content: t, + extraClass: this._resolvePossibleFunction( + this._config.customClass + ), + })), + this._templateFactory + ); + } + _getContentForTemplate() { + return { ".tooltip-inner": this._getTitle() }; + } + _getTitle() { + return ( + this._resolvePossibleFunction(this._config.title) || + this._element.getAttribute("data-bs-original-title") + ); + } + _initializeOnDelegatedTarget(t) { + return this.constructor.getOrCreateInstance( + t.delegateTarget, + this._getDelegateConfig() + ); + } + _isAnimated() { + return ( + this._config.animation || + (this.tip && this.tip.classList.contains(ts)) + ); + } + _isShown() { + return this.tip && this.tip.classList.contains(es); + } + _createPopper(t) { + const e = g(this._config.placement, [this, t, this._element]), + i = rs[e.toUpperCase()]; + return bi(this._element, t, this._getPopperConfig(i)); + } + _getOffset() { + const { offset: t } = this._config; + return "string" == typeof t + ? t.split(",").map((t) => Number.parseInt(t, 10)) + : "function" == typeof t + ? (e) => t(e, this._element) + : t; + } + _resolvePossibleFunction(t) { + return g(t, [this._element]); + } + _getPopperConfig(t) { + const e = { + placement: t, + modifiers: [ + { + name: "flip", + options: { + fallbackPlacements: this._config.fallbackPlacements, + }, + }, + { name: "offset", options: { offset: this._getOffset() } }, + { + name: "preventOverflow", + options: { boundary: this._config.boundary }, + }, + { + name: "arrow", + options: { element: `.${this.constructor.NAME}-arrow` }, + }, + { + name: "preSetPlacement", + enabled: !0, + phase: "beforeMain", + fn: (t) => { + this._getTipElement().setAttribute( + "data-popper-placement", + t.state.placement + ); + }, + }, + ], + }; + return { ...e, ...g(this._config.popperConfig, [e]) }; + } + _setListeners() { + const t = this._config.trigger.split(" "); + for (const e of t) + if ("click" === e) + N.on( + this._element, + this.constructor.eventName("click"), + this._config.selector, + (t) => { + this._initializeOnDelegatedTarget(t).toggle(); + } + ); + else if ("manual" !== e) { + const t = + e === ss + ? this.constructor.eventName("mouseenter") + : this.constructor.eventName("focusin"), + i = + e === ss + ? this.constructor.eventName("mouseleave") + : this.constructor.eventName("focusout"); + N.on(this._element, t, this._config.selector, (t) => { + const e = this._initializeOnDelegatedTarget(t); + (e._activeTrigger["focusin" === t.type ? os : ss] = !0), + e._enter(); + }), + N.on(this._element, i, this._config.selector, (t) => { + const e = this._initializeOnDelegatedTarget(t); + (e._activeTrigger["focusout" === t.type ? os : ss] = + e._element.contains(t.relatedTarget)), + e._leave(); + }); + } + (this._hideModalHandler = () => { + this._element && this.hide(); + }), + N.on(this._element.closest(is), ns, this._hideModalHandler); + } + _fixTitle() { + const t = this._element.getAttribute("title"); + t && + (this._element.getAttribute("aria-label") || + this._element.textContent.trim() || + this._element.setAttribute("aria-label", t), + this._element.setAttribute("data-bs-original-title", t), + this._element.removeAttribute("title")); + } + _enter() { + this._isShown() || this._isHovered + ? (this._isHovered = !0) + : ((this._isHovered = !0), + this._setTimeout(() => { + this._isHovered && this.show(); + }, this._config.delay.show)); + } + _leave() { + this._isWithActiveTrigger() || + ((this._isHovered = !1), + this._setTimeout(() => { + this._isHovered || this.hide(); + }, this._config.delay.hide)); + } + _setTimeout(t, e) { + clearTimeout(this._timeout), (this._timeout = setTimeout(t, e)); + } + _isWithActiveTrigger() { + return Object.values(this._activeTrigger).includes(!0); + } + _getConfig(t) { + const e = F.getDataAttributes(this._element); + for (const t of Object.keys(e)) Zn.has(t) && delete e[t]; + return ( + (t = { ...e, ...("object" == typeof t && t ? t : {}) }), + (t = this._mergeConfigObj(t)), + (t = this._configAfterMerge(t)), + this._typeCheckConfig(t), + t + ); + } + _configAfterMerge(t) { + return ( + (t.container = + !1 === t.container ? document.body : r(t.container)), + "number" == typeof t.delay && + (t.delay = { show: t.delay, hide: t.delay }), + "number" == typeof t.title && (t.title = t.title.toString()), + "number" == typeof t.content && + (t.content = t.content.toString()), + t + ); + } + _getDelegateConfig() { + const t = {}; + for (const [e, i] of Object.entries(this._config)) + this.constructor.Default[e] !== i && (t[e] = i); + return (t.selector = !1), (t.trigger = "manual"), t; + } + _disposePopper() { + this._popper && (this._popper.destroy(), (this._popper = null)), + this.tip && (this.tip.remove(), (this.tip = null)); + } + static jQueryInterface(t) { + return this.each(function () { + const e = cs.getOrCreateInstance(this, t); + if ("string" == typeof t) { + if (void 0 === e[t]) + throw new TypeError(`No method named "${t}"`); + e[t](); + } + }); + } + } + m(cs); + const hs = { + ...cs.Default, + content: "", + offset: [0, 8], + placement: "right", + template: + '', + trigger: "click", + }, + ds = { ...cs.DefaultType, content: "(null|string|element|function)" }; + class us extends cs { + static get Default() { + return hs; + } + static get DefaultType() { + return ds; + } + static get NAME() { + return "popover"; + } + _isWithContent() { + return this._getTitle() || this._getContent(); + } + _getContentForTemplate() { + return { + ".popover-header": this._getTitle(), + ".popover-body": this._getContent(), + }; + } + _getContent() { + return this._resolvePossibleFunction(this._config.content); + } + static jQueryInterface(t) { + return this.each(function () { + const e = us.getOrCreateInstance(this, t); + if ("string" == typeof t) { + if (void 0 === e[t]) + throw new TypeError(`No method named "${t}"`); + e[t](); + } + }); + } + } + m(us); + const fs = ".bs.scrollspy", + ps = `activate${fs}`, + ms = `click${fs}`, + gs = `load${fs}.data-api`, + _s = "active", + bs = "[href]", + vs = ".nav-link", + ys = `${vs}, .nav-item > ${vs}, .list-group-item`, + ws = { + offset: null, + rootMargin: "0px 0px -25%", + smoothScroll: !1, + target: null, + threshold: [0.1, 0.5, 1], + }, + As = { + offset: "(number|null)", + rootMargin: "string", + smoothScroll: "boolean", + target: "element", + threshold: "array", + }; + class Es extends W { + constructor(t, e) { + super(t, e), + (this._targetLinks = new Map()), + (this._observableSections = new Map()), + (this._rootElement = + "visible" === getComputedStyle(this._element).overflowY + ? null + : this._element), + (this._activeTarget = null), + (this._observer = null), + (this._previousScrollData = { + visibleEntryTop: 0, + parentScrollTop: 0, + }), + this.refresh(); + } + static get Default() { + return ws; + } + static get DefaultType() { + return As; + } + static get NAME() { + return "scrollspy"; + } + refresh() { + this._initializeTargetsAndObservables(), + this._maybeEnableSmoothScroll(), + this._observer + ? this._observer.disconnect() + : (this._observer = this._getNewObserver()); + for (const t of this._observableSections.values()) + this._observer.observe(t); + } + dispose() { + this._observer.disconnect(), super.dispose(); + } + _configAfterMerge(t) { + return ( + (t.target = r(t.target) || document.body), + (t.rootMargin = t.offset + ? `${t.offset}px 0px -30%` + : t.rootMargin), + "string" == typeof t.threshold && + (t.threshold = t.threshold + .split(",") + .map((t) => Number.parseFloat(t))), + t + ); + } + _maybeEnableSmoothScroll() { + this._config.smoothScroll && + (N.off(this._config.target, ms), + N.on(this._config.target, ms, bs, (t) => { + const e = this._observableSections.get(t.target.hash); + if (e) { + t.preventDefault(); + const i = this._rootElement || window, + n = e.offsetTop - this._element.offsetTop; + if (i.scrollTo) + return void i.scrollTo({ + top: n, + behavior: "smooth", + }); + i.scrollTop = n; + } + })); + } + _getNewObserver() { + const t = { + root: this._rootElement, + threshold: this._config.threshold, + rootMargin: this._config.rootMargin, + }; + return new IntersectionObserver( + (t) => this._observerCallback(t), + t + ); + } + _observerCallback(t) { + const e = (t) => this._targetLinks.get(`#${t.target.id}`), + i = (t) => { + (this._previousScrollData.visibleEntryTop = + t.target.offsetTop), + this._process(e(t)); + }, + n = (this._rootElement || document.documentElement).scrollTop, + s = n >= this._previousScrollData.parentScrollTop; + this._previousScrollData.parentScrollTop = n; + for (const o of t) { + if (!o.isIntersecting) { + (this._activeTarget = null), this._clearActiveClass(e(o)); + continue; + } + const t = + o.target.offsetTop >= + this._previousScrollData.visibleEntryTop; + if (s && t) { + if ((i(o), !n)) return; + } else s || t || i(o); + } + } + _initializeTargetsAndObservables() { + (this._targetLinks = new Map()), + (this._observableSections = new Map()); + const t = z.find(bs, this._config.target); + for (const e of t) { + if (!e.hash || l(e)) continue; + const t = z.findOne(decodeURI(e.hash), this._element); + a(t) && + (this._targetLinks.set(decodeURI(e.hash), e), + this._observableSections.set(e.hash, t)); + } + } + _process(t) { + this._activeTarget !== t && + (this._clearActiveClass(this._config.target), + (this._activeTarget = t), + t.classList.add(_s), + this._activateParents(t), + N.trigger(this._element, ps, { relatedTarget: t })); + } + _activateParents(t) { + if (t.classList.contains("dropdown-item")) + z.findOne( + ".dropdown-toggle", + t.closest(".dropdown") + ).classList.add(_s); + else + for (const e of z.parents(t, ".nav, .list-group")) + for (const t of z.prev(e, ys)) t.classList.add(_s); + } + _clearActiveClass(t) { + t.classList.remove(_s); + const e = z.find(`${bs}.${_s}`, t); + for (const t of e) t.classList.remove(_s); + } + static jQueryInterface(t) { + return this.each(function () { + const e = Es.getOrCreateInstance(this, t); + if ("string" == typeof t) { + if ( + void 0 === e[t] || + t.startsWith("_") || + "constructor" === t + ) + throw new TypeError(`No method named "${t}"`); + e[t](); + } + }); + } + } + N.on(window, gs, () => { + for (const t of z.find('[data-bs-spy="scroll"]')) + Es.getOrCreateInstance(t); + }), + m(Es); + const Ts = ".bs.tab", + Cs = `hide${Ts}`, + Os = `hidden${Ts}`, + xs = `show${Ts}`, + ks = `shown${Ts}`, + Ls = `click${Ts}`, + Ss = `keydown${Ts}`, + Ds = `load${Ts}`, + $s = "ArrowLeft", + Is = "ArrowRight", + Ns = "ArrowUp", + Ps = "ArrowDown", + Ms = "Home", + js = "End", + Fs = "active", + Hs = "fade", + Ws = "show", + Bs = ".dropdown-toggle", + zs = `:not(${Bs})`, + Rs = + '[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]', + qs = `.nav-link${zs}, .list-group-item${zs}, [role="tab"]${zs}, ${Rs}`, + Vs = `.${Fs}[data-bs-toggle="tab"], .${Fs}[data-bs-toggle="pill"], .${Fs}[data-bs-toggle="list"]`; + class Ks extends W { + constructor(t) { + super(t), + (this._parent = this._element.closest( + '.list-group, .nav, [role="tablist"]' + )), + this._parent && + (this._setInitialAttributes( + this._parent, + this._getChildren() + ), + N.on(this._element, Ss, (t) => this._keydown(t))); + } + static get NAME() { + return "tab"; + } + show() { + const t = this._element; + if (this._elemIsActive(t)) return; + const e = this._getActiveElem(), + i = e ? N.trigger(e, Cs, { relatedTarget: t }) : null; + N.trigger(t, xs, { relatedTarget: e }).defaultPrevented || + (i && i.defaultPrevented) || + (this._deactivate(e, t), this._activate(t, e)); + } + _activate(t, e) { + t && + (t.classList.add(Fs), + this._activate(z.getElementFromSelector(t)), + this._queueCallback( + () => { + "tab" === t.getAttribute("role") + ? (t.removeAttribute("tabindex"), + t.setAttribute("aria-selected", !0), + this._toggleDropDown(t, !0), + N.trigger(t, ks, { relatedTarget: e })) + : t.classList.add(Ws); + }, + t, + t.classList.contains(Hs) + )); + } + _deactivate(t, e) { + t && + (t.classList.remove(Fs), + t.blur(), + this._deactivate(z.getElementFromSelector(t)), + this._queueCallback( + () => { + "tab" === t.getAttribute("role") + ? (t.setAttribute("aria-selected", !1), + t.setAttribute("tabindex", "-1"), + this._toggleDropDown(t, !1), + N.trigger(t, Os, { relatedTarget: e })) + : t.classList.remove(Ws); + }, + t, + t.classList.contains(Hs) + )); + } + _keydown(t) { + if (![$s, Is, Ns, Ps, Ms, js].includes(t.key)) return; + t.stopPropagation(), t.preventDefault(); + const e = this._getChildren().filter((t) => !l(t)); + let i; + if ([Ms, js].includes(t.key)) + i = e[t.key === Ms ? 0 : e.length - 1]; + else { + const n = [Is, Ps].includes(t.key); + i = b(e, t.target, n, !0); + } + i && + (i.focus({ preventScroll: !0 }), + Ks.getOrCreateInstance(i).show()); + } + _getChildren() { + return z.find(qs, this._parent); + } + _getActiveElem() { + return ( + this._getChildren().find((t) => this._elemIsActive(t)) || null + ); + } + _setInitialAttributes(t, e) { + this._setAttributeIfNotExists(t, "role", "tablist"); + for (const t of e) this._setInitialAttributesOnChild(t); + } + _setInitialAttributesOnChild(t) { + t = this._getInnerElement(t); + const e = this._elemIsActive(t), + i = this._getOuterElement(t); + t.setAttribute("aria-selected", e), + i !== t && + this._setAttributeIfNotExists(i, "role", "presentation"), + e || t.setAttribute("tabindex", "-1"), + this._setAttributeIfNotExists(t, "role", "tab"), + this._setInitialAttributesOnTargetPanel(t); + } + _setInitialAttributesOnTargetPanel(t) { + const e = z.getElementFromSelector(t); + e && + (this._setAttributeIfNotExists(e, "role", "tabpanel"), + t.id && + this._setAttributeIfNotExists( + e, + "aria-labelledby", + `${t.id}` + )); + } + _toggleDropDown(t, e) { + const i = this._getOuterElement(t); + if (!i.classList.contains("dropdown")) return; + const n = (t, n) => { + const s = z.findOne(t, i); + s && s.classList.toggle(n, e); + }; + n(Bs, Fs), + n(".dropdown-menu", Ws), + i.setAttribute("aria-expanded", e); + } + _setAttributeIfNotExists(t, e, i) { + t.hasAttribute(e) || t.setAttribute(e, i); + } + _elemIsActive(t) { + return t.classList.contains(Fs); + } + _getInnerElement(t) { + return t.matches(qs) ? t : z.findOne(qs, t); + } + _getOuterElement(t) { + return t.closest(".nav-item, .list-group-item") || t; + } + static jQueryInterface(t) { + return this.each(function () { + const e = Ks.getOrCreateInstance(this); + if ("string" == typeof t) { + if ( + void 0 === e[t] || + t.startsWith("_") || + "constructor" === t + ) + throw new TypeError(`No method named "${t}"`); + e[t](); + } + }); + } + } + N.on(document, Ls, Rs, function (t) { + ["A", "AREA"].includes(this.tagName) && t.preventDefault(), + l(this) || Ks.getOrCreateInstance(this).show(); + }), + N.on(window, Ds, () => { + for (const t of z.find(Vs)) Ks.getOrCreateInstance(t); + }), + m(Ks); + const Qs = ".bs.toast", + Xs = `mouseover${Qs}`, + Ys = `mouseout${Qs}`, + Us = `focusin${Qs}`, + Gs = `focusout${Qs}`, + Js = `hide${Qs}`, + Zs = `hidden${Qs}`, + to = `show${Qs}`, + eo = `shown${Qs}`, + io = "hide", + no = "show", + so = "showing", + oo = { animation: "boolean", autohide: "boolean", delay: "number" }, + ro = { animation: !0, autohide: !0, delay: 5e3 }; + class ao extends W { + constructor(t, e) { + super(t, e), + (this._timeout = null), + (this._hasMouseInteraction = !1), + (this._hasKeyboardInteraction = !1), + this._setListeners(); + } + static get Default() { + return ro; + } + static get DefaultType() { + return oo; + } + static get NAME() { + return "toast"; + } + show() { + N.trigger(this._element, to).defaultPrevented || + (this._clearTimeout(), + this._config.animation && this._element.classList.add("fade"), + this._element.classList.remove(io), + d(this._element), + this._element.classList.add(no, so), + this._queueCallback( + () => { + this._element.classList.remove(so), + N.trigger(this._element, eo), + this._maybeScheduleHide(); + }, + this._element, + this._config.animation + )); + } + hide() { + this.isShown() && + (N.trigger(this._element, Js).defaultPrevented || + (this._element.classList.add(so), + this._queueCallback( + () => { + this._element.classList.add(io), + this._element.classList.remove(so, no), + N.trigger(this._element, Zs); + }, + this._element, + this._config.animation + ))); + } + dispose() { + this._clearTimeout(), + this.isShown() && this._element.classList.remove(no), + super.dispose(); + } + isShown() { + return this._element.classList.contains(no); + } + _maybeScheduleHide() { + this._config.autohide && + (this._hasMouseInteraction || + this._hasKeyboardInteraction || + (this._timeout = setTimeout(() => { + this.hide(); + }, this._config.delay))); + } + _onInteraction(t, e) { + switch (t.type) { + case "mouseover": + case "mouseout": + this._hasMouseInteraction = e; + break; + case "focusin": + case "focusout": + this._hasKeyboardInteraction = e; + } + if (e) return void this._clearTimeout(); + const i = t.relatedTarget; + this._element === i || + this._element.contains(i) || + this._maybeScheduleHide(); + } + _setListeners() { + N.on(this._element, Xs, (t) => this._onInteraction(t, !0)), + N.on(this._element, Ys, (t) => this._onInteraction(t, !1)), + N.on(this._element, Us, (t) => this._onInteraction(t, !0)), + N.on(this._element, Gs, (t) => this._onInteraction(t, !1)); + } + _clearTimeout() { + clearTimeout(this._timeout), (this._timeout = null); + } + static jQueryInterface(t) { + return this.each(function () { + const e = ao.getOrCreateInstance(this, t); + if ("string" == typeof t) { + if (void 0 === e[t]) + throw new TypeError(`No method named "${t}"`); + e[t](this); + } + }); + } + } + return ( + R(ao), + m(ao), + { + Alert: Q, + Button: Y, + Carousel: xt, + Collapse: Bt, + Dropdown: qi, + Modal: On, + Offcanvas: qn, + Popover: us, + ScrollSpy: Es, + Tab: Ks, + Toast: ao, + Tooltip: cs, + } + ); +}); +//# sourceMappingURL=bootstrap.bundle.min.js.map diff --git a/data_embed/index.html b/data_embed/index.html new file mode 100644 index 0000000..dcf0771 --- /dev/null +++ b/data_embed/index.html @@ -0,0 +1,1082 @@ + + + + + + LoRa iGate + + + + + +
+ +
+
+
+
+
+ +
+ + +
+ + +
+
+
+
+ +
+
+
+ + + + + Identification +
+ Add your ham callsign and SSID. Optionally, + you can leave a comment describing your + station. +
+
+
+ + +
+
+ + +
+
+
+
+ +
+
+
+ + + + + + + Auto AP +
+ Add your password and power off timeout to + auto access point. Auto AP will start if + there is no WiFi connection. Timeout will + count from startup or last client + disconnected. +
+
+
+ + +
+
+ + +
+ Set to 0 if you don't + want this option. +
+
+
+
+
+ +
+
+
+ + + + + + + WiFi Access +
+ Add all Wi-Fi Networks intended to be used, + and their respective coordinates to + georeference the iGate location. +
+
+ + +
+ +
+
+ +
+
+
+
+
+ +
+
+
+ + + + + + + APRS-IS +
+ Enter you APRS-IS server and + credentials. +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ +
+ + Km +
+
+
+
+
+
+ +
+
+
+ + + + Display +
+ OLED screen configuration. +
+
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+
+
+ +
+
+
+ + + + Digipeating +
+ Define the location coordinates for the + Digipeater mode, and a special comment if + you like. +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+
+ +
+
+
+ + + + OTA +
+ Set your username and password to allow + firmware updating over-the-air. +
+
+
+ + +
+
+ + +
+
+
+
+ +
+
+
+ + + + + Beaconing +
+ Set APRS beacon attributes. +
+
+
+
+ +
+ + minutes +
+
+
+ +
+ + minutes +
+
+
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ +
+
+
+ + + + Syslog +
+ Broadcast the system log over the + network. +
+
+
+ + +
+
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+
+ + + + LoRa +
+ Config LoRa APRS Xmitting settings. +
+
+
+
+ +
+ + + Hz +
+
+
+ +
+ + Hz +
+
+
+
+
+ +
+ +
+
+
+ + +
+
+
+
+ + +
+
+ +
+ + dBm +
+
+
+
+
+
+
+ +
+
+ + + + + + diff --git a/data_embed/script.js b/data_embed/script.js new file mode 100644 index 0000000..eed32c2 --- /dev/null +++ b/data_embed/script.js @@ -0,0 +1,388 @@ +// Custom scripts + +let currentSettings = null; + +function backupSettings() { + const data = + "data:text/json;charset=utf-8," + + encodeURIComponent(JSON.stringify(currentSettings)); + const a = document.createElement("a"); + a.setAttribute("href", data); + a.setAttribute("download", "iGateConfigurationBackup.json"); + a.click(); +} + +document.getElementById("backup").onclick = backupSettings; + +document.getElementById("restore").onclick = function (e) { + e.preventDefault(); + + document.querySelector("input[type=file]").click(); +}; + +document.querySelector("input[type=file]").onchange = function () { + const files = document.querySelector("input[type=file]").files; + + if (!files.length) return; + + const file = files.item(0); + + const reader = new FileReader(); + reader.readAsText(file); + reader.onload = () => { + const data = JSON.parse(reader.result); + + loadSettings(data); + }; +}; + +function fetchSettings() { + fetch("/configuration.json") + .then((response) => response.json()) + .then((settings) => { + loadSettings(settings); + }) + .catch((err) => { + console.error(err); + + alert(`Failed to load configuration`); + }); +} + +const alwaysOnCheckbox = document.querySelector( + 'input[name="display.alwaysOn"]' +); +const timeoutInput = document.querySelector('input[name="display.timeout"]'); + +alwaysOnCheckbox.addEventListener("change", function () { + timeoutInput.disabled = this.checked; +}); + +// timeoutInput.addEventListener("change", function () { +// alwaysOnCheckbox.disabled = this.value !== ""; +// }); + +const logCheckbox = document.querySelector('input[name="syslog.active"]'); +const serverField = document.querySelector('input[name="syslog.server"]'); +const portField = document.querySelector('input[name="syslog.port"]'); + +logCheckbox.addEventListener("change", function () { + serverField.disabled = !this.checked; + portField.disabled = !this.checked; +}); + +function loadSettings(settings) { + currentSettings = settings; + // General + document.getElementById("callsign").value = settings.callsign; + document.getElementById("stationMode").value = settings.stationMode; + document.getElementById("bme.active").checked = settings.bme.active; + document.getElementById("iGateComment").value = settings.iGateComment; + + document.querySelector(".list-networks").innerHTML = ""; + + // Networks + const wifiNetworks = settings.wifi.AP; + const networksContainer = document.querySelector(".list-networks"); + + let networkCount = 0; + + wifiNetworks.forEach((network) => { + const networkElement = document.createElement("div"); + networkElement.classList.add("row", "network", "border-bottom", "py-2"); + + // Increment the name, id, and for attributes + const attributeName = `wifi.AP.${networkCount}`; + networkElement.innerHTML = ` +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+ `; + networksContainer.appendChild(networkElement); + networkCount++; + }); + + // APRS-IS + document.getElementById("aprs_is.server").value = settings.aprs_is.server; + document.getElementById("aprs_is.port").value = settings.aprs_is.port; + document.getElementById("aprs_is.reportingDistance").value = + settings.aprs_is.reportingDistance; + document.getElementById("aprs_is.passcode").value = + settings.aprs_is.passcode; + + // Display + document.getElementById("display.alwaysOn").checked = + settings.display.alwaysOn; + document.getElementById("display.turn180").checked = + settings.display.turn180; + document.getElementById("display.timeout").value = settings.display.timeout; + + if (settings.display.alwaysOn) { + timeoutInput.disabled = true; + } + + // WiFi Auto AP + document.getElementById("wifi.autoAP.password").value = + settings.wifi.autoAP.password; + document.getElementById("wifi.autoAP.powerOff").value = + settings.wifi.autoAP.powerOff; + + // Digi + document.getElementById("digi.comment").value = settings.digi.comment; + document.getElementById("digi.latitude").value = settings.digi.latitude; + document.getElementById("digi.longitude").value = settings.digi.longitude; + + // OTA + document.getElementById("ota.username").value = settings.ota.username; + document.getElementById("ota.password").value = settings.ota.password; + + // Beacon + document.getElementById("other.beaconInterval").value = + settings.other.beaconInterval; + document.getElementById("other.rememberStationTime").value = + settings.other.rememberStationTime; + document.getElementById("other.sendBatteryVoltage").checked = + settings.other.sendBatteryVoltage; + document.getElementById("other.externalVoltageMeasurement").checked = + settings.other.externalVoltageMeasurement; + document.getElementById("other.externalVoltagePin").value = + settings.other.externalVoltagePin; + document.getElementById("other.igateSendsLoRaBeacon").value = + settings.other.igateSendsLoRaBeacon; + document.getElementById("other.igateRepeatLoRaPackets").value = + settings.other.igateRepeatLoRaPackets; + + // Syslog + document.getElementById("syslog.active").checked = settings.syslog.active; + document.getElementById("syslog.server").value = settings.syslog.server; + document.getElementById("syslog.port").value = settings.syslog.port; + + if (settings.syslog.active) { + serverField.disabled = false; + portField.disabled = false; + } + + // LoRa + document.getElementById("lora.digirepeaterTxFreq").value = + settings.lora.digirepeaterTxFreq; + document.getElementById("lora.iGateFreq").value = settings.lora.iGateFreq; + document.getElementById("lora.digirepeaterRxFreq").value = + settings.lora.digirepeaterRxFreq; + document.getElementById("lora.spreadingFactor").value = + settings.lora.spreadingFactor; + document.getElementById("lora.signalBandwidth").value = + settings.lora.signalBandwidth; + document.getElementById("lora.codingRate4").value = + settings.lora.codingRate4; + document.getElementById("lora.power").value = settings.lora.power; + + updateImage(); +} + +const bmeCheckbox = document.querySelector("input[name='bme.active']"); + +const stationModeSelect = document.querySelector("select[name='stationMode']"); + +const digirepeaterTxFreqInput = document.querySelector( + "input[name='lora.digirepeaterRxFreq']" +); + +function updateImage() { + const image = document.querySelector("img"); + + const isChecked = bmeCheckbox.checked; + + if (isChecked) { + image.src = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAlUSURBVHgB7Z1LiBxFGMe/nt1s1nd8Pw4uERVfCJqDB8XsSUHER1DiTYweRBCTgBdBETyIh6AQDx6SoAc1EjRCCOJBkg1CDoaYBSGHRDQhia+8dJdN9jVtfTXduz09X01XdVd3V/V+P/g2mZqame7+d1V99VV1FQDDMAzjKAE0lnCt+HNz9OI+YbcTmfYL+yv6/wFxOX6EhtEAgaWQLwgbEbZS2FVQ7LzmhE0KG4fODbC7icI7DAoa7hR2TlhYkc0KOyJsi7ARYGwjRT0YXejQAZuKbrJHgMkLlhR5EaccEVVlp4S9D4wuWCpkddh2XFiqGt8JXIWrWBDWF0FVhjfmXhZ6AVkVN0FYSugtsLSRVZpvVbGpoQ+xHpYW0it23XmybdgLGIHmI9snX0SxbbNVl+YKI1ny7j0kbAUwY+LSj0IFtKASZDjxNzAWNyzBnGA1dCJxI1AyFQgsPcntYFxbOCNGWeDNfjS6+Uuj5CoaHQt4AHJRlsDOja/giW4Ux/URlECJZ4t9W3KITvfzUA7ODqBtFcf2ClimpCq6qLhLkpehhMBICbdzXnFDzbQ26BEAfXq6abVhtSRbPrO84rYVaVT6LOiBldMAkT5IpKluhtqwJrLFKhoDGFwtW2JdAe+6lTRLAsu2YzUwtsDq5EuwMKHAQr0k77TtUAiuohVcEHa3OLZjBp/pKrQFz0pGYkRnnbxqBrDAfRDXN7jDIL9VgU/B4tRU3c8QaXNE+sXI0vwNelwWWRoqWoqiO+1dmzhdLeULM3As11RcJifriPa4pWM5BZY/9jQwVYFVyS7IQd4SvAsa/VSEk6yIak0jcggsp4jymG49PG06xGg6hGfBa6acrBlh86m0icjSHAU9rokszS1E2nKg73UnK6nYq1YVzq50U6E+g8JdIop56BUYu4D/LbwaHb0C9uzB89ILlgXBbqAP9UYizauxZ3EBpkXsYfkO+u3n83rRsvQ+CkwNpGelLPtEkbGVmdAHLL3sWLmB8IHm39DJqCkwl173CN7RyaVbgksuvdTkOAxTziVs3vA7Z1Kfj83ZiXimiFJ8IXMwQtdhehisoRrETwuIYcrJxOtlYMZZ6DwLnmZe8fvpe92H1mhom/hzZ3faP6ZetOz3luA5M8WQBeU2gOMrAW6NR5taq1ZNGHvRLwLjKqKauemtfhkyBJbOFQ8oOM3Amn7vZpXgt4FxHeFsTSh9pCyBn4BKoDxbdIZmEzYHZlxMfT62xnjRCYY3qN7p4zxx9VwPJjfcQt6HIBr/PXPmzFAyR78S/CownhDcpHqnn8CPA+MRk69Tqf0EvgsYjxh8ikzt84lLoHYW26MwzOMMNcGB0mXwfjKVzuzeoiGBceSw6eL29AAwLtuanp7u0lRVRa8GxiG0Vi8QReDwI+12u5U0lcBG834YV7i252F7lcArgfGQ4Z7hQ5XAy4HxkODqdIrKi3bAg2bMGbhd9Da63FEe5/UWKo7evgQdq2QKUUXzKqlNgmqDnwXGUwaG0ikWl3Bg6icYTqc0uA1WjfXqPmxuskqPivon7rGT5QWqJabCnrTTp0NbD4AzPsACN4qwZ14TJfABYDylPZVOIdpg3MatCUNteDPPEOnUwi6D0HspVE7WgGaaqnKs1vFqsJOlEniaSFMJ3FLkTbNMkbeOh8r1nCzTOaqME0z/kk5RCTwJTCNQCXwaGA+Z2J1OUbXBJ8H7lWOx/b1ApFOVE0ax0pciXkssjWqofIB4XXUka6pnGUCVwN+C9/OysBKiBLqUSMPhb0ogyju+mki7DnqFx7AwJXCZjtc93wBcc1nWryHGC24xdTP/b+ffs+2kKQSWy9eyJ+0VbXLJ4X6hyt+B8YipH6jUfgKPAeMRP22jUvtFsj4FudVLFVBhQXRwkguvmAbdTgC9tvQJIg2/O32v428PEXmpGcX4GNeVqTR0xignzXQxGR3aIv765HEgLlKfqyZj0tjP4BmWTpIsEPM4QBTPWujynbKKxffCngEP2bPnXQiC3hYoDHUHBlSx6O5ZMePjZ2H9+vNQL2eVG2plCbwJPBV4dPReqI46BcYx4JGvVe9mDPhjNU2GgxhnmPmu37s6Mzq+gNKJq8OkoTMynLAhYChObO73ro7A74HVGQABYdR+EizwItQ1Q+b/FOHJ/YmM6GR1PVOqIbCMav0KjGOgyFMfwqL3TJrupLuXgHEMdK6u+zgrl6bA0tnS3SyBqYSZz3VymUybLbEUU+0w9uCWJ6yMCJANsF8d+whJi8eDqbazMKL0Xq61jplB/E9GtrAUlzARAA8j7cddAUlRz58fhLGxWRGo6J0VGQRtkd5OpeVdmWfhG8R3BIpjXWR8HPPcAPR4cFlzGuc26eY03VYHlwjYB4VuRd29C7H7TU1xpWLJk9A7uolrXZrsFp5GtbmlyYD/MJF3Wcbvqx5TWUBEVQauB/ruwYPWXsqQOhBsi/cBUyMXXjPJbSgwEowCTwaoifYh0fbuMPlEDoElbwJTNaJQ/fEcGFKkLT0o/jwAVjDZIJp6MgHb295HKYuT7WR1oJ5sUM3KzCpTqjZ4dqNo0zdnHEhPG1xEYFzL4zBYGS9mgfuDVfPAKsg+kJ4fzVtFQxTCxL7xUlrxsw5Ed2JiDeSkgMBI8JX4sw2YssDC8xjAimOQk4ICI3JveQ5jlsMHUdc0NxafrQjPQe6No1WOBdUGq3Yuq6qlMHk+uNAiLlujwhN/WRqtNtimwBadLvl9JeUtSiWr7MSbQMeovLVML86iwIhNkZeswGlxERN33JYXTSE967uB53HlhRK3EJYFRljknFgXFylBYMSGyJW0dSVidEw/lyEuUpLAyILIBbpQgYG1KjLd49EGveUHwW/CvTj8ztZlom9XaHcb1R04mLKqwJORJ+WTCGXZFHQmTxRBS+CKGy/ZjRKB87wBkUYwFo2pF6WOblIW2C7LjSNwDZAQlhY4SWKDJXF9AKuo8JQnVWpR2wv2ca0NViHb5lmPxDKxUxbaWlPSgrtCuKVBQp+D+vZ/dFXgGK+FrlPYGNcFjpFVtw9tNHb9DkL1VbEKXwSOkc7YTuj0HV0SFm++LeAevgmcZEHsOko2ltQjHVGd3jzMZ4HTyGp8byS47RJ+LiHoWvAUF4dhCiLFuBk6i6nGDxLdCfQkBFyx9GT0fxwUwQW1DxSdB8UwDMMwDMM0nP8Bl3lxJTtDbOQAAAAASUVORK5CYII="; + } else { + const selectedValue = stationModeSelect.value; + + if (selectedValue === "1") { + if (bmeCheckbox.checked) { + image.src = + "data:image/png;base64,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"; + } else { + image.src = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAQ6SURBVHgB7d0/axRBGMfx50IMWPoKkpA02gUsLfZdqOCLOPBFWAliHbCUVNpaxQNrIY0WVjZaJlUsIsZ5uDu9bGb/zMwzs/PM/L7wIG4Ocflwl7ndvVsihBBCmTajunpk5uHG33+aOSGkvhdmLs1cW+bKzDEhle2a+UZ22Pacrx6PlMRYXc/arrkkIKvIBxfISgrBBXLmSeACOdMkcYGcWTFwgZxJMXGBPHEpcIE8USlxgSzcbGD2KD0ukIXKGRfIAuWOC+TANOACOSAtuEAemQ10a2P2KV9cIA+0ZZltMzurOaT8cYHcUx+wJlwgd9QFrBEXyJZswAekFxfIrdq4GhZUQO7Itlq+QzoXVEC21AdcIm51yF3AJeNWhWwDLmFBBeRVmg4/Atmj2nGLR/bCbZrm2qWx/+7Ekwx5i9LGO/XFzF2qO97/r5QAOSUwcG+WBDkVMHDtRUdOAcz/ed4J4NqLihwbGLjjioYcExi4bkVBjgUMXL/EkWMAAzcsUWRpYODKJIYsCQxc2USQpYCBG6dgZAlg4MYtCDkUGLhp8kYOAQZu2ryQfYGT4S7PAKJVzsg+wEmfubNZbd+XOpgTsiswXpbzaDSyCzBw82oU8lhg4ObZIPIYYODmXS/yEDBwdcQ+72w/6AMGrq6OaHnLghv1AQNXX6/bG7qAPxNwNbbf3mAD5qf5EaEisgG/IVRMNuA9QsVkA94mpLWP7Q2pP5uE4vayvQHA5bQw86m90Qb8i5C22Kyx/cAG/IGQphj3ftcPbcBzM78JaWiN+73rATZgfvAzWn4SHeXbIC7Xtcg6MfOUgJxro3C5vlU0kPNsNC439DYJyHnlhMuNeR8M5DxyxuXGHugA8rR54XIuR7KAPE3euJzroUogpy0Il/M5Fg3kNAXjcr4nG4AcNxFcLuTc78nqz7e0/A7KLDo9PbV+nkn6Q2xnZ2c0n88pQmK4XOjJ/eyQm6YhxYnichLng/FyLZM4Lid1wh/IYUXB5SSv6ACyX9FwOelLdoDsVlRcLsY1WUAeV3RcLtZFd0Duj3EfUGRcLuZVlUC2lwyXi32Re/D75IuLC1osFtYDFXxAo73dts21MV/8wgc6PEqKm7LHZv5QnndASTV8p5U9unl7oaJ6QvUi23CLA+YdqhGZcQ/Jfmu/olrvVE3Ia9wdqgB4sxp+JzPuPv2/+XV1lYzcxq32Q34lvlzbcKsF5h0vCZlxD+g2btXApSCvF1TbBOB/ba4oNSOvn7lr3KpWzC5pXHgVf4RKOk3P5CqOUEmn5eW6CxfAA2n4ndyHC+AR5YwMXOFyWngx7i4h8XJABm7kpkQGbqKmQAZu4lIiA3eiUiADd+JiIgM3k2IgAzezJJGBm2kSyMDNvBBk4CrJBxm4ynJB/kHAVRnf3OucumGvzBxTBZV+eouhn5u5t7HtvZlXhBBCCCEUsb+qGDWF0d8l5AAAAABJRU5ErkJggg=="; + } + digirepeaterTxFreqInput.disabled = true; + } else if (selectedValue === "2") { + if (bmeCheckbox.checked) { + image.src = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAlUSURBVHgB7Z1LiBxFGMe/nt1s1nd8Pw4uERVfCJqDB8XsSUHER1DiTYweRBCTgBdBETyIh6AQDx6SoAc1EjRCCOJBkg1CDoaYBSGHRDQhia+8dJdN9jVtfTXduz09X01XdVd3V/V+P/g2mZqame7+d1V99VV1FQDDMAzjKAE0lnCt+HNz9OI+YbcTmfYL+yv6/wFxOX6EhtEAgaWQLwgbEbZS2FVQ7LzmhE0KG4fODbC7icI7DAoa7hR2TlhYkc0KOyJsi7ARYGwjRT0YXejQAZuKbrJHgMkLlhR5EaccEVVlp4S9D4wuWCpkddh2XFiqGt8JXIWrWBDWF0FVhjfmXhZ6AVkVN0FYSugtsLSRVZpvVbGpoQ+xHpYW0it23XmybdgLGIHmI9snX0SxbbNVl+YKI1ny7j0kbAUwY+LSj0IFtKASZDjxNzAWNyzBnGA1dCJxI1AyFQgsPcntYFxbOCNGWeDNfjS6+Uuj5CoaHQt4AHJRlsDOja/giW4Ux/URlECJZ4t9W3KITvfzUA7ODqBtFcf2ClimpCq6qLhLkpehhMBICbdzXnFDzbQ26BEAfXq6abVhtSRbPrO84rYVaVT6LOiBldMAkT5IpKluhtqwJrLFKhoDGFwtW2JdAe+6lTRLAsu2YzUwtsDq5EuwMKHAQr0k77TtUAiuohVcEHa3OLZjBp/pKrQFz0pGYkRnnbxqBrDAfRDXN7jDIL9VgU/B4tRU3c8QaXNE+sXI0vwNelwWWRoqWoqiO+1dmzhdLeULM3As11RcJifriPa4pWM5BZY/9jQwVYFVyS7IQd4SvAsa/VSEk6yIak0jcggsp4jymG49PG06xGg6hGfBa6acrBlh86m0icjSHAU9rokszS1E2nKg73UnK6nYq1YVzq50U6E+g8JdIop56BUYu4D/LbwaHb0C9uzB89ILlgXBbqAP9UYizauxZ3EBpkXsYfkO+u3n83rRsvQ+CkwNpGelLPtEkbGVmdAHLL3sWLmB8IHm39DJqCkwl173CN7RyaVbgksuvdTkOAxTziVs3vA7Z1Kfj83ZiXimiFJ8IXMwQtdhehisoRrETwuIYcrJxOtlYMZZ6DwLnmZe8fvpe92H1mhom/hzZ3faP6ZetOz3luA5M8WQBeU2gOMrAW6NR5taq1ZNGHvRLwLjKqKauemtfhkyBJbOFQ8oOM3Amn7vZpXgt4FxHeFsTSh9pCyBn4BKoDxbdIZmEzYHZlxMfT62xnjRCYY3qN7p4zxx9VwPJjfcQt6HIBr/PXPmzFAyR78S/CownhDcpHqnn8CPA+MRk69Tqf0EvgsYjxh8ikzt84lLoHYW26MwzOMMNcGB0mXwfjKVzuzeoiGBceSw6eL29AAwLtuanp7u0lRVRa8GxiG0Vi8QReDwI+12u5U0lcBG834YV7i252F7lcArgfGQ4Z7hQ5XAy4HxkODqdIrKi3bAg2bMGbhd9Da63FEe5/UWKo7evgQdq2QKUUXzKqlNgmqDnwXGUwaG0ikWl3Bg6icYTqc0uA1WjfXqPmxuskqPivon7rGT5QWqJabCnrTTp0NbD4AzPsACN4qwZ14TJfABYDylPZVOIdpg3MatCUNteDPPEOnUwi6D0HspVE7WgGaaqnKs1vFqsJOlEniaSFMJ3FLkTbNMkbeOh8r1nCzTOaqME0z/kk5RCTwJTCNQCXwaGA+Z2J1OUbXBJ8H7lWOx/b1ApFOVE0ax0pciXkssjWqofIB4XXUka6pnGUCVwN+C9/OysBKiBLqUSMPhb0ogyju+mki7DnqFx7AwJXCZjtc93wBcc1nWryHGC24xdTP/b+ffs+2kKQSWy9eyJ+0VbXLJ4X6hyt+B8YipH6jUfgKPAeMRP22jUvtFsj4FudVLFVBhQXRwkguvmAbdTgC9tvQJIg2/O32v428PEXmpGcX4GNeVqTR0xignzXQxGR3aIv765HEgLlKfqyZj0tjP4BmWTpIsEPM4QBTPWujynbKKxffCngEP2bPnXQiC3hYoDHUHBlSx6O5ZMePjZ2H9+vNQL2eVG2plCbwJPBV4dPReqI46BcYx4JGvVe9mDPhjNU2GgxhnmPmu37s6Mzq+gNKJq8OkoTMynLAhYChObO73ro7A74HVGQABYdR+EizwItQ1Q+b/FOHJ/YmM6GR1PVOqIbCMav0KjGOgyFMfwqL3TJrupLuXgHEMdK6u+zgrl6bA0tnS3SyBqYSZz3VymUybLbEUU+0w9uCWJ6yMCJANsF8d+whJi8eDqbazMKL0Xq61jplB/E9GtrAUlzARAA8j7cddAUlRz58fhLGxWRGo6J0VGQRtkd5OpeVdmWfhG8R3BIpjXWR8HPPcAPR4cFlzGuc26eY03VYHlwjYB4VuRd29C7H7TU1xpWLJk9A7uolrXZrsFp5GtbmlyYD/MJF3Wcbvqx5TWUBEVQauB/ruwYPWXsqQOhBsi/cBUyMXXjPJbSgwEowCTwaoifYh0fbuMPlEDoElbwJTNaJQ/fEcGFKkLT0o/jwAVjDZIJp6MgHb295HKYuT7WR1oJ5sUM3KzCpTqjZ4dqNo0zdnHEhPG1xEYFzL4zBYGS9mgfuDVfPAKsg+kJ4fzVtFQxTCxL7xUlrxsw5Ed2JiDeSkgMBI8JX4sw2YssDC8xjAimOQk4ICI3JveQ5jlsMHUdc0NxafrQjPQe6No1WOBdUGq3Yuq6qlMHk+uNAiLlujwhN/WRqtNtimwBadLvl9JeUtSiWr7MSbQMeovLVML86iwIhNkZeswGlxERN33JYXTSE967uB53HlhRK3EJYFRljknFgXFylBYMSGyJW0dSVidEw/lyEuUpLAyILIBbpQgYG1KjLd49EGveUHwW/CvTj8ztZlom9XaHcb1R04mLKqwJORJ+WTCGXZFHQmTxRBS+CKGy/ZjRKB87wBkUYwFo2pF6WOblIW2C7LjSNwDZAQlhY4SWKDJXF9AKuo8JQnVWpR2wv2ca0NViHb5lmPxDKxUxbaWlPSgrtCuKVBQp+D+vZ/dFXgGK+FrlPYGNcFjpFVtw9tNHb9DkL1VbEKXwSOkc7YTuj0HV0SFm++LeAevgmcZEHsOko2ltQjHVGd3jzMZ4HTyGp8byS47RJ+LiHoWvAUF4dhCiLFuBk6i6nGDxLdCfQkBFyx9GT0fxwUwQW1DxSdB8UwDMMwDMM0nP8Bl3lxJTtDbOQAAAAASUVORK5CYII="; + } else { + image.src = + "data:image/png;base64,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"; + } + digirepeaterTxFreqInput.disabled = true; + } else if (selectedValue === "3" || selectedValue === "4") { + image.src = + "data:image/png;base64,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"; + digirepeaterTxFreqInput.disabled = selectedValue === "3"; + } else if (selectedValue === "5") { + if (bmeCheckbox.checked) { + image.src = + "data:image/png;base64,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"; + } else { + image.src = + "data:image/png;base64,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"; + } + digirepeaterTxFreqInput.disabled = true; + } + } +} + +stationModeSelect.addEventListener("change", updateImage); +bmeCheckbox.addEventListener("change", updateImage); + +function toggleFields() { + const sendBatteryVoltageCheckbox = document.querySelector( + 'input[name="other.sendBatteryVoltage"]' + ); + const externalVoltageMeasurementCheckbox = document.querySelector( + 'input[name="other.externalVoltageMeasurement"]' + ); + const externalVoltagePinInput = document.querySelector( + 'input[name="other.externalVoltagePin"]' + ); + + externalVoltageMeasurementCheckbox.disabled = + !sendBatteryVoltageCheckbox.checked; + externalVoltagePinInput.disabled = + !externalVoltageMeasurementCheckbox.checked; +} + +const sendBatteryVoltageCheckbox = document.querySelector( + 'input[name="other.sendBatteryVoltage"]' +); +const externalVoltageMeasurementCheckbox = document.querySelector( + 'input[name="other.externalVoltageMeasurement"]' +); +const externalVoltagePinInput = document.querySelector( + 'input[name="other.externalVoltagePin"]' +); + +sendBatteryVoltageCheckbox.addEventListener("change", function () { + externalVoltageMeasurementCheckbox.disabled = !this.checked; + externalVoltagePinInput.disabled = + !externalVoltageMeasurementCheckbox.checked; +}); + +document.querySelector(".new button").addEventListener("click", function () { + const networksContainer = document.querySelector(".list-networks"); + + let networkCount = document.querySelectorAll(".network").length; + + const networkElement = document.createElement("div"); + + networkElement.classList.add("row", "network", "border-bottom", "py-2"); + + // Increment the name, id, and for attributes + const attributeName = `wifi.AP.${networkCount}`; + networkElement.innerHTML = ` +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+ `; + networksContainer.appendChild(networkElement); + + networkCount++; + + // Add the new network element to the end of the document + document.querySelector(".new").before(networkElement); +}); + +toggleFields(); + +const form = document.querySelector("form"); + +const saveModal = new bootstrap.Modal(document.getElementById("saveModal"), { + backdrop: "static", + keyboard: false, +}); + +const savedModal = new bootstrap.Modal( + document.getElementById("savedModal"), + {} +); + +function checkConnection() { + const controller = new AbortController(); + + setTimeout(() => controller.abort(), 2000); + + fetch("/status?_t=" + Date.now(), { signal: controller.signal }) + .then(() => { + saveModal.hide(); + + savedModal.show(); + + setTimeout(function () { + savedModal.hide(); + }, 3000); + + fetchSettings(); + }) + .catch((err) => { + setTimeout(checkConnection, 0); + }); +} + +form.addEventListener("submit", async (event) => { + event.preventDefault(); + + document.getElementById("wifi.APs").value = + document.querySelectorAll(".network").length; + + fetch(form.action, { + method: form.method, + body: new FormData(form), + }); + + saveModal.show(); + + setTimeout(checkConnection, 0); +}); + +fetchSettings(); diff --git a/data_embed/style.css b/data_embed/style.css new file mode 100644 index 0000000..e69de29 diff --git a/installer/bin/esptool/esptool.py b/installer/bin/esptool/esptool.py new file mode 100644 index 0000000..60e5bd3 --- /dev/null +++ b/installer/bin/esptool/esptool.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +# This executable script is a thin wrapper around the main functionality +# in the esptool Python package + +# When updating this script, please also update espefuse.py and espsecure.py + +import contextlib +import os +import sys + +if os.name != "nt": + # Linux/macOS: remove current script directory to avoid importing this file + # as a module; we want to import the installed esptool module instead + with contextlib.suppress(ValueError): + if sys.path[0].endswith("/bin"): + sys.path.pop(0) + sys.path.remove(os.path.dirname(sys.executable)) + + # Linux/macOS: delete imported module entry to force Python to load + # the module from scratch; this enables importing esptool module in + # other Python scripts + with contextlib.suppress(KeyError): + del sys.modules["esptool"] + +import esptool + +if __name__ == "__main__": + esptool._main() diff --git a/installer/bin/esptool/esptool/__init__.py b/installer/bin/esptool/esptool/__init__.py new file mode 100644 index 0000000..24272f2 --- /dev/null +++ b/installer/bin/esptool/esptool/__init__.py @@ -0,0 +1,1055 @@ +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +__all__ = [ + "chip_id", + "detect_chip", + "dump_mem", + "elf2image", + "erase_flash", + "erase_region", + "flash_id", + "get_security_info", + "image_info", + "load_ram", + "make_image", + "merge_bin", + "read_flash", + "read_flash_status", + "read_mac", + "read_mem", + "run", + "verify_flash", + "version", + "write_flash", + "write_flash_status", + "write_mem", +] + +__version__ = "4.5.1" + +import argparse +import inspect +import os +import shlex +import sys +import time +import traceback + +from esptool.cmds import ( + chip_id, + detect_chip, + detect_flash_size, + dump_mem, + elf2image, + erase_flash, + erase_region, + flash_id, + get_security_info, + image_info, + load_ram, + make_image, + merge_bin, + read_flash, + read_flash_status, + read_mac, + read_mem, + run, + verify_flash, + version, + write_flash, + write_flash_status, + write_mem, +) +from esptool.config import load_config_file +from esptool.loader import DEFAULT_CONNECT_ATTEMPTS, ESPLoader, list_ports +from esptool.targets import CHIP_DEFS, CHIP_LIST, ESP32ROM +from esptool.util import ( + FatalError, + NotImplementedInROMError, + flash_size_bytes, + strip_chip_name, +) + +import serial + + +def main(argv=None, esp=None): + """ + Main function for esptool + + argv - Optional override for default arguments parsing (that uses sys.argv), + can be a list of custom arguments as strings. Arguments and their values + need to be added as individual items to the list + e.g. "-b 115200" thus becomes ['-b', '115200']. + + esp - Optional override of the connected device previously + returned by get_default_connected_device() + """ + + external_esp = esp is not None + + parser = argparse.ArgumentParser( + description="esptool.py v%s - Espressif chips ROM Bootloader Utility" + % __version__, + prog="esptool", + ) + + parser.add_argument( + "--chip", + "-c", + help="Target chip type", + type=strip_chip_name, + choices=["auto"] + CHIP_LIST, + default=os.environ.get("ESPTOOL_CHIP", "auto"), + ) + + parser.add_argument( + "--port", + "-p", + help="Serial port device", + default=os.environ.get("ESPTOOL_PORT", None), + ) + + parser.add_argument( + "--baud", + "-b", + help="Serial port baud rate used when flashing/reading", + type=arg_auto_int, + default=os.environ.get("ESPTOOL_BAUD", ESPLoader.ESP_ROM_BAUD), + ) + + parser.add_argument( + "--before", + help="What to do before connecting to the chip", + choices=["default_reset", "usb_reset", "no_reset", "no_reset_no_sync"], + default=os.environ.get("ESPTOOL_BEFORE", "default_reset"), + ) + + parser.add_argument( + "--after", + "-a", + help="What to do after esptool.py is finished", + choices=["hard_reset", "soft_reset", "no_reset", "no_reset_stub"], + default=os.environ.get("ESPTOOL_AFTER", "hard_reset"), + ) + + parser.add_argument( + "--no-stub", + help="Disable launching the flasher stub, only talk to ROM bootloader. " + "Some features will not be available.", + action="store_true", + ) + + parser.add_argument( + "--trace", + "-t", + help="Enable trace-level output of esptool.py interactions.", + action="store_true", + ) + + parser.add_argument( + "--override-vddsdio", + help="Override ESP32 VDDSDIO internal voltage regulator (use with care)", + choices=ESP32ROM.OVERRIDE_VDDSDIO_CHOICES, + nargs="?", + ) + + parser.add_argument( + "--connect-attempts", + help=( + "Number of attempts to connect, negative or 0 for infinite. " + "Default: %d." % DEFAULT_CONNECT_ATTEMPTS + ), + type=int, + default=os.environ.get("ESPTOOL_CONNECT_ATTEMPTS", DEFAULT_CONNECT_ATTEMPTS), + ) + + subparsers = parser.add_subparsers( + dest="operation", help="Run esptool.py {command} -h for additional help" + ) + + def add_spi_connection_arg(parent): + parent.add_argument( + "--spi-connection", + "-sc", + help="ESP32-only argument. Override default SPI Flash connection. " + "Value can be SPI, HSPI or a comma-separated list of 5 I/O numbers " + "to use for SPI flash (CLK,Q,D,HD,CS).", + action=SpiConnectionAction, + ) + + parser_load_ram = subparsers.add_parser( + "load_ram", help="Download an image to RAM and execute" + ) + parser_load_ram.add_argument("filename", help="Firmware image") + + parser_dump_mem = subparsers.add_parser( + "dump_mem", help="Dump arbitrary memory to disk" + ) + parser_dump_mem.add_argument("address", help="Base address", type=arg_auto_int) + parser_dump_mem.add_argument( + "size", help="Size of region to dump", type=arg_auto_int + ) + parser_dump_mem.add_argument("filename", help="Name of binary dump") + + parser_read_mem = subparsers.add_parser( + "read_mem", help="Read arbitrary memory location" + ) + parser_read_mem.add_argument("address", help="Address to read", type=arg_auto_int) + + parser_write_mem = subparsers.add_parser( + "write_mem", help="Read-modify-write to arbitrary memory location" + ) + parser_write_mem.add_argument("address", help="Address to write", type=arg_auto_int) + parser_write_mem.add_argument("value", help="Value", type=arg_auto_int) + parser_write_mem.add_argument( + "mask", + help="Mask of bits to write", + type=arg_auto_int, + nargs="?", + default="0xFFFFFFFF", + ) + + def add_spi_flash_subparsers(parent, allow_keep, auto_detect): + """Add common parser arguments for SPI flash properties""" + extra_keep_args = ["keep"] if allow_keep else [] + + if auto_detect and allow_keep: + extra_fs_message = ", detect, or keep" + flash_sizes = ["detect", "keep"] + elif auto_detect: + extra_fs_message = ", or detect" + flash_sizes = ["detect"] + elif allow_keep: + extra_fs_message = ", or keep" + flash_sizes = ["keep"] + else: + extra_fs_message = "" + flash_sizes = [] + + parent.add_argument( + "--flash_freq", + "-ff", + help="SPI Flash frequency", + choices=extra_keep_args + + [ + "80m", + "60m", + "48m", + "40m", + "30m", + "26m", + "24m", + "20m", + "16m", + "15m", + "12m", + ], + default=os.environ.get("ESPTOOL_FF", "keep" if allow_keep else None), + ) + parent.add_argument( + "--flash_mode", + "-fm", + help="SPI Flash mode", + choices=extra_keep_args + ["qio", "qout", "dio", "dout"], + default=os.environ.get("ESPTOOL_FM", "keep" if allow_keep else "qio"), + ) + parent.add_argument( + "--flash_size", + "-fs", + help="SPI Flash size in MegaBytes " + "(1MB, 2MB, 4MB, 8MB, 16MB, 32MB, 64MB, 128MB) " + "plus ESP8266-only (256KB, 512KB, 2MB-c1, 4MB-c1)" + extra_fs_message, + choices=flash_sizes + + [ + "256KB", + "512KB", + "1MB", + "2MB", + "2MB-c1", + "4MB", + "4MB-c1", + "8MB", + "16MB", + "32MB", + "64MB", + "128MB", + ], + default=os.environ.get("ESPTOOL_FS", "keep" if allow_keep else "1MB"), + ) + add_spi_connection_arg(parent) + + parser_write_flash = subparsers.add_parser( + "write_flash", help="Write a binary blob to flash" + ) + + parser_write_flash.add_argument( + "addr_filename", + metavar="
", + help="Address followed by binary filename, separated by space", + action=AddrFilenamePairAction, + ) + parser_write_flash.add_argument( + "--erase-all", + "-e", + help="Erase all regions of flash (not just write areas) before programming", + action="store_true", + ) + + add_spi_flash_subparsers(parser_write_flash, allow_keep=True, auto_detect=True) + parser_write_flash.add_argument( + "--no-progress", "-p", help="Suppress progress output", action="store_true" + ) + parser_write_flash.add_argument( + "--verify", + help="Verify just-written data on flash " + "(mostly superfluous, data is read back during flashing)", + action="store_true", + ) + parser_write_flash.add_argument( + "--encrypt", + help="Apply flash encryption when writing data " + "(required correct efuse settings)", + action="store_true", + ) + # In order to not break backward compatibility, + # our list of encrypted files to flash is a new parameter + parser_write_flash.add_argument( + "--encrypt-files", + metavar="
", + help="Files to be encrypted on the flash. " + "Address followed by binary filename, separated by space.", + action=AddrFilenamePairAction, + ) + parser_write_flash.add_argument( + "--ignore-flash-encryption-efuse-setting", + help="Ignore flash encryption efuse settings ", + action="store_true", + ) + parser_write_flash.add_argument( + "--force", + help="Force write, skip security and compatibility checks. Use with caution!", + action="store_true", + ) + + compress_args = parser_write_flash.add_mutually_exclusive_group(required=False) + compress_args.add_argument( + "--compress", + "-z", + help="Compress data in transfer (default unless --no-stub is specified)", + action="store_true", + default=None, + ) + compress_args.add_argument( + "--no-compress", + "-u", + help="Disable data compression during transfer " + "(default if --no-stub is specified)", + action="store_true", + ) + + subparsers.add_parser("run", help="Run application code in flash") + + parser_image_info = subparsers.add_parser( + "image_info", help="Dump headers from an application image" + ) + parser_image_info.add_argument("filename", help="Image file to parse") + parser_image_info.add_argument( + "--version", + "-v", + help="Output format version (1 - legacy, 2 - extended)", + choices=["1", "2"], + default="1", + ) + + parser_make_image = subparsers.add_parser( + "make_image", help="Create an application image from binary files" + ) + parser_make_image.add_argument("output", help="Output image file") + parser_make_image.add_argument( + "--segfile", "-f", action="append", help="Segment input file" + ) + parser_make_image.add_argument( + "--segaddr", + "-a", + action="append", + help="Segment base address", + type=arg_auto_int, + ) + parser_make_image.add_argument( + "--entrypoint", + "-e", + help="Address of entry point", + type=arg_auto_int, + default=0, + ) + + parser_elf2image = subparsers.add_parser( + "elf2image", help="Create an application image from ELF file" + ) + parser_elf2image.add_argument("input", help="Input ELF file") + parser_elf2image.add_argument( + "--output", + "-o", + help="Output filename prefix (for version 1 image), " + "or filename (for version 2 single image)", + type=str, + ) + parser_elf2image.add_argument( + "--version", + "-e", + help="Output image version", + choices=["1", "2", "3"], + default="1", + ) + parser_elf2image.add_argument( + # it kept for compatibility + # Minimum chip revision (deprecated, consider using --min-rev-full) + "--min-rev", + "-r", + help=argparse.SUPPRESS, + type=int, + choices=range(256), + metavar="{0, ... 255}", + default=0, + ) + parser_elf2image.add_argument( + "--min-rev-full", + help="Minimal chip revision (in format: major * 100 + minor)", + type=int, + choices=range(65536), + metavar="{0, ... 65535}", + default=0, + ) + parser_elf2image.add_argument( + "--max-rev-full", + help="Maximal chip revision (in format: major * 100 + minor)", + type=int, + choices=range(65536), + metavar="{0, ... 65535}", + default=65535, + ) + parser_elf2image.add_argument( + "--secure-pad", + action="store_true", + help="Pad image so once signed it will end on a 64KB boundary. " + "For Secure Boot v1 images only.", + ) + parser_elf2image.add_argument( + "--secure-pad-v2", + action="store_true", + help="Pad image to 64KB, so once signed its signature sector will" + "start at the next 64K block. For Secure Boot v2 images only.", + ) + parser_elf2image.add_argument( + "--elf-sha256-offset", + help="If set, insert SHA256 hash (32 bytes) of the input ELF file " + "at specified offset in the binary.", + type=arg_auto_int, + default=None, + ) + parser_elf2image.add_argument( + "--dont-append-digest", + dest="append_digest", + help="Don't append a SHA256 digest of the entire image after the checksum. " + "This argument is not supported and ignored for ESP8266.", + action="store_false", + default=True, + ) + parser_elf2image.add_argument( + "--use_segments", + help="If set, ELF segments will be used instead of ELF sections " + "to genereate the image.", + action="store_true", + ) + parser_elf2image.add_argument( + "--flash-mmu-page-size", + help="Change flash MMU page size.", + choices=["64KB", "32KB", "16KB", "8KB"], + ) + parser_elf2image.add_argument( + "--pad-to-size", + help="The block size with which the final binary image after padding " + "must be aligned to. Value 0xFF is used for padding, similar to erase_flash", + default=None, + ) + + add_spi_flash_subparsers(parser_elf2image, allow_keep=False, auto_detect=False) + + subparsers.add_parser("read_mac", help="Read MAC address from OTP ROM") + + subparsers.add_parser("chip_id", help="Read Chip ID from OTP ROM") + + parser_flash_id = subparsers.add_parser( + "flash_id", help="Read SPI flash manufacturer and device ID" + ) + add_spi_connection_arg(parser_flash_id) + + parser_read_status = subparsers.add_parser( + "read_flash_status", help="Read SPI flash status register" + ) + + add_spi_connection_arg(parser_read_status) + parser_read_status.add_argument( + "--bytes", + help="Number of bytes to read (1-3)", + type=int, + choices=[1, 2, 3], + default=2, + ) + + parser_write_status = subparsers.add_parser( + "write_flash_status", help="Write SPI flash status register" + ) + + add_spi_connection_arg(parser_write_status) + parser_write_status.add_argument( + "--non-volatile", + help="Write non-volatile bits (use with caution)", + action="store_true", + ) + parser_write_status.add_argument( + "--bytes", + help="Number of status bytes to write (1-3)", + type=int, + choices=[1, 2, 3], + default=2, + ) + parser_write_status.add_argument("value", help="New value", type=arg_auto_int) + + parser_read_flash = subparsers.add_parser( + "read_flash", help="Read SPI flash content" + ) + add_spi_connection_arg(parser_read_flash) + parser_read_flash.add_argument("address", help="Start address", type=arg_auto_int) + parser_read_flash.add_argument( + "size", help="Size of region to dump", type=arg_auto_int + ) + parser_read_flash.add_argument("filename", help="Name of binary dump") + parser_read_flash.add_argument( + "--no-progress", "-p", help="Suppress progress output", action="store_true" + ) + + parser_verify_flash = subparsers.add_parser( + "verify_flash", help="Verify a binary blob against flash" + ) + parser_verify_flash.add_argument( + "addr_filename", + help="Address and binary file to verify there, separated by space", + action=AddrFilenamePairAction, + ) + parser_verify_flash.add_argument( + "--diff", "-d", help="Show differences", choices=["no", "yes"], default="no" + ) + add_spi_flash_subparsers(parser_verify_flash, allow_keep=True, auto_detect=True) + + parser_erase_flash = subparsers.add_parser( + "erase_flash", help="Perform Chip Erase on SPI flash" + ) + parser_erase_flash.add_argument( + "--force", + help="Erase flash even if security features are enabled. Use with caution!", + action="store_true", + ) + add_spi_connection_arg(parser_erase_flash) + + parser_erase_region = subparsers.add_parser( + "erase_region", help="Erase a region of the flash" + ) + parser_erase_region.add_argument( + "--force", + help="Erase region even if security features are enabled. Use with caution!", + action="store_true", + ) + add_spi_connection_arg(parser_erase_region) + parser_erase_region.add_argument( + "address", help="Start address (must be multiple of 4096)", type=arg_auto_int + ) + parser_erase_region.add_argument( + "size", + help="Size of region to erase (must be multiple of 4096)", + type=arg_auto_int, + ) + + parser_merge_bin = subparsers.add_parser( + "merge_bin", + help="Merge multiple raw binary files into a single file for later flashing", + ) + + parser_merge_bin.add_argument( + "--output", "-o", help="Output filename", type=str, required=True + ) + parser_merge_bin.add_argument( + "--format", "-f", help="Format of the output file", choices="raw", default="raw" + ) # for future expansion + add_spi_flash_subparsers(parser_merge_bin, allow_keep=True, auto_detect=False) + + parser_merge_bin.add_argument( + "--target-offset", + "-t", + help="Target offset where the output file will be flashed", + type=arg_auto_int, + default=0, + ) + parser_merge_bin.add_argument( + "--fill-flash-size", + help="If set, the final binary file will be padded with FF " + "bytes up to this flash size.", + choices=[ + "256KB", + "512KB", + "1MB", + "2MB", + "4MB", + "8MB", + "16MB", + "32MB", + "64MB", + "128MB", + ], + ) + parser_merge_bin.add_argument( + "addr_filename", + metavar="
", + help="Address followed by binary filename, separated by space", + action=AddrFilenamePairAction, + ) + + subparsers.add_parser("get_security_info", help="Get some security-related data") + + subparsers.add_parser("version", help="Print esptool version") + + # internal sanity check - every operation matches a module function of the same name + for operation in subparsers.choices.keys(): + assert operation in globals(), "%s should be a module function" % operation + + argv = expand_file_arguments(argv or sys.argv[1:]) + + args = parser.parse_args(argv) + print("esptool.py v%s" % __version__) + load_config_file(verbose=True) + + # operation function can take 1 arg (args), 2 args (esp, arg) + # or be a member function of the ESPLoader class. + + if args.operation is None: + parser.print_help() + sys.exit(1) + + # Forbid the usage of both --encrypt, which means encrypt all the given files, + # and --encrypt-files, which represents the list of files to encrypt. + # The reason is that allowing both at the same time increases the chances of + # having contradictory lists (e.g. one file not available in one of list). + if ( + args.operation == "write_flash" + and args.encrypt + and args.encrypt_files is not None + ): + raise FatalError( + "Options --encrypt and --encrypt-files " + "must not be specified at the same time." + ) + + operation_func = globals()[args.operation] + operation_args = inspect.getfullargspec(operation_func).args + + if ( + operation_args[0] == "esp" + ): # operation function takes an ESPLoader connection object + if args.before != "no_reset_no_sync": + initial_baud = min( + ESPLoader.ESP_ROM_BAUD, args.baud + ) # don't sync faster than the default baud rate + else: + initial_baud = args.baud + + if args.port is None: + ser_list = get_port_list() + print("Found %d serial ports" % len(ser_list)) + else: + ser_list = [args.port] + esp = esp or get_default_connected_device( + ser_list, + port=args.port, + connect_attempts=args.connect_attempts, + initial_baud=initial_baud, + chip=args.chip, + trace=args.trace, + before=args.before, + ) + + if esp is None: + raise FatalError( + "Could not connect to an Espressif device " + "on any of the %d available serial ports." % len(ser_list) + ) + + if esp.secure_download_mode: + print("Chip is %s in Secure Download Mode" % esp.CHIP_NAME) + else: + print("Chip is %s" % (esp.get_chip_description())) + print("Features: %s" % ", ".join(esp.get_chip_features())) + print("Crystal is %dMHz" % esp.get_crystal_freq()) + read_mac(esp, args) + + if not args.no_stub: + if esp.secure_download_mode: + print( + "WARNING: Stub loader is not supported in Secure Download Mode, " + "setting --no-stub" + ) + args.no_stub = True + elif not esp.IS_STUB and esp.stub_is_disabled: + print( + "WARNING: Stub loader has been disabled for compatibility, " + "setting --no-stub" + ) + args.no_stub = True + else: + esp = esp.run_stub() + + if args.override_vddsdio: + esp.override_vddsdio(args.override_vddsdio) + + if args.baud > initial_baud: + try: + esp.change_baud(args.baud) + except NotImplementedInROMError: + print( + "WARNING: ROM doesn't support changing baud rate. " + "Keeping initial baud rate %d" % initial_baud + ) + + # override common SPI flash parameter stuff if configured to do so + if hasattr(args, "spi_connection") and args.spi_connection is not None: + if esp.CHIP_NAME != "ESP32": + raise FatalError( + "Chip %s does not support --spi-connection option." % esp.CHIP_NAME + ) + print("Configuring SPI flash mode...") + esp.flash_spi_attach(args.spi_connection) + elif args.no_stub: + print("Enabling default SPI flash mode...") + # ROM loader doesn't enable flash unless we explicitly do it + esp.flash_spi_attach(0) + + # XMC chip startup sequence + XMC_VENDOR_ID = 0x20 + + def is_xmc_chip_strict(): + id = esp.flash_id() + rdid = ((id & 0xFF) << 16) | ((id >> 16) & 0xFF) | (id & 0xFF00) + + vendor_id = (rdid >> 16) & 0xFF + mfid = (rdid >> 8) & 0xFF + cpid = rdid & 0xFF + + if vendor_id != XMC_VENDOR_ID: + return False + + matched = False + if mfid == 0x40: + if cpid >= 0x13 and cpid <= 0x20: + matched = True + elif mfid == 0x41: + if cpid >= 0x17 and cpid <= 0x20: + matched = True + elif mfid == 0x50: + if cpid >= 0x15 and cpid <= 0x16: + matched = True + return matched + + def flash_xmc_startup(): + # If the RDID value is a valid XMC one, may skip the flow + fast_check = True + if fast_check and is_xmc_chip_strict(): + return # Successful XMC flash chip boot-up detected by RDID, skipping. + + sfdp_mfid_addr = 0x10 + mf_id = esp.read_spiflash_sfdp(sfdp_mfid_addr, 8) + if mf_id != XMC_VENDOR_ID: # Non-XMC chip detected by SFDP Read, skipping. + return + + print( + "WARNING: XMC flash chip boot-up failure detected! " + "Running XMC25QHxxC startup flow" + ) + esp.run_spiflash_command(0xB9) # Enter DPD + esp.run_spiflash_command(0x79) # Enter UDPD + esp.run_spiflash_command(0xFF) # Exit UDPD + time.sleep(0.002) # Delay tXUDPD + esp.run_spiflash_command(0xAB) # Release Power-Down + time.sleep(0.00002) + # Check for success + if not is_xmc_chip_strict(): + print("WARNING: XMC flash boot-up fix failed.") + print("XMC flash chip boot-up fix successful!") + + # Check flash chip connection + if not esp.secure_download_mode: + try: + flash_id = esp.flash_id() + if flash_id in (0xFFFFFF, 0x000000): + print( + "WARNING: Failed to communicate with the flash chip, " + "read/write operations will fail. " + "Try checking the chip connections or removing " + "any other hardware connected to IOs." + ) + except FatalError as e: + raise FatalError(f"Unable to verify flash chip connection ({e}).") + + # Check if XMC SPI flash chip booted-up successfully, fix if not + if not esp.secure_download_mode: + try: + flash_xmc_startup() + except FatalError as e: + esp.trace(f"Unable to perform XMC flash chip startup sequence ({e}).") + + if hasattr(args, "flash_size"): + print("Configuring flash size...") + detect_flash_size(esp, args) + if args.flash_size != "keep": # TODO: should set this even with 'keep' + esp.flash_set_parameters(flash_size_bytes(args.flash_size)) + # Check if stub supports chosen flash size + if esp.IS_STUB and args.flash_size in ("32MB", "64MB", "128MB"): + print( + "WARNING: Flasher stub doesn't fully support flash size larger " + "than 16MB, in case of failure use --no-stub." + ) + + if esp.IS_STUB and hasattr(args, "address") and hasattr(args, "size"): + if args.address + args.size > 0x1000000: + print( + "WARNING: Flasher stub doesn't fully support flash size larger " + "than 16MB, in case of failure use --no-stub." + ) + + try: + operation_func(esp, args) + finally: + try: # Clean up AddrFilenamePairAction files + for address, argfile in args.addr_filename: + argfile.close() + except AttributeError: + pass + + # Handle post-operation behaviour (reset or other) + if operation_func == load_ram: + # the ESP is now running the loaded image, so let it run + print("Exiting immediately.") + elif args.after == "hard_reset": + esp.hard_reset() + elif args.after == "soft_reset": + print("Soft resetting...") + # flash_finish will trigger a soft reset + esp.soft_reset(False) + elif args.after == "no_reset_stub": + print("Staying in flasher stub.") + else: # args.after == 'no_reset' + print("Staying in bootloader.") + if esp.IS_STUB: + esp.soft_reset(True) # exit stub back to ROM loader + + if not external_esp: + esp._port.close() + + else: + operation_func(args) + + +def arg_auto_int(x): + return int(x, 0) + + +def get_port_list(): + if list_ports is None: + raise FatalError( + "Listing all serial ports is currently not available. " + "Please try to specify the port when running esptool.py or update " + "the pyserial package to the latest version" + ) + return sorted(ports.device for ports in list_ports.comports()) + + +def expand_file_arguments(argv): + """ + Any argument starting with "@" gets replaced with all values read from a text file. + Text file arguments can be split by newline or by space. + Values are added "as-is", as if they were specified in this order + on the command line. + """ + new_args = [] + expanded = False + for arg in argv: + if arg.startswith("@"): + expanded = True + with open(arg[1:], "r") as f: + for line in f.readlines(): + new_args += shlex.split(line) + else: + new_args.append(arg) + if expanded: + print("esptool %s" % (" ".join(new_args[1:]))) + return new_args + return argv + + +def get_default_connected_device( + serial_list, + port, + connect_attempts, + initial_baud, + chip="auto", + trace=False, + before="default_reset", +): + _esp = None + for each_port in reversed(serial_list): + print("Serial port %s" % each_port) + try: + if chip == "auto": + _esp = detect_chip( + each_port, initial_baud, before, trace, connect_attempts + ) + else: + chip_class = CHIP_DEFS[chip] + _esp = chip_class(each_port, initial_baud, trace) + _esp.connect(before, connect_attempts) + break + except (FatalError, OSError) as err: + if port is not None: + raise + print("%s failed to connect: %s" % (each_port, err)) + if _esp and _esp._port: + _esp._port.close() + _esp = None + return _esp + + +class SpiConnectionAction(argparse.Action): + """ + Custom action to parse 'spi connection' override. + Values are SPI, HSPI, or a sequence of 5 pin numbers separated by commas. + """ + + def __call__(self, parser, namespace, value, option_string=None): + if value.upper() == "SPI": + value = 0 + elif value.upper() == "HSPI": + value = 1 + elif "," in value: + values = value.split(",") + if len(values) != 5: + raise argparse.ArgumentError( + self, + "%s is not a valid list of comma-separate pin numbers. " + "Must be 5 numbers - CLK,Q,D,HD,CS." % value, + ) + try: + values = tuple(int(v, 0) for v in values) + except ValueError: + raise argparse.ArgumentError( + self, + "%s is not a valid argument. All pins must be numeric values" + % values, + ) + if any([v for v in values if v > 33 or v < 0]): + raise argparse.ArgumentError( + self, "Pin numbers must be in the range 0-33." + ) + # encode the pin numbers as a 32-bit integer with packed 6-bit values, + # the same way ESP32 ROM takes them + # TODO: make this less ESP32 ROM specific somehow... + clk, q, d, hd, cs = values + value = (hd << 24) | (cs << 18) | (d << 12) | (q << 6) | clk + else: + raise argparse.ArgumentError( + self, + "%s is not a valid spi-connection value. " + "Values are SPI, HSPI, or a sequence of 5 pin numbers CLK,Q,D,HD,CS)." + % value, + ) + setattr(namespace, self.dest, value) + + +class AddrFilenamePairAction(argparse.Action): + """Custom parser class for the address/filename pairs passed as arguments""" + + def __init__(self, option_strings, dest, nargs="+", **kwargs): + super(AddrFilenamePairAction, self).__init__( + option_strings, dest, nargs, **kwargs + ) + + def __call__(self, parser, namespace, values, option_string=None): + # validate pair arguments + pairs = [] + for i in range(0, len(values), 2): + try: + address = int(values[i], 0) + except ValueError: + raise argparse.ArgumentError( + self, 'Address "%s" must be a number' % values[i] + ) + try: + argfile = open(values[i + 1], "rb") + except IOError as e: + raise argparse.ArgumentError(self, e) + except IndexError: + raise argparse.ArgumentError( + self, + "Must be pairs of an address " + "and the binary filename to write there", + ) + pairs.append((address, argfile)) + + # Sort the addresses and check for overlapping + end = 0 + for address, argfile in sorted(pairs, key=lambda x: x[0]): + argfile.seek(0, 2) # seek to end + size = argfile.tell() + argfile.seek(0) + sector_start = address & ~(ESPLoader.FLASH_SECTOR_SIZE - 1) + sector_end = ( + (address + size + ESPLoader.FLASH_SECTOR_SIZE - 1) + & ~(ESPLoader.FLASH_SECTOR_SIZE - 1) + ) - 1 + if sector_start < end: + message = "Detected overlap at address: 0x%x for file: %s" % ( + address, + argfile.name, + ) + raise argparse.ArgumentError(self, message) + end = sector_end + setattr(namespace, self.dest, pairs) + + +def _main(): + try: + main() + except FatalError as e: + print(f"\nA fatal error occurred: {e}") + sys.exit(2) + except serial.serialutil.SerialException as e: + print(f"\nA serial exception error occurred: {e}") + print( + "Note: This error originates from pySerial. " + "It is likely not a problem with esptool, " + "but with the hardware connection or drivers." + ) + print( + "For troubleshooting steps visit: " + "https://docs.espressif.com/projects/esptool/en/latest/troubleshooting.html" + ) + sys.exit(1) + except StopIteration: + print(traceback.format_exc()) + print("A fatal error occurred: The chip stopped responding.") + sys.exit(2) + + +if __name__ == "__main__": + _main() diff --git a/installer/bin/esptool/esptool/__main__.py b/installer/bin/esptool/esptool/__main__.py new file mode 100644 index 0000000..11e3bce --- /dev/null +++ b/installer/bin/esptool/esptool/__main__.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import esptool + +if __name__ == "__main__": + esptool._main() diff --git a/installer/bin/esptool/esptool/bin_image.py b/installer/bin/esptool/esptool/bin_image.py new file mode 100644 index 0000000..b001a1e --- /dev/null +++ b/installer/bin/esptool/esptool/bin_image.py @@ -0,0 +1,1239 @@ +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import binascii +import copy +import hashlib +import io +import os +import re +import struct + +from .loader import ESPLoader +from .targets import ( + ESP32C2ROM, + ESP32C3ROM, + ESP32C6BETAROM, + ESP32C6ROM, + ESP32H2BETA1ROM, + ESP32H2BETA2ROM, + ESP32H2ROM, + ESP32ROM, + ESP32S2ROM, + ESP32S3BETA2ROM, + ESP32S3ROM, + ESP8266ROM, +) +from .util import FatalError, byte, pad_to + + +def align_file_position(f, size): + """Align the position in the file to the next block of specified size""" + align = (size - 1) - (f.tell() % size) + f.seek(align, 1) + + +def LoadFirmwareImage(chip, image_file): + """ + Load a firmware image. Can be for any supported SoC. + + ESP8266 images will be examined to determine if they are original ROM firmware + images (ESP8266ROMFirmwareImage) or "v2" OTA bootloader images. + + Returns a BaseFirmwareImage subclass, either ESP8266ROMFirmwareImage (v1) + or ESP8266V2FirmwareImage (v2). + """ + + def select_image_class(f, chip): + chip = re.sub(r"[-()]", "", chip.lower()) + if chip != "esp8266": + return { + "esp32": ESP32FirmwareImage, + "esp32s2": ESP32S2FirmwareImage, + "esp32s3beta2": ESP32S3BETA2FirmwareImage, + "esp32s3": ESP32S3FirmwareImage, + "esp32c3": ESP32C3FirmwareImage, + "esp32c6beta": ESP32C6BETAFirmwareImage, + "esp32h2beta1": ESP32H2BETA1FirmwareImage, + "esp32h2beta2": ESP32H2BETA2FirmwareImage, + "esp32c2": ESP32C2FirmwareImage, + "esp32c6": ESP32C6FirmwareImage, + "esp32h2": ESP32H2FirmwareImage, + }[chip](f) + else: # Otherwise, ESP8266 so look at magic to determine the image type + magic = ord(f.read(1)) + f.seek(0) + if magic == ESPLoader.ESP_IMAGE_MAGIC: + return ESP8266ROMFirmwareImage(f) + elif magic == ESP8266V2FirmwareImage.IMAGE_V2_MAGIC: + return ESP8266V2FirmwareImage(f) + else: + raise FatalError("Invalid image magic number: %d" % magic) + + if isinstance(image_file, str): + with open(image_file, "rb") as f: + return select_image_class(f, chip) + return select_image_class(image_file, chip) + + +class ImageSegment(object): + """Wrapper class for a segment in an ESP image + (very similar to a section in an ELFImage also)""" + + def __init__(self, addr, data, file_offs=None): + self.addr = addr + self.data = data + self.file_offs = file_offs + self.include_in_checksum = True + if self.addr != 0: + self.pad_to_alignment( + 4 + ) # pad all "real" ImageSegments 4 byte aligned length + + def copy_with_new_addr(self, new_addr): + """Return a new ImageSegment with same data, but mapped at + a new address.""" + return ImageSegment(new_addr, self.data, 0) + + def split_image(self, split_len): + """Return a new ImageSegment which splits "split_len" bytes + from the beginning of the data. Remaining bytes are kept in + this segment object (and the start address is adjusted to match.)""" + result = copy.copy(self) + result.data = self.data[:split_len] + self.data = self.data[split_len:] + self.addr += split_len + self.file_offs = None + result.file_offs = None + return result + + def __repr__(self): + r = "len 0x%05x load 0x%08x" % (len(self.data), self.addr) + if self.file_offs is not None: + r += " file_offs 0x%08x" % (self.file_offs) + return r + + def get_memory_type(self, image): + """ + Return a list describing the memory type(s) that is covered by this + segment's start address. + """ + return [ + map_range[2] + for map_range in image.ROM_LOADER.MEMORY_MAP + if map_range[0] <= self.addr < map_range[1] + ] + + def pad_to_alignment(self, alignment): + self.data = pad_to(self.data, alignment, b"\x00") + + +class ELFSection(ImageSegment): + """Wrapper class for a section in an ELF image, has a section + name as well as the common properties of an ImageSegment.""" + + def __init__(self, name, addr, data): + super(ELFSection, self).__init__(addr, data) + self.name = name.decode("utf-8") + + def __repr__(self): + return "%s %s" % (self.name, super(ELFSection, self).__repr__()) + + +class BaseFirmwareImage(object): + SEG_HEADER_LEN = 8 + SHA256_DIGEST_LEN = 32 + + """ Base class with common firmware image functions """ + + def __init__(self): + self.segments = [] + self.entrypoint = 0 + self.elf_sha256 = None + self.elf_sha256_offset = 0 + self.pad_to_size = 0 + + def load_common_header(self, load_file, expected_magic): + ( + magic, + segments, + self.flash_mode, + self.flash_size_freq, + self.entrypoint, + ) = struct.unpack(" 16: + raise FatalError( + "Invalid segment count %d (max 16). " + "Usually this indicates a linker script problem." % len(self.segments) + ) + + def load_segment(self, f, is_irom_segment=False): + """Load the next segment from the image file""" + file_offs = f.tell() + (offset, size) = struct.unpack(" 0x40200000 or offset < 0x3FFE0000 or size > 65536: + print("WARNING: Suspicious segment 0x%x, length %d" % (offset, size)) + + def maybe_patch_segment_data(self, f, segment_data): + """ + If SHA256 digest of the ELF file needs to be inserted into this segment, do so. + Returns segment data. + """ + segment_len = len(segment_data) + file_pos = f.tell() # file_pos is position in the .bin file + if ( + self.elf_sha256_offset >= file_pos + and self.elf_sha256_offset < file_pos + segment_len + ): + # SHA256 digest needs to be patched into this binary segment, + # calculate offset of the digest inside the binary segment. + patch_offset = self.elf_sha256_offset - file_pos + # Sanity checks + if ( + patch_offset < self.SEG_HEADER_LEN + or patch_offset + self.SHA256_DIGEST_LEN > segment_len + ): + raise FatalError( + "Cannot place SHA256 digest on segment boundary" + "(elf_sha256_offset=%d, file_pos=%d, segment_size=%d)" + % (self.elf_sha256_offset, file_pos, segment_len) + ) + # offset relative to the data part + patch_offset -= self.SEG_HEADER_LEN + if ( + segment_data[patch_offset : patch_offset + self.SHA256_DIGEST_LEN] + != b"\x00" * self.SHA256_DIGEST_LEN + ): + raise FatalError( + "Contents of segment at SHA256 digest offset 0x%x are not all zero." + " Refusing to overwrite." % self.elf_sha256_offset + ) + assert len(self.elf_sha256) == self.SHA256_DIGEST_LEN + segment_data = ( + segment_data[0:patch_offset] + + self.elf_sha256 + + segment_data[patch_offset + self.SHA256_DIGEST_LEN :] + ) + return segment_data + + def save_segment(self, f, segment, checksum=None): + """ + Save the next segment to the image file, + return next checksum value if provided + """ + segment_data = self.maybe_patch_segment_data(f, segment.data) + f.write(struct.pack(" 0: + if len(irom_segments) != 1: + raise FatalError( + "Found %d segments that could be irom0. Bad ELF file?" + % len(irom_segments) + ) + return irom_segments[0] + return None + + def get_non_irom_segments(self): + irom_segment = self.get_irom_segment() + return [s for s in self.segments if s != irom_segment] + + def merge_adjacent_segments(self): + if not self.segments: + return # nothing to merge + + segments = [] + # The easiest way to merge the sections is the browse them backward. + for i in range(len(self.segments) - 1, 0, -1): + # elem is the previous section, the one `next_elem` may need to be + # merged in + elem = self.segments[i - 1] + next_elem = self.segments[i] + if all( + ( + elem.get_memory_type(self) == next_elem.get_memory_type(self), + elem.include_in_checksum == next_elem.include_in_checksum, + next_elem.addr == elem.addr + len(elem.data), + ) + ): + # Merge any segment that ends where the next one starts, + # without spanning memory types + # + # (don't 'pad' any gaps here as they may be excluded from the image + # due to 'noinit' or other reasons.) + elem.data += next_elem.data + else: + # The section next_elem cannot be merged into the previous one, + # which means it needs to be part of the final segments. + # As we are browsing the list backward, the elements need to be + # inserted at the beginning of the final list. + segments.insert(0, next_elem) + + # The first segment will always be here as it cannot be merged into any + # "previous" section. + segments.insert(0, self.segments[0]) + + # note: we could sort segments here as well, but the ordering of segments is + # sometimes important for other reasons (like embedded ELF SHA-256), + # so we assume that the linker script will have produced any adjacent sections + # in linear order in the ELF, anyhow. + self.segments = segments + + def set_mmu_page_size(self, size): + """ + If supported, this should be overridden by the chip-specific class. + Gets called in elf2image. + """ + print( + "WARNING: Changing MMU page size is not supported on {}! " + "Defaulting to 64KB.".format(self.ROM_LOADER.CHIP_NAME) + ) + + +class ESP8266ROMFirmwareImage(BaseFirmwareImage): + """'Version 1' firmware image, segments loaded directly by the ROM bootloader.""" + + ROM_LOADER = ESP8266ROM + + def __init__(self, load_file=None): + super(ESP8266ROMFirmwareImage, self).__init__() + self.flash_mode = 0 + self.flash_size_freq = 0 + self.version = 1 + + if load_file is not None: + segments = self.load_common_header(load_file, ESPLoader.ESP_IMAGE_MAGIC) + + for _ in range(segments): + self.load_segment(load_file) + self.checksum = self.read_checksum(load_file) + + self.verify() + + def default_output_name(self, input_file): + """Derive a default output name from the ELF name.""" + return input_file + "-" + + def save(self, basename): + """Save a set of V1 images for flashing. Parameter is a base filename.""" + # IROM data goes in its own plain binary file + irom_segment = self.get_irom_segment() + if irom_segment is not None: + with open( + "%s0x%05x.bin" + % (basename, irom_segment.addr - ESP8266ROM.IROM_MAP_START), + "wb", + ) as f: + f.write(irom_segment.data) + + # everything but IROM goes at 0x00000 in an image file + normal_segments = self.get_non_irom_segments() + with open("%s0x00000.bin" % basename, "wb") as f: + self.write_common_header(f, normal_segments) + checksum = ESPLoader.ESP_CHECKSUM_MAGIC + for segment in normal_segments: + checksum = self.save_segment(f, segment, checksum) + self.append_checksum(f, checksum) + + +ESP8266ROM.BOOTLOADER_IMAGE = ESP8266ROMFirmwareImage + + +class ESP8266V2FirmwareImage(BaseFirmwareImage): + """'Version 2' firmware image, segments loaded by software bootloader stub + (ie Espressif bootloader or rboot) + """ + + ROM_LOADER = ESP8266ROM + # First byte of the "v2" application image + IMAGE_V2_MAGIC = 0xEA + + # First 'segment' value in a "v2" application image, + # appears to be a constant version value? + IMAGE_V2_SEGMENT = 4 + + def __init__(self, load_file=None): + super(ESP8266V2FirmwareImage, self).__init__() + self.version = 2 + if load_file is not None: + segments = self.load_common_header(load_file, self.IMAGE_V2_MAGIC) + if segments != self.IMAGE_V2_SEGMENT: + # segment count is not really segment count here, + # but we expect to see '4' + print( + 'Warning: V2 header has unexpected "segment" count %d (usually 4)' + % segments + ) + + # irom segment comes before the second header + # + # the file is saved in the image with a zero load address + # in the header, so we need to calculate a load address + irom_segment = self.load_segment(load_file, True) + # for actual mapped addr, add ESP8266ROM.IROM_MAP_START + flashing_addr + 8 + irom_segment.addr = 0 + irom_segment.include_in_checksum = False + + first_flash_mode = self.flash_mode + first_flash_size_freq = self.flash_size_freq + first_entrypoint = self.entrypoint + # load the second header + + segments = self.load_common_header(load_file, ESPLoader.ESP_IMAGE_MAGIC) + + if first_flash_mode != self.flash_mode: + print( + "WARNING: Flash mode value in first header (0x%02x) disagrees " + "with second (0x%02x). Using second value." + % (first_flash_mode, self.flash_mode) + ) + if first_flash_size_freq != self.flash_size_freq: + print( + "WARNING: Flash size/freq value in first header (0x%02x) disagrees " + "with second (0x%02x). Using second value." + % (first_flash_size_freq, self.flash_size_freq) + ) + if first_entrypoint != self.entrypoint: + print( + "WARNING: Entrypoint address in first header (0x%08x) disagrees " + "with second header (0x%08x). Using second value." + % (first_entrypoint, self.entrypoint) + ) + + # load all the usual segments + for _ in range(segments): + self.load_segment(load_file) + self.checksum = self.read_checksum(load_file) + + self.verify() + + def default_output_name(self, input_file): + """Derive a default output name from the ELF name.""" + irom_segment = self.get_irom_segment() + if irom_segment is not None: + irom_offs = irom_segment.addr - ESP8266ROM.IROM_MAP_START + else: + irom_offs = 0 + return "%s-0x%05x.bin" % ( + os.path.splitext(input_file)[0], + irom_offs & ~(ESPLoader.FLASH_SECTOR_SIZE - 1), + ) + + def save(self, filename): + with open(filename, "wb") as f: + # Save first header for irom0 segment + f.write( + struct.pack( + b" 0: + last_addr = flash_segments[0].addr + for segment in flash_segments[1:]: + if segment.addr // self.IROM_ALIGN == last_addr // self.IROM_ALIGN: + raise FatalError( + "Segment loaded at 0x%08x lands in same 64KB flash mapping " + "as segment loaded at 0x%08x. Can't generate binary. " + "Suggest changing linker script or ELF to merge sections." + % (segment.addr, last_addr) + ) + last_addr = segment.addr + + def get_alignment_data_needed(segment): + # Actual alignment (in data bytes) required for a segment header: + # positioned so that after we write the next 8 byte header, + # file_offs % IROM_ALIGN == segment.addr % IROM_ALIGN + # + # (this is because the segment's vaddr may not be IROM_ALIGNed, + # more likely is aligned IROM_ALIGN+0x18 + # to account for the binary file header + align_past = (segment.addr % self.IROM_ALIGN) - self.SEG_HEADER_LEN + pad_len = (self.IROM_ALIGN - (f.tell() % self.IROM_ALIGN)) + align_past + if pad_len == 0 or pad_len == self.IROM_ALIGN: + return 0 # already aligned + + # subtract SEG_HEADER_LEN a second time, + # as the padding block has a header as well + pad_len -= self.SEG_HEADER_LEN + if pad_len < 0: + pad_len += self.IROM_ALIGN + return pad_len + + # try to fit each flash segment on a 64kB aligned boundary + # by padding with parts of the non-flash segments... + while len(flash_segments) > 0: + segment = flash_segments[0] + pad_len = get_alignment_data_needed(segment) + if pad_len > 0: # need to pad + if len(ram_segments) > 0 and pad_len > self.SEG_HEADER_LEN: + pad_segment = ram_segments[0].split_image(pad_len) + if len(ram_segments[0].data) == 0: + ram_segments.pop(0) + else: + pad_segment = ImageSegment(0, b"\x00" * pad_len, f.tell()) + checksum = self.save_segment(f, pad_segment, checksum) + total_segments += 1 + else: + # write the flash segment + assert ( + f.tell() + 8 + ) % self.IROM_ALIGN == segment.addr % self.IROM_ALIGN + checksum = self.save_flash_segment(f, segment, checksum) + flash_segments.pop(0) + total_segments += 1 + + # flash segments all written, so write any remaining RAM segments + for segment in ram_segments: + checksum = self.save_segment(f, segment, checksum) + total_segments += 1 + + if self.secure_pad: + # pad the image so that after signing it will end on a a 64KB boundary. + # This ensures all mapped flash content will be verified. + if not self.append_digest: + raise FatalError( + "secure_pad only applies if a SHA-256 digest " + "is also appended to the image" + ) + align_past = (f.tell() + self.SEG_HEADER_LEN) % self.IROM_ALIGN + # 16 byte aligned checksum + # (force the alignment to simplify calculations) + checksum_space = 16 + if self.secure_pad == "1": + # after checksum: SHA-256 digest + + # (to be added by signing process) version, + # signature + 12 trailing bytes due to alignment + space_after_checksum = 32 + 4 + 64 + 12 + elif self.secure_pad == "2": # Secure Boot V2 + # after checksum: SHA-256 digest + + # signature sector, + # but we place signature sector after the 64KB boundary + space_after_checksum = 32 + pad_len = ( + self.IROM_ALIGN - align_past - checksum_space - space_after_checksum + ) % self.IROM_ALIGN + pad_segment = ImageSegment(0, b"\x00" * pad_len, f.tell()) + + checksum = self.save_segment(f, pad_segment, checksum) + total_segments += 1 + + # done writing segments + self.append_checksum(f, checksum) + image_length = f.tell() + + if self.secure_pad: + assert ((image_length + space_after_checksum) % self.IROM_ALIGN) == 0 + + # kinda hacky: go back to the initial header and write the new segment count + # that includes padding segments. This header is not checksummed + f.seek(1) + f.write(bytes([total_segments])) + + if self.append_digest: + # calculate the SHA256 of the whole file and append it + f.seek(0) + digest = hashlib.sha256() + digest.update(f.read(image_length)) + f.write(digest.digest()) + + if self.pad_to_size: + image_length = f.tell() + if image_length % self.pad_to_size != 0: + pad_by = self.pad_to_size - (image_length % self.pad_to_size) + f.write(b"\xff" * pad_by) + + with open(filename, "wb") as real_file: + real_file.write(f.getvalue()) + + def save_flash_segment(self, f, segment, checksum=None): + """ + Save the next segment to the image file, return next checksum value if provided + """ + segment_end_pos = f.tell() + len(segment.data) + self.SEG_HEADER_LEN + segment_len_remainder = segment_end_pos % self.IROM_ALIGN + if segment_len_remainder < 0x24: + # Work around a bug in ESP-IDF 2nd stage bootloader, that it didn't map the + # last MMU page, if an IROM/DROM segment was < 0x24 bytes + # over the page boundary. + segment.data += b"\x00" * (0x24 - segment_len_remainder) + return self.save_segment(f, segment, checksum) + + def load_extended_header(self, load_file): + def split_byte(n): + return (n & 0x0F, (n >> 4) & 0x0F) + + fields = list( + struct.unpack(self.EXTENDED_HEADER_STRUCT_FMT, load_file.read(16)) + ) + + self.wp_pin = fields[0] + + # SPI pin drive stengths are two per byte + self.clk_drv, self.q_drv = split_byte(fields[1]) + self.d_drv, self.cs_drv = split_byte(fields[2]) + self.hd_drv, self.wp_drv = split_byte(fields[3]) + + self.chip_id = fields[4] + if self.chip_id != self.ROM_LOADER.IMAGE_CHIP_ID: + print( + ( + "Unexpected chip id in image. Expected %d but value was %d. " + "Is this image for a different chip model?" + ) + % (self.ROM_LOADER.IMAGE_CHIP_ID, self.chip_id) + ) + + self.min_rev = fields[5] + self.min_rev_full = fields[6] + self.max_rev_full = fields[7] + + # reserved fields in the middle should all be zero + if any(f for f in fields[8:-1] if f != 0): + print( + "Warning: some reserved header fields have non-zero values. " + "This image may be from a newer esptool.py?" + ) + + append_digest = fields[-1] # last byte is append_digest + if append_digest in [0, 1]: + self.append_digest = append_digest == 1 + else: + raise RuntimeError( + "Invalid value for append_digest field (0x%02x). Should be 0 or 1.", + append_digest, + ) + + def save_extended_header(self, save_file): + def join_byte(ln, hn): + return (ln & 0x0F) + ((hn & 0x0F) << 4) + + append_digest = 1 if self.append_digest else 0 + + fields = [ + self.wp_pin, + join_byte(self.clk_drv, self.q_drv), + join_byte(self.d_drv, self.cs_drv), + join_byte(self.hd_drv, self.wp_drv), + self.ROM_LOADER.IMAGE_CHIP_ID, + self.min_rev, + self.min_rev_full, + self.max_rev_full, + ] + fields += [0] * 4 # padding + fields += [append_digest] + + packed = struct.pack(self.EXTENDED_HEADER_STRUCT_FMT, *fields) + save_file.write(packed) + + +class ESP8266V3FirmwareImage(ESP32FirmwareImage): + """ESP8266 V3 firmware image is very similar to ESP32 image""" + + EXTENDED_HEADER_STRUCT_FMT = "B" * 16 + + def is_flash_addr(self, addr): + return addr > ESP8266ROM.IROM_MAP_START + + def save(self, filename): + total_segments = 0 + with io.BytesIO() as f: # write file to memory first + self.write_common_header(f, self.segments) + + checksum = ESPLoader.ESP_CHECKSUM_MAGIC + + # split segments into flash-mapped vs ram-loaded, + # and take copies so we can mutate them + flash_segments = [ + copy.deepcopy(s) + for s in sorted(self.segments, key=lambda s: s.addr) + if self.is_flash_addr(s.addr) and len(s.data) + ] + ram_segments = [ + copy.deepcopy(s) + for s in sorted(self.segments, key=lambda s: s.addr) + if not self.is_flash_addr(s.addr) and len(s.data) + ] + + # check for multiple ELF sections that are mapped in the same + # flash mapping region. This is usually a sign of a broken linker script, + # but if you have a legitimate use case then let us know + if len(flash_segments) > 0: + last_addr = flash_segments[0].addr + for segment in flash_segments[1:]: + if segment.addr // self.IROM_ALIGN == last_addr // self.IROM_ALIGN: + raise FatalError( + "Segment loaded at 0x%08x lands in same 64KB flash mapping " + "as segment loaded at 0x%08x. Can't generate binary. " + "Suggest changing linker script or ELF to merge sections." + % (segment.addr, last_addr) + ) + last_addr = segment.addr + + # try to fit each flash segment on a 64kB aligned boundary + # by padding with parts of the non-flash segments... + while len(flash_segments) > 0: + segment = flash_segments[0] + # remove 8 bytes empty data for insert segment header + if segment.name == ".flash.rodata": + segment.data = segment.data[8:] + # write the flash segment + checksum = self.save_segment(f, segment, checksum) + flash_segments.pop(0) + total_segments += 1 + + # flash segments all written, so write any remaining RAM segments + for segment in ram_segments: + checksum = self.save_segment(f, segment, checksum) + total_segments += 1 + + # done writing segments + self.append_checksum(f, checksum) + image_length = f.tell() + + # kinda hacky: go back to the initial header and write the new segment count + # that includes padding segments. This header is not checksummed + f.seek(1) + f.write(bytes([total_segments])) + + if self.append_digest: + # calculate the SHA256 of the whole file and append it + f.seek(0) + digest = hashlib.sha256() + digest.update(f.read(image_length)) + f.write(digest.digest()) + + with open(filename, "wb") as real_file: + real_file.write(f.getvalue()) + + def load_extended_header(self, load_file): + def split_byte(n): + return (n & 0x0F, (n >> 4) & 0x0F) + + fields = list( + struct.unpack(self.EXTENDED_HEADER_STRUCT_FMT, load_file.read(16)) + ) + + self.wp_pin = fields[0] + + # SPI pin drive stengths are two per byte + self.clk_drv, self.q_drv = split_byte(fields[1]) + self.d_drv, self.cs_drv = split_byte(fields[2]) + self.hd_drv, self.wp_drv = split_byte(fields[3]) + + if fields[15] in [0, 1]: + self.append_digest = fields[15] == 1 + else: + raise RuntimeError( + "Invalid value for append_digest field (0x%02x). Should be 0 or 1.", + fields[15], + ) + + # remaining fields in the middle should all be zero + if any(f for f in fields[4:15] if f != 0): + print( + "Warning: some reserved header fields have non-zero values. " + "This image may be from a newer esptool.py?" + ) + + +ESP32ROM.BOOTLOADER_IMAGE = ESP32FirmwareImage + + +class ESP32S2FirmwareImage(ESP32FirmwareImage): + """ESP32S2 Firmware Image almost exactly the same as ESP32FirmwareImage""" + + ROM_LOADER = ESP32S2ROM + + +ESP32S2ROM.BOOTLOADER_IMAGE = ESP32S2FirmwareImage + + +class ESP32S3BETA2FirmwareImage(ESP32FirmwareImage): + """ESP32S3 Firmware Image almost exactly the same as ESP32FirmwareImage""" + + ROM_LOADER = ESP32S3BETA2ROM + + +ESP32S3BETA2ROM.BOOTLOADER_IMAGE = ESP32S3BETA2FirmwareImage + + +class ESP32S3FirmwareImage(ESP32FirmwareImage): + """ESP32S3 Firmware Image almost exactly the same as ESP32FirmwareImage""" + + ROM_LOADER = ESP32S3ROM + + +ESP32S3ROM.BOOTLOADER_IMAGE = ESP32S3FirmwareImage + + +class ESP32C3FirmwareImage(ESP32FirmwareImage): + """ESP32C3 Firmware Image almost exactly the same as ESP32FirmwareImage""" + + ROM_LOADER = ESP32C3ROM + + +ESP32C3ROM.BOOTLOADER_IMAGE = ESP32C3FirmwareImage + + +class ESP32C6BETAFirmwareImage(ESP32FirmwareImage): + """ESP32C6 Firmware Image almost exactly the same as ESP32FirmwareImage""" + + ROM_LOADER = ESP32C6BETAROM + + +ESP32C6BETAROM.BOOTLOADER_IMAGE = ESP32C6BETAFirmwareImage + + +class ESP32H2BETA1FirmwareImage(ESP32FirmwareImage): + """ESP32H2 Firmware Image almost exactly the same as ESP32FirmwareImage""" + + ROM_LOADER = ESP32H2BETA1ROM + + +ESP32H2BETA1ROM.BOOTLOADER_IMAGE = ESP32H2BETA1FirmwareImage + + +class ESP32H2BETA2FirmwareImage(ESP32FirmwareImage): + """ESP32H2 Firmware Image almost exactly the same as ESP32FirmwareImage""" + + ROM_LOADER = ESP32H2BETA2ROM + + +ESP32H2BETA2ROM.BOOTLOADER_IMAGE = ESP32H2BETA2FirmwareImage + + +class ESP32C2FirmwareImage(ESP32FirmwareImage): + """ESP32C2 Firmware Image almost exactly the same as ESP32FirmwareImage""" + + ROM_LOADER = ESP32C2ROM + + def set_mmu_page_size(self, size): + if size not in [16384, 32768, 65536]: + raise FatalError( + "{} bytes is not a valid ESP32-C2 page size, " + "select from 64KB, 32KB, 16KB.".format(size) + ) + self.IROM_ALIGN = size + + +ESP32C2ROM.BOOTLOADER_IMAGE = ESP32C2FirmwareImage + + +class ESP32C6FirmwareImage(ESP32FirmwareImage): + """ESP32C6 Firmware Image almost exactly the same as ESP32FirmwareImage""" + + ROM_LOADER = ESP32C6ROM + + def set_mmu_page_size(self, size): + if size not in [8192, 16384, 32768, 65536]: + raise FatalError( + "{} bytes is not a valid ESP32-C6 page size, " + "select from 64KB, 32KB, 16KB, 8KB.".format(size) + ) + self.IROM_ALIGN = size + + +ESP32C6ROM.BOOTLOADER_IMAGE = ESP32C6FirmwareImage + + +class ESP32H2FirmwareImage(ESP32C6FirmwareImage): + """ESP32H2 Firmware Image almost exactly the same as ESP32FirmwareImage""" + + ROM_LOADER = ESP32H2ROM + + +ESP32H2ROM.BOOTLOADER_IMAGE = ESP32H2FirmwareImage + + +class ELFFile(object): + SEC_TYPE_PROGBITS = 0x01 + SEC_TYPE_STRTAB = 0x03 + SEC_TYPE_INITARRAY = 0x0E + SEC_TYPE_FINIARRAY = 0x0F + + PROG_SEC_TYPES = (SEC_TYPE_PROGBITS, SEC_TYPE_INITARRAY, SEC_TYPE_FINIARRAY) + + LEN_SEC_HEADER = 0x28 + + SEG_TYPE_LOAD = 0x01 + LEN_SEG_HEADER = 0x20 + + def __init__(self, name): + # Load sections from the ELF file + self.name = name + with open(self.name, "rb") as f: + self._read_elf_file(f) + + def get_section(self, section_name): + for s in self.sections: + if s.name == section_name: + return s + raise ValueError("No section %s in ELF file" % section_name) + + def _read_elf_file(self, f): + # read the ELF file header + LEN_FILE_HEADER = 0x34 + try: + ( + ident, + _type, + machine, + _version, + self.entrypoint, + _phoff, + shoff, + _flags, + _ehsize, + _phentsize, + _phnum, + shentsize, + shnum, + shstrndx, + ) = struct.unpack("<16sHHLLLLLHHHHHH", f.read(LEN_FILE_HEADER)) + except struct.error as e: + raise FatalError( + "Failed to read a valid ELF header from %s: %s" % (self.name, e) + ) + + if byte(ident, 0) != 0x7F or ident[1:4] != b"ELF": + raise FatalError("%s has invalid ELF magic header" % self.name) + if machine not in [0x5E, 0xF3]: + raise FatalError( + "%s does not appear to be an Xtensa or an RISCV ELF file. " + "e_machine=%04x" % (self.name, machine) + ) + if shentsize != self.LEN_SEC_HEADER: + raise FatalError( + "%s has unexpected section header entry size 0x%x (not 0x%x)" + % (self.name, shentsize, self.LEN_SEC_HEADER) + ) + if shnum == 0: + raise FatalError("%s has 0 section headers" % (self.name)) + self._read_sections(f, shoff, shnum, shstrndx) + self._read_segments(f, _phoff, _phnum, shstrndx) + + def _read_sections(self, f, section_header_offs, section_header_count, shstrndx): + f.seek(section_header_offs) + len_bytes = section_header_count * self.LEN_SEC_HEADER + section_header = f.read(len_bytes) + if len(section_header) == 0: + raise FatalError( + "No section header found at offset %04x in ELF file." + % section_header_offs + ) + if len(section_header) != (len_bytes): + raise FatalError( + "Only read 0x%x bytes from section header (expected 0x%x.) " + "Truncated ELF file?" % (len(section_header), len_bytes) + ) + + # walk through the section header and extract all sections + section_header_offsets = range(0, len(section_header), self.LEN_SEC_HEADER) + + def read_section_header(offs): + name_offs, sec_type, _flags, lma, sec_offs, size = struct.unpack_from( + " 0 + ] + self.sections = prog_sections + + def _read_segments(self, f, segment_header_offs, segment_header_count, shstrndx): + f.seek(segment_header_offs) + len_bytes = segment_header_count * self.LEN_SEG_HEADER + segment_header = f.read(len_bytes) + if len(segment_header) == 0: + raise FatalError( + "No segment header found at offset %04x in ELF file." + % segment_header_offs + ) + if len(segment_header) != (len_bytes): + raise FatalError( + "Only read 0x%x bytes from segment header (expected 0x%x.) " + "Truncated ELF file?" % (len(segment_header), len_bytes) + ) + + # walk through the segment header and extract all segments + segment_header_offsets = range(0, len(segment_header), self.LEN_SEG_HEADER) + + def read_segment_header(offs): + ( + seg_type, + seg_offs, + _vaddr, + lma, + size, + _memsize, + _flags, + _align, + ) = struct.unpack_from(" 0 + ] + self.segments = prog_segments + + def sha256(self): + # return SHA256 hash of the input ELF file + sha256 = hashlib.sha256() + with open(self.name, "rb") as f: + sha256.update(f.read()) + return sha256.digest() diff --git a/installer/bin/esptool/esptool/cmds.py b/installer/bin/esptool/esptool/cmds.py new file mode 100644 index 0000000..a299058 --- /dev/null +++ b/installer/bin/esptool/esptool/cmds.py @@ -0,0 +1,1198 @@ +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import hashlib +import io +import os +import struct +import sys +import time +import zlib + +from .bin_image import ELFFile, ImageSegment, LoadFirmwareImage +from .bin_image import ( + ESP8266ROMFirmwareImage, + ESP8266V2FirmwareImage, + ESP8266V3FirmwareImage, +) +from .loader import ( + DEFAULT_CONNECT_ATTEMPTS, + DEFAULT_TIMEOUT, + ERASE_WRITE_TIMEOUT_PER_MB, + ESPLoader, + timeout_per_mb, +) +from .targets import CHIP_DEFS, CHIP_LIST, ROM_LIST +from .util import ( + FatalError, + NotImplementedInROMError, + NotSupportedError, + UnsupportedCommandError, +) +from .util import ( + div_roundup, + flash_size_bytes, + hexify, + pad_to, + print_overwrite, +) + +DETECTED_FLASH_SIZES = { + 0x12: "256KB", + 0x13: "512KB", + 0x14: "1MB", + 0x15: "2MB", + 0x16: "4MB", + 0x17: "8MB", + 0x18: "16MB", + 0x19: "32MB", + 0x1A: "64MB", + 0x1B: "128MB", + 0x1C: "256MB", + 0x20: "64MB", + 0x21: "128MB", + 0x22: "256MB", + 0x32: "256KB", + 0x33: "512KB", + 0x34: "1MB", + 0x35: "2MB", + 0x36: "4MB", + 0x37: "8MB", + 0x38: "16MB", + 0x39: "32MB", + 0x3A: "64MB", +} + +FLASH_MODES = {"qio": 0, "qout": 1, "dio": 2, "dout": 3} + + +def detect_chip( + port=ESPLoader.DEFAULT_PORT, + baud=ESPLoader.ESP_ROM_BAUD, + connect_mode="default_reset", + trace_enabled=False, + connect_attempts=DEFAULT_CONNECT_ATTEMPTS, +): + """Use serial access to detect the chip type. + + First, get_security_info command is sent to detect the ID of the chip + (supported only by ESP32-C3 and later, works even in the Secure Download Mode). + If this fails, we reconnect and fall-back to reading the magic number. + It's mapped at a specific ROM address and has a different value on each chip model. + This way we use one memory read and compare it to the magic number for each chip. + + This routine automatically performs ESPLoader.connect() (passing + connect_mode parameter) as part of querying the chip. + """ + inst = None + detect_port = ESPLoader(port, baud, trace_enabled=trace_enabled) + if detect_port.serial_port.startswith("rfc2217:"): + detect_port.USES_RFC2217 = True + detect_port.connect(connect_mode, connect_attempts, detecting=True) + try: + print("Detecting chip type...", end="") + chip_id = detect_port.get_chip_id() + for cls in [ + n for n in ROM_LIST if n.CHIP_NAME not in ("ESP8266", "ESP32", "ESP32S2") + ]: + # cmd not supported on ESP8266 and ESP32 + ESP32-S2 doesn't return chip_id + if chip_id == cls.IMAGE_CHIP_ID: + inst = cls(detect_port._port, baud, trace_enabled=trace_enabled) + try: + inst.read_reg( + ESPLoader.CHIP_DETECT_MAGIC_REG_ADDR + ) # Dummy read to check Secure Download mode + except UnsupportedCommandError: + inst.secure_download_mode = True + inst._post_connect() + break + else: + err_msg = f"Unexpected chip ID value {chip_id}." + except (UnsupportedCommandError, struct.error, FatalError) as e: + # UnsupportedCommmanddError: ESP8266/ESP32 ROM + # struct.error: ESP32-S2 + # FatalError: ESP8266/ESP32 STUB + print(" Unsupported detection protocol, switching and trying again...") + try: + # ESP32/ESP8266 are reset after an unsupported command, need to reconnect + # (not needed on ESP32-S2) + if not isinstance(e, struct.error): + detect_port.connect( + connect_mode, connect_attempts, detecting=True, warnings=False + ) + print("Detecting chip type...", end="") + sys.stdout.flush() + chip_magic_value = detect_port.read_reg( + ESPLoader.CHIP_DETECT_MAGIC_REG_ADDR + ) + + for cls in ROM_LIST: + if chip_magic_value in cls.CHIP_DETECT_MAGIC_VALUE: + inst = cls(detect_port._port, baud, trace_enabled=trace_enabled) + inst._post_connect() + inst.check_chip_id() + break + else: + err_msg = f"Unexpected chip magic value {chip_magic_value:#010x}." + except UnsupportedCommandError: + raise FatalError( + "Unsupported Command Error received. " + "Probably this means Secure Download Mode is enabled, " + "autodetection will not work. Need to manually specify the chip." + ) + finally: + if inst is not None: + print(" %s" % inst.CHIP_NAME, end="") + if detect_port.sync_stub_detected: + inst = inst.STUB_CLASS(inst) + inst.sync_stub_detected = True + print("") # end line + return inst + raise FatalError( + f"{err_msg} Failed to autodetect chip type." + "\nProbably it is unsupported by this version of esptool." + ) + + +# "Operation" commands, executable at command line. One function each +# +# Each function takes either two args (, ) or a single +# argument. + + +def load_ram(esp, args): + image = LoadFirmwareImage(esp.CHIP_NAME, args.filename) + + print("RAM boot...") + for seg in image.segments: + size = len(seg.data) + print("Downloading %d bytes at %08x..." % (size, seg.addr), end=" ") + sys.stdout.flush() + esp.mem_begin( + size, div_roundup(size, esp.ESP_RAM_BLOCK), esp.ESP_RAM_BLOCK, seg.addr + ) + + seq = 0 + while len(seg.data) > 0: + esp.mem_block(seg.data[0 : esp.ESP_RAM_BLOCK], seq) + seg.data = seg.data[esp.ESP_RAM_BLOCK :] + seq += 1 + print("done!") + + print("All segments done, executing at %08x" % image.entrypoint) + esp.mem_finish(image.entrypoint) + + +def read_mem(esp, args): + print("0x%08x = 0x%08x" % (args.address, esp.read_reg(args.address))) + + +def write_mem(esp, args): + esp.write_reg(args.address, args.value, args.mask, 0) + print("Wrote %08x, mask %08x to %08x" % (args.value, args.mask, args.address)) + + +def dump_mem(esp, args): + with open(args.filename, "wb") as f: + for i in range(args.size // 4): + d = esp.read_reg(args.address + (i * 4)) + f.write(struct.pack(b"> 16 + args.flash_size = DETECTED_FLASH_SIZES.get(size_id) + if args.flash_size is None: + print( + "Warning: Could not auto-detect Flash size (FlashID=0x%x, SizeID=0x%x)," + " defaulting to 4MB" % (flash_id, size_id) + ) + args.flash_size = "4MB" + else: + print("Auto-detected Flash size:", args.flash_size) + + +def _update_image_flash_params(esp, address, args, image): + """ + Modify the flash mode & size bytes if this looks like an executable bootloader image + """ + if len(image) < 8: + return image # not long enough to be a bootloader image + + # unpack the (potential) image header + magic, _, flash_mode, flash_size_freq = struct.unpack("BBBB", image[:4]) + if address != esp.BOOTLOADER_FLASH_OFFSET: + return image # not flashing bootloader offset, so don't modify this + + if (args.flash_mode, args.flash_freq, args.flash_size) == ("keep",) * 3: + return image # all settings are 'keep', not modifying anything + + # easy check if this is an image: does it start with a magic byte? + if magic != esp.ESP_IMAGE_MAGIC: + print( + "Warning: Image file at 0x%x doesn't look like an image file, " + "so not changing any flash settings." % address + ) + return image + + # make sure this really is an image, and not just data that + # starts with esp.ESP_IMAGE_MAGIC (mostly a problem for encrypted + # images that happen to start with a magic byte + try: + test_image = esp.BOOTLOADER_IMAGE(io.BytesIO(image)) + test_image.verify() + except Exception: + print( + "Warning: Image file at 0x%x is not a valid %s image, " + "so not changing any flash settings." % (address, esp.CHIP_NAME) + ) + return image + + # After the 8-byte header comes the extended header for chips others than ESP8266. + # The 15th byte of the extended header indicates if the image is protected by + # a SHA256 checksum. In that case we should not modify the header because + # the checksum check would fail. + sha_implies_keep = args.chip != "esp8266" and image[8 + 15] == 1 + + def print_keep_warning(arg_to_keep, arg_used): + print( + "Warning: Image file at {addr} is protected with a hash checksum, " + "so not changing the flash {arg} setting. " + "Use the --flash_{arg}=keep option instead of --flash_{arg}={arg_orig} " + "in order to remove this warning, or use the --dont-append-digest option " + "for the elf2image command in order to generate an image file " + "without a hash checksum".format( + addr=hex(address), arg=arg_to_keep, arg_orig=arg_used + ) + ) + + if args.flash_mode != "keep": + new_flash_mode = FLASH_MODES[args.flash_mode] + if flash_mode != new_flash_mode and sha_implies_keep: + print_keep_warning("mode", args.flash_mode) + else: + flash_mode = new_flash_mode + + flash_freq = flash_size_freq & 0x0F + if args.flash_freq != "keep": + new_flash_freq = esp.parse_flash_freq_arg(args.flash_freq) + if flash_freq != new_flash_freq and sha_implies_keep: + print_keep_warning("frequency", args.flash_freq) + else: + flash_freq = new_flash_freq + + flash_size = flash_size_freq & 0xF0 + if args.flash_size != "keep": + new_flash_size = esp.parse_flash_size_arg(args.flash_size) + if flash_size != new_flash_size and sha_implies_keep: + print_keep_warning("size", args.flash_size) + else: + flash_size = new_flash_size + + flash_params = struct.pack(b"BB", flash_mode, flash_size + flash_freq) + if flash_params != image[2:4]: + print("Flash params set to 0x%04x" % struct.unpack(">H", flash_params)) + image = image[0:2] + flash_params + image[4:] + return image + + +def write_flash(esp, args): + # set args.compress based on default behaviour: + # -> if either --compress or --no-compress is set, honour that + # -> otherwise, set --compress unless --no-stub is set + if args.compress is None and not args.no_compress: + args.compress = not args.no_stub + + if not args.force and esp.CHIP_NAME != "ESP8266" and not esp.secure_download_mode: + # Check if secure boot is active + if esp.get_secure_boot_enabled(): + for address, _ in args.addr_filename: + if address < 0x8000: + raise FatalError( + "Secure Boot detected, writing to flash regions < 0x8000 " + "is disabled to protect the bootloader. " + "Use --force to override, " + "please use with caution, otherwise it may brick your device!" + ) + # Check if chip_id and min_rev in image are valid for the target in use + for _, argfile in args.addr_filename: + try: + image = LoadFirmwareImage(esp.CHIP_NAME, argfile) + except (FatalError, struct.error, RuntimeError): + continue + finally: + argfile.seek(0) # LoadFirmwareImage changes the file handle position + if image.chip_id != esp.IMAGE_CHIP_ID: + raise FatalError( + f"{argfile.name} is not an {esp.CHIP_NAME} image. " + "Use --force to flash anyway." + ) + + # this logic below decides which min_rev to use, min_rev or min/max_rev_full + if image.max_rev_full == 0: # image does not have max/min_rev_full fields + use_rev_full_fields = False + elif image.max_rev_full == 65535: # image has default value of max_rev_full + use_rev_full_fields = True + if ( + image.min_rev_full == 0 and image.min_rev != 0 + ): # min_rev_full is not set, min_rev is used + use_rev_full_fields = False + else: # max_rev_full set to a version + use_rev_full_fields = True + + if use_rev_full_fields: + rev = esp.get_chip_revision() + if rev < image.min_rev_full or rev > image.max_rev_full: + error_str = f"{argfile.name} requires chip revision in range " + error_str += ( + f"[v{image.min_rev_full // 100}.{image.min_rev_full % 100} - " + ) + if image.max_rev_full == 65535: + error_str += "max rev not set] " + else: + error_str += ( + f"v{image.max_rev_full // 100}.{image.max_rev_full % 100}] " + ) + error_str += f"(this chip is revision v{rev // 100}.{rev % 100})" + raise FatalError(f"{error_str}. Use --force to flash anyway.") + else: + # In IDF, image.min_rev is set based on Kconfig option. + # For C3 chip, image.min_rev is the Minor revision + # while for the rest chips it is the Major revision. + if esp.CHIP_NAME == "ESP32-C3": + rev = esp.get_minor_chip_version() + else: + rev = esp.get_major_chip_version() + if rev < image.min_rev: + raise FatalError( + f"{argfile.name} requires chip revision " + f"{image.min_rev} or higher (this chip is revision {rev}). " + "Use --force to flash anyway." + ) + + # In case we have encrypted files to write, + # we first do few sanity checks before actual flash + if args.encrypt or args.encrypt_files is not None: + do_write = True + + if not esp.secure_download_mode: + if esp.get_encrypted_download_disabled(): + raise FatalError( + "This chip has encrypt functionality " + "in UART download mode disabled. " + "This is the Flash Encryption configuration for Production mode " + "instead of Development mode." + ) + + crypt_cfg_efuse = esp.get_flash_crypt_config() + + if crypt_cfg_efuse is not None and crypt_cfg_efuse != 0xF: + print("Unexpected FLASH_CRYPT_CONFIG value: 0x%x" % (crypt_cfg_efuse)) + do_write = False + + enc_key_valid = esp.is_flash_encryption_key_valid() + + if not enc_key_valid: + print("Flash encryption key is not programmed") + do_write = False + + # Determine which files list contain the ones to encrypt + files_to_encrypt = args.addr_filename if args.encrypt else args.encrypt_files + + for address, argfile in files_to_encrypt: + if address % esp.FLASH_ENCRYPTED_WRITE_ALIGN: + print( + "File %s address 0x%x is not %d byte aligned, can't flash encrypted" + % (argfile.name, address, esp.FLASH_ENCRYPTED_WRITE_ALIGN) + ) + do_write = False + + if not do_write and not args.ignore_flash_encryption_efuse_setting: + raise FatalError( + "Can't perform encrypted flash write, " + "consult Flash Encryption documentation for more information" + ) + else: + if not args.force and esp.CHIP_NAME != "ESP8266": + # ESP32 does not support `get_security_info()` and `secure_download_mode` + if ( + esp.CHIP_NAME != "ESP32" + and esp.secure_download_mode + and bin(esp.get_security_info()["flash_crypt_cnt"]).count("1") & 1 != 0 + ): + raise FatalError( + "WARNING: Detected flash encryption and " + "secure download mode enabled.\n" + "Flashing plaintext binary may brick your device! " + "Use --force to override the warning." + ) + + if ( + not esp.secure_download_mode + and esp.get_encrypted_download_disabled() + and esp.get_flash_encryption_enabled() + ): + raise FatalError( + "WARNING: Detected flash encryption enabled and " + "download manual encrypt disabled.\n" + "Flashing plaintext binary may brick your device! " + "Use --force to override the warning." + ) + + # verify file sizes fit in flash + if args.flash_size != "keep": # TODO: check this even with 'keep' + flash_end = flash_size_bytes(args.flash_size) + for address, argfile in args.addr_filename: + argfile.seek(0, os.SEEK_END) + if address + argfile.tell() > flash_end: + raise FatalError( + "File %s (length %d) at offset %d " + "will not fit in %d bytes of flash. " + "Use --flash_size argument, or change flashing address." + % (argfile.name, argfile.tell(), address, flash_end) + ) + argfile.seek(0) + + if args.erase_all: + erase_flash(esp, args) + else: + for address, argfile in args.addr_filename: + argfile.seek(0, os.SEEK_END) + write_end = address + argfile.tell() + argfile.seek(0) + bytes_over = address % esp.FLASH_SECTOR_SIZE + if bytes_over != 0: + print( + "WARNING: Flash address {:#010x} is not aligned " + "to a {:#x} byte flash sector. " + "{:#x} bytes before this address will be erased.".format( + address, esp.FLASH_SECTOR_SIZE, bytes_over + ) + ) + # Print the address range of to-be-erased flash memory region + print( + "Flash will be erased from {:#010x} to {:#010x}...".format( + address - bytes_over, + div_roundup(write_end, esp.FLASH_SECTOR_SIZE) + * esp.FLASH_SECTOR_SIZE + - 1, + ) + ) + + """ Create a list describing all the files we have to flash. + Each entry holds an "encrypt" flag marking whether the file needs encryption or not. + This list needs to be sorted. + + First, append to each entry of our addr_filename list the flag args.encrypt + E.g., if addr_filename is [(0x1000, "partition.bin"), (0x8000, "bootloader")], + all_files will be [ + (0x1000, "partition.bin", args.encrypt), + (0x8000, "bootloader", args.encrypt) + ], + where, of course, args.encrypt is either True or False + """ + all_files = [ + (offs, filename, args.encrypt) for (offs, filename) in args.addr_filename + ] + + """ + Now do the same with encrypt_files list, if defined. + In this case, the flag is True + """ + if args.encrypt_files is not None: + encrypted_files_flag = [ + (offs, filename, True) for (offs, filename) in args.encrypt_files + ] + + # Concatenate both lists and sort them. + # As both list are already sorted, we could simply do a merge instead, + # but for the sake of simplicity and because the lists are very small, + # let's use sorted. + all_files = sorted(all_files + encrypted_files_flag, key=lambda x: x[0]) + + for address, argfile, encrypted in all_files: + compress = args.compress + + # Check whether we can compress the current file before flashing + if compress and encrypted: + print("\nWARNING: - compress and encrypt options are mutually exclusive ") + print("Will flash %s uncompressed" % argfile.name) + compress = False + + if args.no_stub: + print("Erasing flash...") + image = pad_to( + argfile.read(), esp.FLASH_ENCRYPTED_WRITE_ALIGN if encrypted else 4 + ) + if len(image) == 0: + print("WARNING: File %s is empty" % argfile.name) + continue + image = _update_image_flash_params(esp, address, args, image) + calcmd5 = hashlib.md5(image).hexdigest() + uncsize = len(image) + if compress: + uncimage = image + image = zlib.compress(uncimage, 9) + # Decompress the compressed binary a block at a time, + # to dynamically calculate the timeout based on the real write size + decompress = zlib.decompressobj() + blocks = esp.flash_defl_begin(uncsize, len(image), address) + else: + blocks = esp.flash_begin(uncsize, address, begin_rom_encrypted=encrypted) + argfile.seek(0) # in case we need it again + seq = 0 + bytes_sent = 0 # bytes sent on wire + bytes_written = 0 # bytes written to flash + t = time.time() + + timeout = DEFAULT_TIMEOUT + + while len(image) > 0: + print_overwrite( + "Writing at 0x%08x... (%d %%)" + % (address + bytes_written, 100 * (seq + 1) // blocks) + ) + sys.stdout.flush() + block = image[0 : esp.FLASH_WRITE_SIZE] + if compress: + # feeding each compressed block into the decompressor lets us + # see block-by-block how much will be written + block_uncompressed = len(decompress.decompress(block)) + bytes_written += block_uncompressed + block_timeout = max( + DEFAULT_TIMEOUT, + timeout_per_mb(ERASE_WRITE_TIMEOUT_PER_MB, block_uncompressed), + ) + if not esp.IS_STUB: + timeout = ( + block_timeout # ROM code writes block to flash before ACKing + ) + esp.flash_defl_block(block, seq, timeout=timeout) + if esp.IS_STUB: + # Stub ACKs when block is received, + # then writes to flash while receiving the block after it + timeout = block_timeout + else: + # Pad the last block + block = block + b"\xff" * (esp.FLASH_WRITE_SIZE - len(block)) + if encrypted: + esp.flash_encrypt_block(block, seq) + else: + esp.flash_block(block, seq) + bytes_written += len(block) + bytes_sent += len(block) + image = image[esp.FLASH_WRITE_SIZE :] + seq += 1 + + if esp.IS_STUB: + # Stub only writes each block to flash after 'ack'ing the receive, + # so do a final dummy operation which will not be 'ack'ed + # until the last block has actually been written out to flash + esp.read_reg(ESPLoader.CHIP_DETECT_MAGIC_REG_ADDR, timeout=timeout) + + t = time.time() - t + speed_msg = "" + if compress: + if t > 0.0: + speed_msg = " (effective %.1f kbit/s)" % (uncsize / t * 8 / 1000) + print_overwrite( + "Wrote %d bytes (%d compressed) at 0x%08x in %.1f seconds%s..." + % (uncsize, bytes_sent, address, t, speed_msg), + last_line=True, + ) + else: + if t > 0.0: + speed_msg = " (%.1f kbit/s)" % (bytes_written / t * 8 / 1000) + print_overwrite( + "Wrote %d bytes at 0x%08x in %.1f seconds%s..." + % (bytes_written, address, t, speed_msg), + last_line=True, + ) + + if not encrypted and not esp.secure_download_mode: + try: + res = esp.flash_md5sum(address, uncsize) + if res != calcmd5: + print("File md5: %s" % calcmd5) + print("Flash md5: %s" % res) + print( + "MD5 of 0xFF is %s" + % (hashlib.md5(b"\xFF" * uncsize).hexdigest()) + ) + raise FatalError("MD5 of file does not match data in flash!") + else: + print("Hash of data verified.") + except NotImplementedInROMError: + pass + + print("\nLeaving...") + + if esp.IS_STUB: + # skip sending flash_finish to ROM loader here, + # as it causes the loader to exit and run user code + esp.flash_begin(0, 0) + + # Get the "encrypted" flag for the last file flashed + # Note: all_files list contains triplets like: + # (address: Integer, filename: String, encrypted: Boolean) + last_file_encrypted = all_files[-1][2] + + # Check whether the last file flashed was compressed or not + if args.compress and not last_file_encrypted: + esp.flash_defl_finish(False) + else: + esp.flash_finish(False) + + if args.verify: + print("Verifying just-written flash...") + print( + "(This option is deprecated, " + "flash contents are now always read back after flashing.)" + ) + # If some encrypted files have been flashed, + # print a warning saying that we won't check them + if args.encrypt or args.encrypt_files is not None: + print("WARNING: - cannot verify encrypted files, they will be ignored") + # Call verify_flash function only if there is at least + # one non-encrypted file flashed + if not args.encrypt: + verify_flash(esp, args) + + +def image_info(args): + def v2(): + def get_key_from_value(dict, val): + """Get key from value in dictionary""" + for key, value in dict.items(): + if value == val: + return key + return None + + print() + title = "{} image header".format(args.chip.upper()) + print(title) + print("=" * len(title)) + print("Image version: {}".format(image.version)) + print( + "Entry point: {:#8x}".format(image.entrypoint) + if image.entrypoint != 0 + else "Entry point not set" + ) + + print("Segments: {}".format(len(image.segments))) + + # Flash size + flash_s_bits = image.flash_size_freq & 0xF0 # high four bits + flash_s = get_key_from_value(image.ROM_LOADER.FLASH_SIZES, flash_s_bits) + print( + "Flash size: {}".format(flash_s) + if flash_s is not None + else "WARNING: Invalid flash size ({:#02x})".format(flash_s_bits) + ) + + # Flash frequency + flash_fr_bits = image.flash_size_freq & 0x0F # low four bits + flash_fr = get_key_from_value(image.ROM_LOADER.FLASH_FREQUENCY, flash_fr_bits) + print( + "Flash freq: {}".format(flash_fr) + if flash_fr is not None + else "WARNING: Invalid flash frequency ({:#02x})".format(flash_fr_bits) + ) + + # Flash mode + flash_mode = get_key_from_value(FLASH_MODES, image.flash_mode) + print( + "Flash mode: {}".format(flash_mode.upper()) + if flash_mode is not None + else "WARNING: Invalid flash mode ({})".format(image.flash_mode) + ) + + # Extended header (ESP32 and later only) + if args.chip != "esp8266": + print() + title = "{} extended image header".format(args.chip.upper()) + print(title) + print("=" * len(title)) + print("WP pin: {:#02x}".format(image.wp_pin)) + print( + "Flash pins drive settings: " + "clk_drv: {:#02x}, q_drv: {:#02x}, d_drv: {:#02x}, " + "cs0_drv: {:#02x}, hd_drv: {:#02x}, wp_drv: {:#02x}".format( + image.clk_drv, + image.q_drv, + image.d_drv, + image.cs_drv, + image.hd_drv, + image.wp_drv, + ) + ) + print("Chip ID: {}".format(image.chip_id)) + print( + "Minimal chip revision: " + f"v{image.min_rev_full // 100}.{image.min_rev_full % 100}, " + f"(legacy min_rev = {image.min_rev})" + ) + print( + "Maximal chip revision: " + f"v{image.max_rev_full // 100}.{image.max_rev_full % 100}" + ) + print() + + # Segments overview + title = "Segments information" + print(title) + print("=" * len(title)) + headers_str = "{:>7} {:>7} {:>10} {:>10} {:10}" + print( + headers_str.format( + "Segment", "Length", "Load addr", "File offs", "Memory types" + ) + ) + print( + "{} {} {} {} {}".format("-" * 7, "-" * 7, "-" * 10, "-" * 10, "-" * 12) + ) + format_str = "{:7} {:#07x} {:#010x} {:#010x} {}" + app_desc = None + for idx, seg in enumerate(image.segments, start=1): + segs = seg.get_memory_type(image) + seg_name = ", ".join(segs) + if "DROM" in segs: # The DROM segment starts with the esp_app_desc_t struct + app_desc = seg.data[:256] + print( + format_str.format(idx, len(seg.data), seg.addr, seg.file_offs, seg_name) + ) + print() + + # Footer + title = f"{args.chip.upper()} image footer" + print(title) + print("=" * len(title)) + calc_checksum = image.calculate_checksum() + print( + "Checksum: {:#02x} ({})".format( + image.checksum, + "valid" + if image.checksum == calc_checksum + else "invalid - calculated {:02x}".format(calc_checksum), + ) + ) + try: + digest_msg = "Not appended" + if image.append_digest: + is_valid = image.stored_digest == image.calc_digest + digest_msg = "{} ({})".format( + hexify(image.calc_digest, uppercase=False), + "valid" if is_valid else "invalid", + ) + print("Validation hash: {}".format(digest_msg)) + except AttributeError: + pass # ESP8266 image has no append_digest field + + if app_desc: + APP_DESC_STRUCT_FMT = " 1 else "")) + + image.verify() + + if args.output is None: + args.output = image.default_output_name(args.input) + image.save(args.output) + + print("Successfully created {} image.".format(args.chip)) + + +def read_mac(esp, args): + mac = esp.read_mac() + + def print_mac(label, mac): + print("%s: %s" % (label, ":".join(map(lambda x: "%02x" % x, mac)))) + + print_mac("MAC", mac) + + +def chip_id(esp, args): + try: + chipid = esp.chip_id() + print("Chip ID: 0x%08x" % chipid) + except NotSupportedError: + print("Warning: %s has no Chip ID. Reading MAC instead." % esp.CHIP_NAME) + read_mac(esp, args) + + +def erase_flash(esp, args): + if not args.force and esp.CHIP_NAME != "ESP8266" and not esp.secure_download_mode: + if esp.get_flash_encryption_enabled() or esp.get_secure_boot_enabled(): + raise FatalError( + "Active security features detected, " + "erasing flash is disabled as a safety measure. " + "Use --force to override, " + "please use with caution, otherwise it may brick your device!" + ) + print("Erasing flash (this may take a while)...") + t = time.time() + esp.erase_flash() + print("Chip erase completed successfully in %.1fs" % (time.time() - t)) + + +def erase_region(esp, args): + if not args.force and esp.CHIP_NAME != "ESP8266" and not esp.secure_download_mode: + if esp.get_flash_encryption_enabled() or esp.get_secure_boot_enabled(): + raise FatalError( + "Active security features detected, " + "erasing flash is disabled as a safety measure. " + "Use --force to override, " + "please use with caution, otherwise it may brick your device!" + ) + print("Erasing region (may be slow depending on size)...") + t = time.time() + esp.erase_region(args.address, args.size) + print("Erase completed successfully in %.1f seconds." % (time.time() - t)) + + +def run(esp, args): + esp.run() + + +def flash_id(esp, args): + flash_id = esp.flash_id() + print("Manufacturer: %02x" % (flash_id & 0xFF)) + flid_lowbyte = (flash_id >> 16) & 0xFF + print("Device: %02x%02x" % ((flash_id >> 8) & 0xFF, flid_lowbyte)) + print( + "Detected flash size: %s" % (DETECTED_FLASH_SIZES.get(flid_lowbyte, "Unknown")) + ) + flash_type = esp.flash_type() + flash_type_dict = {0: "quad (4 data lines)", 1: "octal (8 data lines)"} + flash_type_str = flash_type_dict.get(flash_type) + if flash_type_str: + print(f"Flash type set in eFuse: {flash_type_str}") + + +def read_flash(esp, args): + if args.no_progress: + flash_progress = None + else: + + def flash_progress(progress, length): + msg = "%d (%d %%)" % (progress, progress * 100.0 / length) + padding = "\b" * len(msg) + if progress == length: + padding = "\n" + sys.stdout.write(msg + padding) + sys.stdout.flush() + + t = time.time() + data = esp.read_flash(args.address, args.size, flash_progress) + t = time.time() - t + speed_msg = " ({:.1f} kbit/s)".format(len(data) / t * 8 / 1000) if t > 0.0 else "" + print_overwrite( + "Read {:d} bytes at {:#010x} in {:.1f} seconds{}...".format( + len(data), args.address, t, speed_msg + ), + last_line=True, + ) + with open(args.filename, "wb") as f: + f.write(data) + + +def verify_flash(esp, args): + differences = False + + for address, argfile in args.addr_filename: + image = pad_to(argfile.read(), 4) + argfile.seek(0) # rewind in case we need it again + + image = _update_image_flash_params(esp, address, args, image) + + image_size = len(image) + print( + "Verifying 0x%x (%d) bytes @ 0x%08x in flash against %s..." + % (image_size, image_size, address, argfile.name) + ) + # Try digest first, only read if there are differences. + digest = esp.flash_md5sum(address, image_size) + expected_digest = hashlib.md5(image).hexdigest() + if digest == expected_digest: + print("-- verify OK (digest matched)") + continue + else: + differences = True + if getattr(args, "diff", "no") != "yes": + print("-- verify FAILED (digest mismatch)") + continue + + flash = esp.read_flash(address, image_size) + assert flash != image + diff = [i for i in range(image_size) if flash[i] != image[i]] + print( + "-- verify FAILED: %d differences, first @ 0x%08x" + % (len(diff), address + diff[0]) + ) + for d in diff: + flash_byte = flash[d] + image_byte = image[d] + print(" %08x %02x %02x" % (address + d, flash_byte, image_byte)) + if differences: + raise FatalError("Verify failed.") + + +def read_flash_status(esp, args): + print("Status value: 0x%04x" % esp.read_status(args.bytes)) + + +def write_flash_status(esp, args): + fmt = "0x%%0%dx" % (args.bytes * 2) + args.value = args.value & ((1 << (args.bytes * 8)) - 1) + print(("Initial flash status: " + fmt) % esp.read_status(args.bytes)) + print(("Setting flash status: " + fmt) % args.value) + esp.write_status(args.value, args.bytes, args.non_volatile) + print(("After flash status: " + fmt) % esp.read_status(args.bytes)) + + +def get_security_info(esp, args): + si = esp.get_security_info() + # TODO: better display + print("Flags: {:#010x} ({})".format(si["flags"], bin(si["flags"]))) + print("Flash_Crypt_Cnt: {:#x}".format(si["flash_crypt_cnt"])) + print("Key_Purposes: {}".format(si["key_purposes"])) + if si["chip_id"] is not None and si["api_version"] is not None: + print("Chip_ID: {}".format(si["chip_id"])) + print("Api_Version: {}".format(si["api_version"])) + + +def merge_bin(args): + try: + chip_class = CHIP_DEFS[args.chip] + except KeyError: + msg = ( + "Please specify the chip argument" + if args.chip == "auto" + else "Invalid chip choice: '{}'".format(args.chip) + ) + msg = msg + " (choose from {})".format(", ".join(CHIP_LIST)) + raise FatalError(msg) + + # sort the files by offset. + # The AddrFilenamePairAction has already checked for overlap + input_files = sorted(args.addr_filename, key=lambda x: x[0]) + if not input_files: + raise FatalError("No input files specified") + first_addr = input_files[0][0] + if first_addr < args.target_offset: + raise FatalError( + "Output file target offset is 0x%x. Input file offset 0x%x is before this." + % (args.target_offset, first_addr) + ) + + if args.format != "raw": + raise FatalError( + "This version of esptool only supports the 'raw' output format" + ) + + with open(args.output, "wb") as of: + + def pad_to(flash_offs): + # account for output file offset if there is any + of.write(b"\xFF" * (flash_offs - args.target_offset - of.tell())) + + for addr, argfile in input_files: + pad_to(addr) + image = argfile.read() + image = _update_image_flash_params(chip_class, addr, args, image) + of.write(image) + if args.fill_flash_size: + pad_to(flash_size_bytes(args.fill_flash_size)) + print( + "Wrote 0x%x bytes to file %s, ready to flash to offset 0x%x" + % (of.tell(), args.output, args.target_offset) + ) + + +def version(args): + from . import __version__ + + print(__version__) diff --git a/installer/bin/esptool/esptool/config.py b/installer/bin/esptool/esptool/config.py new file mode 100644 index 0000000..5566bec --- /dev/null +++ b/installer/bin/esptool/esptool/config.py @@ -0,0 +1,92 @@ +# SPDX-FileCopyrightText: 2014-2023 Espressif Systems (Shanghai) CO LTD, +# other contributors as noted. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import configparser +import os + +CONFIG_OPTIONS = [ + "timeout", + "chip_erase_timeout", + "max_timeout", + "sync_timeout", + "md5_timeout_per_mb", + "erase_region_timeout_per_mb", + "erase_write_timeout_per_mb", + "mem_end_rom_timeout", + "serial_write_timeout", + "connect_attempts", + "write_block_attempts", + "reset_delay", + "custom_reset_sequence", +] + + +def _validate_config_file(file_path, verbose=False): + if not os.path.exists(file_path): + return False + + cfg = configparser.RawConfigParser() + try: + cfg.read(file_path, encoding="UTF-8") + # Only consider it a valid config file if it contains [esptool] section + if cfg.has_section("esptool"): + if verbose: + unknown_opts = list(set(cfg.options("esptool")) - set(CONFIG_OPTIONS)) + unknown_opts.sort() + no_of_unknown_opts = len(unknown_opts) + if no_of_unknown_opts > 0: + suffix = "s" if no_of_unknown_opts > 1 else "" + print( + "Ignoring unknown config file option{}: {}".format( + suffix, ", ".join(unknown_opts) + ) + ) + return True + except (UnicodeDecodeError, configparser.Error) as e: + if verbose: + print(f"Ignoring invalid config file {file_path}: {e}") + return False + + +def _find_config_file(dir_path, verbose=False): + for candidate in ("esptool.cfg", "setup.cfg", "tox.ini"): + cfg_path = os.path.join(dir_path, candidate) + if _validate_config_file(cfg_path, verbose): + return cfg_path + return None + + +def load_config_file(verbose=False): + set_with_env_var = False + env_var_path = os.environ.get("ESPTOOL_CFGFILE") + if env_var_path is not None and _validate_config_file(env_var_path): + cfg_file_path = env_var_path + set_with_env_var = True + else: + home_dir = os.path.expanduser("~") + os_config_dir = ( + f"{home_dir}/.config/esptool" + if os.name == "posix" + else f"{home_dir}/AppData/Local/esptool/" + ) + # Search priority: 1) current dir, 2) OS specific config dir, 3) home dir + for dir_path in (os.getcwd(), os_config_dir, home_dir): + cfg_file_path = _find_config_file(dir_path, verbose) + if cfg_file_path: + break + + cfg = configparser.ConfigParser() + cfg["esptool"] = {} # Create an empty esptool config for when no file is found + + if cfg_file_path is not None: + # If config file is found and validated, read and parse it + cfg.read(cfg_file_path) + if verbose: + msg = " (set with ESPTOOL_CFGFILE)" if set_with_env_var else "" + print( + f"Loaded custom configuration from " + f"{os.path.abspath(cfg_file_path)}{msg}" + ) + return cfg, cfg_file_path diff --git a/installer/bin/esptool/esptool/loader.py b/installer/bin/esptool/esptool/loader.py new file mode 100644 index 0000000..eb64023 --- /dev/null +++ b/installer/bin/esptool/esptool/loader.py @@ -0,0 +1,1608 @@ +# SPDX-FileCopyrightText: 2014-2023 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import base64 +import hashlib +import itertools +import json +import os +import re +import string +import struct +import sys +import time + +from .config import load_config_file +from .reset import ( + ClassicReset, + CustomReset, + DEFAULT_RESET_DELAY, + HardReset, + USBJTAGSerialReset, + UnixTightReset, +) +from .util import FatalError, NotImplementedInROMError, UnsupportedCommandError +from .util import byte, hexify, mask_to_shift, pad_to, strip_chip_name + +try: + import serial +except ImportError: + print( + "Pyserial is not installed for %s. " + "Check the README for installation instructions." % (sys.executable) + ) + raise + +# check 'serial' is 'pyserial' and not 'serial' +# ref. https://github.com/espressif/esptool/issues/269 +try: + if "serialization" in serial.__doc__ and "deserialization" in serial.__doc__: + raise ImportError( + "esptool.py depends on pyserial, but there is a conflict with a currently " + "installed package named 'serial'.\n" + "You may work around this by 'pip uninstall serial; pip install pyserial' " + "but this may break other installed Python software " + "that depends on 'serial'.\n" + "There is no good fix for this right now, " + "apart from configuring virtualenvs. " + "See https://github.com/espressif/esptool/issues/269#issuecomment-385298196" + " for discussion of the underlying issue(s)." + ) +except TypeError: + pass # __doc__ returns None for pyserial + +try: + import serial.tools.list_ports as list_ports +except ImportError: + print( + "The installed version (%s) of pyserial appears to be too old for esptool.py " + "(Python interpreter %s). Check the README for installation instructions." + % (sys.VERSION, sys.executable) + ) + raise +except Exception: + if sys.platform == "darwin": + # swallow the exception, this is a known issue in pyserial+macOS Big Sur preview + # ref https://github.com/espressif/esptool/issues/540 + list_ports = None + else: + raise + + +cfg, _ = load_config_file() +cfg = cfg["esptool"] + +# Timeout for most flash operations +DEFAULT_TIMEOUT = cfg.getfloat("timeout", 3) +# Timeout for full chip erase +CHIP_ERASE_TIMEOUT = cfg.getfloat("chip_erase_timeout", 120) +# Longest any command can run +MAX_TIMEOUT = cfg.getfloat("max_timeout", CHIP_ERASE_TIMEOUT * 2) +# Timeout for syncing with bootloader +SYNC_TIMEOUT = cfg.getfloat("sync_timeout", 0.1) +# Timeout (per megabyte) for calculating md5sum +MD5_TIMEOUT_PER_MB = cfg.getfloat("md5_timeout_per_mb", 8) +# Timeout (per megabyte) for erasing a region +ERASE_REGION_TIMEOUT_PER_MB = cfg.getfloat("erase_region_timeout_per_mb", 30) +# Timeout (per megabyte) for erasing and writing data +ERASE_WRITE_TIMEOUT_PER_MB = cfg.getfloat("erase_write_timeout_per_mb", 40) +# Short timeout for ESP_MEM_END, as it may never respond +MEM_END_ROM_TIMEOUT = cfg.getfloat("mem_end_rom_timeout", 0.2) +# Timeout for serial port write +DEFAULT_SERIAL_WRITE_TIMEOUT = cfg.getfloat("serial_write_timeout", 10) +# Default number of times to try connection +DEFAULT_CONNECT_ATTEMPTS = cfg.getint("connect_attempts", 7) +# Number of times to try writing a data block +WRITE_BLOCK_ATTEMPTS = cfg.getint("write_block_attempts", 3) + +STUBS_DIR = os.path.join(os.path.dirname(__file__), "./targets/stub_flasher/") + + +def get_stub_json_path(chip_name): + chip_name = strip_chip_name(chip_name) + chip_name = chip_name.replace("esp", "") + return STUBS_DIR + "stub_flasher_" + chip_name + ".json" + + +def timeout_per_mb(seconds_per_mb, size_bytes): + """Scales timeouts which are size-specific""" + result = seconds_per_mb * (size_bytes / 1e6) + if result < DEFAULT_TIMEOUT: + return DEFAULT_TIMEOUT + return result + + +def check_supported_function(func, check_func): + """ + Decorator implementation that wraps a check around an ESPLoader + bootloader function to check if it's supported. + + This is used to capture the multidimensional differences in + functionality between the ESP8266 & ESP32 (and later chips) ROM loaders, and the + software stub that runs on these. Not possible to do this cleanly + via inheritance alone. + """ + + def inner(*args, **kwargs): + obj = args[0] + if check_func(obj): + return func(*args, **kwargs) + else: + raise NotImplementedInROMError(obj, func) + + return inner + + +def stub_function_only(func): + """Attribute for a function only supported in the software stub loader""" + return check_supported_function(func, lambda o: o.IS_STUB) + + +def stub_and_esp32_function_only(func): + """Attribute for a function only supported by stubs or ESP32 and later chips ROM""" + return check_supported_function( + func, lambda o: o.IS_STUB or o.CHIP_NAME not in ["ESP8266"] + ) + + +def esp32s3_or_newer_function_only(func): + """Attribute for a function only supported by ESP32S3 and later chips ROM""" + return check_supported_function( + func, lambda o: o.CHIP_NAME not in ["ESP8266", "ESP32", "ESP32-S2"] + ) + + +class StubFlasher: + def __init__(self, json_path): + with open(json_path) as json_file: + stub = json.load(json_file) + + self.text = base64.b64decode(stub["text"]) + self.text_start = stub["text_start"] + self.entry = stub["entry"] + + try: + self.data = base64.b64decode(stub["data"]) + self.data_start = stub["data_start"] + except KeyError: + self.data = None + self.data_start = None + + +class ESPLoader(object): + """Base class providing access to ESP ROM & software stub bootloaders. + Subclasses provide ESP8266 & ESP32 Family specific functionality. + + Don't instantiate this base class directly, either instantiate a subclass or + call cmds.detect_chip() which will interrogate the chip and return the + appropriate subclass instance. + + """ + + CHIP_NAME = "Espressif device" + IS_STUB = False + + FPGA_SLOW_BOOT = False + + DEFAULT_PORT = "/dev/ttyUSB0" + + USES_RFC2217 = False + + # Commands supported by ESP8266 ROM bootloader + ESP_FLASH_BEGIN = 0x02 + ESP_FLASH_DATA = 0x03 + ESP_FLASH_END = 0x04 + ESP_MEM_BEGIN = 0x05 + ESP_MEM_END = 0x06 + ESP_MEM_DATA = 0x07 + ESP_SYNC = 0x08 + ESP_WRITE_REG = 0x09 + ESP_READ_REG = 0x0A + + # Some comands supported by ESP32 and later chips ROM bootloader (or -8266 w/ stub) + ESP_SPI_SET_PARAMS = 0x0B + ESP_SPI_ATTACH = 0x0D + ESP_READ_FLASH_SLOW = 0x0E # ROM only, much slower than the stub flash read + ESP_CHANGE_BAUDRATE = 0x0F + ESP_FLASH_DEFL_BEGIN = 0x10 + ESP_FLASH_DEFL_DATA = 0x11 + ESP_FLASH_DEFL_END = 0x12 + ESP_SPI_FLASH_MD5 = 0x13 + + # Commands supported by ESP32-S2 and later chips ROM bootloader only + ESP_GET_SECURITY_INFO = 0x14 + + # Some commands supported by stub only + ESP_ERASE_FLASH = 0xD0 + ESP_ERASE_REGION = 0xD1 + ESP_READ_FLASH = 0xD2 + ESP_RUN_USER_CODE = 0xD3 + + # Flash encryption encrypted data command + ESP_FLASH_ENCRYPT_DATA = 0xD4 + + # Response code(s) sent by ROM + ROM_INVALID_RECV_MSG = 0x05 # response if an invalid message is received + + # Maximum block sized for RAM and Flash writes, respectively. + ESP_RAM_BLOCK = 0x1800 + + FLASH_WRITE_SIZE = 0x400 + + # Default baudrate. The ROM auto-bauds, so we can use more or less whatever we want. + ESP_ROM_BAUD = 115200 + + # First byte of the application image + ESP_IMAGE_MAGIC = 0xE9 + + # Initial state for the checksum routine + ESP_CHECKSUM_MAGIC = 0xEF + + # Flash sector size, minimum unit of erase. + FLASH_SECTOR_SIZE = 0x1000 + + UART_DATE_REG_ADDR = 0x60000078 + + # This ROM address has a different value on each chip model + CHIP_DETECT_MAGIC_REG_ADDR = 0x40001000 + + UART_CLKDIV_MASK = 0xFFFFF + + # Memory addresses + IROM_MAP_START = 0x40200000 + IROM_MAP_END = 0x40300000 + + # The number of bytes in the UART response that signify command status + STATUS_BYTES_LENGTH = 2 + + # Bootloader flashing offset + BOOTLOADER_FLASH_OFFSET = 0x0 + + # ROM supports an encrypted flashing mode + SUPPORTS_ENCRYPTED_FLASH = False + + # Response to ESP_SYNC might indicate that flasher stub is running + # instead of the ROM bootloader + sync_stub_detected = False + + # Device PIDs + USB_JTAG_SERIAL_PID = 0x1001 + + # Chip IDs that are no longer supported by esptool + UNSUPPORTED_CHIPS = {6: "ESP32-S3(beta 3)"} + + def __init__(self, port=DEFAULT_PORT, baud=ESP_ROM_BAUD, trace_enabled=False): + """Base constructor for ESPLoader bootloader interaction + + Don't call this constructor, either instantiate a specific + ROM class directly, or use cmds.detect_chip(). + + This base class has all of the instance methods for bootloader + functionality supported across various chips & stub + loaders. Subclasses replace the functions they don't support + with ones which throw NotImplementedInROMError(). + + """ + # True if esptool detects the ROM is in Secure Download Mode + self.secure_download_mode = False + # True if esptool detects conditions which require the stub to be disabled + self.stub_is_disabled = False + + # Device-and-runtime-specific cache + self.cache = { + "flash_id": None, + "chip_id": None, + "uart_no": None, + } + + if isinstance(port, str): + try: + self._port = serial.serial_for_url(port) + except serial.serialutil.SerialException: + raise FatalError(f"Could not open {port}, the port doesn't exist") + else: + self._port = port + self._slip_reader = slip_reader(self._port, self.trace) + # setting baud rate in a separate step is a workaround for + # CH341 driver on some Linux versions (this opens at 9600 then + # sets), shouldn't matter for other platforms/drivers. See + # https://github.com/espressif/esptool/issues/44#issuecomment-107094446 + self._set_port_baudrate(baud) + self._trace_enabled = trace_enabled + # set write timeout, to prevent esptool blocked at write forever. + try: + self._port.write_timeout = DEFAULT_SERIAL_WRITE_TIMEOUT + except NotImplementedError: + # no write timeout for RFC2217 ports + # need to set the property back to None or it will continue to fail + self._port.write_timeout = None + + @property + def serial_port(self): + return self._port.port + + def _set_port_baudrate(self, baud): + try: + self._port.baudrate = baud + except IOError: + raise FatalError( + "Failed to set baud rate %d. The driver may not support this rate." + % baud + ) + + def read(self): + """Read a SLIP packet from the serial port""" + return next(self._slip_reader) + + def write(self, packet): + """Write bytes to the serial port while performing SLIP escaping""" + buf = ( + b"\xc0" + + (packet.replace(b"\xdb", b"\xdb\xdd").replace(b"\xc0", b"\xdb\xdc")) + + b"\xc0" + ) + self.trace("Write %d bytes: %s", len(buf), HexFormatter(buf)) + self._port.write(buf) + + def trace(self, message, *format_args): + if self._trace_enabled: + now = time.time() + try: + delta = now - self._last_trace + except AttributeError: + delta = 0.0 + self._last_trace = now + prefix = "TRACE +%.3f " % delta + print(prefix + (message % format_args)) + + @staticmethod + def checksum(data, state=ESP_CHECKSUM_MAGIC): + """Calculate checksum of a blob, as it is defined by the ROM""" + for b in data: + state ^= b + + return state + + def command( + self, + op=None, + data=b"", + chk=0, + wait_response=True, + timeout=DEFAULT_TIMEOUT, + ): + """Send a request and read the response""" + saved_timeout = self._port.timeout + new_timeout = min(timeout, MAX_TIMEOUT) + if new_timeout != saved_timeout: + self._port.timeout = new_timeout + + try: + if op is not None: + self.trace( + "command op=0x%02x data len=%s wait_response=%d " + "timeout=%.3f data=%s", + op, + len(data), + 1 if wait_response else 0, + timeout, + HexFormatter(data), + ) + pkt = struct.pack(b" self.STATUS_BYTES_LENGTH: + return data[: -self.STATUS_BYTES_LENGTH] + else: + # otherwise, just return the 'val' field which comes from the reply header + # (this is used by read_reg) + return val + + def flush_input(self): + self._port.flushInput() + self._slip_reader = slip_reader(self._port, self.trace) + + def sync(self): + val, _ = self.command( + self.ESP_SYNC, b"\x07\x07\x12\x20" + 32 * b"\x55", timeout=SYNC_TIMEOUT + ) + + # ROM bootloaders send some non-zero "val" response. The flasher stub sends 0. + # If we receive 0 then it probably indicates that the chip wasn't or couldn't be + # reseted properly and esptool is talking to the flasher stub. + self.sync_stub_detected = val == 0 + + for _ in range(7): + val, _ = self.command() + self.sync_stub_detected &= val == 0 + + def _get_pid(self): + if list_ports is None: + print( + "\nListing all serial ports is currently not available. " + "Can't get device PID." + ) + return + active_port = self._port.port + + # Pyserial only identifies regular ports, URL handlers are not supported + if not active_port.lower().startswith(("com", "/dev/")): + print( + "\nDevice PID identification is only supported on " + "COM and /dev/ serial ports." + ) + return + # Return the real path if the active port is a symlink + if active_port.startswith("/dev/") and os.path.islink(active_port): + active_port = os.path.realpath(active_port) + + # The "cu" (call-up) device has to be used for outgoing communication on MacOS + if sys.platform == "darwin" and "tty" in active_port: + active_port = [active_port, active_port.replace("tty", "cu")] + ports = list_ports.comports() + for p in ports: + if p.device in active_port: + return p.pid + print( + "\nFailed to get PID of a device on {}, " + "using standard reset sequence.".format(active_port) + ) + + def _connect_attempt(self, reset_strategy, mode="default_reset"): + """A single connection attempt""" + last_error = None + boot_log_detected = False + download_mode = False + + # If we're doing no_sync, we're likely communicating as a pass through + # with an intermediate device to the ESP32 + if mode == "no_reset_no_sync": + return last_error + + if mode != "no_reset": + if not self.USES_RFC2217: # Might block on rfc2217 ports + # Empty serial buffer to isolate boot log + self._port.reset_input_buffer() + + reset_strategy() # Reset the chip to bootloader (download mode) + + # Detect the ROM boot log and check actual boot mode (ESP32 and later only) + waiting = self._port.inWaiting() + read_bytes = self._port.read(waiting) + data = re.search( + b"boot:(0x[0-9a-fA-F]+)(.*waiting for download)?", read_bytes, re.DOTALL + ) + if data is not None: + boot_log_detected = True + boot_mode = data.group(1) + download_mode = data.group(2) is not None + + for _ in range(5): + try: + self.flush_input() + self._port.flushOutput() + self.sync() + return None + except FatalError as e: + print(".", end="") + sys.stdout.flush() + time.sleep(0.05) + last_error = e + + if boot_log_detected: + last_error = FatalError( + "Wrong boot mode detected ({})! " + "The chip needs to be in download mode.".format( + boot_mode.decode("utf-8") + ) + ) + if download_mode: + last_error = FatalError( + "Download mode successfully detected, but getting no sync reply: " + "The serial TX path seems to be down." + ) + return last_error + + def get_memory_region(self, name): + """ + Returns a tuple of (start, end) for the memory map entry with the given name, + or None if it doesn't exist + """ + try: + return [(start, end) for (start, end, n) in self.MEMORY_MAP if n == name][0] + except IndexError: + return None + + def _construct_reset_strategy_sequence(self, mode): + """ + Constructs a sequence of reset strategies based on the OS, + used ESP chip, external settings, and environment variables. + Returns a tuple of one or more reset strategies to be tried sequentially. + """ + cfg_custom_reset_sequence = cfg.get("custom_reset_sequence") + if cfg_custom_reset_sequence is not None: + return (CustomReset(self._port, cfg_custom_reset_sequence),) + + cfg_reset_delay = cfg.getfloat("reset_delay") + if cfg_reset_delay is not None: + delay = extra_delay = cfg_reset_delay + else: + delay = DEFAULT_RESET_DELAY + extra_delay = DEFAULT_RESET_DELAY + 0.5 + + # This FPGA delay is for Espressif internal use + if ( + self.FPGA_SLOW_BOOT + and os.environ.get("ESPTOOL_ENV_FPGA", "").strip() == "1" + ): + delay = extra_delay = 7 + + # USB-JTAG/Serial mode + if mode == "usb_reset" or self._get_pid() == self.USB_JTAG_SERIAL_PID: + return (USBJTAGSerialReset(self._port),) + + # USB-to-Serial bridge + if os.name != "nt" and not self._port.name.startswith("rfc2217:"): + return ( + UnixTightReset(self._port, delay), + UnixTightReset(self._port, extra_delay), + ClassicReset(self._port, delay), + ClassicReset(self._port, extra_delay), + ) + + return ( + ClassicReset(self._port, delay), + ClassicReset(self._port, extra_delay), + ) + + def connect( + self, + mode="default_reset", + attempts=DEFAULT_CONNECT_ATTEMPTS, + detecting=False, + warnings=True, + ): + """Try connecting repeatedly until successful, or giving up""" + if warnings and mode in ["no_reset", "no_reset_no_sync"]: + print( + 'WARNING: Pre-connection option "{}" was selected.'.format(mode), + "Connection may fail if the chip is not in bootloader " + "or flasher stub mode.", + ) + print("Connecting...", end="") + sys.stdout.flush() + last_error = None + + reset_sequence = self._construct_reset_strategy_sequence(mode) + try: + for _, reset_strategy in zip( + range(attempts) if attempts > 0 else itertools.count(), + itertools.cycle(reset_sequence), + ): + last_error = self._connect_attempt(reset_strategy, mode) + if last_error is None: + break + finally: + print("") # end 'Connecting...' line + + if last_error is not None: + raise FatalError( + "Failed to connect to {}: {}" + "\nFor troubleshooting steps visit: " + "https://docs.espressif.com/projects/esptool/en/latest/troubleshooting.html".format( # noqa E501 + self.CHIP_NAME, last_error + ) + ) + + if not detecting: + try: + from .targets import ROM_LIST + + # check the date code registers match what we expect to see + chip_magic_value = self.read_reg(ESPLoader.CHIP_DETECT_MAGIC_REG_ADDR) + if chip_magic_value not in self.CHIP_DETECT_MAGIC_VALUE: + actually = None + for cls in ROM_LIST: + if chip_magic_value in cls.CHIP_DETECT_MAGIC_VALUE: + actually = cls + break + if warnings and actually is None: + print( + "WARNING: This chip doesn't appear to be a %s " + "(chip magic value 0x%08x). " + "Probably it is unsupported by this version of esptool." + % (self.CHIP_NAME, chip_magic_value) + ) + else: + raise FatalError( + "This chip is %s not %s. Wrong --chip argument?" + % (actually.CHIP_NAME, self.CHIP_NAME) + ) + except UnsupportedCommandError: + self.secure_download_mode = True + + try: + self.check_chip_id() + except UnsupportedCommandError: + # Fix for ROM not responding in SDM, reconnect and try again + if self.secure_download_mode: + self._connect_attempt(mode, reset_sequence[0]) + self.check_chip_id() + else: + raise + self._post_connect() + + def _post_connect(self): + """ + Additional initialization hook, may be overridden by the chip-specific class. + Gets called after connect, and after auto-detection. + """ + pass + + def read_reg(self, addr, timeout=DEFAULT_TIMEOUT): + """Read memory address in target""" + # we don't call check_command here because read_reg() function is called + # when detecting chip type, and the way we check for success + # (STATUS_BYTES_LENGTH) is different for different chip types (!) + val, data = self.command( + self.ESP_READ_REG, struct.pack(" 0: + # add a dummy write to a date register as an excuse to have a delay + command += struct.pack( + " start: + raise FatalError( + "Software loader is resident at 0x%08x-0x%08x. " + "Can't load binary at overlapping address range 0x%08x-0x%08x. " + "Either change binary loading address, or use the --no-stub " + "option to disable the software loader." + % (start, end, load_start, load_end) + ) + + return self.check_command( + "enter RAM download mode", + self.ESP_MEM_BEGIN, + struct.pack(" length: + raise FatalError("Read more than expected") + + digest_frame = self.read() + if len(digest_frame) != 16: + raise FatalError("Expected digest, got: %s" % hexify(digest_frame)) + expected_digest = hexify(digest_frame).upper() + digest = hashlib.md5(data).hexdigest().upper() + if digest != expected_digest: + raise FatalError( + "Digest mismatch: expected %s, got %s" % (expected_digest, digest) + ) + return data + + def flash_spi_attach(self, hspi_arg): + """Send SPI attach command to enable the SPI flash pins + + ESP8266 ROM does this when you send flash_begin, ESP32 ROM + has it as a SPI command. + """ + # last 3 bytes in ESP_SPI_ATTACH argument are reserved values + arg = struct.pack(" 0: + self.write_reg(SPI_MOSI_DLEN_REG, mosi_bits - 1) + if miso_bits > 0: + self.write_reg(SPI_MISO_DLEN_REG, miso_bits - 1) + flags = 0 + if dummy_len > 0: + flags |= dummy_len - 1 + if addr_len > 0: + flags |= (addr_len - 1) << SPI_USR_ADDR_LEN_SHIFT + if flags: + self.write_reg(SPI_USR1_REG, flags) + + else: + + def set_data_lengths(mosi_bits, miso_bits): + SPI_DATA_LEN_REG = SPI_USR1_REG + SPI_MOSI_BITLEN_S = 17 + SPI_MISO_BITLEN_S = 8 + mosi_mask = 0 if (mosi_bits == 0) else (mosi_bits - 1) + miso_mask = 0 if (miso_bits == 0) else (miso_bits - 1) + flags = (miso_mask << SPI_MISO_BITLEN_S) | ( + mosi_mask << SPI_MOSI_BITLEN_S + ) + if dummy_len > 0: + flags |= dummy_len - 1 + if addr_len > 0: + flags |= (addr_len - 1) << SPI_USR_ADDR_LEN_SHIFT + self.write_reg(SPI_DATA_LEN_REG, flags) + + # SPI peripheral "command" bitmasks for SPI_CMD_REG + SPI_CMD_USR = 1 << 18 + + # shift values + SPI_USR2_COMMAND_LEN_SHIFT = 28 + SPI_USR_ADDR_LEN_SHIFT = 26 + + if read_bits > 32: + raise FatalError( + "Reading more than 32 bits back from a SPI flash " + "operation is unsupported" + ) + if len(data) > 64: + raise FatalError( + "Writing more than 64 bytes of data with one SPI " + "command is unsupported" + ) + + data_bits = len(data) * 8 + old_spi_usr = self.read_reg(SPI_USR_REG) + old_spi_usr2 = self.read_reg(SPI_USR2_REG) + flags = SPI_USR_COMMAND + if read_bits > 0: + flags |= SPI_USR_MISO + if data_bits > 0: + flags |= SPI_USR_MOSI + if addr_len > 0: + flags |= SPI_USR_ADDR + if dummy_len > 0: + flags |= SPI_USR_DUMMY + set_data_lengths(data_bits, read_bits) + self.write_reg(SPI_USR_REG, flags) + self.write_reg( + SPI_USR2_REG, (7 << SPI_USR2_COMMAND_LEN_SHIFT) | spiflash_command + ) + if addr and addr_len > 0: + self.write_reg(SPI_ADDR_REG, addr) + if data_bits == 0: + self.write_reg(SPI_W0_REG, 0) # clear data register before we read it + else: + data = pad_to(data, 4, b"\00") # pad to 32-bit multiple + words = struct.unpack("I" * (len(data) // 4), data) + next_reg = SPI_W0_REG + for word in words: + self.write_reg(next_reg, word) + next_reg += 4 + self.write_reg(SPI_CMD_REG, SPI_CMD_USR) + + def wait_done(): + for _ in range(10): + if (self.read_reg(SPI_CMD_REG) & SPI_CMD_USR) == 0: + return + raise FatalError("SPI command did not complete in time") + + wait_done() + + status = self.read_reg(SPI_W0_REG) + # restore some SPI controller registers + self.write_reg(SPI_USR_REG, old_spi_usr) + self.write_reg(SPI_USR2_REG, old_spi_usr2) + return status + + def read_spiflash_sfdp(self, addr, read_bits): + CMD_RDSFDP = 0x5A + return self.run_spiflash_command( + CMD_RDSFDP, read_bits=read_bits, addr=addr, addr_len=24, dummy_len=8 + ) + + def read_status(self, num_bytes=2): + """Read up to 24 bits (num_bytes) of SPI flash status register contents + via RDSR, RDSR2, RDSR3 commands + + Not all SPI flash supports all three commands. The upper 1 or 2 + bytes may be 0xFF. + """ + SPIFLASH_RDSR = 0x05 + SPIFLASH_RDSR2 = 0x35 + SPIFLASH_RDSR3 = 0x15 + + status = 0 + shift = 0 + for cmd in [SPIFLASH_RDSR, SPIFLASH_RDSR2, SPIFLASH_RDSR3][0:num_bytes]: + status += self.run_spiflash_command(cmd, read_bits=8) << shift + shift += 8 + return status + + def write_status(self, new_status, num_bytes=2, set_non_volatile=False): + """Write up to 24 bits (num_bytes) of new status register + + num_bytes can be 1, 2 or 3. + + Not all flash supports the additional commands to write the + second and third byte of the status register. When writing 2 + bytes, esptool also sends a 16-byte WRSR command (as some + flash types use this instead of WRSR2.) + + If the set_non_volatile flag is set, non-volatile bits will + be set as well as volatile ones (WREN used instead of WEVSR). + + """ + SPIFLASH_WRSR = 0x01 + SPIFLASH_WRSR2 = 0x31 + SPIFLASH_WRSR3 = 0x11 + SPIFLASH_WEVSR = 0x50 + SPIFLASH_WREN = 0x06 + SPIFLASH_WRDI = 0x04 + + enable_cmd = SPIFLASH_WREN if set_non_volatile else SPIFLASH_WEVSR + + # try using a 16-bit WRSR (not supported by all chips) + # this may be redundant, but shouldn't hurt + if num_bytes == 2: + self.run_spiflash_command(enable_cmd) + self.run_spiflash_command(SPIFLASH_WRSR, struct.pack(">= 8 + + self.run_spiflash_command(SPIFLASH_WRDI) + + def get_crystal_freq(self): + """ + Figure out the crystal frequency from the UART clock divider + + Returns a normalized value in integer MHz (only values 40 or 26 are supported) + """ + # The logic here is: + # - We know that our baud rate and the ESP UART baud rate are roughly the same, + # or we couldn't communicate + # - We can read the UART clock divider register to know how the ESP derives this + # from the APB bus frequency + # - Multiplying these two together gives us the bus frequency which is either + # the crystal frequency (ESP32) or double the crystal frequency (ESP8266). + # See the self.XTAL_CLK_DIVIDER parameter for this factor. + uart_div = self.read_reg(self.UART_CLKDIV_REG) & self.UART_CLKDIV_MASK + est_xtal = (self._port.baudrate * uart_div) / 1e6 / self.XTAL_CLK_DIVIDER + norm_xtal = 40 if est_xtal > 33 else 26 + if abs(norm_xtal - est_xtal) > 1: + print( + "WARNING: Detected crystal freq %.2fMHz is quite different to " + "normalized freq %dMHz. Unsupported crystal in use?" + % (est_xtal, norm_xtal) + ) + return norm_xtal + + def hard_reset(self): + print("Hard resetting via RTS pin...") + HardReset(self._port)() + + def soft_reset(self, stay_in_bootloader): + if not self.IS_STUB: + if stay_in_bootloader: + return # ROM bootloader is already in bootloader! + else: + # 'run user code' is as close to a soft reset as we can do + self.flash_begin(0, 0) + self.flash_finish(False) + else: + if stay_in_bootloader: + # soft resetting from the stub loader + # will re-load the ROM bootloader + self.flash_begin(0, 0) + self.flash_finish(True) + elif self.CHIP_NAME != "ESP8266": + raise FatalError( + "Soft resetting is currently only supported on ESP8266" + ) + else: + # running user code from stub loader requires some hacks + # in the stub loader + self.command(self.ESP_RUN_USER_CODE, wait_response=False) + + def check_chip_id(self): + try: + chip_id = self.get_chip_id() + if chip_id != self.IMAGE_CHIP_ID: + print( + "WARNING: Chip ID {} ({}) doesn't match expected Chip ID {}. " + "esptool may not work correctly.".format( + chip_id, + self.UNSUPPORTED_CHIPS.get(chip_id, "Unknown"), + self.IMAGE_CHIP_ID, + ) + ) + # Try to flash anyways by disabling stub + self.stub_is_disabled = True + except NotImplementedInROMError: + pass + + +def slip_reader(port, trace_function): + """Generator to read SLIP packets from a serial port. + Yields one full SLIP packet at a time, raises exception on timeout or invalid data. + + Designed to avoid too many calls to serial.read(1), which can bog + down on slow systems. + """ + + def detect_panic_handler(input): + """ + Checks the input bytes for panic handler messages. + Raises a FatalError if Guru Meditation or Fatal Exception is found, as both + of these are used between different ROM versions. + Tries to also parse the error cause (e.g. IllegalInstruction). + """ + + guru_meditation = ( + rb"G?uru Meditation Error: (?:Core \d panic'ed \(([a-zA-Z ]*)\))?" + ) + fatal_exception = rb"F?atal exception \(\d+\): (?:([a-zA-Z ]*)?.*epc)?" + + # Search either for Guru Meditation or Fatal Exception + data = re.search( + rb"".join([rb"(?:", guru_meditation, rb"|", fatal_exception, rb")"]), + input, + re.DOTALL, + ) + if data is not None: + cause = [ + "({})".format(i.decode("utf-8")) + for i in [data.group(1), data.group(2)] + if i is not None + ] + cause = f" {cause[0]}" if len(cause) else "" + msg = f"Guru Meditation Error detected{cause}" + raise FatalError(msg) + + partial_packet = None + in_escape = False + successful_slip = False + while True: + waiting = port.inWaiting() + read_bytes = port.read(1 if waiting == 0 else waiting) + if read_bytes == b"": + if partial_packet is None: # fail due to no data + msg = ( + "Serial data stream stopped: Possible serial noise or corruption." + if successful_slip + else "No serial data received." + ) + else: # fail during packet transfer + msg = "Packet content transfer stopped (received {} bytes)".format( + len(partial_packet) + ) + trace_function(msg) + raise FatalError(msg) + trace_function("Read %d bytes: %s", len(read_bytes), HexFormatter(read_bytes)) + for b in read_bytes: + b = bytes([b]) + if partial_packet is None: # waiting for packet header + if b == b"\xc0": + partial_packet = b"" + else: + trace_function("Read invalid data: %s", HexFormatter(read_bytes)) + remaining_data = port.read(port.inWaiting()) + trace_function( + "Remaining data in serial buffer: %s", + HexFormatter(remaining_data), + ) + detect_panic_handler(read_bytes + remaining_data) + raise FatalError( + "Invalid head of packet (0x%s): " + "Possible serial noise or corruption." % hexify(b) + ) + elif in_escape: # part-way through escape sequence + in_escape = False + if b == b"\xdc": + partial_packet += b"\xc0" + elif b == b"\xdd": + partial_packet += b"\xdb" + else: + trace_function("Read invalid data: %s", HexFormatter(read_bytes)) + remaining_data = port.read(port.inWaiting()) + trace_function( + "Remaining data in serial buffer: %s", + HexFormatter(remaining_data), + ) + detect_panic_handler(read_bytes + remaining_data) + raise FatalError("Invalid SLIP escape (0xdb, 0x%s)" % (hexify(b))) + elif b == b"\xdb": # start of escape sequence + in_escape = True + elif b == b"\xc0": # end of packet + trace_function("Received full packet: %s", HexFormatter(partial_packet)) + yield partial_packet + partial_packet = None + successful_slip = True + else: # normal byte in packet + partial_packet += b + + +class HexFormatter(object): + """ + Wrapper class which takes binary data in its constructor + and returns a hex string as it's __str__ method. + + This is intended for "lazy formatting" of trace() output + in hex format. Avoids overhead (significant on slow computers) + of generating long hex strings even if tracing is disabled. + + Note that this doesn't save any overhead if passed as an + argument to "%", only when passed to trace() + + If auto_split is set (default), any long line (> 16 bytes) will be + printed as separately indented lines, with ASCII decoding at the end + of each line. + """ + + def __init__(self, binary_string, auto_split=True): + self._s = binary_string + self._auto_split = auto_split + + def __str__(self): + if self._auto_split and len(self._s) > 16: + result = "" + s = self._s + while len(s) > 0: + line = s[:16] + ascii_line = "".join( + c + if ( + c == " " + or (c in string.printable and c not in string.whitespace) + ) + else "." + for c in line.decode("ascii", "replace") + ) + s = s[16:] + result += "\n %-16s %-16s | %s" % ( + hexify(line[:8], False), + hexify(line[8:], False), + ascii_line, + ) + return result + else: + return hexify(self._s, False) diff --git a/installer/bin/esptool/esptool/reset.py b/installer/bin/esptool/esptool/reset.py new file mode 100644 index 0000000..39b9a15 --- /dev/null +++ b/installer/bin/esptool/esptool/reset.py @@ -0,0 +1,178 @@ +# SPDX-FileCopyrightText: 2014-2023 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import os +import struct +import time + +from .util import FatalError + +# Used for resetting into bootloader on Unix-like systems +if os.name != "nt": + import fcntl + import termios + + # Constants used for terminal status lines reading/setting. + # Taken from pySerial's backend for IO: + # https://github.com/pyserial/pyserial/blob/master/serial/serialposix.py + TIOCMSET = getattr(termios, "TIOCMSET", 0x5418) + TIOCMGET = getattr(termios, "TIOCMGET", 0x5415) + TIOCM_DTR = getattr(termios, "TIOCM_DTR", 0x002) + TIOCM_RTS = getattr(termios, "TIOCM_RTS", 0x004) + +DEFAULT_RESET_DELAY = 0.05 # default time to wait before releasing boot pin after reset + + +class ResetStrategy(object): + def __init__(self, port, reset_delay=DEFAULT_RESET_DELAY): + self.port = port + self.reset_delay = reset_delay + + def __call__(): + pass + + def _setDTR(self, state): + self.port.setDTR(state) + + def _setRTS(self, state): + self.port.setRTS(state) + # Work-around for adapters on Windows using the usbser.sys driver: + # generate a dummy change to DTR so that the set-control-line-state + # request is sent with the updated RTS state and the same DTR state + self.port.setDTR(self.port.dtr) + + def _setDTRandRTS(self, dtr=False, rts=False): + status = struct.unpack( + "I", fcntl.ioctl(self.port.fileno(), TIOCMGET, struct.pack("I", 0)) + )[0] + if dtr: + status |= TIOCM_DTR + else: + status &= ~TIOCM_DTR + if rts: + status |= TIOCM_RTS + else: + status &= ~TIOCM_RTS + fcntl.ioctl(self.port.fileno(), TIOCMSET, struct.pack("I", status)) + + +class ClassicReset(ResetStrategy): + """ + Classic reset sequence, sets DTR and RTS lines sequentially. + """ + + def __call__(self): + self._setDTR(False) # IO0=HIGH + self._setRTS(True) # EN=LOW, chip in reset + time.sleep(0.1) + self._setDTR(True) # IO0=LOW + self._setRTS(False) # EN=HIGH, chip out of reset + time.sleep(self.reset_delay) + self._setDTR(False) # IO0=HIGH, done + + +class UnixTightReset(ResetStrategy): + """ + UNIX-only reset sequence with custom implementation, + which allows setting DTR and RTS lines at the same time. + """ + + def __call__(self): + self._setDTRandRTS(False, False) + self._setDTRandRTS(True, True) + self._setDTRandRTS(False, True) # IO0=HIGH & EN=LOW, chip in reset + time.sleep(0.1) + self._setDTRandRTS(True, False) # IO0=LOW & EN=HIGH, chip out of reset + time.sleep(self.reset_delay) + self._setDTRandRTS(False, False) # IO0=HIGH, done + self._setDTR(False) # Needed in some environments to ensure IO0=HIGH + + +class USBJTAGSerialReset(ResetStrategy): + """ + Custom reset sequence, which is required when the device + is connecting via its USB-JTAG-Serial peripheral. + """ + + def __call__(self): + self._setRTS(False) + self._setDTR(False) # Idle + time.sleep(0.1) + self._setDTR(True) # Set IO0 + self._setRTS(False) + time.sleep(0.1) + self._setRTS(True) # Reset. Calls inverted to go through (1,1) instead of (0,0) + self._setDTR(False) + self._setRTS(True) # RTS set as Windows only propagates DTR on RTS setting + time.sleep(0.1) + self._setDTR(False) + self._setRTS(False) # Chip out of reset + + +class HardReset(ResetStrategy): + """ + Reset sequence for hard resetting the chip. + Can be used to reset out of the bootloader or to restart a running app. + """ + + def __init__(self, port, uses_usb_otg=False): + super().__init__(port) + self.uses_usb_otg = uses_usb_otg + + def __call__(self): + self._setRTS(True) # EN->LOW + if self.uses_usb_otg: + # Give the chip some time to come out of reset, + # to be able to handle further DTR/RTS transitions + time.sleep(0.2) + self._setRTS(False) + time.sleep(0.2) + else: + time.sleep(0.1) + self._setRTS(False) + + +class CustomReset(ResetStrategy): + """ + Custom reset strategy defined with a string. + + CustomReset object is created as "rst = CustomReset(port, seq_str)" + and can be later executed simply with "rst()" + + The seq_str input string consists of individual commands divided by "|". + Commands (e.g. R0) are defined by a code (R) and an argument (0). + + The commands are: + D: setDTR - 1=True / 0=False + R: setRTS - 1=True / 0=False + U: setDTRandRTS (Unix-only) - 0,0 / 0,1 / 1,0 / or 1,1 + W: Wait (time delay) - positive float number + + e.g. + "D0|R1|W0.1|D1|R0|W0.05|D0" represents the ClassicReset strategy + "U1,1|U0,1|W0.1|U1,0|W0.05|U0,0" represents the UnixTightReset strategy + """ + + format_dict = { + "D": "self.port.setDTR({})", + "R": "self.port.setRTS({})", + "W": "time.sleep({})", + "U": "self._setDTRandRTS({})", + } + + def __call__(self): + exec(self.constructed_strategy) + + def __init__(self, port, seq_str): + super().__init__(port) + self.constructed_strategy = self._parse_string_to_seq(seq_str) + + def _parse_string_to_seq(self, seq_str): + try: + cmds = seq_str.split("|") + fn_calls_list = [self.format_dict[cmd[0]].format(cmd[1:]) for cmd in cmds] + except Exception as e: + raise FatalError(f'Invalid "custom_reset_sequence" option format: {e}') + return "\n".join(fn_calls_list) diff --git a/installer/bin/esptool/esptool/targets/__init__.py b/installer/bin/esptool/esptool/targets/__init__.py new file mode 100644 index 0000000..9d76ca5 --- /dev/null +++ b/installer/bin/esptool/esptool/targets/__init__.py @@ -0,0 +1,31 @@ +from .esp32 import ESP32ROM +from .esp32c2 import ESP32C2ROM +from .esp32c3 import ESP32C3ROM +from .esp32c6 import ESP32C6ROM +from .esp32c6beta import ESP32C6BETAROM +from .esp32h2 import ESP32H2ROM +from .esp32h2beta1 import ESP32H2BETA1ROM +from .esp32h2beta2 import ESP32H2BETA2ROM +from .esp32s2 import ESP32S2ROM +from .esp32s3 import ESP32S3ROM +from .esp32s3beta2 import ESP32S3BETA2ROM +from .esp8266 import ESP8266ROM + + +CHIP_DEFS = { + "esp8266": ESP8266ROM, + "esp32": ESP32ROM, + "esp32s2": ESP32S2ROM, + "esp32s3beta2": ESP32S3BETA2ROM, + "esp32s3": ESP32S3ROM, + "esp32c3": ESP32C3ROM, + "esp32c6beta": ESP32C6BETAROM, + "esp32h2beta1": ESP32H2BETA1ROM, + "esp32h2beta2": ESP32H2BETA2ROM, + "esp32c2": ESP32C2ROM, + "esp32c6": ESP32C6ROM, + "esp32h2": ESP32H2ROM, +} + +CHIP_LIST = list(CHIP_DEFS.keys()) +ROM_LIST = list(CHIP_DEFS.values()) diff --git a/installer/bin/esptool/esptool/targets/esp32.py b/installer/bin/esptool/esptool/targets/esp32.py new file mode 100644 index 0000000..b805ed4 --- /dev/null +++ b/installer/bin/esptool/esptool/targets/esp32.py @@ -0,0 +1,393 @@ +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import struct +import time + +from ..loader import ESPLoader +from ..util import FatalError, NotSupportedError + + +class ESP32ROM(ESPLoader): + """Access class for ESP32 ROM bootloader""" + + CHIP_NAME = "ESP32" + IMAGE_CHIP_ID = 0 + IS_STUB = False + + FPGA_SLOW_BOOT = True + + CHIP_DETECT_MAGIC_VALUE = [0x00F01D83] + + IROM_MAP_START = 0x400D0000 + IROM_MAP_END = 0x40400000 + + DROM_MAP_START = 0x3F400000 + DROM_MAP_END = 0x3F800000 + + # ESP32 uses a 4 byte status reply + STATUS_BYTES_LENGTH = 4 + + SPI_REG_BASE = 0x3FF42000 + SPI_USR_OFFS = 0x1C + SPI_USR1_OFFS = 0x20 + SPI_USR2_OFFS = 0x24 + SPI_MOSI_DLEN_OFFS = 0x28 + SPI_MISO_DLEN_OFFS = 0x2C + EFUSE_RD_REG_BASE = 0x3FF5A000 + + EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE + 0x18 + EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 7 # EFUSE_RD_DISABLE_DL_ENCRYPT + + EFUSE_SPI_BOOT_CRYPT_CNT_REG = EFUSE_RD_REG_BASE # EFUSE_BLK0_WDATA0_REG + EFUSE_SPI_BOOT_CRYPT_CNT_MASK = 0x7F << 20 # EFUSE_FLASH_CRYPT_CNT + + EFUSE_RD_ABS_DONE_REG = EFUSE_RD_REG_BASE + 0x018 + EFUSE_RD_ABS_DONE_0_MASK = 1 << 4 + EFUSE_RD_ABS_DONE_1_MASK = 1 << 5 + + DR_REG_SYSCON_BASE = 0x3FF66000 + APB_CTL_DATE_ADDR = DR_REG_SYSCON_BASE + 0x7C + APB_CTL_DATE_V = 0x1 + APB_CTL_DATE_S = 31 + + SPI_W0_OFFS = 0x80 + + UART_CLKDIV_REG = 0x3FF40014 + + XTAL_CLK_DIVIDER = 1 + + RTCCALICFG1 = 0x3FF5F06C + TIMERS_RTC_CALI_VALUE = 0x01FFFFFF + TIMERS_RTC_CALI_VALUE_S = 7 + + FLASH_SIZES = { + "1MB": 0x00, + "2MB": 0x10, + "4MB": 0x20, + "8MB": 0x30, + "16MB": 0x40, + "32MB": 0x50, + "64MB": 0x60, + "128MB": 0x70, + } + + FLASH_FREQUENCY = { + "80m": 0xF, + "40m": 0x0, + "26m": 0x1, + "20m": 0x2, + } + + BOOTLOADER_FLASH_OFFSET = 0x1000 + + OVERRIDE_VDDSDIO_CHOICES = ["1.8V", "1.9V", "OFF"] + + MEMORY_MAP = [ + [0x00000000, 0x00010000, "PADDING"], + [0x3F400000, 0x3F800000, "DROM"], + [0x3F800000, 0x3FC00000, "EXTRAM_DATA"], + [0x3FF80000, 0x3FF82000, "RTC_DRAM"], + [0x3FF90000, 0x40000000, "BYTE_ACCESSIBLE"], + [0x3FFAE000, 0x40000000, "DRAM"], + [0x3FFE0000, 0x3FFFFFFC, "DIRAM_DRAM"], + [0x40000000, 0x40070000, "IROM"], + [0x40070000, 0x40078000, "CACHE_PRO"], + [0x40078000, 0x40080000, "CACHE_APP"], + [0x40080000, 0x400A0000, "IRAM"], + [0x400A0000, 0x400BFFFC, "DIRAM_IRAM"], + [0x400C0000, 0x400C2000, "RTC_IRAM"], + [0x400D0000, 0x40400000, "IROM"], + [0x50000000, 0x50002000, "RTC_DATA"], + ] + + FLASH_ENCRYPTED_WRITE_ALIGN = 32 + + """ Try to read the BLOCK1 (encryption key) and check if it is valid """ + + def is_flash_encryption_key_valid(self): + """Bit 0 of efuse_rd_disable[3:0] is mapped to BLOCK1 + this bit is at position 16 in EFUSE_BLK0_RDATA0_REG""" + word0 = self.read_efuse(0) + rd_disable = (word0 >> 16) & 0x1 + + # reading of BLOCK1 is NOT ALLOWED so we assume valid key is programmed + if rd_disable: + return True + else: + # reading of BLOCK1 is ALLOWED so we will read and verify for non-zero. + # When ESP32 has not generated AES/encryption key in BLOCK1, + # the contents will be readable and 0. + # If the flash encryption is enabled it is expected to have a valid + # non-zero key. We break out on first occurance of non-zero value + key_word = [0] * 7 + for i in range(len(key_word)): + key_word[i] = self.read_efuse(14 + i) + # key is non-zero so break & return + if key_word[i] != 0: + return True + return False + + def get_flash_crypt_config(self): + """For flash encryption related commands we need to make sure + user has programmed all the relevant efuse correctly so before + writing encrypted write_flash_encrypt esptool will verify the values + of flash_crypt_config to be non zero if they are not read + protected. If the values are zero a warning will be printed + + bit 3 in efuse_rd_disable[3:0] is mapped to flash_crypt_config + this bit is at position 19 in EFUSE_BLK0_RDATA0_REG""" + word0 = self.read_efuse(0) + rd_disable = (word0 >> 19) & 0x1 + + if rd_disable == 0: + """we can read the flash_crypt_config efuse value + so go & read it (EFUSE_BLK0_RDATA5_REG[31:28])""" + word5 = self.read_efuse(5) + word5 = (word5 >> 28) & 0xF + return word5 + else: + # if read of the efuse is disabled we assume it is set correctly + return 0xF + + def get_encrypted_download_disabled(self): + return ( + self.read_reg(self.EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG) + & self.EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT + ) + + def get_flash_encryption_enabled(self): + flash_crypt_cnt = ( + self.read_reg(self.EFUSE_SPI_BOOT_CRYPT_CNT_REG) + & self.EFUSE_SPI_BOOT_CRYPT_CNT_MASK + ) + # Flash encryption enabled when odd number of bits are set + return bin(flash_crypt_cnt).count("1") & 1 != 0 + + def get_secure_boot_enabled(self): + efuses = self.read_reg(self.EFUSE_RD_ABS_DONE_REG) + rev = self.get_chip_revision() + return efuses & self.EFUSE_RD_ABS_DONE_0_MASK or ( + rev >= 300 and efuses & self.EFUSE_RD_ABS_DONE_1_MASK + ) + + def get_pkg_version(self): + word3 = self.read_efuse(3) + pkg_version = (word3 >> 9) & 0x07 + pkg_version += ((word3 >> 2) & 0x1) << 3 + return pkg_version + + def get_chip_revision(self): + return self.get_major_chip_version() * 100 + self.get_minor_chip_version() + + def get_minor_chip_version(self): + return (self.read_efuse(5) >> 24) & 0x3 + + def get_major_chip_version(self): + rev_bit0 = (self.read_efuse(3) >> 15) & 0x1 + rev_bit1 = (self.read_efuse(5) >> 20) & 0x1 + apb_ctl_date = self.read_reg(self.APB_CTL_DATE_ADDR) + rev_bit2 = (apb_ctl_date >> self.APB_CTL_DATE_S) & self.APB_CTL_DATE_V + combine_value = (rev_bit2 << 2) | (rev_bit1 << 1) | rev_bit0 + + revision = { + 0: 0, + 1: 1, + 3: 2, + 7: 3, + }.get(combine_value, 0) + return revision + + def get_chip_description(self): + pkg_version = self.get_pkg_version() + major_rev = self.get_major_chip_version() + minor_rev = self.get_minor_chip_version() + rev3 = major_rev == 3 + single_core = self.read_efuse(3) & (1 << 0) # CHIP_VER DIS_APP_CPU + + chip_name = { + 0: "ESP32-S0WDQ6" if single_core else "ESP32-D0WDQ6", + 1: "ESP32-S0WD" if single_core else "ESP32-D0WD", + 2: "ESP32-D2WD", + 4: "ESP32-U4WDH", + 5: "ESP32-PICO-V3" if rev3 else "ESP32-PICO-D4", + 6: "ESP32-PICO-V3-02", + 7: "ESP32-D0WDR2-V3", + }.get(pkg_version, "unknown ESP32") + + # ESP32-D0WD-V3, ESP32-D0WDQ6-V3 + if chip_name.startswith("ESP32-D0WD") and rev3: + chip_name += "-V3" + + return f"{chip_name} (revision v{major_rev}.{minor_rev})" + + def get_chip_features(self): + features = ["WiFi"] + word3 = self.read_efuse(3) + + # names of variables in this section are lowercase + # versions of EFUSE names as documented in TRM and + # ESP-IDF efuse_reg.h + + chip_ver_dis_bt = word3 & (1 << 1) + if chip_ver_dis_bt == 0: + features += ["BT"] + + chip_ver_dis_app_cpu = word3 & (1 << 0) + if chip_ver_dis_app_cpu: + features += ["Single Core"] + else: + features += ["Dual Core"] + + chip_cpu_freq_rated = word3 & (1 << 13) + if chip_cpu_freq_rated: + chip_cpu_freq_low = word3 & (1 << 12) + if chip_cpu_freq_low: + features += ["160MHz"] + else: + features += ["240MHz"] + + pkg_version = self.get_pkg_version() + if pkg_version in [2, 4, 5, 6]: + features += ["Embedded Flash"] + + if pkg_version == 6: + features += ["Embedded PSRAM"] + + word4 = self.read_efuse(4) + adc_vref = (word4 >> 8) & 0x1F + if adc_vref: + features += ["VRef calibration in efuse"] + + blk3_part_res = word3 >> 14 & 0x1 + if blk3_part_res: + features += ["BLK3 partially reserved"] + + word6 = self.read_efuse(6) + coding_scheme = word6 & 0x3 + features += [ + "Coding Scheme %s" + % {0: "None", 1: "3/4", 2: "Repeat (UNSUPPORTED)", 3: "Invalid"}[ + coding_scheme + ] + ] + + return features + + def read_efuse(self, n): + """Read the nth word of the ESP3x EFUSE region.""" + return self.read_reg(self.EFUSE_RD_REG_BASE + (4 * n)) + + def chip_id(self): + raise NotSupportedError(self, "chip_id") + + def read_mac(self): + """Read MAC from EFUSE region""" + words = [self.read_efuse(2), self.read_efuse(1)] + bitstring = struct.pack(">II", *words) + bitstring = bitstring[2:8] # trim the 2 byte CRC + return tuple(bitstring) + + def get_erase_size(self, offset, size): + return size + + def override_vddsdio(self, new_voltage): + new_voltage = new_voltage.upper() + if new_voltage not in self.OVERRIDE_VDDSDIO_CHOICES: + raise FatalError( + "The only accepted VDDSDIO overrides are '1.8V', '1.9V' and 'OFF'" + ) + RTC_CNTL_SDIO_CONF_REG = 0x3FF48074 + RTC_CNTL_XPD_SDIO_REG = 1 << 31 + RTC_CNTL_DREFH_SDIO_M = 3 << 29 + RTC_CNTL_DREFM_SDIO_M = 3 << 27 + RTC_CNTL_DREFL_SDIO_M = 3 << 25 + # RTC_CNTL_SDIO_TIEH = (1 << 23) + # not used here, setting TIEH=1 would set 3.3V output, + # not safe for esptool.py to do + RTC_CNTL_SDIO_FORCE = 1 << 22 + RTC_CNTL_SDIO_PD_EN = 1 << 21 + + reg_val = RTC_CNTL_SDIO_FORCE # override efuse setting + reg_val |= RTC_CNTL_SDIO_PD_EN + if new_voltage != "OFF": + reg_val |= RTC_CNTL_XPD_SDIO_REG # enable internal LDO + if new_voltage == "1.9V": + reg_val |= ( + RTC_CNTL_DREFH_SDIO_M | RTC_CNTL_DREFM_SDIO_M | RTC_CNTL_DREFL_SDIO_M + ) # boost voltage + self.write_reg(RTC_CNTL_SDIO_CONF_REG, reg_val) + print("VDDSDIO regulator set to %s" % new_voltage) + + def read_flash_slow(self, offset, length, progress_fn): + BLOCK_LEN = 64 # ROM read limit per command (this limit is why it's so slow) + + data = b"" + while len(data) < length: + block_len = min(BLOCK_LEN, length - len(data)) + r = self.check_command( + "read flash block", + self.ESP_READ_FLASH_SLOW, + struct.pack("> self.TIMERS_RTC_CALI_VALUE_S + ) & self.TIMERS_RTC_CALI_VALUE + clk_8M_freq = self.read_efuse(4) & (0xFF) # EFUSE_RD_CK8M_FREQ + rom_calculated_freq = cali_val * 15625 * clk_8M_freq / 40 + return rom_calculated_freq + + def change_baud(self, baud): + # It's a workaround to avoid esp32 CK_8M frequency drift. + rom_calculated_freq = self.get_rom_cal_crystal_freq() + valid_freq = 40000000 if rom_calculated_freq > 33000000 else 26000000 + false_rom_baud = int(baud * rom_calculated_freq // valid_freq) + + print(f"Changing baud rate to {baud}") + self.command(self.ESP_CHANGE_BAUDRATE, struct.pack("> 22) & 0x07 + + def get_chip_description(self): + chip_name = { + 0: "ESP32-C2", + 1: "ESP32-C2", + }.get(self.get_pkg_version(), "unknown ESP32-C2") + major_rev = self.get_major_chip_version() + minor_rev = self.get_minor_chip_version() + return f"{chip_name} (revision v{major_rev}.{minor_rev})" + + def get_minor_chip_version(self): + num_word = 1 + return (self.read_reg(self.EFUSE_BLOCK2_ADDR + (4 * num_word)) >> 16) & 0xF + + def get_major_chip_version(self): + num_word = 1 + return (self.read_reg(self.EFUSE_BLOCK2_ADDR + (4 * num_word)) >> 20) & 0x3 + + def get_crystal_freq(self): + # The crystal detection algorithm of ESP32/ESP8266 works for ESP32-C2 as well. + return ESPLoader.get_crystal_freq(self) + + def change_baud(self, baud): + rom_with_26M_XTAL = not self.IS_STUB and self.get_crystal_freq() == 26 + if rom_with_26M_XTAL: + # The code is copied over from ESPLoader.change_baud(). + # Probably this is just a temporary solution until the next chip revision. + + # The ROM code thinks it uses a 40 MHz XTAL. Recompute the baud rate + # in order to trick the ROM code to set the correct baud rate for + # a 26 MHz XTAL. + false_rom_baud = baud * 40 // 26 + + print(f"Changing baud rate to {baud}") + self.command( + self.ESP_CHANGE_BAUDRATE, struct.pack("> 21) & 0x07 + + def get_minor_chip_version(self): + hi_num_word = 5 + hi = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * hi_num_word)) >> 23) & 0x01 + low_num_word = 3 + low = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * low_num_word)) >> 18) & 0x07 + return (hi << 3) + low + + def get_major_chip_version(self): + num_word = 5 + return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 24) & 0x03 + + def get_chip_description(self): + chip_name = { + 0: "ESP32-C3", + }.get(self.get_pkg_version(), "unknown ESP32-C3") + major_rev = self.get_major_chip_version() + minor_rev = self.get_minor_chip_version() + return f"{chip_name} (revision v{major_rev}.{minor_rev})" + + def get_chip_features(self): + return ["WiFi", "BLE"] + + def get_crystal_freq(self): + # ESP32C3 XTAL is fixed to 40MHz + return 40 + + def override_vddsdio(self, new_voltage): + raise NotImplementedInROMError( + "VDD_SDIO overrides are not supported for ESP32-C3" + ) + + def read_mac(self): + mac0 = self.read_reg(self.MAC_EFUSE_REG) + mac1 = self.read_reg(self.MAC_EFUSE_REG + 4) # only bottom 16 bits are MAC + bitstring = struct.pack(">II", mac1, mac0)[2:] + return tuple(bitstring) + + def get_flash_crypt_config(self): + return None # doesn't exist on ESP32-C3 + + def get_secure_boot_enabled(self): + return ( + self.read_reg(self.EFUSE_SECURE_BOOT_EN_REG) + & self.EFUSE_SECURE_BOOT_EN_MASK + ) + + def get_key_block_purpose(self, key_block): + if key_block < 0 or key_block > 5: + raise FatalError("Valid key block numbers must be in range 0-5") + + reg, shift = [ + (self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT), + (self.EFUSE_PURPOSE_KEY1_REG, self.EFUSE_PURPOSE_KEY1_SHIFT), + (self.EFUSE_PURPOSE_KEY2_REG, self.EFUSE_PURPOSE_KEY2_SHIFT), + (self.EFUSE_PURPOSE_KEY3_REG, self.EFUSE_PURPOSE_KEY3_SHIFT), + (self.EFUSE_PURPOSE_KEY4_REG, self.EFUSE_PURPOSE_KEY4_SHIFT), + (self.EFUSE_PURPOSE_KEY5_REG, self.EFUSE_PURPOSE_KEY5_SHIFT), + ][key_block] + return (self.read_reg(reg) >> shift) & 0xF + + def is_flash_encryption_key_valid(self): + # Need to see an AES-128 key + purposes = [self.get_key_block_purpose(b) for b in range(6)] + + return any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes) + + def change_baud(self, baud): + ESPLoader.change_baud(self, baud) + + def uses_usb_jtag_serial(self): + """ + Check the UARTDEV_BUF_NO register to see if USB-JTAG/Serial is being used + """ + if self.secure_download_mode: + return False # Can't detect USB-JTAG/Serial in secure download mode + return self.get_uart_no() == self.UARTDEV_BUF_NO_USB_JTAG_SERIAL + + def disable_rtc_watchdog(self): + # When USB-JTAG/Serial is used, the RTC watchdog is not reset + # and can then reset the board during flashing. Disable it. + if self.uses_usb_jtag_serial(): + self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, self.RTC_CNTL_WDT_WKEY) + self.write_reg(self.RTC_CNTL_WDTCONFIG0_REG, 0) + self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, 0) + + def _post_connect(self): + if not self.sync_stub_detected: # Don't run if stub is reused + self.disable_rtc_watchdog() + + +class ESP32C3StubLoader(ESP32C3ROM): + """Access class for ESP32C3 stub loader, runs on top of ROM. + + (Basically the same as ESP32StubLoader, but different base class. + Can possibly be made into a mixin.) + """ + + FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c + STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM + IS_STUB = True + + def __init__(self, rom_loader): + self.secure_download_mode = rom_loader.secure_download_mode + self._port = rom_loader._port + self._trace_enabled = rom_loader._trace_enabled + self.cache = rom_loader.cache + self.flush_input() # resets _slip_reader + + +ESP32C3ROM.STUB_CLASS = ESP32C3StubLoader diff --git a/installer/bin/esptool/esptool/targets/esp32c6.py b/installer/bin/esptool/esptool/targets/esp32c6.py new file mode 100644 index 0000000..0c4575e --- /dev/null +++ b/installer/bin/esptool/esptool/targets/esp32c6.py @@ -0,0 +1,189 @@ +# SPDX-FileCopyrightText: 2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import struct + +from .esp32c3 import ESP32C3ROM +from ..util import FatalError, NotImplementedInROMError + + +class ESP32C6ROM(ESP32C3ROM): + CHIP_NAME = "ESP32-C6" + IMAGE_CHIP_ID = 13 + + FPGA_SLOW_BOOT = False + + IROM_MAP_START = 0x42000000 + IROM_MAP_END = 0x42800000 + DROM_MAP_START = 0x42800000 + DROM_MAP_END = 0x43000000 + + BOOTLOADER_FLASH_OFFSET = 0x0 + + # Magic value for ESP32C6 + CHIP_DETECT_MAGIC_VALUE = [0x2CE0806F] + + SPI_REG_BASE = 0x60003000 + SPI_USR_OFFS = 0x18 + SPI_USR1_OFFS = 0x1C + SPI_USR2_OFFS = 0x20 + SPI_MOSI_DLEN_OFFS = 0x24 + SPI_MISO_DLEN_OFFS = 0x28 + SPI_W0_OFFS = 0x58 + + UART_DATE_REG_ADDR = 0x60000000 + 0x7C + + EFUSE_BASE = 0x600B0800 + EFUSE_BLOCK1_ADDR = EFUSE_BASE + 0x044 + MAC_EFUSE_REG = EFUSE_BASE + 0x044 + + EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # BLOCK0 read base address + + EFUSE_PURPOSE_KEY0_REG = EFUSE_BASE + 0x34 + EFUSE_PURPOSE_KEY0_SHIFT = 24 + EFUSE_PURPOSE_KEY1_REG = EFUSE_BASE + 0x34 + EFUSE_PURPOSE_KEY1_SHIFT = 28 + EFUSE_PURPOSE_KEY2_REG = EFUSE_BASE + 0x38 + EFUSE_PURPOSE_KEY2_SHIFT = 0 + EFUSE_PURPOSE_KEY3_REG = EFUSE_BASE + 0x38 + EFUSE_PURPOSE_KEY3_SHIFT = 4 + EFUSE_PURPOSE_KEY4_REG = EFUSE_BASE + 0x38 + EFUSE_PURPOSE_KEY4_SHIFT = 8 + EFUSE_PURPOSE_KEY5_REG = EFUSE_BASE + 0x38 + EFUSE_PURPOSE_KEY5_SHIFT = 12 + + EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE + EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 20 + + EFUSE_SPI_BOOT_CRYPT_CNT_REG = EFUSE_BASE + 0x034 + EFUSE_SPI_BOOT_CRYPT_CNT_MASK = 0x7 << 18 + + EFUSE_SECURE_BOOT_EN_REG = EFUSE_BASE + 0x038 + EFUSE_SECURE_BOOT_EN_MASK = 1 << 20 + + PURPOSE_VAL_XTS_AES128_KEY = 4 + + SUPPORTS_ENCRYPTED_FLASH = True + + FLASH_ENCRYPTED_WRITE_ALIGN = 16 + + UARTDEV_BUF_NO = 0x4087F580 # Variable in ROM .bss which indicates the port in use + UARTDEV_BUF_NO_USB_JTAG_SERIAL = 3 # The above var when USB-JTAG/Serial is used + + DR_REG_LP_WDT_BASE = 0x600B1C00 + RTC_CNTL_WDTCONFIG0_REG = DR_REG_LP_WDT_BASE + 0x0 # LP_WDT_RWDT_CONFIG0_REG + RTC_CNTL_WDTWPROTECT_REG = DR_REG_LP_WDT_BASE + 0x0018 # LP_WDT_RWDT_WPROTECT_REG + + FLASH_FREQUENCY = { + "80m": 0x0, # workaround for wrong mspi HS div value in ROM + "40m": 0x0, + "20m": 0x2, + } + + MEMORY_MAP = [ + [0x00000000, 0x00010000, "PADDING"], + [0x42800000, 0x43000000, "DROM"], + [0x40800000, 0x40880000, "DRAM"], + [0x40800000, 0x40880000, "BYTE_ACCESSIBLE"], + [0x4004AC00, 0x40050000, "DROM_MASK"], + [0x40000000, 0x4004AC00, "IROM_MASK"], + [0x42000000, 0x42800000, "IROM"], + [0x40800000, 0x40880000, "IRAM"], + [0x50000000, 0x50004000, "RTC_IRAM"], + [0x50000000, 0x50004000, "RTC_DRAM"], + [0x600FE000, 0x60100000, "MEM_INTERNAL2"], + ] + + def get_pkg_version(self): + num_word = 3 + return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 21) & 0x07 + + def get_minor_chip_version(self): + hi_num_word = 5 + hi = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * hi_num_word)) >> 23) & 0x01 + low_num_word = 3 + low = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * low_num_word)) >> 18) & 0x07 + return (hi << 3) + low + + def get_major_chip_version(self): + num_word = 5 + return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 24) & 0x03 + + def get_chip_description(self): + chip_name = { + 0: "ESP32-C6", + }.get(self.get_pkg_version(), "unknown ESP32-C6") + major_rev = self.get_major_chip_version() + minor_rev = self.get_minor_chip_version() + return f"{chip_name} (revision v{major_rev}.{minor_rev})" + + def get_chip_features(self): + return ["WiFi 6", "BT 5", "IEEE802.15.4"] + + def get_crystal_freq(self): + # ESP32C6 XTAL is fixed to 40MHz + return 40 + + def override_vddsdio(self, new_voltage): + raise NotImplementedInROMError( + "VDD_SDIO overrides are not supported for ESP32-C6" + ) + + def read_mac(self): + mac0 = self.read_reg(self.MAC_EFUSE_REG) + mac1 = self.read_reg(self.MAC_EFUSE_REG + 4) # only bottom 16 bits are MAC + bitstring = struct.pack(">II", mac1, mac0)[2:] + return tuple(bitstring) + + def get_flash_crypt_config(self): + return None # doesn't exist on ESP32-C6 + + def get_secure_boot_enabled(self): + return ( + self.read_reg(self.EFUSE_SECURE_BOOT_EN_REG) + & self.EFUSE_SECURE_BOOT_EN_MASK + ) + + def get_key_block_purpose(self, key_block): + if key_block < 0 or key_block > 5: + raise FatalError("Valid key block numbers must be in range 0-5") + + reg, shift = [ + (self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT), + (self.EFUSE_PURPOSE_KEY1_REG, self.EFUSE_PURPOSE_KEY1_SHIFT), + (self.EFUSE_PURPOSE_KEY2_REG, self.EFUSE_PURPOSE_KEY2_SHIFT), + (self.EFUSE_PURPOSE_KEY3_REG, self.EFUSE_PURPOSE_KEY3_SHIFT), + (self.EFUSE_PURPOSE_KEY4_REG, self.EFUSE_PURPOSE_KEY4_SHIFT), + (self.EFUSE_PURPOSE_KEY5_REG, self.EFUSE_PURPOSE_KEY5_SHIFT), + ][key_block] + return (self.read_reg(reg) >> shift) & 0xF + + def is_flash_encryption_key_valid(self): + # Need to see an AES-128 key + purposes = [self.get_key_block_purpose(b) for b in range(6)] + + return any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes) + + +class ESP32C6StubLoader(ESP32C6ROM): + """Access class for ESP32C6 stub loader, runs on top of ROM. + + (Basically the same as ESP32StubLoader, but different base class. + Can possibly be made into a mixin.) + """ + + FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c + STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM + IS_STUB = True + + def __init__(self, rom_loader): + self.secure_download_mode = rom_loader.secure_download_mode + self._port = rom_loader._port + self._trace_enabled = rom_loader._trace_enabled + self.cache = rom_loader.cache + self.flush_input() # resets _slip_reader + + +ESP32C6ROM.STUB_CLASS = ESP32C6StubLoader diff --git a/installer/bin/esptool/esptool/targets/esp32c6beta.py b/installer/bin/esptool/esptool/targets/esp32c6beta.py new file mode 100644 index 0000000..4c4b6be --- /dev/null +++ b/installer/bin/esptool/esptool/targets/esp32c6beta.py @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from .esp32c3 import ESP32C3ROM + + +class ESP32C6BETAROM(ESP32C3ROM): + CHIP_NAME = "ESP32-C6(beta)" + IMAGE_CHIP_ID = 7 + + CHIP_DETECT_MAGIC_VALUE = [0x0DA1806F] + + UART_DATE_REG_ADDR = 0x00000500 + + def get_chip_description(self): + chip_name = { + 0: "ESP32-C6", + }.get(self.get_pkg_version(), "unknown ESP32-C6") + major_rev = self.get_major_chip_version() + minor_rev = self.get_minor_chip_version() + return f"{chip_name} (revision v{major_rev}.{minor_rev})" + + def _post_connect(self): + pass diff --git a/installer/bin/esptool/esptool/targets/esp32h2.py b/installer/bin/esptool/esptool/targets/esp32h2.py new file mode 100644 index 0000000..a37617e --- /dev/null +++ b/installer/bin/esptool/esptool/targets/esp32h2.py @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: 2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from .esp32c6 import ESP32C6ROM + + +class ESP32H2ROM(ESP32C6ROM): + CHIP_NAME = "ESP32-H2" + IMAGE_CHIP_ID = 16 + + # Magic value for ESP32H2 + CHIP_DETECT_MAGIC_VALUE = [0xD7B73E80] + + FLASH_FREQUENCY = { + "48m": 0xF, + "24m": 0x0, + "16m": 0x1, + "12m": 0x2, + } + + def get_pkg_version(self): + num_word = 3 + block1_addr = self.EFUSE_BASE + 0x044 + word3 = self.read_reg(block1_addr + (4 * num_word)) + pkg_version = (word3 >> 21) & 0x0F + return pkg_version + + def get_chip_description(self): + chip_name = { + 0: "ESP32-H2", + }.get(self.get_pkg_version(), "unknown ESP32-H2") + major_rev = self.get_major_chip_version() + minor_rev = self.get_minor_chip_version() + return f"{chip_name} (revision v{major_rev}.{minor_rev})" + + def get_chip_features(self): + return ["BLE"] + + def get_crystal_freq(self): + # ESP32H2 XTAL is fixed to 32MHz + return 32 + + +class ESP32H2StubLoader(ESP32H2ROM): + """Access class for ESP32H2 stub loader, runs on top of ROM. + + (Basically the same as ESP32StubLoader, but different base class. + Can possibly be made into a mixin.) + """ + + FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c + STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM + IS_STUB = True + + def __init__(self, rom_loader): + self.secure_download_mode = rom_loader.secure_download_mode + self._port = rom_loader._port + self._trace_enabled = rom_loader._trace_enabled + self.cache = rom_loader.cache + self.flush_input() # resets _slip_reader + + +ESP32H2ROM.STUB_CLASS = ESP32H2StubLoader diff --git a/installer/bin/esptool/esptool/targets/esp32h2beta1.py b/installer/bin/esptool/esptool/targets/esp32h2beta1.py new file mode 100644 index 0000000..66b6df9 --- /dev/null +++ b/installer/bin/esptool/esptool/targets/esp32h2beta1.py @@ -0,0 +1,164 @@ +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import struct + +from .esp32c3 import ESP32C3ROM +from ..util import FatalError, NotImplementedInROMError + + +class ESP32H2BETA1ROM(ESP32C3ROM): + CHIP_NAME = "ESP32-H2(beta1)" + IMAGE_CHIP_ID = 10 + + IROM_MAP_START = 0x42000000 + IROM_MAP_END = 0x42800000 + DROM_MAP_START = 0x3C000000 + DROM_MAP_END = 0x3C800000 + + SPI_REG_BASE = 0x60002000 + SPI_USR_OFFS = 0x18 + SPI_USR1_OFFS = 0x1C + SPI_USR2_OFFS = 0x20 + SPI_MOSI_DLEN_OFFS = 0x24 + SPI_MISO_DLEN_OFFS = 0x28 + SPI_W0_OFFS = 0x58 + + BOOTLOADER_FLASH_OFFSET = 0x0 + + CHIP_DETECT_MAGIC_VALUE = [0xCA26CC22] + + UART_DATE_REG_ADDR = 0x60000000 + 0x7C + + EFUSE_BASE = 0x6001A000 + EFUSE_BLOCK1_ADDR = EFUSE_BASE + 0x044 + MAC_EFUSE_REG = EFUSE_BASE + 0x044 + + EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # BLOCK0 read base address + + EFUSE_PURPOSE_KEY0_REG = EFUSE_BASE + 0x34 + EFUSE_PURPOSE_KEY0_SHIFT = 24 + EFUSE_PURPOSE_KEY1_REG = EFUSE_BASE + 0x34 + EFUSE_PURPOSE_KEY1_SHIFT = 28 + EFUSE_PURPOSE_KEY2_REG = EFUSE_BASE + 0x38 + EFUSE_PURPOSE_KEY2_SHIFT = 0 + EFUSE_PURPOSE_KEY3_REG = EFUSE_BASE + 0x38 + EFUSE_PURPOSE_KEY3_SHIFT = 4 + EFUSE_PURPOSE_KEY4_REG = EFUSE_BASE + 0x38 + EFUSE_PURPOSE_KEY4_SHIFT = 8 + EFUSE_PURPOSE_KEY5_REG = EFUSE_BASE + 0x38 + EFUSE_PURPOSE_KEY5_SHIFT = 12 + + EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE + EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 20 + + EFUSE_SPI_BOOT_CRYPT_CNT_REG = EFUSE_BASE + 0x034 + EFUSE_SPI_BOOT_CRYPT_CNT_MASK = 0x7 << 18 + + EFUSE_SECURE_BOOT_EN_REG = EFUSE_BASE + 0x038 + EFUSE_SECURE_BOOT_EN_MASK = 1 << 20 + + PURPOSE_VAL_XTS_AES128_KEY = 4 + + SUPPORTS_ENCRYPTED_FLASH = True + + FLASH_ENCRYPTED_WRITE_ALIGN = 16 + + MEMORY_MAP = [] + + FLASH_FREQUENCY = { + "48m": 0xF, + "24m": 0x0, + "16m": 0x1, + "12m": 0x2, + } + + def get_pkg_version(self): + num_word = 3 + return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 21) & 0x0F + + def get_minor_chip_version(self): + hi_num_word = 5 + hi = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * hi_num_word)) >> 23) & 0x01 + low_num_word = 3 + low = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * low_num_word)) >> 18) & 0x07 + return (hi << 3) + low + + def get_major_chip_version(self): + num_word = 5 + return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 24) & 0x03 + + def get_chip_description(self): + chip_name = { + 0: "ESP32-H2", + }.get(self.get_pkg_version(), "unknown ESP32-H2") + major_rev = self.get_major_chip_version() + minor_rev = self.get_minor_chip_version() + return f"{chip_name} (revision v{major_rev}.{minor_rev})" + + def get_chip_features(self): + return ["BLE", "IEEE802.15.4"] + + def get_crystal_freq(self): + return 32 + + def override_vddsdio(self, new_voltage): + raise NotImplementedInROMError( + "VDD_SDIO overrides are not supported for ESP32-H2" + ) + + def read_mac(self): + mac0 = self.read_reg(self.MAC_EFUSE_REG) + mac1 = self.read_reg(self.MAC_EFUSE_REG + 4) # only bottom 16 bits are MAC + bitstring = struct.pack(">II", mac1, mac0)[2:] + return tuple(bitstring) + + def get_flash_crypt_config(self): + return None # doesn't exist on ESP32-H2 + + def get_key_block_purpose(self, key_block): + if key_block < 0 or key_block > 5: + raise FatalError("Valid key block numbers must be in range 0-5") + + reg, shift = [ + (self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT), + (self.EFUSE_PURPOSE_KEY1_REG, self.EFUSE_PURPOSE_KEY1_SHIFT), + (self.EFUSE_PURPOSE_KEY2_REG, self.EFUSE_PURPOSE_KEY2_SHIFT), + (self.EFUSE_PURPOSE_KEY3_REG, self.EFUSE_PURPOSE_KEY3_SHIFT), + (self.EFUSE_PURPOSE_KEY4_REG, self.EFUSE_PURPOSE_KEY4_SHIFT), + (self.EFUSE_PURPOSE_KEY5_REG, self.EFUSE_PURPOSE_KEY5_SHIFT), + ][key_block] + return (self.read_reg(reg) >> shift) & 0xF + + def is_flash_encryption_key_valid(self): + # Need to see an AES-128 key + purposes = [self.get_key_block_purpose(b) for b in range(6)] + + return any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes) + + def _post_connect(self): + pass + + +class ESP32H2BETA1StubLoader(ESP32H2BETA1ROM): + """Access class for ESP32H2BETA1 stub loader, runs on top of ROM. + + (Basically the same as ESP32StubLoader, but different base class. + Can possibly be made into a mixin.) + """ + + FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c + STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM + IS_STUB = True + + def __init__(self, rom_loader): + self.secure_download_mode = rom_loader.secure_download_mode + self._port = rom_loader._port + self._trace_enabled = rom_loader._trace_enabled + self.cache = rom_loader.cache + self.flush_input() # resets _slip_reader + + +ESP32H2BETA1ROM.STUB_CLASS = ESP32H2BETA1StubLoader diff --git a/installer/bin/esptool/esptool/targets/esp32h2beta2.py b/installer/bin/esptool/esptool/targets/esp32h2beta2.py new file mode 100644 index 0000000..6fa8f58 --- /dev/null +++ b/installer/bin/esptool/esptool/targets/esp32h2beta2.py @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from .esp32h2beta1 import ESP32H2BETA1ROM + + +class ESP32H2BETA2ROM(ESP32H2BETA1ROM): + CHIP_NAME = "ESP32-H2(beta2)" + IMAGE_CHIP_ID = 14 + + CHIP_DETECT_MAGIC_VALUE = [0x6881B06F] + + def get_chip_description(self): + chip_name = { + 1: "ESP32-H2(beta2)", + }.get(self.get_pkg_version(), "unknown ESP32-H2") + major_rev = self.get_major_chip_version() + minor_rev = self.get_minor_chip_version() + return f"{chip_name} (revision v{major_rev}.{minor_rev})" + + +class ESP32H2BETA2StubLoader(ESP32H2BETA2ROM): + """Access class for ESP32H2BETA2 stub loader, runs on top of ROM. + + (Basically the same as ESP32StubLoader, but different base class. + Can possibly be made into a mixin.) + """ + + FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c + STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM + IS_STUB = True + + def __init__(self, rom_loader): + self.secure_download_mode = rom_loader.secure_download_mode + self._port = rom_loader._port + self._trace_enabled = rom_loader._trace_enabled + self.cache = rom_loader.cache + self.flush_input() # resets _slip_reader + + +ESP32H2BETA2ROM.STUB_CLASS = ESP32H2BETA2StubLoader diff --git a/installer/bin/esptool/esptool/targets/esp32s2.py b/installer/bin/esptool/esptool/targets/esp32s2.py new file mode 100644 index 0000000..bffd553 --- /dev/null +++ b/installer/bin/esptool/esptool/targets/esp32s2.py @@ -0,0 +1,305 @@ +# SPDX-FileCopyrightText: 2014-2023 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import os +import struct + +from .esp32 import ESP32ROM +from ..loader import ESPLoader +from ..reset import HardReset +from ..util import FatalError, NotImplementedInROMError + + +class ESP32S2ROM(ESP32ROM): + CHIP_NAME = "ESP32-S2" + IMAGE_CHIP_ID = 2 + + FPGA_SLOW_BOOT = False + + IROM_MAP_START = 0x40080000 + IROM_MAP_END = 0x40B80000 + DROM_MAP_START = 0x3F000000 + DROM_MAP_END = 0x3F3F0000 + + CHIP_DETECT_MAGIC_VALUE = [0x000007C6] + + SPI_REG_BASE = 0x3F402000 + SPI_USR_OFFS = 0x18 + SPI_USR1_OFFS = 0x1C + SPI_USR2_OFFS = 0x20 + SPI_MOSI_DLEN_OFFS = 0x24 + SPI_MISO_DLEN_OFFS = 0x28 + SPI_W0_OFFS = 0x58 + + MAC_EFUSE_REG = 0x3F41A044 # ESP32-S2 has special block for MAC efuses + + UART_CLKDIV_REG = 0x3F400014 + + SUPPORTS_ENCRYPTED_FLASH = True + + FLASH_ENCRYPTED_WRITE_ALIGN = 16 + + # todo: use espefuse APIs to get this info + EFUSE_BASE = 0x3F41A000 + EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # BLOCK0 read base address + EFUSE_BLOCK1_ADDR = EFUSE_BASE + 0x044 + EFUSE_BLOCK2_ADDR = EFUSE_BASE + 0x05C + + EFUSE_PURPOSE_KEY0_REG = EFUSE_BASE + 0x34 + EFUSE_PURPOSE_KEY0_SHIFT = 24 + EFUSE_PURPOSE_KEY1_REG = EFUSE_BASE + 0x34 + EFUSE_PURPOSE_KEY1_SHIFT = 28 + EFUSE_PURPOSE_KEY2_REG = EFUSE_BASE + 0x38 + EFUSE_PURPOSE_KEY2_SHIFT = 0 + EFUSE_PURPOSE_KEY3_REG = EFUSE_BASE + 0x38 + EFUSE_PURPOSE_KEY3_SHIFT = 4 + EFUSE_PURPOSE_KEY4_REG = EFUSE_BASE + 0x38 + EFUSE_PURPOSE_KEY4_SHIFT = 8 + EFUSE_PURPOSE_KEY5_REG = EFUSE_BASE + 0x38 + EFUSE_PURPOSE_KEY5_SHIFT = 12 + + EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE + EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 19 + + EFUSE_SPI_BOOT_CRYPT_CNT_REG = EFUSE_BASE + 0x034 + EFUSE_SPI_BOOT_CRYPT_CNT_MASK = 0x7 << 18 + + EFUSE_SECURE_BOOT_EN_REG = EFUSE_BASE + 0x038 + EFUSE_SECURE_BOOT_EN_MASK = 1 << 20 + + EFUSE_RD_REPEAT_DATA3_REG = EFUSE_BASE + 0x3C + EFUSE_RD_REPEAT_DATA3_REG_FLASH_TYPE_MASK = 1 << 9 + + PURPOSE_VAL_XTS_AES256_KEY_1 = 2 + PURPOSE_VAL_XTS_AES256_KEY_2 = 3 + PURPOSE_VAL_XTS_AES128_KEY = 4 + + UARTDEV_BUF_NO = 0x3FFFFD14 # Variable in ROM .bss which indicates the port in use + UARTDEV_BUF_NO_USB_OTG = 2 # Value of the above indicating that USB-OTG is in use + + USB_RAM_BLOCK = 0x800 # Max block size USB-OTG is used + + GPIO_STRAP_REG = 0x3F404038 + GPIO_STRAP_SPI_BOOT_MASK = 0x8 # Not download mode + RTC_CNTL_OPTION1_REG = 0x3F408128 + RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK = 0x1 # Is download mode forced over USB? + + MEMORY_MAP = [ + [0x00000000, 0x00010000, "PADDING"], + [0x3F000000, 0x3FF80000, "DROM"], + [0x3F500000, 0x3FF80000, "EXTRAM_DATA"], + [0x3FF9E000, 0x3FFA0000, "RTC_DRAM"], + [0x3FF9E000, 0x40000000, "BYTE_ACCESSIBLE"], + [0x3FF9E000, 0x40072000, "MEM_INTERNAL"], + [0x3FFB0000, 0x40000000, "DRAM"], + [0x40000000, 0x4001A100, "IROM_MASK"], + [0x40020000, 0x40070000, "IRAM"], + [0x40070000, 0x40072000, "RTC_IRAM"], + [0x40080000, 0x40800000, "IROM"], + [0x50000000, 0x50002000, "RTC_DATA"], + ] + + def get_pkg_version(self): + num_word = 4 + return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 0) & 0x0F + + def get_minor_chip_version(self): + hi_num_word = 3 + hi = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * hi_num_word)) >> 20) & 0x01 + low_num_word = 4 + low = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * low_num_word)) >> 4) & 0x07 + return (hi << 3) + low + + def get_major_chip_version(self): + num_word = 3 + return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 18) & 0x03 + + def get_flash_version(self): + num_word = 3 + return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 21) & 0x0F + + def get_psram_version(self): + num_word = 3 + return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 28) & 0x0F + + def get_block2_version(self): + # BLK_VERSION_MINOR + num_word = 4 + return (self.read_reg(self.EFUSE_BLOCK2_ADDR + (4 * num_word)) >> 4) & 0x07 + + def get_chip_description(self): + chip_name = { + 0: "ESP32-S2", + 1: "ESP32-S2FH2", + 2: "ESP32-S2FH4", + 102: "ESP32-S2FNR2", + 100: "ESP32-S2R2", + }.get( + self.get_flash_version() + self.get_psram_version() * 100, + "unknown ESP32-S2", + ) + major_rev = self.get_major_chip_version() + minor_rev = self.get_minor_chip_version() + return f"{chip_name} (revision v{major_rev}.{minor_rev})" + + def get_chip_features(self): + features = ["WiFi"] + + if self.secure_download_mode: + features += ["Secure Download Mode Enabled"] + + flash_version = { + 0: "No Embedded Flash", + 1: "Embedded Flash 2MB", + 2: "Embedded Flash 4MB", + }.get(self.get_flash_version(), "Unknown Embedded Flash") + features += [flash_version] + + psram_version = { + 0: "No Embedded PSRAM", + 1: "Embedded PSRAM 2MB", + 2: "Embedded PSRAM 4MB", + }.get(self.get_psram_version(), "Unknown Embedded PSRAM") + features += [psram_version] + + block2_version = { + 0: "No calibration in BLK2 of efuse", + 1: "ADC and temperature sensor calibration in BLK2 of efuse V1", + 2: "ADC and temperature sensor calibration in BLK2 of efuse V2", + }.get(self.get_block2_version(), "Unknown Calibration in BLK2") + features += [block2_version] + + return features + + def get_crystal_freq(self): + # ESP32-S2 XTAL is fixed to 40MHz + return 40 + + def override_vddsdio(self, new_voltage): + raise NotImplementedInROMError( + "VDD_SDIO overrides are not supported for ESP32-S2" + ) + + def read_mac(self): + mac0 = self.read_reg(self.MAC_EFUSE_REG) + mac1 = self.read_reg(self.MAC_EFUSE_REG + 4) # only bottom 16 bits are MAC + bitstring = struct.pack(">II", mac1, mac0)[2:] + return tuple(bitstring) + + def flash_type(self): + return ( + 1 + if self.read_reg(self.EFUSE_RD_REPEAT_DATA3_REG) + & self.EFUSE_RD_REPEAT_DATA3_REG_FLASH_TYPE_MASK + else 0 + ) + + def get_flash_crypt_config(self): + return None # doesn't exist on ESP32-S2 + + def get_secure_boot_enabled(self): + return ( + self.read_reg(self.EFUSE_SECURE_BOOT_EN_REG) + & self.EFUSE_SECURE_BOOT_EN_MASK + ) + + def get_key_block_purpose(self, key_block): + if key_block < 0 or key_block > 5: + raise FatalError("Valid key block numbers must be in range 0-5") + + reg, shift = [ + (self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT), + (self.EFUSE_PURPOSE_KEY1_REG, self.EFUSE_PURPOSE_KEY1_SHIFT), + (self.EFUSE_PURPOSE_KEY2_REG, self.EFUSE_PURPOSE_KEY2_SHIFT), + (self.EFUSE_PURPOSE_KEY3_REG, self.EFUSE_PURPOSE_KEY3_SHIFT), + (self.EFUSE_PURPOSE_KEY4_REG, self.EFUSE_PURPOSE_KEY4_SHIFT), + (self.EFUSE_PURPOSE_KEY5_REG, self.EFUSE_PURPOSE_KEY5_SHIFT), + ][key_block] + return (self.read_reg(reg) >> shift) & 0xF + + def is_flash_encryption_key_valid(self): + # Need to see either an AES-128 key or two AES-256 keys + purposes = [self.get_key_block_purpose(b) for b in range(6)] + + if any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes): + return True + + return any(p == self.PURPOSE_VAL_XTS_AES256_KEY_1 for p in purposes) and any( + p == self.PURPOSE_VAL_XTS_AES256_KEY_2 for p in purposes + ) + + def uses_usb_otg(self): + """ + Check the UARTDEV_BUF_NO register to see if USB-OTG console is being used + """ + if self.secure_download_mode: + return False # can't detect native USB in secure download mode + return self.get_uart_no() == self.UARTDEV_BUF_NO_USB_OTG + + def _post_connect(self): + if self.uses_usb_otg(): + self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK + + def _check_if_can_reset(self): + """ + Check the strapping register to see if we can reset out of download mode. + """ + if os.getenv("ESPTOOL_TESTING") is not None: + print("ESPTOOL_TESTING is set, ignoring strapping mode check") + # Esptool tests over USB-OTG run with GPIO0 strapped low, + # don't complain in this case. + return + strap_reg = self.read_reg(self.GPIO_STRAP_REG) + force_dl_reg = self.read_reg(self.RTC_CNTL_OPTION1_REG) + if ( + strap_reg & self.GPIO_STRAP_SPI_BOOT_MASK == 0 + and force_dl_reg & self.RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK == 0 + ): + print( + "WARNING: {} chip was placed into download mode using GPIO0.\n" + "esptool.py can not exit the download mode over USB. " + "To run the app, reset the chip manually.\n" + "To suppress this note, set --after option to 'no_reset'.".format( + self.get_chip_description() + ) + ) + raise SystemExit(1) + + def hard_reset(self): + uses_usb_otg = self.uses_usb_otg() + if uses_usb_otg: + self._check_if_can_reset() + + print("Hard resetting via RTS pin...") + HardReset(self._port, uses_usb_otg)() + + def change_baud(self, baud): + ESPLoader.change_baud(self, baud) + + +class ESP32S2StubLoader(ESP32S2ROM): + """Access class for ESP32-S2 stub loader, runs on top of ROM. + + (Basically the same as ESP32StubLoader, but different base class. + Can possibly be made into a mixin.) + """ + + FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c + STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM + IS_STUB = True + + def __init__(self, rom_loader): + self.secure_download_mode = rom_loader.secure_download_mode + self._port = rom_loader._port + self._trace_enabled = rom_loader._trace_enabled + self.cache = rom_loader.cache + self.flush_input() # resets _slip_reader + + if rom_loader.uses_usb_otg(): + self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK + self.FLASH_WRITE_SIZE = self.USB_RAM_BLOCK + + +ESP32S2ROM.STUB_CLASS = ESP32S2StubLoader diff --git a/installer/bin/esptool/esptool/targets/esp32s3.py b/installer/bin/esptool/esptool/targets/esp32s3.py new file mode 100644 index 0000000..660537b --- /dev/null +++ b/installer/bin/esptool/esptool/targets/esp32s3.py @@ -0,0 +1,315 @@ +# SPDX-FileCopyrightText: 2014-2023 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import os +import struct + +from .esp32 import ESP32ROM +from ..loader import ESPLoader +from ..reset import HardReset +from ..util import FatalError, NotImplementedInROMError + + +class ESP32S3ROM(ESP32ROM): + CHIP_NAME = "ESP32-S3" + + IMAGE_CHIP_ID = 9 + + CHIP_DETECT_MAGIC_VALUE = [0x9] + + FPGA_SLOW_BOOT = False + + IROM_MAP_START = 0x42000000 + IROM_MAP_END = 0x44000000 + DROM_MAP_START = 0x3C000000 + DROM_MAP_END = 0x3E000000 + + UART_DATE_REG_ADDR = 0x60000080 + + SPI_REG_BASE = 0x60002000 + SPI_USR_OFFS = 0x18 + SPI_USR1_OFFS = 0x1C + SPI_USR2_OFFS = 0x20 + SPI_MOSI_DLEN_OFFS = 0x24 + SPI_MISO_DLEN_OFFS = 0x28 + SPI_W0_OFFS = 0x58 + + BOOTLOADER_FLASH_OFFSET = 0x0 + + SUPPORTS_ENCRYPTED_FLASH = True + + FLASH_ENCRYPTED_WRITE_ALIGN = 16 + + # todo: use espefuse APIs to get this info + EFUSE_BASE = 0x60007000 # BLOCK0 read base address + EFUSE_BLOCK1_ADDR = EFUSE_BASE + 0x44 + EFUSE_BLOCK2_ADDR = EFUSE_BASE + 0x5C + MAC_EFUSE_REG = EFUSE_BASE + 0x044 + + EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # BLOCK0 read base address + + EFUSE_PURPOSE_KEY0_REG = EFUSE_BASE + 0x34 + EFUSE_PURPOSE_KEY0_SHIFT = 24 + EFUSE_PURPOSE_KEY1_REG = EFUSE_BASE + 0x34 + EFUSE_PURPOSE_KEY1_SHIFT = 28 + EFUSE_PURPOSE_KEY2_REG = EFUSE_BASE + 0x38 + EFUSE_PURPOSE_KEY2_SHIFT = 0 + EFUSE_PURPOSE_KEY3_REG = EFUSE_BASE + 0x38 + EFUSE_PURPOSE_KEY3_SHIFT = 4 + EFUSE_PURPOSE_KEY4_REG = EFUSE_BASE + 0x38 + EFUSE_PURPOSE_KEY4_SHIFT = 8 + EFUSE_PURPOSE_KEY5_REG = EFUSE_BASE + 0x38 + EFUSE_PURPOSE_KEY5_SHIFT = 12 + + EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE + EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 20 + + EFUSE_SPI_BOOT_CRYPT_CNT_REG = EFUSE_BASE + 0x034 + EFUSE_SPI_BOOT_CRYPT_CNT_MASK = 0x7 << 18 + + EFUSE_SECURE_BOOT_EN_REG = EFUSE_BASE + 0x038 + EFUSE_SECURE_BOOT_EN_MASK = 1 << 20 + + EFUSE_RD_REPEAT_DATA3_REG = EFUSE_BASE + 0x3C + EFUSE_RD_REPEAT_DATA3_REG_FLASH_TYPE_MASK = 1 << 9 + + PURPOSE_VAL_XTS_AES256_KEY_1 = 2 + PURPOSE_VAL_XTS_AES256_KEY_2 = 3 + PURPOSE_VAL_XTS_AES128_KEY = 4 + + UARTDEV_BUF_NO = 0x3FCEF14C # Variable in ROM .bss which indicates the port in use + UARTDEV_BUF_NO_USB_OTG = 3 # The above var when USB-OTG is used + UARTDEV_BUF_NO_USB_JTAG_SERIAL = 4 # The above var when USB-JTAG/Serial is used + + RTC_CNTL_WDT_WKEY = 0x50D83AA1 + RTCCNTL_BASE_REG = 0x60008000 + RTC_CNTL_WDTCONFIG0_REG = RTCCNTL_BASE_REG + 0x0090 + RTC_CNTL_WDTWPROTECT_REG = RTCCNTL_BASE_REG + 0x00B0 + + USB_RAM_BLOCK = 0x800 # Max block size USB-OTG is used + + GPIO_STRAP_REG = 0x60004038 + GPIO_STRAP_SPI_BOOT_MASK = 0x8 # Not download mode + RTC_CNTL_OPTION1_REG = 0x6000812C + RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK = 0x1 # Is download mode forced over USB? + + UART_CLKDIV_REG = 0x60000014 + + MEMORY_MAP = [ + [0x00000000, 0x00010000, "PADDING"], + [0x3C000000, 0x3D000000, "DROM"], + [0x3D000000, 0x3E000000, "EXTRAM_DATA"], + [0x600FE000, 0x60100000, "RTC_DRAM"], + [0x3FC88000, 0x3FD00000, "BYTE_ACCESSIBLE"], + [0x3FC88000, 0x403E2000, "MEM_INTERNAL"], + [0x3FC88000, 0x3FD00000, "DRAM"], + [0x40000000, 0x4001A100, "IROM_MASK"], + [0x40370000, 0x403E0000, "IRAM"], + [0x600FE000, 0x60100000, "RTC_IRAM"], + [0x42000000, 0x42800000, "IROM"], + [0x50000000, 0x50002000, "RTC_DATA"], + ] + + def get_pkg_version(self): + num_word = 3 + return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 21) & 0x07 + + def is_eco0(self, minor_raw): + # Workaround: The major version field was allocated to other purposes + # when block version is v1.1. + # Luckily only chip v0.0 have this kind of block version and efuse usage. + return ( + (minor_raw & 0x7) == 0 + and self.get_blk_version_major() == 1 + and self.get_blk_version_minor() == 1 + ) + + def get_minor_chip_version(self): + minor_raw = self.get_raw_minor_chip_version() + if self.is_eco0(minor_raw): + return 0 + return minor_raw + + def get_raw_minor_chip_version(self): + hi_num_word = 5 + hi = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * hi_num_word)) >> 23) & 0x01 + low_num_word = 3 + low = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * low_num_word)) >> 18) & 0x07 + return (hi << 3) + low + + def get_blk_version_major(self): + num_word = 4 + return (self.read_reg(self.EFUSE_BLOCK2_ADDR + (4 * num_word)) >> 0) & 0x03 + + def get_blk_version_minor(self): + num_word = 3 + return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 24) & 0x07 + + def get_major_chip_version(self): + minor_raw = self.get_raw_minor_chip_version() + if self.is_eco0(minor_raw): + return 0 + return self.get_raw_major_chip_version() + + def get_raw_major_chip_version(self): + num_word = 5 + return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 24) & 0x03 + + def get_chip_description(self): + major_rev = self.get_major_chip_version() + minor_rev = self.get_minor_chip_version() + return f"{self.CHIP_NAME} (revision v{major_rev}.{minor_rev})" + + def get_chip_features(self): + return ["WiFi", "BLE"] + + def get_crystal_freq(self): + # ESP32S3 XTAL is fixed to 40MHz + return 40 + + def get_flash_crypt_config(self): + return None # doesn't exist on ESP32-S3 + + def get_key_block_purpose(self, key_block): + if key_block < 0 or key_block > 5: + raise FatalError("Valid key block numbers must be in range 0-5") + + reg, shift = [ + (self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT), + (self.EFUSE_PURPOSE_KEY1_REG, self.EFUSE_PURPOSE_KEY1_SHIFT), + (self.EFUSE_PURPOSE_KEY2_REG, self.EFUSE_PURPOSE_KEY2_SHIFT), + (self.EFUSE_PURPOSE_KEY3_REG, self.EFUSE_PURPOSE_KEY3_SHIFT), + (self.EFUSE_PURPOSE_KEY4_REG, self.EFUSE_PURPOSE_KEY4_SHIFT), + (self.EFUSE_PURPOSE_KEY5_REG, self.EFUSE_PURPOSE_KEY5_SHIFT), + ][key_block] + return (self.read_reg(reg) >> shift) & 0xF + + def is_flash_encryption_key_valid(self): + # Need to see either an AES-128 key or two AES-256 keys + purposes = [self.get_key_block_purpose(b) for b in range(6)] + + if any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes): + return True + + return any(p == self.PURPOSE_VAL_XTS_AES256_KEY_1 for p in purposes) and any( + p == self.PURPOSE_VAL_XTS_AES256_KEY_2 for p in purposes + ) + + def get_secure_boot_enabled(self): + return ( + self.read_reg(self.EFUSE_SECURE_BOOT_EN_REG) + & self.EFUSE_SECURE_BOOT_EN_MASK + ) + + def override_vddsdio(self, new_voltage): + raise NotImplementedInROMError( + "VDD_SDIO overrides are not supported for ESP32-S3" + ) + + def read_mac(self): + mac0 = self.read_reg(self.MAC_EFUSE_REG) + mac1 = self.read_reg(self.MAC_EFUSE_REG + 4) # only bottom 16 bits are MAC + bitstring = struct.pack(">II", mac1, mac0)[2:] + return tuple(bitstring) + + def flash_type(self): + return ( + 1 + if self.read_reg(self.EFUSE_RD_REPEAT_DATA3_REG) + & self.EFUSE_RD_REPEAT_DATA3_REG_FLASH_TYPE_MASK + else 0 + ) + + def uses_usb_otg(self): + """ + Check the UARTDEV_BUF_NO register to see if USB-OTG console is being used + """ + if self.secure_download_mode: + return False # can't detect native USB in secure download mode + return self.get_uart_no() == self.UARTDEV_BUF_NO_USB_OTG + + def uses_usb_jtag_serial(self): + """ + Check the UARTDEV_BUF_NO register to see if USB-JTAG/Serial is being used + """ + if self.secure_download_mode: + return False # can't detect USB-JTAG/Serial in secure download mode + return self.get_uart_no() == self.UARTDEV_BUF_NO_USB_JTAG_SERIAL + + def disable_rtc_watchdog(self): + # When USB-JTAG/Serial is used, the RTC watchdog is not reset + # and can then reset the board during flashing. Disable it. + if self.uses_usb_jtag_serial(): + self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, self.RTC_CNTL_WDT_WKEY) + self.write_reg(self.RTC_CNTL_WDTCONFIG0_REG, 0) + self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, 0) + + def _post_connect(self): + if self.uses_usb_otg(): + self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK + if not self.sync_stub_detected: # Don't run if stub is reused + self.disable_rtc_watchdog() + + def _check_if_can_reset(self): + """ + Check the strapping register to see if we can reset out of download mode. + """ + if os.getenv("ESPTOOL_TESTING") is not None: + print("ESPTOOL_TESTING is set, ignoring strapping mode check") + # Esptool tests over USB-OTG run with GPIO0 strapped low, + # don't complain in this case. + return + strap_reg = self.read_reg(self.GPIO_STRAP_REG) + force_dl_reg = self.read_reg(self.RTC_CNTL_OPTION1_REG) + if ( + strap_reg & self.GPIO_STRAP_SPI_BOOT_MASK == 0 + and force_dl_reg & self.RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK == 0 + ): + print( + "WARNING: {} chip was placed into download mode using GPIO0.\n" + "esptool.py can not exit the download mode over USB. " + "To run the app, reset the chip manually.\n" + "To suppress this note, set --after option to 'no_reset'.".format( + self.get_chip_description() + ) + ) + raise SystemExit(1) + + def hard_reset(self): + uses_usb_otg = self.uses_usb_otg() + if uses_usb_otg: + self._check_if_can_reset() + + print("Hard resetting via RTS pin...") + HardReset(self._port, uses_usb_otg)() + + def change_baud(self, baud): + ESPLoader.change_baud(self, baud) + + +class ESP32S3StubLoader(ESP32S3ROM): + """Access class for ESP32S3 stub loader, runs on top of ROM. + + (Basically the same as ESP32StubLoader, but different base class. + Can possibly be made into a mixin.) + """ + + FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c + STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM + IS_STUB = True + + def __init__(self, rom_loader): + self.secure_download_mode = rom_loader.secure_download_mode + self._port = rom_loader._port + self._trace_enabled = rom_loader._trace_enabled + self.cache = rom_loader.cache + self.flush_input() # resets _slip_reader + + if rom_loader.uses_usb_otg(): + self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK + self.FLASH_WRITE_SIZE = self.USB_RAM_BLOCK + + +ESP32S3ROM.STUB_CLASS = ESP32S3StubLoader diff --git a/installer/bin/esptool/esptool/targets/esp32s3beta2.py b/installer/bin/esptool/esptool/targets/esp32s3beta2.py new file mode 100644 index 0000000..b7958bd --- /dev/null +++ b/installer/bin/esptool/esptool/targets/esp32s3beta2.py @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from .esp32s3 import ESP32S3ROM + + +class ESP32S3BETA2ROM(ESP32S3ROM): + CHIP_NAME = "ESP32-S3(beta2)" + IMAGE_CHIP_ID = 4 + + CHIP_DETECT_MAGIC_VALUE = [0xEB004136] + + EFUSE_BASE = 0x6001A000 # BLOCK0 read base address + + def get_chip_description(self): + major_rev = self.get_major_chip_version() + minor_rev = self.get_minor_chip_version() + return f"{self.CHIP_NAME} (revision v{major_rev}.{minor_rev})" + + +class ESP32S3BETA2StubLoader(ESP32S3BETA2ROM): + """Access class for ESP32S3 stub loader, runs on top of ROM. + + (Basically the same as ESP32StubLoader, but different base class. + Can possibly be made into a mixin.) + """ + + FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c + STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM + IS_STUB = True + + def __init__(self, rom_loader): + self.secure_download_mode = rom_loader.secure_download_mode + self._port = rom_loader._port + self._trace_enabled = rom_loader._trace_enabled + self.cache = rom_loader.cache + self.flush_input() # resets _slip_reader + + +ESP32S3BETA2ROM.STUB_CLASS = ESP32S3BETA2StubLoader diff --git a/installer/bin/esptool/esptool/targets/esp8266.py b/installer/bin/esptool/esptool/targets/esp8266.py new file mode 100644 index 0000000..f996663 --- /dev/null +++ b/installer/bin/esptool/esptool/targets/esp8266.py @@ -0,0 +1,191 @@ +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from ..loader import ESPLoader +from ..util import FatalError, NotImplementedInROMError + + +class ESP8266ROM(ESPLoader): + """Access class for ESP8266 ROM bootloader""" + + CHIP_NAME = "ESP8266" + IS_STUB = False + + CHIP_DETECT_MAGIC_VALUE = [0xFFF0C101] + + # OTP ROM addresses + ESP_OTP_MAC0 = 0x3FF00050 + ESP_OTP_MAC1 = 0x3FF00054 + ESP_OTP_MAC3 = 0x3FF0005C + + SPI_REG_BASE = 0x60000200 + SPI_USR_OFFS = 0x1C + SPI_USR1_OFFS = 0x20 + SPI_USR2_OFFS = 0x24 + SPI_MOSI_DLEN_OFFS = None + SPI_MISO_DLEN_OFFS = None + SPI_W0_OFFS = 0x40 + + UART_CLKDIV_REG = 0x60000014 + + XTAL_CLK_DIVIDER = 2 + + FLASH_SIZES = { + "512KB": 0x00, + "256KB": 0x10, + "1MB": 0x20, + "2MB": 0x30, + "4MB": 0x40, + "2MB-c1": 0x50, + "4MB-c1": 0x60, + "8MB": 0x80, + "16MB": 0x90, + } + + FLASH_FREQUENCY = { + "80m": 0xF, + "40m": 0x0, + "26m": 0x1, + "20m": 0x2, + } + + BOOTLOADER_FLASH_OFFSET = 0 + + MEMORY_MAP = [ + [0x3FF00000, 0x3FF00010, "DPORT"], + [0x3FFE8000, 0x40000000, "DRAM"], + [0x40100000, 0x40108000, "IRAM"], + [0x40201010, 0x402E1010, "IROM"], + ] + + def get_efuses(self): + # Return the 128 bits of ESP8266 efuse as a single Python integer + result = self.read_reg(0x3FF0005C) << 96 + result |= self.read_reg(0x3FF00058) << 64 + result |= self.read_reg(0x3FF00054) << 32 + result |= self.read_reg(0x3FF00050) + return result + + def _get_flash_size(self, efuses): + # rX_Y = EFUSE_DATA_OUTX[Y] + r0_4 = (efuses & (1 << 4)) != 0 + r3_25 = (efuses & (1 << 121)) != 0 + r3_26 = (efuses & (1 << 122)) != 0 + r3_27 = (efuses & (1 << 123)) != 0 + + if r0_4 and not r3_25: + if not r3_27 and not r3_26: + return 1 + elif not r3_27 and r3_26: + return 2 + if not r0_4 and r3_25: + if not r3_27 and not r3_26: + return 2 + elif not r3_27 and r3_26: + return 4 + return -1 + + def get_chip_description(self): + efuses = self.get_efuses() + is_8285 = ( + efuses & ((1 << 4) | 1 << 80) + ) != 0 # One or the other efuse bit is set for ESP8285 + if is_8285: + flash_size = self._get_flash_size(efuses) + max_temp = ( + efuses & (1 << 5) + ) != 0 # This efuse bit identifies the max flash temperature + chip_name = { + 1: "ESP8285H08" if max_temp else "ESP8285N08", + 2: "ESP8285H16" if max_temp else "ESP8285N16", + }.get(flash_size, "ESP8285") + return chip_name + return "ESP8266EX" + + def get_chip_features(self): + features = ["WiFi"] + if "ESP8285" in self.get_chip_description(): + features += ["Embedded Flash"] + return features + + def flash_spi_attach(self, hspi_arg): + if self.IS_STUB: + super(ESP8266ROM, self).flash_spi_attach(hspi_arg) + else: + # ESP8266 ROM has no flash_spi_attach command in serial protocol, + # but flash_begin will do it + self.flash_begin(0, 0) + + def flash_set_parameters(self, size): + # not implemented in ROM, but OK to silently skip for ROM + if self.IS_STUB: + super(ESP8266ROM, self).flash_set_parameters(size) + + def chip_id(self): + """ + Read Chip ID from efuse - the equivalent of the SDK system_get_chip_id() func + """ + id0 = self.read_reg(self.ESP_OTP_MAC0) + id1 = self.read_reg(self.ESP_OTP_MAC1) + return (id0 >> 24) | ((id1 & 0xFFFFFF) << 8) + + def read_mac(self): + """Read MAC from OTP ROM""" + mac0 = self.read_reg(self.ESP_OTP_MAC0) + mac1 = self.read_reg(self.ESP_OTP_MAC1) + mac3 = self.read_reg(self.ESP_OTP_MAC3) + if mac3 != 0: + oui = ((mac3 >> 16) & 0xFF, (mac3 >> 8) & 0xFF, mac3 & 0xFF) + elif ((mac1 >> 16) & 0xFF) == 0: + oui = (0x18, 0xFE, 0x34) + elif ((mac1 >> 16) & 0xFF) == 1: + oui = (0xAC, 0xD0, 0x74) + else: + raise FatalError("Unknown OUI") + return oui + ((mac1 >> 8) & 0xFF, mac1 & 0xFF, (mac0 >> 24) & 0xFF) + + def get_erase_size(self, offset, size): + """Calculate an erase size given a specific size in bytes. + + Provides a workaround for the bootloader erase bug.""" + + sectors_per_block = 16 + sector_size = self.FLASH_SECTOR_SIZE + num_sectors = (size + sector_size - 1) // sector_size + start_sector = offset // sector_size + + head_sectors = sectors_per_block - (start_sector % sectors_per_block) + if num_sectors < head_sectors: + head_sectors = num_sectors + + if num_sectors < 2 * head_sectors: + return (num_sectors + 1) // 2 * sector_size + else: + return (num_sectors - head_sectors) * sector_size + + def override_vddsdio(self, new_voltage): + raise NotImplementedInROMError( + "Overriding VDDSDIO setting only applies to ESP32" + ) + + +class ESP8266StubLoader(ESP8266ROM): + """Access class for ESP8266 stub loader, runs on top of ROM.""" + + FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c + IS_STUB = True + + def __init__(self, rom_loader): + self.secure_download_mode = rom_loader.secure_download_mode + self._port = rom_loader._port + self._trace_enabled = rom_loader._trace_enabled + self.cache = rom_loader.cache + self.flush_input() # resets _slip_reader + + def get_erase_size(self, offset, size): + return size # stub doesn't have same size bug as ROM loader + + +ESP8266ROM.STUB_CLASS = ESP8266StubLoader diff --git a/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32.json b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32.json new file mode 100644 index 0000000..3e87d81 --- /dev/null +++ b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32.json @@ -0,0 +1,7 @@ +{ + "entry": 1074521560, + "text": "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", + "text_start": 1074520064, + "data": "GOv8P9jnC0Bx6AtA8+wLQO3oC0CP6AtA7egLQEnpC0AG6gtAeOoLQCHqC0CB5wtAo+kLQPjpC0Bn6QtAmuoLQI7pC0Ca6gtAXegLQLPoC0Dt6AtASekLQHfoC0BM6wtAs+wLQKXmC0DX7AtApeYLQKXmC0Cl5gtApeYLQKXmC0Cl5gtApeYLQKXmC0Dz6gtApeYLQM3rC0Cz7AtA", + "data_start": 1073605544 +} \ No newline at end of file diff --git a/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32c2.json b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32c2.json new file mode 100644 index 0000000..07887d4 --- /dev/null +++ b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32c2.json @@ -0,0 +1,7 @@ +{ + "entry": 1077413304, + "text": "ARG3BwBgTsaDqYcASsg3Sco/JspSxAbOIsy3BABgfVoTCQkAwEwTdPQ/DeDyQGJEI6g0AUJJ0kSySSJKBWGCgIhAgycJABN19Q+Cl30U4xlE/8m/EwcADJRBqodjGOUAhUeFxiOgBQB5VYKABUdjh+YACUZjjcYAfVWCgEIFEwewDUGFY5XnAolHnMH1t5MGwA1jFtUAmMETBQAMgoCTBtANfVVjldcAmMETBbANgoC3dcs/QRGThQW6BsZhP2NFBQa3d8s/k4eHsQOnBwgD1kcIE3X1D5MGFgDCBsGCI5LXCDKXIwCnAAPXRwiRZ5OHBwRjHvcCN/fKPxMHh7GhZ7qXA6YHCLc2yz+3d8s/k4eHsZOGhrVjH+YAI6bHCCOg1wgjkgcIIaD5V+MG9fyyQEEBgoAjptcII6DnCN23NycAYHxLnYv1/zc3AGB8S52L9f+CgEERBsbdN7cnAGAjpgcCNwcACJjDmEN9/8hXskATRfX/BYlBAYKAQREGxtk/fd03BwBAtycAYJjDNycAYBxD/f+yQEEBgoBBESLEN0TKP5MHxABKwAOpBwEGxibCYwoJBEU3OcW9RxMExACBRGPWJwEERL2Ik7QUAH03hT8cRDcGgAATl8cAmeA3BgABt/b/AHWPtyYAYNjCkMKYQn3/QUeR4AVHMwnpQLqXIygkARzEskAiRJJEAklBAYKAQREGxhMHAAxjEOUCEwWwDZcAyP/ngIDjEwXADbJAQQEXA8j/ZwCD4hMHsA3jGOX+lwDI/+eAgOETBdANxbdBESLEJsIGxiqEswS1AGMXlACyQCJEkkRBAYKAA0UEAAUERTfttxMFAAwXA8j/ZwAD3nVxJsPO3v10hWn9cpOEhPqThwkHIsVKwdLc1tqmlwbHFpGzhCcAKokmhS6ElzDI/+eAgJOThwkHBWqKl7OKR0Ep5AVnfXUTBIX5kwcHB6KXM4QnABMFhfqTBwcHqpeihTOFJwCXMMj/54CAkCKFwUW5PwFFhWIWkbpAKkSaRApJ9llmWtZaSWGCgKKJY3OKAIVpTobWhUqFlwDI/+eAQOITdfUPAe1OhtaFJoWXMMj/54DAi06ZMwQ0QVm3EwUwBlW/cXH9ck7PUs1Wy17HBtci1SbTStFayWLFZsNqwe7eqokWkRMFAAIuirKKtosCwpcAyP/ngEBIhWdj7FcRhWR9dBMEhPqThwQHopczhCcAIoWXMMj/54AghX17Eww7+ZMMi/kThwQHk4cEB2KX5pcBSTMMJwCzjCcAEk1je00JY3GpA3mgfTWmhYgYSTVdNSaGjBgihZcwyP/ngCCBppkmmWN1SQOzB6lBY/F3A7MEKkFj85oA1oQmhowYToWXAMj/54Dg0xN19Q9V3QLEgUR5XY1NowEBAGKFlwDI/+eAYMR9+QNFMQDmhS0xY04FAOPinf6FZ5OHBweml4qX2pcjiqf4hQT5t+MWpf2RR+OG9PYFZ311kwcHBxMEhfmilzOEJwATBYX6kwcHB6qXM4UnAKKFlyDI/+eAgHflOyKFwUXxM8U7EwUAApcAyP/ngOA2hWIWkbpQKlSaVApZ+klqStpKSku6SypMmkwKTfZdTWGCgAERBs4izFExNwTOP2wAEwVE/5cAyP/ngKDKqocFRZXnskeT9wcgPsZ5OTcnAGAcR7cGQAATBUT/1Y8cx7JFlwDI/+eAIMgzNaAA8kBiRAVhgoBBEbdHyj8GxpOHxwAFRyOA5wAT18UAmMcFZ30XzMPIx/mNOpWqlbGBjMsjqgcAQTcZwRMFUAyyQEEBgoABESLMN0TKP5MHxAAmysRHTsYGzkrIqokTBMQAY/OVAK6EqcADKUQAJpkTWckAHEhjVfAAHERjXvkC4T593UhAJobOhZcAyP/ngCC7E3X1DwHFkwdADFzIXECml1zAXESFj1zE8kBiRNJEQkmySQVhgoDdNm2/t1dBSRlxk4f3hAFFPs6G3qLcptrK2M7W0tTW0trQ3s7izObK6sjuxpcAyP/ngICtt0fKPzd3yz+ThwcAEweHumPg5xSlOZFFaAixMYU5t/fKP5OHh7EhZz6XIyD3CLcFOEC3BzhAAUaThwcLk4UFADdJyj8VRSMg+QCXAMj/54DgGzcHAGBcRxMFAAK3RMo/k+cXEFzHlwDI/+eAoBq3RwBgiF+BRbd5yz9xiWEVEzUVAJcAyP/ngOCwwWf9FxMHABCFZkFmtwUAAQFFk4TEAA1qt3rKP5cAyP/ngOCrk4mJsRMJCQAmmhOLirGDp8kI9d+Dq8kIhUcjpgkIIwLxAoPHGwAJRyMT4QKjAvECAtRNR2OL5wZRR2OJ5wYpR2Of5wCDxzsAA8crAKIH2Y8RR2OW5wCDp4sAnEM+1EE2oUVIEJE+g8c7AAPHKwCiB9mPEWdBB2N+9wITBbANlwDI/+eAQJQTBcANlwDI/+eAgJMTBeAOlwDI/+eAwJKBNr23I6AHAJEHbb3JRyMT8QJ9twPHGwDRRmPn5gKFRmPm5gABTBME8A+dqHkXE3f3D8lG4+jm/rd2yz8KB5OGxro2lxhDAoeTBgcDk/b2DxFG42nW/BMH9wITd/cPjUZj7uYIt3bLPwoHk4aGvzaXGEMChxMHQAJjmucQAtQdRAFFlwDI/+eAIIoBRYE8TTxFPKFFSBB9FEk0ffABTAFEE3X0DyU8E3X8Dw08UTzjEQTsg8cbAElHY2D3LglH43n36vUXk/f3Dz1H42P36jd3yz+KBxMHh8C6l5xDgocFRJ3rcBCBRQFFlwDI/+eAQIkd4dFFaBAVNAFEMagFRIHvlwDI/+eAwI0zNKAAKaAhR2OF5wAFRAFMYbcDrIsAA6TLALNnjADSB/X3mTll9cFsIpz9HH19MwWMQF3cs3eVAZXjwWwzBYxAY+aMAv18MwWMQF3QMYGXAMj/54Bgil35ZpT1tzGBlwDI/+eAYIld8WqU0bdBgZcAyP/ngKCIWfkzBJRBwbchR+OK5/ABTBMEAAw5t0FHzb9BRwVE453n9oOlywADpYsAVTK5v0FHBUTjk+f2A6cLAZFnY+PnHIOlSwEDpYsAMTGBt0FHBUTjlOf0g6cLARFnY2T3GgOnywCDpUsBA6WLADOE5wLdNiOsBAAjJIqwCb8DxwQAYw4HEAOniwDBFxMEAAxjE/cAwEgBR5MG8A5jRvcCg8dbAAPHSwABTKIH2Y8Dx2sAQgddj4PHewDiB9mP44T25hMEEAyFtTOG6wADRoYBBQexjuG3g8cEAPHD3ERjmAcSwEgjgAQAVb1hR2OW5wKDp8sBA6eLAYOmSwEDpgsBg6XLAAOliwCX8Mf/54BgeSqMMzSgAAG9AUwFRCm1EUcFROOd5+YDpYsAgUWX8Mf/54Dgeam1E/f3AOMcB+yT3EcAE4SLAAFMfV3jfJzdSESX8Mf/54BgZBhEVEAQQPmOYwenARxCE0f3/32P2Y4UwgUMQQTZvxFHWb1BRwVE45/n4IOniwADp0sBIyT5ACMi6QD1s4MlSQDBF5Hlic8BTBMEYAxJswMniQBjZvcGE/c3AOMQB+YDKIkAAUYBR7OG5QAzBehAY2n3AOMMBtQjJKkAIyLZALGzM4brABBOEQeQwgVG6b8hRwVE45nn2gMkiQAZwBMEgAwjJAkAIyIJADM0gABhuwFMEwQgDCm7AUwTBIAMCbsBTBMEkAwpsxMHIA1jg+cMEwdADeOW57wDxDsAg8crACIEXYyX8Mf/54AAYgOsxABBFGNzhAEijOMEDLrAQGKUMYCcSGNV8ACcRGNa9Arv8C/kdd3IQGKGk4WLAZfwx//ngABeAcWTB0AM3MjcQOKX3MDcRLOHh0HcxJfwx//ngOBcub4JZRMFBXEDrMsAA6SLAJfwx//ngOBOtwcAYNhLtwYAAcEWk1dHARIHdY+9i9mPs4eHAwFFs9WHApfwx//ngIBPEwWAPpfwx//ngIBLAb6DpksBA6YLAYOlywADpYsA7/DP+e28g8U7AIPHKwAThYsBogXdjcEVUTLVtO/wj92Bt4PHOwADxysAE4yLAaIH2Y8TjQf/BUS3e8s/3ERjBQ0AmcNjTIAAY18ECBMHcAzYyOOWB6qTB5AMWaiTh4u6mEO398o/k4eHsZmPPtaDJ4qwt3zKP2rQk4zMAJONi7oFSGNz/QANSELGOsTv8I/WIkcySDdFyj/ihXwQk4aKsRAQEwVFApfwx//ngABKglcDp4ywg6UNADMN/UAdjz6cslcjpOywKoS+lSOgvQCTh4qxnY0BxaFn45L19lqFfTgjoG0Bob819OOLB6CTB4AM3MgxtIOniwDjkwegAUWX8Mf/54DAPAllEwUFcZfwx//ngCA5l/DH/+eA4DzNsgOkywDjDgScAUWX8Mf/54AgOhMFgD6X8Mf/54CgNgKUwbL2UGZU1lRGWbZZJlqWWgZb9ktmTNZMRk22TQlhgoA=", + "text_start": 1077411840, + "data": "GGvKP+AIOEAsCThAhAk4QCgKOECUCjhAQgo4QKgHOEDkCThAJAo4QJgJOEBYBzhAzAk4QFgHOEC6CDhA/gg4QCwJOECECThAzAg4QBIIOEBCCDhAyAg4QOYMOEAsCThArAs4QJoMOECkBjhAxAw4QKQGOECkBjhApAY4QKQGOECkBjhApAY4QKQGOECkBjhASAs4QKQGOEDICzhAmgw4QA==", + "data_start": 1070295976 +} \ No newline at end of file diff --git a/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32c3.json b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32c3.json new file mode 100644 index 0000000..8426a63 --- /dev/null +++ b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32c3.json @@ -0,0 +1,7 @@ +{ + "entry": 1077413532, + "text": "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", + "text_start": 1077411840, + "data": "IGvIP1YKOECmCjhA/go4QKILOEAODDhAvAs4QCIJOEBeCzhAngs4QBILOEDSCDhARgs4QNIIOEAwCjhAdgo4QKYKOED+CjhAQgo4QIYJOEC2CThAPgo4QGAOOECmCjhAJg04QBgOOEASCDhAQA44QBIIOEASCDhAEgg4QBIIOEASCDhAEgg4QBIIOEASCDhAwgw4QBIIOEBEDThAGA44QA==", + "data_start": 1070164912 +} \ No newline at end of file diff --git a/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32c6.json b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32c6.json new file mode 100644 index 0000000..2bf3acc --- /dev/null +++ b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32c6.json @@ -0,0 +1,7 @@ +{ + "entry": 1082132112, + "text": "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", + "text_start": 1082130432, + "data": "HCuEQCoKgEB6CoBA0gqAQHYLgEDiC4BAkAuAQPYIgEAyC4BAcguAQOYKgECmCIBAGguAQKYIgEAECoBASgqAQHoKgEDSCoBAFgqAQFoJgECKCYBAEgqAQDQOgEB6CoBA+gyAQOwNgEDmB4BAFA6AQOYHgEDmB4BA5geAQOYHgEDmB4BA5geAQOYHgEDmB4BAlgyAQOYHgEAYDYBA7A2AQA==", + "data_start": 1082469292 +} \ No newline at end of file diff --git a/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32c6beta.json b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32c6beta.json new file mode 100644 index 0000000..4709f9d --- /dev/null +++ b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32c6beta.json @@ -0,0 +1,7 @@ +{ + "entry": 1077413318, + "text": "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", + "text_start": 1077411840, + "data": "GGvIP/gIOEBECThAnAk4QEAKOECsCjhAWgo4QMAHOED8CThAPAo4QLAJOEBwBzhA5Ak4QHAHOEDSCDhAFgk4QEQJOECcCThA5Ag4QCoIOEBaCDhA4Ag4QP4MOEBECThAxAs4QLIMOEC8BjhA3Aw4QLwGOEC8BjhAvAY4QLwGOEC8BjhAvAY4QLwGOEC8BjhAYAs4QLwGOEDgCzhAsgw4QA==", + "data_start": 1070164904 +} \ No newline at end of file diff --git a/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32h2.json b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32h2.json new file mode 100644 index 0000000..f003a55 --- /dev/null +++ b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32h2.json @@ -0,0 +1,7 @@ +{ + "entry": 1082132112, + "text": "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", + "text_start": 1082130432, + "data": "HCuDQCoKgEB6CoBA0gqAQHYLgEDiC4BAkAuAQPYIgEAyC4BAcguAQOYKgECmCIBAGguAQKYIgEAECoBASgqAQHoKgEDSCoBAFgqAQFoJgECKCYBAEgqAQDQOgEB6CoBA+gyAQOwNgEDmB4BAFA6AQOYHgEDmB4BA5geAQOYHgEDmB4BA5geAQOYHgEDmB4BAlgyAQOYHgEAYDYBA7A2AQA==", + "data_start": 1082403756 +} \ No newline at end of file diff --git a/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32h2beta1.json b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32h2beta1.json new file mode 100644 index 0000000..37d29e8 --- /dev/null +++ b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32h2beta1.json @@ -0,0 +1,7 @@ +{ + "entry": 1077413318, + "text": "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", + "text_start": 1077411840, + "data": "GGvIP/gIOEBECThAnAk4QEAKOECsCjhAWgo4QMAHOED8CThAPAo4QLAJOEBwBzhA5Ak4QHAHOEDSCDhAFgk4QEQJOECcCThA5Ag4QCoIOEBaCDhA4Ag4QP4MOEBECThAxAs4QLIMOEC8BjhA3Aw4QLwGOEC8BjhAvAY4QLwGOEC8BjhAvAY4QLwGOEC8BjhAYAs4QLwGOEDgCzhAsgw4QA==", + "data_start": 1070164904 +} \ No newline at end of file diff --git a/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32h2beta2.json b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32h2beta2.json new file mode 100644 index 0000000..f7cc1ce --- /dev/null +++ b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32h2beta2.json @@ -0,0 +1,7 @@ +{ + "entry": 1077413318, + "text": "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", + "text_start": 1077411840, + "data": "GGvIP/gIOEBECThAnAk4QEAKOECsCjhAWgo4QMAHOED8CThAPAo4QLAJOEBwBzhA5Ak4QHAHOEDSCDhAFgk4QEQJOECcCThA5Ag4QCoIOEBaCDhA4Ag4QP4MOEBECThAxAs4QLIMOEC8BjhA3Aw4QLwGOEC8BjhAvAY4QLwGOEC8BjhAvAY4QLwGOEC8BjhAYAs4QLwGOEDgCzhAsgw4QA==", + "data_start": 1070164904 +} \ No newline at end of file diff --git a/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32s2.json b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32s2.json new file mode 100644 index 0000000..de8cdc2 --- /dev/null +++ b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32s2.json @@ -0,0 +1,7 @@ +{ + "entry": 1073907696, + "text": "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", + "text_start": 1073905664, + "data": "ZCv9PzaLAkDBiwJAhpACQEqMAkDjiwJASowCQKmMAkByjQJA5Y0CQI2NAkDAigJAC40CQGSNAkDMjAJACI4CQPaMAkAIjgJAr4sCQA6MAkBKjAJAqYwCQMeLAkACiwJAx44CQD2QAkDYiQJAZZACQNiJAkDYiQJA2IkCQNiJAkDYiQJA2IkCQNiJAkDYiQJAZI4CQNiJAkBZjwJAPZACQA==", + "data_start": 1073622012 +} \ No newline at end of file diff --git a/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32s3.json b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32s3.json new file mode 100644 index 0000000..1a73fca --- /dev/null +++ b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32s3.json @@ -0,0 +1,7 @@ +{ + "entry": 1077381684, + "text": "FIADYACAA2BIAMo/BIADYDZBAIH7/wxJwCAAmQjGBAAAgfj/wCAAqAiB9/+goHSICOAIACH2/8AgAIgCJ+jhHfAAAAAIAABgHAAAYBAAAGA2QQAh/P/AIAA4AkH7/8AgACgEICCUnOJB6P9GBAAMODCIAcAgAKgIiASgoHTgCAALImYC6Ib0/yHx/8AgADkCHfAAAOwryz9kq8o/hIAAAEBAAACk68o/8CvLPzZBALH5/yCgdBARIGUoAZYaBoH2/5KhAZCZEZqYwCAAuAmR8/+goHSaiMAgAJIYAJCQ9BvJwMD0wCAAwlgAmpvAIACiSQDAIACSGACB6v+QkPSAgPSHmUeB5f+SoQGQmRGamMAgAMgJoeX/seP/h5wXxgEAfOiHGt7GCADAIACJCsAgALkJRgIAwCAAuQrAIACJCZHX/5qIDAnAIACSWAAd8AAAVCAAYFQwAGA2QQCR/f/AIACICYCAJFZI/5H6/8AgAIgJgIAkVkj/HfAAAAAsIABgACAAYAAAAAg2QQAQESCl/P8h+v8MCMAgAIJiAJH6/4H4/8AgAJJoAMAgAJgIVnn/wCAAiAJ88oAiMCAgBB3wAAAAAEA2QQAQESDl+/8Wav+B7P+R+//AIACSaADAIACYCFZ5/x3wAAAUKABANkEAIKIggf3/4AgAHfAAAHDi+j8IIABgvAoAQMgKAEA2YQAQESBl9P8x+f+9Aa0Dgfr/4AgATQoMEuzqiAGSogCQiBCJARARIOX4/5Hy/6CiAcAgAIgJoIggwCAAiQm4Aa0Dge7/4AgAoCSDHfAAAFgAyj//DwAABCAAQOgIAEA2QQCB+/8MGZJIADCcQZkokfn/ORgpODAwtJoiKjMwPEEMAjlIKViB9P/gCAAnGgiB8//gCAAGAwAQESAl9v8tCowaIqDFHfC4CABANoEAgev/4AgAHAYGDAAAAGBUQwwIDBrQlREMjTkx7QKJYalRmUGJIYkR2QEsDwzMDEuB8v/gCABQRMBaM1oi5hTNDAId8AAA////AAQgAGD0CABADAkAQAAJAEA2gQAx0f8oQxaCERARIGXm/xb6EAz4DAQnqAyIIwwSgIA0gCSTIEB0EBEgZej/EBEgJeH/gcf/4AgAFjoKqCOB6/9AKhEW9AQnKDyBwv/gCACB6P/gCADoIwwCDBqpYalRHI9A7hEMjcKg2AxbKUEpMSkhKREpAYHK/+AIAIG1/+AIAIYCAAAAoKQhgdv/4AgAHAoGIAAAACcoOYGu/+AIAIHU/+AIAOgjDBIcj0DuEQyNLAwMW60CKWEpUUlBSTFJIUkRSQGBtv/gCACBov/gCABGAQCByf/gCAAMGoYNAAAoIwwZQCIRkIkBzBSAiQGRv/+QIhCRvv/AIAAiaQAhW//AIACCYgDAIACIAlZ4/xwKDBJAooMoQ6AiwClDKCOqIikjHfAAADaBAIGK/+AIACwGhg8AAACBr//gCABgVEMMCAwa0JUR7QKpYalRiUGJMZkhORGJASwPDI3CoBKyoASBj//gCACBe//gCABaM1oiUETA5hS/HfAAABQKAEA2YQBBcf9YNFAzYxajC1gUWlNQXEFGAQAQESBl5v9oRKYWBWIkAmel7hARIGXM/xZq/4Fn/+AIABaaBmIkAYFl/+AIAGBQdIKhAFB4wHezCM0DvQKtBgYPAM0HvQKtBlLV/xARICX0/zpVUFhBDAjGBQAAAADCoQCJARARIKXy/4gBctcBG4iAgHRwpoBwsoBXOOFww8AQESDl8P+BTv/gCACGBQCoFM0DvQKB1P/gCACgoHSMSiKgxCJkBSgUOiIpFCg0MCLAKTQd8ABcBwBANkEAgf7/4AgAggoYDAmCyPwMEoApkx3wNkEAgfj/4AgAggoYDAmCyP0MEoApkx3wvP/OP0QAyj9MAMo/QCYAQDQmAEDQJgBANmEAfMitAoeTLTH3/8YFAACoAwwcvQGB9//gCACBj/6iAQCICOAIAKgDgfP/4AgA5hrdxgoAAABmAyYMA80BDCsyYQCB7v/gCACYAYHo/zeZDagIZhoIMeb/wCAAokMAmQgd8EAAyj8AAMo/KCYAQDZBACH8/4Hc/8gCqAix+v+B+//gCAAMCIkCHfCQBgBANkEAEBEgpfP/jLqB8v+ICIxIEBEgpfz/EBEg5fD/FioAoqAEgfb/4AgAHfBIBgBANkEAEBEgpfD/vBqR5v+ICRuoqQmR5f8MCoqZIkkAgsjBDBmAqYOggHTMiqKvQKoiIJiTnNkQESBl9/9GBQCtAoHv/+AIABARIOXq/4xKEBEg5ff/HfAAADZBAKKgwBARIOX5/x3wAAA2QQCCoMCtAoeSEaKg2xARIGX4/6Kg3EYEAAAAAIKg24eSCBARICX3/6Kg3RARIKX2/x3wNkEAOjLGAgAAogIAGyIQESCl+/83kvEd8AAAAFwcAEAgCgBAaBwAQHQcAEA2ISGi0RCB+v/gCABGEAAAAAwUQEQRgcb+4AgAQENjzQS9AYyqrQIQESCltf8GAgAArQKB8P/gCACgoHT8Ws0EELEgotEQgez/4AgASiJAM8BWw/siogsQIrAgoiCy0RCB5//gCACtAhwLEBEgZfb/LQOGAAAioGMd8AAAiCYAQIQbAECUJgBAkBsAQDZBABARIGXb/6yKDBNBcf/wMwGMsqgEgfb/4AgArQPGCQCtA4H0/+AIAKgEgfP/4AgABgkAEBEgpdb/DBjwiAEsA6CDg60IFpIAgez/4AgAhgEAAIHo/+AIAB3wYAYAQDZBIWKkHeBmERpmWQYMF1KgAGLREFClIEB3EVJmGhARIOX3/0e3AsZCAK0Ggbb/4AgAxi8AUHPAgYP+4AgAQHdjzQe9AYy6IKIgEBEgpaT/BgIAAK0Cgaz/4AgAoKB0jJoMCIJmFn0IBhIAABARIGXj/70HrQEQESDl5v8QESBl4v/NBxCxIGCmIIGg/+AIAHoielU3tcmSoQfAmRGCpB0ameCIEZgJGoiICJB1wIc3gwbr/wwJkkZsoqQbEKqggc//4AgAVgr/sqILogZsELuwEBEgpaQA9+oS9kcPkqINEJmwepmiSQAbd4bx/3zpl5rBZkcSgqEHkiYawIgRGoiZCDe5Ape1iyKiCxAisL0GrQKBf//gCAAQESCl2P+tAhwLEBEgJdz/EBEgpdf/DBoQESDl5v8d8AAAyj9PSEFJsIAAYKE62FCQgABg9CvLP6yAN0CYIAxg7IE3QKyFN0AIAAhggCEMYBCAN0AQgANgUIA3QAwAAGA4QABglCzLP///AAAsgQBgjIAAABBAAAD4K8s/CCzLP1AAyj9UAMo/VCzLPxQAAGDw//8A9CvLP2Qryj9wAMo/gAcAQHgbAEC4JgBAZCYAQHQfAEDsCgBAVAkAQFAKAEAABgBAHCkAQCQnAEAIKABA5AYAQHSBBECcCQBA/AkAQAgKAECoBgBAhAkAQGwJAECQCQBAKAgAQNgGAEA24QAhyf8MCinBgeb/4AgAEBEg5bH/rCohxf8xxf9Bxf/AIAA5AgwDwCAAOQTAIAA5AoYBAEkCSyLGAQAhuv8xvv8MBDcy7RARIGXE/wxLosEwEBEg5cf/IqEBEBEgJcP/QYD9kCIRKiTAIABJAjGz/yFY/TkCEBEg5az/LQoW+gUht/7BuP6oAgwrgbr+4AgAMav/saz/HBoMDMAgAKkDgcL/4AgADBrwqgGBN//gCACxpf+oAgwVgb3/4AgAqAKBL//gCACoAoG6/+AIADGf/8AgACgDUCIgwCAAKQOGGAAQESClpP+8GjGZ/xwasZn/wCAAomMAIMIggav/4AgAMZb/DEXAIAAoAwwaUCIgwCAAKQPwqgHGCAAAALGQ/80KDFqBof/gCAAxjf9SoQHAIAAoAywKUCIgwCAAKQOBEv/gCACBnP/gCAAhhv/AIAAoAsy6HMMwIhAiwvgMEyCjgwwLgZX/4AgAgbH94AgAjNqhff+Bkv/gCACBrv3gCADxe/8MHQwcDBvioQBA3REAzBFguwEMCoGK/+AIACF1/ypEIaH9YtIrhhcAAABRbv7AIAAyBQAwMHQW0wQMGvCqAcAgACJFAIHu/uAIAKKiccCqEYF8/+AIAIF7/+AIAHFk/3zowCAAOAd8+oAzEBCqAcAgADkHgXX/4AgAgXX/4AgArQKBdP/gCADAIAAoBBai+QwHwCAAOAQMEsAgAHkEIkEkIgMBDCh5oSJBJYJRExw3dxIkHEd3EiFmkiEiAwNyAwKAIhFwIiBmQhIoI8AgACgCKaGGAQAAABwiIlETEBEg5aL/sqAIosEkEBEgZab/sgMDIgMCgLsRIFsgIT7/ICD0V7IaoqDAEBEgJaH/oqDuEBEgpaD/EBEgZZ//Btr/IgMBHEcnNzf2IhvG+AAAIsIvICB0tkICBiUAcTD/cCKgKAKgAgAAIsL+ICB0HCcntwIG7wBxKv9wIqAoAqACAHLCMHBwdLZXxUbpACxJDAcioMCXFQJG5wB5oQxyrQcQESDlmf+tBxARIGWZ/xARIOWX/xARIKWX/wyLosEkIsL/EBEg5Zr/ViL9RkQADBJWpTXCwRC9Ba0FgSf/4AgAVqo0HEuiwRAQESClmP+GsAAMElZ1M4Eh/+AIAKAlg8bKACaFBAwSxsgAeCMoMyCHIICAtFbY/hARIOVF/yp3rNoG+P8AgSr94AgAUFxBnAqtBYFS/eAIAIYDAAAi0vBGAwCtBYEP/+AIABbq/gbt/yBXwMwSxpYAUJD0Vmn8hgsAgRv94AgAUFD1nEqtBYFC/eAIAIYEAAB8+ACIEYoiRgMArQWBAP/gCAAWqv4G3f8MGQCZESBXwCc5xUYLAAAAAIEL/eAIAFBcQZwKrQWBM/3gCACGAwAAItLwRgMArQWB8P7gCAAW6v4Gzv8gV8BW4vyGdwAMByKgwCaFAsaVAAwHLQcGlAAmtfUGagAMEia1AgaOALgzqCMMBxARICWK/6Ang4aJAAwZZrVfiEMgqREMByKgwoe6AsaGALhTqCOSYREQESAlO/+SIRGgl4NGDgAMGWa1NIhDIKkRDAcioMKHugIGfAAoM7hTqCMgeIKSYREQESAlOP8h2/wMCJIhEYliItIrcmICoJiDLQkGbwAAkdX8DAeiCQAioMZ3mgIGbQB4I7LF8CKgwLeXAShZDAeSoO9GAgB6g4IIGBt3gJkwtyfyggMFcgMEgIgRcIggcgMGAHcRgHcgggMHgIgBcIgggJnAgqDBDAeQKJOGWQCBvfwioMaSCAB9CRaJFZg4DAcioMh3GQLGUgAoWJJIAEZOAByJDAcMEpcVAsZNAPhz6GPYU8hDuDOoI4GV/uAIAAwIfQqgKIPGRgAAAAwSJkUCxkEAqCMMC4GL/uAIAAYgAABQkDQMByKgwHcZAkY9AFBUQYvDfPhGDwCoPIJhEpJhEcJhEIGD/uAIAMIhEIIhEigseByoDJIhEXByECYCDcAgANgKICgw0CIQIHcgwCAAeQobmcLMEFc5vsaT/2ZFAkaS/wwHIqDARiYADBImtQLGIQAhX/6IU3gjiQIhXv55AgwCBh0AoVr+DAfoCgwZssXwjQctB7Apk+CJgyCIECKgxneYX8FU/n0I2AwioMm3PVKw8BQioMBWnwQtCIYCAAAqg4hoSyKJB40JKn4g/cC3Mu0WaN35DHkKxnP/AAwSZoUXIUT+iAKMGIKgyAwHeQIhQP55AgwSgCeDDAcGAQAMByKg/yCgdBARICVg/3CgdBARIKVf/xARICVe/1ZitSIDARwnJzcg9jICBtL+IsL9ICB0DPcntwKGzv5xL/5wIqAoAqACAAAAcqDSdxJfcqDUd5ICBiEAxsb+KDM4IxARICVF/40KVsqwoqJxwKoRgmESgS/+4AgAcSH+kSH+wCAAeAeCIRJwtDXAdxGQdxBwuyAgu4KtCDC7woEu/uAIAKKj6IEj/uAIAEay/gAA2FPIQ7gzqCMQESDlaf+Grf4AsgMDIgMCgLsRILsgssvwosMYEBEgZS//hqb+ACIDA3IDAoAiEXAiIIEc/uAIAHEp/CLC8Ig3gCJjFpKniBeKgoCMQUYDAAAAgmESEBEg5RP/giESkicEphkFkicCl6jnEBEg5fn+Fmr/qBfNArLDGIEL/uAIAIw6MqDEOVc4FyozORc4NyAjwCk3gQX+4AgAhoj+AAByAwIiwxgyAwMMGYAzEXAzIDLD8AYjAHHm/YGY+5gHObGQiMCJQYgmDBmHswEMOZJhERARICUM/5IhEYHe/ZkB6Aeh3f3dCCCyIMLBLPLBEIJhEoHv/eAIALgmnQqosYIhEqC7wLkmoDPAuAeqIqhBDAyquwwauQeQyoOAu8DA0HRWjADC24DArZMWagGtCIJhEpJhERARIOUd/4IhEpIhEYJnAFHm+3g1jKOQjzGQiMDWKABW9/XWqQAx4fsioMcpU0YAAIw5jPcGVf4WF5VR3PsioMgpVYZR/jHZ+yKgySlTxk7+KCNWYpMQESAlM/+ionHAqhGBuf3gCACBxf3gCADGRv4oMxZikRARICUx/6Kj6IGy/eAIAOACAEZA/h3wAAA2QQCdAoKgwCgDh5kPzDIMEoYHAAwCKQN84oYPACYSByYiGIYDAAAAgqDbgCkjh5kqDCIpA3zyRggAAAAioNwnmQoMEikDLQgGBAAAAIKg3Xzyh5kGDBIpAyKg2x3wAAA=", + "text_start": 1077379072, + "data": "ZCvKP5aNN0B7jjdAPJM3QAaPN0CbjjdABo83QGWPN0AykDdApZA3QE2QN0AhjTdAyI83QCSQN0CIjzdAx5A3QLKPN0DHkDdAaY43QMaON0AGjzdAZY83QIGON0BijTdAiJE3QAKTN0A+jDdAIpM3QD6MN0A+jDdAPow3QD6MN0A+jDdAPow3QD6MN0A+jDdAIpE3QD6MN0AdkjdAApM3QAQInwAAAAAAAAAYAQQIBQAAAAAAAAAIAQQIBgAAAAAAAAAAAQQIIQAAAAAAIAAAEQQI3AAAAAAAIAAAEQQIDAAAAAAAIAAAAQQIEgAAAAAAIAAAESAoDAAQAQAA", + "data_start": 1070279668 +} \ No newline at end of file diff --git a/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32s3beta2.json b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32s3beta2.json new file mode 100644 index 0000000..8621a93 --- /dev/null +++ b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_32s3beta2.json @@ -0,0 +1,7 @@ +{ + "entry": 1077381176, + "text": "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", + "text_start": 1077379072, + "data": "ZCvKP4KLN0APjDdA15A3QJqMN0AvjDdAmow3QPmMN0DGjTdAOY43QOGNN0ANizdAXI03QLiNN0AcjTdAXI43QEaNN0BcjjdA/Ys3QFqMN0CajDdA+Yw3QBWMN0BOizdAHI83QJuQN0AsijdAvZA3QCyKN0AsijdALIo3QCyKN0AsijdALIo3QCyKN0AsijdAto43QCyKN0CyjzdAm5A3QA==", + "data_start": 1070279668 +} \ No newline at end of file diff --git a/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_8266.json b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_8266.json new file mode 100644 index 0000000..8d42b24 --- /dev/null +++ b/installer/bin/esptool/esptool/targets/stub_flasher/stub_flasher_8266.json @@ -0,0 +1,7 @@ +{ + "entry": 1074843652, + "text": "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", + "text_start": 1074843648, + "data": "CIH+PwUFBAACAwcAAwMLALnXEEDv1xBAHdgQQLrYEEBo5xBAHtkQQHTZEEDA2RBAaOcQQILaEED/2hBAwNsQQGjnEEBo5xBAWNwQQGjnEEA33xBAAOAQQDvgEEBo5xBAaOcQQNfgEEBo5xBAv+EQQGXiEECj4xBAY+QQQDTlEEBo5xBAaOcQQGjnEEBo5xBAYuYQQGjnEEBX5xBAkN0QQI/YEECm5RBAq9oQQPzZEEBo5xBA7OYQQDHnEEBo5xBAaOcQQGjnEEBo5xBAaOcQQGjnEEBo5xBAaOcQQCLaEEBf2hBAvuUQQAEAAAACAAAAAwAAAAQAAAAFAAAABwAAAAkAAAANAAAAEQAAABkAAAAhAAAAMQAAAEEAAABhAAAAgQAAAMEAAAABAQAAgQEAAAECAAABAwAAAQQAAAEGAAABCAAAAQwAAAEQAAABGAAAASAAAAEwAAABQAAAAWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAgAAAAIAAAADAAAAAwAAAAQAAAAEAAAABQAAAAUAAAAGAAAABgAAAAcAAAAHAAAACAAAAAgAAAAJAAAACQAAAAoAAAAKAAAACwAAAAsAAAAMAAAADAAAAA0AAAANAAAAAAAAAAAAAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAACgAAAAsAAAANAAAADwAAABEAAAATAAAAFwAAABsAAAAfAAAAIwAAACsAAAAzAAAAOwAAAEMAAABTAAAAYwAAAHMAAACDAAAAowAAAMMAAADjAAAAAgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAEAAAABAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADAAAAAwAAAAMAAAAEAAAABAAAAAQAAAAEAAAABQAAAAUAAAAFAAAABQAAAAAAAAAAAAAAAAAAABAREgAIBwkGCgULBAwDDQIOAQ8AAQEAAAEAAAAEAAAA", + "data_start": 1073720488 +} \ No newline at end of file diff --git a/installer/bin/esptool/esptool/util.py b/installer/bin/esptool/esptool/util.py new file mode 100644 index 0000000..66d9bd4 --- /dev/null +++ b/installer/bin/esptool/esptool/util.py @@ -0,0 +1,174 @@ +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import re +import struct +import sys + + +def byte(bitstr, index): + return bitstr[index] + + +def mask_to_shift(mask): + """Return the index of the least significant bit in the mask""" + shift = 0 + while mask & 0x1 == 0: + shift += 1 + mask >>= 1 + return shift + + +def div_roundup(a, b): + """Return a/b rounded up to nearest integer, + equivalent result to int(math.ceil(float(int(a)) / float(int(b))), only + without possible floating point accuracy errors. + """ + return (int(a) + int(b) - 1) // int(b) + + +def flash_size_bytes(size): + """Given a flash size of the type passed in args.flash_size + (ie 512KB or 1MB) then return the size in bytes. + """ + if "MB" in size: + return int(size[: size.index("MB")]) * 1024 * 1024 + elif "KB" in size: + return int(size[: size.index("KB")]) * 1024 + else: + raise FatalError("Unknown size %s" % size) + + +def hexify(s, uppercase=True): + format_str = "%02X" if uppercase else "%02x" + return "".join(format_str % c for c in s) + + +def pad_to(data, alignment, pad_character=b"\xFF"): + """Pad to the next alignment boundary""" + pad_mod = len(data) % alignment + if pad_mod != 0: + data += pad_character * (alignment - pad_mod) + return data + + +def print_overwrite(message, last_line=False): + """Print a message, overwriting the currently printed line. + + If last_line is False, don't append a newline at the end + (expecting another subsequent call will overwrite this one.) + + After a sequence of calls with last_line=False, call once with last_line=True. + + If output is not a TTY (for example redirected a pipe), + no overwriting happens and this function is the same as print(). + """ + if sys.stdout.isatty(): + print("\r%s" % message, end="\n" if last_line else "") + else: + print(message) + + +def expand_chip_name(chip_name): + """Change chip name to official form, e.g. `esp32s3beta2` -> `ESP32-S3(beta2)`""" + # Put "-" after "esp32" + chip_name = re.sub(r"(esp32)(?!$)", r"\1-", chip_name) + # Put "()" around "betaN" + chip_name = re.sub(r"(beta\d*)", r"(\1)", chip_name) + # Uppercase everything before "(betaN)" + chip_name = re.sub(r"^[^\(]+", lambda x: x.group(0).upper(), chip_name) + return chip_name + + +def strip_chip_name(chip_name): + """Strip chip name to normalized form, e.g. `ESP32-S3(beta2)` -> `esp32s3beta2`""" + return re.sub(r"[-()]", "", chip_name.lower()) + + +class FatalError(RuntimeError): + """ + Wrapper class for runtime errors that aren't caused by internal bugs, but by + ESP ROM responses or input content. + """ + + def __init__(self, message): + RuntimeError.__init__(self, message) + + @staticmethod + def WithResult(message, result): + """ + Return a fatal error object that appends the hex values of + 'result' and its meaning as a string formatted argument. + """ + + err_defs = { + # ROM error codes + 0x101: "Out of memory", + 0x102: "Invalid argument", + 0x103: "Invalid state", + 0x104: "Invalid size", + 0x105: "Requested resource not found", + 0x106: "Operation or feature not supported", + 0x107: "Operation timed out", + 0x108: "Received response was invalid", + 0x109: "CRC or checksum was invalid", + 0x10A: "Version was invalid", + 0x10B: "MAC address was invalid", + # Flasher stub error codes + 0xC000: "Bad data length", + 0xC100: "Bad data checksum", + 0xC200: "Bad blocksize", + 0xC300: "Invalid command", + 0xC400: "Failed SPI operation", + 0xC500: "Failed SPI unlock", + 0xC600: "Not in flash mode", + 0xC700: "Inflate error", + 0xC800: "Not enough data", + 0xC900: "Too much data", + 0xFF00: "Command not implemented", + } + + err_code = struct.unpack(">H", result[:2]) + message += " (result was {}: {})".format( + hexify(result), err_defs.get(err_code[0], "Unknown result") + ) + return FatalError(message) + + +class NotImplementedInROMError(FatalError): + """ + Wrapper class for the error thrown when a particular ESP bootloader function + is not implemented in the ROM bootloader. + """ + + def __init__(self, bootloader, func): + FatalError.__init__( + self, + "%s ROM does not support function %s." + % (bootloader.CHIP_NAME, func.__name__), + ) + + +class NotSupportedError(FatalError): + def __init__(self, esp, function_name): + FatalError.__init__( + self, + "Function %s is not supported for %s." % (function_name, esp.CHIP_NAME), + ) + + +class UnsupportedCommandError(RuntimeError): + """ + Wrapper class for when ROM loader returns an invalid command response. + + Usually this indicates the loader is running in Secure Download Mode. + """ + + def __init__(self, esp, op): + if esp.secure_download_mode: + msg = "This command (0x%x) is not supported in Secure Download Mode" % op + else: + msg = "Invalid (unsupported) command 0x%x" % op + RuntimeError.__init__(self, msg) diff --git a/installer/install_upgrade.bat b/installer/install_upgrade.bat new file mode 100644 index 0000000..bc95ac2 --- /dev/null +++ b/installer/install_upgrade.bat @@ -0,0 +1,10 @@ +@echo off +echo Your firmware will be upgraded without factory reset. +echo IF THIS IS YOUR FIRST FLASH ON THIS BOARD PLEASE USE install_with_factory_reset.bat INSTEAD! + +set /p port="Enter COM port (for example COM5): " + +python.exe bin/esptool/esptool.py --chip esp32 --port "%port%" --baud 460800 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size 4MB 0x1000 firmware/bootloader.bin 0x8000 firmware/partitions.bin 0xe000 firmware/boot_app0.bin 0x10000 firmware/firmware.bin + +echo "Firmware flashed" +pause \ No newline at end of file diff --git a/installer/install_upgrade.sh b/installer/install_upgrade.sh new file mode 100644 index 0000000..81a238e --- /dev/null +++ b/installer/install_upgrade.sh @@ -0,0 +1,9 @@ +#!/bin/bash +echo "Your firmware will be upgraded without factory reset." +echo "IF THIS IS YOUR FIRST FLASH ON THIS BOARD PLEASE USE install_with_factory_reset.sh INSTEAD!" + +read -p "Enter COM port (for example /dev/ttyS5): " port + +python3 ./bin/esptool/esptool.py --chip esp32 --port "$port" --baud 460800 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size 4MB 0x1000 firmware/bootloader.bin 0x8000 firmware/partitions.bin 0xe000 firmware/boot_app0.bin 0x10000 firmware/firmware.bin + +echo "Firmware flashed" \ No newline at end of file diff --git a/installer/install_with_factory_reset.bat b/installer/install_with_factory_reset.bat new file mode 100644 index 0000000..03f318c --- /dev/null +++ b/installer/install_with_factory_reset.bat @@ -0,0 +1,14 @@ +@echo off +echo Your current configuration will be LOST!!! +echo Your current configuration will be LOST!!! +echo Your current configuration will be LOST!!! +echo If you already have this board flashed with our firmware please use install_upgrade.bat instead! + +set /p port="Enter COM port (for example COM5): " + +python.exe bin/esptool/esptool.py --chip esp32 --port "%port%" --baud 460800 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size 4MB 2686976 firmware/spiffs.bin + +python.exe bin/esptool/esptool.py --chip esp32 --port "%port%" --baud 460800 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size 4MB 0x1000 firmware/bootloader.bin 0x8000 firmware/partitions.bin 0xe000 firmware/boot_app0.bin 0x10000 firmware/firmware.bin + +echo "Firmware flashed" +pause \ No newline at end of file diff --git a/installer/install_with_factory_reset.sh b/installer/install_with_factory_reset.sh new file mode 100644 index 0000000..e69de29 diff --git a/platformio.ini b/platformio.ini index 6e01e24..f3bbf1c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,6 +15,12 @@ default_envs = ttgo-lora32-v21 platform = espressif32 @ 6.3.1 framework = arduino monitor_speed = 115200 +board_build.embed_files = + data_embed/index.html.gz + data_embed/style.css.gz + data_embed/script.js.gz + data_embed/bootstrap.css.gz + data_embed/bootstrap.js.gz lib_deps = bblanchon/ArduinoJson@^6.20.2 sandeepmistry/LoRa@^0.8.0 @@ -28,9 +34,11 @@ lib_deps = jgromes/RadioLib @ 6.1.0 lewisxhe/XPowersLib@^0.1.8 ayushsharma82/ElegantOTA@^3.1.0 - ottowinter/ESPAsyncWebServer-esphome@ 3.0.0 - esphome/AsyncTCP-esphome@ 2.1.1 + ottowinter/ESPAsyncWebServer-esphome@3.0.0 + esphome/AsyncTCP-esphome@2.1.1 +extra_scripts = + pre:tools/compress.py [env:ttgo-lora32-v21] board = ttgo-lora32-v21 @@ -67,4 +75,11 @@ build_flags = -Werror -Wall -DTTGO_T_Beam_V1_0_SX1268 -DELEGANTOTA_USE_ASYNC_WEB [env:ttgo-t-beam-v1_2_SX1262] board = ttgo-t-beam -build_flags = -Werror -Wall -DTTGO_T_Beam_V1_2_SX1262 -DELEGANTOTA_USE_ASYNC_WEBSERVER=1 \ No newline at end of file +build_flags = -Werror -Wall -DTTGO_T_Beam_V1_2_SX1262 -DELEGANTOTA_USE_ASYNC_WEBSERVER=1 + +[env:heltec_wireless_stick_lite] +platform = espressif32 +board = heltec_wireless_stick_lite +board_build.mcu = esp32c3 +board_build.f_cpu = 240000000L +build_flags = -Werror -Wall -DHELTEC_CT62 -DELEGANTOTA_USE_ASYNC_WEBSERVER=1 \ No newline at end of file diff --git a/src/LoRa_APRS_iGate.cpp b/src/LoRa_APRS_iGate.cpp index 8d4d6d8..6d03f0d 100644 --- a/src/LoRa_APRS_iGate.cpp +++ b/src/LoRa_APRS_iGate.cpp @@ -13,6 +13,7 @@ #include "digi_utils.h" #include "gps_utils.h" #include "bme_utils.h" +#include "web_utils.h" #include "display.h" #include "utils.h" @@ -20,7 +21,7 @@ Configuration Config; WiFiClient espClient; -String versionDate = "2024.01.28"; +String versionDate = "2024.02.23"; int myWiFiAPIndex = 0; int myWiFiAPSize = Config.wifiAPs.size(); WiFi_AP *currentWiFi = &Config.wifiAPs[myWiFiAPIndex]; @@ -34,8 +35,12 @@ uint32_t lastScreenOn = millis(); uint32_t lastWiFiCheck = 0; bool WiFiConnect = true; +bool WiFiConnected = false; int lastStationModeState = 1; +bool WiFiAutoAPStarted = false; +long WiFiAutoAPTime = false; + String batteryVoltage; std::vector lastHeardStation; @@ -46,55 +51,63 @@ std::vector packetBuffer_temp; String firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, iGateBeaconPacket, iGateLoRaBeaconPacket; void setup() { - Serial.begin(115200); - #if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) - pinMode(batteryPin, INPUT); - #endif - #if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa) - pinMode(internalLedPin, OUTPUT); - #endif - if (Config.externalVoltageMeasurement) { - pinMode(Config.externalVoltagePin, INPUT); - } - #if defined(TTGO_T_Beam_V1_0) || defined(TTGO_T_Beam_V1_0_SX1268) || defined(TTGO_T_Beam_V1_2) || defined(TTGO_T_Beam_V1_2_SX1262) - POWER_Utils::setup(); - #endif - delay(1000); - Utils::setupDisplay(); - WIFI_Utils::setup(); - LoRa_Utils::setup(); - Utils::validateDigiFreqs(); - iGateBeaconPacket = GPS_Utils::generateBeacon(); - iGateLoRaBeaconPacket = GPS_Utils::generateiGateLoRaBeacon(); - Utils::startServer(); - SYSLOG_Utils::setup(); - BME_Utils::setup(); + Serial.begin(115200); + + #if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) + pinMode(batteryPin, INPUT); + #endif + #if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa) + pinMode(internalLedPin, OUTPUT); + #endif + if (Config.externalVoltageMeasurement) { + pinMode(Config.externalVoltagePin, INPUT); + } + #if defined(TTGO_T_Beam_V1_0) || defined(TTGO_T_Beam_V1_0_SX1268) || defined(TTGO_T_Beam_V1_2) || defined(TTGO_T_Beam_V1_2_SX1262) + POWER_Utils::setup(); + #endif + delay(1000); + Utils::setupDisplay(); + WIFI_Utils::setup(); + LoRa_Utils::setup(); + Utils::validateDigiFreqs(); + iGateBeaconPacket = GPS_Utils::generateBeacon(); + iGateLoRaBeaconPacket = GPS_Utils::generateiGateLoRaBeacon(); + SYSLOG_Utils::setup(); + BME_Utils::setup(); + WEB_Utils::setup(); } void loop() { - if (stationMode==1 || stationMode==2 ) { // iGate (1 Only Rx / 2 Rx+Tx) - WIFI_Utils::checkWiFi(); - if (!espClient.connected()) { - APRS_IS_Utils::connect(); + WEB_Utils::loop(); + + WIFI_Utils::checkIfAutoAPShouldPowerOff(); + + if (!WiFiConnected) { + thirdLine = Utils::getLocalIP(); } - APRS_IS_Utils::loop(); - } else if (stationMode==3 || stationMode==4) { // DigiRepeater (3 RxFreq=TxFreq / 4 RxFreq!=TxFreq) - DIGI_Utils::loop(); - } else if (stationMode==5) { // iGate when WiFi and APRS available , DigiRepeater when not (RxFreq=TxFreq) - Utils::checkWiFiInterval(); - if (WiFi.status() == WL_CONNECTED) { // iGate Mode - thirdLine = Utils::getLocalIP(); - if (!espClient.connected()) { - APRS_IS_Utils::connect(); - } - if (lastStationModeState == 1) { - iGateBeaconPacket = GPS_Utils::generateBeacon(); - lastStationModeState = 0; - Utils::startServer(); - } - APRS_IS_Utils::loop(); - } else { // DigiRepeater Mode - DIGI_Utils::loop(); + + if (stationMode==1 || stationMode==2 ) { // iGate (1 Only Rx / 2 Rx+Tx) + WIFI_Utils::checkWiFi(); + if (!espClient.connected()) { + APRS_IS_Utils::connect(); + } + APRS_IS_Utils::loop(); + } else if (stationMode==3 || stationMode==4) { // DigiRepeater (3 RxFreq=TxFreq / 4 RxFreq!=TxFreq) + DIGI_Utils::loop(); + } else if (stationMode==5) { // iGate when WiFi and APRS available , DigiRepeater when not (RxFreq=TxFreq) + Utils::checkWiFiInterval(); + if (WiFi.status() == WL_CONNECTED) { // iGate Mode + thirdLine = Utils::getLocalIP(); + if (!espClient.connected()) { + APRS_IS_Utils::connect(); + } + if (lastStationModeState == 1) { + iGateBeaconPacket = GPS_Utils::generateBeacon(); + lastStationModeState = 0; + } + APRS_IS_Utils::loop(); + } else { // DigiRepeater Mode + DIGI_Utils::loop(); + } } - } } \ No newline at end of file diff --git a/src/aprs_is_utils.cpp b/src/aprs_is_utils.cpp index c672f1b..23e506e 100644 --- a/src/aprs_is_utils.cpp +++ b/src/aprs_is_utils.cpp @@ -26,213 +26,214 @@ extern String seventhLine; namespace APRS_IS_Utils { - void upload(String line) { - espClient.print(line + "\r\n"); - } - - void connect(){ - int count = 0; - String aprsauth; - Serial.print("Connecting to APRS-IS ... "); - while (!espClient.connect(Config.aprs_is.server.c_str(), Config.aprs_is.port) && count < 20) { - Serial.println("Didn't connect with server..."); - delay(1000); - espClient.stop(); - espClient.flush(); - Serial.println("Run client.stop"); - Serial.println("Trying to connect with Server: " + String(Config.aprs_is.server) + " AprsServerPort: " + String(Config.aprs_is.port)); - count++; - Serial.println("Try: " + String(count)); + void upload(String line) { + espClient.print(line + "\r\n"); } - if (count == 20) { - Serial.println("Tried: " + String(count) + " FAILED!"); - } else { - Serial.println("Connected!\n(Server: " + String(Config.aprs_is.server) + " / Port: " + String(Config.aprs_is.port) +")"); - aprsauth = "user " + Config.callsign + " pass " + Config.aprs_is.passcode + " vers CA2RXU_LoRa_iGate 1.2 filter t/m/" + Config.callsign + "/" + (String)Config.aprs_is.reportingDistance;// + "\r\n"; - upload(aprsauth); - delay(200); - } - } - void checkStatus() { - String wifiState, aprsisState; - if (WiFi.status() == WL_CONNECTED) { - wifiState = "OK"; - } else { - wifiState = "--"; - if (!Config.display.alwaysOn) { - display_toggle(true); - } - lastScreenOn = millis(); - } - if (espClient.connected()) { - aprsisState = "OK"; - } else { - aprsisState = "--"; - if (!Config.display.alwaysOn) { - display_toggle(true); - } - lastScreenOn = millis(); - } - secondLine = "WiFi: " + wifiState + "/ APRS-IS: " + aprsisState; - } - - String createPacket(String packet) { - if (stationMode>1) { - return packet.substring(3, packet.indexOf(":")) + ",qAR," + Config.callsign + packet.substring(packet.indexOf(":"));// + "\n"; - } else { - return packet.substring(3, packet.indexOf(":")) + ",qAO," + Config.callsign + packet.substring(packet.indexOf(":"));// + "\n"; - } - } - - void processLoRaPacket(String packet) { - bool queryMessage = false; - String aprsPacket, Sender, AddresseeAndMessage, Addressee, ackMessage, receivedMessage; - if (packet != "") { - #ifdef TextSerialOutputForApp - Serial.println(packet.substring(3)); - #else - Serial.print("Received Lora Packet : " + String(packet)); - #endif - if ((packet.substring(0,3) == "\x3c\xff\x01") && (packet.indexOf("TCPIP") == -1) && (packet.indexOf("NOGATE") == -1) && (packet.indexOf("RFONLY") == -1)) { - #ifndef TextSerialOutputForApp - Serial.print(" ---> APRS LoRa Packet!"); - #endif - Sender = packet.substring(3,packet.indexOf(">")); - if (Sender != Config.callsign) { // avoid listening yourself by digirepeating - AddresseeAndMessage = packet.substring(packet.indexOf("::")+2); - Addressee = AddresseeAndMessage.substring(0,AddresseeAndMessage.indexOf(":")); - Addressee.trim(); - - if (stationMode!=1 && Config.igateRepeatsLoRaPackets && Addressee != Config.callsign) { // if its not for me - String digiRepeatedPacket = DIGI_Utils::generateDigiRepeatedPacket(packet.substring(3), Config.callsign); - if (digiRepeatedPacket != "X") { - delay(500); - LoRa_Utils::sendNewPacket("APRS", digiRepeatedPacket); - } - } - - if (packet.indexOf("::") > 10 && Addressee == Config.callsign) { // its a message for me! - if (AddresseeAndMessage.indexOf("{")>0) { // ack? - ackMessage = "ack" + AddresseeAndMessage.substring(AddresseeAndMessage.indexOf("{")+1); - ackMessage.trim(); - delay(4000); - //Serial.println(ackMessage); - for(int i = Sender.length(); i < 9; i++) { - Sender += ' '; - } - LoRa_Utils::sendNewPacket("APRS", Config.callsign + ">APLRG1,RFONLY,WIDE1-1::" + Sender + ":" + ackMessage); - receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1, AddresseeAndMessage.indexOf("{")); - } else { - receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1); - } - if (receivedMessage.indexOf("?") == 0) { - queryMessage = true; - delay(2000); - if (!Config.display.alwaysOn) { - display_toggle(true); - } - LoRa_Utils::sendNewPacket("APRS", QUERY_Utils::process(receivedMessage, Sender, "LoRa")); - lastScreenOn = millis(); - show_display(firstLine, secondLine, thirdLine, fourthLine, fifthLine, "Callsign = " + Sender, "TYPE --> QUERY", 0); - } - } - if (!queryMessage) { - aprsPacket = createPacket(packet); - if (!Config.display.alwaysOn) { - display_toggle(true); - } - lastScreenOn = millis(); - upload(aprsPacket); - #ifndef TextSerialOutputForApp - Serial.println(" ---> Uploaded to APRS-IS"); - #endif - STATION_Utils::updateLastHeard(Sender); - Utils::typeOfPacket(aprsPacket, "LoRa-APRS"); - show_display(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0); - } - } - } else { - #ifndef TextSerialOutputForApp - Serial.println(" ---> LoRa Packet Ignored (first 3 bytes or TCPIP/NOGATE/RFONLY)\n"); - #endif - } - } - } - - void processAPRSISPacket(String packet) { - String Sender, AddresseeAndMessage, Addressee, receivedMessage; - if (!packet.startsWith("#")){ - if (packet.indexOf("::")>0) { - Sender = packet.substring(0,packet.indexOf(">")); - AddresseeAndMessage = packet.substring(packet.indexOf("::")+2); - Addressee = AddresseeAndMessage.substring(0, AddresseeAndMessage.indexOf(":")); - Addressee.trim(); - if (Addressee == Config.callsign) { // its for me! - if (AddresseeAndMessage.indexOf("{")>0) { // ack? - String ackMessage = "ack" + AddresseeAndMessage.substring(AddresseeAndMessage.indexOf("{")+1); - ackMessage.trim(); - delay(4000); - //Serial.println(ackMessage); - for(int i = Sender.length(); i < 9; i++) { - Sender += ' '; - } - String ackPacket = Config.callsign + ">APLRG1,TCPIP,qAC::" + Sender + ":" + ackMessage;// + "\n"; - upload(ackPacket); - receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1, AddresseeAndMessage.indexOf("{")); - } else { - receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1); - } - if (receivedMessage.indexOf("?") == 0) { - #ifndef TextSerialOutputForApp - Serial.println("Received Query APRS-IS : " + packet); - #endif - String queryAnswer = QUERY_Utils::process(receivedMessage, Sender, "APRSIS"); - //Serial.println("---> QUERY Answer : " + queryAnswer.substring(0,queryAnswer.indexOf("\n"))); - if (!Config.display.alwaysOn) { - display_toggle(true); - } - lastScreenOn = millis(); - delay(500); - upload(queryAnswer); - SYSLOG_Utils::log("APRSIS Tx", queryAnswer,0,0,0); - fifthLine = "APRS-IS ----> APRS-IS"; - sixthLine = Config.callsign; - for (int j=sixthLine.length();j<9;j++) { - sixthLine += " "; - } - sixthLine += "> " + Sender; - seventhLine = "QUERY = " + receivedMessage; - } - } else { - #ifndef TextSerialOutputForApp - Serial.print("Received from APRS-IS : " + packet); - #endif - if ((stationMode==2 || stationMode==5) && STATION_Utils::wasHeard(Addressee)) { - LoRa_Utils::sendNewPacket("APRS", LoRa_Utils::generatePacket(packet)); - display_toggle(true); - lastScreenOn = millis(); - Utils::typeOfPacket(packet, "APRS-LoRa"); - } + void connect(){ + int count = 0; + String aprsauth; + Serial.print("Connecting to APRS-IS ... "); + while (!espClient.connect(Config.aprs_is.server.c_str(), Config.aprs_is.port) && count < 20) { + Serial.println("Didn't connect with server..."); + delay(1000); + espClient.stop(); + espClient.flush(); + Serial.println("Run client.stop"); + Serial.println("Trying to connect with Server: " + String(Config.aprs_is.server) + " AprsServerPort: " + String(Config.aprs_is.port)); + count++; + Serial.println("Try: " + String(count)); + } + if (count == 20) { + Serial.println("Tried: " + String(count) + " FAILED!"); + } else { + Serial.println("Connected!\n(Server: " + String(Config.aprs_is.server) + " / Port: " + String(Config.aprs_is.port) +")"); + aprsauth = "user " + Config.callsign + " pass " + Config.aprs_is.passcode + " vers CA2RXU_LoRa_iGate 1.3 filter t/m/" + Config.callsign + "/" + (String)Config.aprs_is.reportingDistance;// + "\r\n"; + upload(aprsauth); + delay(200); } - show_display(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0); - } } - } - void loop() { - checkStatus(); - show_display(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0); - while (espClient.connected()) { - Utils::checkDisplayInterval(); - Utils::checkBeaconInterval(); - processLoRaPacket(LoRa_Utils::receivePacket()); - if (espClient.available()) { - String aprsisPacket; - aprsisPacket.concat(espClient.readStringUntil('\r')); - processAPRSISPacket(aprsisPacket); - } - ElegantOTA.loop(); + void checkStatus() { + String wifiState, aprsisState; + if (WiFi.status() == WL_CONNECTED) { + wifiState = "OK"; + } else { + wifiState = "AP"; + if (!Config.display.alwaysOn) { + display_toggle(true); + } + lastScreenOn = millis(); + } + if (espClient.connected()) { + aprsisState = "OK"; + } else { + aprsisState = "--"; + if (!Config.display.alwaysOn) { + display_toggle(true); + } + lastScreenOn = millis(); + } + secondLine = "WiFi: " + wifiState + "/ APRS-IS: " + aprsisState; } - } + + String createPacket(String packet) { + if (stationMode>1) { + return packet.substring(3, packet.indexOf(":")) + ",qAR," + Config.callsign + packet.substring(packet.indexOf(":"));// + "\n"; + } else { + return packet.substring(3, packet.indexOf(":")) + ",qAO," + Config.callsign + packet.substring(packet.indexOf(":"));// + "\n"; + } + } + + void processLoRaPacket(String packet) { + bool queryMessage = false; + String aprsPacket, Sender, AddresseeAndMessage, Addressee, ackMessage, receivedMessage; + if (packet != "") { + #ifdef TextSerialOutputForApp + Serial.println(packet.substring(3)); + #else + Serial.print("Received Lora Packet : " + String(packet)); + #endif + if ((packet.substring(0,3) == "\x3c\xff\x01") && (packet.indexOf("TCPIP") == -1) && (packet.indexOf("NOGATE") == -1) && (packet.indexOf("RFONLY") == -1)) { + #ifndef TextSerialOutputForApp + Serial.print(" ---> APRS LoRa Packet!"); + #endif + Sender = packet.substring(3,packet.indexOf(">")); + if (Sender != Config.callsign) { // avoid listening yourself by digirepeating + AddresseeAndMessage = packet.substring(packet.indexOf("::")+2); + Addressee = AddresseeAndMessage.substring(0,AddresseeAndMessage.indexOf(":")); + Addressee.trim(); + + if (stationMode!=1 && Config.igateRepeatsLoRaPackets && Addressee != Config.callsign) { // if its not for me + String digiRepeatedPacket = DIGI_Utils::generateDigiRepeatedPacket(packet.substring(3), Config.callsign); + if (digiRepeatedPacket != "X") { + delay(500); + LoRa_Utils::sendNewPacket("APRS", digiRepeatedPacket); + } + } + + if (packet.indexOf("::") > 10 && Addressee == Config.callsign) { // its a message for me! + if (AddresseeAndMessage.indexOf("{")>0) { // ack? + ackMessage = "ack" + AddresseeAndMessage.substring(AddresseeAndMessage.indexOf("{")+1); + ackMessage.trim(); + delay(4000); + //Serial.println(ackMessage); + for(int i = Sender.length(); i < 9; i++) { + Sender += ' '; + } + LoRa_Utils::sendNewPacket("APRS", Config.callsign + ">APLRG1,RFONLY,WIDE1-1::" + Sender + ":" + ackMessage); + receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1, AddresseeAndMessage.indexOf("{")); + } else { + receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1); + } + if (receivedMessage.indexOf("?") == 0) { + queryMessage = true; + delay(2000); + if (!Config.display.alwaysOn) { + display_toggle(true); + } + LoRa_Utils::sendNewPacket("APRS", QUERY_Utils::process(receivedMessage, Sender, "LoRa")); + lastScreenOn = millis(); + show_display(firstLine, secondLine, thirdLine, fourthLine, fifthLine, "Callsign = " + Sender, "TYPE --> QUERY", 0); + } + } + if (!queryMessage) { + aprsPacket = createPacket(packet); + if (!Config.display.alwaysOn) { + display_toggle(true); + } + lastScreenOn = millis(); + upload(aprsPacket); + #ifndef TextSerialOutputForApp + Serial.println(" ---> Uploaded to APRS-IS"); + #endif + STATION_Utils::updateLastHeard(Sender); + Utils::typeOfPacket(aprsPacket, "LoRa-APRS"); + show_display(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0); + } + } + } else { + #ifndef TextSerialOutputForApp + Serial.println(" ---> LoRa Packet Ignored (first 3 bytes or TCPIP/NOGATE/RFONLY)\n"); + #endif + } + } + } + + void processAPRSISPacket(String packet) { + String Sender, AddresseeAndMessage, Addressee, receivedMessage; + if (!packet.startsWith("#")){ + if (packet.indexOf("::")>0) { + Sender = packet.substring(0,packet.indexOf(">")); + AddresseeAndMessage = packet.substring(packet.indexOf("::")+2); + Addressee = AddresseeAndMessage.substring(0, AddresseeAndMessage.indexOf(":")); + Addressee.trim(); + if (Addressee == Config.callsign) { // its for me! + if (AddresseeAndMessage.indexOf("{")>0) { // ack? + String ackMessage = "ack" + AddresseeAndMessage.substring(AddresseeAndMessage.indexOf("{")+1); + ackMessage.trim(); + delay(4000); + //Serial.println(ackMessage); + for(int i = Sender.length(); i < 9; i++) { + Sender += ' '; + } + String ackPacket = Config.callsign + ">APLRG1,TCPIP,qAC::" + Sender + ":" + ackMessage;// + "\n"; + upload(ackPacket); + receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1, AddresseeAndMessage.indexOf("{")); + } else { + receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1); + } + if (receivedMessage.indexOf("?") == 0) { + #ifndef TextSerialOutputForApp + Serial.println("Received Query APRS-IS : " + packet); + #endif + String queryAnswer = QUERY_Utils::process(receivedMessage, Sender, "APRSIS"); + //Serial.println("---> QUERY Answer : " + queryAnswer.substring(0,queryAnswer.indexOf("\n"))); + if (!Config.display.alwaysOn) { + display_toggle(true); + } + lastScreenOn = millis(); + delay(500); + upload(queryAnswer); + SYSLOG_Utils::log("APRSIS Tx", queryAnswer,0,0,0); + fifthLine = "APRS-IS ----> APRS-IS"; + sixthLine = Config.callsign; + for (int j=sixthLine.length();j<9;j++) { + sixthLine += " "; + } + sixthLine += "> " + Sender; + seventhLine = "QUERY = " + receivedMessage; + } + } else { + #ifndef TextSerialOutputForApp + Serial.print("Received from APRS-IS : " + packet); + #endif + if ((stationMode==2 || stationMode==5) && STATION_Utils::wasHeard(Addressee)) { + LoRa_Utils::sendNewPacket("APRS", LoRa_Utils::generatePacket(packet)); + display_toggle(true); + lastScreenOn = millis(); + Utils::typeOfPacket(packet, "APRS-LoRa"); + } + } + show_display(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0); + } + } + } + + void loop() { + checkStatus(); + show_display(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0); + while (espClient.connected()) { + Utils::checkDisplayInterval(); + Utils::checkBeaconInterval(); + processLoRaPacket(LoRa_Utils::receivePacket()); + if (espClient.available()) { + String aprsisPacket; + aprsisPacket.concat(espClient.readStringUntil('\r')); + processAPRSISPacket(aprsisPacket); + } + ElegantOTA.loop(); + } + } + } \ No newline at end of file diff --git a/src/bme_utils.cpp b/src/bme_utils.cpp index 376562d..6547c08 100644 --- a/src/bme_utils.cpp +++ b/src/bme_utils.cpp @@ -13,144 +13,144 @@ extern String fifthLine; namespace BME_Utils { - #ifdef BME280Sensor - Adafruit_BME280 bme; - #endif - #ifdef BMP280Sensor - Adafruit_BMP280 bme; - #endif - #ifdef BME680Sensor - Adafruit_BME680 bme; - #endif - - void setup() { - if (Config.bme.active) { - bool status; - status = bme.begin(0x76); // Don't forget to join pins for righ direction on BME280! - if (!status) { - Serial.println("Could not find a valid BME280 or BMP280 sensor, check wiring!"); - show_display("ERROR", "", "BME/BMP sensor active", "but no sensor found..."); - while (1); // sacar esto para que quede pegado si no encuentra BME280 - } else { - #ifdef BME280Sensor - Serial.println("init : BME280 Module ... done!"); - #endif - #ifdef BMP280Sensor - Serial.println("init : BMP280 Module ... done!"); - #endif - #ifdef BME680Sensor - Serial.println("init : BME680 Module ... done!"); - #endif - } - } else { - Serial.println("(BME/BMP sensor not 'active' in 'igate_conf.json')"); - } - } - - String generateTempString(float bmeTemp) { - String strTemp; - strTemp = String((int)bmeTemp); - switch (strTemp.length()) { - case 1: - return "00" + strTemp; - break; - case 2: - return "0" + strTemp; - break; - case 3: - return strTemp; - break; - default: - return "-999"; - } - } - - String generateHumString(float bmeHum) { - String strHum; - strHum = String((int)bmeHum); - switch (strHum.length()) { - case 1: - return "0" + strHum; - break; - case 2: - return strHum; - break; - case 3: - if ((int)bmeHum == 100) { - return "00"; - } else { - return "-99"; - } - break; - default: - return "-99"; - } - } - - String generatePresString(float bmePress) { - String strPress; - strPress = String((int)bmePress); - switch (strPress.length()) { - case 1: - return "000" + strPress + "0"; - break; - case 2: - return "00" + strPress + "0"; - break; - case 3: - return "0" + strPress + "0"; - break; - case 4: - return strPress + "0"; - break; - case 5: - return strPress; - break; - default: - return "-99999"; - } - } - - String readDataSensor() { - String wx, tempStr, humStr, presStr; - float newHum; - - float newTemp = bme.readTemperature(); - #if defined(BME280Sensor) || defined(BME680Sensor) - newHum = bme.readHumidity(); + #ifdef BME280Sensor + Adafruit_BME280 bme; #endif #ifdef BMP280Sensor - newHum = 0; + Adafruit_BMP280 bme; #endif - float newPress = (bme.readPressure() / 100.0F); - #ifdef BME680Sensor - float newGas = bme.gas_resistance / 1000.0; // in Kilo ohms + Adafruit_BME680 bme; #endif - - //bme.readAltitude(SEALEVELPRESSURE_HPA) // this is for approximate Altitude Calculation. - - if (isnan(newTemp) || isnan(newHum) || isnan(newPress)) { - Serial.println("BME/BMP Module data failed"); - wx = ".../...g...t...r...p...P...h..b....."; - fifthLine = ""; - return wx; - } else { - tempStr = generateTempString((newTemp * 1.8) + 32); - #if defined(BME280Sensor) || defined(BME680Sensor) - humStr = generateHumString(newHum); - #endif - #ifdef BMP280Sensor - humStr = ".."; - #endif - presStr = generatePresString(newPress + (HEIGHT_CORRECTION/CORRECTION_FACTOR)); - fifthLine = "BME-> " + String(int(newTemp))+"C " + humStr + "% " + presStr.substring(0,4) + "hPa"; - wx = ".../...g...t" + tempStr + "r...p...P...h" + humStr + "b" + presStr; - #ifdef BME680Sensor - wx += "Gas: " + String(newGas) + "Kohms "; - #endif - return wx; + + void setup() { + if (Config.bme.active) { + bool status; + status = bme.begin(0x76); // Don't forget to join pins for righ direction on BME280! + if (!status) { + Serial.println("Could not find a valid BME280 or BMP280 sensor, check wiring!"); + show_display("ERROR", "", "BME/BMP sensor active", "but no sensor found..."); + while (1); // sacar esto para que quede pegado si no encuentra BME280 + } else { + #ifdef BME280Sensor + Serial.println("init : BME280 Module ... done!"); + #endif + #ifdef BMP280Sensor + Serial.println("init : BMP280 Module ... done!"); + #endif + #ifdef BME680Sensor + Serial.println("init : BME680 Module ... done!"); + #endif + } + } else { + Serial.println("(BME/BMP sensor not 'active' in 'igate_conf.json')"); + } + } + + String generateTempString(float bmeTemp) { + String strTemp; + strTemp = String((int)bmeTemp); + switch (strTemp.length()) { + case 1: + return "00" + strTemp; + break; + case 2: + return "0" + strTemp; + break; + case 3: + return strTemp; + break; + default: + return "-999"; + } + } + + String generateHumString(float bmeHum) { + String strHum; + strHum = String((int)bmeHum); + switch (strHum.length()) { + case 1: + return "0" + strHum; + break; + case 2: + return strHum; + break; + case 3: + if ((int)bmeHum == 100) { + return "00"; + } else { + return "-99"; + } + break; + default: + return "-99"; + } + } + + String generatePresString(float bmePress) { + String strPress; + strPress = String((int)bmePress); + switch (strPress.length()) { + case 1: + return "000" + strPress + "0"; + break; + case 2: + return "00" + strPress + "0"; + break; + case 3: + return "0" + strPress + "0"; + break; + case 4: + return strPress + "0"; + break; + case 5: + return strPress; + break; + default: + return "-99999"; + } + } + + String readDataSensor() { + String wx, tempStr, humStr, presStr; + float newHum; + + float newTemp = bme.readTemperature(); + #if defined(BME280Sensor) || defined(BME680Sensor) + newHum = bme.readHumidity(); + #endif + #ifdef BMP280Sensor + newHum = 0; + #endif + float newPress = (bme.readPressure() / 100.0F); + + #ifdef BME680Sensor + float newGas = bme.gas_resistance / 1000.0; // in Kilo ohms + #endif + + //bme.readAltitude(SEALEVELPRESSURE_HPA) // this is for approximate Altitude Calculation. + + if (isnan(newTemp) || isnan(newHum) || isnan(newPress)) { + Serial.println("BME/BMP Module data failed"); + wx = ".../...g...t...r...p...P...h..b....."; + fifthLine = ""; + return wx; + } else { + tempStr = generateTempString((newTemp * 1.8) + 32); + #if defined(BME280Sensor) || defined(BME680Sensor) + humStr = generateHumString(newHum); + #endif + #ifdef BMP280Sensor + humStr = ".."; + #endif + presStr = generatePresString(newPress + (HEIGHT_CORRECTION/CORRECTION_FACTOR)); + fifthLine = "BME-> " + String(int(newTemp))+"C " + humStr + "% " + presStr.substring(0,4) + "hPa"; + wx = ".../...g...t" + tempStr + "r...p...P...h" + humStr + "b" + presStr; + #ifdef BME680Sensor + wx += "Gas: " + String(newGas) + "Kohms "; + #endif + return wx; + } } - } } \ No newline at end of file diff --git a/src/configuration.cpp b/src/configuration.cpp index a073d34..0d95409 100644 --- a/src/configuration.cpp +++ b/src/configuration.cpp @@ -3,84 +3,229 @@ #include "configuration.h" #include "display.h" -Configuration::Configuration() { - _filePath = "/igate_conf.json"; - if (!SPIFFS.begin(false)) { - Serial.println("SPIFFS Mount Failed"); - return; - } - readFile(SPIFFS, _filePath.c_str()); -} -void Configuration::readFile(fs::FS &fs, const char *fileName) { + +void Configuration::writeFile() { + Serial.println("Saving config.."); + StaticJsonDocument<1536> data; - File configFile = fs.open(fileName, "r"); - DeserializationError error = deserializeJson(data, configFile); - if (error) { - Serial.println("Failed to read file, using default configuration"); + File configFile = SPIFFS.open("/igate_conf.json", "w"); + + if (wifiAPs[0].ssid != "") { // We don't want to save Auto AP empty SSID + for (int i = 0; i < wifiAPs.size(); i++) { + data["wifi"]["AP"][i]["ssid"] = wifiAPs[i].ssid; + data["wifi"]["AP"][i]["password"] = wifiAPs[i].password; + data["wifi"]["AP"][i]["latitude"] = wifiAPs[i].latitude; + data["wifi"]["AP"][i]["longitude"] = wifiAPs[i].longitude; + } } - JsonArray WiFiArray = data["wifi"]["AP"]; - for (int i = 0; i < WiFiArray.size(); i++) { - WiFi_AP wifiap; - wifiap.ssid = WiFiArray[i]["ssid"].as(); - wifiap.password = WiFiArray[i]["password"].as(); - wifiap.latitude = WiFiArray[i]["latitude"].as(); - wifiap.longitude = WiFiArray[i]["longitude"].as(); + data["wifi"]["autoAP"]["password"] = wifiAutoAP.password; + data["wifi"]["autoAP"]["powerOff"] = wifiAutoAP.powerOff; - wifiAPs.push_back(wifiap); - } + data["callsign"] = callsign; + data["stationMode"] = stationMode; + data["iGateComment"] = iGateComment; - callsign = data["callsign"].as(); - stationMode = data["stationMode"].as(); - iGateComment = data["iGateComment"].as(); - beaconInterval = data["other"]["beaconInterval"].as(); - igateSendsLoRaBeacons = data["other"]["igateSendsLoRaBeacons"].as(); - igateRepeatsLoRaPackets = data["other"]["igateRepeatsLoRaPackets"].as(); - rememberStationTime = data["other"]["rememberStationTime"].as(); - sendBatteryVoltage = data["other"]["sendBatteryVoltage"].as(); - externalVoltageMeasurement = data["other"]["externalVoltageMeasurement"].as(); - externalVoltagePin = data["other"]["externalVoltagePin"].as(); + data["other"]["beaconInterval"] = beaconInterval; + data["other"]["igateSendsLoRaBeacons"] = igateSendsLoRaBeacons; + data["other"]["igateRepeatsLoRaPackets"] = igateRepeatsLoRaPackets; + data["other"]["rememberStationTime"] = rememberStationTime; + data["other"]["sendBatteryVoltage"] = sendBatteryVoltage; + data["other"]["externalVoltageMeasurement"] = externalVoltageMeasurement; + data["other"]["externalVoltagePin"] = externalVoltagePin; - digi.comment = data["digi"]["comment"].as(); - digi.latitude = data["digi"]["latitude"].as(); - digi.longitude = data["digi"]["longitude"].as(); + data["digi"]["comment"] = digi.comment; + data["digi"]["latitude"] = digi.latitude; + data["digi"]["longitude"] = digi.longitude; - aprs_is.passcode = data["aprs_is"]["passcode"].as(); - aprs_is.server = data["aprs_is"]["server"].as(); - aprs_is.port = data["aprs_is"]["port"].as(); - aprs_is.reportingDistance = data["aprs_is"]["reportingDistance"].as(); + data["aprs_is"]["passcode"] = aprs_is.passcode; + data["aprs_is"]["server"] = aprs_is.server; + data["aprs_is"]["port"] = aprs_is.port; + data["aprs_is"]["reportingDistance"] = aprs_is.reportingDistance; - loramodule.iGateFreq = data["lora"]["iGateFreq"].as(); - loramodule.digirepeaterTxFreq = data["lora"]["digirepeaterTxFreq"].as(); - loramodule.digirepeaterRxFreq = data["lora"]["digirepeaterRxFreq"].as(); - loramodule.spreadingFactor = data["lora"]["spreadingFactor"].as(); - loramodule.signalBandwidth = data["lora"]["signalBandwidth"].as(); - loramodule.codingRate4 = data["lora"]["codingRate4"].as(); - loramodule.power = data["lora"]["power"].as(); + data["lora"]["iGateFreq"] = loramodule.iGateFreq; + data["lora"]["digirepeaterTxFreq"] = loramodule.digirepeaterTxFreq; + data["lora"]["digirepeaterRxFreq"] = loramodule.digirepeaterRxFreq; + data["lora"]["spreadingFactor"] = loramodule.spreadingFactor; + data["lora"]["signalBandwidth"] = loramodule.signalBandwidth; + data["lora"]["codingRate4"] = loramodule.codingRate4; + data["lora"]["power"] = loramodule.power; - display.alwaysOn = data["display"]["alwaysOn"].as(); - display.timeout = data["display"]["timeout"].as(); - display.turn180 = data["display"]["turn180"].as(); + data["display"]["alwaysOn"] = display.alwaysOn; + data["display"]["timeout"] = display.timeout; + data["display"]["turn180"] = display.turn180; - syslog.active = data["syslog"]["active"].as(); - syslog.server = data["syslog"]["server"].as(); - syslog.port = data["syslog"]["port"].as(); + data["syslog"]["active"] = syslog.active; + data["syslog"]["server"] = syslog.server; + data["syslog"]["port"] = syslog.port; - bme.active = data["bme"]["active"].as(); + data["bme"]["active"] = bme.active; - ota.username = data["ota"]["username"].as(); - ota.password = data["ota"]["password"].as(); + data["ota"]["username"] = ota.username; + data["ota"]["password"] = ota.password; + serializeJson(data, configFile); + configFile.close(); + + Serial.println("Config saved"); } -void Configuration::validateConfigFile(String currentBeaconCallsign) { - if (currentBeaconCallsign == "NOCALL-10") { - Serial.println("Change Callsign in /data/igate_conf.json"); - show_display("------- ERROR -------", "Change your settings", "on 'igate_conf.json'", "--> File System image", 0); - while (true) { - delay(1000); +bool Configuration::readFile() { + Serial.println("Reading config.."); + + File configFile = SPIFFS.open("/igate_conf.json", "r"); + + if (configFile) { + StaticJsonDocument<1536> data; + + DeserializationError error = deserializeJson(data, configFile); + if (error) { + Serial.println("Failed to read file, using default configuration"); + } + + JsonArray WiFiArray = data["wifi"]["AP"]; + for (int i = 0; i < WiFiArray.size(); i++) { + WiFi_AP wifiap; + wifiap.ssid = WiFiArray[i]["ssid"].as(); + wifiap.password = WiFiArray[i]["password"].as(); + wifiap.latitude = WiFiArray[i]["latitude"].as(); + wifiap.longitude = WiFiArray[i]["longitude"].as(); + + wifiAPs.push_back(wifiap); + } + + wifiAutoAP.password = data["wifi"]["autoAP"]["password"].as(); + wifiAutoAP.powerOff = data["wifi"]["autoAP"]["powerOff"].as(); + + callsign = data["callsign"].as(); + stationMode = data["stationMode"].as(); + iGateComment = data["iGateComment"].as(); + beaconInterval = data["other"]["beaconInterval"].as(); + igateSendsLoRaBeacons = data["other"]["igateSendsLoRaBeacons"].as(); + igateRepeatsLoRaPackets = data["other"]["igateRepeatsLoRaPackets"].as(); + rememberStationTime = data["other"]["rememberStationTime"].as(); + sendBatteryVoltage = data["other"]["sendBatteryVoltage"].as(); + externalVoltageMeasurement = data["other"]["externalVoltageMeasurement"].as(); + externalVoltagePin = data["other"]["externalVoltagePin"].as(); + + digi.comment = data["digi"]["comment"].as(); + digi.latitude = data["digi"]["latitude"].as(); + digi.longitude = data["digi"]["longitude"].as(); + + aprs_is.passcode = data["aprs_is"]["passcode"].as(); + aprs_is.server = data["aprs_is"]["server"].as(); + aprs_is.port = data["aprs_is"]["port"].as(); + aprs_is.reportingDistance = data["aprs_is"]["reportingDistance"].as(); + + loramodule.iGateFreq = data["lora"]["iGateFreq"].as(); + loramodule.digirepeaterTxFreq = data["lora"]["digirepeaterTxFreq"].as(); + loramodule.digirepeaterRxFreq = data["lora"]["digirepeaterRxFreq"].as(); + loramodule.spreadingFactor = data["lora"]["spreadingFactor"].as(); + loramodule.signalBandwidth = data["lora"]["signalBandwidth"].as(); + loramodule.codingRate4 = data["lora"]["codingRate4"].as(); + loramodule.power = data["lora"]["power"].as(); + + display.alwaysOn = data["display"]["alwaysOn"].as(); + display.timeout = data["display"]["timeout"].as(); + display.turn180 = data["display"]["turn180"].as(); + + syslog.active = data["syslog"]["active"].as(); + syslog.server = data["syslog"]["server"].as(); + syslog.port = data["syslog"]["port"].as(); + + bme.active = data["bme"]["active"].as(); + + ota.username = data["ota"]["username"].as(); + ota.password = data["ota"]["password"].as(); + + if (wifiAPs.size() == 0) { // If we don't have any WiFi's from config we need to add "empty" SSID for AUTO AP + WiFi_AP wifiap; + wifiap.ssid = ""; + wifiap.password = ""; + wifiap.latitude = 0.0; + wifiap.longitude = 0.0; + + wifiAPs.push_back(wifiap); + } + configFile.close(); + Serial.println("Config read successfuly"); + return true; + } else { + Serial.println("Config file not found"); + return false; } - } +} + +void Configuration::init() { + WiFi_AP wifiap; + wifiap.ssid = ""; + wifiap.password = ""; + wifiap.latitude = 0.0; + wifiap.longitude = 0.0; + wifiAPs.push_back(wifiap); + + wifiAutoAP.password = "1234567890"; + wifiAutoAP.powerOff = 15; + + callsign = "N0CALL"; + stationMode = 1; + iGateComment = "LoRa_APRS_iGate Development"; + + digi.comment = "LoRa_APRS_iGate Development"; + digi.latitude = 0.0; + digi.longitude = 0.0; + + aprs_is.passcode = "XYZVW"; + aprs_is.server = "rotate.aprs2.net"; + aprs_is.port = 14580; + aprs_is.reportingDistance = 30; + + loramodule.iGateFreq = 433775000; + loramodule.digirepeaterTxFreq = 433775000; + loramodule.digirepeaterRxFreq = 433900000; + loramodule.spreadingFactor = 12; + loramodule.signalBandwidth = 125000; + loramodule.codingRate4 = 5; + loramodule.power = 20; + + display.alwaysOn = true; + display.timeout = 4; + display.turn180 = false; + + syslog.active = false; + syslog.server = "192.168.0.100"; + syslog.port = 514; + + bme.active = false; + + ota.username = ""; + ota.password = ""; + + beaconInterval = 15; + igateSendsLoRaBeacons = false; + igateRepeatsLoRaPackets = false; + rememberStationTime = 30; + sendBatteryVoltage = false; + externalVoltageMeasurement = false; + externalVoltagePin = 34; + + Serial.println("todo escrito"); +} + +Configuration::Configuration() { + if (!SPIFFS.begin(false)) { + Serial.println("SPIFFS Mount Failed"); + return; + } else { + Serial.println("montado"); + } + bool exists = SPIFFS.exists("/igate_conf.json"); + if (!exists) { + init(); + writeFile(); + ESP.restart(); + } + readFile(); } \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index 91a0405..04ee554 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -7,61 +7,68 @@ class WiFi_AP { public: - String ssid; - String password; - double latitude; - double longitude; + String ssid; + String password; + double latitude; + double longitude; }; +class WiFi_Auto_AP { +public: + String password; + int powerOff; +}; + + class DIGI { public: - String comment; - double latitude; - double longitude; + String comment; + double latitude; + double longitude; }; class APRS_IS { public: - String passcode; - String server; - int port; - int reportingDistance; + String passcode; + String server; + int port; + int reportingDistance; }; class LoraModule { public: - long iGateFreq; - long digirepeaterTxFreq; - long digirepeaterRxFreq; - int spreadingFactor; - long signalBandwidth; - int codingRate4; - int power; + long iGateFreq; + long digirepeaterTxFreq; + long digirepeaterRxFreq; + int spreadingFactor; + long signalBandwidth; + int codingRate4; + int power; }; class Display { public: - bool alwaysOn; - int timeout; - bool turn180; + bool alwaysOn; + int timeout; + bool turn180; }; class SYSLOG { public: - bool active; - String server; - int port; + bool active; + String server; + int port; }; class BME { public: - bool active; + bool active; }; class OTA { public: - String username; - String password; + String username; + String password; }; @@ -69,31 +76,33 @@ public: class Configuration { public: - String callsign; - int stationMode; - String iGateComment; - int beaconInterval; - bool igateSendsLoRaBeacons; - bool igateRepeatsLoRaPackets; - int rememberStationTime; - bool sendBatteryVoltage; - bool externalVoltageMeasurement; - int externalVoltagePin; - std::vector wifiAPs; - DIGI digi; - APRS_IS aprs_is; - LoraModule loramodule; - Display display; - SYSLOG syslog; - BME bme; - OTA ota; + String callsign; + int stationMode; + String iGateComment; + int beaconInterval; + bool igateSendsLoRaBeacons; + bool igateRepeatsLoRaPackets; + int rememberStationTime; + bool sendBatteryVoltage; + bool externalVoltageMeasurement; + int externalVoltagePin; + std::vector wifiAPs; + WiFi_Auto_AP wifiAutoAP; + DIGI digi; + APRS_IS aprs_is; + LoraModule loramodule; + Display display; + SYSLOG syslog; + BME bme; + OTA ota; - - Configuration(); - void validateConfigFile(String currentBeaconCallsign); + void init(); + void writeFile(); + Configuration(); private: - void readFile(fs::FS &fs, const char *fileName) ; - String _filePath; + bool readFile(); + String _filePath; }; + #endif \ No newline at end of file diff --git a/src/display.cpp b/src/display.cpp index 8b60471..1857015 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -10,156 +10,156 @@ Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); extern Configuration Config; void setup_display() { - Wire.begin(OLED_SDA, OLED_SCL); + Wire.begin(OLED_SDA, OLED_SCL); - if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { - Serial.println(F("SSD1306 allocation failed")); - for(;;); // Don't proceed, loop forever - } - if (Config.display.turn180) { - display.setRotation(2); - } - display.clearDisplay(); - display.setTextColor(WHITE); - display.setTextSize(1); - display.setCursor(0, 0); - display.ssd1306_command(SSD1306_SETCONTRAST); - display.ssd1306_command(1); - display.display(); - delay(1000); + if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { + Serial.println(F("SSD1306 allocation failed")); + for(;;); // Don't proceed, loop forever + } + if (Config.display.turn180) { + display.setRotation(2); + } + display.clearDisplay(); + display.setTextColor(WHITE); + display.setTextSize(1); + display.setCursor(0, 0); + display.ssd1306_command(SSD1306_SETCONTRAST); + display.ssd1306_command(1); + display.display(); + delay(1000); } void display_toggle(bool toggle) { - if (toggle) { - display.ssd1306_command(SSD1306_DISPLAYON); - } else { - display.ssd1306_command(SSD1306_DISPLAYOFF); - } + if (toggle) { + display.ssd1306_command(SSD1306_DISPLAYON); + } else { + display.ssd1306_command(SSD1306_DISPLAYOFF); + } } void show_display(String line1, int wait) { - display.clearDisplay(); - display.setTextColor(WHITE); - display.setTextSize(1); - display.setCursor(0, 0); - display.println(line1); - display.ssd1306_command(SSD1306_SETCONTRAST); - display.ssd1306_command(1); - display.display(); - delay(wait); + display.clearDisplay(); + display.setTextColor(WHITE); + display.setTextSize(1); + display.setCursor(0, 0); + display.println(line1); + display.ssd1306_command(SSD1306_SETCONTRAST); + display.ssd1306_command(1); + display.display(); + delay(wait); } void show_display(String line1, String line2, int wait) { - display.clearDisplay(); - display.setTextColor(WHITE); - display.setTextSize(1); - display.setCursor(0, 0); - display.println(line1); - display.setCursor(0, 8); - display.println(line2); - display.ssd1306_command(SSD1306_SETCONTRAST); - display.ssd1306_command(1); - display.display(); - delay(wait); + display.clearDisplay(); + display.setTextColor(WHITE); + display.setTextSize(1); + display.setCursor(0, 0); + display.println(line1); + display.setCursor(0, 8); + display.println(line2); + display.ssd1306_command(SSD1306_SETCONTRAST); + display.ssd1306_command(1); + display.display(); + delay(wait); } void show_display(String line1, String line2, String line3, int wait) { - display.clearDisplay(); - display.setTextColor(WHITE); - display.setTextSize(1); - display.setCursor(0, 0); - display.println(line1); - display.setCursor(0, 8); - display.println(line2); - display.setCursor(0, 16); - display.println(line3); - display.ssd1306_command(SSD1306_SETCONTRAST); - display.ssd1306_command(1); - display.display(); - delay(wait); + display.clearDisplay(); + display.setTextColor(WHITE); + display.setTextSize(1); + display.setCursor(0, 0); + display.println(line1); + display.setCursor(0, 8); + display.println(line2); + display.setCursor(0, 16); + display.println(line3); + display.ssd1306_command(SSD1306_SETCONTRAST); + display.ssd1306_command(1); + display.display(); + delay(wait); } void show_display(String line1, String line2, String line3, String line4, int wait) { - display.clearDisplay(); - display.setTextColor(WHITE); - display.setTextSize(1); - display.setCursor(0, 0); - display.println(line1); - display.setCursor(0, 8); - display.println(line2); - display.setCursor(0, 16); - display.println(line3); - display.setCursor(0, 24); - display.println(line4); - display.ssd1306_command(SSD1306_SETCONTRAST); - display.ssd1306_command(1); - display.display(); - delay(wait); + display.clearDisplay(); + display.setTextColor(WHITE); + display.setTextSize(1); + display.setCursor(0, 0); + display.println(line1); + display.setCursor(0, 8); + display.println(line2); + display.setCursor(0, 16); + display.println(line3); + display.setCursor(0, 24); + display.println(line4); + display.ssd1306_command(SSD1306_SETCONTRAST); + display.ssd1306_command(1); + display.display(); + delay(wait); } void show_display(String line1, String line2, String line3, String line4, String line5, int wait) { - display.clearDisplay(); - display.setTextColor(WHITE); - display.setTextSize(1); - display.setCursor(0, 0); - display.println(line1); - display.setCursor(0, 8); - display.println(line2); - display.setCursor(0, 16); - display.println(line3); - display.setCursor(0, 24); - display.println(line4); - display.setCursor(0, 32); - display.println(line5); - display.ssd1306_command(SSD1306_SETCONTRAST); - display.ssd1306_command(1); - display.display(); - delay(wait); + display.clearDisplay(); + display.setTextColor(WHITE); + display.setTextSize(1); + display.setCursor(0, 0); + display.println(line1); + display.setCursor(0, 8); + display.println(line2); + display.setCursor(0, 16); + display.println(line3); + display.setCursor(0, 24); + display.println(line4); + display.setCursor(0, 32); + display.println(line5); + display.ssd1306_command(SSD1306_SETCONTRAST); + display.ssd1306_command(1); + display.display(); + delay(wait); } void show_display(String line1, String line2, String line3, String line4, String line5, String line6, int wait) { - display.clearDisplay(); - display.setTextColor(WHITE); - display.setTextSize(1); - display.setCursor(0, 0); - display.println(line1); - display.setCursor(0, 8); - display.println(line2); - display.setCursor(0, 16); - display.println(line3); - display.setCursor(0, 24); - display.println(line4); - display.setCursor(0, 32); - display.println(line5); - display.setCursor(0, 40); - display.println(line6); - display.ssd1306_command(SSD1306_SETCONTRAST); - display.ssd1306_command(1); - display.display(); - delay(wait); + display.clearDisplay(); + display.setTextColor(WHITE); + display.setTextSize(1); + display.setCursor(0, 0); + display.println(line1); + display.setCursor(0, 8); + display.println(line2); + display.setCursor(0, 16); + display.println(line3); + display.setCursor(0, 24); + display.println(line4); + display.setCursor(0, 32); + display.println(line5); + display.setCursor(0, 40); + display.println(line6); + display.ssd1306_command(SSD1306_SETCONTRAST); + display.ssd1306_command(1); + display.display(); + delay(wait); } void show_display(String line1, String line2, String line3, String line4, String line5, String line6, String line7, int wait) { - display.clearDisplay(); - display.setTextColor(WHITE); - display.setTextSize(2); - display.setCursor(0, 0); - display.println(line1); - display.setTextSize(1); - display.setCursor(0, 16); - display.println(line2); - display.setCursor(0, 24); - display.println(line3); - display.setCursor(0, 32); - display.println(line4); - display.setCursor(0, 40); - display.println(line5); - display.setCursor(0, 48); - display.println(line6); - display.setCursor(0, 56); - display.println(line7); - display.ssd1306_command(SSD1306_SETCONTRAST); - display.ssd1306_command(1); - display.display(); - delay(wait); + display.clearDisplay(); + display.setTextColor(WHITE); + display.setTextSize(2); + display.setCursor(0, 0); + display.println(line1); + display.setTextSize(1); + display.setCursor(0, 16); + display.println(line2); + display.setCursor(0, 24); + display.println(line3); + display.setCursor(0, 32); + display.println(line4); + display.setCursor(0, 40); + display.println(line5); + display.setCursor(0, 48); + display.println(line6); + display.setCursor(0, 56); + display.println(line7); + display.ssd1306_command(SSD1306_SETCONTRAST); + display.ssd1306_command(1); + display.display(); + delay(wait); } \ No newline at end of file diff --git a/src/gps_utils.cpp b/src/gps_utils.cpp index c868da7..1c7916b 100644 --- a/src/gps_utils.cpp +++ b/src/gps_utils.cpp @@ -11,188 +11,188 @@ String distance; namespace GPS_Utils { - String double2string(double n, int ndec) { - String r = ""; - if (n>-1 && n<0) { - r = "-"; - } - int v = n; - r += v; - r += '.'; - for (int i=0;i-1 && n<0) { + r = "-"; + } + int v = n; + r += v; + r += '.'; + for (int i=0;ilatitude); - stationLongitude = processLongitudeAPRS(currentWiFi->longitude); - beaconPacket = Config.callsign + ">APLRG1,WIDE1-1"; - if (stationMode!=6) { - beaconPacket += ",qAC"; - } - beaconPacket += ":=" + stationLatitude + "L" + stationLongitude; - if (stationMode==1) { - beaconPacket += "&"; - } else { - beaconPacket += "a"; - } - beaconPacket += Config.iGateComment; - } else { //stationMode 3, 4 and 5 - if (stationMode==5) { + String generateBeacon() { + String stationLatitude, stationLongitude, beaconPacket; + if (stationMode==1 || stationMode==2 || (stationMode==5 && WiFi.status()==WL_CONNECTED && espClient.connected())) { + stationLatitude = processLatitudeAPRS(currentWiFi->latitude); + stationLongitude = processLongitudeAPRS(currentWiFi->longitude); + beaconPacket = Config.callsign + ">APLRG1,WIDE1-1"; + if (stationMode!=6) { + beaconPacket += ",qAC"; + } + beaconPacket += ":=" + stationLatitude + "L" + stationLongitude; + if (stationMode==1) { + beaconPacket += "&"; + } else { + beaconPacket += "a"; + } + beaconPacket += Config.iGateComment; + } else { //stationMode 3, 4 and 5 + if (stationMode==5) { + stationLatitude = processLatitudeAPRS(currentWiFi->latitude); + stationLongitude = processLongitudeAPRS(currentWiFi->longitude); + } else { + stationLatitude = processLatitudeAPRS(Config.digi.latitude); + stationLongitude = processLongitudeAPRS(Config.digi.longitude); + } + beaconPacket = Config.callsign + ">APLRG1,WIDE1-1:=" + stationLatitude + "L" + stationLongitude + "#" + Config.digi.comment; + } + return beaconPacket; + } + + String generateiGateLoRaBeacon() { + String stationLatitude, stationLongitude, beaconPacket; stationLatitude = processLatitudeAPRS(currentWiFi->latitude); stationLongitude = processLongitudeAPRS(currentWiFi->longitude); - } else { - stationLatitude = processLatitudeAPRS(Config.digi.latitude); - stationLongitude = processLongitudeAPRS(Config.digi.longitude); - } - beaconPacket = Config.callsign + ">APLRG1,WIDE1-1:=" + stationLatitude + "L" + stationLongitude + "#" + Config.digi.comment; + beaconPacket = Config.callsign + ">APLRG1,RFONLY:=" + stationLatitude + "L" + stationLongitude; + if (Config.bme.active) { + beaconPacket += "_"; + } else { + beaconPacket += "a"; + } + return beaconPacket; } - return beaconPacket; - } - String generateiGateLoRaBeacon() { - String stationLatitude, stationLongitude, beaconPacket; - stationLatitude = processLatitudeAPRS(currentWiFi->latitude); - stationLongitude = processLongitudeAPRS(currentWiFi->longitude); - beaconPacket = Config.callsign + ">APLRG1,RFONLY:=" + stationLatitude + "L" + stationLongitude; - if (Config.bme.active) { - beaconPacket += "_"; - } else { - beaconPacket += "a"; + double calculateDistanceTo(double latitude, double longitude) { + return TinyGPSPlus::distanceBetween(currentWiFi->latitude,currentWiFi->longitude, latitude, longitude) / 1000.0; } - return beaconPacket; - } - double calculateDistanceTo(double latitude, double longitude) { - return TinyGPSPlus::distanceBetween(currentWiFi->latitude,currentWiFi->longitude, latitude, longitude) / 1000.0; - } + String decodeEncodedGPS(String packet) { + String GPSPacket = packet.substring(packet.indexOf(":!")+3); + String encodedLatitude = GPSPacket.substring(0,4); + String encodedLongtitude = GPSPacket.substring(4,8); - String decodeEncodedGPS(String packet) { - String GPSPacket = packet.substring(packet.indexOf(":!")+3); - String encodedLatitude = GPSPacket.substring(0,4); - String encodedLongtitude = GPSPacket.substring(4,8); - - int Y1 = int(encodedLatitude[0]); - int Y2 = int(encodedLatitude[1]); - int Y3 = int(encodedLatitude[2]); - int Y4 = int(encodedLatitude[3]); - float decodedLatitude = 90.0 - ((((Y1-33) * pow(91,3)) + ((Y2-33) * pow(91,2)) + ((Y3-33) * 91) + Y4-33) / 380926.0); - - int X1 = int(encodedLongtitude[0]); - int X2 = int(encodedLongtitude[1]); - int X3 = int(encodedLongtitude[2]); - int X4 = int(encodedLongtitude[3]); - float decodedLongitude = -180.0 + ((((X1-33) * pow(91,3)) + ((X2-33) * pow(91,2)) + ((X3-33) * 91) + X4-33) / 190463.0); - distance = String(calculateDistanceTo(decodedLatitude, decodedLongitude),1); - return String(decodedLatitude,5) + "N / " + String(decodedLongitude,5) + "E / " + distance + "km"; - } - - String getReceivedGPS(String packet) { - String infoGPS; - if (packet.indexOf(":!") > 10) { - infoGPS = packet.substring(packet.indexOf(":!")+2); - } else if (packet.indexOf(":=") > 10) { - infoGPS = packet.substring(packet.indexOf(":=")+2); + int Y1 = int(encodedLatitude[0]); + int Y2 = int(encodedLatitude[1]); + int Y3 = int(encodedLatitude[2]); + int Y4 = int(encodedLatitude[3]); + float decodedLatitude = 90.0 - ((((Y1-33) * pow(91,3)) + ((Y2-33) * pow(91,2)) + ((Y3-33) * 91) + Y4-33) / 380926.0); + + int X1 = int(encodedLongtitude[0]); + int X2 = int(encodedLongtitude[1]); + int X3 = int(encodedLongtitude[2]); + int X4 = int(encodedLongtitude[3]); + float decodedLongitude = -180.0 + ((((X1-33) * pow(91,3)) + ((X2-33) * pow(91,2)) + ((X3-33) * 91) + X4-33) / 190463.0); + distance = String(calculateDistanceTo(decodedLatitude, decodedLongitude),1); + return String(decodedLatitude,5) + "N / " + String(decodedLongitude,5) + "E / " + distance + "km"; } - String Latitude = infoGPS.substring(0,8); - String Longitude = infoGPS.substring(9,18); - float convertedLatitude, convertedLongitude; - String firstLatPart = Latitude.substring(0,2); - String secondLatPart = Latitude.substring(2,4); - String thirdLatPart = Latitude.substring(Latitude.indexOf(".")+1,Latitude.indexOf(".")+3); - String firstLngPart = Longitude.substring(0,3); - String secondLngPart = Longitude.substring(3,5); - String thirdLngPart = Longitude.substring(Longitude.indexOf(".")+1,Longitude.indexOf(".")+3); - convertedLatitude = firstLatPart.toFloat() + (secondLatPart.toFloat()/60) + (thirdLatPart.toFloat()/(60*100)); - convertedLongitude = firstLngPart.toFloat() + (secondLngPart.toFloat()/60) + (thirdLngPart.toFloat()/(60*100)); - - String LatSign = String(Latitude[7]); - String LngSign = String(Longitude[8]); - if (LatSign == "S") { - convertedLatitude = -convertedLatitude; - } - if (LngSign == "W") { - convertedLongitude = -convertedLongitude; - } - distance = String(calculateDistanceTo(convertedLatitude, convertedLongitude),1); - return String(convertedLatitude,5) + "N / " + String(convertedLongitude,5) + "E / " + distance + "km"; - } + String getReceivedGPS(String packet) { + String infoGPS; + if (packet.indexOf(":!") > 10) { + infoGPS = packet.substring(packet.indexOf(":!")+2); + } else if (packet.indexOf(":=") > 10) { + infoGPS = packet.substring(packet.indexOf(":=")+2); + } + String Latitude = infoGPS.substring(0,8); + String Longitude = infoGPS.substring(9,18); - String getDistance(String packet) { - int encodedBytePosition = 0; - if (packet.indexOf(":!") > 10) { - encodedBytePosition = packet.indexOf(":!") + 14; + float convertedLatitude, convertedLongitude; + String firstLatPart = Latitude.substring(0,2); + String secondLatPart = Latitude.substring(2,4); + String thirdLatPart = Latitude.substring(Latitude.indexOf(".")+1,Latitude.indexOf(".")+3); + String firstLngPart = Longitude.substring(0,3); + String secondLngPart = Longitude.substring(3,5); + String thirdLngPart = Longitude.substring(Longitude.indexOf(".")+1,Longitude.indexOf(".")+3); + convertedLatitude = firstLatPart.toFloat() + (secondLatPart.toFloat()/60) + (thirdLatPart.toFloat()/(60*100)); + convertedLongitude = firstLngPart.toFloat() + (secondLngPart.toFloat()/60) + (thirdLngPart.toFloat()/(60*100)); + + String LatSign = String(Latitude[7]); + String LngSign = String(Longitude[8]); + if (LatSign == "S") { + convertedLatitude = -convertedLatitude; + } + if (LngSign == "W") { + convertedLongitude = -convertedLongitude; + } + distance = String(calculateDistanceTo(convertedLatitude, convertedLongitude),1); + return String(convertedLatitude,5) + "N / " + String(convertedLongitude,5) + "E / " + distance + "km"; } - if (packet.indexOf(":=") > 10) { - encodedBytePosition = packet.indexOf(":=") + 14; + + String getDistance(String packet) { + int encodedBytePosition = 0; + if (packet.indexOf(":!") > 10) { + encodedBytePosition = packet.indexOf(":!") + 14; + } + if (packet.indexOf(":=") > 10) { + encodedBytePosition = packet.indexOf(":=") + 14; + } + if (encodedBytePosition != 0) { + if (String(packet[encodedBytePosition]) == "G" || String(packet[encodedBytePosition]) == "Q" || String(packet[encodedBytePosition]) == "[" || String(packet[encodedBytePosition]) == "H") { + return decodeEncodedGPS(packet); + } else { + return getReceivedGPS(packet); + } + } else { + return " _ / _ / _ "; + } } - if (encodedBytePosition != 0) { - if (String(packet[encodedBytePosition]) == "G" || String(packet[encodedBytePosition]) == "Q" || String(packet[encodedBytePosition]) == "[" || String(packet[encodedBytePosition]) == "H") { - return decodeEncodedGPS(packet); - } else { - return getReceivedGPS(packet); - } - } else { - return " _ / _ / _ "; - } - } } \ No newline at end of file diff --git a/src/lora_utils.cpp b/src/lora_utils.cpp index 36e534c..0f91491 100644 --- a/src/lora_utils.cpp +++ b/src/lora_utils.cpp @@ -26,197 +26,197 @@ float snr; namespace LoRa_Utils { - void setFlag(void) { - #ifdef HAS_SX126X - transmissionFlag = true; - #endif - } + void setFlag(void) { + #ifdef HAS_SX126X + transmissionFlag = true; + #endif + } - void setup() { - #ifdef HAS_SX127X - SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS); - LoRa.setPins(LORA_CS, LORA_RST, LORA_IRQ); - long freq; - if (stationMode==1 || stationMode==2) { - freq = Config.loramodule.iGateFreq; - } else { - freq = Config.loramodule.digirepeaterTxFreq; + void setup() { + #ifdef HAS_SX127X + SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS); + LoRa.setPins(LORA_CS, LORA_RST, LORA_IRQ); + long freq; + if (stationMode==1 || stationMode==2) { + freq = Config.loramodule.iGateFreq; + } else { + freq = Config.loramodule.digirepeaterTxFreq; + } + if (!LoRa.begin(freq)) { + Serial.println("Starting LoRa failed!"); + show_display("ERROR", "Starting LoRa failed!"); + while (true) { + delay(1000); + } + } + LoRa.setSpreadingFactor(Config.loramodule.spreadingFactor); + LoRa.setSignalBandwidth(Config.loramodule.signalBandwidth); + LoRa.setCodingRate4(Config.loramodule.codingRate4); + LoRa.enableCrc(); + LoRa.setTxPower(Config.loramodule.power); + Serial.print("init : LoRa Module ... done!"); + #endif + #ifdef HAS_SX126X + SPI.begin(RADIO_SCLK_PIN, RADIO_MISO_PIN, RADIO_MOSI_PIN); + float freq = (float)Config.loramodule.iGateFreq/1000000; + int state = radio.begin(freq); + if (state == RADIOLIB_ERR_NONE) { + Serial.print("Initializing SX126X LoRa Module"); + } else { + Serial.println("Starting LoRa failed!"); + while (true); + } + radio.setDio1Action(setFlag); + radio.setSpreadingFactor(Config.loramodule.spreadingFactor); + radio.setBandwidth(Config.loramodule.signalBandwidth); + radio.setCodingRate(Config.loramodule.codingRate4); + #if defined(ESP32_DIY_1W_LoRa) + radio.setRfSwitchPins(RADIO_RXEN, RADIO_TXEN); + #endif + #if defined(HELTEC_V3) || defined(TTGO_T_Beam_V1_0_SX1268) || defined(TTGO_T_Beam_V1_2_SX1262) + state = radio.setOutputPower(Config.loramodule.power + 2); // values available: 10, 17, 22 --> if 20 in tracker_conf.json it will be updated to 22. + #endif + #ifdef ESP32_DIY_1W_LoRa_GPS + state = radio.setOutputPower(Config.loramodule.power); // max value 20 (when 20dB in setup 30dB in output as 400M30S has Low Noise Amp) + #endif + if (state == RADIOLIB_ERR_NONE) { + Serial.println("init : LoRa Module ... done!"); + } else { + Serial.println("Starting LoRa failed!"); + while (true); + } + #endif } - if (!LoRa.begin(freq)) { - Serial.println("Starting LoRa failed!"); - show_display("ERROR", "Starting LoRa failed!"); - while (true) { - delay(1000); - } - } - LoRa.setSpreadingFactor(Config.loramodule.spreadingFactor); - LoRa.setSignalBandwidth(Config.loramodule.signalBandwidth); - LoRa.setCodingRate4(Config.loramodule.codingRate4); - LoRa.enableCrc(); - LoRa.setTxPower(Config.loramodule.power); - Serial.print("init : LoRa Module ... done!"); - #endif - #ifdef HAS_SX126X - SPI.begin(RADIO_SCLK_PIN, RADIO_MISO_PIN, RADIO_MOSI_PIN); - float freq = (float)Config.loramodule.iGateFreq/1000000; - int state = radio.begin(freq); - if (state == RADIOLIB_ERR_NONE) { - Serial.print("Initializing SX126X LoRa Module"); - } else { - Serial.println("Starting LoRa failed!"); - while (true); - } - radio.setDio1Action(setFlag); - radio.setSpreadingFactor(Config.loramodule.spreadingFactor); - radio.setBandwidth(Config.loramodule.signalBandwidth); - radio.setCodingRate(Config.loramodule.codingRate4); - #if defined(ESP32_DIY_1W_LoRa) - radio.setRfSwitchPins(RADIO_RXEN, RADIO_TXEN); - #endif - #if defined(HELTEC_V3) || defined(TTGO_T_Beam_V1_0_SX1268) || defined(TTGO_T_Beam_V1_2_SX1262) - state = radio.setOutputPower(Config.loramodule.power + 2); // values available: 10, 17, 22 --> if 20 in tracker_conf.json it will be updated to 22. - #endif - #ifdef ESP32_DIY_1W_LoRa_GPS - state = radio.setOutputPower(Config.loramodule.power); // max value 20 (when 20dB in setup 30dB in output as 400M30S has Low Noise Amp) - #endif - if (state == RADIOLIB_ERR_NONE) { - Serial.println("init : LoRa Module ... done!"); - } else { - Serial.println("Starting LoRa failed!"); - while (true); - } - #endif - } - void sendNewPacket(const String &typeOfMessage, const String &newPacket) { - #if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa) - digitalWrite(internalLedPin,HIGH); - #endif - #ifdef HAS_SX127X - LoRa.beginPacket(); - LoRa.write('<'); - if (typeOfMessage == "APRS") { - LoRa.write(0xFF); - } else if (typeOfMessage == "LoRa") { - LoRa.write(0xF8); + void sendNewPacket(const String &typeOfMessage, const String &newPacket) { + #if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa) + digitalWrite(internalLedPin,HIGH); + #endif + #ifdef HAS_SX127X + LoRa.beginPacket(); + LoRa.write('<'); + if (typeOfMessage == "APRS") { + LoRa.write(0xFF); + } else if (typeOfMessage == "LoRa") { + LoRa.write(0xF8); + } + LoRa.write(0x01); + LoRa.write((const uint8_t *)newPacket.c_str(), newPacket.length()); + LoRa.endPacket(); + #endif + #ifdef HAS_SX126X + int state = radio.transmit("\x3c\xff\x01" + newPacket); + if (state == RADIOLIB_ERR_NONE) { + //Serial.println(F("success!")); + } else if (state == RADIOLIB_ERR_PACKET_TOO_LONG) { + Serial.println(F("too long!")); + } else if (state == RADIOLIB_ERR_TX_TIMEOUT) { + Serial.println(F("timeout!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + } + #endif + #if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa) + digitalWrite(internalLedPin,LOW); + #endif + SYSLOG_Utils::log("Tx", newPacket,0,0,0); + Serial.print("---> LoRa Packet Tx : "); + Serial.println(newPacket); } - LoRa.write(0x01); - LoRa.write((const uint8_t *)newPacket.c_str(), newPacket.length()); - LoRa.endPacket(); - #endif - #ifdef HAS_SX126X - int state = radio.transmit("\x3c\xff\x01" + newPacket); - if (state == RADIOLIB_ERR_NONE) { - //Serial.println(F("success!")); - } else if (state == RADIOLIB_ERR_PACKET_TOO_LONG) { - Serial.println(F("too long!")); - } else if (state == RADIOLIB_ERR_TX_TIMEOUT) { - Serial.println(F("timeout!")); - } else { - Serial.print(F("failed, code ")); - Serial.println(state); - } - #endif - #if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa) - digitalWrite(internalLedPin,LOW); - #endif - SYSLOG_Utils::log("Tx", newPacket,0,0,0); - Serial.print("---> LoRa Packet Tx : "); - Serial.println(newPacket); - } - String generatePacket(String aprsisPacket) { - String firstPart, messagePart; - aprsisPacket.trim(); - firstPart = aprsisPacket.substring(0, aprsisPacket.indexOf(",")); - messagePart = aprsisPacket.substring(aprsisPacket.indexOf("::")+2); - return firstPart + ",TCPIP,WIDE1-1," + Config.callsign + "::" + messagePart; - } + String generatePacket(String aprsisPacket) { + String firstPart, messagePart; + aprsisPacket.trim(); + firstPart = aprsisPacket.substring(0, aprsisPacket.indexOf(",")); + messagePart = aprsisPacket.substring(aprsisPacket.indexOf("::")+2); + return firstPart + ",TCPIP,WIDE1-1," + Config.callsign + "::" + messagePart; + } - String packetSanitization(String packet) { - Serial.println(packet); - if (packet.indexOf("\0")>0) { - packet.replace("\0","000"); + String packetSanitization(String packet) { + Serial.println(packet); + if (packet.indexOf("\0")>0) { + packet.replace("\0",""); + } + if (packet.indexOf("\r")>0) { + packet.replace("\r",""); + } + if (packet.indexOf("\n")>0) { + packet.replace("\n",""); + } + return packet; } - if (packet.indexOf("\r")>0) { - packet.replace("\r","RRR"); - } - if (packet.indexOf("\n")>0) { - packet.replace("\n","NNN"); - } - return packet; - } - String receivePacket() { - String loraPacket = ""; - #ifdef HAS_SX127X - int packetSize = LoRa.parsePacket(); - if (packetSize) { - while (LoRa.available()) { - int inChar = LoRa.read(); - loraPacket += (char)inChar; - } - rssi = LoRa.packetRssi(); - snr = LoRa.packetSnr(); - freqError = LoRa.packetFrequencyError(); + String receivePacket() { + String loraPacket = ""; + #ifdef HAS_SX127X + int packetSize = LoRa.parsePacket(); + if (packetSize) { + while (LoRa.available()) { + int inChar = LoRa.read(); + loraPacket += (char)inChar; + } + rssi = LoRa.packetRssi(); + snr = LoRa.packetSnr(); + freqError = LoRa.packetFrequencyError(); + } + #endif + #ifdef HAS_SX126X + if (transmissionFlag) { + transmissionFlag = false; + radio.startReceive(); + int state = radio.readData(loraPacket); + if (state == RADIOLIB_ERR_NONE) { + Serial.println("LoRa Rx ---> " + loraPacket); + rssi = radio.getRSSI(); + snr = radio.getSNR(); + freqError = radio.getFrequencyError(); + } else if (state == RADIOLIB_ERR_RX_TIMEOUT) { + // timeout occurred while waiting for a packet + } else if (state == RADIOLIB_ERR_CRC_MISMATCH) { + Serial.println(F("CRC error!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + } + } + #endif + // // // // // // + if ((loraPacket.indexOf("\0")!=-1) || (loraPacket.indexOf("\r")!=-1) || (loraPacket.indexOf("\n")!=-1)) { + loraPacket = packetSanitization(loraPacket); + } + // // // // // // + #ifndef TextSerialOutputForApp + if (loraPacket!="") { + Serial.println("(RSSI:" +String(rssi) + " / SNR:" + String(snr) + " / FreqErr:" + String(freqError) + ")"); + } + #endif + if (Config.syslog.active && (stationMode==1 || stationMode==2 || (stationMode==5 && WiFi.status()==WL_CONNECTED)) && loraPacket!="") { + SYSLOG_Utils::log("Rx", loraPacket, rssi, snr, freqError); + } + return loraPacket; } - #endif - #ifdef HAS_SX126X - if (transmissionFlag) { - transmissionFlag = false; - radio.startReceive(); - int state = radio.readData(loraPacket); - if (state == RADIOLIB_ERR_NONE) { - Serial.println("LoRa Rx ---> " + loraPacket); - rssi = radio.getRSSI(); - snr = radio.getSNR(); - freqError = radio.getFrequencyError(); - } else if (state == RADIOLIB_ERR_RX_TIMEOUT) { - // timeout occurred while waiting for a packet - } else if (state == RADIOLIB_ERR_CRC_MISMATCH) { - Serial.println(F("CRC error!")); - } else { - Serial.print(F("failed, code ")); - Serial.println(state); - } - } - #endif - // // // // // // - if ((loraPacket.indexOf("\0")!=-1) || (loraPacket.indexOf("\r")!=-1) || (loraPacket.indexOf("\n")!=-1)) { - loraPacket = packetSanitization(loraPacket); - } - // // // // // // - #ifndef TextSerialOutputForApp - if (loraPacket!="") { - Serial.println("(RSSI:" +String(rssi) + " / SNR:" + String(snr) + " / FreqErr:" + String(freqError) + ")"); - } - #endif - if (Config.syslog.active && (stationMode==1 || stationMode==2 || (stationMode==5 && WiFi.status()==WL_CONNECTED)) && loraPacket!="") { - SYSLOG_Utils::log("Rx", loraPacket, rssi, snr, freqError); - } - return loraPacket; - } - void changeFreqTx() { - delay(500); - #ifdef HAS_SX127X - LoRa.setFrequency(Config.loramodule.digirepeaterTxFreq); - #endif - #ifdef HAS_SX126X - float freq = (float)Config.loramodule.digirepeaterTxFreq/1000000; - radio.setFrequency(freq); - #endif - } + void changeFreqTx() { + delay(500); + #ifdef HAS_SX127X + LoRa.setFrequency(Config.loramodule.digirepeaterTxFreq); + #endif + #ifdef HAS_SX126X + float freq = (float)Config.loramodule.digirepeaterTxFreq/1000000; + radio.setFrequency(freq); + #endif + } - void changeFreqRx() { - delay(500); - #ifdef HAS_SX127X - LoRa.setFrequency(Config.loramodule.digirepeaterRxFreq); - #endif - #ifdef HAS_SX126X - float freq = (float)Config.loramodule.digirepeaterRxFreq/1000000; - radio.setFrequency(freq); - #endif - } + void changeFreqRx() { + delay(500); + #ifdef HAS_SX127X + LoRa.setFrequency(Config.loramodule.digirepeaterRxFreq); + #endif + #ifdef HAS_SX126X + float freq = (float)Config.loramodule.digirepeaterRxFreq/1000000; + radio.setFrequency(freq); + #endif + } } \ No newline at end of file diff --git a/src/ota_utils.cpp b/src/ota_utils.cpp new file mode 100644 index 0000000..ffd176e --- /dev/null +++ b/src/ota_utils.cpp @@ -0,0 +1,56 @@ +#include +#include +#include +#include "configuration.h" +#include "display.h" +#include "ota_utils.h" + +extern Configuration Config; +extern uint32_t lastScreenOn; + +unsigned long ota_progress_millis = 0; + +namespace OTA_Utils { + + void setup(AsyncWebServer *server) { + if (Config.ota.username != "" && Config.ota.password != "") { + ElegantOTA.begin(server, Config.ota.username.c_str(), Config.ota.password.c_str()); + } else { + ElegantOTA.begin(server); + } + + ElegantOTA.setAutoReboot(true); + ElegantOTA.onStart(onOTAStart); + ElegantOTA.onProgress(onOTAProgress); + ElegantOTA.onEnd(onOTAEnd); + } + + void onOTAStart() { + Serial.println("OTA update started!"); + display_toggle(true); + lastScreenOn = millis(); + show_display("", "", "", " OTA update started!", "", "", "", 1000); + } + + void onOTAProgress(size_t current, size_t final) { + if (millis() - ota_progress_millis > 1000) { + display_toggle(true); + lastScreenOn = millis(); + ota_progress_millis = millis(); + Serial.printf("OTA Progress Current: %u bytes, Final: %u bytes\n", current, final); + show_display("", "", " OTA Progress : " + String((current*100)/final) + "%", "", "", "", "", 100); + } + } + + void onOTAEnd(bool success) { + display_toggle(true); + lastScreenOn = millis(); + if (success) { + Serial.println("OTA update finished successfully!"); + show_display("", "", " OTA update success!", "", " Rebooting ...", "", "", 4000); + } else { + Serial.println("There was an error during OTA update!"); + show_display("", "", " OTA update fail!", "", "", "", "", 4000); + } + } +} \ No newline at end of file diff --git a/src/ota_utils.h b/src/ota_utils.h new file mode 100644 index 0000000..3b1acd3 --- /dev/null +++ b/src/ota_utils.h @@ -0,0 +1,16 @@ +#ifndef OTA_UTILS_H_ +#define OTA_UTILS_H_ + +#include +#include + +namespace OTA_Utils { + + void setup(AsyncWebServer *server); + void onOTAStart(); + void onOTAProgress(size_t current, size_t final); + void onOTAEnd(bool success); + +} + +#endif \ No newline at end of file diff --git a/src/power_utils.cpp b/src/power_utils.cpp index c682702..2a3ddee 100644 --- a/src/power_utils.cpp +++ b/src/power_utils.cpp @@ -20,124 +20,121 @@ extern Configuration Config; namespace POWER_Utils { - bool BatteryIsConnected = false; - String batteryVoltage = ""; - String batteryChargeDischargeCurrent = ""; + bool BatteryIsConnected = false; + String batteryVoltage = ""; + String batteryChargeDischargeCurrent = ""; - void activateMeasurement() { - #if defined(HAS_AXP192) || defined(HAS_AXP2101) - PMU.disableTSPinMeasure(); - PMU.enableBattDetection(); - PMU.enableVbusVoltageMeasure(); - PMU.enableBattVoltageMeasure(); - PMU.enableSystemVoltageMeasure(); - #endif - } - - void activateLoRa() { - #ifdef HAS_AXP192 - PMU.setLDO2Voltage(3300); - PMU.enableLDO2(); - #endif - #ifdef HAS_AXP2101 - PMU.setALDO2Voltage(3300); - PMU.enableALDO2(); - #endif - } - - void deactivateLoRa() { - #ifdef HAS_AXP192 - PMU.disableLDO2(); - #endif - #ifdef HAS_AXP2101 - PMU.disableALDO2(); - #endif - } - - bool begin(TwoWire &port) { - #if defined (TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(ESP32_DIY_LoRa) || defined(HELTEC_V3) || defined(ESP32_DIY_1W_LoRa) - return true; - #endif - - #ifdef HAS_AXP192 - bool result = PMU.begin(Wire, AXP192_SLAVE_ADDRESS, I2C_SDA, I2C_SCL); - if (result) { - PMU.disableDC2(); - PMU.disableLDO2(); - PMU.disableLDO3(); - PMU.setDC1Voltage(3300); - PMU.enableDC1(); - PMU.setProtectedChannel(XPOWERS_DCDC3); - PMU.disableIRQ(XPOWERS_AXP192_ALL_IRQ); + void activateMeasurement() { + #if defined(HAS_AXP192) || defined(HAS_AXP2101) + PMU.disableTSPinMeasure(); + PMU.enableBattDetection(); + PMU.enableVbusVoltageMeasure(); + PMU.enableBattVoltageMeasure(); + PMU.enableSystemVoltageMeasure(); + #endif } - return result; - #endif - #ifdef HAS_AXP2101 - bool result = PMU.begin(Wire, AXP2101_SLAVE_ADDRESS, I2C_SDA, I2C_SCL); - if (result) { - PMU.disableDC2(); - PMU.disableDC3(); - PMU.disableDC4(); - PMU.disableDC5(); - PMU.disableALDO1(); - PMU.disableALDO4(); - PMU.disableBLDO1(); - PMU.disableBLDO2(); - PMU.disableDLDO1(); - PMU.disableDLDO2(); - PMU.setDC1Voltage(3300); - PMU.enableDC1(); - PMU.setButtonBatteryChargeVoltage(3300); - PMU.enableButtonBatteryCharge(); - PMU.disableIRQ(XPOWERS_AXP2101_ALL_IRQ); + void activateLoRa() { + #ifdef HAS_AXP192 + PMU.setLDO2Voltage(3300); + PMU.enableLDO2(); + #endif + #ifdef HAS_AXP2101 + PMU.setALDO2Voltage(3300); + PMU.enableALDO2(); + #endif } - return result; - #endif - } - void setup() { - Wire.end(); - #ifdef HAS_AXP192 - Wire.begin(SDA, SCL); - if (begin(Wire)) { - Serial.println("AXP192 init done!"); - } else { - Serial.println("AXP192 init failed!"); + void deactivateLoRa() { + #ifdef HAS_AXP192 + PMU.disableLDO2(); + #endif + #ifdef HAS_AXP2101 + PMU.disableALDO2(); + #endif } - activateLoRa(); - activateMeasurement(); - PMU.setChargerTerminationCurr(XPOWERS_AXP192_CHG_ITERM_LESS_10_PERCENT); - PMU.setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2); - PMU.setChargerConstantCurr(XPOWERS_AXP192_CHG_CUR_780MA); - PMU.setSysPowerDownVoltage(2600); - #endif - #ifdef HAS_AXP2101 - Wire.begin(SDA, SCL); - if (begin(Wire)) { - Serial.println("AXP2101 init done!"); - } else { - Serial.println("AXP2101 init failed!"); + bool begin(TwoWire &port) { + #if defined(HAS_AXP192) + bool result = PMU.begin(Wire, AXP192_SLAVE_ADDRESS, I2C_SDA, I2C_SCL); + if (result) { + PMU.disableDC2(); + PMU.disableLDO2(); + PMU.disableLDO3(); + PMU.setDC1Voltage(3300); + PMU.enableDC1(); + PMU.setProtectedChannel(XPOWERS_DCDC3); + PMU.disableIRQ(XPOWERS_AXP192_ALL_IRQ); + } + return result; + #elif defined(HAS_AXP2101) + bool result = PMU.begin(Wire, AXP2101_SLAVE_ADDRESS, I2C_SDA, I2C_SCL); + if (result) { + PMU.disableDC2(); + PMU.disableDC3(); + PMU.disableDC4(); + PMU.disableDC5(); + PMU.disableALDO1(); + PMU.disableALDO4(); + PMU.disableBLDO1(); + PMU.disableBLDO2(); + PMU.disableDLDO1(); + PMU.disableDLDO2(); + PMU.setDC1Voltage(3300); + PMU.enableDC1(); + PMU.setButtonBatteryChargeVoltage(3300); + PMU.enableButtonBatteryCharge(); + PMU.disableIRQ(XPOWERS_AXP2101_ALL_IRQ); + } + return result; + #else + return true; + #endif } - activateLoRa(); - activateMeasurement(); - PMU.setPrechargeCurr(XPOWERS_AXP2101_PRECHARGE_200MA); - PMU.setChargerTerminationCurr(XPOWERS_AXP2101_CHG_ITERM_25MA); - PMU.setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2); - PMU.setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_800MA); - PMU.setSysPowerDownVoltage(2600); - #endif - } - /*void lowerCpuFrequency() { - #if defined(TTGO_T_Beam_V1_0) || defined(TTGO_T_Beam_V1_0_SX1268) || defined(TTGO_T_Beam_V1_2) || defined(ESP32_DIY_LoRa_GPS) || defined(TTGO_T_LORA32_V2_1_GPS) || defined(TTGO_T_LORA32_V2_1_TNC) || defined(ESP32_DIY_1W_LoRa_GPS) || defined(TTGO_T_Beam_V1_2_SX1262) - if (setCpuFrequencyMhz(80)) { - logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "Main", "CPU frequency set to 80MHz"); - } else { - logger.log(logging::LoggerLevel::LOGGER_LEVEL_WARN, "Main", "CPU frequency unchanged"); + + void setup() { + Wire.end(); + #ifdef HAS_AXP192 + Wire.begin(SDA, SCL); + if (begin(Wire)) { + Serial.println("AXP192 init done!"); + } else { + Serial.println("AXP192 init failed!"); + } + activateLoRa(); + activateMeasurement(); + PMU.setChargerTerminationCurr(XPOWERS_AXP192_CHG_ITERM_LESS_10_PERCENT); + PMU.setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2); + PMU.setChargerConstantCurr(XPOWERS_AXP192_CHG_CUR_780MA); + PMU.setSysPowerDownVoltage(2600); + #endif + + #ifdef HAS_AXP2101 + Wire.begin(SDA, SCL); + if (begin(Wire)) { + Serial.println("AXP2101 init done!"); + } else { + Serial.println("AXP2101 init failed!"); + } + activateLoRa(); + activateMeasurement(); + PMU.setPrechargeCurr(XPOWERS_AXP2101_PRECHARGE_200MA); + PMU.setChargerTerminationCurr(XPOWERS_AXP2101_CHG_ITERM_25MA); + PMU.setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2); + PMU.setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_800MA); + PMU.setSysPowerDownVoltage(2600); + #endif } - #endif - }*/ + + /*void lowerCpuFrequency() { + #if defined(TTGO_T_Beam_V1_0) || defined(TTGO_T_Beam_V1_0_SX1268) || defined(TTGO_T_Beam_V1_2) || defined(ESP32_DIY_LoRa_GPS) || defined(TTGO_T_LORA32_V2_1_GPS) || defined(TTGO_T_LORA32_V2_1_TNC) || defined(ESP32_DIY_1W_LoRa_GPS) || defined(TTGO_T_Beam_V1_2_SX1262) + if (setCpuFrequencyMhz(80)) { + logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "Main", "CPU frequency set to 80MHz"); + } else { + logger.log(logging::LoggerLevel::LOGGER_LEVEL_WARN, "Main", "CPU frequency unchanged"); + } + #endif + }*/ } \ No newline at end of file diff --git a/src/power_utils.h b/src/power_utils.h index b57623b..c7d71df 100644 --- a/src/power_utils.h +++ b/src/power_utils.h @@ -6,15 +6,13 @@ namespace POWER_Utils { - void activateMeasurement(); + void activateMeasurement(); + void activateLoRa(); + void deactivateLoRa(); + bool begin(TwoWire &port); + void setup(); + //void lowerCpuFrequency(); - void activateLoRa(); - void deactivateLoRa(); - - bool begin(TwoWire &port); - void setup(); - - //void lowerCpuFrequency(); } #endif diff --git a/src/query_utils.cpp b/src/query_utils.cpp index fe093f4..a20f518 100644 --- a/src/query_utils.cpp +++ b/src/query_utils.cpp @@ -10,40 +10,40 @@ extern int stationMode; namespace QUERY_Utils { - String process(String query, String station, String queryOrigin) { - String answer; - if (query=="?APRS?" || query=="?aprs?" || query=="?Aprs?" || query=="H" || query=="h" || query=="HELP" || query=="Help" || query=="help" || query=="?") { - answer = "?APRSV ?APRSP ?APRSL ?APRSH ?WHERE callsign"; - } else if (query=="?APRSV" || query=="?aprsv" || query=="?Aprsv") { - answer = "CA2RXU_LoRa_iGate 1.2 v" + versionDate + " sM" + String(stationMode); - } else if (query=="?APRSP" || query=="?aprsp" || query=="?Aprsp") { - answer = "iGate QTH: " + String(currentWiFi->latitude,2) + " " + String(currentWiFi->longitude,2); - } else if (query=="?APRSL" || query=="?aprsl" || query=="?Aprsl") { - if (lastHeardStation.size() == 0) { - answer = "No Station Listened in the last " + String(Config.rememberStationTime) + "min."; - } else { - for (int i=0; ilatitude,2) + " " + String(currentWiFi->longitude,2); + } else if (query=="?APRSL" || query=="?aprsl" || query=="?Aprsl") { + if (lastHeardStation.size() == 0) { + answer = "No Station Listened in the last " + String(Config.rememberStationTime) + "min."; + } else { + for (int i=0; i posicion + Serial.println("estaciones escuchadas directo (ultimos 30 min)"); + answer = "?WHERE on development 73!"; + } + for(int i = station.length(); i < 9; i++) { + station += ' '; + } + if (queryOrigin == "APRSIS") { + return Config.callsign + ">APLRG1,TCPIP,qAC::" + station + ":" + answer;// + "\n"; + } else { //} if (queryOrigin == "LoRa") { + return Config.callsign + ">APLRG1,RFONLY,WIDE1-1::" + station + ":" + answer; } - answer.trim(); - } - } else if (query.indexOf("?APRSH") == 0 || query.indexOf("?aprsh") == 0 || query.indexOf("?Aprsh") == 0) { - // sacar callsign despues de ?APRSH - Serial.println("escuchaste a X estacion? en las ultimas 24 o 8 horas?"); - answer = "APRSH on development 73!"; - } else if (query.indexOf("?WHERE") == 0) { - // agregar callsign para completar donde esta X callsign --> posicion - Serial.println("estaciones escuchadas directo (ultimos 30 min)"); - answer = "?WHERE on development 73!"; } - for(int i = station.length(); i < 9; i++) { - station += ' '; - } - if (queryOrigin == "APRSIS") { - return Config.callsign + ">APLRG1,TCPIP,qAC::" + station + ":" + answer;// + "\n"; - } else { //} if (queryOrigin == "LoRa") { - return Config.callsign + ">APLRG1,RFONLY,WIDE1-1::" + station + ":" + answer; - } - } } \ No newline at end of file diff --git a/src/station_utils.cpp b/src/station_utils.cpp index 53bbf68..c45f754 100644 --- a/src/station_utils.cpp +++ b/src/station_utils.cpp @@ -12,93 +12,93 @@ extern String fourthLine; namespace STATION_Utils { - void deleteNotHeard() { - for (int i=0; i Listened Station"); - return true; - } + #ifndef TextSerialOutputForApp ////// This is just for debugging + Serial.print("Stations Near (last " + String(Config.rememberStationTime) + " minutes): "); + for (int k=0; k Station not Heard for last 30 min (Not Tx)\n"); - return false; - } - void checkBuffer() { - for (int i=0; i Listened Station"); + return true; + } + } + Serial.println(" ---> Station not Heard for last 30 min (Not Tx)\n"); + return false; } - packetBuffer.clear(); - for (int j=0; j") == -1) && (packet.indexOf(":`") == -1)) { - String sender = packet.substring(3,packet.indexOf(">")); - String tempAddressee = packet.substring(packet.indexOf("::") + 2); - String addressee = tempAddressee.substring(0,tempAddressee.indexOf(":")); - addressee.trim(); - String message = tempAddressee.substring(tempAddressee.indexOf(":")+1); - //Serial.println(String(millis()) + "," + sender + "," + addressee + "," + message); - packetBuffer.push_back(String(millis()) + "," + sender + "," + addressee + "," + message); - checkBuffer(); + // BORRAR ESTO !! + for (int i=0; i") == -1) && (packet.indexOf(":`") == -1)) { + String sender = packet.substring(3,packet.indexOf(">")); + String tempAddressee = packet.substring(packet.indexOf("::") + 2); + String addressee = tempAddressee.substring(0,tempAddressee.indexOf(":")); + addressee.trim(); + String message = tempAddressee.substring(tempAddressee.indexOf(":")+1); + //Serial.println(String(millis()) + "," + sender + "," + addressee + "," + message); + packetBuffer.push_back(String(millis()) + "," + sender + "," + addressee + "," + message); + checkBuffer(); + } } - } } \ No newline at end of file diff --git a/src/utils.cpp b/src/utils.cpp index fa8a444..233e544 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,7 +1,3 @@ -#include -#include -#include -#include #include #include "configuration.h" #include "station_utils.h" @@ -16,8 +12,6 @@ #include "display.h" #include "utils.h" -AsyncWebServer server(80); - extern WiFiClient espClient; extern Configuration Config; extern String versionDate; @@ -40,21 +34,13 @@ extern int rssi; extern float snr; extern int freqError; extern String distance; -extern String versionDate; extern uint32_t lastWiFiCheck; extern bool WiFiConnect; +extern bool WiFiConnected; -String name; -String email; - -unsigned long ota_progress_millis = 0; namespace Utils { - void notFound(AsyncWebServerRequest *request) { - request->send(404, "text/plain", "Not found"); - } - void processStatus() { String status = Config.callsign + ">APLRG1,WIDE1-1"; if (stationMode==1 || stationMode==2 || (stationMode==5 && WiFi.status() == WL_CONNECTED)) { @@ -77,7 +63,11 @@ namespace Utils { } String getLocalIP() { - return "IP : " + String(WiFi.localIP()[0]) + "." + String(WiFi.localIP()[1]) + "." + String(WiFi.localIP()[2]) + "." + String(WiFi.localIP()[3]); + if (!WiFiConnected) { + return "IP : 192.168.4.1"; + } else { + return "IP : " + String(WiFi.localIP()[0]) + "." + String(WiFi.localIP()[1]) + "." + String(WiFi.localIP()[2]) + "." + String(WiFi.localIP()[3]); + } } void setupDisplay() { @@ -85,8 +75,8 @@ namespace Utils { #if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa) digitalWrite(internalLedPin,HIGH); #endif - Serial.println("\nStarting iGate: " + Config.callsign + " Version: " + versionDate); - show_display(" LoRa APRS", "", " ( iGATE )", "", "", "Richonguzman / CA2RXU", " " + versionDate, 4000); + Serial.println("\nStarting Station: " + Config.callsign + " Version: " + versionDate); + show_display(" LoRa APRS", "", " ( iGATE & Digi )", "", "", "Richonguzman / CA2RXU", " " + versionDate, 4000); #if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa) digitalWrite(internalLedPin,LOW); #endif @@ -254,9 +244,11 @@ namespace Utils { void validateDigiFreqs() { if (stationMode==4) { if (abs(Config.loramodule.digirepeaterTxFreq - Config.loramodule.digirepeaterRxFreq) < 125000) { - Serial.println("Tx Freq less than 125kHz from Rx Freq ---> NOT VALID, check 'data/igate_conf.json'"); - show_display("Tx Freq is less than ", "125kHz from Rx Freq", "change it on : /data/", "igate_conf.json", 0); - while (1); + Serial.println("Tx Freq less than 125kHz from Rx Freq ---> NOT VALID"); + show_display("Tx Freq is less than ", "125kHz from Rx Freq", "device will autofix", "and then reboot", 1000); + Config.loramodule.digirepeaterTxFreq = Config.loramodule.digirepeaterRxFreq; // Inform about that but then change the digirepeaterTxFreq to digirepeaterRxFreq and reset the device + Config.writeFile(); + ESP.restart(); } } } @@ -318,83 +310,4 @@ namespace Utils { } } - void onOTAStart() { - Serial.println("OTA update started!"); - display_toggle(true); - lastScreenOn = millis(); - show_display("", "", "", " OTA update started!", "", "", "", 1000); - } - - void onOTAProgress(size_t current, size_t final) { - if (millis() - ota_progress_millis > 1000) { - display_toggle(true); - lastScreenOn = millis(); - ota_progress_millis = millis(); - Serial.printf("OTA Progress Current: %u bytes, Final: %u bytes\n", current, final); - show_display("", "", " OTA Progress : " + String((current*100)/final) + "%", "", "", "", "", 100); - } - } - - void onOTAEnd(bool success) { - display_toggle(true); - lastScreenOn = millis(); - if (success) { - Serial.println("OTA update finished successfully!"); - show_display("", "", " OTA update success!", "", " Rebooting ...", "", "", 4000); - } else { - Serial.println("There was an error during OTA update!"); - show_display("", "", " OTA update fail!", "", "", "", "", 4000); - } - } - - void startServer() { - if (stationMode==1 || stationMode==2 || (stationMode==5 && WiFi.status()==WL_CONNECTED)) { - server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { - request->send(200, "text/plain", "Hi " + Config.callsign + ", \n\nthis is your (Richonguzman/CA2RXU) LoRa APRS iGate , version " + versionDate + "\n\nTo update your firmware or filesystem go to: http://" + getLocalIP().substring(getLocalIP().indexOf(":")+3) + "/update\n\n\n73!"); - }); - - server.on("/test", HTTP_GET, [](AsyncWebServerRequest *request) { - request->send(SPIFFS, "/test_info_1.html", "text/html");//"application/json"); - }); - - server.on("/test2", HTTP_GET, [](AsyncWebServerRequest *request) { - request->send(SPIFFS, "/test1.html", "text/html"); - }); - - if (Config.ota.username != "" && Config.ota.password != "") { - ElegantOTA.begin(&server, Config.ota.username.c_str(), Config.ota.password.c_str()); - } else { - ElegantOTA.begin(&server); - } - ElegantOTA.setAutoReboot(true); - ElegantOTA.onStart(onOTAStart); - ElegantOTA.onProgress(onOTAProgress); - ElegantOTA.onEnd(onOTAEnd); - - server.on("/process_form.php", HTTP_POST, [](AsyncWebServerRequest *request){ - String message; - - if (request->hasParam("email", true) && request->hasParam("name", true)) { - email = request->getParam("email", true)->value(); - name = request->getParam("name", true)->value(); - - String responseMessage = "Received EMAIL: " + email + ", NAME: " + name; - - // Assuming you're sending an HTTP response, for example, in an HTTP server context - request->send(200, "text/plain", responseMessage); - } else { - // Handle the case where one or both parameters are missing - request->send(400, "text/plain", "Both EMAIL and NAME parameters are required."); - } - }); - - server.onNotFound(notFound); - - server.serveStatic("/", SPIFFS, "/"); - - server.begin(); - Serial.println("init : OTA Server ... done!"); - } - } - } \ No newline at end of file diff --git a/src/utils.h b/src/utils.h index 701ea1d..4cefb9c 100644 --- a/src/utils.h +++ b/src/utils.h @@ -17,7 +17,6 @@ namespace Utils { void onOTAStart(); void onOTAProgress(size_t current, size_t final); void onOTAEnd(bool success); - void startServer(); } diff --git a/src/web_utils.cpp b/src/web_utils.cpp new file mode 100644 index 0000000..e9f0647 --- /dev/null +++ b/src/web_utils.cpp @@ -0,0 +1,192 @@ +#include "ota_utils.h" +#include "web_utils.h" +#include "configuration.h" + +extern Configuration Config; + +extern const char web_index_html[] asm("_binary_data_embed_index_html_gz_start"); +extern const char web_index_html_end[] asm("_binary_data_embed_index_html_gz_end"); +extern const size_t web_index_html_len = web_index_html_end - web_index_html; + +extern const char web_style_css[] asm("_binary_data_embed_style_css_gz_start"); +extern const char web_style_css_end[] asm("_binary_data_embed_style_css_gz_end"); +extern const size_t web_style_css_len = web_style_css_end - web_style_css; + +extern const char web_script_js[] asm("_binary_data_embed_script_js_gz_start"); +extern const char web_script_js_end[] asm("_binary_data_embed_script_js_gz_end"); +extern const size_t web_script_js_len = web_script_js_end - web_script_js; + +extern const char web_bootstrap_css[] asm("_binary_data_embed_bootstrap_css_gz_start"); +extern const char web_bootstrap_css_end[] asm("_binary_data_embed_bootstrap_css_gz_end"); +extern const size_t web_bootstrap_css_len = web_bootstrap_css_end - web_bootstrap_css; + +extern const char web_bootstrap_js[] asm("_binary_data_embed_bootstrap_js_gz_start"); +extern const char web_bootstrap_js_end[] asm("_binary_data_embed_bootstrap_js_gz_end"); +extern const size_t web_bootstrap_js_len = web_bootstrap_js_end - web_bootstrap_js; + +namespace WEB_Utils { + + AsyncWebServer server(80); + + void loop() { + + } + + void handleNotFound(AsyncWebServerRequest *request) { + AsyncWebServerResponse *response = request->beginResponse(404, "text/plain", "Not found"); + response->addHeader("Cache-Control", "max-age=3600"); + request->send(response); + } + + void handleStatus(AsyncWebServerRequest *request) { + request->send(200, "text/plain", "OK"); + } + + void handleHome(AsyncWebServerRequest *request) { + AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", (const uint8_t*)web_index_html, web_index_html_len); + response->addHeader("Content-Encoding", "gzip"); + request->send(response); + } + + void handleReadConfiguration(AsyncWebServerRequest *request) { + File file = SPIFFS.open("/igate_conf.json"); + + String fileContent; + while(file.available()){ + fileContent += String((char)file.read()); + } + + request->send(200, "application/json", fileContent); + } + + void handleWriteConfiguration(AsyncWebServerRequest *request) { + Serial.println("Got new config from www"); + + int networks = request->getParam("wifi.APs", true)->value().toInt(); + + Config.wifiAPs = {}; + + for (int i=0; igetParam("wifi.AP." + String(i) + ".ssid", true)->value(); + wifiap.password = request->getParam("wifi.AP." + String(i) + ".password", true)->value(); + wifiap.latitude = request->getParam("wifi.AP." + String(i) + ".latitude", true)->value().toDouble(); + wifiap.longitude = request->getParam("wifi.AP." + String(i) + ".longitude", true)->value().toDouble(); + + Config.wifiAPs.push_back(wifiap); + } + + Config.callsign = request->getParam("callsign", true)->value(); + Config.stationMode = request->getParam("stationMode", true)->value().toInt(); + Config.iGateComment = request->getParam("iGateComment", true)->value(); + + Config.wifiAutoAP.password = request->getParam("wifi.autoAP.password", true)->value(); + Config.wifiAutoAP.powerOff = request->getParam("wifi.autoAP.powerOff", true)->value().toInt(); + + Config.digi.comment = request->getParam("digi.comment", true)->value(); + Config.digi.latitude = request->getParam("digi.latitude", true)->value().toDouble(); + Config.digi.longitude = request->getParam("digi.longitude", true)->value().toDouble(); + + Config.aprs_is.passcode = request->getParam("aprs_is.passcode", true)->value(); + Config.aprs_is.server = request->getParam("aprs_is.server", true)->value(); + Config.aprs_is.port = request->getParam("aprs_is.port", true)->value().toInt(); + Config.aprs_is.reportingDistance = request->getParam("aprs_is.reportingDistance", true)->value().toInt(); + + Config.loramodule.iGateFreq = request->getParam("lora.iGateFreq", true)->value().toInt(); + + if (request->hasParam("lora.digirepeaterTxFreq", true)) { + Config.loramodule.digirepeaterTxFreq = request->getParam("lora.digirepeaterTxFreq", true)->value().toInt(); + } + + if (request->hasParam("lora.digirepeaterRxFreq", true)) { + Config.loramodule.digirepeaterRxFreq = request->getParam("lora.digirepeaterRxFreq", true)->value().toInt(); + } + + Config.loramodule.spreadingFactor = request->getParam("lora.spreadingFactor", true)->value().toInt(); + Config.loramodule.signalBandwidth = request->getParam("lora.signalBandwidth", true)->value().toInt(); + Config.loramodule.codingRate4 = request->getParam("lora.codingRate4", true)->value().toInt(); + Config.loramodule.power = request->getParam("lora.power", true)->value().toInt(); + + Config.display.alwaysOn = request->hasParam("display.alwaysOn", true); + + if (!Config.display.alwaysOn) { + Config.display.timeout = request->getParam("display.timeout", true)->value().toInt(); + } + + Config.display.turn180 = request->hasParam("display.turn180", true); + + Config.syslog.active = request->hasParam("syslog.active", true); + + if (Config.syslog.active) { + Config.syslog.server = request->getParam("syslog.server", true)->value(); + Config.syslog.port = request->getParam("syslog.port", true)->value().toInt(); + } + + Config.bme.active = request->hasParam("bme.active", true); + + Config.ota.username = request->getParam("ota.username", true)->value(); + Config.ota.password = request->getParam("ota.password", true)->value(); + + Config.beaconInterval = request->getParam("other.beaconInterval", true)->value().toInt(); + Config.igateSendsLoRaBeacons = request->hasParam("other.igateSendsLoRaBeacons", true); + Config.igateRepeatsLoRaPackets = request->hasParam("other.igateRepeatsLoRaPackets", true); + Config.rememberStationTime = request->getParam("other.rememberStationTime", true)->value().toInt(); + Config.sendBatteryVoltage = request->hasParam("other.sendBatteryVoltage", true); + Config.externalVoltageMeasurement = request->hasParam("other.externalVoltageMeasurement", true); + + if (Config.externalVoltageMeasurement) { + Config.externalVoltagePin = request->getParam("other.externalVoltagePin", true)->value().toInt(); + } + + Config.writeFile(); + + AsyncWebServerResponse *response = request->beginResponse(302, "text/html", ""); + response->addHeader("Location", "/"); + request->send(response); + + ESP.restart(); + } + + void handleStyle(AsyncWebServerRequest *request) { + AsyncWebServerResponse *response = request->beginResponse_P(200, "text/css", (const uint8_t*)web_style_css, web_style_css_len); + response->addHeader("Content-Encoding", "gzip"); + request->send(response); + } + + void handleScript(AsyncWebServerRequest *request) { + AsyncWebServerResponse *response = request->beginResponse_P(200, "text/javascript", (const uint8_t*)web_script_js, web_script_js_len); + response->addHeader("Content-Encoding", "gzip"); + request->send(response); + } + + void handleBootstrapStyle(AsyncWebServerRequest *request) { + AsyncWebServerResponse *response = request->beginResponse_P(200, "text/css", (const uint8_t*)web_bootstrap_css, web_bootstrap_css_len); + response->addHeader("Content-Encoding", "gzip"); + response->addHeader("Cache-Control", "max-age=3600"); + request->send(response); + } + + void handleBootstrapScript(AsyncWebServerRequest *request) { + AsyncWebServerResponse *response = request->beginResponse_P(200, "text/javascript", (const uint8_t*)web_bootstrap_js, web_bootstrap_js_len); + response->addHeader("Content-Encoding", "gzip"); + response->addHeader("Cache-Control", "max-age=3600"); + request->send(response); + } + + void setup() { + server.on("/", HTTP_GET, handleHome); + server.on("/status", HTTP_GET, handleStatus); + server.on("/configuration.json", HTTP_GET, handleReadConfiguration); + server.on("/configuration.json", HTTP_POST, handleWriteConfiguration); + server.on("/style.css", HTTP_GET, handleStyle); + server.on("/script.js", HTTP_GET, handleScript); + server.on("/bootstrap.css", HTTP_GET, handleBootstrapStyle); + server.on("/bootstrap.js", HTTP_GET, handleBootstrapScript); + + OTA_Utils::setup(&server); // Include OTA Updater for WebServer + + server.onNotFound(handleNotFound); + + server.begin(); + } +} diff --git a/src/web_utils.h b/src/web_utils.h new file mode 100644 index 0000000..c9dd7b4 --- /dev/null +++ b/src/web_utils.h @@ -0,0 +1,31 @@ +#ifndef WEB_UTILS_H_ +#define WEB_UTILS_H_ + +#include +#include +#include +#include +#include + + +namespace WEB_Utils { + + void loop(); + + void handleNotFound(AsyncWebServerRequest *request); + void handleStatus(AsyncWebServerRequest *request); + void handleHome(AsyncWebServerRequest *request); + + //void handleReadConfiguration(AsyncWebServerRequest *request); + //void handleWriteConfiguration(AsyncWebServerRequest *request); + + void handleStyle(AsyncWebServerRequest *request); + void handleScript(AsyncWebServerRequest *request); + void handleBootstrapStyle(AsyncWebServerRequest *request); + void handleBootstrapScript(AsyncWebServerRequest *request); + + void setup(); + +} + +#endif \ No newline at end of file diff --git a/src/wifi_utils.cpp b/src/wifi_utils.cpp index 777d952..e3a3284 100644 --- a/src/wifi_utils.cpp +++ b/src/wifi_utils.cpp @@ -10,93 +10,141 @@ extern int myWiFiAPIndex; extern int myWiFiAPSize; extern int stationMode; extern uint32_t previousWiFiMillis; +extern bool WiFiConnected; +extern long WiFiAutoAPTime; +extern bool WiFiAutoAPStarted; namespace WIFI_Utils { - void checkWiFi() { - if ((WiFi.status() != WL_CONNECTED) && ((millis() - previousWiFiMillis) >= 30*1000)) { - Serial.print(millis()); - Serial.println("Reconnecting to WiFi..."); - WiFi.disconnect(); - WiFi.reconnect(); - previousWiFiMillis = millis(); - } - } - - void startWiFi() { - int wifiCounter = 0; - WiFi.mode(WIFI_STA); - WiFi.disconnect(); - delay(500); - unsigned long start = millis(); - show_display("", "", "Connecting to Wifi:", "", currentWiFi->ssid + " ...", 0); - Serial.print("\nConnecting to WiFi '"); Serial.print(currentWiFi->ssid); Serial.println("' ..."); - WiFi.begin(currentWiFi->ssid.c_str(), currentWiFi->password.c_str()); - while (WiFi.status() != WL_CONNECTED && wifiCounter<2) { - delay(500); - #if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa) - digitalWrite(internalLedPin,HIGH); - #endif - Serial.print('.'); - delay(500); - #if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa) - digitalWrite(internalLedPin,LOW); - #endif - if ((millis() - start) > 10000){ - delay(1000); - if(myWiFiAPIndex >= (myWiFiAPSize-1)) { - myWiFiAPIndex = 0; - if (stationMode==5) { - wifiCounter++; - } - } else { - myWiFiAPIndex++; + void checkWiFi() { + if ((WiFi.status() != WL_CONNECTED) && ((millis() - previousWiFiMillis) >= 30*1000) && !WiFiAutoAPStarted) { + Serial.print(millis()); + Serial.println("Reconnecting to WiFi..."); + WiFi.disconnect(); + WiFi.reconnect(); + previousWiFiMillis = millis(); } - currentWiFi = &Config.wifiAPs[myWiFiAPIndex]; - start = millis(); - Serial.print("\nConnecting to WiFi '"); Serial.print(currentWiFi->ssid); Serial.println("' ..."); - show_display("", "", "Connecting to Wifi:", "", currentWiFi->ssid + " ...", 0); - WiFi.disconnect(); - WiFi.begin(currentWiFi->ssid.c_str(), currentWiFi->password.c_str()); - } } - #if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa) - digitalWrite(internalLedPin,LOW); - #endif - if (WiFi.status() == WL_CONNECTED) { - Serial.print("Connected as "); - Serial.println(WiFi.localIP()); - show_display("", "", " Connected!!", "" , " loading ...", 1000); - } else if (WiFi.status() != WL_CONNECTED && stationMode==5) { - Serial.println("\nNot connected to WiFi! (DigiRepeater Mode)"); - show_display("", "", " WiFi Not Connected!", " DigiRepeater MODE" , " loading ...", 2000); - } - } - void setup() { - if (stationMode==1 || stationMode==2) { - if (stationMode==1) { - Serial.println("stationMode ---> iGate (only Rx)"); - } else { - Serial.println("stationMode ---> iGate (Rx + Tx)"); - } - startWiFi(); - btStop(); - } else if (stationMode==3 || stationMode==4) { - if (stationMode==3) { - Serial.println("stationMode ---> DigiRepeater (Rx freq == Tx freq)"); - } else { - Serial.println("stationMode ---> DigiRepeater (Rx freq != Tx freq)"); - } - WiFi.mode(WIFI_OFF); - btStop(); - } else if (stationMode==5) { - Serial.println("stationMode ---> iGate when Wifi/APRS available (DigiRepeater when not)"); - } else { - Serial.println("stationMode ---> NOT VALID, check '/data/igate_conf.json'"); - show_display("------- ERROR -------", "stationMode Not Valid", "change it on : /data/", "igate_conf.json", 0); - while (1); + void startWiFi() { + bool startAP = false; + if (currentWiFi->ssid == "") { + startAP = true; + } else { + int wifiCounter = 0; + WiFi.mode(WIFI_STA); + WiFi.disconnect(); + delay(500); + unsigned long start = millis(); + show_display("", "", "Connecting to Wifi:", "", currentWiFi->ssid + " ...", 0); + Serial.print("\nConnecting to WiFi '"); Serial.print(currentWiFi->ssid); Serial.println("' ..."); + WiFi.begin(currentWiFi->ssid.c_str(), currentWiFi->password.c_str()); + while (WiFi.status() != WL_CONNECTED && wifiCounter 10000){ + delay(1000); + if(myWiFiAPIndex >= (myWiFiAPSize-1)) { + myWiFiAPIndex = 0; + if (stationMode==5) { + wifiCounter++; + } + } else { + myWiFiAPIndex++; + } + wifiCounter++; + currentWiFi = &Config.wifiAPs[myWiFiAPIndex]; + start = millis(); + Serial.print("\nConnecting to WiFi '"); Serial.print(currentWiFi->ssid); Serial.println("' ..."); + show_display("", "", "Connecting to Wifi:", "", currentWiFi->ssid + " ...", 0); + WiFi.disconnect(); + WiFi.begin(currentWiFi->ssid.c_str(), currentWiFi->password.c_str()); + } + } + } + #if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa) + digitalWrite(internalLedPin,LOW); + #endif + if (WiFi.status() == WL_CONNECTED) { + Serial.print("Connected as "); + Serial.println(WiFi.localIP()); + show_display("", "", " Connected!!", "" , " loading ...", 1000); + } else if (WiFi.status() != WL_CONNECTED) { + startAP = true; + + Serial.println("\nNot connected to WiFi! Starting Auto AP"); + show_display("", "", " WiFi Not Connected!", "" , " loading ...", 1000); + } + WiFiConnected = !startAP; + if (startAP) { + Serial.println("\nNot connected to WiFi! Starting Auto AP"); + show_display("", "", " Starting Auto AP", " Please connect to it " , " loading ...", 1000); + + WiFi.mode(WIFI_MODE_NULL); + + WiFi.mode(WIFI_AP); + WiFi.softAP(Config.callsign + " AP", "1234567890"); + + WiFiAutoAPTime = millis(); + WiFiAutoAPStarted = true; + } + } + + void checkIfAutoAPShouldPowerOff() { + if (WiFiAutoAPStarted && Config.wifiAutoAP.powerOff > 0) { + if (WiFi.softAPgetStationNum() > 0) { + WiFiAutoAPTime = 0; + } else { + if (WiFiAutoAPTime == 0) { + WiFiAutoAPTime = millis(); + } else if ((millis() - WiFiAutoAPTime) > Config.wifiAutoAP.powerOff * 60 * 1000) { + Serial.println("Stopping auto AP"); + + WiFiAutoAPStarted = false; + WiFi.softAPdisconnect(true); + + Serial.println("Auto AP stopped (timeout)"); + } + } + } + } + + void setup() { + if (stationMode==1 || stationMode==2) { + if (stationMode==1) { + Serial.println("stationMode ---> iGate (only Rx)"); + } else { + Serial.println("stationMode ---> iGate (Rx + Tx)"); + } + startWiFi(); + btStop(); + } else if (stationMode==3 || stationMode==4) { + if (stationMode==3) { + Serial.println("stationMode ---> DigiRepeater (Rx freq == Tx freq)"); + } else { + Serial.println("stationMode ---> DigiRepeater (Rx freq != Tx freq)"); + } + WiFi.mode(WIFI_OFF); + btStop(); + } else if (stationMode==5) { + Serial.println("stationMode ---> iGate when Wifi/APRS available (DigiRepeater when not)"); + } else { + Serial.println("stationMode ---> NOT VALID"); + show_display("------- ERROR -------", "stationMode Not Valid", "device will autofix", "and then reboot", 1000); + + Config.stationMode = 1; // Inform about that but then change the station mode to 1 and reset the device + Config.writeFile(); + + ESP.restart(); + } + } - } } \ No newline at end of file diff --git a/src/wifi_utils.h b/src/wifi_utils.h index fc4c5e9..d74eedf 100644 --- a/src/wifi_utils.h +++ b/src/wifi_utils.h @@ -7,7 +7,9 @@ namespace WIFI_Utils { void checkWiFi(); void startWiFi(); + void checkIfAutoAPShouldPowerOff(); void setup(); + } diff --git a/tools/compress.py b/tools/compress.py new file mode 100644 index 0000000..c2407b0 --- /dev/null +++ b/tools/compress.py @@ -0,0 +1,20 @@ +import gzip + +files = [ + 'data_embed/index.html', + 'data_embed/script.js', + 'data_embed/style.css', + 'data_embed/bootstrap.js', + 'data_embed/bootstrap.css', +] + + +for src in files: + out = src + ".gz" + + + with open(src, 'rb') as f: + content = f.read() + + with open(out, 'wb') as f: + f.write(gzip.compress(content, compresslevel=9)) \ No newline at end of file