mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-16 00:17:03 +03:00
Compare commits
68 Commits
SqueezeAmp
...
SqueezeAmp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
527187b2f8 | ||
|
|
102d2f6af5 | ||
|
|
4edd429b0a | ||
|
|
35099d1131 | ||
|
|
ce9c3952e8 | ||
|
|
4ee6352185 | ||
|
|
3a720a1e7d | ||
|
|
987fa5d18c | ||
|
|
23c936ec93 | ||
|
|
d4f10a761e | ||
|
|
d68d163538 | ||
|
|
94580c6771 | ||
|
|
2717277c6e | ||
|
|
b6d537a207 | ||
|
|
cf047c8098 | ||
|
|
21f3768ada | ||
|
|
9cb18fa980 | ||
|
|
d4f6289500 | ||
|
|
21c3ce1fba | ||
|
|
48e8525ba9 | ||
|
|
0db9631700 | ||
|
|
192cb975e2 | ||
|
|
d1f6085199 | ||
|
|
afaa5323d7 | ||
|
|
f92447e9b9 | ||
|
|
22f8d1d88b | ||
|
|
8136b7fd9a | ||
|
|
e588deb3af | ||
|
|
38ec8ac6f8 | ||
|
|
791167f794 | ||
|
|
c9b859ef8c | ||
|
|
db74419bd7 | ||
|
|
265403d364 | ||
|
|
d3dd8b9078 | ||
|
|
de41fb9583 | ||
|
|
2699216d25 | ||
|
|
c7b1c7cd83 | ||
|
|
f51af21b90 | ||
|
|
5ec63f400c | ||
|
|
1753e11698 | ||
|
|
58c6ca059b | ||
|
|
5ef63dc3e4 | ||
|
|
468c847499 | ||
|
|
8c0e766cd7 | ||
|
|
0f792d71ee | ||
|
|
1fc7675c14 | ||
|
|
7b439ae6ee | ||
|
|
c5d7fd521d | ||
|
|
a856b26181 | ||
|
|
a949ec2d24 | ||
|
|
5ec5236991 | ||
|
|
d4cd400cd9 | ||
|
|
64bb5f018b | ||
|
|
593927aac3 | ||
|
|
51761d0890 | ||
|
|
5c56abfe75 | ||
|
|
9ac7c5bbeb | ||
|
|
be28555a40 | ||
|
|
1d32479bc4 | ||
|
|
c83ddc4adc | ||
|
|
5a7cf9b8fe | ||
|
|
190326726c | ||
|
|
387276f2f3 | ||
|
|
1a4a8ba559 | ||
|
|
7ad39a02f5 | ||
|
|
f96d06912f | ||
|
|
36571d3dad | ||
|
|
b075bbaea3 |
4
.github/workflows/CrossBuild.yml
vendored
4
.github/workflows/CrossBuild.yml
vendored
@@ -25,13 +25,13 @@ jobs:
|
||||
strategy:
|
||||
max-parallel: 1
|
||||
matrix:
|
||||
node: [I2S-4MFlash, SqueezeAmp, ESP32-A1S]
|
||||
node: [I2S-4MFlash, SqueezeAmp]
|
||||
depth: [16, 32]
|
||||
steps:
|
||||
- name: Set target name
|
||||
run: |
|
||||
echo "TARGET_BUILD_NAME=${{ matrix.node }}" >> $GITHUB_ENV
|
||||
echo "build_version_prefix=V0." >> $GITHUB_ENV
|
||||
echo "build_version_prefix=1." >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 15
|
||||
|
||||
60
README.md
60
README.md
@@ -23,29 +23,35 @@ Other features include
|
||||
- Full web interface for further configuration/management
|
||||
- Firmware over-the-air update
|
||||
|
||||
To control the equalizer or use the display on LMS, a new player model is required and this is provided through a plugin that can be found [here]( https://raw.githubusercontent.com/sle118/squeezelite-esp32/master/plugin/repo.xml)
|
||||
|
||||
## Performances
|
||||
*(opinions presented here so I = @philippe44)*
|
||||
The main build of squeezelite-esp32 is a 16 bits internal core with all calculations in 32 bits or float precision. This is a design choice I've made to preserve CPU performances (it is already stretching a lot the esp32 chipset) and optimize memory usage as we only have 4MB of usable RAM. Some might correctly comment that the WROVER module have 8MB of RAM, but the processor is only able to address 4MB and the remaining 4MB must be paginated by smaller blocks and I don't have patience to that.
|
||||
|
||||
Now, when I did the porting of squeezelite to esp32, I've also made the core 16 or 32 bits compatible at compile-time. So far, it works in 32 bits but less tests have been done. You can chose to compile it in 32 bits mode. I'm not very interested above 16 bits samples because it does not bring anything (I have an engineering background in theory of information).
|
||||
|
||||
| Capability |16 bits|32 bits| comment |
|
||||
|----------------------------|-------|-------|-------------------------------------------------------------------|
|
||||
| max sampling rate | 192k | 96k | 192k is very challenging, especially when combined with display |
|
||||
| max bit depth | 16 | 24 | 24 bits are truncated in 16 bits mode |
|
||||
| spdif |16 bits|20 bits| |
|
||||
| mp3, aac, opus, ogg/vorbis | 48k | 48k | |
|
||||
| alac, flac, ogg/flac | 96k | 96k | |
|
||||
| pcm, wav, aif | 192k | 96k | |
|
||||
| equalizer | Y | N | 48kHz max (after resampling) - equalization skipped on 96k tracks |
|
||||
| resampling | Y | N | |
|
||||
| cross-fade | 10s | <5s | depends on buffer size and sampling rate |
|
||||
| Capability |16 bits|32 bits| comment |
|
||||
|----------------------------|-------|-------|--------------------------------------------------------------------|
|
||||
| max sampling rate | 192k | 96k | 192k is very challenging, especially when combined with display |
|
||||
| max bit depth | 16 | 24 | 24 bits are truncated in 16 bits mode |
|
||||
| spdif |16 bits|20 bits| |
|
||||
| mp3, aac, opus, ogg/vorbis | 48k | 48k | |
|
||||
| alac, flac, ogg/flac | 96k | 96k | |
|
||||
| pcm, wav, aif | 192k | 96k | |
|
||||
| equalizer | Y | N | 48kHz max (after resampling) - equalization skipped on >48k tracks |
|
||||
| resampling | Y | N | |
|
||||
| cross-fade | 10s | <5s | depends on buffer size and sampling rate |
|
||||
|
||||
The esp32 must run at 240 MHz, with Quad-SPI I/O at 80 MHz and a clock of 40 Mhz. Still, it's a lot to run, especially knowing that it has a serial Flash and PSRAM, so kudos to Espressif for their chipset optimization. Now, to have all the decoding, resampling, equalizing, gain, display, spectrum/vu is a very (very) delicate equilibrium between use of internal /external RAM, tasks priorities and buffer handling. It is not perfect and the more you push the system to the limit, the higher the risk that some files would not play (see below). In general, the display will always have the lowest priority and you'll notice slowdown in scrolling and VU/Spectrum refresh rates. Now, even display thread has some critical section and impacts the capabilities. For example, a 16 bits-depth color display with low SPI speed might prevent 24/96 flac to work but still work with pcm 24/96
|
||||
|
||||
In 16 bits mode, although 192 kHz is reported as max rate, it's highly recommended to limit reported sampling rate to 96k (-Z 96000). Note that some high-speed 24/96k on-line streams might stutter because of TCP/IP stack performances. It is usually due to the fact that the server sends small packets of data and the esp32 cannot receive encoded audio fast enough, regardless of task priority settings (I've tried to tweak that a fair bit). The best option in that case is to let LMS proxy the stream as it will provide larger chunks and a "smoother" stream that can then be handled.
|
||||
|
||||
Note as well that some codecs consume more CPU than others or have not been optimized as much. I've done my best to tweak these, but that level of optimization includes writing some assembly which is painful. One very demanding codec is AAC when files are encoded with SBR. It allows reconstruction of upper part of spectrum and thus higher sampling rate, but the codec spec is such that this is optional, you can decode simply lower band and accept lower sampling rate - See the AAC_DISABLE_SBR option below.
|
||||
## Supported Hardware
|
||||
Any esp32-based hardware with at least 4MB of flash and 4MB of PSRAM will be capable of running squeezelite-esp32 and there are various boards that include such chip. A few are mentionned below, but any should work. You can find various help & instructions [here](https://forums.slimdevices.com/showthread.php?112697-ANNOUNCE-Squeezelite-ESP32-(dedicated-thread))
|
||||
|
||||
**For the sake of clarity, WROOM modules DO NOT work as they don't include PSRAM. Some designs might add it externally, but it's (very) unlikely.**
|
||||
### Raw WROVER module
|
||||
Per above description, a [WROVER module](https://www.espressif.com/en/products/modules/esp32) is enough to run Squeezelite-esp32, but that requires a bit of tinkering to extend it to have analogue audio or hardware buttons (e.g.)
|
||||
|
||||
@@ -64,7 +70,7 @@ NB: You can use the pre-build binaries SqueezeAMP4MBFlash which has all the hard
|
||||
- spdif_config: bck=33,ws=25,do=15
|
||||
|
||||
### ESP32-A1S
|
||||
Works with [ESP32-A1S](https://docs.ai-thinker.com/esp32-a1s) module that includes audio codec and headset output. You still need to use a demo board like [this](https://www.aliexpress.com/item/4000765857347.html?spm=2114.12010615.8148356.11.5d963cd0j669ns) or an external amplifier if you want direct speaker connection.
|
||||
Works with [ESP32-A1S](https://docs.ai-thinker.com/esp32-a1s) module that includes audio codec and headset output. You still need to use a demo board like [this](https://www.aliexpress.com/item/4001060963585.html) or an external amplifier if you want direct speaker connection. Note that there is a version with AC101 codec and another one with ES8388 (see below)
|
||||
|
||||
The board shown above has the following IO set
|
||||
- amplifier: GPIO21
|
||||
@@ -81,11 +87,15 @@ The board shown above has the following IO set
|
||||
|
||||
So a possible config would be
|
||||
- set_GPIO: 21=amp,22=green:0,39=jack:0
|
||||
- dac_config: model=AC101,bck=27,ws=26,do=25,di=35,sda=33,scl=32
|
||||
- a button mapping:
|
||||
```
|
||||
[{"gpio":5,"normal":{"pressed":"ACTRLS_TOGGLE"}},{"gpio":18,"pull":true,"shifter_gpio":5,"normal":{"pressed":"ACTRLS_VOLUP"}, "shifted":{"pressed":"ACTRLS_NEXT"}}, {"gpio":23,"pull":true,"shifter_gpio":5,"normal":{"pressed":"ACTRLS_VOLDOWN"},"shifted":{"pressed":"ACTRLS_PREV"}}]
|
||||
```
|
||||
for AC101
|
||||
- dac_config: model=AC101,bck=27,ws=26,do=25,di=35,sda=33,scl=32
|
||||
|
||||
for ES8388
|
||||
- dac_config model=ES8388,bck=5,ws=26,do=25,sda=18,scl=23,i2c=16
|
||||
### T-WATCH2020 by LilyGo
|
||||
This is a fun [smartwatch](http://www.lilygo.cn/prod_view.aspx?TypeId=50036&Id=1290&FId=t3:50036:3) based on ESP32. It has a 240x240 ST7789 screen and onboard audio. Not very useful to listen to anything but it works. This is an example of a device that requires an I2C set of commands for its dac (see below). There is a build-option if you decide to rebuild everything by yourself, otherwise the I2S default option works with the following parameters
|
||||
|
||||
@@ -130,13 +140,13 @@ The NVS parameter "spi_config" set the spi's gpio used for generic purpose (e.g.
|
||||
data=<gpio>,clk=<gpio>[,dc=<gpio>][,host=1|2]
|
||||
```
|
||||
### DAC/I2S
|
||||
The NVS parameter "dac_config" set the gpio used for i2s communication with your DAC. You can define the defaults at compile time but nvs parameter takes precedence except for SqueezeAMP and A1S where these are forced at runtime. If your DAC also requires i2c, then you must go the re-compile route. Syntax is
|
||||
The NVS parameter "dac_config" set the gpio used for i2s communication with your DAC. You can define the defaults at compile time but nvs parameter takes precedence except for SqueezeAMP and A1S where these are forced at runtime. Syntax is
|
||||
```
|
||||
bck=<gpio>,ws=<gpio>,do=<gpio>[,mute=<gpio>[:0|1][,model=TAS57xx|TAS5713|AC101|I2S][,sda=<gpio>,scl=gpio[,i2c=<addr>]]
|
||||
bck=<gpio>,ws=<gpio>,do=<gpio>[,mck][,mute=<gpio>[:0|1][,model=TAS57xx|TAS5713|AC101|I2S][,sda=<gpio>,scl=gpio[,i2c=<addr>]]
|
||||
```
|
||||
if "model" is not set or is not recognized, then default "I2S" is used. I2C parameters are optional an only needed if your dac requires an I2C control (See 'dac_controlset' below). Note that "i2c" parameters are decimal, hex notation is not allowed.
|
||||
if "model" is not set or is not recognized, then default "I2S" is used. The option "mck" is used for some codecs that require a master clock (although they should not). Only GPIO can be used as MCLK. I2C parameters are optional an only needed if your dac requires an I2C control (See 'dac_controlset' below). Note that "i2c" parameters are decimal, hex notation is not allowed.
|
||||
|
||||
The parameter "dac_controlset" allows definition of simple commands to be sent over i2c for init, power on and off using a JSON syntax:
|
||||
So far, TAS75xx, TAS5714, AC101 and ES8388 are recognized models where the proper init sequence/volume/power controls are sent. For other codecs that might require an I2C commands, please use the parameter "dac_controlset" that allows definition of simple commands to be sent over i2c for init, power on and off using a JSON syntax:
|
||||
```
|
||||
{ init: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
|
||||
poweron: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
|
||||
@@ -144,7 +154,7 @@ The parameter "dac_controlset" allows definition of simple commands to be sent o
|
||||
```
|
||||
This is standard JSON notation, so if you are not familiar with it, Google is your best friend. Be aware that the '...' means you can have as many entries as you want, it's not part of the syntax. Every section is optional, but it does not make sense to set i2c in the 'dac_config' parameter and not setting anything here. The parameter 'mode' allows to *or* the register with the value or to *and* it. Don't set 'mode' if you simply want to write. **Note that all values must be decimal**. You can use a validator like [this](https://jsonlint.com) to verify your syntax
|
||||
|
||||
NB: For well-known configuration, this is ignored
|
||||
NB: For specific builds (all except I2S), all this is ignored. For know codecs, the built-in sequences can be overwritten using dac_controlset
|
||||
|
||||
<strong>Please note that you can not use the same GPIO or port as the I2C</strong>
|
||||
### SPDIF
|
||||
@@ -184,16 +194,16 @@ SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,back=<gpio>][,reset=<gpio>][,speed
|
||||
- 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)
|
||||
- SSD1322 is 128x128 16-level grayscale SPI [here](https://www.amazon.com/gp/product/B079N1LLG8/ref=ox_sc_act_title_1?smid=A1N6DLY3NQK2VM&psc=1) - artwork can be up to 96x96 with vertical vu-meter/spectrum
|
||||
- SSD1351 is 128x128 65k/262k color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/1.5inch-rgb-oled-module.htm)
|
||||
- SSD1322 is 256x64 grayscale 16-levels SPI in multiple sizes [here](https://www.buydisplay.com/oled-display/oled-display-module?resolution=159) - it is very nice
|
||||
- SSD1326 is 256x32 monochrome or grayscale 16-levels SPI [here](https://www.aliexpress.com/item/32833603664.html?spm=a2g0o.productlist.0.0.2d19776cyQvsBi&algo_pvid=c7a3db92-e019-4095-8a28-dfdf0a087f98&algo_expid=c7a3db92-e019-4095-8a28-dfdf0a087f98-1&btsid=0ab6f81e15955375483301352e4208&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_)
|
||||
- SSD1327 is 256x64 grayscale 16-levels SPI in multiple sizes [here](https://www.buydisplay.com/oled-display/oled-display-module?resolution=159) - it is very nice
|
||||
- SSD1327 is 128x128 16-level grayscale SPI [here](https://www.amazon.com/gp/product/B079N1LLG8/ref=ox_sc_act_title_1?smid=A1N6DLY3NQK2VM&psc=1) - artwork can be up to 96x96 with vertical vu-meter/spectrum
|
||||
- SSD1351 is 128x128 65k/262k color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/1.5inch-rgb-oled-module.htm)
|
||||
- SSD1675 is an e-ink paper and is experimental as e-ink is really not suitable for LMS du to its very low refresh rate
|
||||
- ST7735 is a 128x160 65k color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/1.8inch-lcd-module.htm). This needs a backlight control
|
||||
- ST7789 is a 240x320 65k (262k not enabled) color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/2inch-lcd-module.htm). It also exist with 240x240 displays. See **rotate** for use in portrait mode
|
||||
- ILI9341 is another 240x320 65k (262k capable) color SPI. I've not used it much, the driver it has been provided by one external contributor to the project
|
||||
|
||||
To use the display on LMS, add repository https://raw.githubusercontent.com/sle118/squeezelite-esp32/master/plugin/repo.xml. You will then be able to tweak how the vu-meter and spectrum analyzer are displayed, as well as size of artwork. You can also install the excellent plugin "Music Information Screen" which is super useful to tweak the layout.
|
||||
You can tweak how the vu-meter and spectrum analyzer are displayed, as well as size of artwork through a dedicated menu in player's settings (don't forget to add the plugin).
|
||||
|
||||
The NVS parameter "metadata_config" sets how metadata is displayed for AirPlay and Bluetooth. Syntax is
|
||||
```
|
||||
@@ -357,9 +367,9 @@ The benefit of the "raw" mode is that you can build a player which is as close a
|
||||
There is no good or bad option, it's your choice. Use the NVS parameter "lms_ctrls_raw" to change that option
|
||||
|
||||
### Battery / ADC
|
||||
The NVS parameter "bat_config" sets the ADC1 channel used to measure battery/DC voltage. Scale is a float ratio applied to every sample of the 12 bits ADC. A measure is taken every 10s and an average is made every 5 minutes (not a sliding window). Syntax is
|
||||
The NVS parameter "bat_config" sets the ADC1 channel used to measure battery/DC voltage. The "atten" value attenuates the input voltage to the ADC input (the read value maintains a 0-1V rage) where: 0=no attenuation(0..800mV), 1=2.5dB attenuation(0..1.1V), 2=6dB attenuation(0..1.35V), 3=11dB attenuation(0..2.6V). Scale is a float ratio applied to every sample of the 12 bits ADC. A measure is taken every 10s and an average is made every 5 minutes (not a sliding window). Syntax is
|
||||
```
|
||||
channel=0..7,scale=<scale>,cells=<2|3>
|
||||
channel=0..7,scale=<scale>,cells=<2|3>,[atten=<0|1|2|3>]
|
||||
```
|
||||
NB: Set parameter to empty to disable battery reading. For well-known configuration, this is ignored (except for SqueezeAMP where number of cells is required)
|
||||
# Configuration
|
||||
@@ -447,7 +457,7 @@ Create and tweak your config using `idf.py menuconfig` then build binaries using
|
||||
```
|
||||
Use `idf.py monitor` to monitor the application (see esp-idf documentation)
|
||||
|
||||
Note: You can use `idf.py build -DDEPTH=32` to build the 32 bits version and add the `-DVERSION=<your_version>` to add a custom version name (it will be 0.0-<your_version>). If you want to change the whole version string, see squeezelite.h
|
||||
Note: You can use `idf.py build -DDEPTH=32` to build the 32 bits version and add the `-DVERSION=<your_version>` to add a custom version name (it will be 0.0-<your_version>). If you want to change the whole version string, see squeezelite.h. You can also disable the SBR extension of AAC codecs as it consumes a lot of CPU and might overload the esp32. Use `-DAAC_DISABLE_SBR=1` for that
|
||||
|
||||
If you have already cloned the repository and you are getting compile errors on one of the submodules (e.g. telnet), run the following git command in the root of the repository location: `git submodule update --init --recursive`
|
||||
### Rebuild codecs (highly recommended to NOT try that)
|
||||
|
||||
@@ -2,9 +2,14 @@ idf_component_register(
|
||||
INCLUDE_DIRS . ./inc inc/alac inc/FLAC inc/helix-aac inc/mad inc/ogg inc/opus inc/opusfile inc/resample16 inc/soxr inc/vorbis
|
||||
)
|
||||
|
||||
if (DEFINED AAC_DISABLE_SBR)
|
||||
add_prebuilt_library(libhelix-aac lib/libhelix-aac.a )
|
||||
else ()
|
||||
add_prebuilt_library(libhelix-aac lib/libhelix-aac-sbr.a )
|
||||
endif()
|
||||
|
||||
add_prebuilt_library(libmad lib/libmad.a)
|
||||
add_prebuilt_library(libFLAC lib/libFLAC.a )
|
||||
add_prebuilt_library(libhelix-aac lib/libhelix-aac.a )
|
||||
add_prebuilt_library(libvorbisidec lib/libvorbisidec.a )
|
||||
add_prebuilt_library(libogg lib/libogg.a )
|
||||
add_prebuilt_library(libalac lib/libalac.a )
|
||||
|
||||
BIN
components/codecs/lib/libhelix-aac-sbr.a
Normal file
BIN
components/codecs/lib/libhelix-aac-sbr.a
Normal file
Binary file not shown.
Binary file not shown.
@@ -238,9 +238,9 @@ void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
|
||||
|
||||
void GDS_SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate ) { if (Device->SetLayout) Device->SetLayout( Device, HFlip, VFlip, Rotate ); }
|
||||
void GDS_SetDirty( struct GDS_Device* Device ) { Device->Dirty = true; }
|
||||
int GDS_GetWidth( struct GDS_Device* Device ) { return Device->Width; }
|
||||
int GDS_GetHeight( struct GDS_Device* Device ) { return Device->Height; }
|
||||
int GDS_GetDepth( struct GDS_Device* Device ) { return Device->Depth; }
|
||||
int GDS_GetMode( struct GDS_Device* Device ) { return Device->Mode; }
|
||||
int GDS_GetWidth( struct GDS_Device* Device ) { return Device ? Device->Width : 0; }
|
||||
int GDS_GetHeight( struct GDS_Device* Device ) { return Device ? Device->Height : 0; }
|
||||
int GDS_GetDepth( struct GDS_Device* Device ) { return Device ? Device->Depth : 0; }
|
||||
int GDS_GetMode( struct GDS_Device* Device ) { return Device ? Device->Mode : 0; }
|
||||
void GDS_DisplayOn( struct GDS_Device* Device ) { if (Device->DisplayOn) Device->DisplayOn( Device ); }
|
||||
void GDS_DisplayOff( struct GDS_Device* Device ) { if (Device->DisplayOff) Device->DisplayOff( Device ); }
|
||||
@@ -135,7 +135,7 @@ void display_init(char *welcome) {
|
||||
displayer.by = 2;
|
||||
displayer.pause = 3600;
|
||||
displayer.speed = 33;
|
||||
displayer.task = xTaskCreateStatic( (TaskFunction_t) displayer_task, "displayer_thread", DISPLAYER_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
|
||||
displayer.task = xTaskCreateStatic( (TaskFunction_t) displayer_task, "common_displayer", DISPLAYER_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
|
||||
|
||||
// set lines for "fixed" text mode
|
||||
GDS_TextSetFontAuto(display, 1, GDS_FONT_LINE_1, -3);
|
||||
|
||||
@@ -133,12 +133,14 @@ static void bt_next(bool pressed) {
|
||||
}
|
||||
|
||||
const static actrls_t controls = {
|
||||
NULL, // power
|
||||
bt_volume_up, bt_volume_down, // volume up, volume down
|
||||
bt_toggle, bt_play, // toggle, play
|
||||
bt_pause, bt_stop, // pause, stop
|
||||
NULL, NULL, // rew, fwd
|
||||
bt_prev, bt_next, // prev, next
|
||||
NULL, NULL, NULL, NULL, // left, right, up, down
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, // pre1-6
|
||||
bt_volume_down, bt_volume_up, bt_toggle// knob left, knob_right, knob push
|
||||
};
|
||||
|
||||
@@ -648,10 +650,12 @@ void bt_sink_init(bt_cmd_vcb_t cmd_cb, bt_data_cb_t data_cb)
|
||||
|
||||
void bt_sink_deinit(void)
|
||||
{
|
||||
/* this still does not work, can't figure out how to stop properly this BT stack */
|
||||
bt_app_task_shut_down();
|
||||
ESP_LOGD(BT_AV_TAG, "bt_app_task shutdown successfully");
|
||||
if (esp_bluedroid_disable() != ESP_OK) return;
|
||||
// this disable has a sleep timer BTA_DISABLE_DELAY in bt_target.h and
|
||||
// if we don't wait for it then disable crashes... don't know why
|
||||
vTaskDelay(2*200 / portTICK_PERIOD_MS);
|
||||
ESP_LOGD(BT_AV_TAG, "esp_bluedroid_disable called successfully");
|
||||
if (esp_bluedroid_deinit() != ESP_OK) return;
|
||||
ESP_LOGD(BT_AV_TAG, "esp_bluedroid_deinit called successfully");
|
||||
|
||||
@@ -81,12 +81,14 @@ static void raop_next(bool pressed) {
|
||||
}
|
||||
|
||||
const static actrls_t controls = {
|
||||
NULL, // power
|
||||
raop_volume_up, raop_volume_down, // volume up, volume down
|
||||
raop_toggle, raop_play, // toggle, play
|
||||
raop_pause, raop_stop, // pause, stop
|
||||
NULL, NULL, // rew, fwd
|
||||
raop_prev, raop_next, // prev, next
|
||||
NULL, NULL, NULL, NULL, // left, right, up, down
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, // pre1-6
|
||||
raop_volume_down, raop_volume_up, raop_toggle// knob left, knob_right, knob push
|
||||
};
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ static const actrls_config_map_t actrls_config_map[] =
|
||||
};
|
||||
|
||||
// BEWARE: the actions below need to stay aligned with the corresponding enum to properly support json parsing
|
||||
// along with the actrls_t controls in LMS_controls, bt_sink and raop_sink
|
||||
#define EP(x) [x] = #x /* ENUM PRINT */
|
||||
static const char * actrls_action_s[ ] = { EP(ACTRLS_POWER),EP(ACTRLS_VOLUP),EP(ACTRLS_VOLDOWN),EP(ACTRLS_TOGGLE),EP(ACTRLS_PLAY),
|
||||
EP(ACTRLS_PAUSE),EP(ACTRLS_STOP),EP(ACTRLS_REW),EP(ACTRLS_FWD),EP(ACTRLS_PREV),EP(ACTRLS_NEXT),
|
||||
|
||||
@@ -33,11 +33,12 @@ static struct {
|
||||
int channel;
|
||||
float sum, avg, scale;
|
||||
int count;
|
||||
int cells;
|
||||
int cells, attenuation;
|
||||
TimerHandle_t timer;
|
||||
} battery = {
|
||||
.channel = CONFIG_BAT_CHANNEL,
|
||||
.cells = 2,
|
||||
.attenuation = ADC_ATTEN_DB_0,
|
||||
};
|
||||
|
||||
/****************************************************************************************
|
||||
@@ -82,6 +83,7 @@ void battery_svc_init(void) {
|
||||
#ifndef CONFIG_BAT_LOCKED
|
||||
if ((p = strcasestr(nvs_item, "channel")) != NULL) battery.channel = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(nvs_item, "scale")) != NULL) battery.scale = atof(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(nvs_item, "atten")) != NULL) battery.attenuation = atoi(strchr(p, '=') + 1);
|
||||
#endif
|
||||
if ((p = strcasestr(nvs_item, "cells")) != NULL) battery.cells = atof(strchr(p, '=') + 1);
|
||||
free(nvs_item);
|
||||
@@ -89,7 +91,7 @@ void battery_svc_init(void) {
|
||||
|
||||
if (battery.channel != -1) {
|
||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||
adc1_config_channel_atten(battery.channel, ADC_ATTEN_DB_0);
|
||||
adc1_config_channel_atten(battery.channel, battery.attenuation);
|
||||
|
||||
battery.avg = adc1_get_raw(battery.channel) * battery.scale / 4095.0;
|
||||
battery.timer = xTimerCreate("battery", BATTERY_TIMER / portTICK_RATE_MS, pdTRUE, NULL, battery_callback);
|
||||
|
||||
@@ -37,4 +37,8 @@ else()
|
||||
add_definitions(-DRESAMPLE16 -DBYTES_PER_FRAME=4)
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED AAC_DISABLED_SBR)
|
||||
add_definitions(-DAAC_ENABLE_SBR)
|
||||
endif()
|
||||
|
||||
add_compile_options (-O3 )
|
||||
|
||||
@@ -89,7 +89,12 @@ static bool init(char *config, int i2c_port, i2s_config_t *i2s_config) {
|
||||
adac_write_word(AC101_ADDR, I2S_SR_CTRL, BIN(0111,0000,0000,0000)); // 44.1kHz
|
||||
|
||||
// analogue config
|
||||
#if BYTES_PER_FRAME == 8
|
||||
adac_write_word(AC101_ADDR, I2S1LCK_CTRL, BIN(1000,1000,0111,0000)); // Slave, BCLK=I2S/8,LRCK=32,24bits,I2Smode, Stereo
|
||||
i2s_config->bits_per_sample = 24;
|
||||
#else
|
||||
adac_write_word(AC101_ADDR, I2S1LCK_CTRL, BIN(1000,1000,0101,0000)); // Slave, BCLK=I2S/8,LRCK=32,16bits,I2Smode, Stereo
|
||||
#endif
|
||||
adac_write_word(AC101_ADDR, I2S1_SDOUT_CTRL, BIN(1100,0000,0000,0000)); // I2S1ADC (R&L)
|
||||
adac_write_word(AC101_ADDR, I2S1_SDIN_CTRL, BIN(1100,0000,0000,0000)); // IS21DAC (R&L)
|
||||
adac_write_word(AC101_ADDR, I2S1_MXR_SRC, BIN(0010,0010,0000,0000)); // ADCL, ADCR
|
||||
|
||||
@@ -26,8 +26,16 @@ static int i2c_port = -1;
|
||||
int adac_init(char *config, int i2c_port_num) {
|
||||
char *p;
|
||||
int i2c_addr = 0;
|
||||
i2c_port = i2c_port_num;
|
||||
|
||||
// some crappy codecs require MCLK to work
|
||||
if ((p = strcasestr(config, "mck")) != NULL) {
|
||||
ESP_LOGI(TAG, "Configuring MCLK on GPIO0");
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
|
||||
REG_WRITE(PIN_CTRL, 0xFFFFFFF0);
|
||||
}
|
||||
|
||||
i2c_port = i2c_port_num;
|
||||
|
||||
// configure i2c
|
||||
i2c_config_t i2c_config = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
|
||||
@@ -166,6 +166,26 @@ const actrls_t LMS_controls = {
|
||||
lms_knob_left, lms_knob_right, lms_knob_push,
|
||||
};
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static void connect_cli_socket(void) {
|
||||
struct sockaddr_in addr = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_addr.s_addr = server_ip,
|
||||
.sin_port = htons(server_cport),
|
||||
};
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
|
||||
cli_sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
if (connect(cli_sock, (struct sockaddr *) &addr, addrlen) < 0) {
|
||||
LOG_ERROR("unable to connect to server %s:%hu with cli", inet_ntoa(server_ip), server_cport);
|
||||
closesocket(cli_sock);
|
||||
cli_sock = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
@@ -175,8 +195,12 @@ static void cli_send_cmd(char *cmd) {
|
||||
|
||||
len = sprintf(packet, "%02x:%02x:%02x:%02x:%02x:%02x %s\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], cmd);
|
||||
LOG_DEBUG("sending command %s at %s:%hu", packet, inet_ntoa(server_ip), server_cport);
|
||||
|
||||
|
||||
if (cli_sock < 0) connect_cli_socket();
|
||||
|
||||
if (send(cli_sock, packet, len, MSG_DONTWAIT) < 0) {
|
||||
closesocket(cli_sock);
|
||||
cli_sock = -1;
|
||||
LOG_WARN("cannot send CLI %s", packet);
|
||||
}
|
||||
|
||||
@@ -188,26 +212,14 @@ static void cli_send_cmd(char *cmd) {
|
||||
* Notification when server changes
|
||||
*/
|
||||
static void notify(in_addr_t ip, u16_t hport, u16_t cport) {
|
||||
struct sockaddr_in addr;
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
|
||||
server_ip = ip;
|
||||
server_hport = hport;
|
||||
server_cport = cport;
|
||||
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = server_ip;
|
||||
addr.sin_port = htons(server_cport);
|
||||
|
||||
// close existing CLI connection and open new one
|
||||
if (cli_sock >= 0) closesocket(cli_sock);
|
||||
cli_sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
if (connect(cli_sock, (struct sockaddr *) &addr, addrlen) < 0) {
|
||||
LOG_ERROR("unable to connect to server %s:%hu with cli", inet_ntoa(server_ip), server_cport);
|
||||
closesocket(cli_sock);
|
||||
cli_sock = -1;
|
||||
}
|
||||
connect_cli_socket();
|
||||
|
||||
LOG_INFO("notified server %s hport %hu cport %hu", inet_ntoa(ip), hport, cport);
|
||||
|
||||
|
||||
@@ -113,14 +113,23 @@ struct ANIC_header {
|
||||
u8_t mode;
|
||||
};
|
||||
|
||||
struct dmxt_packet {
|
||||
char opcode[4];
|
||||
u16_t x;
|
||||
u16_t length;
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
static struct {
|
||||
TaskHandle_t task;
|
||||
SemaphoreHandle_t mutex;
|
||||
int width, height;
|
||||
bool dirty;
|
||||
bool owned;
|
||||
int wake;
|
||||
bool owned;
|
||||
struct {
|
||||
SemaphoreHandle_t mutex;
|
||||
int width, height;
|
||||
bool dirty;
|
||||
};
|
||||
} displayer = { .dirty = true, .owned = true };
|
||||
|
||||
static uint32_t *grayMap;
|
||||
@@ -143,13 +152,13 @@ static uint32_t *grayMap;
|
||||
static struct scroller_s {
|
||||
// copy of grfs content
|
||||
u8_t screen;
|
||||
u32_t pause, speed;
|
||||
int wake;
|
||||
u32_t pause;
|
||||
u16_t mode;
|
||||
s16_t by;
|
||||
// scroller management & sharing between grfg and scrolling task
|
||||
bool active, first, overflow;
|
||||
int scrolled;
|
||||
int speed, wake;
|
||||
struct {
|
||||
u8_t *frame;
|
||||
u32_t width;
|
||||
@@ -167,7 +176,7 @@ static struct {
|
||||
u8_t *data;
|
||||
u32_t size;
|
||||
u16_t x, y;
|
||||
bool enable;
|
||||
bool enable, full;
|
||||
} artwork;
|
||||
|
||||
#define MAX_BARS 32
|
||||
@@ -175,15 +184,13 @@ static struct {
|
||||
static EXT_RAM_ATTR struct {
|
||||
int bar_gap, bar_width, bar_border;
|
||||
bool rotate;
|
||||
struct {
|
||||
struct bar_s {
|
||||
int current, max;
|
||||
int limit;
|
||||
} bars[MAX_BARS];
|
||||
float spectrum_scale;
|
||||
int n, col, row, height, width, border, style, max;
|
||||
enum { VISU_BLANK, VISU_VUMETER, VISU_SPECTRUM, VISU_WAVEFORM } mode;
|
||||
int speed, wake;
|
||||
float fft[FFT_LEN*2], samples[FFT_LEN*2], hanning[FFT_LEN];
|
||||
enum { VISU_BLANK, VISU_VUMETER = 0x01, VISU_SPECTRUM = 0x02, VISU_WAVEFORM } mode;
|
||||
struct {
|
||||
u8_t *frame;
|
||||
int width;
|
||||
@@ -191,6 +198,18 @@ static EXT_RAM_ATTR struct {
|
||||
} back;
|
||||
} visu;
|
||||
|
||||
static EXT_RAM_ATTR struct {
|
||||
float fft[FFT_LEN*2], samples[FFT_LEN*2], hanning[FFT_LEN];
|
||||
int levels[2];
|
||||
} meters;
|
||||
|
||||
static EXT_RAM_ATTR struct {
|
||||
int mode;
|
||||
int max;
|
||||
u16_t config;
|
||||
struct bar_s bars[MAX_BARS] ;
|
||||
} led_visu;
|
||||
|
||||
extern const uint8_t vu_bitmap[] asm("_binary_vu_data_start");
|
||||
|
||||
#define ANIM_NONE 0x00
|
||||
@@ -211,7 +230,7 @@ static bool (*display_bus_chain)(void *from, enum display_bus_cmd_e cmd);
|
||||
#define max(a,b) (((a) > (b)) ? (a) : (b))
|
||||
|
||||
static void server(in_addr_t ip, u16_t hport, u16_t cport);
|
||||
static void sendSETD(u16_t width, u16_t height);
|
||||
static void sendSETD(u16_t width, u16_t height, u16_t led_config);
|
||||
static void sendANIC(u8_t code);
|
||||
static bool handler(u8_t *data, int len);
|
||||
static bool display_bus_handler(void *from, enum display_bus_cmd_e cmd);
|
||||
@@ -222,8 +241,12 @@ static void grfs_handler(u8_t *data, int len);
|
||||
static void grfg_handler(u8_t *data, int len);
|
||||
static void grfa_handler(u8_t *data, int len);
|
||||
static void visu_handler(u8_t *data, int len);
|
||||
static void dmxt_handler(u8_t *data, int len);
|
||||
static void displayer_task(void* arg);
|
||||
|
||||
// PLACEHOLDER
|
||||
void *led_display = 0x1000;
|
||||
|
||||
/* scrolling undocumented information
|
||||
grfs
|
||||
B: screen number
|
||||
@@ -277,50 +300,61 @@ static void displayer_task(void* arg);
|
||||
Right channel parameters (not required for mono):
|
||||
4-5 - same as left channel parameters
|
||||
*/
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
bool sb_display_init(void) {
|
||||
bool sb_displayer_init(void) {
|
||||
static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
|
||||
static EXT_RAM_ATTR StackType_t xStack[SCROLL_STACK_SIZE] __attribute__ ((aligned (4)));
|
||||
|
||||
// no display, just make sure we won't have requests
|
||||
if (!display || GDS_GetWidth(display) <= 0 || GDS_GetHeight(display) <= 0) {
|
||||
LOG_INFO("no display for LMS");
|
||||
if ((GDS_GetWidth(display) <= 0 || GDS_GetHeight(display) <= 0) && !led_display) {
|
||||
LOG_INFO("no display or led visualizer for LMS");
|
||||
return false;
|
||||
}
|
||||
|
||||
// inform LMS of our screen dimensions
|
||||
sendSETD(GDS_GetWidth(display), GDS_GetHeight(display));
|
||||
if (display) {
|
||||
// need to force height to 32 maximum
|
||||
displayer.width = GDS_GetWidth(display);
|
||||
displayer.height = min(GDS_GetHeight(display), SB_HEIGHT);
|
||||
|
||||
// need to force height to 32 maximum
|
||||
displayer.width = GDS_GetWidth(display);
|
||||
displayer.height = min(GDS_GetHeight(display), SB_HEIGHT);
|
||||
// allocate gray-color mapping if needed;
|
||||
if (GDS_GetMode(display) > GDS_GRAYSCALE) {
|
||||
grayMap = malloc(256*sizeof(*grayMap));
|
||||
for (int i = 0; i < 256; i++) grayMap[i] = GDS_GrayMap(display, i);
|
||||
}
|
||||
|
||||
// allocate gray-color mapping if needed;
|
||||
if (GDS_GetMode(display) > GDS_GRAYSCALE) {
|
||||
grayMap = malloc(256*sizeof(*grayMap));
|
||||
for (int i = 0; i < 256; i++) grayMap[i] = GDS_GrayMap(display, i);
|
||||
// create visu configuration
|
||||
visu.bar_gap = 1;
|
||||
visu.back.frame = calloc(1, (displayer.width * displayer.height) / 8);
|
||||
|
||||
// size scroller (width + current screen)
|
||||
scroller.scroll.max = (displayer.width * displayer.height / 8) * (15 + 1);
|
||||
scroller.scroll.frame = malloc(scroller.scroll.max);
|
||||
scroller.back.frame = malloc(displayer.width * displayer.height / 8);
|
||||
scroller.frame = malloc(displayer.width * displayer.height / 8);
|
||||
|
||||
// chain handlers
|
||||
display_bus_chain = display_bus;
|
||||
display_bus = display_bus_handler;
|
||||
}
|
||||
|
||||
if (led_display) {
|
||||
// PLACEHOLDER to init config
|
||||
led_visu.mode = VISU_VUMETER;
|
||||
}
|
||||
|
||||
// create visu configuration
|
||||
visu.bar_gap = 1;
|
||||
visu.speed = 100;
|
||||
visu.back.frame = calloc(1, (displayer.width * displayer.height) / 8);
|
||||
dsps_fft2r_init_fc32(visu.fft, FFT_LEN);
|
||||
dsps_wind_hann_f32(visu.hanning, FFT_LEN);
|
||||
|
||||
// create scroll management task
|
||||
displayer.mutex = xSemaphoreCreateMutex();
|
||||
displayer.task = xTaskCreateStatic( (TaskFunction_t) displayer_task, "displayer_thread", SCROLL_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
|
||||
// inform LMS of our screen/led dimensions
|
||||
sendSETD(GDS_GetWidth(display), GDS_GetHeight(display), led_visu.config);
|
||||
|
||||
dsps_fft2r_init_fc32(meters.fft, FFT_LEN);
|
||||
dsps_wind_hann_f32(meters.hanning, FFT_LEN);
|
||||
|
||||
// create displayer management task
|
||||
displayer.mutex = xSemaphoreCreateMutex();
|
||||
displayer.task = xTaskCreateStatic( (TaskFunction_t) displayer_task, "squeeze_displayer", SCROLL_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
|
||||
|
||||
// size scroller (width + current screen)
|
||||
scroller.scroll.max = (displayer.width * displayer.height / 8) * (15 + 1);
|
||||
scroller.scroll.frame = malloc(scroller.scroll.max);
|
||||
scroller.back.frame = malloc(displayer.width * displayer.height / 8);
|
||||
scroller.frame = malloc(displayer.width * displayer.height / 8);
|
||||
|
||||
// chain handlers
|
||||
slimp_handler_chain = slimp_handler;
|
||||
slimp_handler = handler;
|
||||
@@ -328,10 +362,7 @@ bool sb_display_init(void) {
|
||||
notify_chain = server_notify;
|
||||
server_notify = server;
|
||||
|
||||
display_bus_chain = display_bus;
|
||||
display_bus = display_bus_handler;
|
||||
|
||||
return true;
|
||||
return display != NULL;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
@@ -380,14 +411,14 @@ static void sendANIC(u8_t code) {
|
||||
/****************************************************************************************
|
||||
* Send SETD for width
|
||||
*/
|
||||
static void sendSETD(u16_t width, u16_t height) {
|
||||
static void sendSETD(u16_t width, u16_t height, u16_t led_config) {
|
||||
struct SETD_header pkt_header;
|
||||
|
||||
memset(&pkt_header, 0, sizeof(pkt_header));
|
||||
memcpy(&pkt_header.opcode, "SETD", 4);
|
||||
|
||||
pkt_header.id = 0xfe; // id 0xfe is width S:P:Squeezebox2
|
||||
pkt_header.length = htonl(sizeof(pkt_header) + 4 - 8);
|
||||
pkt_header.length = htonl(sizeof(pkt_header) + 6 - 8);
|
||||
|
||||
LOG_INFO("sending dimension %ux%u", width, height);
|
||||
|
||||
@@ -398,6 +429,7 @@ static void sendSETD(u16_t width, u16_t height) {
|
||||
send_packet((uint8_t *) &pkt_header, sizeof(pkt_header));
|
||||
send_packet((uint8_t *) &width, 2);
|
||||
send_packet((uint8_t *) &height, 2);
|
||||
send_packet((uint8_t *) &led_config, 2);
|
||||
UNLOCK_P;
|
||||
}
|
||||
|
||||
@@ -410,13 +442,13 @@ static void server(in_addr_t ip, u16_t hport, u16_t cport) {
|
||||
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
|
||||
|
||||
sprintf(msg, "%s:%hu", inet_ntoa(ip), hport);
|
||||
if (displayer.owned) GDS_TextPos(display, GDS_FONT_DEFAULT, GDS_TEXT_CENTERED, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, msg);
|
||||
if (display && displayer.owned) GDS_TextPos(display, GDS_FONT_DEFAULT, GDS_TEXT_CENTERED, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, msg);
|
||||
displayer.dirty = true;
|
||||
|
||||
xSemaphoreGive(displayer.mutex);
|
||||
|
||||
// inform new LMS server of our capabilities
|
||||
sendSETD(displayer.width, GDS_GetHeight(display));
|
||||
sendSETD(GDS_GetWidth(display), GDS_GetHeight(display), led_visu.config);
|
||||
|
||||
if (notify_chain) (*notify_chain)(ip, hport, cport);
|
||||
}
|
||||
@@ -441,6 +473,8 @@ static bool handler(u8_t *data, int len){
|
||||
grfa_handler(data, len);
|
||||
} else if (!strncmp((char*) data, "visu", 4)) {
|
||||
visu_handler(data, len);
|
||||
} else if (!strncmp((char*) data, "dmxt", 4)) {
|
||||
dmxt_handler(data, len);
|
||||
} else {
|
||||
res = false;
|
||||
}
|
||||
@@ -629,8 +663,7 @@ static void grfe_handler( u8_t *data, int len) {
|
||||
scroller.active = false;
|
||||
|
||||
// full screen artwork or for small screen, full screen visu has priority
|
||||
if (((visu.mode & VISU_ESP32) && !visu.col && visu.row < displayer.height) ||
|
||||
(artwork.enable && artwork.x == 0 && artwork.y == 0)) {
|
||||
if (((visu.mode & VISU_ESP32) && !visu.col && visu.row < displayer.height) || artwork.full) {
|
||||
xSemaphoreGive(displayer.mutex);
|
||||
return;
|
||||
}
|
||||
@@ -753,8 +786,7 @@ static void grfg_handler(u8_t *data, int len) {
|
||||
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
|
||||
if (((visu.mode & VISU_ESP32) && !visu.col && visu.row < displayer.height) ||
|
||||
(artwork.enable && artwork.x == 0 && artwork.y == 0)) {
|
||||
if (((visu.mode & VISU_ESP32) && !visu.col && visu.row < displayer.height) || artwork.full) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -808,6 +840,7 @@ static void grfa_handler(u8_t *data, int len) {
|
||||
artwork.y = htons(pkt->y);
|
||||
} else if (artwork.size) GDS_ClearWindow(display, artwork.x, artwork.y, -1, -1, GDS_COLOR_BLACK);
|
||||
|
||||
artwork.full = artwork.enable && artwork.x == 0 && artwork.y == 0;
|
||||
LOG_INFO("gfra en:%u x:%hu, y:%hu", artwork.enable, artwork.x, artwork.y);
|
||||
|
||||
// done in any case
|
||||
@@ -825,6 +858,7 @@ static void grfa_handler(u8_t *data, int len) {
|
||||
// now use new parameters
|
||||
artwork.x = htons(pkt->x);
|
||||
artwork.y = htons(pkt->y);
|
||||
artwork.full = artwork.enable && artwork.x == 0 && artwork.y == 0;
|
||||
if (artwork.data) free(artwork.data);
|
||||
artwork.data = malloc(length);
|
||||
}
|
||||
@@ -843,95 +877,57 @@ static void grfa_handler(u8_t *data, int len) {
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Update visualization bars
|
||||
* Fit spectrum into N bands and convert to dB
|
||||
*/
|
||||
static void visu_update(void) {
|
||||
// no update when artwork is full screen (but no need to protect against not owning the display as we are playing
|
||||
if ((artwork.enable && artwork.x == 0 && artwork.y == 0) || pthread_mutex_trylock(&visu_export.mutex)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int mode = visu.mode & ~VISU_ESP32;
|
||||
|
||||
// not enough frames
|
||||
if (visu_export.level < (mode == VISU_VUMETER ? RMS_LEN : FFT_LEN) && visu_export.running) {
|
||||
pthread_mutex_unlock(&visu_export.mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
// reset bars for all cases first
|
||||
for (int i = visu.n; --i >= 0;) visu.bars[i].current = 0;
|
||||
|
||||
if (visu_export.running) {
|
||||
|
||||
if (mode == VISU_VUMETER) {
|
||||
s16_t *iptr = (s16_t*) visu_export.buffer + (BYTES_PER_FRAME / 4) - 1;
|
||||
|
||||
// calculate sum(L²+R²), try to not overflow at the expense of some precision
|
||||
for (int i = RMS_LEN; --i >= 0;) {
|
||||
visu.bars[0].current += (*iptr * *iptr + (1 << (RMS_LEN_BIT - 2))) >> (RMS_LEN_BIT - 1);
|
||||
iptr += BYTES_PER_FRAME / 4;
|
||||
visu.bars[1].current += (*iptr * *iptr + (1 << (RMS_LEN_BIT - 2))) >> (RMS_LEN_BIT - 1);
|
||||
iptr += BYTES_PER_FRAME / 4;
|
||||
}
|
||||
|
||||
// convert to dB (1 bit remaining for getting X²/N, 60dB dynamic starting from 0dBFS = 3 bits back-off)
|
||||
for (int i = visu.n; --i >= 0;) {
|
||||
visu.bars[i].current = visu.max * (0.01667f*10*log10f(0.0000001f + (visu.bars[i].current >> (visu_export.gain == FIXED_ONE ? 8 : 1))) - 0.2543f);
|
||||
if (visu.bars[i].current > visu.max) visu.bars[i].current = visu.max;
|
||||
else if (visu.bars[i].current < 0) visu.bars[i].current = 0;
|
||||
}
|
||||
} else {
|
||||
s16_t *iptr = (s16_t*) visu_export.buffer + (BYTES_PER_FRAME / 4) - 1;
|
||||
// on xtensa/esp32 the floating point FFT takes 1/2 cycles of the fixed point
|
||||
for (int i = 0 ; i < FFT_LEN ; i++) {
|
||||
// don't normalize here, but we are due INT16_MAX and FFT_LEN / 2 / 2
|
||||
visu.samples[i * 2 + 0] = (float) (*iptr + *(iptr+BYTES_PER_FRAME/4)) * visu.hanning[i];
|
||||
visu.samples[i * 2 + 1] = 0;
|
||||
iptr += 2 * BYTES_PER_FRAME / 4;
|
||||
}
|
||||
void spectrum_scale(int n, struct bar_s *bars, int max, float *samples) {
|
||||
float rate = visu_export.rate;
|
||||
// now arrange the result with the number of bar and sampling rate (don't want DC)
|
||||
for (int i = 0, j = 1; i < n && j < (FFT_LEN / 2); i++) {
|
||||
float power, count;
|
||||
|
||||
// actual FFT that might be less cycle than all the crap below
|
||||
dsps_fft2r_fc32_ae32(visu.samples, FFT_LEN);
|
||||
dsps_bit_rev_fc32_ansi(visu.samples, FFT_LEN);
|
||||
float rate = visu_export.rate;
|
||||
|
||||
// now arrange the result with the number of bar and sampling rate (don't want DC)
|
||||
for (int i = 0, j = 1; i < visu.n && j < (FFT_LEN / 2); i++) {
|
||||
float power, count;
|
||||
|
||||
// find the next point in FFT (this is real signal, so only half matters)
|
||||
for (count = 0, power = 0; j * visu_export.rate < visu.bars[i].limit * FFT_LEN && j < FFT_LEN / 2; j++, count += 1) {
|
||||
power += visu.samples[2*j] * visu.samples[2*j] + visu.samples[2*j+1] * visu.samples[2*j+1];
|
||||
}
|
||||
// due to sample rate, we have reached the end of the available spectrum
|
||||
if (j >= (FFT_LEN / 2)) {
|
||||
// normalize accumulated data
|
||||
if (count) power /= count * 2.;
|
||||
} else if (count) {
|
||||
// how much of what remains do we need to add
|
||||
float ratio = j - (visu.bars[i].limit * FFT_LEN) / rate;
|
||||
power += (visu.samples[2*j] * visu.samples[2*j] + visu.samples[2*j+1] * visu.samples[2*j+1]) * ratio;
|
||||
|
||||
// normalize accumulated data
|
||||
power /= (count + ratio) * 2;
|
||||
} else {
|
||||
// no data for that band (sampling rate too high), just assume same as previous one
|
||||
power = (visu.samples[2*j] * visu.samples[2*j] + visu.samples[2*j+1] * visu.samples[2*j+1]) / 2.;
|
||||
}
|
||||
|
||||
// convert to dB and bars, same back-off
|
||||
if (power) visu.bars[i].current = visu.max * (0.01667f*10*(log10f(power) - log10f(FFT_LEN*(visu_export.gain == FIXED_ONE ? 256 : 2))) - 0.2543f);
|
||||
if (visu.bars[i].current > visu.max) visu.bars[i].current = visu.max;
|
||||
else if (visu.bars[i].current < 0) visu.bars[i].current = 0;
|
||||
}
|
||||
// find the next point in FFT (this is real signal, so only half matters)
|
||||
for (count = 0, power = 0; j * visu_export.rate < bars[i].limit * FFT_LEN && j < FFT_LEN / 2; j++, count += 1) {
|
||||
power += samples[2*j] * samples[2*j] + samples[2*j+1] * samples[2*j+1];
|
||||
}
|
||||
}
|
||||
|
||||
// we took what we want, we can release the buffer
|
||||
visu_export.level = 0;
|
||||
pthread_mutex_unlock(&visu_export.mutex);
|
||||
// due to sample rate, we have reached the end of the available spectrum
|
||||
if (j >= (FFT_LEN / 2)) {
|
||||
// normalize accumulated data
|
||||
if (count) power /= count * 2.;
|
||||
} else if (count) {
|
||||
// how much of what remains do we need to add
|
||||
float ratio = j - (bars[i].limit * FFT_LEN) / rate;
|
||||
power += (samples[2*j] * samples[2*j] + samples[2*j+1] * samples[2*j+1]) * ratio;
|
||||
|
||||
// normalize accumulated data
|
||||
power /= (count + ratio) * 2;
|
||||
} else {
|
||||
// no data for that band (sampling rate too high), just assume same as previous one
|
||||
power = (samples[2*j] * samples[2*j] + samples[2*j+1] * samples[2*j+1]) / 2.;
|
||||
}
|
||||
|
||||
// convert to dB and bars, same back-off
|
||||
bars[i].current = max * (0.01667f*10*(log10f(0.0000001f + power) - log10f(FFT_LEN*(visu_export.gain == FIXED_ONE ? 256 : 2))) - 0.2543f);
|
||||
if (bars[i].current > max) bars[i].current = max;
|
||||
else if (bars[i].current < 0) bars[i].current = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Fit levels to max and convert to dB
|
||||
*/
|
||||
void vu_scale(struct bar_s *bars, int max, int *levels) {
|
||||
// convert to dB (1 bit remaining for getting X²/N, 60dB dynamic starting from 0dBFS = 3 bits back-off)
|
||||
for (int i = 2; --i >= 0;) {
|
||||
bars[i].current = max * (0.01667f*10*log10f(0.0000001f + (levels[i] >> (visu_export.gain == FIXED_ONE ? 8 : 1))) - 0.2543f);
|
||||
if (bars[i].current > max) bars[i].current = max;
|
||||
else if (bars[i].current < 0) bars[i].current = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* visu draw
|
||||
*/
|
||||
void visu_draw(void) {
|
||||
// don't refresh screen if all max are 0 (we were are somewhat idle)
|
||||
int clear = 0;
|
||||
for (int i = visu.n; --i >= 0;) clear = max(clear, visu.bars[i].max);
|
||||
@@ -942,9 +938,8 @@ static void visu_update(void) {
|
||||
GDS_DrawBitmapCBR(display, visu.back.frame, visu.back.width, displayer.height, GDS_COLOR_WHITE);
|
||||
}
|
||||
|
||||
if (mode != VISU_VUMETER || !visu.style) {
|
||||
if ((visu.mode & ~VISU_ESP32) != VISU_VUMETER || !visu.style) {
|
||||
// there is much more optimization to be done here, like not redrawing bars unless needed
|
||||
|
||||
for (int i = visu.n; --i >= 0;) {
|
||||
// update maximum
|
||||
if (visu.bars[i].current > visu.bars[i].max) visu.bars[i].max = visu.bars[i].current;
|
||||
@@ -986,8 +981,79 @@ static void visu_update(void) {
|
||||
int level = (visu.bars[0].current + visu.bars[1].current) / 2;
|
||||
draw_VU(display, vu_bitmap, level, 0, visu.row, visu.rotate ? visu.height : visu.width, visu.rotate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Update displayer
|
||||
*/
|
||||
static void displayer_update(void) {
|
||||
// no update when artwork is full screen and no led_strip (but no need to protect against not owning the display as we are playing
|
||||
if ((artwork.full && !led_visu.mode) || pthread_mutex_trylock(&visu_export.mutex)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int mode = (visu.mode & ~VISU_ESP32) | led_visu.mode;
|
||||
|
||||
// not enough frames
|
||||
if (visu_export.level < (mode & VISU_SPECTRUM ? FFT_LEN : RMS_LEN) && visu_export.running) {
|
||||
pthread_mutex_unlock(&visu_export.mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
// reset all levels no matter what
|
||||
meters.levels[0] = meters.levels[1] = 0;
|
||||
memset(meters.samples, 0, sizeof(meters.samples));
|
||||
|
||||
if (visu_export.running) {
|
||||
|
||||
// calculate data for VU-meter
|
||||
if (mode & VISU_VUMETER) {
|
||||
s16_t *iptr = (s16_t*) visu_export.buffer + (BYTES_PER_FRAME / 4) - 1;
|
||||
int *left = &meters.levels[0], *right = &meters.levels[1];
|
||||
// calculate sum(L²+R²), try to not overflow at the expense of some precision
|
||||
for (int i = RMS_LEN; --i >= 0;) {
|
||||
*left += (*iptr * *iptr + (1 << (RMS_LEN_BIT - 2))) >> (RMS_LEN_BIT - 1);
|
||||
iptr += BYTES_PER_FRAME / 4;
|
||||
*right += (*iptr * *iptr + (1 << (RMS_LEN_BIT - 2))) >> (RMS_LEN_BIT - 1);
|
||||
iptr += BYTES_PER_FRAME / 4;
|
||||
}
|
||||
}
|
||||
|
||||
// calculate data for spectrum
|
||||
if (mode & VISU_SPECTRUM) {
|
||||
s16_t *iptr = (s16_t*) visu_export.buffer + (BYTES_PER_FRAME / 4) - 1;
|
||||
// on xtensa/esp32 the floating point FFT takes 1/2 cycles of the fixed point
|
||||
for (int i = 0 ; i < FFT_LEN ; i++) {
|
||||
// don't normalize here, but we are due INT16_MAX and FFT_LEN / 2 / 2
|
||||
meters.samples[i * 2 + 0] = (float) (*iptr + *(iptr+BYTES_PER_FRAME/4)) * meters.hanning[i];
|
||||
meters.samples[i * 2 + 1] = 0;
|
||||
iptr += 2 * BYTES_PER_FRAME / 4;
|
||||
}
|
||||
|
||||
// actual FFT that might be less cycle than all the crap below
|
||||
dsps_fft2r_fc32_ae32(meters.samples, FFT_LEN);
|
||||
dsps_bit_rev_fc32_ansi(meters.samples, FFT_LEN);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// we took what we want, we can release the buffer
|
||||
visu_export.level = 0;
|
||||
pthread_mutex_unlock(&visu_export.mutex);
|
||||
|
||||
// actualize the display
|
||||
if (visu.mode && !artwork.full) {
|
||||
if (visu.mode & VISU_SPECTRUM) spectrum_scale(visu.n, visu.bars, visu.max, meters.samples);
|
||||
else for (int i = 2; --i >= 0;) vu_scale(visu.bars, visu.max, meters.levels);
|
||||
visu_draw();
|
||||
}
|
||||
|
||||
// actualize led_vu
|
||||
if (led_visu.mode) {
|
||||
// PLACEHOLDER to handle led_display. you need potentially scaling of spectrum (X and Y)
|
||||
// and scaling of levels (Y) and then call the
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Calculate spectrum spread
|
||||
@@ -1129,7 +1195,7 @@ static void visu_handler( u8_t *data, int len) {
|
||||
if (visu.row < displayer.height) scroller.active = false;
|
||||
vTaskResume(displayer.task);
|
||||
}
|
||||
visu.wake = 0;
|
||||
displayer.wake = 0;
|
||||
|
||||
// reset bars maximum
|
||||
for (int i = visu.n; --i >= 0;) visu.bars[i].max = 0;
|
||||
@@ -1144,6 +1210,25 @@ static void visu_handler( u8_t *data, int len) {
|
||||
xSemaphoreGive(displayer.mutex);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Dmx style packet handler
|
||||
* ToDo: make packet match dmx protocol format
|
||||
*/
|
||||
static void dmxt_handler( u8_t *data, int len) {
|
||||
struct dmxt_packet *pkt = (struct dmxt_packet*) data;
|
||||
uint16_t offset = htons(pkt->x);
|
||||
uint16_t length = htons(pkt->length);
|
||||
|
||||
LOG_INFO("dmx packet len:%u offset:%u", length, offset);
|
||||
|
||||
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
|
||||
|
||||
// PLACEHOLDER
|
||||
//led_vu_data(data + sizeof(struct dmxt_packet), offset, length);
|
||||
|
||||
xSemaphoreGive(displayer.mutex);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Scroll task
|
||||
* - with the addition of the visualizer, it's a bit a 2-headed beast not easy to
|
||||
@@ -1156,15 +1241,15 @@ static void displayer_task(void *args) {
|
||||
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
|
||||
|
||||
// suspend ourselves if nothing to do, grfg or visu will wake us up
|
||||
if (!scroller.active && !visu.mode) {
|
||||
if (!scroller.active && !visu.mode && !led_visu.mode) {
|
||||
xSemaphoreGive(displayer.mutex);
|
||||
vTaskSuspend(NULL);
|
||||
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
|
||||
scroller.wake = visu.wake = 0;
|
||||
scroller.wake = displayer.wake = 0;
|
||||
}
|
||||
|
||||
|
||||
// go for long sleep when either item is disabled
|
||||
if (!visu.mode) visu.wake = LONG_WAKE;
|
||||
if (!visu.mode && !led_visu.mode) displayer.wake = LONG_WAKE;
|
||||
if (!scroller.active) scroller.wake = LONG_WAKE;
|
||||
|
||||
// scroll required amount of columns (within the window)
|
||||
@@ -1200,20 +1285,20 @@ static void displayer_task(void *args) {
|
||||
}
|
||||
|
||||
// update visu if active
|
||||
if (visu.mode && visu.wake <= 0) {
|
||||
visu_update();
|
||||
visu.wake = 100;
|
||||
if ((visu.mode || led_visu.mode) && displayer.wake <= 0) {
|
||||
displayer_update();
|
||||
displayer.wake = 100;
|
||||
}
|
||||
|
||||
// need to make sure we own display
|
||||
if (displayer.owned) GDS_Update(display);
|
||||
if (display && displayer.owned) GDS_Update(display);
|
||||
|
||||
// release semaphore and sleep what's needed
|
||||
xSemaphoreGive(displayer.mutex);
|
||||
|
||||
sleep = min(visu.wake, scroller.wake);
|
||||
sleep = min(displayer.wake, scroller.wake);
|
||||
vTaskDelay(sleep / portTICK_PERIOD_MS);
|
||||
scroller.wake -= sleep;
|
||||
visu.wake -= sleep;
|
||||
displayer.wake -= sleep;
|
||||
}
|
||||
}
|
||||
@@ -45,14 +45,14 @@ uint32_t _gettime_ms_(void) {
|
||||
}
|
||||
|
||||
extern void sb_controls_init(void);
|
||||
extern bool sb_display_init(void);
|
||||
extern bool sb_displayer_init(void);
|
||||
|
||||
u8_t custom_player_id = 12;
|
||||
|
||||
void embedded_init(void) {
|
||||
mutex_create(slimp_mutex);
|
||||
sb_controls_init();
|
||||
custom_player_id = sb_display_init() ? 100 : 101;
|
||||
custom_player_id = sb_displayer_init() ? 100 : 101;
|
||||
}
|
||||
|
||||
u16_t get_RSSI(void) {
|
||||
|
||||
@@ -7,13 +7,14 @@
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "platform_config.h"
|
||||
#include "squeezelite.h"
|
||||
#include "equalizer.h"
|
||||
#include "esp_equalizer.h"
|
||||
|
||||
#define EQ_BANDS 10
|
||||
|
||||
#define EQ_BANDS 10
|
||||
|
||||
static log_level loglevel = lINFO;
|
||||
|
||||
static struct {
|
||||
@@ -21,6 +22,25 @@ static struct {
|
||||
float gain[EQ_BANDS];
|
||||
bool update;
|
||||
} equalizer = { .update = true };
|
||||
|
||||
/****************************************************************************************
|
||||
* initialize equalizer
|
||||
*/
|
||||
void equalizer_init(void) {
|
||||
s8_t gain[EQ_BANDS] = { };
|
||||
char *config = config_alloc_get(NVS_TYPE_STR, "equalizer");
|
||||
char *p = strtok(config, ", !");
|
||||
|
||||
for (int i = 0; p && i < EQ_BANDS; i++) {
|
||||
gain[i] = atoi(p);
|
||||
p = strtok(NULL, ", :");
|
||||
}
|
||||
|
||||
free(config);
|
||||
equalizer_update(gain);
|
||||
|
||||
LOG_INFO("initializing equalizer");
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* open equalizer
|
||||
@@ -68,7 +88,18 @@ void equalizer_close(void) {
|
||||
* update equalizer gain
|
||||
*/
|
||||
void equalizer_update(s8_t *gain) {
|
||||
for (int i = 0; i < EQ_BANDS; i++) equalizer.gain[i] = gain[i];
|
||||
char config[EQ_BANDS * 4 + 1] = { };
|
||||
char *p = config;
|
||||
|
||||
for (int i = 0; i < EQ_BANDS; i++) {
|
||||
equalizer.gain[i] = gain[i];
|
||||
if (gain[i] < 0) *p++ = '-';
|
||||
*p++ = (gain[i] / 10) + 0x30;
|
||||
*p++ = (gain[i] % 10) + 0x30;
|
||||
if (i < EQ_BANDS - 1) *p++ = ',';
|
||||
}
|
||||
|
||||
config_set_value(NVS_TYPE_STR, "equalizer", config);
|
||||
equalizer.update = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
void equalizer_init(void);
|
||||
void equalizer_open(u32_t sample_rate);
|
||||
void equalizer_close(void);
|
||||
void equalizer_update(s8_t *gain);
|
||||
|
||||
31
components/squeezelite/external/dac_external.c
vendored
31
components/squeezelite/external/dac_external.c
vendored
@@ -32,6 +32,21 @@ const struct adac_s dac_external = { "i2s", init, adac_deinit, power, speaker, h
|
||||
static cJSON *i2c_json;
|
||||
static int i2c_addr;
|
||||
|
||||
static struct {
|
||||
char *model;
|
||||
bool mclk;
|
||||
char *controlset;
|
||||
} codecs[] = {
|
||||
{ "es8388", true,
|
||||
"{\"init\":[ \
|
||||
{\"reg\":8,\"val\":0}, {\"reg\":2,\"val\":243}, {\"reg\":43,\"val\":128}, {\"reg\":0,\"val\":5}, \
|
||||
{\"reg\":1,\"val\":64}, {\"reg\":4,\"val\":60}, {\"reg\":23,\"val\":24}, {\"reg\":24,\"val\":2}, \
|
||||
{\"reg\":26,\"val\":0}, {\"reg\":27,\"val\":0}, {\"reg\":25,\"val\":50}, {\"reg\":38,\"val\":0}, \
|
||||
{\"reg\":39,\"val\":184}, {\"reg\":42,\"val\":184}, {\"reg\":46,\"val\":30}, {\"reg\":47,\"val\":30}, \
|
||||
{\"reg\":48,\"val\":30}, {\"reg\":49,\"val\":30}, {\"reg\":2,\"val\":170}]}" },
|
||||
{ NULL, false, NULL }
|
||||
};
|
||||
|
||||
/****************************************************************************************
|
||||
* init
|
||||
*/
|
||||
@@ -44,10 +59,22 @@ static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) {
|
||||
ESP_LOGI(TAG, "DAC on I2C @%d", i2c_addr);
|
||||
|
||||
p = config_alloc_get_str("dac_controlset", CONFIG_DAC_CONTROLSET, NULL);
|
||||
|
||||
if ((!p || !*p) && (p = strcasestr(config, "model")) != NULL) {
|
||||
char model[32] = "";
|
||||
int i;
|
||||
sscanf(p, "%*[^=]=%31[^,]", model);
|
||||
for (i = 0; *model && ((p = codecs[i].controlset) != NULL) && strcasecmp(codecs[i].model, model); i++);
|
||||
if (p && codecs[i].mclk) {
|
||||
ESP_LOGI(TAG, "Configuring MCLK on GPIO0");
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
|
||||
REG_WRITE(PIN_CTRL, 0xFFFFFFF0);
|
||||
}
|
||||
}
|
||||
|
||||
i2c_json = cJSON_Parse(p);
|
||||
|
||||
if (!i2c_json) {
|
||||
if (p) free(p);
|
||||
ESP_LOGW(TAG, "no i2c controlset found");
|
||||
return true;
|
||||
}
|
||||
@@ -56,7 +83,7 @@ static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) {
|
||||
ESP_LOGE(TAG, "could not intialize DAC");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -160,6 +160,7 @@ static int read_mp4_header(unsigned long *samplerate_p, unsigned char *channels_
|
||||
info.nChans = (*ptr & 0x7f) >> 3;
|
||||
*channels_p = info.nChans;
|
||||
// Note that 24 bits frequencies are not handled
|
||||
#if AAC_ENABLE_SBR
|
||||
if (AOT == 5 || AOT == 29) {
|
||||
*samplerate_p = rates[((ptr[0] & 0x03) << 1) | (ptr[1] >> 7)];
|
||||
LOG_WARN("AAC stream with SBR => high CPU required (use LMS proxied mode)");
|
||||
@@ -172,6 +173,9 @@ static int read_mp4_header(unsigned long *samplerate_p, unsigned char *channels_
|
||||
*samplerate_p = 44100;
|
||||
LOG_ERROR("AAC audio object type %d not handled", AOT);
|
||||
}
|
||||
#else
|
||||
*samplerate_p = info.sampRateCore;
|
||||
#endif
|
||||
HAAC(a, SetRawBlockParams, a->hAac, 0, &info);
|
||||
LOG_DEBUG("playable aac track: %u (p:%x, r:%d, c:%d, desc_len:%d)", trak, AOT, info.sampRateCore, info.nChans, desc_len);
|
||||
play = trak;
|
||||
@@ -438,7 +442,7 @@ static decode_state helixaac_decode(void) {
|
||||
|
||||
// not finished header parsing come back next time
|
||||
UNLOCK_S;
|
||||
LOG_INFO("header not found yet");
|
||||
LOG_DEBUG("header not found yet");
|
||||
return DECODE_RUNNING;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ frames_t _output_frames(frames_t avail) {
|
||||
silence = false;
|
||||
|
||||
// start when threshold met
|
||||
if (output.state == OUTPUT_BUFFER && frames > output.threshold * output.next_sample_rate / 10 && frames > output.start_frames) {
|
||||
if (output.state == OUTPUT_BUFFER && (frames * BYTES_PER_FRAME) > output.threshold * output.next_sample_rate / 10 && frames > output.start_frames) {
|
||||
output.state = OUTPUT_RUNNING;
|
||||
LOG_INFO("start buffer frames: %u", frames);
|
||||
wake_controller();
|
||||
|
||||
@@ -73,11 +73,15 @@ void output_init_embedded(log_level level, char *device, unsigned output_buf_siz
|
||||
slimp_handler_chain = slimp_handler;
|
||||
slimp_handler = handler;
|
||||
|
||||
// init equalizer before backends
|
||||
equalizer_init();
|
||||
|
||||
memset(&output, 0, sizeof(output));
|
||||
output_init_common(level, device, output_buf_size, rates, idle);
|
||||
output.start_frames = FRAME_BLOCK;
|
||||
output.rate_delay = rate_delay;
|
||||
|
||||
|
||||
if (strcasestr(device, "BT ") || !strcasecmp(device, "BT")) {
|
||||
LOG_INFO("init Bluetooth");
|
||||
close_cb = &output_close_bt;
|
||||
|
||||
@@ -169,7 +169,7 @@ static void jack_handler(bool inserted) {
|
||||
static void set_amp_gpio(int gpio, char *value) {
|
||||
char *p;
|
||||
|
||||
if (!strcasecmp(value, "amp")) {
|
||||
if (strcasestr(value, "amp")) {
|
||||
amp_control.gpio = gpio;
|
||||
if ((p = strchr(value, ':')) != NULL) amp_control.active = atoi(p + 1);
|
||||
|
||||
@@ -357,8 +357,8 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
||||
{
|
||||
static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
|
||||
static DRAM_ATTR StackType_t xStack[OUTPUT_THREAD_STACK_SIZE] __attribute__ ((aligned (4)));
|
||||
output_i2s_task = xTaskCreateStatic( (TaskFunction_t) output_thread_i2s, "output_i2s", OUTPUT_THREAD_STACK_SIZE,
|
||||
NULL, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1, xStack, &xTaskBuffer );
|
||||
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 );
|
||||
}
|
||||
|
||||
// do we want stats
|
||||
@@ -419,7 +419,11 @@ 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);
|
||||
}
|
||||
|
||||
output_visu_export(obuf + 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
|
||||
if (silence || output.external || _buf_used(outputbuf) > outputbuf->size >> 2 ) {
|
||||
output_visu_export(obuf + oframes * BYTES_PER_FRAME, out_frames, output.current_sample_rate, silence, (gainL + gainR) / 2);
|
||||
}
|
||||
|
||||
oframes += out_frames;
|
||||
|
||||
return out_frames;
|
||||
@@ -569,10 +573,11 @@ static void output_thread_i2s(void *arg) {
|
||||
SET_MIN_MAX( TIME_MEASUREMENT_GET(timer_start),i2s_time);
|
||||
|
||||
}
|
||||
|
||||
vTaskDelete(NULL);
|
||||
|
||||
if (spdif.enabled) free(spdif.buf);
|
||||
ended = true;
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
#include "squeezelite.h"
|
||||
|
||||
#define VISUEXPORT_SIZE 2048
|
||||
#define VISUEXPORT_SIZE 512
|
||||
|
||||
EXT_BSS struct visu_export_s visu_export;
|
||||
static struct visu_export_s *visu = &visu_export;
|
||||
@@ -37,7 +37,7 @@ void output_visu_export(void *frames, frames_t out_frames, u32_t rate, bool sile
|
||||
return;
|
||||
}
|
||||
|
||||
// do not block, try to stuff data put wait for consumer to have used them
|
||||
// do not block, try to stuff data but wait for consumer to have used them
|
||||
if (!pthread_mutex_trylock(&visu->mutex)) {
|
||||
// don't mix sample rates
|
||||
if (visu->rate != rate) visu->level = 0;
|
||||
|
||||
@@ -331,7 +331,7 @@ static void *stream_thread() {
|
||||
if (stream.meta_interval) {
|
||||
space = min(space, stream.meta_next);
|
||||
}
|
||||
|
||||
|
||||
n = _recv(ssl, fd, streambuf->writep, space, 0);
|
||||
if (n == 0) {
|
||||
LOG_INFO("end of stream (%u bytes)", stream.bytes);
|
||||
|
||||
@@ -43,6 +43,11 @@ static const struct tas57xx_cmd_s tas57xx_init_sequence[] = {
|
||||
{ 0x25, 0x08 }, // ignore SCK halt
|
||||
{ 0x08, 0x10 }, // Mute control enable (from TAS5780)
|
||||
{ 0x54, 0x02 }, // Mute output control (from TAS5780)
|
||||
#if BYTES_PER_FRAME == 8
|
||||
{ 0x28, 0x03 }, // I2S length 32 bits
|
||||
#else
|
||||
{ 0x28, 0x00 }, // I2S length 16 bits
|
||||
#endif
|
||||
{ 0x02, 0x00 }, // restart
|
||||
{ 0xff, 0xff } // end of table
|
||||
};
|
||||
|
||||
@@ -62,7 +62,7 @@ static bool init(char *config, int i2c_port, i2s_config_t *i2s_config) {
|
||||
i2c_write_shadow(49, 102);
|
||||
|
||||
// Configure system clk to GPIO0 for DAC MCLK input
|
||||
ESP_LOGI(TAG, "Configuring MCLK on pin:%d", 0);
|
||||
ESP_LOGI(TAG, "Configuring MCLK on GPIO0");
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
|
||||
REG_WRITE(PIN_CTRL, 0xFFFFFFF0);
|
||||
|
||||
|
||||
@@ -240,12 +240,7 @@ void process_received_data(const char * buffer, size_t size){
|
||||
const char * c=buffer;
|
||||
|
||||
// scrub from any escape command
|
||||
if(*c == '\e'){
|
||||
while(*(c++) !='n'){
|
||||
--size;
|
||||
};
|
||||
--size;
|
||||
}
|
||||
if(*c == '\e') while (size && size-- && *c++ != '\n');
|
||||
memcpy(command,c,size);
|
||||
command[size]='\0';
|
||||
if(command[0]!='\r' && command[0]!='\n'){
|
||||
|
||||
@@ -1 +1 @@
|
||||
[{"C:\\Users\\sle11\\Documents\\VSCode\\squeezelite-esp32\\components\\wifi-manager\\webapp\\src\\js\\test.js":"1","C:\\Users\\sle11\\Documents\\VSCode\\squeezelite-esp32\\components\\wifi-manager\\webapp\\src\\js\\custom.js":"2"},{"size":4775,"mtime":1608244817341,"results":"3","hashOfConfig":"4"},{"size":61704,"mtime":1618438544167,"results":"5","hashOfConfig":"4"},{"filePath":"6","messages":"7","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"8"},"1275pne",{"filePath":"9","messages":"10","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"C:\\Users\\sle11\\Documents\\VSCode\\squeezelite-esp32\\components\\wifi-manager\\webapp\\src\\js\\test.js",[],[],"C:\\Users\\sle11\\Documents\\VSCode\\squeezelite-esp32\\components\\wifi-manager\\webapp\\src\\js\\custom.js",[]]
|
||||
[{"/Users/mh/SynologyDrive/git/squeezelite-esp32/components/wifi-manager/webapp/src/js/custom.js":"1"},{"size":59815,"mtime":1618633783112,"results":"2","hashOfConfig":"3"},{"filePath":"4","messages":"5","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"15w6qa4","/Users/mh/SynologyDrive/git/squeezelite-esp32/components/wifi-manager/webapp/src/js/custom.js",[]]
|
||||
@@ -1085,15 +1085,15 @@ window.setURL = function(button) {
|
||||
|
||||
function rssiToIcon(rssi) {
|
||||
if (rssi >= -55) {
|
||||
return `#signal-wifi-fill`;
|
||||
return `signal-wifi-fill`;
|
||||
} else if (rssi >= -60) {
|
||||
return `#signal-wifi-3-fill`;
|
||||
return `signal-wifi-3-fill`;
|
||||
} else if (rssi >= -65) {
|
||||
return `#signal-wifi-2-fill`;
|
||||
return `signal-wifi-2-fill`;
|
||||
} else if (rssi >= -70) {
|
||||
return `#signal-wifi-1-fill`;
|
||||
return `signal-wifi-1-fill`;
|
||||
} else {
|
||||
return `#signal-wifi-line`;
|
||||
return `signal-wifi-line`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1496,7 +1496,8 @@ function checkStatus() {
|
||||
const baseUrl = 'http://' + data.lms_ip + ':' + data.lms_port;
|
||||
prevLMSIP=data.lms_ip;
|
||||
$.ajax({
|
||||
url: baseUrl + '/plugins/SqueezeESP32/firmware/-99',
|
||||
url: baseUrl + '/plugins/SqueezeESP32/firmware/-check.bin',
|
||||
type: 'HEAD',
|
||||
dataType: 'text',
|
||||
cache: false,
|
||||
error: function() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
target_add_binary_data( __idf_wifi-manager ./webapp/webpack/dist/favicon-32x32.png BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager ./webapp/webpack/dist/index.html.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager ./webapp/webpack/dist/js/index.18c3b7.bundle.js.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager ./webapp/webpack/dist/js/node-modules.18c3b7.bundle.js.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager ./webapp/webpack/dist/js/runtime.18c3b7.bundle.js.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager ./webapp/webpack/dist/js/index.0e064e.bundle.js.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager ./webapp/webpack/dist/js/node-modules.0e064e.bundle.js.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager ./webapp/webpack/dist/js/runtime.0e064e.bundle.js.gz BINARY)
|
||||
|
||||
@@ -4,31 +4,31 @@ extern const uint8_t _favicon_32x32_png_start[] asm("_binary_favicon_32x32_png_s
|
||||
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_18c3b7_bundle_js_gz_start[] asm("_binary_index_18c3b7_bundle_js_gz_start");
|
||||
extern const uint8_t _index_18c3b7_bundle_js_gz_end[] asm("_binary_index_18c3b7_bundle_js_gz_end");
|
||||
extern const uint8_t _node_modules_18c3b7_bundle_js_gz_start[] asm("_binary_node_modules_18c3b7_bundle_js_gz_start");
|
||||
extern const uint8_t _node_modules_18c3b7_bundle_js_gz_end[] asm("_binary_node_modules_18c3b7_bundle_js_gz_end");
|
||||
extern const uint8_t _runtime_18c3b7_bundle_js_gz_start[] asm("_binary_runtime_18c3b7_bundle_js_gz_start");
|
||||
extern const uint8_t _runtime_18c3b7_bundle_js_gz_end[] asm("_binary_runtime_18c3b7_bundle_js_gz_end");
|
||||
extern const uint8_t _index_0e064e_bundle_js_gz_start[] asm("_binary_index_0e064e_bundle_js_gz_start");
|
||||
extern const uint8_t _index_0e064e_bundle_js_gz_end[] asm("_binary_index_0e064e_bundle_js_gz_end");
|
||||
extern const uint8_t _node_modules_0e064e_bundle_js_gz_start[] asm("_binary_node_modules_0e064e_bundle_js_gz_start");
|
||||
extern const uint8_t _node_modules_0e064e_bundle_js_gz_end[] asm("_binary_node_modules_0e064e_bundle_js_gz_end");
|
||||
extern const uint8_t _runtime_0e064e_bundle_js_gz_start[] asm("_binary_runtime_0e064e_bundle_js_gz_start");
|
||||
extern const uint8_t _runtime_0e064e_bundle_js_gz_end[] asm("_binary_runtime_0e064e_bundle_js_gz_end");
|
||||
const char * resource_lookups[] = {
|
||||
"/favicon-32x32.png",
|
||||
"/index.html.gz",
|
||||
"/js/index.18c3b7.bundle.js.gz",
|
||||
"/js/node-modules.18c3b7.bundle.js.gz",
|
||||
"/js/runtime.18c3b7.bundle.js.gz",
|
||||
"/js/index.0e064e.bundle.js.gz",
|
||||
"/js/node-modules.0e064e.bundle.js.gz",
|
||||
"/js/runtime.0e064e.bundle.js.gz",
|
||||
""
|
||||
};
|
||||
const uint8_t * resource_map_start[] = {
|
||||
_favicon_32x32_png_start,
|
||||
_index_html_gz_start,
|
||||
_index_18c3b7_bundle_js_gz_start,
|
||||
_node_modules_18c3b7_bundle_js_gz_start,
|
||||
_runtime_18c3b7_bundle_js_gz_start
|
||||
_index_0e064e_bundle_js_gz_start,
|
||||
_node_modules_0e064e_bundle_js_gz_start,
|
||||
_runtime_0e064e_bundle_js_gz_start
|
||||
};
|
||||
const uint8_t * resource_map_end[] = {
|
||||
_favicon_32x32_png_end,
|
||||
_index_html_gz_end,
|
||||
_index_18c3b7_bundle_js_gz_end,
|
||||
_node_modules_18c3b7_bundle_js_gz_end,
|
||||
_runtime_18c3b7_bundle_js_gz_end
|
||||
_index_0e064e_bundle_js_gz_end,
|
||||
_node_modules_0e064e_bundle_js_gz_end,
|
||||
_runtime_0e064e_bundle_js_gz_end
|
||||
};
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
/***********************************
|
||||
webpack_headers
|
||||
Hash: 18c3b78fe9dd6db2c31d
|
||||
Hash: 0e064eadc7c8b7881633
|
||||
Version: webpack 4.46.0
|
||||
Time: 8782ms
|
||||
Built at: 2021-04-21 12 h 01 min 40 s
|
||||
Time: 9582ms
|
||||
Built at: 2021-07-09 11 h 14 min 41 s
|
||||
Asset Size Chunks Chunk Names
|
||||
./js/index.18c3b7.bundle.js 232 KiB 0 [emitted] [immutable] index
|
||||
./js/index.18c3b7.bundle.js.br 32.5 KiB [emitted]
|
||||
./js/index.18c3b7.bundle.js.gz 41.9 KiB [emitted]
|
||||
./js/node-modules.18c3b7.bundle.js 266 KiB 1 [emitted] [immutable] [big] node-modules
|
||||
./js/node-modules.18c3b7.bundle.js.br 76.3 KiB [emitted]
|
||||
./js/node-modules.18c3b7.bundle.js.gz 88.7 KiB [emitted]
|
||||
./js/runtime.18c3b7.bundle.js 1.46 KiB 2 [emitted] [immutable] runtime
|
||||
./js/runtime.18c3b7.bundle.js.br 644 bytes [emitted]
|
||||
./js/runtime.18c3b7.bundle.js.gz 722 bytes [emitted]
|
||||
./js/index.0e064e.bundle.js 232 KiB 0 [emitted] [immutable] index
|
||||
./js/index.0e064e.bundle.js.br 32.7 KiB [emitted]
|
||||
./js/index.0e064e.bundle.js.gz 42 KiB [emitted]
|
||||
./js/node-modules.0e064e.bundle.js 266 KiB 1 [emitted] [immutable] [big] node-modules
|
||||
./js/node-modules.0e064e.bundle.js.br 76.3 KiB [emitted]
|
||||
./js/node-modules.0e064e.bundle.js.gz 88.7 KiB [emitted]
|
||||
./js/runtime.0e064e.bundle.js 1.46 KiB 2 [emitted] [immutable] runtime
|
||||
./js/runtime.0e064e.bundle.js.br 644 bytes [emitted]
|
||||
./js/runtime.0e064e.bundle.js.gz 722 bytes [emitted]
|
||||
favicon-32x32.png 634 bytes [emitted]
|
||||
index.html 21.7 KiB [emitted]
|
||||
index.html.br 4.74 KiB [emitted]
|
||||
index.html.gz 5.75 KiB [emitted]
|
||||
sprite.svg 4.4 KiB [emitted]
|
||||
sprite.svg.br 898 bytes [emitted]
|
||||
Entrypoint index [big] = ./js/runtime.18c3b7.bundle.js ./js/node-modules.18c3b7.bundle.js ./js/index.18c3b7.bundle.js
|
||||
Entrypoint index [big] = ./js/runtime.0e064e.bundle.js ./js/node-modules.0e064e.bundle.js ./js/index.0e064e.bundle.js
|
||||
[6] ./node_modules/bootstrap/dist/js/bootstrap-exposed.js 437 bytes {1} [built]
|
||||
[11] ./src/sass/main.scss 1.55 KiB {0} [built]
|
||||
[16] ./node_modules/remixicon/icons/Device/signal-wifi-fill.svg 340 bytes {1} [built]
|
||||
@@ -35,7 +35,7 @@ Entrypoint index [big] = ./js/runtime.18c3b7.bundle.js ./js/node-modules.18c3b7.
|
||||
[25] ./node_modules/remixicon/icons/Device/device-recover-fill.svg 346 bytes {1} [built]
|
||||
[26] ./node_modules/remixicon/icons/Device/bluetooth-fill.svg 336 bytes {1} [built]
|
||||
[27] ./node_modules/remixicon/icons/Device/bluetooth-connect-fill.svg 352 bytes {1} [built]
|
||||
[38] ./src/index.ts + 1 modules 62.5 KiB {0} [built]
|
||||
[38] ./src/index.ts + 1 modules 62.6 KiB {0} [built]
|
||||
| ./src/index.ts 1.4 KiB [built]
|
||||
| ./src/js/custom.js 61 KiB [built]
|
||||
+ 24 hidden modules
|
||||
@@ -43,14 +43,14 @@ Entrypoint index [big] = ./js/runtime.18c3b7.bundle.js ./js/node-modules.18c3b7.
|
||||
WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
|
||||
This can impact web performance.
|
||||
Assets:
|
||||
./js/node-modules.18c3b7.bundle.js (266 KiB)
|
||||
./js/node-modules.0e064e.bundle.js (266 KiB)
|
||||
|
||||
WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
|
||||
Entrypoints:
|
||||
index (499 KiB)
|
||||
./js/runtime.18c3b7.bundle.js
|
||||
./js/node-modules.18c3b7.bundle.js
|
||||
./js/index.18c3b7.bundle.js
|
||||
./js/runtime.0e064e.bundle.js
|
||||
./js/node-modules.0e064e.bundle.js
|
||||
./js/index.0e064e.bundle.js
|
||||
|
||||
|
||||
WARNING in webpack performance recommendations:
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
components/wifi-manager/webapp/webpack/dist/js/index.0e064e.bundle.js.br
vendored
Normal file
BIN
components/wifi-manager/webapp/webpack/dist/js/index.0e064e.bundle.js.br
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -367,8 +367,7 @@ module.exports = merge(common, {
|
||||
filename: 'test',
|
||||
minify: false,
|
||||
excludeChunks: ['index'],
|
||||
}),
|
||||
new SpriteLoaderPlugin({plainSprite: true})
|
||||
})
|
||||
|
||||
],
|
||||
});
|
||||
|
||||
@@ -49,49 +49,39 @@ menu "Squeezelite-ESP32"
|
||||
select I2C_LOCKED
|
||||
select LED_LOCKED
|
||||
select SPKFAULT_LOCKED
|
||||
config A1S
|
||||
bool "ESP32-A1S module"
|
||||
select I2C_LOCKED
|
||||
config DAC32
|
||||
bool "DAC32 module"
|
||||
config BASIC_I2C_BT
|
||||
bool "Generic I2S & Bluetooth"
|
||||
config TWATCH2020
|
||||
bool "T-WATCH2020 by LilyGo"
|
||||
select I2C_LOCKED
|
||||
config BASIC_I2C_BT
|
||||
bool "Generic I2S & Bluetooth"
|
||||
endchoice
|
||||
config RELEASE_API
|
||||
string "Software update URL"
|
||||
default "https://api.github.com/repos/sle118/squeezelite-esp32/releases" if !DAC32
|
||||
default "https://yourdomain/api/releases" if DAC32
|
||||
default "https://api.github.com/repos/sle118/squeezelite-esp32/releases"
|
||||
help
|
||||
Set the URL of the API that the front-end UI will use to fetch software updates
|
||||
config SQUEEZELITE_ESP32_RELEASE_URL
|
||||
string "Release URL"
|
||||
default "https://github.com/sle118/squeezelite-esp32/releases" if !DAC32
|
||||
default "https://yourdomain/releases" if DAC32
|
||||
default "https://github.com/sle118/squeezelite-esp32/releases"
|
||||
help
|
||||
Set the URL where users can see a list of releases
|
||||
# you can't change default values once they are set so changing "Target" will not reset
|
||||
# project name if they are visible config - they have to be silent strings
|
||||
config PROJECT_NAME
|
||||
string "Project Name"
|
||||
default "Squeezelite-A1S" if A1S
|
||||
string
|
||||
default "SqueezeAMP" if SQUEEZEAMP
|
||||
default "DAC32" if DAC32
|
||||
default "Squeezelite-TWATCH" if TWATCH2020
|
||||
default "Squeezelite-ESP32" if !A1S && !SQUEEZEAMP && !DAC32 && !TWATCH2020
|
||||
default "Squeezelite-ESP32"
|
||||
config FW_PLATFORM_NAME
|
||||
string "Hardware Platform Name"
|
||||
string
|
||||
default "SqueezeAmp" if SQUEEZEAMP
|
||||
default "DAC32" if DAC32
|
||||
default "ESP32-A1S" if A1S
|
||||
default "TWATCH" if TWATCH2020
|
||||
default "I2S-4MFlash" if !A1S && !SQUEEZEAMP && !DAC32 && !TWATCH2020
|
||||
default "ESP32"
|
||||
# AGGREGATES - begin
|
||||
# these parameters are "aggregates" that take precedence. They must have a default value
|
||||
config DAC_CONFIG
|
||||
string
|
||||
default "model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0" if SQUEEZEAMP
|
||||
default "model=AC101,bck=27,ws=26,do=25,di=35,sda=33,scl=32" if A1S
|
||||
default "model=I2S,bck=26,ws=25,do=33,i2c=53,sda=21,scl=22" if TWATCH2020
|
||||
default ""
|
||||
config SPDIF_CONFIG
|
||||
@@ -138,7 +128,7 @@ menu "Squeezelite-ESP32"
|
||||
I2S data output gpio pin to use.
|
||||
config I2S_DI_IO
|
||||
int "I2S Data Input GPIO number. "
|
||||
default -1 if !A1S
|
||||
default -1
|
||||
help
|
||||
I2S data input gpio pin to use (not used mostly, leave it to -1).
|
||||
endmenu
|
||||
@@ -188,16 +178,7 @@ menu "Squeezelite-ESP32"
|
||||
help
|
||||
I2S data output IO use to simulate SPDIF
|
||||
endmenu
|
||||
|
||||
menu "SPDIF settings"
|
||||
visible if A1S
|
||||
config SPDIF_DO_IO
|
||||
int "SPDIF Data I/O GPIO number"
|
||||
default -1
|
||||
help
|
||||
I2S data output IO use to simulate SPDIF
|
||||
endmenu
|
||||
|
||||
|
||||
menu "A2DP settings"
|
||||
config A2DP_SINK_NAME
|
||||
string "Name of Bluetooth A2DP device"
|
||||
|
||||
@@ -326,6 +326,9 @@ void register_default_nvs(){
|
||||
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "bypass_wm", "0");
|
||||
config_set_default(NVS_TYPE_STR, "bypass_wm", "0", 0);
|
||||
|
||||
ESP_LOGD(TAG,"Registering default value for equalizer");
|
||||
config_set_default(NVS_TYPE_STR, "equalizer", "", 0);
|
||||
|
||||
ESP_LOGD(TAG,"Registering default Audio control board type %s, value ","actrls_config");
|
||||
config_set_default(NVS_TYPE_STR, "actrls_config", "", 0);
|
||||
|
||||
|
||||
Binary file not shown.
@@ -13,25 +13,75 @@ use constant FIRMWARE_POLL_INTERVAL => 3600 * (5 + rand());
|
||||
use constant GITHUB_RELEASES_URI => "https://api.github.com/repos/sle118/squeezelite-esp32/releases";
|
||||
use constant GITHUB_ASSET_URI => GITHUB_RELEASES_URI . "/assets/";
|
||||
use constant GITHUB_DOWNLOAD_URI => "https://github.com/sle118/squeezelite-esp32/releases/download/";
|
||||
my $FW_DOWNLOAD_ID_REGEX = qr|plugins/SqueezeESP32/firmware/(-?\d+)|;
|
||||
my $FW_DOWNLOAD_REGEX = qr|plugins/SqueezeESP32/firmware/([-a-z0-9-/.]+\.bin)$|i;
|
||||
use constant ESP32_STATUS_URI => "http://%s/status.json";
|
||||
use constant BASE_PATH => 'plugins/SqueezeESP32/firmware/';
|
||||
|
||||
my $FW_DOWNLOAD_REGEX = qr{plugins/SqueezeESP32/firmware/(-99|[-a-z0-9-/.]+\.bin)(?:\?.*)?$}i;
|
||||
my $FW_CUSTOM_REGEX = qr/^((?:squeezelite-esp32-)?custom\.bin)$/;
|
||||
my $FW_FILENAME_REGEX = qr/^squeezelite-esp32-.*\.bin(\.tmp)?$/;
|
||||
my $FW_TAG_REGEX = qr/\/(ESP32-A1S|SqueezeAmp|I2S-4MFlash)\.(16|32)\.(\d+)\.(.*)\//;
|
||||
my $FW_TAG_REGEX = qr/\b(ESP32-A1S|SqueezeAmp|I2S-4MFlash)\.(16|32)\.(\d+)\.([-a-zA-Z0-9]+)\b/;
|
||||
|
||||
use constant MAX_FW_IMAGE_SIZE => 10 * 1024 * 1024;
|
||||
|
||||
my $prefs = preferences('plugin.squeezeesp32');
|
||||
my $log = logger('plugin.squeezeesp32');
|
||||
|
||||
my $initialized;
|
||||
|
||||
sub init {
|
||||
Slim::Web::Pages->addRawFunction($FW_DOWNLOAD_ID_REGEX, \&handleFirmwareDownload);
|
||||
Slim::Web::Pages->addRawFunction($FW_DOWNLOAD_REGEX, \&handleFirmwareDownloadDirect);
|
||||
my ($client) = @_;
|
||||
|
||||
if (!$initialized) {
|
||||
$initialized = 1;
|
||||
Slim::Web::Pages->addRawFunction($FW_DOWNLOAD_REGEX, \&handleFirmwareDownload);
|
||||
Slim::Web::Pages->addRawFunction('plugins/SqueezeESP32/firmware/upload', \&handleFirmwareUpload);
|
||||
}
|
||||
|
||||
# start checking for firmware updates
|
||||
Slim::Utils::Timers::setTimer(undef, Time::HiRes::time() + 30 + rand(30), \&prefetchFirmware);
|
||||
Slim::Utils::Timers::setTimer($client, Time::HiRes::time() + 3.0 + rand(3.0), \&initFirmwareDownload);
|
||||
}
|
||||
|
||||
sub initFirmwareDownload {
|
||||
my ($client, $cb) = @_;
|
||||
|
||||
Slim::Utils::Timers::killTimers($client, \&initFirmwareDownload);
|
||||
|
||||
return unless preferences('server')->get('checkVersion') || $cb;
|
||||
|
||||
Slim::Networking::SimpleAsyncHTTP->new(
|
||||
sub {
|
||||
my $http = shift;
|
||||
my $content = eval { from_json( $http->content ) };
|
||||
|
||||
if ($content && ref $content) {
|
||||
my $releaseInfo = getFirmwareTag($content->{version});
|
||||
|
||||
if ($releaseInfo && ref $releaseInfo) {
|
||||
prefetchFirmware($releaseInfo, $cb);
|
||||
}
|
||||
else {
|
||||
$cb->() if $cb;
|
||||
}
|
||||
}
|
||||
},
|
||||
sub {
|
||||
my ($http, $error) = @_;
|
||||
$log->error("Failed to get releases from Github: $error");
|
||||
|
||||
$cb->() if $cb;
|
||||
},
|
||||
{
|
||||
timeout => 10
|
||||
}
|
||||
)->get(sprintf(ESP32_STATUS_URI, $client->ip));
|
||||
|
||||
Slim::Utils::Timers::setTimer($client, Time::HiRes::time() + FIRMWARE_POLL_INTERVAL, \&initFirmwareDownload);
|
||||
}
|
||||
|
||||
sub prefetchFirmware {
|
||||
Slim::Utils::Timers::killTimers(undef, \&prefetchFirmware);
|
||||
my $releaseInfo = $prefs->get('lastReleaseTagUsed');
|
||||
my ($releaseInfo, $cb) = @_;
|
||||
|
||||
return unless $releaseInfo;
|
||||
|
||||
Slim::Networking::SimpleAsyncHTTP->new(
|
||||
sub {
|
||||
@@ -54,16 +104,21 @@ sub prefetchFirmware {
|
||||
}
|
||||
}
|
||||
|
||||
downloadFirmwareFile(sub {
|
||||
main::INFOLOG && $log->is_info && $log->info("Pre-cached firmware file: " . $_[0]);
|
||||
}, sub {
|
||||
my ($http, $error, $url, $code) = @_;
|
||||
$error ||= ($http && $http->error) || 'unknown error';
|
||||
$url ||= ($http && $http->url) || 'no URL';
|
||||
my $customFwUrl = _urlFromPath('custom.bin') if $cb && -f _customFirmwareFile();
|
||||
|
||||
$log->error(sprintf("Failed to get firmware image from Github: %s (%s)", $error || $http->error, $url));
|
||||
}, $url) if $url && $url =~ /^https?/;
|
||||
if ( ($url && $url =~ /^https?/) || $customFwUrl ) {
|
||||
downloadFirmwareFile(sub {
|
||||
main::INFOLOG && $log->is_info && $log->info("Pre-cached firmware file: " . $_[0]);
|
||||
}, sub {
|
||||
my ($http, $error, $url, $code) = @_;
|
||||
$error ||= ($http && $http->error) || 'unknown error';
|
||||
$url ||= ($http && $http->url) || 'no URL';
|
||||
|
||||
$log->error(sprintf("Failed to get firmware image from Github: %s (%s)", $error || $http->error, $url));
|
||||
}, $url) if $url;
|
||||
|
||||
$cb->($releaseInfo, _gh2lmsUrl($url), $customFwUrl) if $cb;
|
||||
}
|
||||
},
|
||||
sub {
|
||||
my ($http, $error) = @_;
|
||||
@@ -74,9 +129,23 @@ sub prefetchFirmware {
|
||||
cache => 1,
|
||||
expires => 3600
|
||||
}
|
||||
)->get(GITHUB_RELEASES_URI) if $releaseInfo;
|
||||
)->get(GITHUB_RELEASES_URI);
|
||||
}
|
||||
|
||||
Slim::Utils::Timers::setTimer(undef, Time::HiRes::time() + FIRMWARE_POLL_INTERVAL, \&prefetchFirmware);
|
||||
sub _gh2lmsUrl {
|
||||
my ($url) = @_;
|
||||
my $ghPrefix = GITHUB_DOWNLOAD_URI;
|
||||
my $baseUrl = Slim::Utils::Network::serverURL();
|
||||
$url =~ s/$ghPrefix/$baseUrl\/plugins\/SqueezeESP32\/firmware\//;
|
||||
return $url;
|
||||
}
|
||||
|
||||
sub _urlFromPath {
|
||||
return sprintf('%s/%s%s', Slim::Utils::Network::serverURL(), BASE_PATH, basename(shift));
|
||||
}
|
||||
|
||||
sub _customFirmwareFile {
|
||||
return catfile(scalar Slim::Utils::OSDetect::dirsFor('updates'), 'squeezelite-esp32-custom.bin');
|
||||
}
|
||||
|
||||
sub handleFirmwareDownload {
|
||||
@@ -88,13 +157,13 @@ sub handleFirmwareDownload {
|
||||
_errorDownloading($httpClient, $response, @_);
|
||||
};
|
||||
|
||||
my $id;
|
||||
if (!defined $request || !(($id) = $request->uri =~ $FW_DOWNLOAD_ID_REGEX)) {
|
||||
my $path;
|
||||
if (!defined $request || !(($path) = $request->uri =~ $FW_DOWNLOAD_REGEX)) {
|
||||
return $_errorDownloading->(undef, 'Invalid request', $request->uri, 400);
|
||||
}
|
||||
|
||||
# this is the magic number used on the client to figure out whether the plugin does support download proxying
|
||||
if ($id == -99) {
|
||||
# this is the magic request used on the client to figure out whether the plugin does support download proxying
|
||||
if ($path =~ /^(?:-99|-check.bin)$/) {
|
||||
$response->code(204);
|
||||
$response->header('Access-Control-Allow-Origin' => '*');
|
||||
|
||||
@@ -102,48 +171,20 @@ sub handleFirmwareDownload {
|
||||
return Slim::Web::HTTP::closeHTTPSocket($httpClient);
|
||||
}
|
||||
|
||||
Slim::Networking::SimpleAsyncHTTP->new(
|
||||
sub {
|
||||
my $http = shift;
|
||||
my $content = eval { from_json( $http->content ) };
|
||||
if ($path =~ $FW_CUSTOM_REGEX) {
|
||||
my $firmwareFile = _customFirmwareFile();
|
||||
|
||||
if (!$content || !ref $content) {
|
||||
$@ && $log->error("Failed to parse response: $@");
|
||||
return $_errorDownloading->($http);
|
||||
}
|
||||
elsif (!$content->{browser_download_url} || !$content->{name}) {
|
||||
return $_errorDownloading->($http, 'No download URL found');
|
||||
}
|
||||
|
||||
downloadFirmwareFile(sub {
|
||||
my $firmwareFile = shift;
|
||||
$response->code(200);
|
||||
Slim::Web::HTTP::sendStreamingFile($httpClient, $response, 'application/octet-stream', $firmwareFile, undef, 1);
|
||||
}, $_errorDownloading, $content->{browser_download_url}, $content->{name});
|
||||
},
|
||||
$_errorDownloading,
|
||||
{
|
||||
timeout => 10,
|
||||
cache => 1,
|
||||
expires => 86400
|
||||
if (! -f $firmwareFile) {
|
||||
main::INFOLOG && $log->is_info && $log->info("Failed to find custom firmware build: $firmwareFile");
|
||||
$response->code(404);
|
||||
$httpClient->send_response($response);
|
||||
return Slim::Web::HTTP::closeHTTPSocket($httpClient);
|
||||
}
|
||||
)->get(GITHUB_ASSET_URI . $id);
|
||||
|
||||
return;
|
||||
}
|
||||
main::INFOLOG && $log->is_info && $log->info("Getting custom firmware build");
|
||||
|
||||
sub handleFirmwareDownloadDirect {
|
||||
my ($httpClient, $response) = @_;
|
||||
|
||||
my $request = $response->request;
|
||||
|
||||
my $_errorDownloading = sub {
|
||||
_errorDownloading($httpClient, $response, @_);
|
||||
};
|
||||
|
||||
my $path;
|
||||
if (!defined $request || !(($path) = $request->uri =~ $FW_DOWNLOAD_REGEX)) {
|
||||
return $_errorDownloading->(undef, 'Invalid request', $request->uri, 400);
|
||||
$response->code(200);
|
||||
return Slim::Web::HTTP::sendStreamingFile($httpClient, $response, 'application/octet-stream', $firmwareFile, undef, 1);
|
||||
}
|
||||
|
||||
main::INFOLOG && $log->is_info && $log->info("Requesting firmware from: $path");
|
||||
@@ -159,7 +200,7 @@ sub downloadFirmwareFile {
|
||||
my ($cb, $ecb, $url, $name) = @_;
|
||||
|
||||
# keep track of the last firmware we requested, to prefetch it in the future
|
||||
_getFirmwareTag($url);
|
||||
my $releaseInfo = getFirmwareTag($url);
|
||||
|
||||
$name ||= basename($url);
|
||||
|
||||
@@ -167,9 +208,21 @@ sub downloadFirmwareFile {
|
||||
return $ecb->(undef, 'Unexpected firmware image name: ' . $name, $url, 400);
|
||||
}
|
||||
|
||||
my $updatesDir = Slim::Utils::OSDetect::dirsFor('updates');
|
||||
my $updatesDir = _getTempDir();
|
||||
my $firmwareFile = catfile($updatesDir, $name);
|
||||
Slim::Utils::Misc::deleteFiles($updatesDir, $FW_FILENAME_REGEX, $firmwareFile);
|
||||
|
||||
if (-f $firmwareFile) {
|
||||
main::INFOLOG && $log->is_info && $log->info("Found uploaded firmware file $name");
|
||||
return $cb->($firmwareFile);
|
||||
}
|
||||
|
||||
$updatesDir = Slim::Utils::OSDetect::dirsFor('updates');
|
||||
$firmwareFile = catfile($updatesDir, $name);
|
||||
|
||||
if ($releaseInfo) {
|
||||
my $fileMatchRegex = join('-', '', $releaseInfo->{branch}, $releaseInfo->{model}, $releaseInfo->{res});
|
||||
Slim::Utils::Misc::deleteFiles($updatesDir, $fileMatchRegex, $firmwareFile);
|
||||
}
|
||||
|
||||
if (-f $firmwareFile) {
|
||||
main::INFOLOG && $log->is_info && $log->info("Found cached firmware file");
|
||||
@@ -188,7 +241,11 @@ sub downloadFirmwareFile {
|
||||
|
||||
return $cb->($firmwareFile);
|
||||
},
|
||||
$ecb,
|
||||
sub {
|
||||
my ($http, $error) = @_;
|
||||
$http->code(404) if $error =~ /\b404\b/;
|
||||
$ecb->(@_);
|
||||
},
|
||||
{
|
||||
saveAs => "$firmwareFile.tmp",
|
||||
}
|
||||
@@ -197,10 +254,10 @@ sub downloadFirmwareFile {
|
||||
return;
|
||||
}
|
||||
|
||||
sub _getFirmwareTag {
|
||||
my ($url) = @_;
|
||||
sub getFirmwareTag {
|
||||
my ($info) = @_;
|
||||
|
||||
if (my ($model, $resolution, $version, $branch) = $url =~ $FW_TAG_REGEX) {
|
||||
if (my ($model, $resolution, $version, $branch) = $info =~ $FW_TAG_REGEX) {
|
||||
my $releaseInfo = {
|
||||
model => $model,
|
||||
res => $resolution,
|
||||
@@ -208,8 +265,6 @@ sub _getFirmwareTag {
|
||||
branch => $branch
|
||||
};
|
||||
|
||||
$prefs->set('lastReleaseTagUsed', $releaseInfo);
|
||||
|
||||
return $releaseInfo;
|
||||
}
|
||||
}
|
||||
@@ -233,5 +288,123 @@ sub _errorDownloading {
|
||||
Slim::Web::HTTP::closeHTTPSocket($httpClient);
|
||||
};
|
||||
|
||||
sub handleFirmwareUpload {
|
||||
my ($httpClient, $response) = @_;
|
||||
|
||||
my $request = $response->request;
|
||||
my $result = {};
|
||||
|
||||
my $t = Time::HiRes::time();
|
||||
|
||||
main::INFOLOG && $log->is_info && $log->info("New firmware image to upload. Size: " . formatMB($request->content_length));
|
||||
|
||||
if ( $request->method !~ /HEAD|OPTIONS|POST/ ) {
|
||||
$log->error("Invalid HTTP verb: " . $request->method);
|
||||
$result = {
|
||||
error => 'Invalid request.',
|
||||
code => 400,
|
||||
};
|
||||
}
|
||||
elsif ( $request->content_length > MAX_FW_IMAGE_SIZE ) {
|
||||
$log->error("Upload data is too large: " . $request->content_length);
|
||||
$result = {
|
||||
error => string('PLUGIN_DNDPLAY_FILE_TOO_LARGE', formatMB($request->content_length), formatMB(MAX_FW_IMAGE_SIZE)),
|
||||
code => 413,
|
||||
};
|
||||
}
|
||||
else {
|
||||
my $ct = $request->header('Content-Type');
|
||||
my ($boundary) = $ct =~ /boundary=(.*)/;
|
||||
|
||||
my ($uploadedFwFh, $filename, $inUpload, $buf);
|
||||
|
||||
# open a pseudo-filehandle to the uploaded data ref for further processing
|
||||
open TEMP, '<', $request->content_ref;
|
||||
|
||||
while (<TEMP>) {
|
||||
if ( Time::HiRes::time - $t > 0.2 ) {
|
||||
main::idleStreams();
|
||||
$t = Time::HiRes::time();
|
||||
}
|
||||
|
||||
# a new part starts - reset some variables
|
||||
if ( /--\Q$boundary\E/i ) {
|
||||
$filename = '';
|
||||
|
||||
if ($buf) {
|
||||
$buf =~ s/\r\n$//;
|
||||
print $uploadedFwFh $buf if $uploadedFwFh;
|
||||
}
|
||||
|
||||
close $uploadedFwFh if $uploadedFwFh;
|
||||
$inUpload = undef;
|
||||
}
|
||||
|
||||
# write data to file handle
|
||||
elsif ( $inUpload && $uploadedFwFh ) {
|
||||
print $uploadedFwFh $buf if defined $buf;
|
||||
$buf = $_;
|
||||
}
|
||||
|
||||
# we got an uploaded file name
|
||||
elsif ( /filename="(.+?)"/i ) {
|
||||
$filename = $1;
|
||||
main::INFOLOG && $log->is_info && $log->info("New file to upload: $filename")
|
||||
}
|
||||
|
||||
# we got the separator after the upload file name: file data comes next. Open a file handle to write the data to.
|
||||
elsif ( $filename && /^\s*$/ ) {
|
||||
$inUpload = 1;
|
||||
|
||||
$uploadedFwFh = File::Temp->new(
|
||||
DIR => _getTempDir(),
|
||||
SUFFIX => '.bin',
|
||||
TEMPLATE => 'squeezelite-esp32-upload-XXXXXX',
|
||||
UNLINK => 0,
|
||||
) or $log->warn("Failed to open file: $@");
|
||||
|
||||
binmode $uploadedFwFh;
|
||||
|
||||
# remove file after a few minutes
|
||||
Slim::Utils::Timers::setTimer($uploadedFwFh->filename, Time::HiRes::time() + 15 * 60, sub { unlink shift });
|
||||
}
|
||||
}
|
||||
|
||||
close TEMP;
|
||||
close $uploadedFwFh if $uploadedFwFh;
|
||||
|
||||
main::idleStreams();
|
||||
|
||||
if (!$result->{error}) {
|
||||
$result->{url} = _urlFromPath($uploadedFwFh->filename);
|
||||
$result->{size} = -s $uploadedFwFh->filename;
|
||||
}
|
||||
}
|
||||
|
||||
$log->error($result->{error}) if $result->{error};
|
||||
|
||||
my $content = to_json($result);
|
||||
$response->header( 'Content-Length' => length($content) );
|
||||
$response->code($result->{code} || 200);
|
||||
$response->header('Connection' => 'close');
|
||||
$response->content_type('application/json');
|
||||
|
||||
Slim::Web::HTTP::addHTTPResponse( $httpClient, $response, \$content );
|
||||
}
|
||||
|
||||
my $tempDir;
|
||||
sub _getTempDir {
|
||||
return $tempDir if $tempDir;
|
||||
|
||||
eval { $tempDir = Slim::Utils::Misc::getTempDir() }; # LMS 8.2+ only
|
||||
$tempDir ||= File::Temp::tempdir(CLEANUP => 1, DIR => preferences('server')->get('cachedir'));
|
||||
|
||||
return $tempDir;
|
||||
}
|
||||
|
||||
sub formatMB {
|
||||
return Slim::Utils::Misc::delimitThousands(int($_[0] / 1024 / 1024)) . 'MB';
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
@@ -1,5 +1,21 @@
|
||||
[% PROCESS settings/header.html %]
|
||||
|
||||
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_FIRMWARE" desc="" %]
|
||||
<div><a href="http://[% player_ip %]" target="_blank">[% "PLUGIN_SQUEEZEESP32_PLAYERSETTINGS" | string %] ([% player_ip %])</a></div>
|
||||
[% IF fwUpdateAvailable %]
|
||||
<div>
|
||||
<input type="submit" name="installUpdate" class="stdclick" value="[% "CONTROLPANEL_INSTALL_UPDATE" | string %]"/>
|
||||
[% fwUpdateAvailable %]
|
||||
</div>
|
||||
[% END %]
|
||||
[% IF fwCustomUpdateAvailable %]
|
||||
<div>
|
||||
<input type="submit" name="installCustomUpdate" class="stdclick" value="[% "CONTROLPANEL_INSTALL_UPDATE" | string %]"/>
|
||||
[% fwCustomUpdateAvailable | string %]
|
||||
</div>
|
||||
[% END %]
|
||||
[% END %]
|
||||
|
||||
[% IF prefs.pref_width %]
|
||||
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_WIDTH" desc="PLUGIN_SQUEEZEESP32_WIDTH_DESC" %]
|
||||
<!--<input type="text" readonly class="stdedit" name="pref_width" id="width" value="[% prefs.pref_width %]" size="3">-->
|
||||
@@ -101,6 +117,6 @@
|
||||
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.9" id="pref_equalizer.9" value="[% pref_equalizer.9 %]" size="2">
|
||||
[% END %]
|
||||
[% END %]
|
||||
[% END %]
|
||||
[% END %]
|
||||
|
||||
[% PROCESS settings/footer.html %]
|
||||
|
||||
@@ -9,6 +9,8 @@ use List::Util qw(min);
|
||||
use Slim::Utils::Log;
|
||||
use Slim::Utils::Prefs;
|
||||
|
||||
use Plugins::SqueezeESP32::FirmwareHelper;
|
||||
|
||||
my $sprefs = preferences('server');
|
||||
my $prefs = preferences('plugin.squeezeesp32');
|
||||
my $log = logger('plugin.squeezeesp32');
|
||||
@@ -95,6 +97,8 @@ sub init {
|
||||
}
|
||||
|
||||
$client->SUPER::init(@_);
|
||||
Plugins::SqueezeESP32::FirmwareHelper::init($client);
|
||||
|
||||
main::INFOLOG && $log->is_info && $log->info("SqueezeESP player connected: " . $client->id);
|
||||
}
|
||||
|
||||
@@ -207,7 +211,7 @@ sub update_artwork {
|
||||
my $cprefs = $prefs->client($client);
|
||||
|
||||
my $artwork = $cprefs->get('artwork') || return;
|
||||
return unless $artwork->{'enable'};
|
||||
return unless $artwork->{'enable'} && $client->display->isa("Plugins::SqueezeESP32::Graphics");
|
||||
|
||||
my $header = pack('Nnn', $artwork->{'enable'}, $artwork->{'x'}, $artwork->{'y'});
|
||||
$client->sendFrame( grfa => \$header );
|
||||
@@ -282,7 +286,7 @@ sub reconnect {
|
||||
$client->SUPER::reconnect(@_);
|
||||
|
||||
$client->pluginData('artwork_md5', '');
|
||||
$client->config_artwork;
|
||||
$client->config_artwork if $client->display->isa("Plugins::SqueezeESP32::Graphics");
|
||||
$client->send_equalizer;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package Plugins::SqueezeESP32::PlayerSettings;
|
||||
|
||||
use strict;
|
||||
use base qw(Slim::Web::Settings);
|
||||
use JSON::XS::VersionOneAndTwo;
|
||||
use List::Util qw(first);
|
||||
|
||||
use Slim::Utils::Log;
|
||||
@@ -36,7 +37,7 @@ sub prefs {
|
||||
}
|
||||
|
||||
sub handler {
|
||||
my ($class, $client, $paramRef) = @_;
|
||||
my ($class, $client, $paramRef, $callback, @args) = @_;
|
||||
|
||||
my ($cprefs, @prefs) = $class->prefs($client);
|
||||
|
||||
@@ -62,7 +63,7 @@ sub handler {
|
||||
x => $paramRef->{'pref_artwork_x'} || 0,
|
||||
y => $paramRef->{'pref_artwork_y'} || 0,
|
||||
};
|
||||
|
||||
|
||||
$cprefs->set('artwork', $artwork);
|
||||
$client->display->modes($client->display->build_modes);
|
||||
# the display update will be done below, after all is completed
|
||||
@@ -76,14 +77,14 @@ sub handler {
|
||||
|
||||
}
|
||||
|
||||
if ($client->depth == 16) {
|
||||
if ($client->can('depth') && $client->depth == 16) {
|
||||
my $equalizer = $cprefs->get('equalizer');
|
||||
for my $i (0 .. $#{$equalizer}) {
|
||||
$equalizer->[$i] = $paramRef->{"pref_equalizer.$i"} || 0;
|
||||
}
|
||||
$cprefs->set('equalizer', $equalizer);
|
||||
$client->update_tones($equalizer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($client->displayWidth) {
|
||||
@@ -93,9 +94,46 @@ sub handler {
|
||||
$paramRef->{'pref_artwork'} = $cprefs->get('artwork');
|
||||
}
|
||||
|
||||
$paramRef->{'pref_equalizer'} = $cprefs->get('equalizer') if $client->depth == 16;
|
||||
$paramRef->{'pref_equalizer'} = $cprefs->get('equalizer') if $client->can('depth') && $client->depth == 16;
|
||||
$paramRef->{'player_ip'} = $client->ip;
|
||||
|
||||
return $class->SUPER::handler($client, $paramRef);
|
||||
Plugins::SqueezeESP32::FirmwareHelper::initFirmwareDownload($client, sub {
|
||||
my ($currentFWInfo, $newFWUrl, $customFwUrl) = @_;
|
||||
|
||||
$currentFWInfo ||= {};
|
||||
my $newFWInfo = Plugins::SqueezeESP32::FirmwareHelper::getFirmwareTag($newFWUrl) || {};
|
||||
|
||||
if ($paramRef->{installUpdate} || $paramRef->{installCustomUpdate}) {
|
||||
my $http = Slim::Networking::SimpleAsyncHTTP->new(sub {
|
||||
main::INFOLOG && $log->is_info && $log->info("Firmware update triggered");
|
||||
}, sub {
|
||||
main::INFOLOG && $log->is_info && $log->info("Failed to trigger firmware update");
|
||||
main::DEBUGLOG && $log->is_debug && $log->debug(Data::Dump::dump(@_));
|
||||
})->post(sprintf('http://%s/config.json', $client->ip), to_json({
|
||||
timestamp => int(Time::HiRes::time() * 1000) * 1,
|
||||
config => {
|
||||
fwurl => {
|
||||
value => $paramRef->{installCustomUpdate} ? $customFwUrl : $newFWUrl,
|
||||
type => 33
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
else {
|
||||
if ($currentFWInfo->{version} && $newFWInfo->{version} && $currentFWInfo->{version} > $newFWInfo->{version}) {
|
||||
main::INFOLOG && $log->is_info && $log->info("There's an update for your SqueezeESP32 player: $newFWUrl");
|
||||
$paramRef->{fwUpdateAvailable} = sprintf($client->string('PLUGIN_SQUEEZEESP32_FIRMWARE_AVAILABLE'), $newFWInfo->{version}, $currentFWInfo->{version});
|
||||
}
|
||||
if ($customFwUrl) {
|
||||
main::INFOLOG && $log->is_info && $log->info("There's a custom firmware for your SqueezeESP32 player: $customFwUrl");
|
||||
$paramRef->{fwCustomUpdateAvailable} = 'PLUGIN_SQUEEZEESP32_CUSTOM_FIRMWARE_AVAILABLE';
|
||||
}
|
||||
}
|
||||
|
||||
$callback->( $client, $paramRef, $class->SUPER::handler($client, $paramRef), @args );
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -8,8 +8,6 @@ use Slim::Utils::Prefs;
|
||||
use Slim::Utils::Log;
|
||||
use Slim::Web::ImageProxy;
|
||||
|
||||
use Plugins::SqueezeESP32::FirmwareHelper;
|
||||
|
||||
my $prefs = preferences('plugin.squeezeesp32');
|
||||
|
||||
my $log = Slim::Utils::Log->addLogCategory({
|
||||
@@ -39,12 +37,13 @@ $prefs->setChange(sub {
|
||||
sub initPlugin {
|
||||
my $class = shift;
|
||||
|
||||
# enable the following to test the firmware downloading code without a SqueezeliteESP32 player
|
||||
# require Plugins::SqueezeESP32::FirmwareHelper;
|
||||
# Plugins::SqueezeESP32::FirmwareHelper::init();
|
||||
|
||||
if ( main::WEBUI ) {
|
||||
require Plugins::SqueezeESP32::PlayerSettings;
|
||||
Plugins::SqueezeESP32::PlayerSettings->new;
|
||||
|
||||
# require Plugins::SqueezeESP32::Settings;
|
||||
# Plugins::SqueezeESP32::Settings->new;
|
||||
}
|
||||
|
||||
$class->SUPER::initPlugin(@_);
|
||||
@@ -60,8 +59,6 @@ sub initPlugin {
|
||||
Slim::Control::Request::subscribe( sub { onNotification(@_) }, [ ['newmetadata'] ] );
|
||||
Slim::Control::Request::subscribe( sub { onNotification(@_) }, [ ['playlist'], ['open', 'newsong'] ]);
|
||||
Slim::Control::Request::subscribe( \&onStopClear, [ ['playlist'], ['stop', 'clear'] ]);
|
||||
|
||||
Plugins::SqueezeESP32::FirmwareHelper->init();
|
||||
}
|
||||
|
||||
sub onStopClear {
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
<name>PLUGIN_SQUEEZEESP32</name>
|
||||
<description>PLUGIN_SQUEEZEESP32_DESC</description>
|
||||
<module>Plugins::SqueezeESP32::Plugin</module>
|
||||
<version>0.310</version>
|
||||
<version>0.351</version>
|
||||
<creator>Philippe</creator>
|
||||
</extensions>
|
||||
|
||||
@@ -21,6 +21,17 @@ PLUGIN_SQUEEZEESP32_PLAYERSETTINGS
|
||||
DE ESP32 Einstellungen
|
||||
EN ESP32 settings
|
||||
|
||||
PLUGIN_SQUEEZEESP32_FIRMWARE
|
||||
EN Firmware
|
||||
|
||||
PLUGIN_SQUEEZEESP32_FIRMWARE_AVAILABLE
|
||||
DE Es steht eine neue Firmware Version v%s zur Verfügung (aktuell installiert: v%s).
|
||||
EN A new firmware version v%s is available (currently installed: v%s).
|
||||
|
||||
PLUGIN_SQUEEZEESP32_CUSTOM_FIRMWARE_AVAILABLE
|
||||
DE Es steht eine benutzerdefinierte Firmware Version zur Verfügung.
|
||||
EN A custom firmware image is available for installation.
|
||||
|
||||
PLUGIN_SQUEEZEESP32_WIDTH
|
||||
DE Displaybreite
|
||||
EN Screen width
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
<extensions>
|
||||
<plugins>
|
||||
<plugin version="0.200" name="SqueezeESP32" minTarget="7.9" maxTarget="*">
|
||||
<plugin version="0.351" name="SqueezeESP32" minTarget="7.9" maxTarget="*">
|
||||
<link>https://github.com/sle118/squeezelite-esp32</link>
|
||||
<creator>Philippe</creator>
|
||||
<sha>ab2d65f5ba8e73f0f78a1a8650af19ebb1e8e724</sha>
|
||||
<sha>3209d93e2b02c1c9161572977f03c93938272b30</sha>
|
||||
<email>philippe_44@outlook.com</email>
|
||||
<desc lang="EN">SqueezeESP32 additional player id (100)</desc>
|
||||
<url>http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip</url>
|
||||
<desc lang="EN">SqueezeESP32 additional player id (100/101)</desc>
|
||||
<url>http://github.com/sle118/squeezelite-esp32/raw/master-cmake/plugin/SqueezeESP32.zip</url>
|
||||
<title lang="EN">SqueezeESP32</title>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
Reference in New Issue
Block a user