Compare commits

...

48 Commits

Author SHA1 Message Date
philippe44
7d45fb6f8f CRLF hell 2022-09-26 20:01:28 -07:00
philippe44
d76001a023 update plugin link 2022-09-25 11:43:59 -07:00
Philippe G
7951c59d11 Windows CRLF!!! 2021-04-28 17:58:53 -07:00
Philippe G
64d609cc33 remove plugin from master 2021-04-28 17:27:50 -07:00
Philippe G
6b368a7c02 0.310 (download cache + battery) 2021-04-04 15:52:47 -07:00
Philippe G
3159edc26c download using LMS proxy 2021-04-03 21:27:36 -07:00
Philippe G
86cee80393 SHA! 2021-03-04 20:25:19 -08:00
Philippe G
5e11ccbd87 remove equalizer if player is 32 bits 2021-03-04 19:18:58 -08:00
Philippe G
874abb99ed 0.220
Clear artwork when power off
2021-01-24 13:17:18 -08:00
Philippe G
3c418097b8 plugin: sync players artwork & spectrum typo 2020-12-03 20:59:06 -08:00
Philippe G
c576587de4 hires display fixes 2020-12-02 14:05:23 -08:00
philippe44
f06c08cef7 Update README.md 2020-10-31 22:20:20 -07:00
philippe44
6385fe9be4 Update README.md 2020-10-31 22:13:53 -07:00
philippe44
31cb0f5df4 Update README.md 2020-10-31 22:10:38 -07:00
Sébastien
2df24e2463 Update cmake.yml 2020-10-20 09:58:27 -04:00
Philippe G
c844ce01e4 safe spectrum parameter updates (thx @mherger)
I've remove the dclone as I confuses me a bit and I don't think it's needed
2020-10-17 17:00:07 -07:00
philippe44
a883bd97ef Merge pull request #56 from michaelherger/sanitize-prefs
Initialize and sanitize spectrum and artwork prefs
2020-10-17 16:53:03 -07:00
Sébastien
b7932a6407 test workflow 2020-10-16 12:20:48 -04:00
Sébastien
8e8a1ffe3d Create I2S-4MB Build Workflow
Testing the github actions to automate the build with espressif's docker inage
2020-10-16 09:49:55 -04:00
Michael Herger
a35b5204d5 Initialize and sanitize spectrum and artwork prefs. 2020-10-16 00:27:00 +02:00
Philippe G
ee7d2a492c artwork init @ undef 2020-10-10 23:51:43 -07:00
Philippe G
46f89026ed 0.203 2020-10-08 01:01:30 -07:00
Philippe G
3c317b0b86 equalizer fix 2020-10-07 14:48:58 -07:00
Philippe G
8b97e3562d just plugin 2020-10-06 22:43:25 -07:00
philippe44
57ae93447c Update README.md 2020-10-06 08:33:25 -07:00
Philippe G
29a74b9ca8 tweak dac_controlset timing - release 2020-09-01 16:23:41 -07:00
Philippe G
d95d1cd3db green led fix + i2c timeout - release 2020-09-01 13:41:17 -07:00
Philippe G
8947f2fc75 shidt i2c_addr by one for consistency - release 2020-08-31 15:37:29 -07:00
philippe44
b6c296460c Update README.md 2020-08-31 11:34:04 -07:00
Philippe G
e25c18a070 no mutex needed for polling check - release 2020-08-30 12:29:40 -07:00
Philippe G
8e3fde6f6e faster & simpler solution for stream poll() - release 2020-08-30 11:23:05 -07:00
Philippe G
26d3d99738 mad fix through library + ST77xx memory corruption + add full screen + poll mutex for stream - release 2020-08-29 22:03:17 -07:00
philippe44
ff5fb3a7d1 Update README.md 2020-08-29 15:52:13 -07:00
Philippe G
c3bf7f4a04 better mad sync - release 2020-08-27 16:40:48 -07:00
Philippe G
3df6568b6a dual vertical VU - release 2020-08-19 00:25:51 -07:00
Philippe G
ca97b8045e GPIO 7 might be used by error - release 2020-08-18 18:11:33 -07:00
Philippe G
5e8a3fd755 better synchronize bass/treble & equalizer
remove large fonts - release

finalize equalizer/tone interaction
2020-08-17 21:01:57 -07:00
philippe44
92fffb7635 Update README.md 2020-08-15 23:44:58 -07:00
philippe44
5e372860c8 Update README.md 2020-08-15 23:43:27 -07:00
philippe44
36c3f0eb49 Update README.md 2020-08-15 23:38:45 -07:00
Philippe G
4ecc9da6d3 take prefs from the correct namespace 2020-08-15 19:25:27 -07:00
Philippe G
45e0dd38df update this $%*!§* plugin for good, this time 2020-08-15 19:09:23 -07:00
Philippe G
3922851129 update plugin! 2020-08-15 19:07:47 -07:00
Philippe G
f08bde8d48 headset, bass/treble, battery from LMS - release 2020-08-15 18:45:59 -07:00
philippe44
a6b57604d3 Update README.md 2020-08-15 16:05:16 -07:00
Philippe G
ca33ff4ba9 fix DAC defaults in KConfig 2020-08-14 10:56:39 -07:00
Philippe G
8b55fa3986 DAC I2S default were wrong - release 2020-08-14 09:23:53 -07:00
Philippe G
e04a631665 typo in config - release 2020-08-13 14:28:11 -07:00
34 changed files with 351 additions and 958 deletions

