Compare commits

..

96 Commits

Author SHA1 Message Date
philippe44
d27825d62c Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2025-12-26 21:53:56 +01:00
philippe44
a33f699dc7 fix messages 2025-12-26 21:53:43 +01:00
philippe44
70a79b8731 Update Platform_build.yml 2025-12-26 21:45:05 +01:00
philippe44
b585f24f37 Create client_info.h 2025-12-26 21:43:51 +01:00
philippe44
9d8be934ac Update CMakeLists.txt 2025-12-26 21:37:35 +01:00
philippe44
d55768ffe1 Update CMakeLists.txt 2025-12-26 21:35:33 +01:00
philippe44
dd62f1aae6 Update Platform_build.yml 2025-12-26 21:23:41 +01:00
philippe44
adfb8b0767 Delete components/spotify/client_info.h 2025-12-26 21:21:55 +01:00
philippe44
83d6322bcf Update client_info.h 2025-12-26 21:14:07 +01:00
philippe44
cd56b20a06 Update Platform_build.yml 2025-12-26 20:43:33 +01:00
github-actions
ec60400673 Update prebuilt objects [skip actions] 2025-12-26 17:16:37 +00:00
philippe44
25f4855375 Update Platform_build.yml 2025-12-26 18:07:49 +01:00
philippe44
1d21662f89 Update Platform_build.yml 2025-12-26 18:05:22 +01:00
philippe44
1835423924 Update Platform_build.yml 2025-12-26 18:04:16 +01:00
philippe44
2abdd91aa6 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2025-12-26 18:02:33 +01:00
philippe44
d7093b0bab Spotify secrets 2025-12-26 18:02:29 +01:00
philippe44
4d36958008 Update Platform_build.yml 2025-12-26 17:24:47 +01:00
philippe44
2d90c823af Update Platform_build.yml 2025-12-26 17:20:20 +01:00
philippe44
289026527b Spotify 2025-12-25 12:22:18 +01:00
philippe44
bca8c21322 Merge pull request #511 from Jeeere/master-v4.3
Fix PCM5102 pin configuration example in README.md
2025-12-25 12:14:54 +01:00
philippe44
f6763ebead Merge pull request #521 from RASPIAUDIO/master-v4.3
Raspiaudio Muse Luxe 2025/11/26
2025-12-25 12:13:29 +01:00
philippe44
f64c09cf30 Update README.md 2025-12-25 12:05:40 +01:00
philippe44
c0d2add55b Use client id & secret for Spotify 2025-12-25 00:02:02 +01:00
philippe44
1e3de24bdf ignore 2025-12-24 19:24:31 +01:00
philippe44
f87d38b5e9 add client_info 2025-12-24 19:24:10 +01:00
RASPIAUDIOadmin
23234f7189 Raspiaudio 2025/11/26 2025-11-26 12:03:56 +01:00
Jere
9f783b6b5d Fix PCM5102 pin configuration example in README.md
Add missing newline
Change SCL to SCK
2025-10-09 13:50:40 +03:00
philippe44
752cfbf3b2 Update .gitattributes 2025-03-30 14:19:09 +02:00
philippe44
cecb7fd876 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2025-02-17 23:26:07 +01:00
philippe44
e92e431b45 add MCK in get_dac_config 2025-02-17 23:26:01 +01:00
philippe44
db792e47bd Update Platform_build.yml 2025-02-17 22:47:05 +01:00
philippe44
a22f75a13a limit display checks 2025-02-17 22:39:48 +01:00
philippe44
1f220895e6 Update README.md 2025-02-17 12:55:44 +01:00
philippe44
769ff99f7d Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-09-28 23:17:23 +02:00
philippe44
424fb93ec4 add 2nd encoder for volume only 2024-09-28 23:17:09 +02:00
philippe44
e270963dbd Update README.md 2024-09-28 23:11:52 +02:00
philippe44
2cae41d29c Update README.md 2024-09-28 23:11:10 +02:00
philippe44
84b95cd79c Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-09-28 18:55:48 +02:00
philippe44
6369f4bd69 another misplaced NVS #ifdef 2024-09-28 18:55:43 +02:00
philippe44
4c1bca3166 Update CHANGELOG 2024-09-28 14:43:23 +02:00
philippe44
3a5163e6f6 Update esp_app_main.c
autoexec default was created at the wrong place!
2024-09-28 14:42:08 +02:00
philippe44
cbe42b56bc Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-09-27 18:53:46 +02:00
philippe44
ab9812cb75 make i2s emergency stop platform independant 2024-09-27 18:52:26 +02:00
philippe44
084caedd7e Update README.md 2024-09-27 16:35:15 +02:00
philippe44
f254bf49af Update README.md 2024-09-27 15:35:59 +02:00
philippe44
66bd26f007 Update README.md 2024-09-27 15:35:03 +02:00
philippe44
dd6c932c39 Update README.md 2024-09-27 15:06:38 +02:00
philippe44
50070378ad Merge pull request #440 from digidocs/loudness_plugin_fix1
SqueezeESP32 plugin loudness control fix
2024-09-12 12:33:31 +02:00
github-actions
b50bc8f376 Update prebuilt objects [skip actions] 2024-09-12 09:32:21 +00:00
philippe44
e6723dfa2f Update CHANGELOG 2024-09-12 11:27:55 +02:00
philippe44
ffaff5ac27 Merge pull request #430 from StefanKrupop/aw9523_expander
Add support for AW9523 GPIO expander
2024-09-12 11:25:31 +02:00
philippe44
33ef4b01e7 Update CHANGELOG 2024-09-12 11:24:23 +02:00
github-actions
302865b167 Update prebuilt objects [skip actions] 2024-09-11 18:05:43 +00:00
philippe44
7f0ae69e81 Merge pull request #439 from digidocs/eq_update_fix2
Fix for ESP32 equalizer settings don't update when expected
2024-09-11 20:02:25 +02:00
David Carr
e21e2cf7f9 Equalizer: change gain to int8 and memcmp-based update check 2024-09-04 15:07:42 -05:00
David Carr
57cd009e4c Revert "Equalizer: check if requested gain has changed before updating"
This reverts commit 78e8d60021.
2024-09-04 14:52:27 -05:00
David Carr
fdd8b0a4c9 Change to //= operator 2024-09-04 14:48:07 -05:00
David Carr
78e8d60021 Equalizer: check if requested gain has changed before updating 2024-09-04 13:49:51 -05:00
digidocs
4068e07a45 Fix for I2S noise burst when ESP32 panic occurs (#437) 2024-09-02 08:51:40 -04:00
David Carr
f8d7ac23e1 Updated SqueezeESP32 plugin and zip file 2024-08-21 12:07:57 -05:00
David Carr
befc81f573 Fix for wrong loudness value being sent when user requests loudness 0 2024-08-21 11:01:18 -05:00
David Carr
a633524936 Fix for ESP32 equalizer settings don't update when expected 2024-08-21 00:08:06 -05:00
Stefan Krupop
9d71b8ee26 Added "aw9523" to list of possible expanders 2024-07-26 21:01:10 +02:00
Stefan Krupop
672aca8258 Fixed resetting interrupt 2024-07-26 20:18:55 +02:00
Stefan Krupop
a2351ba0d5 Add support for AW9523 port expander 2024-07-25 01:13:14 +02:00
wizmo2
40a698e2f1 add checks for grfX handlers to prevent divide-by-zero with slimproto (#424)
Thank you!
2024-06-07 18:19:33 -04:00
github-actions
38d28ae8c4 Update prebuilt objects [skip actions] 2024-05-07 21:41:36 +00:00
Sebastien L
21407e8c1c Fix crash from led_vu when no display - release 2024-05-07 17:39:02 -04:00
github-actions
12aa555ff3 Update prebuilt objects [skip actions] 2024-03-22 03:03:10 +00:00
philippe44
cb47ec855b Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-03-20 22:36:28 -07:00
philippe44
787a5d9a6e spdif glitch at track transition 2024-03-20 22:36:22 -07:00
github-actions
7b9deb795c Update prebuilt objects [skip actions] 2024-01-28 06:33:54 +00:00
philippe44
4b1f8a8d4b see CHANGELOG - release 2024-01-27 22:32:01 -08:00
philippe44
4f8661100b Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-01-19 16:14:12 -08:00
philippe44
6b2eb1b3c0 see CHANGELOG 2024-01-19 16:14:08 -08:00
github-actions
f3593fa2f4 Update prebuilt objects [skip actions] 2024-01-17 02:53:45 +00:00
philippe44
ccc7b86369 See CHANGELOG - release 2024-01-16 18:51:33 -08:00
philippe44
9e3c6dcf30 Update README.md 2024-01-10 21:53:03 -08:00
github-actions
7be81887a6 Update prebuilt objects [skip actions] 2024-01-11 05:41:18 +00:00
philippe44
29f71fc677 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-01-10 21:36:53 -08:00
philippe44
a14a6edc1b reset flac packet flag - release 2024-01-10 21:36:50 -08:00
github-actions
35bf0a3c10 Update prebuilt objects [skip actions] 2024-01-11 03:00:35 +00:00
philippe44
55a8658f3a OggFlac & displayer fixes (see CHANGELOG) - release 2024-01-10 18:58:47 -08:00
github-actions
b0d9e1668c Update prebuilt objects [skip actions] 2024-01-04 08:48:28 +00:00
philippe44
922367baf5 no false alarm on OggS miss + homogeneous use of u32/size_t/int - release 2024-01-04 00:46:36 -08:00
philippe44
01320db007 always copy granule unless it's -1 (not valid) 2024-01-03 23:47:00 -08:00
philippe44
f677695fc7 handle pages with no terminated packet 2024-01-03 23:25:08 -08:00
github-actions
7902af2bf0 Update prebuilt objects [skip actions] 2024-01-03 04:08:04 +00:00
philippe44
5faecb08f6 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-01-02 18:15:43 -08:00
philippe44
8e3d91020f bring ogg parsing context purely inside stream.c - release 2024-01-02 18:15:39 -08:00
github-actions
4f54a47c83 Update prebuilt objects [skip actions] 2024-01-02 08:50:27 +00:00
philippe44
58840f894f Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-01-02 00:44:20 -08:00
philippe44
94109ebf38 fix copy typo - release 2024-01-02 00:43:32 -08:00
github-actions
fc20618fa2 Update prebuilt objects [skip actions] 2024-01-02 08:37:41 +00:00
philippe44
70720d3445 don't allocate segments - release 2024-01-02 00:35:47 -08:00
github-actions
4abe1304e8 Update prebuilt objects [skip actions] 2024-01-01 02:54:46 +00:00
113 changed files with 1120 additions and 669 deletions

2
.gitattributes vendored
View File

@@ -1,5 +1,5 @@
# Auto detect text files and perform LF normalization
* text=auto
# * text=auto
# Custom for Visual Studio
*.cs diff=csharp

View File

@@ -136,6 +136,8 @@ jobs:
name: prebuilt_objects
- name: Build the firmware
if: ${{ needs.bootstrap.outputs.mock == 0 }}
env:
SPOTIFY_SECRET: -DCLIENT_ID="${{ secrets.SPOTIFY_CLIENT_ID }}" -DCLIENT_SECRET="${{ secrets.SPOTIFY_CLIENT_SECRET }}"
run: |
. ${IDF_PYTHON_ENV_PATH}/bin/activate
echo "Copying target sdkconfig"
@@ -171,7 +173,7 @@ jobs:
zip build/${artifact_file_name} partitions*.csv components/ build/*.bin build/bootloader/bootloader.bin build/partition_table/partition-table.bin build/flash_project_args build/size_*.txt
fi
- name: Upload Build Artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: ${{ needs.bootstrap.outputs.mock == 0 }}
with:
name: ${{ env.artifact_prefix }}

1
.gitignore vendored
View File

@@ -18,3 +18,4 @@ components/wifi-manager/UML-State-Machine-in-C
envfile.txt
artifacts
web-installer
client_info.h

View File

@@ -1,3 +1,51 @@
2025-12-25
- Use dedicated client_id and client_secret for spotify
2025-02-17
- reverse some checks on display not NULL in gds.c. As it is about being fast, I'd prefer the caller to know that there is no display and don't call. I'm sure I have missed something when there is only led_vu and no display, but people will remind me soon enough :-)
2024-09-28
- add dedicated volume encoder
- fix memory leak in rotary config creation
2024-09-28
- create autoexec NVS entry at the right place (not only whne BT is enabled!
- try to make i2s panic mode work for all esp versions
2024-09-12
- add AW9523 GPIO expander credits @Stefan Krupop (https://github.com/sle118/squeezelite-esp32/pull/430
2024-09-10
- Merge pull request # 439 from digidocs/eq_update_fix2 (# 309)
- Fix for I2S noise burst when ESP32 panic occurs (# 437)
2024-05-05
- Fix crash when led_vu is configured without display
2024-01-27
- complete libflac fix and add chaining enablement
- fixed stream Ogg demux issue with unknown granule
2024-01-19
- fixed libflac with OggFlac
- AirPlay missed frame logging
2024-01-16
- catch-up with cspot latest
- refactor airplay flush/first packet
- new libFLAC that supports multi-stream OggFlac
- fix output threshold
- log missed frames
2024-01-10
- add OggFlac to stream metadata
- fix OggFlac deadlock in flac callback when not enough data in streambuf
- fix no displayer due to threadshold too high (use 500ms instead)
- reset outputbuf when cspot starts
2024-01-01
- ogg stream are parsed to foward metadata to LMS
- fix some ogg parsing on multi-stream containers
2023-11-19
- more robust (?) airplay RTP frame recovery
- initialize of scratch string in monitor (trying to figure out random reboot)
@@ -10,7 +58,7 @@
- force gpio_pad_select_gpio in dac_controlset in case somebody uses UART gpio's (or other pre-programmed)
2023-11-08
- execute dac_controlset even whne there is no i2s (for gpio)
- execute dac_controlset even when there is no i2s (for gpio)
2023-11-07
- led-vu gain + misc fixes

View File

@@ -73,7 +73,7 @@ set_target_properties(recovery.elf PROPERTIES LINK_LIBRARIES "${BCA};idf::app_re
# build squeezelite, add app_squeezelite to the link
add_executable(squeezelite.elf "CMakeLists.txt")
add_dependencies(squeezelite.elf recovery.elf)
set_target_properties(squeezelite.elf PROPERTIES LINK_LIBRARIES "${BCA};idf::app_squeezelite;-Wl,--Map=${BUILD_DIR}/squeezelite.map")
set_target_properties(squeezelite.elf PROPERTIES LINK_LIBRARIES "${BCA};idf::app_squeezelite;-Wl,--Map=${BUILD_DIR}/squeezelite.map,--wrap=esp_panic_handler")
add_custom_command(
TARGET squeezelite.elf
POST_BUILD
@@ -228,4 +228,4 @@ endif()
# target_compile_definitions(__idf_wear_levelling PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_wifi_provisioning PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_wpa_supplicant PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_xtensa PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_xtensa PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)

View File

@@ -15,6 +15,7 @@ Depending on the hardware connected to the esp32, you can send audio to a local
But squeezelite-esp32 is highly extensible and you can add
- [Buttons](#buttons) and [Rotary Encoder](#rotary-encoder) and map/combine them to various functions (play, pause, volume, next ...)
- [Volume Encoder](#volume-rotary-encoder) for a dedicated volume rotary encoder
- [GPIO expander](#gpio-expanders) (buttons, led and rotary)
- [IR receiver](#infrared) (no pullup resistor or capacitor needed, just the 38kHz receiver)
- [Monochrome, GrayScale or Color displays](#display) using SPI or I2C (supported drivers are SH1106, SSD1306, SSD1322, SSD1326/7, SSD1351, ST7735, ST7789 and ILI9341).
@@ -147,12 +148,12 @@ VCC - 3.3V
GND - GND
FLT - GND
DMP - GND
SCL - GND
SCK - GND
BCK - (BCK - see below)
DIN - (DO - see below)
LCK - (WS - see below)
LCK - (WS - see below)
FMT - GND
XMT - 3.3V
XMT - 3.3V
Use the `squeezelite-esp32-I2S-4MFlash-sdkconfig.defaults` configuration file.
@@ -237,13 +238,14 @@ Ground -------------------------- coax signal ground
### Display
The NVS parameter "display_config" sets the parameters for an optional display. It can be I2C (see [here](#i2c) for shared bus) or SPI (see [here](#spi) for shared bus) Syntax is
```
I2C,width=<pixels>,height=<pixels>[address=<i2c_address>][,reset=<gpio>][,HFlip][,VFlip][driver=SSD1306|SSD1326[:1|4]|SSD1327|SH1106]
SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,back=<gpio>][,reset=<gpio>][,speed=<speed>][,HFlip][,VFlip][driver=SSD1306|SSD1322|SSD1326[:1|4]|SSD1327|SH1106|SSD1675|ST7735|ST7789[:x=<offset>][:y=<offset>]|ILI9341[:16|18][,rotate]]
I2C,width=<pixels>,height=<pixels>[address=<i2c_address>][,reset=<gpio>][,HFlip][,VFlip][,invert][driver=SSD1306|SSD1326[:1|4]|SSD1327|SH1106]
SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,back=<gpio>][,reset=<gpio>][,speed=<speed>][,HFlip][,VFlip][,invert][driver=SSD1306|SSD1322|SSD1326[:1|4]|SSD1327|SH1106|SSD1675|ST7735|ST7789[:x=<offset>][:y=<offset>]|ILI9341[:16|18][,rotate]]
```
- back: a LED backlight used by some older devices (ST7735). It is PWM controlled for brightness
- reset: some display have a reset pin that is should normally be pulled up if unused. Most displays require reset and will not initialize well otherwise.
- VFlip and HFlip are optional can be used to change display orientation
- rotate: for non-square *drivers*, move to portrait mode. Note that *width* and *height* must be inverted then
- invert: invert each pixel colorspace
- Default speed is 8000000 (8MHz) but SPI can work up to 26MHz or even 40MHz
- SH1106 is 128x64 monochrome I2C/SPI [here](https://www.waveshare.com/wiki/1.3inch_OLED_HAT)
- SSD1306 is 128x32 monochrome I2C/SPI [here](https://www.buydisplay.com/i2c-blue-0-91-inch-oled-display-module-128x32-arduino-raspberry-pi)
@@ -308,7 +310,7 @@ The parameter "gpio_exp_config" is a semicolon (;) separated list with following
```
model=<model>,addr=<addr>,[,port=system|dac][,base=<n>][,count=<n>][,intr=<gpio>][,cs=<gpio>][,speed=<Hz>]
```
- model: pca9535, pca85xx, mcp23017 and mcp23s17 (SPI version)
- model: pca9535, pca85xx, mcp23017, aw9523 and mcp23s17 (SPI version)
- addr: chip i2c/spi address (decimal)
- port (I2C): use either "system" port (shared with display for example) or "dac" port (system is default)
- cs (SPI): gpio used for Chip Select
@@ -341,9 +343,9 @@ The latest LMS plugin update is required to set the visualizer mode and brightne
| \<playerid\> dmx \<R,G,B,R,G,B, ... R,G,B\> \[\<offset\>\] | Sets the LED color starting at position "offset"<br /> with "R"(red),"G"(green),and "B"(blue) color sequences.<br />Add additional RGB values to the delimited string to set multiple LEDs.<br /> |
### Rotary Encoder
One rotary encoder is supported, quadrature shift with press. Such encoders usually have 2 pins for encoders (A and B), and common C that must be set to ground and an optional SW pin for press. A, B and SW must be pulled up, so automatic pull-up is provided by ESP32, but you can add your own resistors. A bit of filtering on A and B (~470nF) helps for debouncing which is not made by software.
One general rotary encoder is supported, quadrature shift with press. Such encoders usually have 2 pins for encoders (A and B), and common C that must be set to ground and an optional SW pin for press. A, B and SW must be pulled up, so automatic pull-up is provided by ESP32, but you can add your own resistors. A bit of filtering on A and B (~470nF) helps for debouncing which is not made by software.
Encoder is normally hard-coded to respectively knob left, right and push on LMS and to volume down/up/play toggle on BT and AirPlay. Using the option 'volume' makes it hard-coded to volume down/up/play toggle all the time (even in LMS). The option 'longpress' allows an alternate mode when SW is long-pressed. In that mode, left is previous, right is next and press is toggle. Every long press on SW alternates between modes (the main mode actual behavior depends on 'volume').
Encoder is normally hard-coded to respectively knob left, right and push on LMS and to volume down/up/play toggle on BT, AirPlay and Spotify. Using the option 'volume' makes it hard-coded to volume down/up/play toggle all the time (even in LMS). The option 'longpress' allows an alternate mode when SW is long-pressed. In that mode, left is previous, right is next and press is toggle. Every long press on SW alternates between modes (the main mode actual behavior depends on 'volume').
There is also the possibility to use 'knobonly' option (exclusive with 'volume' and 'longpress'). This mode attempts to offer a single knob full navigation which is a bit contorded due to LMS UI's principles. Left, Right and Press obey to LMS's navigation rules and especially Press always goes to lower submenu item, even when navigating in the Music Library. That causes a challenge as there is no 'Play', 'Back' or 'Pause' button. Workaround are as of below:
- longpress is 'Play'
@@ -364,7 +366,16 @@ The SW gpio is optional, you can re-affect it to a pure button if you prefer but
See also the "IMPORTANT NOTE" on the "Buttons" section and remember that when 'lms_ctrls_raw' (see below) is activated, none of these knobonly,volume,longpress options apply, raw button codes (not actions) are simply sent to LMS
**Note that gpio 36 and 39 are input only and cannot use interrupt, so they cannot be set to A or B. When using them for SW, a 100ms polling is used which is expensive**
**Note that on esp32, gpio 36 and 39 are input only and cannot use interrupt, so they cannot be set to A or B. When using them for SW, a 100ms polling is used which is expensive**
### Volume Rotary Encoder
One dedicated volume rotary encoder is supported, quadrature shift with press. Encoder is hard-coded to volume-up, down and play toggle for LMS, BT, AirPlay and Spotify (see note above for filtering and HW note as well GPIO 36 and 39 on esp32)
Use parameter volume_rotary with the following syntax:
```
A=<gpio>,B=<gpio>[,SW=gpio>]
```
### Buttons
Buttons are described using a JSON string with the following syntax
@@ -632,12 +643,20 @@ docker run -it -v `pwd`:/workspace/squeezelite-esp32 sle118/squeezelite-esp32-id
The above command will mount this repo into the docker container and start a bash terminal. From there, simply run idf.py build to build, etc. Note that at the time of writing these lines, flashing is not possible for docker running under windows https://github.com/docker/for-win/issues/1018.
### Manual Install of ESP-IDF
You can install IDF manually on Linux or Windows (using the Subsystem for Linux) following the instructions at: https://www.instructables.com/id/ESP32-Development-on-Windows-Subsystem-for-Linux/ or see here https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/windows-setup.html for a direct install. You also need a few extra Python libraries for cspot by addingsudo `pip3 install protobuf grpcio-tools`
**Use the esp-idf 4.3.5 https://github.com/espressif/esp-idf/tree/release/v4.3.5 ** or the 4.4.5 (and above version) if you want to build for esp32-s3
First you need git and python (e.g 3.10.x), install these and let it add to system path.
**Use the esp-idf 4.3.5 https://github.com/espressif/esp-idf/tree/release/v4.3.5 ** or the 4.4.5 (and above version) if you want to build for esp32-s3. You should clone recursively the whole branch (at the version you need) `git clone -b v4.3.5 https://github.com/espressif/esp-idf --recursive`and run the installer (`install.bat [esp32[,esp32s3]]` from there. Some Windows version (at least) have now a SSL certificate issue. You can workaround this by editing idf-tools.py and adding the following under ìmport ssl`
```
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
```
And because the fun never ends, some Windows installations might fail to build a few files and spit a tons of errors on the output. It seems that the cache of the compile is a problem, so try to disable it by running `idf.py --no-ccache build` (I know...)
## Building SqueezeESP32
When initially cloning the repo, make sure you do it recursively. For example: `git clone --recursive https://github.com/sle118/squeezelite-esp32.git`
When initially cloning the repo, make sure you do it recursively. For example: `git clone --recursive https://github.com/sle118/squeezelite-esp32.git`. You also should install cspot additional components for protobuf use.
```
$ sudo pip3 install protobuf grpcio-tools
```
NB: I need to check on a fresh installation, but you might also require "protoc". You should do that within the esp32 local Python environment.
Don't forget to choose one of the config files in build_scripts/ and rename it sdkconfig.defaults or sdkconfig as many important WiFi/BT options are set there. **The codecs libraries will not be rebuilt by these scripts (it's a tedious process - see below)**

View File

@@ -1,6 +1,6 @@
/* libFLAC - Free Lossless Audio Codec library
* Copyright (C) 2000-2009 Josh Coalson
* Copyright (C) 2011-2022 Xiph.Org Foundation
* Copyright (C) 2011-2023 Xiph.Org Foundation
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -782,6 +782,25 @@ FLAC_API void FLAC__stream_decoder_delete(FLAC__StreamDecoder *decoder);
*/
FLAC_API FLAC__bool FLAC__stream_decoder_set_ogg_serial_number(FLAC__StreamDecoder *decoder, long serial_number);
/** Set the "allow Ogg chaining" flag. If set, the Ogg decoder will
* prepare to receive a new stream once the last Ogg page arrives for
* the stream encapsulating the FLAC audio data. This can be used to
* support chained Ogg FLAC streams; a new \c STREAMINFO signals the
* beginning of a new stream.
*
* \note
* This function has no effect with native FLAC decoding.
*
* \default \c false
* \param decoder A decoder instance to set.
* \param allow Whether to allow chained streams.
* \assert
* \code decoder != NULL \endcode
* \retval FLAC__bool
* \c false if the decoder is already initialized, else \c true.
*/
FLAC_API FLAC__bool FLAC__stream_decoder_set_ogg_chaining(FLAC__StreamDecoder* decoder, FLAC__bool value);
/** Set the "MD5 signature checking" flag. If \c true, the decoder will
* compute the MD5 signature of the unencoded audio data while decoding
* and compare it to the signature from the STREAMINFO block, if it
@@ -906,6 +925,17 @@ FLAC_API FLAC__StreamDecoderState FLAC__stream_decoder_get_state(const FLAC__Str
*/
FLAC_API const char *FLAC__stream_decoder_get_resolved_state_string(const FLAC__StreamDecoder *decoder);
/** Get the "allow Ogg chaining" flag as described in
* \code FLAC__stream_decoder_set_ogg_chaining \endcode.
*
* \param decoder A decoder instance to query.
* \assert
* \code decoder != NULL \endcode
* \retval FLAC__bool
* See above.
*/
FLAC_API FLAC__bool FLAC__stream_decoder_get_ogg_chaining(const FLAC__StreamDecoder* decoder);
/** Get the "MD5 signature checking" flag.
* This is the value of the setting, not whether or not the decoder is
* currently checking the MD5 (remember, it can be turned off automatically

Binary file not shown.

View File

@@ -62,6 +62,7 @@ static void register_free();
static void register_setdevicename();
static void register_heap();
static void register_dump_heap();
static void register_abort();
static void register_version();
static void register_restart();
#if CONFIG_WITH_CONFIG_UI
@@ -90,6 +91,7 @@ void register_system()
register_free();
register_heap();
register_dump_heap();
register_abort();
register_version();
register_restart();
register_factory_boot();
@@ -144,6 +146,27 @@ static void register_version()
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}
/* 'abort' command */
static int cmd_abort(int argc, char **argv)
{
cmd_send_messaging(argv[0],MESSAGING_INFO,"ABORT!\r\n");
abort();
return 0;
}
static void register_abort()
{
const esp_console_cmd_t cmd = {
.command = "abort",
.help = "Crash now!",
.hint = NULL,
.func = &cmd_abort,
};
cmd_to_json(&cmd);
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}
esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
{
if(is_recovery_running){

View File

@@ -96,6 +96,7 @@ typedef struct __attribute__((__packed__)) audio_buffer_entry { // decoded aud
u16_t len;
u8_t ready;
u8_t allocated;
u8_t missed;
} abuf_t;
typedef struct rtp_s {
@@ -126,11 +127,6 @@ typedef struct rtp_s {
u32_t rtp, time;
u8_t status;
} synchro;
struct {
u32_t time;
seq_t seqno;
u32_t rtptime;
} record;
int latency; // rtp hold depth in samples
u32_t resent_req, resent_rec; // total resent + recovered frames
u32_t silent_frames; // total silence frames
@@ -147,8 +143,8 @@ typedef struct rtp_s {
#endif
struct alac_codec_s *alac_codec;
int flush_seqno;
bool playing;
int first_seqno;
enum { RTP_WAIT, RTP_STREAM, RTP_PLAY } state;
int stalled;
raop_data_cb_t data_cb;
raop_cmd_cb_t cmd_cb;
@@ -231,7 +227,7 @@ rtp_resp_t rtp_init(struct in_addr host, int latency, char *aeskey, char *aesiv,
ctx->rtp_host.sin_family = AF_INET;
ctx->rtp_host.sin_addr.s_addr = INADDR_ANY;
pthread_mutex_init(&ctx->ab_mutex, 0);
ctx->flush_seqno = -1;
ctx->first_seqno = -1;
ctx->latency = latency;
ctx->ab_read = ctx->ab_write;
@@ -339,24 +335,23 @@ void rtp_end(rtp_t *ctx)
/*---------------------------------------------------------------------------*/
bool rtp_flush(rtp_t *ctx, unsigned short seqno, unsigned int rtptime, bool exit_locked)
{
bool rc = true;
u32_t now = gettime_ms();
{
pthread_mutex_lock(&ctx->ab_mutex);
// always store flush seqno as we only want stricly above it, even when equal to RECORD
ctx->first_seqno = seqno;
bool flushed = false;
if (now < ctx->record.time + 250 || (ctx->record.seqno == seqno && ctx->record.rtptime == rtptime)) {
rc = false;
LOG_ERROR("[%p]: FLUSH ignored as same as RECORD (%hu - %u)", ctx, seqno, rtptime);
} else {
pthread_mutex_lock(&ctx->ab_mutex);
buffer_reset(ctx->audio_buffer);
ctx->playing = false;
ctx->flush_seqno = seqno;
if (!exit_locked) pthread_mutex_unlock(&ctx->ab_mutex);
// no need to stop playing if recent or equal to record - but first_seqno is needed
if (ctx->state == RTP_PLAY) {
buffer_reset(ctx->audio_buffer);
ctx->state = RTP_WAIT;
flushed = true;
LOG_INFO("[%p]: FLUSH packets below %hu - %u", ctx, seqno, rtptime);
}
LOG_INFO("[%p]: flush %hu %u", ctx, seqno, rtptime);
return rc;
if (!exit_locked || !flushed) pthread_mutex_unlock(&ctx->ab_mutex);
return flushed;
}
/*---------------------------------------------------------------------------*/
@@ -367,11 +362,9 @@ void rtp_flush_release(rtp_t *ctx) {
/*---------------------------------------------------------------------------*/
void rtp_record(rtp_t *ctx, unsigned short seqno, unsigned rtptime) {
ctx->record.seqno = seqno;
ctx->record.rtptime = rtptime;
ctx->record.time = gettime_ms();
LOG_INFO("[%p]: record %hu %u", ctx, seqno, rtptime);
ctx->first_seqno = (seqno || rtptime) ? seqno : -1;
ctx->state = RTP_WAIT;
LOG_INFO("[%p]: record %hu - %u", ctx, seqno, rtptime);
}
/*---------------------------------------------------------------------------*/
@@ -442,29 +435,54 @@ static void alac_decode(rtp_t *ctx, s16_t *dest, char *buf, int len, u16_t *outs
/*---------------------------------------------------------------------------*/
static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool first, char *data, int len) {
abuf_t *abuf = NULL;
u32_t playtime;
pthread_mutex_lock(&ctx->ab_mutex);
if (!ctx->playing) {
if ((ctx->flush_seqno == -1 || seq_order(ctx->flush_seqno, seqno)) &&
(ctx->synchro.status & RTP_SYNC) && (ctx->synchro.status & NTP_SYNC)) {
ctx->ab_write = seqno-1;
ctx->ab_read = seqno;
ctx->flush_seqno = -1;
ctx->playing = true;
ctx->resent_req = ctx->resent_rec = ctx->silent_frames = ctx->discarded = 0;
playtime = ctx->synchro.time + ((rtptime - ctx->synchro.rtp) * 10) / (RAOP_SAMPLE_RATE / 100);
ctx->cmd_cb(RAOP_PLAY, playtime);
} else {
pthread_mutex_unlock(&ctx->ab_mutex);
return;
}
}
/* if we have received a RECORD with a seqno, then this is the first allowed rtp sequence number
* and we are in RTP_WAIT state. If seqno was 0, then we are waiting for a flush that will tell
* us what should be our first allowed packet but we must accept everything, wait and clean when
* we the it arrives. This means that first packet moves us to RTP_STREAM state where we accept
* frames but wait for the FLUSH. If this was a FLUSH while playing, then we are also in RTP_WAIT
* state but we do have an allowed seqno and we should not accept any frame before we have it */
// if we have a pending first seqno and we are below, always ignore it
if (ctx->first_seqno != -1 && seq_order(seqno, ctx->first_seqno)) {
pthread_mutex_unlock(&ctx->ab_mutex);
return;
}
if (ctx->state == RTP_WAIT) {
ctx->ab_write = seqno - 1;
ctx->ab_read = ctx->ab_write + 1;
ctx->resent_req = ctx->resent_rec = ctx->silent_frames = ctx->discarded = 0;
if (ctx->first_seqno != -1) {
LOG_INFO("[%p]: 1st accepted packet:%d, now playing", ctx, seqno);
ctx->state = RTP_PLAY;
ctx->first_seqno = -1;
u32_t playtime = ctx->synchro.time + ((rtptime - ctx->synchro.rtp) * 10) / (RAOP_SAMPLE_RATE / 100);
ctx->cmd_cb(RAOP_PLAY, playtime);
} else {
ctx->state = RTP_STREAM;
LOG_INFO("[%p]: 1st accepted packet:%hu, waiting for FLUSH", ctx, seqno);
}
} else if (ctx->state == RTP_STREAM && ctx->first_seqno != -1 && seq_order(ctx->first_seqno, seqno + 1)) {
// now we're talking, but first discard all packets with a seqno below first_seqno AND not ready
while (seq_order(ctx->ab_read, ctx->first_seqno) ||
!ctx->audio_buffer[BUFIDX(ctx->ab_read)].ready) {
ctx->audio_buffer[BUFIDX(ctx->ab_read)].ready = false;
ctx->ab_read++;
}
LOG_INFO("[%p]: done waiting for FLUSH with packet:%d, now playing starting:%hu", ctx, seqno, ctx->ab_read);
ctx->state = RTP_PLAY;
ctx->first_seqno = -1;
u32_t playtime = ctx->synchro.time + ((rtptime - ctx->synchro.rtp) * 10) / (RAOP_SAMPLE_RATE / 100);
ctx->cmd_cb(RAOP_PLAY, playtime);
}
abuf = ctx->audio_buffer + BUFIDX(seqno);
if (seqno == (u16_t) (ctx->ab_write+1)) {
// expected packet
abuf = ctx->audio_buffer + BUFIDX(seqno);
ctx->ab_write = seqno;
LOG_SDEBUG("packet expected seqno:%hu rtptime:%u (W:%hu R:%hu)", seqno, rtptime, ctx->ab_write, ctx->ab_read);
} else if (seq_order(ctx->ab_write, seqno)) {
@@ -475,7 +493,7 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
ctx->ab_read = seqno;
} else {
// request re-send missed frames and evaluate resent date as a whole *after*
rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1);
if (ctx->state == RTP_PLAY) rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1);
// resend date is after all requests have been sent
u32_t now = gettime_ms();
@@ -488,16 +506,15 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
LOG_DEBUG("[%p]: packet newer seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
}
abuf = ctx->audio_buffer + BUFIDX(seqno);
ctx->ab_write = seqno;
} else if (seq_order(ctx->ab_read, seqno + 1)) {
// recovered packet, not yet sent
abuf = ctx->audio_buffer + BUFIDX(seqno);
ctx->resent_rec++;
LOG_DEBUG("[%p]: packet recovered seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
} else {
// too late
LOG_DEBUG("[%p]: packet too late seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
// too late
if (abuf->missed) LOG_INFO("[%p]: packet too late seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
abuf = NULL;
}
if (ctx->in_frames++ > 1000) {
@@ -508,6 +525,7 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
if (abuf) {
alac_decode(ctx, abuf->data, data, len, &abuf->len);
abuf->ready = 1;
abuf->missed = 0;
// this is the local rtptime when this frame is expected to play
abuf->rtptime = rtptime;
buffer_push_packet(ctx);
@@ -528,7 +546,7 @@ static void buffer_push_packet(rtp_t *ctx) {
u32_t now, playtime, hold = max((ctx->latency * 1000) / (8 * RAOP_SAMPLE_RATE), 100);
// not ready to play yet
if (!ctx->playing || ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return;
if (ctx->state != RTP_PLAY || ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return;
// there is always at least one frame in the buffer
do {
@@ -551,6 +569,7 @@ static void buffer_push_packet(rtp_t *ctx) {
LOG_DEBUG("[%p]: created zero frame (W:%hu R:%hu)", ctx, ctx->ab_write, ctx->ab_read);
ctx->data_cb(silence_frame, ctx->frame_size * 4, playtime);
ctx->silent_frames++;
curframe->missed = 1;
}
} else if (curframe->ready) {
ctx->data_cb((const u8_t*) curframe->data, curframe->len, playtime);

View File

@@ -70,7 +70,7 @@ static char * get_dac_config_string(){
return config_alloc_get_str("dac_config", CONFIG_DAC_CONFIG, "model=i2s,bck=" STR(CONFIG_I2S_BCK_IO)
",ws=" STR(CONFIG_I2S_WS_IO) ",do=" STR(CONFIG_I2S_DO_IO)
",sda=" STR(CONFIG_I2C_SDA) ",scl=" STR(CONFIG_I2C_SCL)
",mute=" STR(CONFIG_MUTE_GPIO));
",mute=" STR(CONFIG_MUTE_GPIO) ",mck=" STR(CONFIG_I2S_MCK_IO));
}
/****************************************************************************************

View File

@@ -38,6 +38,7 @@ static esp_err_t actrls_process_action (const cJSON * member, actrls_config_t *c
static esp_err_t actrls_init_json(const char *profile_name, bool create);
static void control_rotary_handler(void *client, rotary_event_e event, bool long_press);
static void volume_rotary_handler(void *client, rotary_event_e event, bool long_press);
static void rotary_timer( TimerHandle_t xTimer );
static const actrls_config_map_t actrls_config_map[] =
@@ -157,6 +158,24 @@ esp_err_t actrls_init(const char *profile_name) {
err = create_rotary(NULL, A, B, SW, longpress, control_rotary_handler) ? ESP_OK : ESP_FAIL;
}
free(config);
config = config_alloc_get_default(NVS_TYPE_STR, "volume_rotary", NULL, 0);
// now see if we have a dedicated volume rotary
if (config && *config) {
int A = -1, B = -1, SW = -1;
// parse config
PARSE_PARAM(config, "A", '=', A);
PARSE_PARAM(config, "B", '=', B);
PARSE_PARAM(config, "SW", '=', SW);
// create rotary (no handling of long press)
err |= create_volume_rotary(NULL, A, B, SW, volume_rotary_handler) ? ESP_OK : ESP_FAIL;
}
free(config);
// set infrared GPIO if any
parse_set_GPIO(set_ir_gpio);
@@ -290,6 +309,29 @@ static void control_rotary_handler(void *client, rotary_event_e event, bool long
if (action != ACTRLS_NONE) (*current_controls[action])(pressed);
}
/****************************************************************************************
*
*/
static void volume_rotary_handler(void *client, rotary_event_e event, bool long_press) {
actrls_action_e action = ACTRLS_NONE;
bool pressed = true;
switch(event) {
case ROTARY_LEFT:
action = ACTRLS_VOLDOWN;
break;
case ROTARY_RIGHT:
action = ACTRLS_VOLUP;
break;
case ROTARY_PRESSED:
action = ACTRLS_TOGGLE;
default:
break;
}
if (action != ACTRLS_NONE) (*current_controls[action])(pressed);
}
/****************************************************************************************
*
*/
@@ -568,6 +610,13 @@ exit:
return err;
}
/****************************************************************************************
*
*/
actrls_handler get_ctrl_handler(actrls_action_e action) {
return current_controls[action];
}
/****************************************************************************************
*
*/

View File

@@ -53,3 +53,9 @@ void actrls_set_default(const actrls_t controls, bool raw_controls, actrls_hook_
void actrls_set(const actrls_t controls, bool raw_controls, actrls_hook_t *hook, actrls_ir_handler_t *ir_handler);
void actrls_unset(void);
bool actrls_ir_action(uint16_t addr, uint16_t code);
/* Call this to get the handler for any of the audio actions. It will map to the control specific
to the current mode (LMS, AirPlay, Spotify). This is useful if you have a custom way to create
buttons (like analogue buttons)
*/
actrls_handler get_ctrl_handler(actrls_action_e);

View File

@@ -58,13 +58,13 @@ static struct {
static TimerHandle_t polled_timer;
static EXT_RAM_ATTR struct {
static EXT_RAM_ATTR struct encoder {
QueueHandle_t queue;
void *client;
rotary_encoder_info_t info;
int A, B, SW;
rotary_handler handler;
} rotary;
} rotary, volume;
static EXT_RAM_ATTR struct {
RingbufHandle_t rb;
@@ -227,11 +227,22 @@ static void buttons_task(void* arg) {
// received a rotary event
xQueueReceive(rotary.queue, &event, 0);
ESP_LOGD(TAG, "Event: position %d, direction %s", event.state.position,
ESP_LOGD(TAG, "Rotary event: position %d, direction %s", event.state.position,
event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW" : "CCW") : "NOT_SET");
rotary.handler(rotary.client, event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ?
ROTARY_RIGHT : ROTARY_LEFT, false);
} else if (xActivatedMember == volume.queue) {
rotary_encoder_event_t event = { 0 };
// received a volume rotary event
xQueueReceive(volume.queue, &event, 0);
ESP_LOGD(TAG, "Volume event: position %d, direction %s", event.state.position,
event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW" : "CCW") : "NOT_SET");
volume.handler(volume.client, event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ?
ROTARY_RIGHT : ROTARY_LEFT, false);
} else {
// this is IR
active = infrared_receive(infrared.rb, infrared.handler);
@@ -395,7 +406,55 @@ void *button_remap(void *client, int gpio, button_handler handler, int long_pres
}
/****************************************************************************************
* Rotary encoder handler
* Create rotary encoder
*/
static bool create_rotary_encoder(struct encoder *encoder, void *id, int A, int B, int SW, int long_press, rotary_handler handler, button_handler button) {
// nasty ESP32 bug: fire-up constantly INT on GPIO 36/39 if ADC1, AMP, Hall used which WiFi does when PS is activated
if (A == -1 || B == -1 || A == 36 || A == 39 || B == 36 || B == 39) {
ESP_LOGI(TAG, "Cannot create rotary %d %d", A, B);
return false;
}
encoder->A = A;
encoder->B = B;
encoder->SW = SW;
encoder->client = id;
encoder->handler = handler;
// Initialise the rotary encoder device with the GPIOs for A and B signals
rotary_encoder_init(&encoder->info, A, B);
// Create a queue for events from the rotary encoder driver.
encoder->queue = rotary_encoder_create_queue();
rotary_encoder_set_queue(&encoder->info, encoder->queue);
common_task_init();
xQueueAddToSet( encoder->queue, common_queue_set );
// create companion button if rotary has a switch
if (SW != -1) button_create(id, SW, BUTTON_LOW, true, 0, button, long_press, -1);
return true;
}
/****************************************************************************************
* Volume button encoder handler
*/
static void volume_button_handler(void *id, button_event_e event, button_press_e mode, bool long_press) {
ESP_LOGI(TAG, "Volume encoder push-button %d", event);
volume.handler(id, event == BUTTON_PRESSED ? ROTARY_PRESSED : ROTARY_RELEASED, long_press);
}
/****************************************************************************************
* Create volume encoder
*/
bool create_volume_rotary(void *id, int A, int B, int SW, rotary_handler handler) {
ESP_LOGI(TAG, "Created volume encoder A:%d B:%d, SW:%d", A, B, SW);
return create_rotary_encoder(&volume, id, A, B, SW, false, handler, volume_button_handler);
}
/****************************************************************************************
* Rotary button encoder handler
*/
static void rotary_button_handler(void *id, button_event_e event, button_press_e mode, bool long_press) {
ESP_LOGI(TAG, "Rotary push-button %d", event);
@@ -406,34 +465,8 @@ static void rotary_button_handler(void *id, button_event_e event, button_press_e
* Create rotary encoder
*/
bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handler handler) {
// nasty ESP32 bug: fire-up constantly INT on GPIO 36/39 if ADC1, AMP, Hall used which WiFi does when PS is activated
if (A == -1 || B == -1 || A == 36 || A == 39 || B == 36 || B == 39) {
ESP_LOGI(TAG, "Cannot create rotary %d %d", A, B);
return false;
}
rotary.A = A;
rotary.B = B;
rotary.SW = SW;
rotary.client = id;
rotary.handler = handler;
// Initialise the rotary encoder device with the GPIOs for A and B signals
rotary_encoder_init(&rotary.info, A, B);
// Create a queue for events from the rotary encoder driver.
rotary.queue = rotary_encoder_create_queue();
rotary_encoder_set_queue(&rotary.info, rotary.queue);
common_task_init();
xQueueAddToSet( rotary.queue, common_queue_set );
// create companion button if rotary has a switch
if (SW != -1) button_create(id, SW, BUTTON_LOW, true, 0, rotary_button_handler, long_press, -1);
ESP_LOGI(TAG, "Created rotary encoder A:%d B:%d, SW:%d", A, B, SW);
return true;
return create_rotary_encoder(&rotary, id, A, B, SW, long_press, handler, rotary_button_handler);
}
/****************************************************************************************

View File

@@ -34,5 +34,5 @@ typedef enum { ROTARY_LEFT, ROTARY_RIGHT, ROTARY_PRESSED, ROTARY_RELEASED } rota
typedef void (*rotary_handler)(void *id, rotary_event_e event, bool long_press);
bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handler handler);
bool create_volume_rotary(void *id, int A, int B, int SW, rotary_handler handler);
bool create_infrared(int gpio, infrared_handler handler, infrared_mode_t mode);

View File

@@ -83,6 +83,10 @@ static void mcp23s17_set_direction(gpio_exp_t* self);
static uint32_t mcp23s17_read(gpio_exp_t* self);
static void mcp23s17_write(gpio_exp_t* self);
static void aw9523_set_direction(gpio_exp_t* self);
static uint32_t aw9523_read(gpio_exp_t* self);
static void aw9523_write(gpio_exp_t* self);
static void service_handler(void *arg);
static void debounce_handler( TimerHandle_t xTimer );
@@ -130,6 +134,11 @@ static const struct gpio_exp_model_s {
.set_pull_mode = mcp23s17_set_pull_mode,
.read = mcp23s17_read,
.write = mcp23s17_write, },
{ .model = "aw9523",
.trigger = GPIO_INTR_LOW_LEVEL,
.set_direction = aw9523_set_direction,
.read = aw9523_read,
.write = aw9523_write, },
};
static EXT_RAM_ATTR uint8_t n_expanders;
@@ -671,6 +680,24 @@ static void mcp23s17_write(gpio_exp_t* self) {
spi_write(self->spi_handle, self->phy.addr, 0x12, self->shadow, 2);
}
/****************************************************************************************
* AW9523 family : direction, read and write
*/
static void aw9523_set_direction(gpio_exp_t* self) {
i2c_write(self->phy.port, self->phy.addr, 0x04, self->r_mask, 2);
i2c_write(self->phy.port, self->phy.addr, 0x06, ~self->r_mask, 2);
}
static uint32_t aw9523_read(gpio_exp_t* self) {
// Reading both registers in one go does not seem to reset IRQ correctly
uint8_t port1 = i2c_read(self->phy.port, self->phy.addr, 0x00, 1);
return (i2c_read(self->phy.port, self->phy.addr, 0x01, 1) << 8) | port1;
}
static void aw9523_write(gpio_exp_t* self) {
i2c_write(self->phy.port, self->phy.addr, 0x02, self->shadow, 2);
}
/***************************************************************************************
I2C low level
***************************************************************************************/
@@ -793,4 +820,4 @@ static uint32_t spi_read(spi_device_handle_t handle, uint8_t addr, uint8_t reg,
free(transaction);
return data;
}
}

View File

@@ -248,7 +248,8 @@ void monitor_svc_init(void) {
// re-use button management for jack handler, it's a GPIO after all
if (jack.gpio != -1) {
ESP_LOGI(TAG,"Adding jack (%s) detection GPIO %d", jack.active ? "high" : "low", jack.gpio);
button_create(NULL, jack.gpio, jack.active ? BUTTON_HIGH : BUTTON_LOW, false, 250, jack_handler_default, 0, -1);
// Use GPIO pull so the line does not float (Muse jack detect is active-low and needs pull-up)
button_create(NULL, jack.gpio, jack.active ? BUTTON_HIGH : BUTTON_LOW, true, 250, jack_handler_default, 0, -1);
}
#ifdef CONFIG_SPKFAULT_GPIO_LEVEL
@@ -297,4 +298,4 @@ void monitor_svc_init(void) {
*/
monitor_gpio_t * get_jack_insertion_gpio(){
return &jack;
}
}

View File

@@ -21,6 +21,12 @@ set(BELL_DISABLE_MQTT ON)
set(BELL_DISABLE_WEBSERVER ON)
set(CSPOT_TARGET_ESP32 ON)
set(_secret $ENV{SPOTIFY_SECRET})
if(_secret)
separate_arguments(_secret)
set_source_files_properties(Shim.cpp PROPERTIES COMPILE_OPTIONS "${_secret}")
endif()
# because CMake is so broken, the cache set below overrides a normal "set" for the first build
set(BELL_EXTERNAL_VORBIS "idf::codecs" CACHE STRING "provide own codecs")
set(BELL_EXTERNAL_CJSON "idf::json" CACHE STRING "provide own CJSON")

View File

@@ -32,6 +32,16 @@
#include "platform_config.h"
#include "nvs_utilities.h"
#include "tools.h"
#if !defined(CLIENT_ID) || !defined(CLIENT_SECRET)
#if __has_include("client_info.h")
#include "client_info.h"
#else
#warning "missing Spotify's CLIENT_ID and/or CLIENT_SECRET (set SPOTIFY_SECRET env variable or set it in client_info.h)"
#define CLIENT_ID "<your client id>"
#define CLIENT_SECRET "<your client secret>"
#endif
#endif
static class cspotPlayer *player;
@@ -123,7 +133,7 @@ size_t cspotPlayer::pcmWrite(uint8_t *pcm, size_t bytes, std::string_view trackI
}
return dataHandler(pcm, bytes);
}
}
extern "C" {
static esp_err_t handleGET(httpd_req_t *request) {
@@ -365,6 +375,8 @@ void cspotPlayer::runTask() {
ctx->session->connectWithRandomAp();
ctx->config.authData = ctx->session->authenticate(blob);
ctx->config.clientId = CLIENT_ID;
ctx->config.clientSecret = CLIENT_SECRET;
// Auth successful
if (ctx->config.authData.size() > 0) {

View File

@@ -0,0 +1,4 @@
#pragma once
#define CLIENT_ID "<your spotify client's id>"
#define CLIENT_SECRET "<your spotify client's secret>"

View File

@@ -22482,9 +22482,13 @@ mg_init_library(unsigned features)
file_mutex_init =
pthread_mutex_init(&global_log_file_lock, &pthread_mutex_attr);
if (file_mutex_init == 0) {
#ifdef WINSOCK_START
/* Start WinSock */
WSADATA data;
failed = wsa = WSAStartup(MAKEWORD(2, 2), &data);
#else
failed = wsa = 0;
#endif
}
#else
mutexattr_init = pthread_mutexattr_init(&pthread_mutex_attr);
@@ -22498,7 +22502,9 @@ mg_init_library(unsigned features)
if (failed) {
#if defined(_WIN32)
if (wsa == 0) {
#ifdef WINSOCK_START
(void)WSACleanup();
#endif
}
if (file_mutex_init == 0) {
(void)pthread_mutex_destroy(&global_log_file_lock);
@@ -22598,7 +22604,9 @@ mg_exit_library(void)
#endif
#if defined(_WIN32)
#ifdef WINSOCK_START
(void)WSACleanup();
#endif
(void)pthread_mutex_destroy(&global_log_file_lock);
#else
(void)pthread_mutexattr_destroy(&pthread_mutex_attr);

View File

@@ -12,6 +12,8 @@
using namespace bell;
std::mutex BellHTTPServer::initMutex;
class WebSocketHandler : public CivetWebSocketHandler {
public:
BellHTTPServer::WSDataHandler dataHandler;
@@ -187,6 +189,7 @@ bool BellHTTPServer::handlePost(CivetServer* server,
}
BellHTTPServer::BellHTTPServer(int serverPort) {
std::lock_guard lock(initMutex);
mg_init_library(0);
BELL_LOG(info, "HttpServer", "Server listening on port %d", serverPort);
this->serverPort = serverPort;
@@ -197,6 +200,11 @@ BellHTTPServer::BellHTTPServer(int serverPort) {
server = std::make_unique<CivetServer>(civetWebOptions);
}
BellHTTPServer::~BellHTTPServer() {
std::lock_guard lock(initMutex);
mg_exit_library();
}
std::unique_ptr<BellHTTPServer::HTTPResponse> BellHTTPServer::makeJsonResponse(
const std::string& json, int status) {
auto response = std::make_unique<BellHTTPServer::HTTPResponse>();

View File

@@ -19,6 +19,7 @@ namespace bell {
class BellHTTPServer : public CivetHandler {
public:
BellHTTPServer(int serverPort);
~BellHTTPServer();
enum class WSState { CONNECTED, READY, CLOSED };
@@ -100,6 +101,8 @@ class BellHTTPServer : public CivetHandler {
std::mutex responseMutex;
HTTPHandler notFoundHandler;
static std::mutex initMutex;
bool handleGet(CivetServer* server, struct mg_connection* conn);
bool handlePost(CivetServer* server, struct mg_connection* conn);
};

View File

@@ -6,6 +6,7 @@
#include <cstring>
#include <vector>
#include <mutex>
#include <atomic>
#if __has_include("avahi-client/client.h")
#include <avahi-client/client.h>
@@ -41,8 +42,9 @@ class implMDNSService : public MDNSService {
#endif
static struct mdnsd* mdnsServer;
static in_addr_t host;
static std::atomic<size_t> instances;
implMDNSService(struct mdns_service* service) : service(service){};
implMDNSService(struct mdns_service* service) : service(service){ instances++; };
#ifndef BELL_DISABLE_AVAHI
implMDNSService(AvahiEntryGroup* avahiGroup) : avahiGroup(avahiGroup){};
#endif
@@ -51,6 +53,7 @@ class implMDNSService : public MDNSService {
struct mdnsd* implMDNSService::mdnsServer = NULL;
in_addr_t implMDNSService::host = INADDR_ANY;
std::atomic<size_t> implMDNSService::instances = 0;
static std::mutex registerMutex;
#ifndef BELL_DISABLE_AVAHI
AvahiClient* implMDNSService::avahiClient = NULL;
@@ -66,11 +69,21 @@ void implMDNSService::unregisterService() {
#ifndef BELL_DISABLE_AVAHI
if (avahiGroup) {
avahi_entry_group_free(avahiGroup);
if (!--instances && implMDNSService::avahiClient) {
avahi_client_free(implMDNSService::avahiClient);
avahi_simple_poll_free(implMDNSService::avahiPoll);
implMDNSService::avahiClient = nullptr;
implMDNSService::avahiPoll = nullptr;
}
} else
#endif
{
mdns_service_remove(implMDNSService::mdnsServer, service);
}
if (!--instances && implMDNSService::mdnsServer) {
mdnsd_stop(implMDNSService::mdnsServer);
implMDNSService::mdnsServer = nullptr;
}
}
}
std::unique_ptr<MDNSService> MDNSService::registerService(
@@ -180,19 +193,14 @@ std::unique_ptr<MDNSService> MDNSService::registerService(
std::string type(serviceType + "." + serviceProto + ".local");
BELL_LOG(info, "MDNS", "using built-in mDNS for %s", serviceName.c_str());
struct mdns_service* mdnsService =
auto service =
mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(),
type.c_str(), servicePort, NULL, txt.data());
if (mdnsService) {
auto service =
mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(),
type.c_str(), servicePort, NULL, txt.data());
return std::make_unique<implMDNSService>(service);
}
if (service) return std::make_unique<implMDNSService>(service);
}
BELL_LOG(error, "MDNS", "cannot start any mDNS listener for %s",
serviceName.c_str());
return NULL;
return nullptr;
}

View File

@@ -1,5 +1,6 @@
#include <cassert>
#include <vector>
#include <mutex>
#include "BellLogger.h"
#include "MDNSService.h"
@@ -19,13 +20,12 @@ using namespace bell;
class implMDNSService : public MDNSService {
private:
struct mdns_service* service;
void unregisterService(void) {
mdns_service_remove(implMDNSService::mdnsServer, service);
};
void unregisterService(void);
public:
static struct mdnsd* mdnsServer;
implMDNSService(struct mdns_service* service) : service(service){};
static std::atomic<size_t> instances;
implMDNSService(struct mdns_service* service) : service(service) { instances++; };
};
/**
@@ -33,8 +33,18 @@ class implMDNSService : public MDNSService {
**/
struct mdnsd* implMDNSService::mdnsServer = NULL;
std::atomic<size_t> implMDNSService::instances = 0;
static std::mutex registerMutex;
void implMDNSService::unregisterService() {
mdns_service_remove(implMDNSService::mdnsServer, service);
if (!--instances && implMDNSService::mdnsServer) {
mdnsd_stop(implMDNSService::mdnsServer);
implMDNSService::mdnsServer = nullptr;
}
}
std::unique_ptr<MDNSService> MDNSService::registerService(
const std::string& serviceName, const std::string& serviceType,
const std::string& serviceProto, const std::string& serviceHost,
@@ -94,5 +104,5 @@ std::unique_ptr<MDNSService> MDNSService::registerService(
mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(),
type.c_str(), servicePort, NULL, txt.data());
return std::make_unique<implMDNSService>(service);
return service ? std::make_unique<implMDNSService>(service) : nullptr;
}

View File

@@ -72,7 +72,11 @@ class Task {
(LPTHREAD_START_ROUTINE)taskEntryFunc, this, 0, NULL);
return thread != NULL;
#else
return (pthread_create(&thread, NULL, taskEntryFunc, this) == 0);
if (!pthread_create(&thread, NULL, taskEntryFunc, this)) {
pthread_detach(thread);
return true;
}
return false;
#endif
}
@@ -108,13 +112,7 @@ class Task {
#endif
static void* taskEntryFunc(void* This) {
Task* self = (Task*)This;
self->runTask();
#if _WIN32
WaitForSingleObject(self->thread, INFINITE);
#else
pthread_join(self->thread, NULL);
#endif
((Task*)This)->runTask();
return NULL;
}
};

View File

@@ -24,6 +24,8 @@ struct Context {
AudioFormat audioFormat = AudioFormat::AudioFormat_OGG_VORBIS_160;
std::string deviceId;
std::string deviceName;
std::string clientId;
std::string clientSecret;
std::vector<uint8_t> authData;
int volume;

View File

@@ -24,7 +24,7 @@ class CDNAudioFile;
// Used in got track info event
struct TrackInfo {
std::string name, album, artist, imageUrl, trackId;
uint32_t duration;
uint32_t duration, number, discNumber;
void loadPbTrack(Track* pbTrack, const std::vector<uint8_t>& gid);
void loadPbEpisode(Episode* pbEpisode, const std::vector<uint8_t>& gid);

View File

@@ -32,6 +32,8 @@ message Artist {
message Track {
optional bytes gid = 1;
optional string name = 2;
optional sint32 number = 5;
optional sint32 disc_number = 6;
optional sint32 duration = 0x7;
optional Album album = 0x3;
repeated Artist artist = 0x4;
@@ -44,6 +46,7 @@ message Episode {
optional bytes gid = 1;
optional string name = 2;
optional sint32 duration = 7;
optional sint32 number = 65;
repeated AudioFile file = 12;
repeated Restriction restriction = 0x4B;
optional ImageGroup covers = 0x44;

View File

@@ -7,6 +7,7 @@
#include <vector> // for vector
#include "BellLogger.h" // for AbstractLogger
#include "BellUtils.h" // for BELL_SLEEP_MS
#include "CSpotContext.h" // for Context
#include "HTTPClient.h"
#include "Logger.h" // for CSPOT_LOG
@@ -24,13 +25,8 @@
#include "nlohmann/json_fwd.hpp" // for json
#endif
#include "protobuf/login5.pb.h" // for LoginRequest
using namespace cspot;
static std::string CLIENT_ID =
"65b708073fc0480ea92a077233ca87bd"; // Spotify web client's client id
static std::string SCOPES =
"streaming,user-library-read,user-library-modify,user-top-read,user-read-"
"recently-played"; // Required access scopes
@@ -68,69 +64,43 @@ void AccessKeyFetcher::updateAccessKey() {
keyPending = true;
// Prepare a protobuf login request
static LoginRequest loginRequest = LoginRequest_init_zero;
static LoginResponse loginResponse = LoginResponse_init_zero;
// Assign necessary request fields
loginRequest.client_info.client_id.funcs.encode = &bell::nanopb::encodeString;
loginRequest.client_info.client_id.arg = &CLIENT_ID;
loginRequest.client_info.device_id.funcs.encode = &bell::nanopb::encodeString;
loginRequest.client_info.device_id.arg = &ctx->config.deviceId;
loginRequest.login_method.stored_credential.username.funcs.encode =
&bell::nanopb::encodeString;
loginRequest.login_method.stored_credential.username.arg =
&ctx->config.username;
// Set login method to stored credential
loginRequest.which_login_method = LoginRequest_stored_credential_tag;
loginRequest.login_method.stored_credential.data.funcs.encode =
&bell::nanopb::encodeVector;
loginRequest.login_method.stored_credential.data.arg = &ctx->config.authData;
// Max retry of 3, can receive different hash cat types
int retryCount = 3;
bool success = false;
do {
auto encodedRequest = pbEncode(LoginRequest_fields, &loginRequest);
CSPOT_LOG(info, "Access token expired, fetching new one... %d",
encodedRequest.size());
CSPOT_LOG(info, "Access token expired, fetching new one...");
// Perform a login5 request, containing the encoded protobuf data
auto credentials = "grant_type=client_credentials&client_id=" + ctx->config.clientId + "&client_secret=" + ctx->config.clientSecret;
std::vector<uint8_t> body(credentials.begin(), credentials.end());
auto response = bell::HTTPClient::post(
"https://login5.spotify.com/v3/login",
{{"Content-Type", "application/x-protobuf"}}, encodedRequest);
auto responseBytes = response->bytes();
// Deserialize the response
pbDecode(loginResponse, LoginResponse_fields, responseBytes);
if (loginResponse.which_response == LoginResponse_ok_tag) {
// Successfully received an auth token
"https://accounts.spotify.com/api/token",
{ {"Content-Type", "application/x-www-form-urlencoded"} }, body);
#ifdef BELL_ONLY_CJSON
cJSON* root = cJSON_Parse(response->body().data());
if (!cJSON_GetObjectItem(root, "error")) {
accessKey = std::string(cJSON_GetObjectItem(root, "access_token")->valuestring);
int expiresIn = cJSON_GetObjectItem(root, "expires_in")->valueint;
cJSON_Delete(root);
#else
auto root = nlohmann::json::parse(response->bytes());
if (!root.contains("error")) {
accessKey = std::string(root["access_token"]);
int expiresIn = root["expires_in"];
#endif
// Successfully received an auth token
CSPOT_LOG(info, "Access token sucessfully fetched");
success = true;
accessKey = std::string(loginResponse.response.ok.access_token);
// Expire in ~30 minutes
int expiresIn = 3600 / 2;
if (loginResponse.response.ok.has_access_token_expires_in) {
int expiresIn = loginResponse.response.ok.access_token_expires_in / 2;
}
this->expiresAt =
ctx->timeProvider->getSyncedTimestamp() + (expiresIn * 1000);
} else {
CSPOT_LOG(error, "Failed to fetch access token");
ctx->timeProvider->getSyncedTimestamp() + (expiresIn * 1000);
}
else {
CSPOT_LOG(error, "Failed to fetch access token");
BELL_SLEEP_MS(3000);
}
// Free up allocated memory for response
pb_release(LoginResponse_fields, &loginResponse);
retryCount--;
} while (retryCount >= 0 && !success);

View File

@@ -97,6 +97,8 @@ void TrackInfo::loadPbTrack(Track* pbTrack, const std::vector<uint8_t>& gid) {
}
}
number = pbTrack->has_number ? pbTrack->number : 0;
discNumber = pbTrack->has_disc_number ? pbTrack->disc_number : 0;
duration = pbTrack->duration;
}
@@ -113,6 +115,8 @@ void TrackInfo::loadPbEpisode(Episode* pbEpisode,
imageUrl = "https://i.scdn.co/image/" + bytesToHexString(imageId);
}
number = pbEpisode->has_number ? pbEpisode->number : 0;
discNumber = 0;
duration = pbEpisode->duration;
}

View File

@@ -288,6 +288,7 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
output.frames_played = 0;
output.external = DECODE_RAOP;
output.state = OUTPUT_STOPPED;
if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
LOG_INFO("resizing buffer %u", outputbuf->size);
break;
@@ -377,6 +378,7 @@ static bool cspot_cmd_handler(cspot_event_t cmd, va_list args)
output.state = OUTPUT_STOPPED;
sink_state = SINK_ABORT;
_buf_flush(outputbuf);
_buf_limit(outputbuf, 0);
if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
LOG_INFO("CSpot start track");
break;

View File

@@ -687,6 +687,8 @@ void draw_VU(struct GDS_Device * display, int level, int x, int y, int width, bo
static void grfe_handler( u8_t *data, int len) {
struct grfe_packet *pkt = (struct grfe_packet*) data;
if (!display) return;
// we don't support transition, simply claim we're done
if (pkt->transition != 'c') {
LOG_INFO("Transition %c requested with offset %hu, param %d", pkt->transition, pkt->offset, pkt->param);
@@ -763,6 +765,8 @@ static void grfs_handler(u8_t *data, int len) {
int size = len - sizeof(struct grfs_packet);
int offset = htons(pkt->offset);
if (!display) return;
LOG_DEBUG("grfs s:%u d:%u p:%u sp:%u by:%hu m:%hu w:%hu o:%hu",
(int) pkt->screen,
(int) pkt->direction, // 1=left, 2=right
@@ -773,7 +777,7 @@ static void grfs_handler(u8_t *data, int len) {
htons(pkt->width), // last column of animation that contains a "full" screen
htons(pkt->offset) // offset if multiple packets are sent
);
// new grfs frame, build scroller info
if (!offset) {
// use the display as a general lock
@@ -818,6 +822,8 @@ static void grfs_handler(u8_t *data, int len) {
static void grfg_handler(u8_t *data, int len) {
struct grfg_packet *pkt = (struct grfg_packet*) data;
if (!display) return;
LOG_DEBUG("gfrg s:%hu w:%hu (len:%u)", htons(pkt->screen), htons(pkt->width), len);
// full screen artwork or for small screen, visu has priority when full screen
@@ -864,6 +870,8 @@ static void grfa_handler(u8_t *data, int len) {
int offset = htonl(pkt->offset);
int length = htonl(pkt->length);
if (!display) return;
// when using full screen visualizer on small screen there is a brief overlay
artwork.enable = (length != 0);

View File

@@ -22,7 +22,8 @@ static EXT_RAM_ATTR struct {
void *handle;
float loudness, volume;
uint32_t samplerate;
float gain[EQ_BANDS], loudness_gain[EQ_BANDS];
int8_t gain[EQ_BANDS];
float loudness_gain[EQ_BANDS];
bool update;
} equalizer;
@@ -151,6 +152,8 @@ void equalizer_set_gain(int8_t *gain) {
char config[EQ_BANDS * 4 + 1] = { };
int n = 0;
if (memcmp(equalizer.gain, gain, EQ_BANDS) != 0) equalizer.update = true;
for (int i = 0; i < EQ_BANDS; i++) {
equalizer.gain[i] = gain[i];
n += sprintf(config + n, "%d,", gain[i]);
@@ -159,9 +162,6 @@ void equalizer_set_gain(int8_t *gain) {
config[n-1] = '\0';
config_set_value(NVS_TYPE_STR, "equalizer", config);
// update only if something changed
if (!memcmp(equalizer.gain, gain, EQ_BANDS)) equalizer.update = true;
LOG_INFO("equalizer gain %s", config);
#else
LOG_INFO("no equalizer with 32 bits samples");

View File

@@ -70,6 +70,7 @@ struct flac {
);
FLAC__bool (* FLAC__stream_decoder_process_single)(FLAC__StreamDecoder *decoder);
FLAC__StreamDecoderState (* FLAC__stream_decoder_get_state)(const FLAC__StreamDecoder *decoder);
FLAC__bool (*FLAC__stream_decoder_set_ogg_chaining)(FLAC__StreamDecoder* decoder, FLAC__bool allow);
#endif
};
@@ -121,6 +122,9 @@ static FLAC__StreamDecoderReadStatus read_cb(const FLAC__StreamDecoder *decoder,
_buf_inc_readp(streambuf, bytes);
UNLOCK_S;
// give some time for stream to acquire data otherwise flac will hammer us
if (!end && !bytes) usleep(100 * 1000);
*want = bytes;
return end ? FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM : FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
@@ -137,8 +141,8 @@ static FLAC__StreamDecoderWriteStatus write_cb(const FLAC__StreamDecoder *decode
FLAC__int32 *rptr = (FLAC__int32 *)buffer[channels > 1 ? 1 : 0];
if (decode.new_stream) {
LOG_INFO("setting track_start");
LOCK_O;
LOG_INFO("setting track_start");
output.track_start = outputbuf->writep;
decode.new_stream = false;
@@ -253,6 +257,7 @@ static void flac_open(u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t en
if ( f->container == 'o' ) {
LOG_INFO("ogg/flac container - using init_ogg_stream");
FLAC(f, stream_decoder_set_ogg_chaining, f->decoder, true);
FLAC(f, stream_decoder_init_ogg_stream, f->decoder, &read_cb, NULL, NULL, NULL, NULL, &write_cb, NULL, &error_cb, NULL);
} else {
FLAC(f, stream_decoder_init_stream, f->decoder, &read_cb, NULL, NULL, NULL, NULL, &write_cb, NULL, &error_cb, NULL);
@@ -295,6 +300,7 @@ static bool load_flac() {
f->FLAC__stream_decoder_init_ogg_stream = dlsym(handle, "FLAC__stream_decoder_init_ogg_stream");
f->FLAC__stream_decoder_process_single = dlsym(handle, "FLAC__stream_decoder_process_single");
f->FLAC__stream_decoder_get_state = dlsym(handle, "FLAC__stream_decoder_get_state");
f->FLAC__stream_decoder_set_ogg_chaining = dlsym(handle, "FLAC__stream_decoder_set_ogg_chaining");
if ((err = dlerror()) != NULL) {
LOG_INFO("dlerror: %s", err);

View File

@@ -194,7 +194,7 @@ static int read_opus_header(void) {
// nothing has been found and we have no more bytes, come back later
if (status <= 0) break;
// always set stream serialno if we have a new one
// always set stream serialno if we have a new one (no multiplexed streams)
if (OG(&go, page_bos, &u->page)) OG(&go, stream_reset_serialno, &u->state, OG(&go, page_serialno, &u->page));
// bring new page in if we want it (otherwise we're just skipping)
@@ -227,6 +227,7 @@ static int read_opus_header(void) {
} else if (u->status == OGG_COMMENT_HEADER) {
// don't consume VorbisComment which could be a huge packet, just skip it
if (!OG(&go, page_packets, &u->page)) continue;
LOG_INFO("comment skipped successfully");
done = 1;
}
}

View File

@@ -60,7 +60,7 @@ frames_t _output_frames(frames_t avail) {
silence = false;
// start when threshold met
if (output.state == OUTPUT_BUFFER && (frames * BYTES_PER_FRAME) > output.threshold * output.next_sample_rate / 10 && frames > output.start_frames) {
if (output.state == OUTPUT_BUFFER && frames > output.threshold * output.next_sample_rate / 10 && frames > output.start_frames) {
output.state = OUTPUT_RUNNING;
LOG_INFO("start buffer frames: %u", frames);
wake_controller();

View File

@@ -139,8 +139,11 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g
u8_t *buf = silencebuf;
memcpy(btout + oframes * BYTES_PER_FRAME, buf, out_frames * BYTES_PER_FRAME);
}
output_visu_export(btout + oframes * BYTES_PER_FRAME, out_frames, output.current_sample_rate, silence, (gainL + gainR) / 2);
// don't update visu if we don't have enough data in buffer (500 ms)
if (silence || _buf_used(outputbuf) > BYTES_PER_FRAME * output.current_sample_rate / 2) {
output_visu_export(btout + oframes * BYTES_PER_FRAME, out_frames, output.current_sample_rate, silence, (gainL + gainR) / 2);
}
oframes += out_frames;

View File

@@ -222,6 +222,25 @@ static void set_i2s_pin(char *config, i2s_pin_config_t *pin_config) {
#endif
}
/* When a panic occurs during playback, the I2S interface can produce a loud noise burst.
* This code runs just before the system panic handler to "emergency stop" the I2S iterface
* to prevent the noise burst from happening. Note that when this code is called the system
* has already crashed, so no need to disable interrupts, acquire locks, or otherwise be nice.
*
* This code makes use of the linker --wrap feature to intercept the call to esp_panic_handler.
*/
void __real_esp_panic_handler(void*);
void __wrap_esp_panic_handler (void* info) {
esp_rom_printf("I2S abort!\r\n");
i2s_stop(CONFIG_I2S_NUM);
/* Call the original panic handler function to finish processing this error */
__real_esp_panic_handler(info);
}
/****************************************************************************************
* Initialize the DAC output
*/
@@ -439,7 +458,7 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
static EXT_RAM_ATTR StackType_t xStack[OUTPUT_THREAD_STACK_SIZE] __attribute__ ((aligned (4)));
output_i2s_task = xTaskCreateStaticPinnedToCore( (TaskFunction_t) output_thread_i2s, "output_i2s", OUTPUT_THREAD_STACK_SIZE,
NULL, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1, xStack, &xTaskBuffer, 0 );
NULL, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 10, xStack, &xTaskBuffer, 0 );
}
}
@@ -485,8 +504,8 @@ static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32
memcpy(obuf + oframes * BYTES_PER_FRAME, silencebuf, out_frames * BYTES_PER_FRAME);
}
// don't update visu if we don't have enough data in buffer
if (silence || output.external || _buf_used(outputbuf) > outputbuf->size >> 2 ) {
// don't update visu if we don't have enough data in buffer (500 ms)
if (silence || _buf_used(outputbuf) > BYTES_PER_FRAME * output.current_sample_rate / 2) {
output_visu_export(obuf + oframes * BYTES_PER_FRAME, out_frames, output.current_sample_rate, silence, (gainL + gainR) / 2);
}

View File

@@ -393,7 +393,9 @@ static void process_strm(u8_t *pkt, int len) {
stream_file(header, header_len, strm->threshold * 1024);
autostart -= 2;
} else {
stream_sock(ip, port, strm->format, header, header_len, strm->threshold * 1024, autostart >= 2);
stream_sock(ip, port, strm->flags & 0x20,
strm->format == 'o' || strm->format == 'u' || (strm->format == 'f' && strm->pcm_sample_size == 'o'),
header, header_len, strm->threshold * 1024, autostart >= 2);
}
sendSTAT("STMc", 0);
sentSTMu = sentSTMo = sentSTMl = false;
@@ -412,8 +414,8 @@ static void process_strm(u8_t *pkt, int len) {
output.fade_secs = strm->transition_period;
output.invert = (strm->flags & 0x03) == 0x03;
output.channels = (strm->flags & 0x0c) >> 2;
UNLOCK_O;
LOG_DEBUG("set fade: %u, channels: %u, invert: %u", output.fade_mode, output.channels, output.invert);
UNLOCK_O;
}
break;
default:

View File

@@ -580,26 +580,12 @@ struct streamstate {
u32_t meta_next;
u32_t meta_left;
bool meta_send;
struct {
enum { STREAM_OGG_OFF, STREAM_OGG_SYNC, STREAM_OGG_HEADER, STREAM_OGG_SEGMENTS, STREAM_OGG_PAGE } state;
u32_t want, miss, match;
u8_t* data;
#pragma pack(push, 1)
struct {
char pattern[4];
u8_t version, type;
u64_t granule;
u32_t serial, page, checksum;
u8_t count;
} header;
} ogg;
#pragma pack(pop)
};
void stream_init(log_level level, unsigned stream_buf_size);
void stream_close(void);
void stream_file(const char *header, size_t header_len, unsigned threshold);
void stream_sock(u32_t ip, u16_t port, char codec, const char *header, size_t header_len, unsigned threshold, bool cont_wait);
void stream_sock(u32_t ip, u16_t port, bool use_ssl, bool use_ogg, const char *header, size_t header_len, unsigned threshold, bool cont_wait);
bool stream_disconnect(void);
// decode.c

View File

@@ -59,7 +59,24 @@ is enough and much faster than a mutex
static bool polling;
static sockfd fd;
struct streamstate stream;
struct EXT_RAM_ATTR streamstate stream;
static EXT_RAM_ATTR struct {
bool flac;
u64_t serial;
enum { OGG_OFF, OGG_SYNC, OGG_HEADER, OGG_SEGMENTS, OGG_PAGE } state;
size_t want, miss, match;
u8_t* data, segments[255];
#pragma pack(push, 1)
struct {
char pattern[4];
u8_t version, type;
u64_t granule;
u32_t serial, page, checksum;
u8_t count;
} header;
#pragma pack(pop)
} ogg;
#if USE_SSL
static SSL_CTX *SSLctx;
@@ -113,7 +130,6 @@ static int _poll(SSL *ssl, struct pollfd *pollinfo, int timeout) {
}
#endif
static bool send_header(void) {
char *ptr = stream.header;
int len = stream.header_len;
@@ -148,8 +164,8 @@ static bool running = true;
static void _disconnect(stream_state state, disconnect_code disconnect) {
stream.state = state;
stream.disconnect = disconnect;
if (stream.ogg.state == STREAM_OGG_HEADER && stream.ogg.data) free(stream.ogg.data);
stream.ogg.data = NULL;
if (ogg.state == OGG_PAGE && ogg.data) free(ogg.data);
ogg.data = NULL;
#if USE_SSL
if (ssl) {
SSL_shutdown(ssl);
@@ -162,82 +178,96 @@ static void _disconnect(stream_state state, disconnect_code disconnect) {
wake_controller();
}
static u32_t memfind(const u8_t* haystack, u32_t n, const char* needle, u32_t len, u32_t *offset) {
int i;
static size_t memfind(const u8_t* haystack, size_t n, const char* needle, size_t len, size_t* offset) {
size_t i;
for (i = 0; i < n && *offset != len; i++) *offset = (haystack[i] == needle[*offset]) ? *offset + 1 : 0;
return i;
}
/* https://xiph.org/ogg/doc/framing.html
* https://xiph.org/flac/ogg_mapping.html
* https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-610004.2 */
static void stream_ogg(size_t n) {
if (stream.ogg.state == STREAM_OGG_OFF) return;
if (ogg.state == OGG_OFF) return;
u8_t* p = streambuf->writep;
while (n) {
size_t consumed = min(stream.ogg.miss, n);
size_t consumed = min(ogg.miss, n);
// copy as many bytes as possible and come back later if we do'nt have enough
if (stream.ogg.data) {
memcpy(stream.ogg.data + stream.ogg.want - stream.ogg.miss, p, consumed);
stream.ogg.miss -= consumed;
if (stream.ogg.miss) return;
if (ogg.data) {
memcpy(ogg.data + ogg.want - ogg.miss, p, consumed);
ogg.miss -= consumed;
if (ogg.miss) return;
}
// we have what we want, let's parse
switch (stream.ogg.state) {
case STREAM_OGG_SYNC: {
stream.ogg.miss -= consumed;
switch (ogg.state) {
case OGG_SYNC: {
ogg.miss -= consumed;
if (consumed) break;
// we have to memorize position in case any of last 3 bytes match...
int pos = memfind(p, n, "OggS", 4, &stream.ogg.match);
if (stream.ogg.match == 4) {
consumed = pos - stream.ogg.match;
stream.ogg.state = STREAM_OGG_HEADER;
stream.ogg.miss = stream.ogg.want = sizeof(stream.ogg.header);
stream.ogg.data = (u8_t*) &stream.ogg.header;
stream.ogg.match = 0;
size_t pos = memfind(p, n, "OggS", 4, &ogg.match);
if (ogg.match == 4) {
consumed = pos - ogg.match;
ogg.state = OGG_HEADER;
ogg.miss = ogg.want = sizeof(ogg.header);
ogg.data = (u8_t*) &ogg.header;
ogg.match = 0;
} else {
LOG_INFO("OggS not at expected position");
if (!ogg.match) LOG_INFO("OggS not at expected position %zu/%zu", pos, n);
LOG_INFO("OggS not at expected position %zu/%zu", pos, n);
return;
}
break;
}
case STREAM_OGG_HEADER:
if (!memcmp(stream.ogg.header.pattern, "OggS", 4)) {
stream.ogg.miss = stream.ogg.want = stream.ogg.header.count;
stream.ogg.data = malloc(stream.ogg.miss);
stream.ogg.state = STREAM_OGG_SEGMENTS;
case OGG_HEADER:
if (!memcmp(ogg.header.pattern, "OggS", 4)) {
ogg.miss = ogg.want = ogg.header.count;
ogg.data = ogg.segments;
ogg.state = OGG_SEGMENTS;
} else {
stream.ogg.state = STREAM_OGG_SYNC;
stream.ogg.data = NULL;
ogg.state = OGG_SYNC;
ogg.data = NULL;
}
break;
case STREAM_OGG_SEGMENTS:
case OGG_SEGMENTS:
// calculate size of page using lacing values
for (int i = 0; i < stream.ogg.want; i++) stream.ogg.miss += stream.ogg.data[i];
stream.ogg.want = stream.ogg.miss;
for (size_t i = 0; i < ogg.want; i++) ogg.miss += ogg.data[i];
ogg.want = ogg.miss;
// acquire serial number when we are looking for headers and hit a bos
if (ogg.serial == ULLONG_MAX && (ogg.header.type & 0x02)) ogg.serial = ogg.header.serial;
if (stream.ogg.header.granule == 0) {
// granule 0 means a new stream, so let's look into it
stream.ogg.state = STREAM_OGG_PAGE;
stream.ogg.data = realloc(stream.ogg.data, stream.ogg.want);
} else {
// we have overshot and missed header, reset serial number to restart search (O and -1 are le/be)
if (ogg.header.serial == ogg.serial && ogg.header.granule && ogg.header.granule != -1) ogg.serial = ULLONG_MAX;
// not our serial (the above protected us from granule > 0)
if (ogg.header.serial != ogg.serial) {
// otherwise, jump over data
stream.ogg.state = STREAM_OGG_SYNC;
free(stream.ogg.data);
stream.ogg.data = NULL;
ogg.state = OGG_SYNC;
ogg.data = NULL;
} else {
ogg.state = OGG_PAGE;
ogg.data = malloc(ogg.want);
}
break;
case STREAM_OGG_PAGE: {
u32_t offset = 0;
case OGG_PAGE: {
char** tag = (char* []){ "\x3vorbis", "OpusTags", NULL };
size_t ofs = 0;
// try to find one of valid Ogg pattern (vorbis, opus)
for (char** tag = (char*[]) { "\x3vorbis", "OpusTags", NULL }; *tag; tag++, offset = 0) {
u32_t pos = memfind(stream.ogg.data, stream.ogg.want, *tag, strlen(*tag), &offset);
if (offset != strlen(*tag)) continue;
/* with OggFlac, we need the next page (packet) - VorbisComment is wrapped into a FLAC_METADATA
* and except with vorbis, comment packet starts a new page but even in vorbis, it won't span
* accross multiple pages */
if (ogg.flac) ofs = 4;
else if (!memcmp(ogg.data, "\x7f""FLAC", 5)) ogg.flac = true;
else for (size_t n = 0; *tag; tag++, ofs = 0) if ((ofs = memfind(ogg.data, ogg.want, *tag, strlen(*tag), &n)) && n == strlen(*tag)) break;
if (ofs) {
// u32:len,char[]:vendorId, u32:N, N x (u32:len,char[]:comment)
char* p = (char*) stream.ogg.data + pos;
char* p = (char*) ogg.data + ofs;
p += *p + 4;
u32_t count = *p;
p += 4;
@@ -257,16 +287,19 @@ static void stream_ogg(size_t n) {
stream.header[stream.header_len++] = len;
memcpy(stream.header + stream.header_len, p, len);
stream.header_len += len;
LOG_INFO("metadata: %.*s", len, p);
}
}
ogg.flac = false;
ogg.serial = ULLONG_MAX;
stream.meta_send = true;
wake_controller();
LOG_INFO("Ogg meta len: %u", stream.header_len);
break;
LOG_INFO("Ogg metadata length: %u", stream.header_len - 3);
}
free(stream.ogg.data);
stream.ogg.state = STREAM_OGG_SYNC;
free(ogg.data);
ogg.data = NULL;
ogg.state = OGG_SYNC;
break;
}
default:
@@ -604,7 +637,7 @@ void stream_file(const char *header, size_t header_len, unsigned threshold) {
UNLOCK;
}
void stream_sock(u32_t ip, u16_t port, char codec, const char *header, size_t header_len, unsigned threshold, bool cont_wait) {
void stream_sock(u32_t ip, u16_t port, bool use_ssl, bool use_ogg, const char *header, size_t header_len, unsigned threshold, bool cont_wait) {
struct sockaddr_in addr;
#if EMBEDDED
@@ -704,8 +737,10 @@ void stream_sock(u32_t ip, u16_t port, char codec, const char *header, size_t he
stream.bytes = 0;
stream.threshold = threshold;
stream.ogg.miss = stream.ogg.match = 0;
stream.ogg.state = (codec == 'o' || codec == 'p') ? STREAM_OGG_SYNC : STREAM_OGG_OFF;
ogg.miss = ogg.match = 0;
ogg.state = use_ogg ? OGG_SYNC : OGG_OFF;
ogg.flac = false;
ogg.serial = ULLONG_MAX;
UNLOCK;
}
@@ -726,8 +761,8 @@ bool stream_disconnect(void) {
disc = true;
}
stream.state = STOPPED;
if (stream.ogg.state == STREAM_OGG_HEADER && stream.ogg.data) free(stream.ogg.data);
stream.ogg.data = NULL;
if (ogg.state == OGG_PAGE && ogg.data) free(ogg.data);
ogg.data = NULL;
UNLOCK;
return disc;
}

View File

@@ -201,7 +201,7 @@ static int read_vorbis_header(void) {
// nothing has been found and we have no more bytes, come back later
if (status <= 0) break;
// always set stream serialno if we have a new one
// always set stream serialno if we have a new one (no multiplexed streams)
if (OG(&go, page_bos, &v->page)) OG(&go, stream_reset_serialno, &v->state, OG(&go, page_serialno, &v->page));
// bring new page in if we want it (otherwise we're just skipping)
@@ -242,7 +242,7 @@ static int read_vorbis_header(void) {
OV(&gv, comment_init, &v->comment);
v->comment.vendor = "N/A";
fetch = true;
LOG_INFO("comment skipped succesfully");
LOG_INFO("comment skipped successfully");
// because of lack of page alignment, we might have the setup page already fully in
if (packets == 1) continue;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -72,6 +72,22 @@ declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getStatus(): {};
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
declare function getRadioButton(entry: any): string;
@@ -220,6 +236,14 @@ declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare let sd: {};
declare let rf: boolean;
declare function refreshStatus(): void;

View File

@@ -1,5 +1,5 @@
target_add_binary_data( __idf_wifi-manager webapp/dist/css/index.4bbe29a78a667faa2b6f.css.gz BINARY)
target_add_binary_data( __idf_wifi-manager webapp/dist/css/index.3b0bbfde52d921a84f9b.css.gz BINARY)
target_add_binary_data( __idf_wifi-manager webapp/dist/favicon-32x32.png BINARY)
target_add_binary_data( __idf_wifi-manager webapp/dist/index.html.gz BINARY)
target_add_binary_data( __idf_wifi-manager webapp/dist/js/index.4cb8fa.bundle.js.gz BINARY)
target_add_binary_data( __idf_wifi-manager webapp/dist/js/node_vendors.4cb8fa.bundle.js.gz BINARY)
target_add_binary_data( __idf_wifi-manager webapp/dist/js/index.3fded7.bundle.js.gz BINARY)
target_add_binary_data( __idf_wifi-manager webapp/dist/js/node_vendors.3fded7.bundle.js.gz BINARY)

View File

@@ -1,34 +1,34 @@
// Automatically generated. Do not edit manually!.
#include <inttypes.h>
extern const uint8_t _index_4bbe29a78a667faa2b6f_css_gz_start[] asm("_binary_index_4bbe29a78a667faa2b6f_css_gz_start");
extern const uint8_t _index_4bbe29a78a667faa2b6f_css_gz_end[] asm("_binary_index_4bbe29a78a667faa2b6f_css_gz_end");
extern const uint8_t _index_3b0bbfde52d921a84f9b_css_gz_start[] asm("_binary_index_3b0bbfde52d921a84f9b_css_gz_start");
extern const uint8_t _index_3b0bbfde52d921a84f9b_css_gz_end[] asm("_binary_index_3b0bbfde52d921a84f9b_css_gz_end");
extern const uint8_t _favicon_32x32_png_start[] asm("_binary_favicon_32x32_png_start");
extern const uint8_t _favicon_32x32_png_end[] asm("_binary_favicon_32x32_png_end");
extern const uint8_t _index_html_gz_start[] asm("_binary_index_html_gz_start");
extern const uint8_t _index_html_gz_end[] asm("_binary_index_html_gz_end");
extern const uint8_t _index_4cb8fa_bundle_js_gz_start[] asm("_binary_index_4cb8fa_bundle_js_gz_start");
extern const uint8_t _index_4cb8fa_bundle_js_gz_end[] asm("_binary_index_4cb8fa_bundle_js_gz_end");
extern const uint8_t _node_vendors_4cb8fa_bundle_js_gz_start[] asm("_binary_node_vendors_4cb8fa_bundle_js_gz_start");
extern const uint8_t _node_vendors_4cb8fa_bundle_js_gz_end[] asm("_binary_node_vendors_4cb8fa_bundle_js_gz_end");
extern const uint8_t _index_3fded7_bundle_js_gz_start[] asm("_binary_index_3fded7_bundle_js_gz_start");
extern const uint8_t _index_3fded7_bundle_js_gz_end[] asm("_binary_index_3fded7_bundle_js_gz_end");
extern const uint8_t _node_vendors_3fded7_bundle_js_gz_start[] asm("_binary_node_vendors_3fded7_bundle_js_gz_start");
extern const uint8_t _node_vendors_3fded7_bundle_js_gz_end[] asm("_binary_node_vendors_3fded7_bundle_js_gz_end");
const char * resource_lookups[] = {
"/css/index.4bbe29a78a667faa2b6f.css.gz",
"/css/index.3b0bbfde52d921a84f9b.css.gz",
"/favicon-32x32.png",
"/index.html.gz",
"/js/index.4cb8fa.bundle.js.gz",
"/js/node_vendors.4cb8fa.bundle.js.gz",
"/js/index.3fded7.bundle.js.gz",
"/js/node_vendors.3fded7.bundle.js.gz",
""
};
const uint8_t * resource_map_start[] = {
_index_4bbe29a78a667faa2b6f_css_gz_start,
_index_3b0bbfde52d921a84f9b_css_gz_start,
_favicon_32x32_png_start,
_index_html_gz_start,
_index_4cb8fa_bundle_js_gz_start,
_node_vendors_4cb8fa_bundle_js_gz_start
_index_3fded7_bundle_js_gz_start,
_node_vendors_3fded7_bundle_js_gz_start
};
const uint8_t * resource_map_end[] = {
_index_4bbe29a78a667faa2b6f_css_gz_end,
_index_3b0bbfde52d921a84f9b_css_gz_end,
_favicon_32x32_png_end,
_index_html_gz_end,
_index_4cb8fa_bundle_js_gz_end,
_node_vendors_4cb8fa_bundle_js_gz_end
_index_3fded7_bundle_js_gz_end,
_node_vendors_3fded7_bundle_js_gz_end
};

View File

@@ -1,6 +1,6 @@
/***********************************
webpack_headers
dist/css/index.4bbe29a78a667faa2b6f.css.gz,dist/favicon-32x32.png,dist/index.html.gz,dist/js/index.4cb8fa.bundle.js.gz,dist/js/node_vendors.4cb8fa.bundle.js.gz
dist/css/index.3b0bbfde52d921a84f9b.css.gz,dist/favicon-32x32.png,dist/index.html.gz,dist/js/index.3fded7.bundle.js.gz,dist/js/node_vendors.3fded7.bundle.js.gz
***********************************/
#pragma once
#include <inttypes.h>

View File

@@ -129,7 +129,7 @@ menu "Squeezelite-ESP32"
config DAC_CONTROLSET
string
default "{ \"init\": [ {\"reg\":41, \"val\":128}, {\"reg\":18, \"val\":255} ], \"poweron\": [ {\"reg\":18, \"val\":64, \"mode\":\"or\"} ], \"poweroff\": [ {\"reg\":18, \"val\":191, \"mode\":\"and\"} ] }" if TWATCH2020
default "{\"init\":[ {\"reg\":0,\"val\":128}, {\"reg\":0,\"val\":0}, {\"reg\":25,\"val\":4}, {\"reg\":1,\"val\":80}, {\"reg\":2,\"val\":0}, {\"reg\":8,\"val\":0}, {\"reg\":4,\"val\":192}, {\"reg\":0,\"val\":18}, {\"reg\":1,\"val\":0}, {\"reg\":23,\"val\":24}, {\"reg\":24,\"val\":2}, {\"reg\":38,\"val\":9}, {\"reg\":39,\"val\":144}, {\"reg\":42,\"val\":144}, {\"reg\":43,\"val\":128}, {\"reg\":45,\"val\":128}, {\"reg\":27,\"val\":0}, {\"reg\":26,\"val\":0}, {\"reg\":2,\"val\":240}, {\"reg\":2,\"val\":0}, {\"reg\":29,\"val\":28}, {\"reg\":4,\"val\":48}, {\"reg\":25,\"val\":0}, {\"reg\":46,\"val\":33}, {\"reg\":47,\"val\":33} ]}" if MUSE
default "{\"init\":[ {\"reg\":0,\"val\":128}, {\"reg\":0,\"val\":0}, {\"reg\":25,\"val\":4}, {\"reg\":1,\"val\":80}, {\"reg\":2,\"val\":0}, {\"reg\":8,\"val\":0}, {\"reg\":4,\"val\":192}, {\"reg\":0,\"val\":18}, {\"reg\":1,\"val\":0}, {\"reg\":23,\"val\":24}, {\"reg\":24,\"val\":2}, {\"reg\":38,\"val\":9}, {\"reg\":39,\"val\":144}, {\"reg\":42,\"val\":144}, {\"reg\":43,\"val\":128}, {\"reg\":45,\"val\":128}, {\"reg\":27,\"val\":0}, {\"reg\":26,\"val\":0}, {\"reg\":2,\"val\":240}, {\"reg\":2,\"val\":0}, {\"reg\":29,\"val\":28}, {\"reg\":4,\"val\":60}, {\"reg\":25,\"val\":0}, {\"reg\":46,\"val\":33}, {\"reg\":47,\"val\":33} , {\"reg\":48,\"val\":33}, {\"reg\":49,\"val\":33}]}" if MUSE
default ""
config AUDIO_CONTROLS
string
@@ -153,7 +153,8 @@ menu "Squeezelite-ESP32"
default -1
config JACK_GPIO
int
default 34 if SQUEEZEAMP || MUSE
default 27 if MUSE
default 34 if SQUEEZEAMP
default -1
config SPKFAULT_GPIO
int
@@ -369,6 +370,12 @@ menu "Squeezelite-ESP32"
help
Set GPIO for rotary encoder (quadrature phase). See README on SqueezeESP32 project's GitHub for more details
A=<gpio>,B=<gpio>[,SW=gpio>[[,knobonly[=<ms>]|[,volume][,longpress]]
config VOLUME_ROTARY_ENCODER
string "Volume Rotary Encoder configuration"
default ""
help
Set GPIO for volume rotary encoder (quadrature phase). See README on SqueezeESP32 project's GitHub for more details
A=<gpio>,B=<gpio>[,SW=gpio>]
config GPIO_EXP_CONFIG
string "GPIO expander configuration"
help
@@ -457,4 +464,4 @@ menu "Squeezelite-ESP32"
default "squeezelite -o I2S -b 500:2000 -d all=info -C 30"
help
This is the command to run when starting the device
endmenu
endmenu

View File

@@ -89,6 +89,7 @@ const DefaultStringVal defaultStringVals[] = {
{"actrls_config", ""},
{"lms_ctrls_raw", "n"},
{"rotary_config", CONFIG_ROTARY_ENCODER},
{"volume_rotary", CONFIG_VOLUME_ROTARY_ENCODER},
{"display_config", CONFIG_DISPLAY_CONFIG},
{"eth_config", CONFIG_ETH_CONFIG},
{"i2c_config", CONFIG_I2C_CONFIG},
@@ -114,6 +115,7 @@ const DefaultStringVal defaultStringVals[] = {
{"dhcp_tmout", "8"},
{"target", CONFIG_TARGET},
{"led_vu_config", ""},
{"autoexec", "1"},
#ifdef CONFIG_BT_SINK
{"bt_sink_pin", STR(CONFIG_BT_SINK_PIN)},
{"bt_sink_volume", "127"},
@@ -123,11 +125,10 @@ const DefaultStringVal defaultStringVals[] = {
{"a2dp_ctmt", STR(CONFIG_A2DP_CONNECT_TIMEOUT_MS)},
{"a2dp_ctrld", STR(CONFIG_A2DP_CONTROL_DELAY_MS)},
{"a2dp_sink_name", CONFIG_A2DP_SINK_NAME},
{"autoexec", "1"},
#endif
#ifdef CONFIG_AIRPLAY_SINK
{"airplay_port", CONFIG_AIRPLAY_PORT},
{"enable_airplay", STR(CONFIG_AIRPLAY_SINK)}
#endif
#endif
};
static bool bNetworkConnected=false;

Binary file not shown.

View File

@@ -220,7 +220,8 @@ sub send_equalizer {
sub send_loudness {
my ($client, $loudness) = @_;
$loudness ||= $prefs->client($client)->get('loudness') || 0;
$loudness //= $prefs->client($client)->get('loudness');
my $data = pack("c1", $loudness);
$client->sendFrame( loud => \$data );
}

View File

@@ -10,6 +10,6 @@
<name>PLUGIN_SQUEEZEESP32</name>
<description>PLUGIN_SQUEEZEESP32_DESC</description>
<module>Plugins::SqueezeESP32::Plugin</module>
<version>0.600</version>
<version>0.601</version>
<creator>Philippe</creator>
</extensions>

View File

@@ -1,10 +1,10 @@
<?xml version='1.0' standalone='yes'?>
<extensions>
<plugins>
<plugin version="0.600" name="SqueezeESP32" minTarget="7.9" maxTarget="*">
<plugin version="0.601" name="SqueezeESP32" minTarget="7.9" maxTarget="*">
<link>https://github.com/sle118/squeezelite-esp32</link>
<creator>Philippe</creator>
<sha>335b585e22eddbddacce00f34001d8bf2ee0f54f</sha>
<sha>439637e01db7410157a333446cd284509f877849</sha>
<email>philippe_44@outlook.com</email>
<desc lang="EN">SqueezeESP32 additional player id (100/101)</desc>
<url>http://raw.githubusercontent.com/sle118/squeezelite-esp32/master-v4.3/plugin/SqueezeESP32.zip</url>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,22 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----

View File

@@ -1,24 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIEFzCCAv+gAwIBAgIQB/LzXIeod6967+lHmTUlvTANBgkqhkiG9w0BAQwFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0yMTA0MTQwMDAwMDBaFw0zMTA0MTMyMzU5NTlaMFYxCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMDAuBgNVBAMTJ0RpZ2lDZXJ0IFRMUyBI
eWJyaWQgRUNDIFNIQTM4NCAyMDIwIENBMTB2MBAGByqGSM49AgEGBSuBBAAiA2IA
BMEbxppbmNmkKaDp1AS12+umsmxVwP/tmMZJLwYnUcu/cMEFesOxnYeJuq20ExfJ
qLSDyLiQ0cx0NTY8g3KwtdD3ImnI8YDEe0CPz2iHJlw5ifFNkU3aiYvkA8ND5b8v
c6OCAYIwggF+MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFAq8CCkXjKU5
bXoOzjPHLrPt+8N6MB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA4G
A1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdgYI
KwYBBQUHAQEEajBoMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j
b20wQAYIKwYBBQUHMAKGNGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp
Q2VydEdsb2JhbFJvb3RDQS5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2Ny
bDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDA9BgNVHSAE
NjA0MAsGCWCGSAGG/WwCATAHBgVngQwBATAIBgZngQwBAgEwCAYGZ4EMAQICMAgG
BmeBDAECAzANBgkqhkiG9w0BAQwFAAOCAQEAR1mBf9QbH7Bx9phdGLqYR5iwfnYr
6v8ai6wms0KNMeZK6BnQ79oU59cUkqGS8qcuLa/7Hfb7U7CKP/zYFgrpsC62pQsY
kDUmotr2qLcy/JUjS8ZFucTP5Hzu5sn4kL1y45nDHQsFfGqXbbKrAjbYwrwsAZI/
BKOLdRHHuSm8EdCGupK8JvllyDfNJvaGEwwEqonleLHBTnm8dqMLUeTF0J5q/hos
Vq4GNiejcxwIfZMy0MJEGdqN9A57HSgDKwmKdsp33Id6rHtSJlWncg+d0ohP/rEh
xRqhqjn1VtvChMQ1H3Dau0bwhr9kAMQ+959GG50jBbl9s08PqUU643QwmA==
-----END CERTIFICATE-----

View File

@@ -1,28 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIEvjCCA6agAwIBAgIQBtjZBNVYQ0b2ii+nVCJ+xDANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0yMTA0MTQwMDAwMDBaFw0zMTA0MTMyMzU5NTlaME8xCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxKTAnBgNVBAMTIERpZ2lDZXJ0IFRMUyBS
U0EgU0hBMjU2IDIwMjAgQ0ExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAwUuzZUdwvN1PWNvsnO3DZuUfMRNUrUpmRh8sCuxkB+Uu3Ny5CiDt3+PE0J6a
qXodgojlEVbbHp9YwlHnLDQNLtKS4VbL8Xlfs7uHyiUDe5pSQWYQYE9XE0nw6Ddn
g9/n00tnTCJRpt8OmRDtV1F0JuJ9x8piLhMbfyOIJVNvwTRYAIuE//i+p1hJInuW
raKImxW8oHzf6VGo1bDtN+I2tIJLYrVJmuzHZ9bjPvXj1hJeRPG/cUJ9WIQDgLGB
Afr5yjK7tI4nhyfFK3TUqNaX3sNk+crOU6JWvHgXjkkDKa77SU+kFbnO8lwZV21r
eacroicgE7XQPUDTITAHk+qZ9QIDAQABo4IBgjCCAX4wEgYDVR0TAQH/BAgwBgEB
/wIBADAdBgNVHQ4EFgQUt2ui6qiqhIx56rTaD5iyxZV2ufQwHwYDVR0jBBgwFoAU
A95QNVbRTLtm8KPiGxvDl7I90VUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQG
CCsGAQUFBwMBBggrBgEFBQcDAjB2BggrBgEFBQcBAQRqMGgwJAYIKwYBBQUHMAGG
GGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBABggrBgEFBQcwAoY0aHR0cDovL2Nh
Y2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNydDBCBgNV
HR8EOzA5MDegNaAzhjFodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRH
bG9iYWxSb290Q0EuY3JsMD0GA1UdIAQ2MDQwCwYJYIZIAYb9bAIBMAcGBWeBDAEB
MAgGBmeBDAECATAIBgZngQwBAgIwCAYGZ4EMAQIDMA0GCSqGSIb3DQEBCwUAA4IB
AQCAMs5eC91uWg0Kr+HWhMvAjvqFcO3aXbMM9yt1QP6FCvrzMXi3cEsaiVi6gL3z
ax3pfs8LulicWdSQ0/1s/dCYbbdxglvPbQtaCdB73sRD2Cqk3p5BJl+7j5nL3a7h
qG+fh/50tx8bIKuxT8b1Z11dmzzp/2n3YWzW2fP9NsarA4h20ksudYbj/NhVfSbC
EXffPgK2fPOre3qGNm+499iTcc+G33Mw+nur7SpZyEKEOxEXGlLzyQ4UfaJbcme6
ce1XR2bFuAJKZTRei9AqPCCcUZlM51Ke92sRKw2Sfh3oius2FkOH6ipjv3U/697E
A7sKPPcw7+uvTPyLNhBzPvOk
-----END CERTIFICATE-----

View File

@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDqDCCAy6gAwIBAgIRAPNkTmtuAFAjfglGvXvh9R0wCgYIKoZIzj0EAwMwgYgx
CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5MRQwEgYDVQQHEwtKZXJz
ZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMS4wLAYDVQQD
EyVVU0VSVHJ1c3QgRUNDIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTE4MTEw
MjAwMDAwMFoXDTMwMTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAkdCMRswGQYDVQQI
ExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGDAWBgNVBAoT
D1NlY3RpZ28gTGltaXRlZDE3MDUGA1UEAxMuU2VjdGlnbyBFQ0MgRG9tYWluIFZh
bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEH
A0IABHkYk8qfbZ5sVwAjBTcLXw9YWsTef1Wj6R7W2SUKiKAgSh16TwUwimNJE4xk
IQeV/To14UrOkPAY9z2vaKb71EijggFuMIIBajAfBgNVHSMEGDAWgBQ64QmG1M8Z
wpZ2dEl23OA1xmNjmjAdBgNVHQ4EFgQU9oUKOxGG4QR9DqoLLNLuzGR7e64wDgYD
VR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0lBBYwFAYIKwYB
BQUHAwEGCCsGAQUFBwMCMBsGA1UdIAQUMBIwBgYEVR0gADAIBgZngQwBAgEwUAYD
VR0fBEkwRzBFoEOgQYY/aHR0cDovL2NybC51c2VydHJ1c3QuY29tL1VTRVJUcnVz
dEVDQ0NlcnRpZmljYXRpb25BdXRob3JpdHkuY3JsMHYGCCsGAQUFBwEBBGowaDA/
BggrBgEFBQcwAoYzaHR0cDovL2NydC51c2VydHJ1c3QuY29tL1VTRVJUcnVzdEVD
Q0FkZFRydXN0Q0EuY3J0MCUGCCsGAQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1
c3QuY29tMAoGCCqGSM49BAMDA2gAMGUCMEvnx3FcsVwJbZpCYF9z6fDWJtS1UVRs
cS0chWBNKPFNpvDKdrdKRe+oAkr2jU+ubgIxAODheSr2XhcA7oz9HmedGdMhlrd9
4ToKFbZl+/OnFFzqnvOhcjHvClECEQcKmc8fmA==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,35 @@
-----BEGIN CERTIFICATE-----
MIIGEzCCA/ugAwIBAgIQfVtRJrR2uhHbdBYLvFMNpzANBgkqhkiG9w0BAQwFADCB
iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTgx
MTAyMDAwMDAwWhcNMzAxMjMxMjM1OTU5WjCBjzELMAkGA1UEBhMCR0IxGzAZBgNV
BAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEYMBYGA1UE
ChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQDEy5TZWN0aWdvIFJTQSBEb21haW4g
VmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEA1nMz1tc8INAA0hdFuNY+B6I/x0HuMjDJsGz99J/LEpgPLT+N
TQEMgg8Xf2Iu6bhIefsWg06t1zIlk7cHv7lQP6lMw0Aq6Tn/2YHKHxYyQdqAJrkj
eocgHuP/IJo8lURvh3UGkEC0MpMWCRAIIz7S3YcPb11RFGoKacVPAXJpz9OTTG0E
oKMbgn6xmrntxZ7FN3ifmgg0+1YuWMQJDgZkW7w33PGfKGioVrCSo1yfu4iYCBsk
Haswha6vsC6eep3BwEIc4gLw6uBK0u+QDrTBQBbwb4VCSmT3pDCg/r8uoydajotY
uK3DGReEY+1vVv2Dy2A0xHS+5p3b4eTlygxfFQIDAQABo4IBbjCCAWowHwYDVR0j
BBgwFoAUU3m/WqorSs9UgOHYm8Cd8rIDZsswHQYDVR0OBBYEFI2MXsRUrYrhd+mb
+ZsF4bgBjWHhMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMB0G
A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAbBgNVHSAEFDASMAYGBFUdIAAw
CAYGZ4EMAQIBMFAGA1UdHwRJMEcwRaBDoEGGP2h0dHA6Ly9jcmwudXNlcnRydXN0
LmNvbS9VU0VSVHJ1c3RSU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDB2Bggr
BgEFBQcBAQRqMGgwPwYIKwYBBQUHMAKGM2h0dHA6Ly9jcnQudXNlcnRydXN0LmNv
bS9VU0VSVHJ1c3RSU0FBZGRUcnVzdENBLmNydDAlBggrBgEFBQcwAYYZaHR0cDov
L29jc3AudXNlcnRydXN0LmNvbTANBgkqhkiG9w0BAQwFAAOCAgEAMr9hvQ5Iw0/H
ukdN+Jx4GQHcEx2Ab/zDcLRSmjEzmldS+zGea6TvVKqJjUAXaPgREHzSyrHxVYbH
7rM2kYb2OVG/Rr8PoLq0935JxCo2F57kaDl6r5ROVm+yezu/Coa9zcV3HAO4OLGi
H19+24rcRki2aArPsrW04jTkZ6k4Zgle0rj8nSg6F0AnwnJOKf0hPHzPE/uWLMUx
RP0T7dWbqWlod3zu4f+k+TY4CFM5ooQ0nBnzvg6s1SQ36yOoeNDT5++SR2RiOSLv
xvcRviKFxmZEJCaOEDKNyJOuB56DPi/Z+fVGjmO+wea03KbNIaiGCpXZLoUmGv38
sbZXQm2V0TP2ORQGgkE49Y9Y3IBbpNV9lXj9p5v//cWoaasm56ekBYdbqbe4oyAL
l6lFhd2zi+WJN44pDfwGF/Y4QA5C5BIG+3vzxhFoYt/jmPQT2BVPi7Fp2RBgvGQq
6jG35LWjOhSbJuMLe/0CjraZwTiXWTb2qHSihrZe68Zk6s+go/lunrotEbaGmAhY
LcmsJWTyXnW0OMGuf1pGg+pRyrbxmRE1a6Vqe8YAsOf4vmSyrcjC8azjUeqkk+B5
yOGBQMkKW+ESPMFgKuOXwIlCypTPRpgSabuY0MLTDXJLR27lk8QyKGOHQ+SwMj4K
00u/I5sUKUErmgQfky3xxzlIPK1aEn8=
-----END CERTIFICATE-----

Binary file not shown.

View File

@@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIID0zCCArugAwIBAgIQVmcdBOpPmUxvEIFHWdJ1lDANBgkqhkiG9w0BAQwFADB7
MQswCQYDVQQGEwJHQjEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD
VQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UE
AwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTE5MDMxMjAwMDAwMFoXDTI4
MTIzMTIzNTk1OVowgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5
MRQwEgYDVQQHEwtKZXJzZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBO
ZXR3b3JrMS4wLAYDVQQDEyVVU0VSVHJ1c3QgRUNDIENlcnRpZmljYXRpb24gQXV0
aG9yaXR5MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEGqxUWqn5aCPnetUkb1PGWthL
q8bVttHmc3Gu3ZzWDGH926CJA7gFFOxXzu5dP+Ihs8731Ip54KODfi2X0GHE8Znc
JZFjq38wo7Rw4sehM5zzvy5cU7Ffs30yf4o043l5o4HyMIHvMB8GA1UdIwQYMBaA
FKARCiM+lvEH7OKvKe+CpX/QMKS0MB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1
xmNjmjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zARBgNVHSAECjAI
MAYGBFUdIAAwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5j
b20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEEKDAmMCQG
CCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZIhvcNAQEM
BQADggEBABns652JLCALBIAdGN5CmXKZFjK9Dpx1WywV4ilAbe7/ctvbq5AfjJXy
ij0IckKJUAfiORVsAYfZFhr1wHUrxeZWEQff2Ji8fJ8ZOd+LygBkc7xGEJuTI42+
FsMuCIKchjN0djsoTI0DQoWz4rIjQtUfenVqGtF8qmchxDM6OW1TyaLtYiKou+JV
bJlsQ2uRl9EMC5MCHdK8aXdJ5htN978UeAOwproLtOGFfy/cQjutdAFI3tZs4RmY
CV4Ks2dH/hzg1cEo70qLRDEmBDeNiXQ2Lu+lIg+DdEmSx/cQwgwp+7e9un/jX9Wf
8qn0dNW44bOwgeThpWOjzOoEeJBuv/c=
-----END CERTIFICATE-----

Binary file not shown.

View File

@@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFgTCCBGmgAwIBAgIQOXJEOvkit1HX02wQ3TE1lTANBgkqhkiG9w0BAQwFADB7
MQswCQYDVQQGEwJHQjEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD
VQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UE
AwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTE5MDMxMjAwMDAwMFoXDTI4
MTIzMTIzNTk1OVowgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5
MRQwEgYDVQQHEwtKZXJzZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBO
ZXR3b3JrMS4wLAYDVQQDEyVVU0VSVHJ1c3QgUlNBIENlcnRpZmljYXRpb24gQXV0
aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAgBJlFzYOw9sI
s9CsVw127c0n00ytUINh4qogTQktZAnczomfzD2p7PbPwdzx07HWezcoEStH2jnG
vDoZtF+mvX2do2NCtnbyqTsrkfjib9DsFiCQCT7i6HTJGLSR1GJk23+jBvGIGGqQ
Ijy8/hPwhxR79uQfjtTkUcYRZ0YIUcuGFFQ/vDP+fmyc/xadGL1RjjWmp2bIcmfb
IWax1Jt4A8BQOujM8Ny8nkz+rwWWNR9XWrf/zvk9tyy29lTdyOcSOk2uTIq3XJq0
tyA9yn8iNK5+O2hmAUTnAU5GU5szYPeUvlM3kHND8zLDU+/bqv50TmnHa4xgk97E
xwzf4TKuzJM7UXiVZ4vuPVb+DNBpDxsP8yUmazNt925H+nND5X4OpWaxKXwyhGNV
icQNwZNUMBkTrNN9N6frXTpsNVzbQdcS2qlJC9/YgIoJk2KOtWbPJYjNhLixP6Q5
D9kCnusSTJV882sFqV4Wg8y4Z+LoE53MW4LTTLPtW//e5XOsIzstAL81VXQJSdhJ
WBp/kjbmUZIO8yZ9HE0XvMnsQybQv0FfQKlERPSZ51eHnlAfV1SoPv10Yy+xUGUJ
5lhCLkMaTLTwJUdZ+gQek9QmRkpQgbLevni3/GcV4clXhB4PY9bpYrrWX1Uu6lzG
KAgEJTm4Diup8kyXHAc/DVL17e8vgg8CAwEAAaOB8jCB7zAfBgNVHSMEGDAWgBSg
EQojPpbxB+zirynvgqV/0DCktDAdBgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rID
ZsswDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAG
BgRVHSAAMEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwuY29tb2RvY2EuY29t
L0FBQUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDQGCCsGAQUFBwEBBCgwJjAkBggr
BgEFBQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2EuY29tMA0GCSqGSIb3DQEBDAUA
A4IBAQAYh1HcdCE9nIrgJ7cz0C7M7PDmy14R3iJvm3WOnnL+5Nb+qh+cli3vA0p+
rvSNb3I8QzvAP+u431yqqcau8vzY7qN7Q/aGNnwU4M309z/+3ri0ivCRlv79Q2R+
/czSAaF9ffgZGclCKxO/WIu6pKJmBHaIkU4MiRTOok3JMrO66BQavHHxW/BBC5gA
CiIDEOUMsfnNkjcZ7Tvx5Dq2+UUTJnWvu6rvP3t3O9LEApE9GQDTF1w52z97GA1F
zZOFli9d31kWTz9RvdVFGD/tSo7oBmF0Ixa1DVBzJ0RHfxBdiSprhTEUxOipakyA
vGp4z7h/jnZymQyd/teRCBaho1+V
-----END CERTIFICATE-----

View File

@@ -1,31 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIFajCCBPGgAwIBAgIQDNCovsYyz+ZF7KCpsIT7HDAKBggqhkjOPQQDAzBWMQsw
CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMTAwLgYDVQQDEydEaWdp
Q2VydCBUTFMgSHlicmlkIEVDQyBTSEEzODQgMjAyMCBDQTEwHhcNMjMwMjE0MDAw
MDAwWhcNMjQwMzE0MjM1OTU5WjBmMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2Fs
aWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHVi
LCBJbmMuMRMwEQYDVQQDEwpnaXRodWIuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D
AQcDQgAEo6QDRgPfRlFWy8k5qyLN52xZlnqToPu5QByQMog2xgl2nFD1Vfd2Xmgg
nO4i7YMMFTAQQUReMqyQodWq8uVDs6OCA48wggOLMB8GA1UdIwQYMBaAFAq8CCkX
jKU5bXoOzjPHLrPt+8N6MB0GA1UdDgQWBBTHByd4hfKdM8lMXlZ9XNaOcmfr3jAl
BgNVHREEHjAcggpnaXRodWIuY29tgg53d3cuZ2l0aHViLmNvbTAOBgNVHQ8BAf8E
BAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMIGbBgNVHR8EgZMw
gZAwRqBEoEKGQGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRMU0h5
YnJpZEVDQ1NIQTM4NDIwMjBDQTEtMS5jcmwwRqBEoEKGQGh0dHA6Ly9jcmw0LmRp
Z2ljZXJ0LmNvbS9EaWdpQ2VydFRMU0h5YnJpZEVDQ1NIQTM4NDIwMjBDQTEtMS5j
cmwwPgYDVR0gBDcwNTAzBgZngQwBAgIwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3
dy5kaWdpY2VydC5jb20vQ1BTMIGFBggrBgEFBQcBAQR5MHcwJAYIKwYBBQUHMAGG
GGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBPBggrBgEFBQcwAoZDaHR0cDovL2Nh
Y2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VExTSHlicmlkRUNDU0hBMzg0MjAy
MENBMS0xLmNydDAJBgNVHRMEAjAAMIIBgAYKKwYBBAHWeQIEAgSCAXAEggFsAWoA
dwDuzdBk1dsazsVct520zROiModGfLzs3sNRSFlGcR+1mwAAAYZQ3Rv6AAAEAwBI
MEYCIQDkFq7T4iy6gp+pefJLxpRS7U3gh8xQymmxtI8FdzqU6wIhALWfw/nLD63Q
YPIwG3EFchINvWUfB6mcU0t2lRIEpr8uAHYASLDja9qmRzQP5WoC+p0w6xxSActW
3SyB2bu/qznYhHMAAAGGUN0cKwAABAMARzBFAiAePGAyfiBR9dbhr31N9ZfESC5G
V2uGBTcyTyUENrH3twIhAPwJfsB8A4MmNr2nW+sdE1n2YiCObW+3DTHr2/UR7lvU
AHcAO1N3dT4tuYBOizBbBv5AO2fYT8P0x70ADS1yb+H61BcAAAGGUN0cOgAABAMA
SDBGAiEAzOBr9OZ0+6OSZyFTiywN64PysN0FLeLRyL5jmEsYrDYCIQDu0jtgWiMI
KU6CM0dKcqUWLkaFE23c2iWAhYAHqrFRRzAKBggqhkjOPQQDAwNnADBkAjAE3A3U
3jSZCpwfqOHBdlxi9ASgKTU+wg0qw3FqtfQ31OwLYFdxh0MlNk/HwkjRSWgCMFbQ
vMkXEPvNvv4t30K6xtpG26qmZ+6OiISBIIXMljWnsiYR1gyZnTzIg3AQSw4Vmw==
MIIEoTCCBEigAwIBAgIRAKtmhrVie+gFloITMBKGSfUwCgYIKoZIzj0EAwIwgY8x
CzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNV
BAcTB1NhbGZvcmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDE3MDUGA1UEAxMu
U2VjdGlnbyBFQ0MgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTAe
Fw0yNTAyMDUwMDAwMDBaFw0yNjAyMDUyMzU5NTlaMBUxEzARBgNVBAMTCmdpdGh1
Yi5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQgNFxG/yzL+CSarvC7L3ep
H5chNnG6wiYYxR5D/Z1J4MxGnIX8KbT5fCgLoyzHXL9v50bdBIq6y4AtN4gN7gbW
o4IC/DCCAvgwHwYDVR0jBBgwFoAU9oUKOxGG4QR9DqoLLNLuzGR7e64wHQYDVR0O
BBYEFFPIf96emE7HTda83quVPjA9PdHIMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMB
Af8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBJBgNVHSAEQjBA
MDQGCysGAQQBsjEBAgIHMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8vc2VjdGlnby5j
b20vQ1BTMAgGBmeBDAECATCBhAYIKwYBBQUHAQEEeDB2ME8GCCsGAQUFBzAChkNo
dHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29FQ0NEb21haW5WYWxpZGF0aW9u
U2VjdXJlU2VydmVyQ0EuY3J0MCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0
aWdvLmNvbTCCAX4GCisGAQQB1nkCBAIEggFuBIIBagFoAHUAlpdkv1VYl633Q4do
NwhCd+nwOtX2pPM2bkakPw/KqcYAAAGU02uUSwAABAMARjBEAiA7i6o+LpQjt6Ae
EjltHhs/TiECnHd0xTeer/3vD1xgsAIgYlGwRot+SqEBCs//frx/YHTPwox9QLdy
7GjTLWHfcMAAdwAZhtTHKKpv/roDb3gqTQGRqs4tcjEPrs5dcEEtJUzH1AAAAZTT
a5PtAAAEAwBIMEYCIQDlrInx7J+3MfqgxB2+Fvq3dMlk1qj4chOw/+HkYVfG0AIh
AMT+JKAQfUuIdBGxfryrGrwsOD3pRs1tyAyykdPGRgsTAHYAyzj3FYl8hKFEX1vB
3fvJbvKaWc1HCmkFhbDLFMMUWOcAAAGU02uUJQAABAMARzBFAiEA1GKW92agDFNJ
IYrMH3gaJdXsdIVpUcZOfxH1FksbuLECIFJCfslINhc53Q0TIMJHdcFOW2tgG4tB
A1dL881tXbMnMCUGA1UdEQQeMByCCmdpdGh1Yi5jb22CDnd3dy5naXRodWIuY29t
MAoGCCqGSM49BAMCA0cAMEQCIHGMp27BBBJ1356lCe2WYyzYIp/fAONQM3AkeE/f
ym0sAiBtVfN3YgIZ+neHEfwcRhhz4uDpc8F+tKmtceWJSicMkA==
-----END CERTIFICATE-----

View File

@@ -1,191 +1,222 @@
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
MIIDqDCCAy6gAwIBAgIRAPNkTmtuAFAjfglGvXvh9R0wCgYIKoZIzj0EAwMwgYgx
CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5MRQwEgYDVQQHEwtKZXJz
ZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMS4wLAYDVQQD
EyVVU0VSVHJ1c3QgRUNDIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTE4MTEw
MjAwMDAwMFoXDTMwMTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAkdCMRswGQYDVQQI
ExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGDAWBgNVBAoT
D1NlY3RpZ28gTGltaXRlZDE3MDUGA1UEAxMuU2VjdGlnbyBFQ0MgRG9tYWluIFZh
bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEH
A0IABHkYk8qfbZ5sVwAjBTcLXw9YWsTef1Wj6R7W2SUKiKAgSh16TwUwimNJE4xk
IQeV/To14UrOkPAY9z2vaKb71EijggFuMIIBajAfBgNVHSMEGDAWgBQ64QmG1M8Z
wpZ2dEl23OA1xmNjmjAdBgNVHQ4EFgQU9oUKOxGG4QR9DqoLLNLuzGR7e64wDgYD
VR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0lBBYwFAYIKwYB
BQUHAwEGCCsGAQUFBwMCMBsGA1UdIAQUMBIwBgYEVR0gADAIBgZngQwBAgEwUAYD
VR0fBEkwRzBFoEOgQYY/aHR0cDovL2NybC51c2VydHJ1c3QuY29tL1VTRVJUcnVz
dEVDQ0NlcnRpZmljYXRpb25BdXRob3JpdHkuY3JsMHYGCCsGAQUFBwEBBGowaDA/
BggrBgEFBQcwAoYzaHR0cDovL2NydC51c2VydHJ1c3QuY29tL1VTRVJUcnVzdEVD
Q0FkZFRydXN0Q0EuY3J0MCUGCCsGAQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1
c3QuY29tMAoGCCqGSM49BAMDA2gAMGUCMEvnx3FcsVwJbZpCYF9z6fDWJtS1UVRs
cS0chWBNKPFNpvDKdrdKRe+oAkr2jU+ubgIxAODheSr2XhcA7oz9HmedGdMhlrd9
4ToKFbZl+/OnFFzqnvOhcjHvClECEQcKmc8fmA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEFzCCAv+gAwIBAgIQB/LzXIeod6967+lHmTUlvTANBgkqhkiG9w0BAQwFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0yMTA0MTQwMDAwMDBaFw0zMTA0MTMyMzU5NTlaMFYxCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMDAuBgNVBAMTJ0RpZ2lDZXJ0IFRMUyBI
eWJyaWQgRUNDIFNIQTM4NCAyMDIwIENBMTB2MBAGByqGSM49AgEGBSuBBAAiA2IA
BMEbxppbmNmkKaDp1AS12+umsmxVwP/tmMZJLwYnUcu/cMEFesOxnYeJuq20ExfJ
qLSDyLiQ0cx0NTY8g3KwtdD3ImnI8YDEe0CPz2iHJlw5ifFNkU3aiYvkA8ND5b8v
c6OCAYIwggF+MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFAq8CCkXjKU5
bXoOzjPHLrPt+8N6MB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA4G
A1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdgYI
KwYBBQUHAQEEajBoMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j
b20wQAYIKwYBBQUHMAKGNGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp
Q2VydEdsb2JhbFJvb3RDQS5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2Ny
bDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDA9BgNVHSAE
NjA0MAsGCWCGSAGG/WwCATAHBgVngQwBATAIBgZngQwBAgEwCAYGZ4EMAQICMAgG
BmeBDAECAzANBgkqhkiG9w0BAQwFAAOCAQEAR1mBf9QbH7Bx9phdGLqYR5iwfnYr
6v8ai6wms0KNMeZK6BnQ79oU59cUkqGS8qcuLa/7Hfb7U7CKP/zYFgrpsC62pQsY
kDUmotr2qLcy/JUjS8ZFucTP5Hzu5sn4kL1y45nDHQsFfGqXbbKrAjbYwrwsAZI/
BKOLdRHHuSm8EdCGupK8JvllyDfNJvaGEwwEqonleLHBTnm8dqMLUeTF0J5q/hos
Vq4GNiejcxwIfZMy0MJEGdqN9A57HSgDKwmKdsp33Id6rHtSJlWncg+d0ohP/rEh
xRqhqjn1VtvChMQ1H3Dau0bwhr9kAMQ+959GG50jBbl9s08PqUU643QwmA==
MIIGEzCCA/ugAwIBAgIQfVtRJrR2uhHbdBYLvFMNpzANBgkqhkiG9w0BAQwFADCB
iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTgx
MTAyMDAwMDAwWhcNMzAxMjMxMjM1OTU5WjCBjzELMAkGA1UEBhMCR0IxGzAZBgNV
BAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEYMBYGA1UE
ChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQDEy5TZWN0aWdvIFJTQSBEb21haW4g
VmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEA1nMz1tc8INAA0hdFuNY+B6I/x0HuMjDJsGz99J/LEpgPLT+N
TQEMgg8Xf2Iu6bhIefsWg06t1zIlk7cHv7lQP6lMw0Aq6Tn/2YHKHxYyQdqAJrkj
eocgHuP/IJo8lURvh3UGkEC0MpMWCRAIIz7S3YcPb11RFGoKacVPAXJpz9OTTG0E
oKMbgn6xmrntxZ7FN3ifmgg0+1YuWMQJDgZkW7w33PGfKGioVrCSo1yfu4iYCBsk
Haswha6vsC6eep3BwEIc4gLw6uBK0u+QDrTBQBbwb4VCSmT3pDCg/r8uoydajotY
uK3DGReEY+1vVv2Dy2A0xHS+5p3b4eTlygxfFQIDAQABo4IBbjCCAWowHwYDVR0j
BBgwFoAUU3m/WqorSs9UgOHYm8Cd8rIDZsswHQYDVR0OBBYEFI2MXsRUrYrhd+mb
+ZsF4bgBjWHhMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMB0G
A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAbBgNVHSAEFDASMAYGBFUdIAAw
CAYGZ4EMAQIBMFAGA1UdHwRJMEcwRaBDoEGGP2h0dHA6Ly9jcmwudXNlcnRydXN0
LmNvbS9VU0VSVHJ1c3RSU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDB2Bggr
BgEFBQcBAQRqMGgwPwYIKwYBBQUHMAKGM2h0dHA6Ly9jcnQudXNlcnRydXN0LmNv
bS9VU0VSVHJ1c3RSU0FBZGRUcnVzdENBLmNydDAlBggrBgEFBQcwAYYZaHR0cDov
L29jc3AudXNlcnRydXN0LmNvbTANBgkqhkiG9w0BAQwFAAOCAgEAMr9hvQ5Iw0/H
ukdN+Jx4GQHcEx2Ab/zDcLRSmjEzmldS+zGea6TvVKqJjUAXaPgREHzSyrHxVYbH
7rM2kYb2OVG/Rr8PoLq0935JxCo2F57kaDl6r5ROVm+yezu/Coa9zcV3HAO4OLGi
H19+24rcRki2aArPsrW04jTkZ6k4Zgle0rj8nSg6F0AnwnJOKf0hPHzPE/uWLMUx
RP0T7dWbqWlod3zu4f+k+TY4CFM5ooQ0nBnzvg6s1SQ36yOoeNDT5++SR2RiOSLv
xvcRviKFxmZEJCaOEDKNyJOuB56DPi/Z+fVGjmO+wea03KbNIaiGCpXZLoUmGv38
sbZXQm2V0TP2ORQGgkE49Y9Y3IBbpNV9lXj9p5v//cWoaasm56ekBYdbqbe4oyAL
l6lFhd2zi+WJN44pDfwGF/Y4QA5C5BIG+3vzxhFoYt/jmPQT2BVPi7Fp2RBgvGQq
6jG35LWjOhSbJuMLe/0CjraZwTiXWTb2qHSihrZe68Zk6s+go/lunrotEbaGmAhY
LcmsJWTyXnW0OMGuf1pGg+pRyrbxmRE1a6Vqe8YAsOf4vmSyrcjC8azjUeqkk+B5
yOGBQMkKW+ESPMFgKuOXwIlCypTPRpgSabuY0MLTDXJLR27lk8QyKGOHQ+SwMj4K
00u/I5sUKUErmgQfky3xxzlIPK1aEn8=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEvjCCA6agAwIBAgIQBtjZBNVYQ0b2ii+nVCJ+xDANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0yMTA0MTQwMDAwMDBaFw0zMTA0MTMyMzU5NTlaME8xCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxKTAnBgNVBAMTIERpZ2lDZXJ0IFRMUyBS
U0EgU0hBMjU2IDIwMjAgQ0ExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAwUuzZUdwvN1PWNvsnO3DZuUfMRNUrUpmRh8sCuxkB+Uu3Ny5CiDt3+PE0J6a
qXodgojlEVbbHp9YwlHnLDQNLtKS4VbL8Xlfs7uHyiUDe5pSQWYQYE9XE0nw6Ddn
g9/n00tnTCJRpt8OmRDtV1F0JuJ9x8piLhMbfyOIJVNvwTRYAIuE//i+p1hJInuW
raKImxW8oHzf6VGo1bDtN+I2tIJLYrVJmuzHZ9bjPvXj1hJeRPG/cUJ9WIQDgLGB
Afr5yjK7tI4nhyfFK3TUqNaX3sNk+crOU6JWvHgXjkkDKa77SU+kFbnO8lwZV21r
eacroicgE7XQPUDTITAHk+qZ9QIDAQABo4IBgjCCAX4wEgYDVR0TAQH/BAgwBgEB
/wIBADAdBgNVHQ4EFgQUt2ui6qiqhIx56rTaD5iyxZV2ufQwHwYDVR0jBBgwFoAU
A95QNVbRTLtm8KPiGxvDl7I90VUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQG
CCsGAQUFBwMBBggrBgEFBQcDAjB2BggrBgEFBQcBAQRqMGgwJAYIKwYBBQUHMAGG
GGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBABggrBgEFBQcwAoY0aHR0cDovL2Nh
Y2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNydDBCBgNV
HR8EOzA5MDegNaAzhjFodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRH
bG9iYWxSb290Q0EuY3JsMD0GA1UdIAQ2MDQwCwYJYIZIAYb9bAIBMAcGBWeBDAEB
MAgGBmeBDAECATAIBgZngQwBAgIwCAYGZ4EMAQIDMA0GCSqGSIb3DQEBCwUAA4IB
AQCAMs5eC91uWg0Kr+HWhMvAjvqFcO3aXbMM9yt1QP6FCvrzMXi3cEsaiVi6gL3z
ax3pfs8LulicWdSQ0/1s/dCYbbdxglvPbQtaCdB73sRD2Cqk3p5BJl+7j5nL3a7h
qG+fh/50tx8bIKuxT8b1Z11dmzzp/2n3YWzW2fP9NsarA4h20ksudYbj/NhVfSbC
EXffPgK2fPOre3qGNm+499iTcc+G33Mw+nur7SpZyEKEOxEXGlLzyQ4UfaJbcme6
ce1XR2bFuAJKZTRei9AqPCCcUZlM51Ke92sRKw2Sfh3oius2FkOH6ipjv3U/697E
A7sKPPcw7+uvTPyLNhBzPvOk
MIID0zCCArugAwIBAgIQVmcdBOpPmUxvEIFHWdJ1lDANBgkqhkiG9w0BAQwFADB7
MQswCQYDVQQGEwJHQjEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD
VQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UE
AwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTE5MDMxMjAwMDAwMFoXDTI4
MTIzMTIzNTk1OVowgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5
MRQwEgYDVQQHEwtKZXJzZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBO
ZXR3b3JrMS4wLAYDVQQDEyVVU0VSVHJ1c3QgRUNDIENlcnRpZmljYXRpb24gQXV0
aG9yaXR5MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEGqxUWqn5aCPnetUkb1PGWthL
q8bVttHmc3Gu3ZzWDGH926CJA7gFFOxXzu5dP+Ihs8731Ip54KODfi2X0GHE8Znc
JZFjq38wo7Rw4sehM5zzvy5cU7Ffs30yf4o043l5o4HyMIHvMB8GA1UdIwQYMBaA
FKARCiM+lvEH7OKvKe+CpX/QMKS0MB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1
xmNjmjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zARBgNVHSAECjAI
MAYGBFUdIAAwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5j
b20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEEKDAmMCQG
CCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZIhvcNAQEM
BQADggEBABns652JLCALBIAdGN5CmXKZFjK9Dpx1WywV4ilAbe7/ctvbq5AfjJXy
ij0IckKJUAfiORVsAYfZFhr1wHUrxeZWEQff2Ji8fJ8ZOd+LygBkc7xGEJuTI42+
FsMuCIKchjN0djsoTI0DQoWz4rIjQtUfenVqGtF8qmchxDM6OW1TyaLtYiKou+JV
bJlsQ2uRl9EMC5MCHdK8aXdJ5htN978UeAOwproLtOGFfy/cQjutdAFI3tZs4RmY
CV4Ks2dH/hzg1cEo70qLRDEmBDeNiXQ2Lu+lIg+DdEmSx/cQwgwp+7e9un/jX9Wf
8qn0dNW44bOwgeThpWOjzOoEeJBuv/c=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFajCCBPGgAwIBAgIQDNCovsYyz+ZF7KCpsIT7HDAKBggqhkjOPQQDAzBWMQsw
CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMTAwLgYDVQQDEydEaWdp
Q2VydCBUTFMgSHlicmlkIEVDQyBTSEEzODQgMjAyMCBDQTEwHhcNMjMwMjE0MDAw
MDAwWhcNMjQwMzE0MjM1OTU5WjBmMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2Fs
aWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHVi
LCBJbmMuMRMwEQYDVQQDEwpnaXRodWIuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D
AQcDQgAEo6QDRgPfRlFWy8k5qyLN52xZlnqToPu5QByQMog2xgl2nFD1Vfd2Xmgg
nO4i7YMMFTAQQUReMqyQodWq8uVDs6OCA48wggOLMB8GA1UdIwQYMBaAFAq8CCkX
jKU5bXoOzjPHLrPt+8N6MB0GA1UdDgQWBBTHByd4hfKdM8lMXlZ9XNaOcmfr3jAl
BgNVHREEHjAcggpnaXRodWIuY29tgg53d3cuZ2l0aHViLmNvbTAOBgNVHQ8BAf8E
BAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMIGbBgNVHR8EgZMw
gZAwRqBEoEKGQGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRMU0h5
YnJpZEVDQ1NIQTM4NDIwMjBDQTEtMS5jcmwwRqBEoEKGQGh0dHA6Ly9jcmw0LmRp
Z2ljZXJ0LmNvbS9EaWdpQ2VydFRMU0h5YnJpZEVDQ1NIQTM4NDIwMjBDQTEtMS5j
cmwwPgYDVR0gBDcwNTAzBgZngQwBAgIwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3
dy5kaWdpY2VydC5jb20vQ1BTMIGFBggrBgEFBQcBAQR5MHcwJAYIKwYBBQUHMAGG
GGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBPBggrBgEFBQcwAoZDaHR0cDovL2Nh
Y2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VExTSHlicmlkRUNDU0hBMzg0MjAy
MENBMS0xLmNydDAJBgNVHRMEAjAAMIIBgAYKKwYBBAHWeQIEAgSCAXAEggFsAWoA
dwDuzdBk1dsazsVct520zROiModGfLzs3sNRSFlGcR+1mwAAAYZQ3Rv6AAAEAwBI
MEYCIQDkFq7T4iy6gp+pefJLxpRS7U3gh8xQymmxtI8FdzqU6wIhALWfw/nLD63Q
YPIwG3EFchINvWUfB6mcU0t2lRIEpr8uAHYASLDja9qmRzQP5WoC+p0w6xxSActW
3SyB2bu/qznYhHMAAAGGUN0cKwAABAMARzBFAiAePGAyfiBR9dbhr31N9ZfESC5G
V2uGBTcyTyUENrH3twIhAPwJfsB8A4MmNr2nW+sdE1n2YiCObW+3DTHr2/UR7lvU
AHcAO1N3dT4tuYBOizBbBv5AO2fYT8P0x70ADS1yb+H61BcAAAGGUN0cOgAABAMA
SDBGAiEAzOBr9OZ0+6OSZyFTiywN64PysN0FLeLRyL5jmEsYrDYCIQDu0jtgWiMI
KU6CM0dKcqUWLkaFE23c2iWAhYAHqrFRRzAKBggqhkjOPQQDAwNnADBkAjAE3A3U
3jSZCpwfqOHBdlxi9ASgKTU+wg0qw3FqtfQ31OwLYFdxh0MlNk/HwkjRSWgCMFbQ
vMkXEPvNvv4t30K6xtpG26qmZ+6OiISBIIXMljWnsiYR1gyZnTzIg3AQSw4Vmw==
MIIFgTCCBGmgAwIBAgIQOXJEOvkit1HX02wQ3TE1lTANBgkqhkiG9w0BAQwFADB7
MQswCQYDVQQGEwJHQjEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD
VQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UE
AwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTE5MDMxMjAwMDAwMFoXDTI4
MTIzMTIzNTk1OVowgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5
MRQwEgYDVQQHEwtKZXJzZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBO
ZXR3b3JrMS4wLAYDVQQDEyVVU0VSVHJ1c3QgUlNBIENlcnRpZmljYXRpb24gQXV0
aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAgBJlFzYOw9sI
s9CsVw127c0n00ytUINh4qogTQktZAnczomfzD2p7PbPwdzx07HWezcoEStH2jnG
vDoZtF+mvX2do2NCtnbyqTsrkfjib9DsFiCQCT7i6HTJGLSR1GJk23+jBvGIGGqQ
Ijy8/hPwhxR79uQfjtTkUcYRZ0YIUcuGFFQ/vDP+fmyc/xadGL1RjjWmp2bIcmfb
IWax1Jt4A8BQOujM8Ny8nkz+rwWWNR9XWrf/zvk9tyy29lTdyOcSOk2uTIq3XJq0
tyA9yn8iNK5+O2hmAUTnAU5GU5szYPeUvlM3kHND8zLDU+/bqv50TmnHa4xgk97E
xwzf4TKuzJM7UXiVZ4vuPVb+DNBpDxsP8yUmazNt925H+nND5X4OpWaxKXwyhGNV
icQNwZNUMBkTrNN9N6frXTpsNVzbQdcS2qlJC9/YgIoJk2KOtWbPJYjNhLixP6Q5
D9kCnusSTJV882sFqV4Wg8y4Z+LoE53MW4LTTLPtW//e5XOsIzstAL81VXQJSdhJ
WBp/kjbmUZIO8yZ9HE0XvMnsQybQv0FfQKlERPSZ51eHnlAfV1SoPv10Yy+xUGUJ
5lhCLkMaTLTwJUdZ+gQek9QmRkpQgbLevni3/GcV4clXhB4PY9bpYrrWX1Uu6lzG
KAgEJTm4Diup8kyXHAc/DVL17e8vgg8CAwEAAaOB8jCB7zAfBgNVHSMEGDAWgBSg
EQojPpbxB+zirynvgqV/0DCktDAdBgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rID
ZsswDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAG
BgRVHSAAMEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwuY29tb2RvY2EuY29t
L0FBQUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDQGCCsGAQUFBwEBBCgwJjAkBggr
BgEFBQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2EuY29tMA0GCSqGSIb3DQEBDAUA
A4IBAQAYh1HcdCE9nIrgJ7cz0C7M7PDmy14R3iJvm3WOnnL+5Nb+qh+cli3vA0p+
rvSNb3I8QzvAP+u431yqqcau8vzY7qN7Q/aGNnwU4M309z/+3ri0ivCRlv79Q2R+
/czSAaF9ffgZGclCKxO/WIu6pKJmBHaIkU4MiRTOok3JMrO66BQavHHxW/BBC5gA
CiIDEOUMsfnNkjcZ7Tvx5Dq2+UUTJnWvu6rvP3t3O9LEApE9GQDTF1w52z97GA1F
zZOFli9d31kWTz9RvdVFGD/tSo7oBmF0Ixa1DVBzJ0RHfxBdiSprhTEUxOipakyA
vGp4z7h/jnZymQyd/teRCBaho1+V
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIHEjCCBfqgAwIBAgIQBE1y13zdpwLdWmfyoju92TANBgkqhkiG9w0BAQsFADBP
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMSkwJwYDVQQDEyBE
aWdpQ2VydCBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTAeFw0yMzAyMjEwMDAwMDBa
Fw0yNDAzMjAyMzU5NTlaMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9y
bmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRUwEwYDVQQKEwxHaXRIdWIsIElu
Yy4xFDASBgNVBAMMCyouZ2l0aHViLmlvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAuLBgDhov8bGGS2TsEZ+meb7oh/GIxbRJmxC7yq/qr75UDHhDf8p7
TkVbCyQp8bsj/Bmkx2xwSXZT0wkjZbJIe7Ycqgca4nka+Xpe5xb4pkrVOaPiDfdX
7+34CHZbUtqL0OYebi/5D5lLalLKNOGkySAz05foenfFAxAmQYJhR6KvxFY/dqI4
y7JwrnJ6Q8F+J6Ne1uP256UwcL0qlid6e/tA0ld3ryMSJ0I6xgtqjL26Le4/nxXu
YlekppVQr0OwrHa44Q7Z/1bsdFCGtR+WLNGVBeW3BWeTTp7yWjgfp49DWt48V9pI
elDGiDgVyJcsLOz4OQk2vRmNA1ZBZgck4wIDAQABo4ID0DCCA8wwHwYDVR0jBBgw
FoAUt2ui6qiqhIx56rTaD5iyxZV2ufQwHQYDVR0OBBYEFI0CHHVazcamQXhpKMP3
qqeYO9W7MHsGA1UdEQR0MHKCCyouZ2l0aHViLmlvgglnaXRodWIuaW+CDCouZ2l0
aHViLmNvbYIKZ2l0aHViLmNvbYIOd3d3LmdpdGh1Yi5jb22CFyouZ2l0aHVidXNl
cmNvbnRlbnQuY29tghVnaXRodWJ1c2VyY29udGVudC5jb20wDgYDVR0PAQH/BAQD
AgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjCBjwYDVR0fBIGHMIGE
MECgPqA8hjpodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUTFNSU0FT
SEEyNTYyMDIwQ0ExLTQuY3JsMECgPqA8hjpodHRwOi8vY3JsNC5kaWdpY2VydC5j
b20vRGlnaUNlcnRUTFNSU0FTSEEyNTYyMDIwQ0ExLTQuY3JsMD4GA1UdIAQ3MDUw
MwYGZ4EMAQICMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29t
L0NQUzB/BggrBgEFBQcBAQRzMHEwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp
Z2ljZXJ0LmNvbTBJBggrBgEFBQcwAoY9aHR0cDovL2NhY2VydHMuZGlnaWNlcnQu
Y29tL0RpZ2lDZXJ0VExTUlNBU0hBMjU2MjAyMENBMS0xLmNydDAJBgNVHRMEAjAA
MIIBfgYKKwYBBAHWeQIEAgSCAW4EggFqAWgAdwB2/4g/Crb7lVHCYcz1h7o0tKTN
uyncaEIKn+ZnTFo6dAAAAYZ0gHV7AAAEAwBIMEYCIQCqfmfSO8MxeeVZ/fJzqqBB
p+VqeRDUOUBVGyTTOn43ewIhAJT0S27mmGUlpqNiDADP+Jo8C6kYHF+7U6TY74bH
XHAaAHYAc9meiRtMlnigIH1HneayxhzQUV5xGSqMa4AQesF3crUAAAGGdIB1agAA
BAMARzBFAiEAguB+XQVANBj2MPcJzbz+LBPrkDDOEO3op52jdHUSW3ICIF0fnYdW
qvdtmgQNSns13pAppdQWp4/f/jerNYskI7krAHUASLDja9qmRzQP5WoC+p0w6xxS
ActW3SyB2bu/qznYhHMAAAGGdIB1SgAABAMARjBEAiAT/wA2qGGHSKZqBAm84z6q
E+dGPQZ1aCMY52pFSfcw8QIgP/SciuZG02X2mBO/miDT2hCp4y5d2sc7FE5PThyC
pbMwDQYJKoZIhvcNAQELBQADggEBADekGxEin/yfyWcHj6qGE5/gCB1uDI1l+wN5
UMZ2ujCQoKQceRMHuVoYjZdMBXGK0CIXxhmiIosD9iyEcWxV3+KZQ2Xl17e3N0zG
yOXx2Kd7B13ruBxQpKOO8Ez4uGpyWb5DDoretV6Pnj9aQ2SCzODedvS+phIKBmi7
d+FM70tNZ6/2csdrG5xIU6d/7XYYXPD2xkwkU1dX4UKmPa7h9ZPyavopcgE+twbx
LxoOkcXsNb/12jOV3iQSDfXDI41AgtFc694KCOjlg+UKizpemE53T5/cq37OqChP
qnlPyb6PYIhua/kgbH84ltba1xEDQ9i4UYfOMiJNZEzEdSfQ498=
MIIEoTCCBEigAwIBAgIRAKtmhrVie+gFloITMBKGSfUwCgYIKoZIzj0EAwIwgY8x
CzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNV
BAcTB1NhbGZvcmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDE3MDUGA1UEAxMu
U2VjdGlnbyBFQ0MgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTAe
Fw0yNTAyMDUwMDAwMDBaFw0yNjAyMDUyMzU5NTlaMBUxEzARBgNVBAMTCmdpdGh1
Yi5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQgNFxG/yzL+CSarvC7L3ep
H5chNnG6wiYYxR5D/Z1J4MxGnIX8KbT5fCgLoyzHXL9v50bdBIq6y4AtN4gN7gbW
o4IC/DCCAvgwHwYDVR0jBBgwFoAU9oUKOxGG4QR9DqoLLNLuzGR7e64wHQYDVR0O
BBYEFFPIf96emE7HTda83quVPjA9PdHIMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMB
Af8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBJBgNVHSAEQjBA
MDQGCysGAQQBsjEBAgIHMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8vc2VjdGlnby5j
b20vQ1BTMAgGBmeBDAECATCBhAYIKwYBBQUHAQEEeDB2ME8GCCsGAQUFBzAChkNo
dHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29FQ0NEb21haW5WYWxpZGF0aW9u
U2VjdXJlU2VydmVyQ0EuY3J0MCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0
aWdvLmNvbTCCAX4GCisGAQQB1nkCBAIEggFuBIIBagFoAHUAlpdkv1VYl633Q4do
NwhCd+nwOtX2pPM2bkakPw/KqcYAAAGU02uUSwAABAMARjBEAiA7i6o+LpQjt6Ae
EjltHhs/TiECnHd0xTeer/3vD1xgsAIgYlGwRot+SqEBCs//frx/YHTPwox9QLdy
7GjTLWHfcMAAdwAZhtTHKKpv/roDb3gqTQGRqs4tcjEPrs5dcEEtJUzH1AAAAZTT
a5PtAAAEAwBIMEYCIQDlrInx7J+3MfqgxB2+Fvq3dMlk1qj4chOw/+HkYVfG0AIh
AMT+JKAQfUuIdBGxfryrGrwsOD3pRs1tyAyykdPGRgsTAHYAyzj3FYl8hKFEX1vB
3fvJbvKaWc1HCmkFhbDLFMMUWOcAAAGU02uUJQAABAMARzBFAiEA1GKW92agDFNJ
IYrMH3gaJdXsdIVpUcZOfxH1FksbuLECIFJCfslINhc53Q0TIMJHdcFOW2tgG4tB
A1dL881tXbMnMCUGA1UdEQQeMByCCmdpdGh1Yi5jb22CDnd3dy5naXRodWIuY29t
MAoGCCqGSM49BAMCA0cAMEQCIHGMp27BBBJ1356lCe2WYyzYIp/fAONQM3AkeE/f
ym0sAiBtVfN3YgIZ+neHEfwcRhhz4uDpc8F+tKmtceWJSicMkA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIIOzCCByOgAwIBAgIQDMnYcxFdn1+HZt0ULgDEwTANBgkqhkiG9w0BAQsFADA8
MIIGhTCCBW2gAwIBAgIRAJB3NEFHMW75lZl2eur98bkwDQYJKoZIhvcNAQELBQAw
gY8xCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
BgNVBAcTB1NhbGZvcmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDE3MDUGA1UE
AxMuU2VjdGlnbyBSU0EgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBD
QTAeFw0yNTAzMDcwMDAwMDBaFw0yNjAzMDcyMzU5NTlaMBYxFDASBgNVBAMMCyou
Z2l0aHViLmlvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxKQLElVm
JYKnZ9dmKMWrb4fy4BWFm658EQemS4hJgrt+1NFpL2tGVaFupVyV3vmKorCX3zej
c7+gH8Ugpemmj9r5tk1NZ0SXXALTjvT2i03oSqjwCzkn+R1o0TYg+G7PyQ5pE18A
E+K3VUcpch1f5RyBTEvE4+HUg4/6OpAIYFVznJ3yk8a+bo1i/HBp2MbtPzssSlT8
mPLY76SETtKdwgIdY91MkTiJd1x0KJDM2GPKM7pNTc81NMSw6WBzsxg4PFbR+BCY
82/sYu8iMy/AdYcUz72hh2DGXnVypzzV/LLgJ/VAP5m+md0lVH5KIG/cduDrajlo
LQ4LKJktO4VmwQIDAQABo4IDUjCCA04wHwYDVR0jBBgwFoAUjYxexFStiuF36Zv5
mwXhuAGNYeEwHQYDVR0OBBYEFBLwftAxb+SvNbWJ+0LZ7bcLk80EMA4GA1UdDwEB
/wQEAwIFoDAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF
BQcDAjBJBgNVHSAEQjBAMDQGCysGAQQBsjEBAgIHMCUwIwYIKwYBBQUHAgEWF2h0
dHBzOi8vc2VjdGlnby5jb20vQ1BTMAgGBmeBDAECATCBhAYIKwYBBQUHAQEEeDB2
ME8GCCsGAQUFBzAChkNodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29SU0FE
b21haW5WYWxpZGF0aW9uU2VjdXJlU2VydmVyQ0EuY3J0MCMGCCsGAQUFBzABhhdo
dHRwOi8vb2NzcC5zZWN0aWdvLmNvbTCCAX4GCisGAQQB1nkCBAIEggFuBIIBagFo
AHYAlpdkv1VYl633Q4doNwhCd+nwOtX2pPM2bkakPw/KqcYAAAGVbeysdQAABAMA
RzBFAiEA+YIgsAqb2cqQVlF4JP2ERIVCH3RXdB7DjIPc6Ch5aK4CIHjqUoV7F5Mk
fcIQcmdn7Z5UR8nYtPA2OLvYc3mCFcLuAHcAGYbUxyiqb/66A294Kk0BkarOLXIx
D67OXXBBLSVMx9QAAAGVbeysDgAABAMASDBGAiEAjryAbXlHsXj/v4f7CWXJzDUX
SUuvA5kRH3doh4WPUQcCIQC+nojCqhCn/ZupbnI50O1T3FSKBQu/LOZ33fApzLJW
hQB1AMs49xWJfIShRF9bwd37yW7ymlnNRwppBYWwyxTDFFjnAAABlW3srDcAAAQD
AEYwRAIgS98L1D2W8nzV3tIQ0R4UJWxwxb7I/TT6e9ly0nA0QsACIFpl7s/WA1Qm
z1Vm8ZtihoNFubO/AiiaVGaeDQiznHFCMHsGA1UdEQR0MHKCCyouZ2l0aHViLmlv
ggwqLmdpdGh1Yi5jb22CFyouZ2l0aHVidXNlcmNvbnRlbnQuY29tggpnaXRodWIu
Y29tgglnaXRodWIuaW+CFWdpdGh1YnVzZXJjb250ZW50LmNvbYIOd3d3LmdpdGh1
Yi5jb20wDQYJKoZIhvcNAQELBQADggEBAHksjTVCptW9CtbBXu+7J2cDDmKRz/EA
kUyONuojOnKoI3d2f5DQDkqzu/gSj6B28YO3a4EYFktvwq3KnXAu9KzSM1ehlhtA
lxlvjjGUgXvux7DjnBH40ItKiE723opeWVbm2WExdRPSckm/CDwshz2U3Sl3M3Wt
v0xPuZJrg1tMIL58RqrS5PpFlAIIlEUC6dr+xVQrwLNcYXVVgvZsRSX/YbrzboLM
gWhuDSQPcaeDGHcy7NxRZHmlpHz+/Ot067VuxjGqm9veKNGZMUdroS+ocxAJBXv3
Z1NCCowvpZazNxKccQg7izYwd6HL70WMxCWFU0e70uw9KZqteG7SVcQ=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIIOzCCByOgAwIBAgIQCHNhNIH5XVH5G3SANwHecTANBgkqhkiG9w0BAQsFADA8
MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRwwGgYDVQQDExNBbWF6b24g
UlNBIDIwNDggTTAxMB4XDTIzMTAxMDAwMDAwMFoXDTI0MDcxMDIzNTk1OVowGzEZ
UlNBIDIwNDggTTAxMB4XDTI1MDkxOTAwMDAwMFoXDTI2MDgyMzIzNTk1OVowGzEZ
MBcGA1UEAxMQczMuYW1hem9uYXdzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBANbrGvFbxrAM6TKussVOuHpCNsZX1V+jR6TAfzrO8VQWqiz+DCq+
LlGFT2is2kRxY+hv2NswgK+Ie4SUdypQDrJ+mKFadmM2UOqIcFepA4EIUESAXZHg
lHJAv5460i6gp9Lh6imN3jGjC1Ax0rgrfBZA+uzIy8dorBEptIj/YpE4wD4WPHnk
Y+wCDfMZdm6b6hqsrOkbqBWHH2zSH8TWfnYRguGe3NkLpM4pBNa2lTb1GBeZtm/W
TB3dEAaxcGCu+WQbicI6BxkbzklgxKTHZ2Lc3mL8P5Ca1yWQG3asdUxw0SIkCZVd
aCzQxqtLlXSxpe4xK2KjOfq2ZM+MZhq/tR0CAwEAAaOCBVgwggVUMB8GA1UdIwQY
MBaAFIG4DmOKiRIY5fo7O1CVn+blkBOFMB0GA1UdDgQWBBQi0miDsNb3pJw4EouH
AR3hoCAaqzCCAokGA1UdEQSCAoAwggJ8ghBzMy5hbWF6b25hd3MuY29tghIqLnMz
LmFtYXpvbmF3cy5jb22CJiouczMuZHVhbHN0YWNrLnVzLWVhc3QtMS5hbWF6b25h
d3MuY29tgiRzMy5kdWFsc3RhY2sudXMtZWFzdC0xLmFtYXpvbmF3cy5jb22CHCou
czMudXMtZWFzdC0xLmFtYXpvbmF3cy5jb22CGnMzLnVzLWVhc3QtMS5hbWF6b25h
d3MuY29tgiQqLnMzLWNvbnRyb2wudXMtZWFzdC0xLmFtYXpvbmF3cy5jb22CInMz
LWNvbnRyb2wudXMtZWFzdC0xLmFtYXpvbmF3cy5jb22CLiouczMtY29udHJvbC5k
dWFsc3RhY2sudXMtZWFzdC0xLmFtYXpvbmF3cy5jb22CLHMzLWNvbnRyb2wuZHVh
bHN0YWNrLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tgigqLnMzLWFjY2Vzc3BvaW50
LnVzLWVhc3QtMS5hbWF6b25hd3MuY29tgjIqLnMzLWFjY2Vzc3BvaW50LmR1YWxz
dGFjay51cy1lYXN0LTEuYW1hem9uYXdzLmNvbYInKi5zMy1kZXByZWNhdGVkLnVz
LWVhc3QtMS5hbWF6b25hd3MuY29tgiVzMy1kZXByZWNhdGVkLnVzLWVhc3QtMS5h
bWF6b25hd3MuY29tghtzMy1leHRlcm5hbC0xLmFtYXpvbmF3cy5jb22CHSouczMt
ZXh0ZXJuYWwtMS5hbWF6b25hd3MuY29tghtzMy1leHRlcm5hbC0yLmFtYXpvbmF3
cy5jb22CHSouczMtZXh0ZXJuYWwtMi5hbWF6b25hd3MuY29tMBMGA1UdIAQMMAow
ADCCAQoCggEBAMdxOeavAJSP5sHcShN1qqDR2LF9RtWaXtp5bdgNNKlgt4buDh3f
EGe60xX+RNSANTeyWtUZ8fhtQ7ugj7OoMZi+a2KczPFj3Q34OgM/8+290Z9joZ0p
wkKNwY03qEO6vlr9E/qA0Y9A6xZBD6QNUmD7jR4Etqq7jtBFgocdjZ6bgMqGQ8Uh
Ss5qapqokrKQrbKZLBHKRCqYGoKwgD1ciXiV6iXuJj+GXDmwte7MZAfFyg5OJngh
GKNCMdU38AYRGrfTWoVH5RC67JlfZMenQBK9sB/XnrB3kOkRbBR/spRYsZg+mbaR
Q7uyPL0hTHyDu1KAe8BpmxYoDrvkmkLPr58CAwEAAaOCBVgwggVUMB8GA1UdIwQY
MBaAFIG4DmOKiRIY5fo7O1CVn+blkBOFMB0GA1UdDgQWBBSLsv8vDlH1xvXIpmJ3
htLYjcrMszCCAokGA1UdEQSCAoAwggJ8ghBzMy5hbWF6b25hd3MuY29tghIqLnMz
LmFtYXpvbmF3cy5jb22CKCouczMtYWNjZXNzcG9pbnQudXMtZWFzdC0xLmFtYXpv
bmF3cy5jb22CJCouczMtY29udHJvbC51cy1lYXN0LTEuYW1hem9uYXdzLmNvbYIk
czMuZHVhbHN0YWNrLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tgicqLnMzLWRlcHJl
Y2F0ZWQudXMtZWFzdC0xLmFtYXpvbmF3cy5jb22CLHMzLWNvbnRyb2wuZHVhbHN0
YWNrLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tgh0qLnMzLWV4dGVybmFsLTIuYW1h
em9uYXdzLmNvbYIaczMudXMtZWFzdC0xLmFtYXpvbmF3cy5jb22CMiouczMtYWNj
ZXNzcG9pbnQuZHVhbHN0YWNrLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tghwqLnMz
LnVzLWVhc3QtMS5hbWF6b25hd3MuY29tgh0qLnMzLWV4dGVybmFsLTEuYW1hem9u
YXdzLmNvbYImKi5zMy5kdWFsc3RhY2sudXMtZWFzdC0xLmFtYXpvbmF3cy5jb22C
G3MzLWV4dGVybmFsLTIuYW1hem9uYXdzLmNvbYIiczMtY29udHJvbC51cy1lYXN0
LTEuYW1hem9uYXdzLmNvbYIbczMtZXh0ZXJuYWwtMS5hbWF6b25hd3MuY29tgiVz
My1kZXByZWNhdGVkLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tgi4qLnMzLWNvbnRy
b2wuZHVhbHN0YWNrLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tMBMGA1UdIAQMMAow
CAYGZ4EMAQIBMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYI
KwYBBQUHAwIwOwYDVR0fBDQwMjAwoC6gLIYqaHR0cDovL2NybC5yMm0wMS5hbWF6
b250cnVzdC5jb20vcjJtMDEuY3JsMHUGCCsGAQUFBwEBBGkwZzAtBggrBgEFBQcw
AYYhaHR0cDovL29jc3AucjJtMDEuYW1hem9udHJ1c3QuY29tMDYGCCsGAQUFBzAC
hipodHRwOi8vY3J0LnIybTAxLmFtYXpvbnRydXN0LmNvbS9yMm0wMS5jZXIwDAYD
VR0TAQH/BAIwADCCAX0GCisGAQQB1nkCBAIEggFtBIIBaQFnAHYA7s3QZNXbGs7F
XLedtM0TojKHRny87N7DUUhZRnEftZsAAAGLG2S5PwAABAMARzBFAiEAxOJvK1tq
gzf5d6NPN4PZui5PhQePCg1bH/fVMd7T5ekCIHeOglhFW5BC0TNeHY+cfUOvDONT
DV4fAVcaWsBXErw9AHUASLDja9qmRzQP5WoC+p0w6xxSActW3SyB2bu/qznYhHMA
AAGLG2S5LwAABAMARjBEAiBYu16xOFIzUE9aMrB1vrFkTsMC3veucw9k0Hv4qO2v
XAIgTTMRV9sq+ytuTU53XdoKLF33qeUZHGqop7O7xoFEENMAdgDatr9rP7W2Ip+b
wrtca+hwkXFsu1GEhTS9pD0wSNf7qwAAAYsbZLlmAAAEAwBHMEUCIQCD6n2r6DVx
UMUgtfPRi3ieJdlObPzsrc0aGVhl6gCP5QIgbzkLqEOfJ7bozTgYzUO8ZDvAd0te
cdpZv182ZEQ974wwDQYJKoZIhvcNAQELBQADggEBAHEAZnHOPS7CoRXWTqWbQu5V
8x2uFNl5jjsbSH68xnviW24ShllvFyHH1iD2R3ov/3Ri8T8TJ0bA14u+3D/iPZk1
BgX2/YdL/0/CptgISLhBtugBk2+MsRry6i7hhbf4/pIyL1uWHUeDuYrW882Xw1ul
uD1wyC1z2CqXZWy0WL0zMV6Yfp/eFnCJ9tj0NoullHGhgk9RTU3qULwuAQmzMqS7
XlhmgdzVPDq3s8qCawHDU6yDdT5FAO3SJ23Dj2efM7enBzq0jryfEZn9vTCRJfKI
yCZT56FgMdEqvmQutAV5UCiL1acK8YU2zUQSxuWn+AyDivV4JSOYw8ho1yZrZZI=
VR0TAQH/BAIwADCCAX0GCisGAQQB1nkCBAIEggFtBIIBaQFnAHYA2AlVO5RPev/I
FhlvlE+Fq7D4/F6HVSYPFdEucrtFSxQAAAGZY2zFywAABAMARzBFAiEA52TfXblf
xGMn7zs62yXq7rXB/ccKutSfyoReVaLtluACIFL9G5g8WzioAe/Tmx0cJjYXBzof
9go+a5TjVIY4bpnnAHYAwjF+V0UZo0XufzjespBB68fCIVoiv3/Vta12mtkOUs0A
AAGZY2zFywAABAMARzBFAiAdPbpyBei1fmph20ZWi1ZN7nl9b6EDX0s/gYxduL7k
CQIhAMeQp4B56jeit+b3akK3MHyTQoZBgR2CZMBZI6QVk5DUAHUAlE5Dh/rswe+B
8xkkJqgYZQHH0184AgE/cmd9VTcuGdgAAAGZY2zF4AAABAMARjBEAiBpEesssZJD
0SnUOe2Alz/5nGHs/Usxgzgz/xrMmEAXwAIgI90hHJajlzg30MZC3RKkXV8wezmC
pK+h+FLlSvMOUVowDQYJKoZIhvcNAQELBQADggEBAMMItDbq4GvNzuM8t2iBx90Z
F0hcsivxMOHbbxvBRrLtEYjY5wyvmGdWF2t24YFaYzyEMb0qX0YLxLGpoJCf5vIJ
olRHHgdDdkxg354SVQ4+SB1SvpJlAjyzqAGD4XQHAU3t0+jK+9OfJp8Zh57Z3jzI
zhA5ILi5hT3mW3hbHaNfvDpiauB6Sm9T0jc7853Msuh17LvABsIYlt2gVx48ivJc
oLmsInswJM2kWvD8WGTc0sG0wQfT6GNxEGG9anP3odTgdPUNnm8OKqW8nabzmhfu
sMKmxp3ShPpT0KiO5gBlGsMkDKu0+xKB0QjGaRoRbFC/9f0if9jQX5Hiz/xxpS4=
-----END CERTIFICATE-----

View File

@@ -1,40 +1,37 @@
-----BEGIN CERTIFICATE-----
MIIHEjCCBfqgAwIBAgIQBE1y13zdpwLdWmfyoju92TANBgkqhkiG9w0BAQsFADBP
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMSkwJwYDVQQDEyBE
aWdpQ2VydCBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTAeFw0yMzAyMjEwMDAwMDBa
Fw0yNDAzMjAyMzU5NTlaMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9y
bmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRUwEwYDVQQKEwxHaXRIdWIsIElu
Yy4xFDASBgNVBAMMCyouZ2l0aHViLmlvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAuLBgDhov8bGGS2TsEZ+meb7oh/GIxbRJmxC7yq/qr75UDHhDf8p7
TkVbCyQp8bsj/Bmkx2xwSXZT0wkjZbJIe7Ycqgca4nka+Xpe5xb4pkrVOaPiDfdX
7+34CHZbUtqL0OYebi/5D5lLalLKNOGkySAz05foenfFAxAmQYJhR6KvxFY/dqI4
y7JwrnJ6Q8F+J6Ne1uP256UwcL0qlid6e/tA0ld3ryMSJ0I6xgtqjL26Le4/nxXu
YlekppVQr0OwrHa44Q7Z/1bsdFCGtR+WLNGVBeW3BWeTTp7yWjgfp49DWt48V9pI
elDGiDgVyJcsLOz4OQk2vRmNA1ZBZgck4wIDAQABo4ID0DCCA8wwHwYDVR0jBBgw
FoAUt2ui6qiqhIx56rTaD5iyxZV2ufQwHQYDVR0OBBYEFI0CHHVazcamQXhpKMP3
qqeYO9W7MHsGA1UdEQR0MHKCCyouZ2l0aHViLmlvgglnaXRodWIuaW+CDCouZ2l0
aHViLmNvbYIKZ2l0aHViLmNvbYIOd3d3LmdpdGh1Yi5jb22CFyouZ2l0aHVidXNl
cmNvbnRlbnQuY29tghVnaXRodWJ1c2VyY29udGVudC5jb20wDgYDVR0PAQH/BAQD
AgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjCBjwYDVR0fBIGHMIGE
MECgPqA8hjpodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUTFNSU0FT
SEEyNTYyMDIwQ0ExLTQuY3JsMECgPqA8hjpodHRwOi8vY3JsNC5kaWdpY2VydC5j
b20vRGlnaUNlcnRUTFNSU0FTSEEyNTYyMDIwQ0ExLTQuY3JsMD4GA1UdIAQ3MDUw
MwYGZ4EMAQICMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29t
L0NQUzB/BggrBgEFBQcBAQRzMHEwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp
Z2ljZXJ0LmNvbTBJBggrBgEFBQcwAoY9aHR0cDovL2NhY2VydHMuZGlnaWNlcnQu
Y29tL0RpZ2lDZXJ0VExTUlNBU0hBMjU2MjAyMENBMS0xLmNydDAJBgNVHRMEAjAA
MIIBfgYKKwYBBAHWeQIEAgSCAW4EggFqAWgAdwB2/4g/Crb7lVHCYcz1h7o0tKTN
uyncaEIKn+ZnTFo6dAAAAYZ0gHV7AAAEAwBIMEYCIQCqfmfSO8MxeeVZ/fJzqqBB
p+VqeRDUOUBVGyTTOn43ewIhAJT0S27mmGUlpqNiDADP+Jo8C6kYHF+7U6TY74bH
XHAaAHYAc9meiRtMlnigIH1HneayxhzQUV5xGSqMa4AQesF3crUAAAGGdIB1agAA
BAMARzBFAiEAguB+XQVANBj2MPcJzbz+LBPrkDDOEO3op52jdHUSW3ICIF0fnYdW
qvdtmgQNSns13pAppdQWp4/f/jerNYskI7krAHUASLDja9qmRzQP5WoC+p0w6xxS
ActW3SyB2bu/qznYhHMAAAGGdIB1SgAABAMARjBEAiAT/wA2qGGHSKZqBAm84z6q
E+dGPQZ1aCMY52pFSfcw8QIgP/SciuZG02X2mBO/miDT2hCp4y5d2sc7FE5PThyC
pbMwDQYJKoZIhvcNAQELBQADggEBADekGxEin/yfyWcHj6qGE5/gCB1uDI1l+wN5
UMZ2ujCQoKQceRMHuVoYjZdMBXGK0CIXxhmiIosD9iyEcWxV3+KZQ2Xl17e3N0zG
yOXx2Kd7B13ruBxQpKOO8Ez4uGpyWb5DDoretV6Pnj9aQ2SCzODedvS+phIKBmi7
d+FM70tNZ6/2csdrG5xIU6d/7XYYXPD2xkwkU1dX4UKmPa7h9ZPyavopcgE+twbx
LxoOkcXsNb/12jOV3iQSDfXDI41AgtFc694KCOjlg+UKizpemE53T5/cq37OqChP
qnlPyb6PYIhua/kgbH84ltba1xEDQ9i4UYfOMiJNZEzEdSfQ498=
MIIGhTCCBW2gAwIBAgIRAJB3NEFHMW75lZl2eur98bkwDQYJKoZIhvcNAQELBQAw
gY8xCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
BgNVBAcTB1NhbGZvcmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDE3MDUGA1UE
AxMuU2VjdGlnbyBSU0EgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBD
QTAeFw0yNTAzMDcwMDAwMDBaFw0yNjAzMDcyMzU5NTlaMBYxFDASBgNVBAMMCyou
Z2l0aHViLmlvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxKQLElVm
JYKnZ9dmKMWrb4fy4BWFm658EQemS4hJgrt+1NFpL2tGVaFupVyV3vmKorCX3zej
c7+gH8Ugpemmj9r5tk1NZ0SXXALTjvT2i03oSqjwCzkn+R1o0TYg+G7PyQ5pE18A
E+K3VUcpch1f5RyBTEvE4+HUg4/6OpAIYFVznJ3yk8a+bo1i/HBp2MbtPzssSlT8
mPLY76SETtKdwgIdY91MkTiJd1x0KJDM2GPKM7pNTc81NMSw6WBzsxg4PFbR+BCY
82/sYu8iMy/AdYcUz72hh2DGXnVypzzV/LLgJ/VAP5m+md0lVH5KIG/cduDrajlo
LQ4LKJktO4VmwQIDAQABo4IDUjCCA04wHwYDVR0jBBgwFoAUjYxexFStiuF36Zv5
mwXhuAGNYeEwHQYDVR0OBBYEFBLwftAxb+SvNbWJ+0LZ7bcLk80EMA4GA1UdDwEB
/wQEAwIFoDAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF
BQcDAjBJBgNVHSAEQjBAMDQGCysGAQQBsjEBAgIHMCUwIwYIKwYBBQUHAgEWF2h0
dHBzOi8vc2VjdGlnby5jb20vQ1BTMAgGBmeBDAECATCBhAYIKwYBBQUHAQEEeDB2
ME8GCCsGAQUFBzAChkNodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29SU0FE
b21haW5WYWxpZGF0aW9uU2VjdXJlU2VydmVyQ0EuY3J0MCMGCCsGAQUFBzABhhdo
dHRwOi8vb2NzcC5zZWN0aWdvLmNvbTCCAX4GCisGAQQB1nkCBAIEggFuBIIBagFo
AHYAlpdkv1VYl633Q4doNwhCd+nwOtX2pPM2bkakPw/KqcYAAAGVbeysdQAABAMA
RzBFAiEA+YIgsAqb2cqQVlF4JP2ERIVCH3RXdB7DjIPc6Ch5aK4CIHjqUoV7F5Mk
fcIQcmdn7Z5UR8nYtPA2OLvYc3mCFcLuAHcAGYbUxyiqb/66A294Kk0BkarOLXIx
D67OXXBBLSVMx9QAAAGVbeysDgAABAMASDBGAiEAjryAbXlHsXj/v4f7CWXJzDUX
SUuvA5kRH3doh4WPUQcCIQC+nojCqhCn/ZupbnI50O1T3FSKBQu/LOZ33fApzLJW
hQB1AMs49xWJfIShRF9bwd37yW7ymlnNRwppBYWwyxTDFFjnAAABlW3srDcAAAQD
AEYwRAIgS98L1D2W8nzV3tIQ0R4UJWxwxb7I/TT6e9ly0nA0QsACIFpl7s/WA1Qm
z1Vm8ZtihoNFubO/AiiaVGaeDQiznHFCMHsGA1UdEQR0MHKCCyouZ2l0aHViLmlv
ggwqLmdpdGh1Yi5jb22CFyouZ2l0aHVidXNlcmNvbnRlbnQuY29tggpnaXRodWIu
Y29tgglnaXRodWIuaW+CFWdpdGh1YnVzZXJjb250ZW50LmNvbYIOd3d3LmdpdGh1
Yi5jb20wDQYJKoZIhvcNAQELBQADggEBAHksjTVCptW9CtbBXu+7J2cDDmKRz/EA
kUyONuojOnKoI3d2f5DQDkqzu/gSj6B28YO3a4EYFktvwq3KnXAu9KzSM1ehlhtA
lxlvjjGUgXvux7DjnBH40ItKiE723opeWVbm2WExdRPSckm/CDwshz2U3Sl3M3Wt
v0xPuZJrg1tMIL58RqrS5PpFlAIIlEUC6dr+xVQrwLNcYXVVgvZsRSX/YbrzboLM
gWhuDSQPcaeDGHcy7NxRZHmlpHz+/Ot067VuxjGqm9veKNGZMUdroS+ocxAJBXv3
Z1NCCowvpZazNxKccQg7izYwd6HL70WMxCWFU0e70uw9KZqteG7SVcQ=
-----END CERTIFICATE-----

BIN
server_certs/r2m01.cer.42 Normal file

Binary file not shown.

BIN
server_certs/r2m01.cer.43 Normal file

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More