30
.github/workflows/I2S-4MBFlash.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
# This is a basic workflow to help you get started with Actions
name: I2S-4MBFlash
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
branches: [ master-cmake ]
pull_request:
branches: [ master-cmake ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Cache build
id: cache-build
uses: actions/cache@v1
with:
path: ${{github.workspace}}/build
key: ${{ runner.os }}-I2S-4MBFlash
- name: Build the firmware
run: |
docker run --rm -v $PWD:/project -w /project espressif/idf:release-v4.0 /bin/bash -c "python3 -m pip install --upgrade pip setuptools wheel && pip3 install protobuf grpcio-tools && cp build-scripts/I2S-4MFlash-sdkconfig.defaults sdkconfig && idf.py build"

20
.github/workflows/cmake.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
# This is a basic workflow to help you get started with Actions
name: Test-workflow
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Set Target
run: echo "TARGET_BUILD_NAME=SqueezeAmp" >> $GITHUB_ENV
- name: Get Target
run: |
echo "Target is ${TARGET_BUILD_NAME}"
env

1
.gitignore vendored
View File

@@ -67,3 +67,4 @@ libs/
/_*
sdkconfig
squeezelite-esp32-jsonblob.zip
*.bak

203
README.md
View File

@@ -1,11 +1,41 @@
# Squeezelite-esp32
## Supported Hardware
## What is this
Squeezelite-esp32 is an audio software suite made to run on espressif's ESP32 wifi (b/g/n) and bluetooth chipset. It offers the following capabilities
- Stream your local music and connect to all major on-line music providers (Spotify, Deezer, Tidal, Qobuz) using [Logitech Media Server - a.k.a LMS](https://forums.slimdevices.com/) and enjoy multi-room audio synchronization. LMS can be extended by numerous plugins and can be controlled using a Web browser or dedicated applications (iPhone, Android). It can also send audio to UPnP, Sonos, ChromeCast and AirPlay speakers/devices.
- Stream from a Bluetooth device (iPhone, Android)
- Stream from an AirPlay controller (iPhone, iTunes ...) and enjoy synchronization multiroom as well (although it's AirPlay 1 only)
Depending on the hardware connected to the ESP32, you can send audio to a local DAC, to SPDIF or to a Bluetooth speaker. The bare minimum required hardware is a WROVER module with 4MB of Flash and 4MB of PSRAM (https://www.espressif.com/en/products/modules/esp32). With that module standalone, just apply power and you can stream to a Bluetooth speaker. You can also send audio to most I2S DAC as well as to SPDIF receivers using just a cable or an optical transducer (you'll need *one* resistor...)
But squeezelite-esp32 is highly extensible and you can add
- Buttons and Rotary Encoder and map/combine them to various functions (play, pause, volume, next ...)
- IR receiver (no pullup resistor or capacitor needed, just the 38kHz receiver)
- Monochrome, GrayScale or Color displays using SPI or I2S (supported drivers are SH1106, SSD1306, SSD1322, SSD1326/7, SSD1351, ST7735 and ST7789).
Other features include
- Resampling
- 10-bands equalizer
- Automatic initial setup using any WiFi device
- Full web interface for further configuration/management
- Firmware over-the-air update
## 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.
### 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.)
Please note that when sending to a Bluetooth speaker, then resampling *must* be enabled (using -R) option if you want to send audio with rate other than 44.1kHz. Similarly, when using SPDIF, only 44.1kHz and 48kHz are supported so you might have to enable resampling as well. If you connect a DAC, choice will depends on its capabilities. See below for more details.
Most DAC will work out-of-the-box with simply an I2S connection, but some require specific commands to be sent using I2C. See DAC option below to understand how to send these dedicated commands. There is build-in support for TAS575x, TAS5780, TAS5713 and AC101 DAC.
### SqueezeAMP
Works with the SqueezeAMP see [here](https://forums.slimdevices.com/showthread.php?110926-pre-ANNOUNCE-SqueezeAMP-and-SqueezeliteESP32) and [here](https://github.com/philippe44/SqueezeAMP).
This is the main hardware companion of Squeezelite-esp32 and has been developped together. Details on capabilities can be found [here](https://forums.slimdevices.com/showthread.php?110926-pre-ANNOUNCE-SqueezeAMP-and-SqueezeliteESP32) and [here](https://github.com/philippe44/SqueezeAMP).
if you want to rebuild, use the `squeezelite-esp32-SqueezeAmp-sdkconfig.defaults` configuration file.
NB: You can use the pre-build binaries SqueezeAMP4MBFlash/SqueezeAMP8MBFlash which has all the hardware I/O set properly. You can also use the generic binary I2S4MBFlash in which case the NVS parameters shall be set to get the exact same behavior
NB: You can use the pre-build binaries SqueezeAMP4MBFlash which has all the hardware I/O set properly. You can also use the generic binary I2S4MBFlash in which case the NVS parameters shall be set to get the exact same behavior
- set_GPIO: 12=green,13=red,34=jack,2=spkfault
- batt_config: channel=7,scale=20.24
- dac_config: model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0
@@ -84,8 +114,9 @@ The parameter "dac_controlset" allows definition of simple commands to be sent o
poweron: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
poweroff: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ] }
```
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**
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
### SPDIF
The NVS parameter "spdif_config" sets the i2s's gpio needed for SPDIF.
@@ -97,13 +128,15 @@ Leave it blank to disable SPDIF usage, you can also define them at compile time
```
bck=<gpio>,ws=<gpio>,do=<gpio>
```
NB: For well-known configuration, this is ignored
### Display
The NVS parameter "display_config" sets the parameters for an optional display. Syntax is
```
I2C,width=<pixels>,height=<pixels>[address=<i2c_address>][,HFlip][,VFlip][driver=SSD1306|SSD1326[:1|4]|SSD1327|SH1106]
SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,back=<gpio>][,speed=<speed>][,HFlip][,VFlip][driver=SSD1306|SSD1322|SSD1326[:1|4]|SSD1327|SH1106|SSD1675|ST7735|ST7789[,rotate]]
I2C,width=<pixels>,height=<pixels>[address=<i2c_address>][,reset=<gpio>][,HFlip][,VFlip][driver=SSD1306|SSD1326[:1|4]|SSD1327|SH1106]
SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,back=<gpio>][,reset=<gpio>][,speed=<speed>][,HFlip][,VFlip][driver=SSD1306|SSD1322|SSD1326[:1|4]|SSD1327|SH1106|SSD1675|ST7735|ST7789[,rotate]]
```
- back: a LED backlight used by some older devices (ST7735). It is PWM controlled for brightness
- reset: some display have a reset pin that is should normally be pulled up if unused
- VFlip and HFlip are optional can be used to change display orientation
- rotate: for non-square *drivers*, move to portrait mode. Note that *width* and *height* must be inverted then
- Default speed is 8000000 (8MHz) but SPI can work up to 26MHz or even 40MHz
@@ -156,12 +189,13 @@ Syntax is:
```
<gpio>=Vcc|GND|amp[:1|0]|ir|jack[:0|1]|green[:0|1]|red[:0|1]|spkfault[:0|1][,<repeated sequence for next GPIO>]
```
You can define the defaults for jack, spkfault leds at compile time but nvs parameter takes precedence except for SqueezeAMP where these are forced at runtime.
You can define the defaults for jack, spkfault leds at compile time but nvs parameter takes precedence except for well-known configurations where these are forced at runtime.
### LED
See §**set_GPIO** for how to set the green and red LEDs. In addition, their brightness can be controlled using the "led_brigthness" parameter. The syntax is
```
[green=0..100][,red=0..100]
```
NB: For well-known configuration, this is ignored
### Rotary Encoder
One rotary encoder is supported, quadrature shift with press. Such encoders usually have 2 pins for encoders (A and B), and common C that must be set to ground and an optional SW pin for press. A, B and SW must be pulled up, so automatic pull-up is provided by ESP32, but you can add your own resistors. A bit of filtering on A and B (~470nF) helps for debouncing which is not made by software.
@@ -278,12 +312,67 @@ There is no good or bad option, it's your choice. Use the NVS parameter "lms_ctr
### 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
```
channel=0..7,scale=<scale>
channel=0..7,scale=<scale>,cells=<2|3>
```
NB: Set parameter to empty to disable battery reading
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
## Setup WiFi
- Boot the esp, look for a new wifi access point showing up and connect to it. Default build ssid and passwords are "squeezelite"/"squeezelite".
- Once connected, navigate to 192.168.4.1
- Wait for the list of access points visible from the device to populate in the web page.
- Choose an access point and enter any credential as needed
- Once connection is established, note down the address the device received; this is the address you will use to configure it going forward
## Setup squeezelite command line (optional)
At this point, the device should have disabled its built-in access point and should be connected to a known WiFi network.
- navigate to the address that was noted in step #1
- Using the list of predefined options, choose the mode in which you want squeezelite to start
- Generate the command
- Add or change any additional command line option (for example player name, etc)
- Activate squeezelite execution: this tells the device to automatiaclly run the command at start
- Update the configuration
- click on the "start toggle" button. This will force a reboot.
- The toggle switch should be set to 'ON' to ensure that squeezelite is active after booting (you might have to fiddle with it a few times)
- You can enable accessto NVS parameters under 'credits'
## Monitor
In addition of the esp-idf serial link monitor option, you can also enable a telnet server (see NVS parameters) where you'll have access to a ton of logs of what's happening inside the WROVER.
## Update Squeezelite
- From the firmware tab, click on "Check for Updates"
- Look for updated binaries
- Select a line
- Click on "Flash!"
- The system will reboot into recovery mode (if not already in that mode), wipe the squeezelite partition and download/flash the selected version
- You can choose a local file or have a local webserver
## Recovery
- From the firmware tab, click on the "Recovery" button. This will reboot the ESP32 into recovery, where additional configuration options are available from the NVS editor
## Additional configuration notes (from the Web UI)
The squeezelite options are very similar to the regular Linux ones. Differences are :
- the output is -o ["BT -n '<sinkname>' "] | [I2S]
- if you've compiled with RESAMPLE option, normal soxr options are available using -R [-u <options>]. Note that anything above LQ or MQ will overload the CPU
- if you've used RESAMPLE16, <options> are (b|l|m)[:i], with b = basic linear interpolation, l = 13 taps, m = 21 taps, i = interpolate filter coefficients
For example, so use a BT speaker named MySpeaker, accept audio up to 192kHz and resample everything to 44100 and use 16 bits resample with medium quality, the command line is:
squeezelite -o "BT -n 'BT <sinkname>'" -b 500:2000 -R -u m -Z 192000 -r "44100-44100"
See squeezlite command line, but keys options are
- Z <rate> : tell LMS what is the max sample rate supported before LMS resamples
- R (see above)
- r "<minrate>-<maxrate>"
- C <sec> : set timeout to switch off amp gpio
- W : activate WAV and AIFF header parsing
# Building everything yourself
## Setting up ESP-IDF
### Docker
You can use docker to build squeezelite-esp32
You can use docker to build squeezelite-esp32 (optional)
First you need to build the Docker container:
```
docker build -t esp-idf .
@@ -301,9 +390,11 @@ If you want to use a more recent version of gcc and IDF (4.0 stable), move to cm
You can install IDF manually on Linux or Windows (using the Subsystem for Linux) following the instructions at: https://www.instructables.com/id/ESP32-Development-on-Windows-Subsystem-for-Linux/
And then copying the i2s.c patch file from this repo over to the esp-idf folder
You also need to use esp-dsp recent version or at least make sure you have this patch https://github.com/espressif/esp-dsp/pull/12/commits/8b082c1071497d49346ee6ed55351470c1cb4264
You also need to use esp-dsp recent version or at least make sure you have this patch https://github.com/espressif/esp-dsp/pull/12/commits/8b082c1071497d49346ee6ed55351470c1cb4264. As of this writing (08.2020), espressif has patched esp-dsp so this is no more needed
## Building Squeezelite-esp32
Don't forget the to choose one of the config files in build_scripts/ and rename it sdkconfig.defaults or sdkconfig as many important WiFi/BT options are set there. The codecs libraries will not be rebuilt by these scripts (it's a tedious process - see below)
### Using make (deprecated)
MOST IMPORTANT: create the right default config file
- make defconfig
(Note: You can also copy over config files from the build-scripts folder to ./sdkconfig)
@@ -334,59 +425,30 @@ You can also manually download the recovery & initial boot
```
python ${IDF_PATH}/components/esptool_py/esptool/esptool.py --chip esp32 --port ${ESPPORT} --baud ${ESPBAUD} --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect 0xd000 ./build/ota_data_initial.bin 0x1000 ./build/bootloader/bootloader.bin 0x10000 ./build/recovery.bin 0x8000 ./build/partitions.bin
```
# Configuration
1/ setup WiFi
- Boot the esp, look for a new wifi access point showing up and connect to it. Default build ssid and passwords are "squeezelite"/"squeezelite".
- Once connected, navigate to 192.168.4.1
- Wait for the list of access points visible from the device to populate in the web page.
- Choose an access point and enter any credential as needed
- Once connection is established, note down the address the device received; this is the address you will use to configure it going forward
2/ setup squeezelite command line (optional)
At this point, the device should have disabled its built-in access point and should be connected to a known WiFi network.
- navigate to the address that was noted in step #1
- Using the list of predefined options, hoose the mode in which you want squeezelite to start
- Generate the command
- Add or change any additional command line option (for example player name, etc)
- Activate squeezelite execution: this tells the device to automatiaclly run the command at start
- Update the configuration
- click on the "start toggle" button. This will force a reboot.
- The toggle switch should be set to 'ON' to ensure that squeezelite is active after booting
3/ Updating Squeezelite
- From the firmware tab, click on "Check for Updates"
- Look for updated binaries
- Select a line
- Click on "Flash!"
- The system will reboot into recovery mode (if not already in that mode), wipe the squeezelite partition and download/flash the selected version
3/ Recovery
- From the firmware tab, click on the "Recovery" button. This will reboot the ESP32 into recovery, where additional configuration options are available from the NVS editor
# Additional command line notes, configured from the http configuration
The squeezelite options are very similar to the regular Linux ones. Differences are :
- the output is -o ["BT -n '<sinkname>' "] | [I2S]
- if you've compiled with RESAMPLE option, normal soxr options are available using -R [-u <options>]. Note that anything above LQ or MQ will overload the CPU
- if you've used RESAMPLE16, <options> are (b|l|m)[:i], with b = basic linear interpolation, l = 13 taps, m = 21 taps, i = interpolate filter coefficients
For example, so use a BT speaker named MySpeaker, accept audio up to 192kHz and resample everything to 44100 and use 16 bits resample with medium quality, the command line is:
squeezelite -o "BT -n 'BT <sinkname>'" -b 500:2000 -R -u m -Z 192000 -r "44100-44100"
See squeezlite command line, but keys options are
- Z <rate> : tell LMS what is the max sample rate supported before LMS resamples
- R (see above)
- r "<minrate>-<maxrate>"
## Additional misc notes to do you build
### Using cmake
Create you config using 'idf.py menuconfig' then build binaries using 'idf.py all'. It will build the recovery and the application (squeezelite) itself. See the recommended command to upload everything. Otherwise, if you just want to download squeezelite, do
```
python.exe <idf_path>\components\esptool_py\esptool\esptool.py -p COM<n> -b 921600 --before default_reset --after hard_reset write_flash --flash_mode dio --flash_size detect --flash_freq 80m 0x150000 build\squeezelite.bin
```
Use 'idf monitor' to monitor the application (see esp-idf documentation)
## Additional misc notes to do you build (kitchen sink)
- don't forget to set IDF_PATH, ESPPORT and ESPBAUD
- When initially cloning the repo, make sure you do it recursively. For example:
- git clone --recursive https://github.com/sle118/squeezelite-esp32.git
- 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
- as of this writing, ESP-IDF has a bug int he way the PLL values are calculated for i2s, so you *must* use the i2s.c file in the patch directory
- misc compiler #define
- use no resampling or set RESAMPLE (soxr - but overloads CPU) or set RESAMPLE16 for fast fixed 16 bits resampling
- use LOOPBACK (mandatory)
- use BYTES_PER_FRAME=4 (8 is not fully functionnal)
- LINKALL (mandatory)
- NO_FAAD unless you want to us faad, which currently overloads the CPU
- TREMOR_ONLY (mandatory)
### codecs
- for codecs libraries, add -mlongcalls if you want to rebuild them, but you should not (use the provided ones in codecs/lib). if you really want to rebuild them, open an issue
- libmad, libflac (no esp's version), libvorbis (tremor - not esp's version), alac work
- libfaad does not really support real time, but if you want to try
- libfaad does not really support real time, but if you want to try (but using helixaac is a better option)
- -O3 -DFIXED_POINT -DSMALL_STACK
- change ac_link in configure and case ac_files, remove ''
- compiler but in cfft.c and cffti1, must disable optimization using
@@ -399,21 +461,4 @@ See squeezlite command line, but keys options are
- change ac_files to remove ''
- add DEPS_CFLAGS and DEPS_LIBS to avoid pkg-config to be required
- stack consumption can be very high with some codec variants, so set NONTHREADSAFE_PSEUDOSTACK and GLOBAL_STACK_SIZE=32000 and unset VAR_ARRAYS in config.h
- better use helixaac
- set IDF_PATH=/home/esp-idf
- set ESPPORT=COM9
- update flash partition size
- other compiler #define
- use no resampling or set RESAMPLE (soxr) or set RESAMPLE16 for fast fixed 16 bits resampling
- use LOOPBACK (mandatory)
- use BYTES_PER_FRAME=4 (8 is not fully functionnal)
- LINKALL (mandatory)
- NO_FAAD unless you want to us faad, which currently overloads the CPU
- TREMOR_ONLY (mandatory)
- When initially cloning the repo, make sure you do it recursively. For example:
- git clone --recursive https://github.com/sle118/squeezelite-esp32.git
- 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
- libmad has been patched to avoid using a lot of stack and is not provided here. There is an issue with sync detection in 1.15.1b from where the original stack patch was done but since a few fixes have been made wrt sync detection. This 1.15.1b-10 found on debian fixes the issue where mad thinks it has reached sync but has not and so returns a wrong sample rate. It comes at the expense of 8KB (!) of code where a simple check in squeezelite/mad.c that next_frame[0] is 0xff and next_frame[1] & 0xf0 is 0xf0 does the trick ...

View File

@@ -26,7 +26,7 @@ CONFIG_SPKFAULT_GPIO=-1
CONFIG_SPKFAULT_GPIO_LEVEL=0
CONFIG_BAT_CHANNEL=-1
CONFIG_BAT_SCALE="0"
CONFIG_SDIF_NUM=0
CONFIG_SPDIF_NUM=0
CONFIG_SPDIF_BCK_IO=27
CONFIG_SPDIF_WS_IO=26
CONFIG_SPDIF_DO_IO=-1
@@ -116,10 +116,6 @@ CONFIG_INCLUDE_VORBIS=y
CONFIG_INCLUDE_ALAC=y
CONFIG_A1S=y
CONFIG_SDIF_NUM=0
CONFIG_SPDIF_BCK_IO=-1
CONFIG_SPDIF_WS_IO=-1
CONFIG_SPDIF_DO_IO=-1
CONFIG_A2DP_SINK_NAME="SMSL BT4.2"
CONFIG_A2DP_DEV_NAME="Squeezelite"

View File

@@ -25,13 +25,13 @@ CONFIG_SPKFAULT_GPIO_LEVEL=0
CONFIG_BAT_CHANNEL=-1
CONFIG_BAT_SCALE="0"
CONFIG_I2S_NUM=0
CONFIG_I2S_BCK_IO=33
CONFIG_I2S_WS_IO=25
CONFIG_I2S_DO_IO=32
CONFIG_I2S_BCK_IO=-1
CONFIG_I2S_WS_IO=-1
CONFIG_I2S_DO_IO=-1
CONFIG_I2S_DI_IO=-1
CONFIG_SDIF_NUM=0
CONFIG_SPDIF_BCK_IO=33
CONFIG_SPDIF_WS_IO=25
CONFIG_SPDIF_NUM=0
CONFIG_SPDIF_BCK_IO=-1
CONFIG_SPDIF_WS_IO=-1
CONFIG_SPDIF_DO_IO=-1
CONFIG_MUTE_GPIO=-1
CONFIG_MUTE_GPIO_LEVEL=-1
@@ -121,11 +121,6 @@ CONFIG_INCLUDE_ALAC=y
CONFIG_BASIC_I2C_BT=y
CONFIG_SDIF_NUM=0
CONFIG_SPDIF_BCK_IO=-1
CONFIG_SPDIF_WS_IO=-1
CONFIG_SPDIF_DO_IO=-1
CONFIG_A2DP_SINK_NAME="SMSL BT4.2"
CONFIG_A2DP_DEV_NAME="Squeezelite"
CONFIG_A2DP_CONTROL_DELAY_MS=500

View File

@@ -29,7 +29,7 @@ CONFIG_SPKFAULT_GPIO_LEVEL=0
CONFIG_BAT_CHANNEL=7
CONFIG_BAT_SCALE="20.24"
CONFIG_I2S_NUM=0
CONFIG_SDIF_NUM=0
CONFIG_SPDIF_NUM=0
CONFIG_SPDIF_CONFIG="bck=33,ws=25,do=15"
CONFIG_DAC_CONFIG="model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0"
CONFIG_IDF_TARGET_ESP32=y

View File

@@ -29,7 +29,7 @@ CONFIG_SPKFAULT_GPIO_LEVEL=0
CONFIG_BAT_CHANNEL=7
CONFIG_BAT_SCALE="20.24"
CONFIG_I2S_NUM=0
CONFIG_SDIF_NUM=0
CONFIG_SPDIF_NUM=0
CONFIG_SPDIF_CONFIG="bck=33,ws=25,do=15"
CONFIG_DAC_CONFIG="model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14"
CONFIG_MUTE_GPIO_LEVEL=-1

Binary file not shown.

View File

@@ -90,7 +90,7 @@ static void Update16( struct GDS_Device* Device ) {
for (int i = FirstRow; i <= LastRow; i++) {
memcpy(optr, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 2, ChunkSize);
optr += ChunkSize;
if (optr - Private->iRAM < PAGE_BLOCK && i < LastRow) continue;
if (optr - Private->iRAM <= (PAGE_BLOCK - ChunkSize) && i < LastRow) continue;
Device->WriteData(Device, Private->iRAM, optr - Private->iRAM);
optr = Private->iRAM;
}
@@ -157,7 +157,7 @@ static void Update24( struct GDS_Device* Device ) {
for (int i = FirstRow; i <= LastRow; i++) {
memcpy(optr, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 3, ChunkSize);
optr += ChunkSize;
if (optr - Private->iRAM < PAGE_BLOCK && i < LastRow) continue;
if (optr - Private->iRAM <= (PAGE_BLOCK - ChunkSize) && i < LastRow) continue;
Device->WriteData(Device, Private->iRAM, optr - Private->iRAM);
optr = Private->iRAM;
}

View File

@@ -35,12 +35,20 @@ static const struct GDS_FontDef *GuessFont( struct GDS_Device *Device, int FontT
case GDS_FONT_MEDIUM:
default:
return &Font_droid_sans_fallback_15x17;
#ifdef USE_LARGE_FONTS
case GDS_FONT_LARGE:
return &Font_droid_sans_fallback_24x28;
break;
case GDS_FONT_SEGMENT:
if (Device->Height == 32) return &Font_Tarable7Seg_16x32;
else return &Font_Tarable7Seg_32x64;
#else
case GDS_FONT_LARGE:
case GDS_FONT_SEGMENT:
ESP_LOGW(TAG, "large fonts disabled");
return &Font_droid_sans_fallback_15x17;
break;
#endif
}
}

View File

@@ -35,7 +35,7 @@ static bool I2CDefaultWriteData( struct GDS_Device* Device, const uint8_t* Data,
bool GDS_I2CInit( int PortNumber, int SDA, int SCL, int Speed ) {
I2CPortNumber = PortNumber;
I2CWait = pdMS_TO_TICKS( Speed ? Speed / 4000 : 100 );
I2CWait = pdMS_TO_TICKS( Speed ? (250 * 250000) / Speed : 250 );
if (SDA != -1 && SCL != -1) {
i2c_config_t Config = { 0 };

View File

@@ -33,15 +33,27 @@ static struct {
int channel;
float sum, avg, scale;
int count;
int cells;
TimerHandle_t timer;
} battery;
} battery = {
.channel = CONFIG_BAT_CHANNEL,
.cells = 2,
};
/****************************************************************************************
*
*/
int battery_value_svc(void) {
return battery.avg;
return battery.avg;
}
/****************************************************************************************
*
*/
uint8_t battery_level_svc(void) {
// TODO: this is totally incorrect
return battery.avg ? (battery.avg - (3.0 * battery.cells)) / ((4.2 - 3.0) * battery.cells) * 100 : 0;
}
/****************************************************************************************
*
@@ -59,29 +71,30 @@ static void battery_callback(TimerHandle_t xTimer) {
*
*/
void battery_svc_init(void) {
battery.channel = CONFIG_BAT_CHANNEL;
#ifdef CONFIG_BAT_SCALE
battery.scale = atof(CONFIG_BAT_SCALE);
#endif
#ifndef CONFIG_BAT_LOCKED
char *nvs_item = config_alloc_get_default(NVS_TYPE_STR, "bat_config", "n", 0);
if (nvs_item) {
char *p;
char *p;
#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);
#endif
if ((p = strcasestr(nvs_item, "cells")) != NULL) battery.cells = atof(strchr(p, '=') + 1);
free(nvs_item);
}
#endif
if (battery.channel != -1) {
ESP_LOGI(TAG, "Battery measure channel: %u, scale %f", battery.channel, battery.scale);
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(battery.channel, ADC_ATTEN_DB_0);
battery.avg = adc1_get_raw(battery.channel) * battery.scale / 4095.0;
battery.timer = xTimerCreate("battery", BATTERY_TIMER / portTICK_RATE_MS, pdTRUE, NULL, battery_callback);
xTimerStart(battery.timer, portMAX_DELAY);
ESP_LOGI(TAG, "Battery measure channel: %u, scale %f, cells %u, avg %.2fV", battery.channel, battery.scale, battery.cells, battery.avg);
} else {
ESP_LOGI(TAG, "No battery");
}

View File

@@ -17,4 +17,5 @@ extern void (*spkfault_handler_svc)(bool inserted);
extern bool spkfault_svc(void);
extern int battery_value_svc(void);
extern uint8_t battery_level_svc(void);

View File

@@ -585,7 +585,7 @@ void draw_VU(struct GDS_Device * display, const uint8_t *data, int level, int x,
int scale = 8 - GDS_GetDepth(display);
// use "fast" version as we are not beyond screen boundaries
if (visu.rotate) {
if (rotate) {
for (int r = 0; r < width; r++) {
for (int c = VU_HEIGHT; --c >= 0;) {
GDS_DrawPixelFast(display, c + x, r + y, *data++ >> scale);
@@ -594,13 +594,13 @@ void draw_VU(struct GDS_Device * display, const uint8_t *data, int level, int x,
} else {
for (int r = 0; r < width; r++) {
for (int c = 0; c < VU_HEIGHT; c++) {
GDS_DrawPixelFast(display, r + x, c + y, *data++ >> scale);
GDS_DrawPixelFast(display, r + x, c + y, *data++ >> scale);
}
}
}
} else {
// use "fast" version as we are not beyond screen boundaries
if (visu.rotate) {
if (rotate) {
for (int r = 0; r < width; r++) {
for (int c = VU_HEIGHT; --c >= 0;) {
GDS_DrawPixelFast(display, c + x, r + y, grayMap[*data++]);
@@ -609,11 +609,10 @@ void draw_VU(struct GDS_Device * display, const uint8_t *data, int level, int x,
} else {
for (int r = 0; r < width; r++) {
for (int c = 0; c < VU_HEIGHT; c++) {
GDS_DrawPixelFast(display, r + x, c + y, grayMap[*data++]);
GDS_DrawPixelFast(display, r + x, c + y, grayMap[*data++]);
}
}
}
}
// need to manually set dirty flag as DrawPixel does not do it
@@ -629,8 +628,9 @@ static void grfe_handler( u8_t *data, int len) {
scroller.active = false;
// visu has priority when full screen on small screens
if ((visu.mode & VISU_ESP32) && !visu.col && visu.row < displayer.height) {
// 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)) {
xSemaphoreGive(displayer.mutex);
return;
}
@@ -752,8 +752,11 @@ 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);
// on small screen, visu has priority when full screen
if ((visu.mode & VISU_ESP32) && !visu.col && visu.row < displayer.height) return;
// 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)) {
return;
}
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
@@ -796,7 +799,7 @@ static void grfa_handler(u8_t *data, int len) {
// when using full screen visualizer on small screen there is a brief overlay
artwork.enable = (length != 0);
// just a config or an actual artwork
if (length < 32) {
if (artwork.enable) {
@@ -841,8 +844,10 @@ static void grfa_handler(u8_t *data, int len) {
* Update visualization bars
*/
static void visu_update(void) {
// no need to protect against no woning the display as we are playing
if (pthread_mutex_trylock(&visu_export.mutex)) return;
// 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;
@@ -966,9 +971,13 @@ static void visu_update(void) {
}
}
} else if (displayer.width / 2 > 3 * VU_WIDTH / 4) {
int width = visu.rotate ? visu.height : visu.width;
draw_VU(display, vu_bitmap, visu.bars[0].current, 0, visu.row, width / 2, visu.rotate);
draw_VU(display, vu_bitmap, visu.bars[1].current, width / 2, visu.row, width / 2, visu.rotate);
if (visu.rotate) {
draw_VU(display, vu_bitmap, visu.bars[0].current, 0, visu.row, visu.height / 2, visu.rotate);
draw_VU(display, vu_bitmap, visu.bars[1].current, 0, visu.row + visu.height / 2, visu.height / 2, visu.rotate);
} else {
draw_VU(display, vu_bitmap, visu.bars[0].current, 0, visu.row, visu.width / 2, visu.rotate);
draw_VU(display, vu_bitmap, visu.bars[1].current, visu.width / 2, visu.row, visu.width / 2, visu.rotate);
}
} else {
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);

View File

@@ -14,6 +14,7 @@
#include "esp_system.h"
#include "esp_timer.h"
#include "esp_wifi.h"
#include "monitor.h"
mutex_type slimp_mutex;
@@ -60,3 +61,11 @@ u16_t get_RSSI(void) {
if (wifidata.primary != 0) return 100 + wifidata.rssi + 30;
else return 0xffff;
}
u16_t get_plugged(void) {
return jack_inserted_svc() ? PLUG_HEADPHONE : 0;
}
u8_t get_battery(void) {
return (battery_level_svc() * 16) / 100;
}

View File

@@ -63,8 +63,13 @@ extern mutex_type slimp_mutex;
#define LOCK_P mutex_lock(slimp_mutex)
#define UNLOCK_P mutex_unlock(slimp_mutex)
// must provide or define as 0xffff
u16_t get_RSSI(void);
// bitmap of plugs status
#define PLUG_LINE_IN 0x01
#define PLUG_LINE_OUT 0x02
#define PLUG_HEADPHONE 0x04
u16_t get_RSSI(void); // must provide or define as 0xffff
u16_t get_plugged(void); // must provide or define as 0x0
u8_t get_battery(void); // must provide 0..15 or define as 0x0
// to be defined to nothing if you don't want to support these
extern struct visu_export_s {

View File

@@ -128,12 +128,12 @@ static esp_err_t i2c_write_reg(uint8_t reg, uint8_t val) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, i2c_addr | I2C_MASTER_WRITE, I2C_MASTER_NACK);
i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
i2c_master_write_byte(cmd, val, I2C_MASTER_NACK);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_RATE_MS);
ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
if (ret != ESP_OK) {
@@ -153,15 +153,15 @@ static uint8_t i2c_read_reg(uint8_t reg) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, i2c_addr | I2C_MASTER_WRITE, I2C_MASTER_NACK);
i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
i2c_master_start(cmd);
i2c_master_write_byte(cmd, i2c_addr | I2C_MASTER_READ, I2C_MASTER_NACK);
i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_READ, I2C_MASTER_NACK);
i2c_master_read_byte(cmd, &data, I2C_MASTER_NACK);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_RATE_MS);
ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
if (ret != ESP_OK) {

View File

@@ -31,6 +31,7 @@ sure that using rate_delay would fix that
*/
#include "squeezelite.h"
#include "slimproto.h"
#include "esp_pthread.h"
#include "driver/i2s.h"
#include "driver/i2c.h"
@@ -82,6 +83,7 @@ const struct adac_s *adac = &dac_external;
static log_level loglevel;
static bool (*slimp_handler_chain)(u8_t *data, int len);
static bool jack_mutes_amp;
static bool running, isI2SStarted;
static i2s_config_t i2s_config;
@@ -108,6 +110,36 @@ static void (*jack_handler_chain)(bool inserted);
#define I2C_PORT 0
/****************************************************************************************
* AUDO packet handler
*/
static bool handler(u8_t *data, int len){
bool res = true;
if (!strncmp((char*) data, "audo", 4)) {
struct audo_packet *pkt = (struct audo_packet*) data;
// 0 = headphone (internal speakers off), 1 = sub out,
// 2 = always on (internal speakers on), 3 = always off
if (jack_mutes_amp != (pkt->config == 0)) {
jack_mutes_amp = pkt->config == 0;
config_set_value(NVS_TYPE_STR, "jack_mutes_amp", jack_mutes_amp ? "y" : "n");
if (jack_mutes_amp && jack_inserted_svc()) adac->speaker(false);
else adac->speaker(true);
}
LOG_INFO("got AUDO %02x", pkt->config);
} else {
res = false;
}
// chain protocol handlers (bitwise or is fine)
if (*slimp_handler_chain) res |= (*slimp_handler_chain)(data, len);
return res;
}
/****************************************************************************************
* jack insertion handler
*/
@@ -164,6 +196,10 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
int silent_do = -1;
char *p;
esp_err_t res;
// chain SLIMP handlers
slimp_handler_chain = slimp_handler;
slimp_handler = handler;
p = config_alloc_get_default(NVS_TYPE_STR, "jack_mutes_amp", "n", 0);
jack_mutes_amp = (strcmp(p,"1") == 0 ||strcasecmp(p,"y") == 0);
@@ -192,12 +228,17 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
char *dac_config = config_alloc_get_str("dac_config", CONFIG_DAC_CONFIG, "model=i2s,bck=" STR(CONFIG_I2S_BCK_IO)
",ws=" STR(CONFIG_I2S_WS_IO) ",do=" STR(CONFIG_I2S_DO_IO)
",sda=" STR(CONFIG_I2C_SDA) ",scl=" STR(CONFIG_I2C_SCL)
",mute" STR(CONFIG_MUTE_GPIO));
",mute=" STR(CONFIG_MUTE_GPIO));
i2s_pin_config_t i2s_dac_pin, i2s_spdif_pin;
set_i2s_pin(spdif_config, &i2s_spdif_pin);
set_i2s_pin(dac_config, &i2s_dac_pin);
/* BEWARE: i2s. must be patched to set tx_msb_right/rx_msb_right to 1
* or SPDIF will not work. These settings are not accessible from
* userland and I don't know why
*/
// common I2S initialization
i2s_config.mode = I2S_MODE_MASTER | I2S_MODE_TX;
i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
@@ -248,7 +289,7 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
char model[32] = "i2s";
if ((p = strcasestr(dac_config, "model")) != NULL) sscanf(p, "%*[^=]=%31[^,]", model);
if ((p = strcasestr(dac_config, "mute")) != NULL) {
char mute[8];
char mute[8] = "";
sscanf(p, "%*[^=]=%7[^,]", mute);
mute_control.gpio = atoi(mute);
if ((p = strchr(mute, ':')) != NULL) mute_control.active = atoi(p + 1);
@@ -256,7 +297,7 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
for (int i = 0; adac == &dac_external && dac_set[i]; i++) if (strcasestr(dac_set[i]->model, model)) adac = dac_set[i];
res = adac->init(dac_config, I2C_PORT, &i2s_config) ? ESP_OK : ESP_FAIL;
res |= i2s_driver_install(CONFIG_I2S_NUM, &i2s_config, 0, NULL);
res |= i2s_set_pin(CONFIG_I2S_NUM, &i2s_dac_pin);

View File

@@ -190,6 +190,7 @@ static void sendSTAT(const char *event, u32_t server_timestamp) {
packN(&pkt.bytes_received_L, (u64_t)status.stream_bytes & 0xffffffff);
#if EMBEDDED
packn(&pkt.signal_strength, get_RSSI());
packn(&pkt.voltage, (get_battery() << 4) | get_plugged());
#else
pkt.signal_strength = 0xffff;
#endif
@@ -197,7 +198,6 @@ static void sendSTAT(const char *event, u32_t server_timestamp) {
packN(&pkt.output_buffer_size, status.output_size);
packN(&pkt.output_buffer_fullness, status.output_full);
packN(&pkt.elapsed_seconds, ms_played / 1000);
// voltage;
packN(&pkt.elapsed_milliseconds, ms_played);
pkt.server_timestamp = server_timestamp; // keep this is server format - don't unpack/pack
// error_code;

View File

@@ -178,6 +178,12 @@ struct codc_packet {
u8_t pcm_endianness;
};
// initially Boom
struct audo_packet {
char opcode[4];
u8_t config;
};
#ifndef SUN
#pragma pack(pop)
#else

View File

@@ -42,9 +42,21 @@ static log_level loglevel;
static struct buffer buf;
struct buffer *streambuf = &buf;
#define LOCK mutex_lock(streambuf->mutex)
#define UNLOCK mutex_unlock(streambuf->mutex)
#define LOCK mutex_lock(streambuf->mutex)
#define UNLOCK mutex_unlock(streambuf->mutex)
/*
When LMS sends a close/open sequence very quickly, the stream thread might
still be waiting in the poll() on the closed socket. It is never recommended
to have a thread closing a socket used by another thread but it works, as
opposed to an infinite select().
In stream_sock() a new socket is created and full OS will allocate a different
one but on RTOS and simple IP stack, the same might be re-used and that causes
an exception as a thread is already waiting on a newly allocated socket
A simple variable that forces stream_sock() to wait until we are out of poll()
is enough and much faster than a mutex
*/
static bool polling;
static sockfd fd;
struct streamstate stream;
@@ -195,9 +207,12 @@ static void *stream_thread() {
}
UNLOCK;
// no mutex needed - we just want to know if we are inside poll()
polling = true;
if (_poll(ssl, &pollinfo, 100)) {
polling = false;
LOCK;
// check socket has not been closed while in poll
@@ -350,7 +365,7 @@ static void *stream_thread() {
UNLOCK;
} else {
polling = false;
LOG_SDEBUG("poll timeout");
}
}
@@ -438,7 +453,7 @@ void stream_file(const char *header, size_t header_len, unsigned threshold) {
buf_flush(streambuf);
LOCK;
stream.header_len = header_len;
memcpy(stream.header, header, header_len);
*(stream.header+header_len) = '\0';
@@ -473,6 +488,11 @@ void stream_file(const char *header, size_t header_len, unsigned threshold) {
void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, unsigned threshold, bool cont_wait) {
struct sockaddr_in addr;
#if EMBEDDED
// wait till we are not polling anymore
while (polling && running) { usleep(10000); }
#endif
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {

View File

@@ -44,7 +44,7 @@ menu "Squeezelite-ESP32"
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=106,sda=21,scl=22" if TWATCH2020
default "model=I2S,bck=26,ws=25,do=33,i2c=53,sda=21,scl=22" if TWATCH2020
default ""
config SPDIF_CONFIG
string
@@ -97,17 +97,17 @@ menu "Squeezelite-ESP32"
I2S dma channel to use.
config I2S_BCK_IO
int "I2S Bit clock GPIO number. "
default 33
default -1
help
I2S Bit Clock gpio pin to use.
config I2S_WS_IO
int "I2S Word Select GPIO number. "
default 25
default -1
help
I2S Word Select gpio pin to use.
config I2S_DO_IO
int "I2S Data Output GPIO number. "
default 32
default -1
help
I2S data output gpio pin to use.
config I2S_DI_IO

View File

@@ -333,6 +333,9 @@ void register_default_nvs(){
ESP_LOGD(TAG,"Registering default value for key %s", "dac_controlset");
config_set_default(NVS_TYPE_STR, "dac_controlset", "", 0);
ESP_LOGD(TAG,"Registering default value for key %s", "jack_mutes_amp");
config_set_default(NVS_TYPE_STR, "jack_mutes_amp", "n", 0);
ESP_LOGD(TAG,"Registering default value for key %s", "bat_config");
config_set_default(NVS_TYPE_STR, "bat_config", "", 0);
@@ -410,7 +413,7 @@ void app_main()
/* start the wifi manager */
ESP_LOGD(TAG,"Blinking led");
led_blink(LED_GREEN, 250, 250);
led_blink_pushed(LED_GREEN, 250, 250);
if(bypass_wifi_manager){
ESP_LOGW(TAG,"\n\nwifi manager is disabled. Please use wifi commands to connect to your wifi access point.\n\n");

Binary file not shown.

View File

@@ -1,231 +0,0 @@
package Plugins::SqueezeESP32::Graphics;
use strict;
use base qw(Slim::Display::Squeezebox2);
use Slim::Utils::Prefs;
use Slim::Utils::Log;
my $prefs = preferences('plugin.squeezeesp32');
my $log = logger('plugin.squeezeesp32');
my $VISUALIZER_NONE = 0;
my $VISUALIZER_VUMETER = 1;
my $VISUALIZER_SPECTRUM_ANALYZER = 2;
my $VISUALIZER_WAVEFORM = 3;
my $VISUALIZER_VUMETER_ESP32 = 0x11;
my $VISUALIZER_SPECTRUM_ANALYZER_ESP32 = 0x12;
{
#__PACKAGE__->mk_accessor('array', 'modes');
__PACKAGE__->mk_accessor('rw', 'modes');
__PACKAGE__->mk_accessor('rw', qw(vfdmodel));
}
sub new {
my $class = shift;
my $client = shift;
my $display = $class->SUPER::new($client);
my $cprefs = $prefs->client($client);
$cprefs->init( {
width => 128,
small_VU => 15,
spectrum => { scale => 25,
small => { size => 25, band => 5.33 },
full => { band => 8 },
},
}
);
$display->init_accessor(
modes => $display->build_modes,
# Only seems to matter for screensaver and update to decide font. Not
# any value is acceptable, so use Boom value which seems to be best
# compromise
vfdmodel => 'graphic-160x32',
);
return $display;
}
=comment
sub modes {
return \@modes;
}
=cut
sub nmodes {
return scalar($#{shift->modes()});
}
sub displayWidth {
my $display = shift;
my $client = $display->client;
# if we're showing the always-on visualizer & the current buttonmode
# hasn't overridden, then use the playing display mode to index
# into the display width, otherwise, it's fullscreen.
my $mode = 0;
if ( $display->showVisualizer() && !defined($client->modeParam('visu')) ) {
my $cprefs = preferences('server')->client($client);
$mode = $cprefs->get('playingDisplayModes')->[ $cprefs->get('playingDisplayMode') ];
}
if ($display->widthOverride) {
my $artwork = $prefs->client($client)->get('artwork');
if ($artwork->{'enable'} && $artwork->{'y'} < 32 && ($client->isPlaying || $client->isPaused)) {
return $artwork->{x} + ($display->modes->[$mode || 0]{_width} || 0);
} else {
return $display->widthOverride + ($display->modes->[$mode || 0]{_width} || 0);
}
} else {
return $display->modes->[$mode || 0]{width};
}
}
sub brightnessMap {
return (0 .. 5);
}
=comment
sub bytesPerColumn {
return 4;
}
=cut
# I don't think LMS renderer handles properly screens other than 32 pixels. It
# seems that all we get is a 32 pixel-tall data with anything else padded to 0
# i.e. if we try 64 pixels height, bytes 0..3 and 4..7 will contains the same
# pattern than the 32 pixels version, where one would have expected bytes 4..7
# to be empty
sub displayHeight {
return 32;
}
sub build_modes {
my $client = shift->client;
my $cprefs = $prefs->client($client);
my $artwork = $cprefs->get('artwork');
my $disp_width = $cprefs->get('width') || 128;
# if artwork is in main display, reduce width
my $width = ($artwork->{'enable'} && $artwork->{'y'} < 32) ? $artwork->{'x'} : $disp_width;
my $width_low = ($artwork->{'enable'} && ($artwork->{'y'} >= 32 || $disp_width - $artwork->{'x'} > 32)) ? $artwork->{'x'} : $disp_width;
my $small_VU = $cprefs->get('small_VU');
my $spectrum = $cprefs->get('spectrum');
my $small_spectrum_pos = { x => $width - int ($spectrum->{small}->{size} * $width / 100),
width => int ($spectrum->{small}->{size} * $width / 100),
};
my $small_VU_pos = { x => $width - int ($small_VU * $width / 100),
width => int ($small_VU * $width / 100),
};
my @modes = (
# mode 0
{ desc => ['BLANK'],
bar => 0, secs => 0, width => $width,
params => [$VISUALIZER_NONE] },
# mode 1
{ desc => ['PROGRESS_BAR'],
bar => 1, secs => 0, width => $width,
params => [$VISUALIZER_NONE] },
# mode 2
{ desc => ['ELAPSED'],
bar => 0, secs => 1, width => $width,
params => [$VISUALIZER_NONE] },
# mode 3
{ desc => ['ELAPSED', 'AND', 'PROGRESS_BAR'],
bar => 1, secs => 1, width => $width,
params => [$VISUALIZER_NONE] },
# mode 4
{ desc => ['REMAINING'],
bar => 0, secs => -1, width => $width,
params => [$VISUALIZER_NONE] },
# mode 5
{ desc => ['CLOCK'],
bar => 0, secs => 0, width => $width, clock => 1,
params => [$VISUALIZER_NONE] },
# mode 6
{ desc => ['SETUP_SHOWBUFFERFULLNESS'],
bar => 0, secs => 0, width => $width, fullness => 1,
params => [$VISUALIZER_NONE] },
# mode 7
{ desc => ['VISUALIZER_VUMETER_SMALL'],
bar => 0, secs => 0, width => $width, _width => -$small_VU_pos->{'width'},
# extra parameters (width, height, col (< 0 = from right), row (< 0 = from bottom), left_space)
params => [$VISUALIZER_VUMETER_ESP32, $small_VU_pos->{'width'}, 32, $small_VU_pos->{'x'}, 0, 2] },
# mode 8
{ desc => ['VISUALIZER_SPECTRUM_ANALYZER_SMALL'],
bar => 0, secs => 0, width => $width, _width => -$small_spectrum_pos->{'width'},
# extra parameters (width, height, col (< 0 = from right), row (< 0 = from bottom), left_space, #bars, scale)
params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, $small_spectrum_pos->{width}, 32, $small_spectrum_pos->{'x'}, 0, 2, $small_spectrum_pos->{'width'} / $spectrum->{small}->{band}, $spectrum->{scale}] },
# mode 9
{ desc => ['VISUALIZER_VUMETER'],
bar => 0, secs => 0, width => $width,
params => [$VISUALIZER_VUMETER_ESP32, $width_low, 0] },
# mode 10
{ desc => ['VISUALIZER_ANALOG_VUMETER'],
bar => 0, secs => 0, width => $width,
params => [$VISUALIZER_VUMETER_ESP32, $width_low, 1] },
# mode 11
{ desc => ['VISUALIZER_SPECTRUM_ANALYZER'],
bar => 0, secs => 0, width => $width,
# extra parameters (bars)
params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, $width_low, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },
);
my @extra = (
# mode E1
{ desc => ['VISUALIZER_VUMETER', 'AND', 'ELAPSED'],
bar => 0, secs => 1, width => $width,
params => [$VISUALIZER_VUMETER_ESP32, $width_low, 0] },
# mode E2
{ desc => ['VISUALIZER_ANALOG_VUMETER', 'AND', 'ELAPSED'],
bar => 0, secs => 1, width => $width,
params => [$VISUALIZER_VUMETER_ESP32, $width_low, 1] },
# mode E3
{ desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'ELAPSED'],
bar => 0, secs => 1, width => $width,
# extra parameters (bars)
params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, $width_low, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },
# mode E4
{ desc => ['VISUALIZER_VUMETER', 'AND', 'REMAINING'],
bar => 0, secs => -1, width => $width,
params => [$VISUALIZER_VUMETER_ESP32, $width_low, 0] },
# mode E5
{ desc => ['VISUALIZER_ANALOG_VUMETER', 'AND', 'REMAINING'],
bar => 0, secs => -1, width => $width,
params => [$VISUALIZER_VUMETER_ESP32, $width_low, 1] },
# mode E6
{ desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'REMAINING'],
bar => 0, secs => -1, width => $width,
# extra parameters (bars)
params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, $width_low, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },
# mode E7
{ desc => ['VISUALIZER_VUMETER', 'AND', 'PROGRESS_BAR', 'AND', 'REMAINING'],
bar => 1, secs => -1, width => $width,
params => [$VISUALIZER_VUMETER_ESP32, $width_low, 0] },
# mode E8
{ desc => ['VISUALIZER_ANALOG_VUMETER', 'AND', 'PROGRESS_BAR', 'AND', 'REMAINING'],
bar => 1, secs => -1, width => $width,
params => [$VISUALIZER_VUMETER_ESP32, $width_low, 1] },
# mode E9
{ desc => ['VISUALIZER_SPECTRUM_ANALYZER', 'AND', 'PROGRESS_BAR', 'AND', 'REMAINING'],
bar => 1, secs => -1, width => $width,
# extra parameters (bars)
params => [$VISUALIZER_SPECTRUM_ANALYZER_ESP32, $width_low, int ($width/$spectrum->{full}->{band}), $spectrum->{scale}] },
);
@modes = (@modes, @extra) if $cprefs->get('height') > 32;
return \@modes;
}
1;

View File

@@ -1,104 +0,0 @@
[% PROCESS settings/header.html %]
[% 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">-->
<input type="hidden" name="pref_width" value="[% prefs.pref_width %]">
[% prefs.pref_width %]
[% END %]
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_SMALL_VU" desc="PLUGIN_SQUEEZEESP32_SMALL_VU_DESC" %]
<input type="number" min="10" max= "50" step="5"class="stdedit" name="pref_small_VU" id="small_VU" value="[% prefs.pref_small_VU %]" size="3">
[% END %]
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE" desc="PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE_DESC" %]
<input type="number" min="10" max= "50" step="5" class="stdedit" name="pref_spectrum_scale" id="spectrum_scale" value="[% pref_spectrum.scale %]" size="3">
[% END %]
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM" desc="PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_DESC" %]
[% "PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_SIZE" | string %]&nbsp
<input type="number" min="10" max= "50" step="5"class="stdedit" name="pref_spectrum_small_size" id="spectrum_small_size" value="[% pref_spectrum.small.size %]" size="3">
[% "PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_BAND" | string %]&nbsp
<input type="text" class="stdedit" name="pref_spectrum_small_band" id="spectrum_small_band" value="[% pref_spectrum.small.band %]" size="3">
[% END %]
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND" desc="PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND_DESC" %]
<input type="text" class="stdedit" name="pref_spectrum_full_band" id="spectrum_full_band" value="[% pref_spectrum.full.band %]" size="3">
[% END %]
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_ARTWORK" desc="PLUGIN_SQUEEZEESP32_ARTWORK_DESC" %]
[% "PLUGIN_SQUEEZEESP32_ARTWORK_ENABLE" | string %]&nbsp
<input type="checkbox" name="pref_artwork_enable" [% IF pref_artwork.enable %] checked [% END %]>&nbsp;
[% "PLUGIN_SQUEEZEESP32_ARTWORK_X" | string %]&nbsp
<input type="text" class="stdedit" name="pref_artwork_x" id="artwork_x" value="[% pref_artwork.x %]" size="2">
[% "PLUGIN_SQUEEZEESP32_ARTWORK_Y" | string %]&nbsp
<input type="text" class="stdedit" name="pref_artwork_y" id="artwork_y" value="[% pref_artwork.y %]" size="2">
[% END %]
<hr>
[% END %]
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_EQUALIZER" desc="" %]
<div>[% "PLUGIN_SQUEEZEESP32_EQUALIZER_SAVE" | string %]</div>
[% END %]
<script TYPE="text/javascript">
if (Ext) {
Ext.onReady(function () {
new Ext.util.TaskRunner().start({
run: checkEq,
interval: 1000
});
});
function checkEq() {
var eqValues = [];
this.lastValues = this.lastValues || [];
for (var x = 0; x < 10; x++) {
eqValues[x] = Ext.get('pref_equalizer.' + x).dom.value || 0;
}
if (eqValues.join() != this.lastValues.join()) {
this.lastValues = eqValues;
SqueezeJS.Controller.request({
params: ['[% playerid %]', ['squeezeesp32', 'seteq', eqValues.join()]]
});
}
}
}
</script>
[% WRAPPER settingSection %]
[% WRAPPER settingGroup title='31Hz' desc="" %]
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.0" id="pref_equalizer.0" value="[% pref_equalizer.0 %]" size="2"">
[% END %]
[% WRAPPER settingGroup title='62Hz' desc="" %]
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.1" id="pref_equalizer.1" value="[% pref_equalizer.1 %]" size="2">
[% END %]
[% WRAPPER settingGroup title='125Hz' desc="" %]
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.2" id="pref_equalizer.2" value="[% pref_equalizer.2 %]" size="2">
[% END %]
[% WRAPPER settingGroup title='250Hz' desc="" %]
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.3" id="pref_equalizer.3" value="[% pref_equalizer.3 %]" size="2">
[% END %]
[% WRAPPER settingGroup title='500Hz' desc="" %]
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.4" id="pref_equalizer.4" value="[% pref_equalizer.4 %]" size="2">
[% END %]
[% WRAPPER settingGroup title='1kHz' desc="" %]
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.5" id="pref_equalizer.5" value="[% pref_equalizer.5 %]" size="2">
[% END %]
[% WRAPPER settingGroup title='2kHz' desc="" %]
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.6" id="pref_equalizer.6" value="[% pref_equalizer.6 %]" size="2">
[% END %]
[% WRAPPER settingGroup title='4kHz' desc="" %]
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.7" id="pref_equalizer.7" value="[% pref_equalizer.7 %]" size="2">
[% END %]
[% WRAPPER settingGroup title='8kHz' desc="" %]
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.8" id="pref_equalizer.8" value="[% pref_equalizer.8 %]" size="2">
[% END %]
[% WRAPPER settingGroup title='16kHz' desc="" %]
<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.9" id="pref_equalizer.9" value="[% pref_equalizer.9 %]" size="2">
[% END %]
[% END %]
[% PROCESS settings/footer.html %]

View File

@@ -1,134 +0,0 @@
package Plugins::SqueezeESP32::Player;
use strict;
use base qw(Slim::Player::SqueezePlay);
use Digest::MD5 qw(md5);
use List::Util qw(min);
use Slim::Utils::Log;
use Slim::Utils::Prefs;
my $prefs = preferences('plugin.squeezeesp32');
my $log = logger('plugin.squeezeesp32');
sub model { 'squeezeesp32' }
sub modelName { 'SqueezeESP32' }
sub hasIR { 0 }
sub init {
my $client = shift;
$client->SUPER::init(@_);
$client->config_artwork();
}
# Allow the player to define it's display width (and probably more)
sub playerSettingsFrame {
my $client = shift;
my $data_ref = shift;
my $value;
my $id = unpack('C', $$data_ref);
# New SETD command 0xfe for display width & height
if ($id == 0xfe) {
$value = (unpack('Cn', $$data_ref))[1];
if ($value > 100 && $value < 400) {
$prefs->client($client)->set('width', $value);
my $height = (unpack('Cnn', $$data_ref))[2];
$prefs->client($client)->set('height', $height || 0);
$client->display->modes($client->display->build_modes);
$client->display->widthOverride(1, $value);
$client->update;
main::INFOLOG && $log->is_info && $log->info("Setting player $value" . "x" . "$height for ", $client->name);
}
}
$client->SUPER::playerSettingsFrame($data_ref);
}
sub hasScrolling {
return 1;
}
sub hasIR {
return 1;
}
sub update_artwork {
my $client = shift;
my $cprefs = $prefs->client($client);
my $artwork = $cprefs->get('artwork') || return;
return unless $artwork->{'enable'};
my $s = min($cprefs->get('height') - $artwork->{'y'}, $cprefs->get('width') - $artwork->{'x'});
my $params = { force => shift || 0 };
my $path = 'music/current/cover_' . $s . 'x' . $s . '_o.jpg';
my $body = Slim::Web::Graphics::artworkRequest($client, $path, $params, \&send_artwork, undef, HTTP::Response->new);
send_artwork($client, undef, \$body) if $body;
}
sub send_artwork {
my ($client, $params, $dataref) = @_;
# I'm not sure why we are called so often, so only send when needed
my $md5 = md5($$dataref);
return if $client->pluginData('artwork_md5') eq $md5 && !$params->{'force'};
$client->pluginData('artwork', $dataref);
$client->pluginData('artwork_md5', $md5);
my $artwork = $prefs->client($client)->get('artwork') || {};
my $length = length $$dataref;
my $offset = 0;
$log->info("got resized artwork (length: ", length $$dataref, ")");
my $header = pack('Nnn', $length, $artwork->{'x'}, $artwork->{'y'});
while ($length > 0) {
$length = 1280 if $length > 1280;
$log->info("sending grfa $length");
my $data = $header . pack('N', $offset) . substr( $$dataref, 0, $length, '' );
$client->sendFrame( grfa => \$data );
$offset += $length;
$length = length $$dataref;
}
}
sub clear_artwork {
my ($client, $request) = @_;
my $artwork = $prefs->client($client)->get('artwork');
if ($artwork && $artwork->{'enable'}) {
main::INFOLOG && $log->is_info && $log->info("artwork stop/clear " . $request->getRequestString());
$client->pluginData('artwork_md5', '');
}
}
sub config_artwork {
my ($client) = @_;
if ( my $artwork = $prefs->client($client)->get('artwork') ) {
my $header = pack('Nnn', $artwork->{'enable'}, $artwork->{'x'}, $artwork->{'y'});
$client->sendFrame( grfa => \$header );
}
}
sub reconnect {
my $client = shift;
$client->pluginData('artwork_md5', '');
$client->SUPER::reconnect(@_);
}
1;

View File

@@ -1,90 +0,0 @@
package Plugins::SqueezeESP32::PlayerSettings;
use strict;
use base qw(Slim::Web::Settings);
use List::Util qw(first);
use Slim::Utils::Log;
use Slim::Utils::Prefs;
my $sprefs = preferences('server');
my $prefs = preferences('plugin.squeezeesp32');
my $log = logger('plugin.squeezeesp32');
sub name {
return Slim::Web::HTTP::CSRF->protectName('PLUGIN_SQUEEZEESP32_PLAYERSETTINGS');
}
sub needsClient {
return 1;
}
sub validFor {
my ($class, $client) = @_;
return $client->model eq 'squeezeesp32';
}
sub page {
return Slim::Web::HTTP::CSRF->protectURI('plugins/SqueezeESP32/settings/player.html');
}
sub prefs {
my ($class, $client) = @_;
my @prefs;
push @prefs, qw(width small_VU) if $client->displayWidth;
return ($prefs->client($client), @prefs);
}
sub handler {
my ($class, $client, $paramRef) = @_;
my ($cprefs, @prefs) = $class->prefs($client);
if ($paramRef->{'saveSettings'}) {
if ($client->displayWidth) {
$cprefs->set('small_VU', $paramRef->{'pref_small_VU'} || 15);
my $spectrum = {
scale => $paramRef->{'pref_spectrum_scale'} || 25,
small => { size => $paramRef->{'pref_spectrum_small_size'} || 25,
band => $paramRef->{'pref_spectrum_small_band'} || 5.33 },
full => { band => $paramRef->{'pref_spectrum_full_band'} } || 8,
};
$cprefs->set('spectrum', $spectrum);
my $artwork = {
enable => $paramRef->{'pref_artwork_enable'},
x => $paramRef->{'pref_artwork_x'} || 0,
y => $paramRef->{'pref_artwork_y'} || 0,
};
$cprefs->set('artwork', $artwork);
$client->display->modes($client->display->build_modes);
$client->display->update;
# force update or disable artwork
if ($artwork->{'enable'}) {
$client->update_artwork(1);
} else {
$client->config_artwork();
}
}
my $equalizer = $cprefs->get('equalizer');
for my $i (0 .. $#{$equalizer}) {
$equalizer->[$i] = $paramRef->{"pref_equalizer.$i"} || 0;
}
$cprefs->set('equalizer', $equalizer);
}
if ($client->displayWidth) {
# the Settings super class can't handle anything but scalar values
# we need to populate the $paramRef for the other prefs manually
$paramRef->{'pref_spectrum'} = $cprefs->get('spectrum');
$paramRef->{'pref_artwork'} = $cprefs->get('artwork');
}
$paramRef->{'pref_equalizer'} = $cprefs->get('equalizer');
return $class->SUPER::handler($client, $paramRef);
}
1;

View File

@@ -1,121 +0,0 @@
package Plugins::SqueezeESP32::Plugin;
use strict;
use base qw(Slim::Plugin::Base);
use Slim::Utils::Prefs;
use Slim::Utils::Log;
use Slim::Web::ImageProxy;
my $prefs = preferences('plugin.squeezeesp32');
my $log = Slim::Utils::Log->addLogCategory({
'category' => 'plugin.squeezeesp32',
'defaultLevel' => 'INFO',
'description' => 'PLUGIN_SQUEEZEESP32',
});
# migrate 'eq' pref, as that's a reserved word and could cause problems in the future
$prefs->migrateClient(1, sub {
my ($cprefs, $client) = @_;
$cprefs->set('equalizer', $cprefs->get('eq'));
$cprefs->remove('eq');
1;
});
$prefs->setChange(sub {
send_equalizer($_[2]);
}, 'equalizer');
sub initPlugin {
my $class = shift;
if ( main::WEBUI ) {
require Plugins::SqueezeESP32::PlayerSettings;
Plugins::SqueezeESP32::PlayerSettings->new;
# require Plugins::SqueezeESP32::Settings;
# Plugins::SqueezeESP32::Settings->new;
}
$class->SUPER::initPlugin(@_);
Slim::Networking::Slimproto::addPlayerClass($class, 100, 'squeezeesp32', { client => 'Plugins::SqueezeESP32::Player', display => 'Plugins::SqueezeESP32::Graphics' });
main::INFOLOG && $log->is_info && $log->info("Added class 100 for SqueezeESP32");
# register a command to set the EQ - without saving the values! Send params as single comma separated list of values
Slim::Control::Request::addDispatch(['squeezeesp32', 'seteq', '_eq'], [1, 0, 0, \&setEQ]);
# Note for some forgetful know-it-all: we need to wrap the callback to make it unique. Otherwise subscriptions would overwrite each other.
Slim::Control::Request::subscribe( sub { onNotification(@_) }, [ ['newmetadata'] ] );
Slim::Control::Request::subscribe( sub { onNotification(@_) }, [ ['playlist'], ['open', 'newsong'] ]);
Slim::Control::Request::subscribe( \&onStopClear, [ ['playlist'], ['stop', 'clear'] ]);
# the custom player class is only initialized if it has a display - thus we need to listen to connect events in order to initializes other player prefs
Slim::Control::Request::subscribe( \&onPlayer,[ ['client'], [ 'new', 'reconnect' ] ] );
}
sub onStopClear {
my $request = shift;
my $client = $request->client || return;
if ($client->isa('Plugins::SqueezeESP32::Player')) {
$client->clear_artwork($request);
}
}
sub onPlayer {
my $request = shift;
my $client = $request->client || return;
if ($client->model eq 'squeezeesp32') {
main::INFOLOG && $log->is_info && $log->info("SqueezeESP player connected: " . $client->id);
$prefs->client($client)->init( {
equalizer => [(0) x 10],
} );
send_equalizer($client);
}
}
sub onNotification {
my $request = shift;
my $client = $request->client || return;
if ($client->isa('Plugins::SqueezeESP32::Player')) {
$client->update_artwork();
}
}
sub setEQ {
my $request = shift;
# check this is the correct command.
if ($request->isNotCommand([['squeezeesp32'],['seteq']])) {
$request->setStatusBadDispatch();
return;
}
# get our parameters
my $client = $request->client();
my @eqParams = split(/,/, $request->getParam('_eq') || '');
for (my $x = 0; $x < 10; $x++) {
$eqParams[$x] ||= 0;
}
send_equalizer($client, \@eqParams);
}
sub send_equalizer {
my ($client, $equalizer) = @_;
if ($client->model eq 'squeezeesp32') {
$equalizer ||= $prefs->client($client)->get('equalizer') || [(0) x 10];
my $size = @$equalizer;
my $data = pack("c[$size]", @{$equalizer});
$client->sendFrame( eqlz => \$data );
}
}
1;

View File

@@ -1,12 +0,0 @@
package Plugins::SqueezeESP32::Text;
use strict;
use base qw(Slim::Display::Text);
# we don't want the special Noritake codes
sub vfdmodel {
return 'squeezeslave';
}
1;

View File

@@ -1,15 +0,0 @@
<?xml version='1.0' standalone='yes'?>
<extensions>
<defaultState>enabled</defaultState>
<email>philippe_44@outlook.com</email>
<targetApplication>
<minVersion>7.9</minVersion>
<maxVersion>*</maxVersion>
<id>SlimServer</id>
</targetApplication>
<name>PLUGIN_SQUEEZEESP32</name>
<description>PLUGIN_SQUEEZEESP32_DESC</description>
<module>Plugins::SqueezeESP32::Plugin</module>
<version>0.94</version>
<creator>Philippe</creator>
</extensions>

View File

@@ -1,102 +0,0 @@
WELCOME_TO_SQUEEZEESP32
DE Willkommen bei SqueezeESP32!
EN Welcome to SqueezeESP32
PLUGIN_SQUEEZEESP32
EN SqueezeESP32
PLUGIN_SQUEEZEESP32_BANNER
DE WARNUNG
EN WARNING
PLUGIN_SQUEEZEESP32_BANNER_TEXT
DE Sie müssen LMS neu starten, damit diese Einstellungen aktiv werden
EN You need to restart LMS for these parameters to be taken into account
PLUGIN_SQUEEZEESP32_DESC
DE Konfiguriert eine neue Player ID (100), um Displays an SqueezeESP32 zu unterstützen
EN Adds a new player id (100) to enable display with SqueezeESP32
PLUGIN_SQUEEZEESP32_PLAYERSETTINGS
DE ESP32 Einstellungen
EN ESP32 settings
PLUGIN_SQUEEZEESP32_WIDTH
DE Displaybreite
EN Screen width
PLUGIN_SQUEEZEESP32_WIDTH_DESC
DE Breite des Displays in Pixeln, wie es vom Player angegeben wird
EN Width of the display in pixel as reported by the player
PLUGIN_SQUEEZEESP32_SMALL_VU
DE Kleine VU Grösse
EN Small VU size
PLUGIN_SQUEEZEESP32_SMALL_VU_DESC
DE Prozentsatz des Displays, das für den kleinen VU verwendet wird (rechts ausgerichtet)
EN % of the display used for small VU (right-justified)
PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE
DE Spektrum-Skalierung
EN Spectrum scaling
PLUGIN_SQUEEZEESP32_SPECTRUM_SCALE_DESC
DE Prozentsatz des Spektrums, das in der ersten Hälfte des Bildschirms angezeigt wird. Z.B. 50 bedeutet 50% des Spektrums wird auf dem halben Bildschirm angezeigt.
DE Aber 25 bedeutet, dass nur 25% des Spektrums auf dem halben Bildschirm angezeigt wird.
EN % of Spectrum displayed in first half of the screen. For example, 50 means that 50% of spectrum is displayed in 1/2 of the screen
EN But 25 means that only 25% of spectrum is displayed in 1/2 of the screen, so it's a sort of log
PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM
DE Kleines Spektrum
EN Small spectrum options
PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_DESC
DE <i>Grösse</i>: Prozentsatz des Displays, das für das kleine Spektrum verwendet wird.
DE <br><i>Band-Faktor</i>: die Anzahl Bänder ist die Breite der <b>Spektrumsanzeige</b> dividiert durch diesen Faktor.
EN <i>Size</i>: % of the screen used by small spectrum
EN <br><i>Band factor</i>: number of bands is the width of the <b>spectrum</b> screen divided by this factor
PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_SIZE
DE Grösse
EN Size
PLUGIN_SQUEEZEESP32_SMALL_SPECTRUM_BAND
DE Band-Faktor
EN Band factor
PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND
DE Band-Faktor für ganzes Spektrum
EN Full spectrum band factor
PLUGIN_SQUEEZEESP32_FULL_SPECTRUM_BAND_DESC
DE Die Anzahl Bänder ist die Breite der Anzeige dividiert durch diesen Faktor.
EN The number of bands is the width of the screen divided by this factor
PLUGIN_SQUEEZEESP32_ARTWORK
DE Plattenhüllen
EN Artwork
PLUGIN_SQUEEZEESP32_ARTWORK_DESC
DE Wenn die Y Position kleiner als 32 ist, dann werden Plattenhüllen auf der rechten Seite angezeigt, und x definiert die Startposition.
DE Plattenhüllen werden auf Displays mit weniger als 16 Graustufen in sehr geringer Qualität angezeigt.
EN When Y position is less than 32, then artwork is displayed at the right of the main screen and x defines the starting position
EN Using artwork on less than 16-levels grayscale display if really poor quality
PLUGIN_SQUEEZEESP32_ARTWORK_ENABLE
DE Aktivieren
EN Enable
PLUGIN_SQUEEZEESP32_ARTWORK_X
EN X
PLUGIN_SQUEEZEESP32_ARTWORK_Y
EN Y
PLUGIN_SQUEEZEESP32_EQUALIZER
DE Parametrischer Equalizer
EN Parametric equalizer
PLUGIN_SQUEEZEESP32_EQUALIZER_SAVE
DE Bitte speichern Sie die Equalizer Einstellungen, falls das Gerät diese dauerhaft verwenden soll. Ansonsten werden sie beim nächsten Start zurückgesetzt.
EN Don't forget to save the Equalizer settings if you want them to stick. Otherwise they'll be reset next time you restart the device.

View File

@@ -1,13 +1,13 @@
<?xml version='1.0' standalone='yes'?>
<extensions>
<plugins>
<plugin version="0.94" name="SqueezeESP32" minTarget="7.9" maxTarget="*">
<plugin version="0.353" name="SqueezeESP32" minTarget="7.9" maxTarget="*">
<link>https://github.com/sle118/squeezelite-esp32</link>
<creator>Philippe</creator>
<sha>a9bf10b47d13508ba051e4067cdabc2c283f4824</sha>
<sha>357d715715e7bf10f83ad15bc3fd794fc45e9e5a</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>